Generowanie pakietów przeglądarek zoptymalizowanych pod kątem rozmiaru za pomocą TensorFlow.js

Przegląd

TensorFlow.js 3.0 zapewnia obsługę tworzenia pakietów przeglądarek zoptymalizowanych pod kątem rozmiaru i zorientowanych na produkcję . Inaczej mówiąc, chcemy ułatwić Ci wysyłanie mniejszej liczby JavaScript do przeglądarki.

Ta funkcja jest przeznaczona dla użytkowników korzystających z zastosowań produkcyjnych, którzy szczególnie skorzystaliby na usunięciu bajtów ze swojego ładunku (i dlatego są gotowi włożyć wysiłek, aby to osiągnąć). Aby skorzystać z tej funkcji, powinieneś znać moduły ES , narzędzia do łączenia JavaScript, takie jak pakiety internetowe lub pakiety zbiorcze , oraz koncepcje, takie jak potrząsanie drzewem/eliminacja martwego kodu .

W tym samouczku pokazano, jak utworzyć niestandardowy moduł tensorflow.js, którego można używać z programem pakującym w celu wygenerowania kompilacji zoptymalizowanej pod względem rozmiaru dla programu korzystającego z tensorflow.js.

Terminologia

W kontekście tego dokumentu będziemy używać kilku kluczowych terminów:

Moduły ES - Standardowy system modułów JavaScript . Wprowadzono w ES6/ES2015. Można je zidentyfikować za pomocą zestawień importowych i eksportowych .

Łączenie — pobranie zestawu zasobów JavaScript i grupowanie/łączenie ich w jeden lub więcej zasobów JavaScript, których można używać w przeglądarce. Na tym etapie zwykle powstają końcowe zasoby udostępniane przeglądarce. Aplikacje zazwyczaj wykonują swoje własne pakiety bezpośrednio z transpilowanych źródeł bibliotecznych . Typowe pakiety obejmują pakiet zbiorczy i pakiet internetowy . Końcowym rezultatem łączenia jest tzw. pakiet (lub czasami fragment , jeśli jest podzielony na wiele części).

Potrząsanie drzewem / eliminacja martwego kodu — usunięcie kodu, który nie jest używany w ostatecznej pisemnej aplikacji. Odbywa się to podczas łączenia, zazwyczaj na etapie minimalizacji.

Operacje (Ops) — operacja matematyczna na jednym lub większej liczbie tensorów, która daje jako wynik jeden lub więcej tensorów. Operacje to kod „wysokiego poziomu” i mogą używać innych operacji do definiowania swojej logiki.

Jądro - Specyficzna implementacja opcji powiązanej z konkretnymi możliwościami sprzętowymi. Jądra są „niskiego poziomu” i specyficzne dla backendu. Niektóre operacje mają mapowanie jeden do jednego z op na jądro, podczas gdy inne korzystają z wielu jąder.

Zakres i przypadki użycia

Tylko wnioskowanie w modelach graficznych

Podstawowym przypadkiem użycia, o którym słyszeliśmy od użytkowników w związku z tym i który obsługujemy w tej wersji, jest wnioskowanie z modelami wykresów TensorFlow.js . Jeśli używasz modelu warstw TensorFlow.js , możesz przekonwertować go na format modelu graficznego za pomocą konwertera tfjs . Format modelu wykresu jest bardziej efektywny w przypadku użycia wnioskowania.

Manipulacja tensorem niskiego poziomu za pomocą rdzenia tfjs

Innym przypadkiem użycia, który obsługujemy, są programy, które bezpośrednio korzystają z pakietu @tensorflow/tjfs-core do manipulacji tensorem niższego poziomu.

Nasze podejście do konstrukcji niestandardowych

Nasze podstawowe zasady podczas projektowania tej funkcjonalności obejmują:

  • Maksymalnie wykorzystaj system modułów JavaScript (ESM) i pozwól użytkownikom TensorFlow.js zrobić to samo.
  • Spraw, aby TensorFlow.js był jak najbardziej podatny na wstrząsy za pomocą istniejących programów pakujących (np. pakiet internetowy, pakiet zbiorczy itp.). Dzięki temu użytkownicy mogą korzystać ze wszystkich możliwości tych programów pakujących, w tym takich jak dzielenie kodu.
  • W miarę możliwości należy zachować łatwość obsługi dla użytkowników, którzy nie są tak wrażliwi na rozmiar pakietu . Oznacza to, że kompilacje produkcyjne będą wymagały więcej wysiłku, ponieważ wiele ustawień domyślnych w naszych bibliotekach zapewnia łatwość użycia w porównaniu z kompilacjami zoptymalizowanymi pod względem rozmiaru.

Głównym celem naszego przepływu pracy jest stworzenie niestandardowego modułu JavaScript dla TensorFlow.js, który zawiera tylko funkcje wymagane dla programu, który staramy się optymalizować. W celu przeprowadzenia faktycznej optymalizacji polegamy na istniejących programach pakujących.

Chociaż opieramy się głównie na systemie modułów JavaScript, zapewniamy również niestandardowe narzędzie CLI do obsługi części, które nie są łatwe do określenia za pomocą systemu modułów w kodzie dostępnym dla użytkownika. Dwa przykłady tego to:

  • Specyfikacje modelu przechowywane w plikach model.json
  • Używany przez nas system wysyłki dla specyficznego dla backendu jądra.

To sprawia, że ​​generowanie niestandardowej kompilacji tfjs jest nieco bardziej skomplikowane niż samo wskazywanie pakera na zwykły pakiet @tensorflow/tfjs.

