Kwantyzacja po treningu

Kwantyzacja po treningu to technika konwersji, która może zmniejszyć rozmiar modelu, jednocześnie poprawiając opóźnienia procesora i akceleratora sprzętowego, przy niewielkim pogorszeniu dokładności modelu. Możesz skwantyzować już wytrenowany model float TensorFlow, konwertując go do formatu TensorFlow Lite za pomocą TensorFlow Lite Converter .

Metody optymalizacji

Do wyboru jest kilka opcji kwantyzacji po treningu. Oto tabela podsumowująca możliwości wyboru i korzyści, jakie zapewniają:

Technika Korzyści Sprzęt komputerowy
Kwantyzacja zakresu dynamicznego 4x mniejsze, 2x-3x przyspieszenie procesor
Pełna kwantyzacja liczb całkowitych 4x mniejsze, 3x+ przyspieszenie Procesor, Edge TPU, mikrokontrolery
Kwantyzacja Float16 2x mniejszy, akceleracja GPU CPU, GPU

Poniższe drzewo decyzyjne może pomóc w ustaleniu, która metoda kwantyzacji po szkoleniu jest najlepsza dla danego przypadku użycia:

opcje optymalizacji po szkoleniu

Kwantyzacja zakresu dynamicznego

Najprostsza forma kwantyzacji po treningu polega na kwantyzacji statycznej tylko wag od liczby zmiennoprzecinkowej do liczby całkowitej, która ma 8-bitową precyzję:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

Wnioskując, wagi są konwertowane z 8-bitowej precyzji na zmiennoprzecinkowe i obliczane przy użyciu jądra zmiennoprzecinkowego. Ta konwersja jest wykonywana raz i buforowana w celu zmniejszenia opóźnień.

Aby jeszcze bardziej poprawić opóźnienia, operatorzy „zakresu dynamicznego” dynamicznie kwantyzują aktywacje w oparciu o ich zakres do 8-bitów i wykonują obliczenia z 8-bitowymi wagami i aktywacjami. Ta optymalizacja zapewnia opóźnienia zbliżone do wnioskowania w pełni stałoprzecinkowego. Jednak dane wyjściowe są nadal przechowywane w postaci zmiennoprzecinkowej, więc przyspieszenie z operacjami w zakresie dynamicznym jest mniejsze niż pełne obliczenie stałoprzecinkowe.

Pełna kwantyzacja liczb całkowitych

Możesz uzyskać dalsze ulepszenia opóźnień, zmniejszenie szczytowego zużycia pamięci i kompatybilność z urządzeniami sprzętowymi lub akceleratorami wykorzystującymi tylko liczby całkowite, upewniając się, że wszystkie obliczenia matematyczne modelu są skwantowane.

Aby uzyskać pełną kwantyzację liczb całkowitych, należy skalibrować lub oszacować zakres, tj. (min., maks.) wszystkich tensorów zmiennoprzecinkowych w modelu. W przeciwieństwie do stałych tensorów, takich jak wagi i obciążenia, zmienne tensory, takie jak dane wejściowe modelu, aktywacje (wyjścia warstw pośrednich) i dane wyjściowe modelu, nie mogą być kalibrowane, chyba że przeprowadzimy kilka cykli wnioskowania. W rezultacie konwerter wymaga reprezentatywnego zestawu danych do ich kalibracji. Ten zbiór danych może być małym podzbiorem (około ~100-500 próbek) danych uczących lub walidacyjnych. Zapoznaj się z funkcją representative_dataset() poniżej.

Od wersji TensorFlow 2.7 można określić reprezentatywny zestaw danych za pomocą podpisu , jak w poniższym przykładzie:

def representative_dataset():
  for data in dataset:
    yield {
      "image": data.image,
      "bias": data.bias,
    }

Jeśli w danym modelu TensorFlow istnieje więcej niż jedna sygnatura, możesz określić wiele zestawów danych, określając klucze sygnatur:

def representative_dataset():
  # Feed data set for the "encode" signature.
  for data in encode_signature_dataset:
    yield (
      "encode", {
        "image": data.image,
        "bias": data.bias,
      }
    )

  # Feed data set for the "decode" signature.
  for data in decode_signature_dataset:
    yield (
      "decode", {
        "image": data.image,
        "hint": data.hint,
      },
    )

Możesz wygenerować reprezentatywny zbiór danych, dostarczając listę tensorów wejściowych:

def representative_dataset():
  for data in tf.data.Dataset.from_tensor_slices((images)).batch(1).take(100):
    yield [tf.dtypes.cast(data, tf.float32)]

Od wersji TensorFlow 2.7 zalecamy stosowanie podejścia opartego na sygnaturach zamiast podejścia opartego na liście tensorów wejściowych, ponieważ kolejność tensorów wejściowych można łatwo odwrócić.

Do celów testowych możesz użyć fikcyjnego zbioru danych w następujący sposób:

def representative_dataset():
    for _ in range(100):
      data = np.random.rand(1, 244, 244, 3)
      yield [data.astype(np.float32)]
 

Liczba całkowita z rezerwą zmiennoprzecinkową (przy użyciu domyślnego wejścia/wyjścia zmiennoprzecinkowego)

Aby w pełni skwantyzować model, ale używaj operatorów zmiennoprzecinkowych, gdy nie mają implementacji liczb całkowitych (aby zapewnić płynną konwersję), wykonaj następujące kroki:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
tflite_quant_model = converter.convert()

Tylko liczby całkowite

Tworzenie modeli wyłącznie z liczbami całkowitymi jest powszechnym przypadkiem użycia dla TensorFlow Lite dla mikrokontrolerów i Coral Edge TPU .

