DUB-AI: multi-task модель (тип озвучки + автор-спан + 18+), обучение/инференс/ONNX/сервис, тесты и документация

This commit is contained in:
DUB-AI committed 2026-10-27 16:43:36 +07:00
commit 8dbca3f4a3
2873 files changed
+44479

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
# Интеграция из Rust / TypeScript
Два пути. **А (HTTP)** — самый простой: Python-процесс один раз загружает
модель, любой язык ходит по REST. **Б (ONNX)** — без Python в рантайме:
модель запускается локально в вашем процессе.
| | Путь А: HTTP-сервис | Путь Б: ONNX |
|---|---|---|
| Зависимости рантайма | запущенный Python-процесс | onnxruntime (+ токенизатор) в вашем языке |
| Сегментация HTML | да, на стороне сервиса | нет — в примерах обрабатывается одна дорожка; сегментацию нужно вызвать отдельно или портировать (`html_extract.py` ~60 строк) |
| Канонизация имён | да, на стороне сервиса | по `aliases.json` (простой словарь) |
| Скорость | сеть + инференс (~мс) | локальный инференс (~мс) |
| Кому подходит | сервис/бэкенд | CLI/десктоп/edge, где Python нельзя |
## Путь А: HTTP-сервис
Запуск (из корня проекта):
```bash
python -m src.service --model models/best --port 8000
# или открыто наружу: python -m src.service --host 0.0.0.0 --port 8000
```
API:
* `GET /health` → `{"status": "ok", "model": "...", "device": "...", "classes": [...]}`
* `POST /predict` — ровно одно из полей:
* `{"text": "Перевод: Дублированный | MovieDalen"}` → одна дорожка;
* `{"html": "<html>…вся страница…"}` → все дорожки релиза.
Ответ: `{"tracks": [ …контракт из docs/inference.md… ]}`.
curl:
```bash
curl -X POST http://127.0.0.1:8000/predict -H "Content-Type: application/json" \
-d '{"text": "Перевод: Дублированный | MovieDalen"}'
```
> **Windows/Git Bash:** кириллица в аргументе `-d` может искажаться кодировкой
> консоли — используйте `--data-binary @req.json` с файлом в UTF-8.
### Rust (examples/rust/http_client)
```bash
cd examples/rust/http_client
cargo run -- --text "Перевод: Дублированный | MovieDalen"
cargo run -- --html page.html
DUB_URL=http://host:8000 cargo run -- --text "..."
```
Ядро клиента:
```rust
let resp: PredictResponse = client
.post("http://127.0.0.1:8000/predict")
.json(&serde_json::json!({ "html": page_html }))
.send().await?
.json().await?;
for t in &resp.tracks {
println!("{} — {}{}", t.translation_type,
t.studio.clone().unwrap_or_else(|| "—".into()),
if t.is_18plus { " 18+" } else { "" });
}
```
### TypeScript / Node.js (examples/typescript)
Без зависимостей (Node >= 18, глобальный fetch):
```bash
node examples/typescript/predict.mjs --text "Одноголосый закадровый — Дмитрий Есарев"
node examples/typescript/predict.mjs --html page.html
```
```ts
const res = await fetch("http://127.0.0.1:8000/predict", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: "Одноголосый закадровый — Дмитрий Есарев" }),
});
const { tracks } = await res.json();
```
Типизированная версия — `examples/typescript/predict.ts` (запуск: `npx tsx predict.ts --text "..."`).
## Путь Б: ONNX без Python
Экспорт (из корня проекта, после обучения):
```bash
python -m src.export_onnx # -> models/best-onnx/ + reports/onnx_parity.md
```
Состав `models/best-onnx/`: `model.onnx` (входы `input_ids`,
`attention_mask`, `token_type_ids` int64 [B, seq]; выходы `type_logits`
[B, 14], `bio_logits` [B, seq, 3], `age_logits` [B, 2]), `tokenizer.json`
(HF fast-токенизатор), `label_maps.json` (классы), `aliases.json`
(нормализация имён).
Эквивалентность PyTorch проверяется на всём held-out test (совпадение
типа/спана/18+ ≥ 99%) — отчёт в `reports/onnx_parity.md`.
Клиентская постобработка (одинакова во всех языках):
1. токенизировать дорожку (max_len 128, truncation);
2. прогнать ONNX, взять `argmax`;
3. спан: первый токен с меткой B, затем цепочка I; текст спана — по offsets
токенизатора (или `decode` токенов спана);
4. `is_18plus = argmax(age_logits) == 1`;
5. канонизация автора по `aliases.json`.
### Rust (examples/rust/onnx_local)
```bash
cd examples/rust/onnx_local && cargo build
ORT_DYLIB_PATH="$(find ../../.venv -name onnxruntime.dll | head -1)" \
./target/debug/dub-ai-onnx-local.exe ../../models/best-onnx \
"Перевод: Дублированный | MovieDalen"
```
Крейты: `ort 2.0.0-rc.15` + `tokenizers` (читает `tokenizer.json` как есть).
Библиотека onnxruntime грузится динамически — DLL можно взять из pip-пакета
`onnxruntime` (уже есть в `.venv` проекта) или из официального релиза;
проверено: тип, спан автора и 18+ совпадают с Python-инференсом.
### TypeScript (examples/typescript/onnx_local.mjs)
```bash
cd examples/typescript
npm init -y && npm i onnxruntime-node @huggingface/transformers
node onnx_local.mjs "../../models/best-onnx" "Перевод: Дублированный | MovieDalen"
```
`@huggingface/transformers` используется только как токенизатор;
инференс — `onnxruntime-node`.
## Замечание про сегментацию страниц в пути Б
Сервис (путь А) сам находит все дорожки в HTML. В пути Б примеры принимают
одну дорожку; для полной обработки страницы нужно сначала получить сегменты
(например, один вызов лёгкого HTTP-эндпоинта или порт `html_extract.py` —
это регулярки без зависимостей, кроме BeautifulSoup для HTML).