Cześć, tu zbrek
to moje archiwum z tutorialami, blogiem, moją sztuką i designem, apkami i pluginami
✨ Automatically translated by Claude AI

Hostuj swój model AI po stronie klienta za pomocą Transformers.js i ONNX

Uwaga: ten przewodnik został napisany dla wersji 3.8.1 biblioteki Transformers.js

Czym jest biblioteka @huggingface/transformers

Transformers.js to biblioteka od HuggingFace, „zaprojektowana tak, aby była funkcjonalnie równoważna z biblioteką transformers od Hugging Face”. Możesz ją zainstalować za pomocą npm:

npm i @huggingface/transformers
Czym @xenova/transformers różni się od @huggingface/transformers

Być może spotkałeś się też gdzieś z @xenova/transformers – to starsza, wycofana wersja biblioteki („Transformers.js v2” w przeciwieństwie do „Transformers.js v3”).

Nie obsługuje WebGPU i oczekuje starszego formatu modelu (na przykład scalenia tokenizatora (merges) muszą być w formacie tablicy ciągów znaków (array-of-strings), a nie tablicy tablic ciągów znaków (array-of-arrays-of-strings)).

Czy @huggingface/transformers pozwala hostować aplikację po stronie klienta

Transformers.js pozwala hostować całą aplikację, w tym wymagane pliki WASM oraz model, po stronie klienta.

Jednak ponieważ ta biblioteka została stworzona z myślą o zewnętrznych wywołaniach API, nie zawiera oficjalnego przewodnika na ten temat i na pewno natkniesz się na kilka dziwactw. Ten przewodnik oszczędzi ci konieczności samodzielnego przechodzenia przez proces debugowania.

Jeśli chcesz tylko działający przykład, przejdź do sekcji Zbuduj prostą aplikację Transformers.js po stronie klienta. Zawiera ona polecenia powłoki oraz kod, których potrzebujesz.

Zwróć uwagę, że ONNX Runtime udostępnia w przeglądarce (Web) jedynie swoje API InferenceSession. Nie możesz modyfikować pliku modelu (trenować go, kwantyzować itd.) ze względu na bezpieczeństwo plików w JavaScript.

Jak przekonwertować model PyTorch do formatu ONNX

Możesz użyć biblioteki Optimum dla Pythona, aby przekonwertować swoje modele do formatu ONNX.

pip install "optimum-onnx[onnxruntime]"

Pobierzmy oficjalny model GPT-2 z HuggingFace i jednocześnie wyeksportujmy go do ONNX:

optimum-cli export onnx --model gpt2 gpt2_onnx

Możesz też użyć optimum-cli do eksportowania modeli lokalnych; w takim przypadku musisz określić argument task.

optimum-cli export onnx --model source_folder --task text-generation destination_folder

Argument task przydaje się również, jeśli chcesz użyć modelu ONNX do czegoś innego, niż określono na HuggingFace (na przykład możesz chcieć użyć feature-extraction, aby zobaczyć ukryte stany (hidden states) modelu).

Ostrzeżenia podczas eksportu do ONNX

Prawdopodobnie napotkasz kilka ostrzeżeń podczas eksportowania modelu do ONNX; to normalne i oczekiwane ze względu na różnice między operatorami/środowiskami uruchomieniowymi PyTorch i ONNX.

Domyślnie ONNX stara się zapewnić tolerancję bezwzględną (atol) epsilon równą 1e-05 (więc każde prawdopodobieństwo logitu na pewno nie będzie różnić się od oryginalnego prawdopodobieństwa logitu o więcej niż 1e-05).

Ważna uwaga: Transformers.js oczekuje, że plik model.onnx będzie znajdować się w podfolderze onnx. Po wyeksportowaniu modelu powinieneś utworzyć podfolder onnx wewnątrz swojego destination_folder i przenieść tam swój model.

cd destination_folder
mkdir onnx
mv model.onnx onnx/model.onnx

Jak skwantyzować model za pomocą ONNX