Dodatkowo, aby zapewnić kompatybilność z urządzeniami obsługującymi tylko liczby całkowite (takie jak 8-bitowe mikrokontrolery) i akceleratorami (takim jak Coral Edge TPU), możesz wymusić pełną kwantyzację liczb całkowitych dla wszystkich operacji, w tym wejścia i wyjścia, wykonując następujące kroki:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8  # or tf.uint8
converter.inference_output_type = tf.int8  # or tf.uint8
tflite_quant_model = converter.convert()

Kwantyzacja Float16

Możesz zmniejszyć rozmiar modelu zmiennoprzecinkowego, kwantyzując wagi do float16, standardu IEEE dla 16-bitowych liczb zmiennoprzecinkowych. Aby włączyć kwantyzację wagi float16, wykonaj następujące kroki:

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_quant_model = converter.convert()

Zalety kwantyzacji float16 są następujące:

  • Zmniejsza rozmiar modelu nawet o połowę (ponieważ wszystkie ciężary stają się o połowę mniejsze).
  • Powoduje minimalną utratę dokładności.
  • Obsługuje niektóre delegaty (np. delegat GPU), które mogą działać bezpośrednio na danych float16, co skutkuje szybszym wykonaniem niż obliczenia float32.

Wady kwantyzacji float16 są następujące:

  • Nie zmniejsza latencji tak bardzo, jak kwantyzacja do matematyki stałoprzecinkowej.
  • Domyślnie, skwantowany model float16 „zdekwantyzuje” wartości wag do float32, gdy jest uruchamiany na procesorze. (Zauważ, że delegat GPU nie wykona tej dekwantyzacji, ponieważ może operować na danych typu float16).

Tylko liczby całkowite: 16-bitowe aktywacje z 8-bitowymi wagami (eksperymentalne)

To jest eksperymentalny schemat kwantyzacji. Jest podobny do schematu „tylko liczby całkowite”, ale aktywacje są kwantowane na podstawie ich zakresu do 16-bitów, wagi są kwantowane do 8-bitowej liczby całkowitej, a odchylenie jest kwantowane do 64-bitowej liczby całkowitej. Jest to dalej określane jako kwantyzacja 16x8.

Główną zaletą tej kwantyzacji jest to, że może znacznie poprawić dokładność, ale tylko nieznacznie zwiększyć rozmiar modelu.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8]
tflite_quant_model = converter.convert()

Jeśli kwantyzacja 16x8 nie jest obsługiwana dla niektórych operatorów w modelu, to model nadal może być skwantowany, ale nieobsługiwane operatory są utrzymywane w postaci zmiennoprzecinkowej. Aby to umożliwić, należy dodać następującą opcję do target_spec.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8,
tf.lite.OpsSet.TFLITE_BUILTINS]
tflite_quant_model = converter.convert()

Przykłady przypadków użycia, w których ulepszenia dokładności zapewnione przez ten schemat kwantyzacji obejmują: * super rozdzielczość, * przetwarzanie sygnału audio, takie jak redukcja szumów i formowanie wiązki, * usuwanie szumów obrazu, * rekonstrukcja HDR z pojedynczego obrazu.

Wadą tej kwantyzacji jest:

  • Obecnie wnioskowanie jest zauważalnie wolniejsze niż 8-bitowa pełna liczba całkowita ze względu na brak zoptymalizowanej implementacji jądra.
  • Obecnie jest niekompatybilny z istniejącymi delegatami TFLite z akceleracją sprzętową.

Samouczek dotyczący tego trybu kwantyzacji można znaleźć tutaj .

Dokładność modelu

Ponieważ wagi są skwantowane po uczeniu, może wystąpić utrata dokładności, szczególnie w przypadku mniejszych sieci. Wstępnie wytrenowane, w pełni skwantyzowane modele są dostępne dla określonych sieci w TensorFlow Hub . Ważne jest, aby sprawdzić dokładność modelu skwantowanego, aby zweryfikować, czy jakiekolwiek pogorszenie dokładności mieści się w dopuszczalnych granicach. Istnieją narzędzia do oceny dokładności modelu TensorFlow Lite .

Alternatywnie, jeśli spadek dokładności jest zbyt duży, rozważ skorzystanie z treningu uwzględniającego kwantyzację . Jednak wykonanie tego wymaga modyfikacji podczas uczenia modelu, aby dodać fałszywe węzły kwantyzacji, podczas gdy techniki kwantyzacji po szkoleniu na tej stronie wykorzystują istniejący wstępnie wytrenowany model.

Reprezentacja skwantowanych tensorów

Kwantyzacja 8-bitowa aproksymuje wartości zmiennoprzecinkowe za pomocą następującego wzoru.

\[real\_value = (int8\_value - zero\_point) \times scale\]

Przedstawienie składa się z dwóch głównych części:

  • Wagi na oś (czyli na kanał) lub na tensor reprezentowane przez wartości uzupełnienia do dwóch int8 z zakresu [-127, 127] z punktem zerowym równym 0.

  • Aktywacje/wejścia na tensor reprezentowane przez wartości dopełnienia do dwóch int8 w zakresie [-128, 127], z punktem zerowym w zakresie [-128, 127].

Aby uzyskać szczegółowy widok naszego schematu kwantyzacji, zapoznaj się z naszą specyfikacją kwantyzacji . Producentów sprzętu, którzy chcą podłączyć się do interfejsu delegata TensorFlow Lite, zachęca się do wdrożenia opisanego tam schematu kwantyzacji.