Jak tworzyć niestandardowe pakiety zoptymalizowane pod względem rozmiaru

Krok 1: Określ, jakich jąder używa Twój program

Ten krok pozwala nam określić wszystkie jądra używane przez dowolne uruchomione modele lub kod przetwarzania przed/końcowego, biorąc pod uwagę wybrany backend.

Użyj tf.profile, aby uruchomić części aplikacji korzystające z tensorflow.js i pobrać jądra. Będzie to wyglądać mniej więcej tak

const profileInfo = await tf.profile(() => {
  // You must profile all uses of tf symbols.
  runAllMyTfjsCode();
});

const kernelNames = profileInfo.kernelNames
console.log(kernelNames);

Skopiuj tę listę jąder do schowka, aby przejść do następnego kroku.

Musisz sprofilować kod przy użyciu tych samych backendów, których chcesz używać w swoim pakiecie niestandardowym.

Będziesz musiał powtórzyć ten krok, jeśli zmieni się Twój model lub zmieni się kod przetwarzania przed/końcowego.

Krok 2. Napisz plik konfiguracyjny dla niestandardowego modułu tfjs

Oto przykładowy plik konfiguracyjny.

To wygląda tak:

{
  "kernels": ["Reshape", "_FusedMatMul", "Identity"],
  "backends": [
      "cpu"
  ],
  "models": [
      "./model/model.json"
  ],
  "outputPath": "./custom_tfjs",
  "forwardModeOnly": true
}
  • jądra: lista jąder, które należy uwzględnić w pakiecie. Skopiuj to z wyniku kroku 1.
  • backendy: lista backendów, które chcesz uwzględnić. Prawidłowe opcje to „cpu”, „webgl” i „wasm”.
  • modele: lista plików model.json dla modeli ładowanych do aplikacji. Może być pusta, jeśli program nie używa tfjs_converter do ładowania modelu wykresu.
  • OutputPath: Ścieżka do folderu, w którym należy umieścić wygenerowane moduły.
  • forwardModeOnly: Ustaw tę opcję na false, jeśli chcesz uwzględnić gradienty dla jąder wymienionych wcześniej.

Krok 3. Wygeneruj niestandardowy moduł tfjs

Uruchom narzędzie do kompilacji niestandardowej z plikiem konfiguracyjnym jako argumentem. Aby mieć dostęp do tego narzędzia, musisz mieć zainstalowany pakiet @tensorflow/tfjs .

npx tfjs-custom-module  --config custom_tfjs_config.json

Spowoduje to utworzenie folderu w outputPath z kilkoma nowymi plikami.

Krok 4. Skonfiguruj program pakujący, aby uzyskać alias tfjs do nowego modułu niestandardowego.

W pakietach takich jak webpack i rollup możemy aliasować istniejące odniesienia do modułów tfjs, aby wskazywały na nasze nowo wygenerowane niestandardowe moduły tfjs. Aby uzyskać maksymalne oszczędności w rozmiarze pakietu, należy utworzyć aliasy dla trzech modułów.

Oto fragment tego, jak to wygląda w pakiecie internetowym ( pełny przykład tutaj ):

...

config.resolve = {
  alias: {
    '@tensorflow/tfjs$':
        path.resolve(__dirname, './custom_tfjs/custom_tfjs.js'),
    '@tensorflow/tfjs-core$': path.resolve(
        __dirname, './custom_tfjs/custom_tfjs_core.js'),
    '@tensorflow/tfjs-core/dist/ops/ops_for_converter': path.resolve(
        __dirname, './custom_tfjs/custom_ops_for_converter.js'),
  }
}

...

A oto równoważny fragment kodu dla pakietu zbiorczego ( pełny przykład tutaj ):

import alias from '@rollup/plugin-alias';

...

alias({
  entries: [
    {
      find: /@tensorflow\/tfjs$/,
      replacement: path.resolve(__dirname, './custom_tfjs/custom_tfjs.js'),
    },
    {
      find: /@tensorflow\/tfjs-core$/,
      replacement: path.resolve(__dirname, './custom_tfjs/custom_tfjs_core.js'),
    },
    {
      find: '@tensorflow/tfjs-core/dist/ops/ops_for_converter',
      replacement: path.resolve(__dirname, './custom_tfjs/custom_ops_for_converter.js'),
    },
  ],
}));

...

Jeśli Twój program pakujący nie obsługuje aliasingu modułów, będziesz musiał zmienić instrukcje import , aby zaimportować plik tensorflow.js z wygenerowanego custom_tfjs.js , który został utworzony w kroku 3. Definicje operacji nie zostaną podzielone na drzewa, ale jądra nadal będą drzewiaste -przygotowany w shakerze. Generalnie to jądra wstrząsające drzewami zapewniają największe oszczędności w ostatecznym rozmiarze pakietu.

Jeśli używasz tylko pakietu @tensoflow/tfjs-core, wystarczy utworzyć alias tylko dla tego jednego pakietu.

Krok 5. Stwórz swój pakiet

Uruchom program pakujący (np. webpack lub rollup ), aby utworzyć pakiet. Rozmiar pakietu powinien być mniejszy niż w przypadku uruchomienia programu pakującego bez aliasowania modułów. Możesz także użyć wizualizatorów takich jak ten , aby zobaczyć, co znalazło się w ostatecznym pakiecie.

Krok 6. Przetestuj swoją aplikację

Sprawdź, czy Twoja aplikacja działa zgodnie z oczekiwaniami!