Limit pamięci dla ONNX WASM Runtime wynosi 4 GB, ponieważ używa on adresowania 32-bitowego (jak napisano w ich dokumentacji). Możesz więc chcieć skwantyzować swój model do rozsądniejszego rozmiaru.

Ogólnie rzecz biorąc, lepiej skwantyzować model PRZED wyeksportowaniem go do ONNX (zyskujesz większą elastyczność i bardziej dojrzałe narzędzie). Ale dla porządku, oto jak zrobić to za pomocą ONNX:

python
from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic(model_input="gpt2_to_onnx/model.onnx", model_output="model.int8.onnx", weight_type=QuantType.QInt8)

Możesz też użyć optimum-cli quantize, ale ma to wadę polegającą na konieczności określenia pliku konfiguracyjnego lub konkretnej architektury, pod którą ma być przeprowadzona optymalizacja (co w teorii może powodować problemy w WASM).

Jak skonfigurować Transformers.js i jego backend ONNX

Aby używać modeli lokalnych, musisz włączyć opcję allowLocalModels. Jeśli używasz wyłącznie modeli lokalnych, możesz chcieć wyłączyć allowRemoteModels, aby zapobiec zdalnemu pobieraniu jako opcji zapasowej.

import { env } from '@huggingface/transformers'
env.allowRemoteModels = false;
env.allowLocalModels = true;

Możesz też ustawić zmienną localModelPath (domyślnie “/models/”).

env.localModelPath = "/not_models/"

Umieść folder z modelem w folderze public, aby pliki modelu były dostępne pod adresem:

site.com/your_path/model_name/file.json
site.com/your_path/model_name/onnx/model.onnx

Domyślnie backend ONNX pobiera swoje pliki WASM z sieci dostarczania treści (CDN) HuggingFace. Jeśli chcesz samodzielnie dostarczyć je klientowi, możesz pobrać prekompilowane pliki WASM tutaj: https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1/dist/ – potrzebujesz pliku .wasm oraz pliku .mjs.

Po pobraniu plików WASM musisz ustawić wasmPaths w ONNX:

env.backends.onnx.wasm.wasmPaths = "/wasm/"

lub

env.backends.onnx.wasm.wasmPaths = {
   "mjs": '/wasm/ort-wasm-simd-threaded.jsep.mjs',
   "wasm": '/wasm/ort-wasm-simd-threaded.jsep.wasm'
}
Jak rozwiązać błąd Vite cannot import from public folder

Vite nie pozwoli ONNX importować plików jako modułów z folderu public – w takim przypadku po prostu przenieś wasm do folderu źródłowego i pozwól mu poprawnie zaimportować pliki.

import mjs from './wasm/ort-wasm-simd-threaded.jsep.mjs?url'
import wasm from './wasm/ort-wasm-simd-threaded.jsep.wasm?url'

env.backends.onnx.wasm.wasmPaths = {
	"mjs": mjs,
	"wasm": wasm
}
Jak działa env.localModelPath w Transformers.js

Jeśli podasz w pipeline nazwę, która nie jest prawidłowym identyfikatorem modelu HuggingFace (jak ścieżka zaczynająca się od / lub .), zmienna localModelPath jest ignorowana. Więc w tym przykładzie:

env.localModelPath = "/not_models/"

const tokenizer = await AutoTokenizer.from_pretrained("gpt2")
const input_tokens = await tokenizer("strawberry").input_ids.data
console.log(`How many tokens are there in "strawberry"?\n${input_tokens.length} tokens! Here's a list: ${input_tokens.join(", ")}!`)

const generator = await pipeline("text-generation", "/models/gpt2", {dtype: "fp32", device: "webgpu"});
const result = await generator("Once upon a time,", {do_sample: true, temperature: 2.0});
console.log(result[0].generated_text)

Tokenizator będzie szukał pliku w /not_models/gpt2, ale generator będzie szukał pliku w /models/gpt2, a nie w /not_models/models/gpt2.

Oto fragment biblioteki odpowiedzialny za to działanie:

>>> transformers/src/utils/hub.js:110

const validModelId = isValidHfModelId(path_or_repo_id);

const localPath = validModelId ? pathJoin(env.localModelPath, requestURL) : requestURL;

Zbuduj prostą aplikację Transformers.js po stronie klienta

Krok 1. Zainstaluj bibliotekę Transformers.js

npm init
npm i @huggingface/transformers

Krok 2. Pobierz model AI, wyeksportuj go do formatu ONNX i odpowiednio ustrukturyzuj folder

pip install "optimum-onnx[onnxruntime]"
mkdir -p models
optimum-cli export onnx --model gpt2 models/gpt2
mkdir models/gpt2/onnx
mv models/gpt2/model.onnx models/gpt2/onnx

Krok 3. Pobierz nasze pliki WASM

mkdir wasm
curl https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1/dist/ort-wasm-simd-threaded.jsep.mjs -o wasm/ort-wasm-simd-threaded.jsep.mjs
curl https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1/dist/ort-wasm-simd-threaded.jsep.wasm -o wasm/ort-wasm-simd-threaded.jsep.wasm

Krok 4. Utwórz index.html

>>> index.html

<!doctype html>
<html>
<head>
    <title>Ask my model anything!</title>
    <style>
    body {
        max-width: 600px;
        margin: 40px auto;
    }
    .box {
        border: 1px solid #7a6272;
        padding: 10px;
        margin: 20px;
        display: flex;
    }
    textarea {
        flex: 1;
        resize: none;
    }
</style>
</head>
<body>
    <div class="box">
        <p>Chat history:</p>
        <div id="chat-history"></div>
    </div>
    <div class="box">
        <textarea id="user-input"></textarea>
        <button id="submit-button">Let's chat!</button>
    </div>
    <script type="module" src="/src/main.js"></script>
</body>
</html>

Krok 5. i utwórz src/main.js

>>> src/main.js

import { pipeline, env } from "../node_modules/@huggingface/transformers/dist/transformers.min.js";

const chat_history = document.getElementById("chat-history");
const input = document.getElementById("user-input");
const button = document.getElementById("submit-button");

button.disabled = true;

env.allowLocalModels = true;
env.backends.onnx.wasm.wasmPaths = "/wasm/"

const generator = await pipeline("text-generation", "gpt2", { dtype: "fp32" });

button.disabled = false;

async function talkToAI() {
	const question = input.value;
	if (!question) return;
	input.value = '';
	button.disabled = true
	
	const user_item = document.createElement("div");
	const user_tag = document.createElement("b");
	const user_message = document.createElement("span");
	user_tag.innerText = "User: ";
	user_message.innerText = question;
	user_item.appendChild(user_tag);
	user_item.appendChild(user_message);
	chat_history.appendChild(user_item);
	
	const result = await generator(`User: ${question}\nBot: `, { do_sample: true, return_full_text: false });
	const result_text = result[0].generated_text.split("\n")[0];
	
	const bot_item = document.createElement("div");
	const bot_tag = document.createElement("b");
	const bot_message = document.createElement("span");
	bot_tag.innerText = "Bot: ";
	bot_message.innerText = result_text;
	bot_item.appendChild(bot_tag);
	bot_item.appendChild(bot_message);
	chat_history.appendChild(bot_item);
	
	button.disabled = false;
}

button.addEventListener("click", talkToAI);

Krok 6. Na koniec udostępnij swoją stronę za pomocą prostego http.server

python -m http.server 8000

Twoja strona powinna działać niezależnie od tego, czy jesteś online, czy offline!

Możesz usunąć przestarzałe pliki, takie jak merges.txt, vocab.json i special_tokens_map.json, z folderu modelu. Twoja struktura plików powinna wyglądać tak:

├── node_modules
├── index.html
├── models
│   └── gpt2
│       ├── config.json
│       ├── generation_config.json
│       ├── onnx
│       │   └── model.onnx
│       ├── tokenizer.json
│       ├── tokenizer_config.json
├── package-lock.json
├── package.json
├── src
│   └── main.js
└── wasm
    ├── ort-wasm-simd-threaded.jsep.mjs
    └── ort-wasm-simd-threaded.jsep.wasm