TextMachine project repository (AI translation of literary books). Includes: v2 architecture decisions, MVP plan, research 01-12, polygon experiments 01-03, backend-session revalidation verdict (03-implementation-notes.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
13 KiB
Эксперимент 03. Локальный стенд: скорость, память, качество, интеграция
Дата прогона: 2026-07-04. Статус: первый замер выполнен (ollama; llama.cpp --n-cpu-moe — следующий шаг). Основание: docs/research/06-local-models.md; задача 3 полигона.
Стенд и методика
- Железо: GTX 1070 8GB VRAM; WSL2 (RAM внутри WSL ~19GB из 32).
- Рантайм: ollama 0.30.11 в боевом конфиге владельца (как в
~/vojo-local-up.sh):OLLAMA_KEEP_ALIVE=-1 OLLAMA_FLASH_ATTENTION=1 OLLAMA_KV_CACHE_TYPE=q8_0. llama.cpp (llama-server) на стенде не установлен. - Модели (установлены на стенде):
huihui_ai/qwen3-abliterated:8b(5.0GB),qwen3-vojo:latest(тот же 8b + запечённые сэмплеры Qwen3: temp 0.7, top_p 0.8, top_k 20, repeat_penalty 1.1, num_ctx 16384),huihui_ai/qwen3-abliterated:30b-a3b(MoE 30B, 3B активных, 18GB). Для задачи 5 дотянутbge-m3. - Скрипт:
eval/local_bench.py— 3 фрагмента (~1.4–1.5k знаков: Расёмон ja, Мелос ja, Моление о счастье zh) × перевод на русский через/api/generate(num_ctx 8192, temp 0.3), метрики из ответа ollama, пик VRAM — опрос nvidia-smi. Эталон DeepSeek API не снят (нет ключа) — добавится автоматически при появленииDEEPSEEK_API_KEYвeval/.env. - Грабли окружения, задокументированные по пути: системный прокси перехватывает запросы к 127.0.0.1 (
NO_PROXY=<local>— WinINET-нотация, не работает в curl/urllib/Go) — все локальные вызовы в обход прокси;pkill -fиз inline-команд матчит собственный шелл.
Скорость и память
| Модель | Генерация, tok/s | Prefill, tok/s | Пик VRAM | Время на фрагмент |
|---|---|---|---|---|
| qwen3-abliterated:8b | 26.8–27.3 | 519–556 | ~6.6 GB (целиком в VRAM) | 63–93 с |
| qwen3-vojo (8b, сэмплеры) | 26.5–26.7 | 521–599 | ~6.5 GB | 68–96 с |
| qwen3-abliterated:30b-a3b | 9.9–10.0 | 73–302 | 7.6 GB + эксперты в RAM (mmap) | 164–278 с |
Замечания:
- 8b @ ~27 tok/s совпадает с референсом из vojo-плана (~28 tok/s на этом же железе). Холодная загрузка 8b ~14–20 с (из vojo-замеров), 30b — заметно дольше (первый фрагмент 278 с против 164 с у второго — разница в основном прогрев/подкачка 18GB с диска).
- Замер RAM через MemAvailable недооценивает след 30b: веса подключаются mmap'ом и живут в page cache. Фактически модель занимает почти весь свободный запас WSL; параллельная тяжёлая нагрузка на RAM в момент работы 30b нежелательна.
- Prefill 30b (73–302 tok/s) в разы медленнее 8b (~520) — для «прочитай много, выведи мало» ролей (скрининг, экстракция) 8b выгоднее и по этой оси.
Расхождение с research/06: скорость MoE
Док 06 обещает ~25–30 tok/s для 35B-A3B через llama.cpp --n-cpu-moe на этом классе железа. Фактически ollama даёт только ~10 tok/s на 30B-A3B: её автоматический офлоад не эквивалентен ручному --n-cpu-moe (не держит все эксперты в RAM при закреплении attention/router на GPU). Вывод дока «рантайм — llama-server» подтверждается и по скорости, не только по параллелизму/контролю: следующий шаг — поставить llama.cpp и перемерить тем же скриптом (llama-server совместим по /v1; для замера через /api/generate добавить ветку). До этого 30b-a3b на ollama пригоден для несрочных фоновых ролей.
Второе расхождение — поколение моделей: док 06 рекомендует Qwen3.5-9B / Qwen3.6-35B-A3B (март 2026), на стенде — предыдущее поколение Qwen3 (abliterated). huihui-ai уже публикует Huihui-Qwen3.5-35B-A3B-abliterated — при обновлении стенда качество ниже перемерить.
Качество ja→ru / zh→ru (субъективно, с эталоном DeepSeek API)
Переводы сохранены в eval/data/local_bench/<model>/*.ru.txt. Общая картина обоих размеров: русский текст беглый и связный, но реалии и имена систематически ломаются:
- 8b, «Расёмон»: ворота 羅生門 → «Рождественская дверь» (!), 朱雀大路 прочитана по-китайски («улица Чжуцзянь»); 30b: «Рашимон» / «Чжуцзяньдаи» — лучше, но всё равно мимо традиционной передачи (Расёмон, дорога Судзаку).
- zh, «Моление о счастье»: реалия 送灶 (проводы бога очага) → у 8b «проводили старый год», у 30b — «петухи на кухне» (галлюцинация); заголовок 祝福 у 30b — «Слава».
- 30b местами хуже 8b в грамматике («одна сверчок», «по-временам») при чуть лучшей передаче смысла длинных фраз; на 10 tok/s это качество обходится втрое дороже по времени.
- Мелос (ja, простая динамичная проза) — у обоих приемлемый черновик с мелкими сдвигами смысла («изгнать короля» вместо «убрать/убить», 十里 → «десять лин»).
Эталон DeepSeek API (deepseek-chat, снят 2026-07-04 после пополнения ключа, eval/data/local_bench/deepseek-api/) закрывает вопрос: те же фрагменты за 13–22 с (~2.2–2.9k токенов ≈ $0.0005/фрагмент) переведены с корректными реалиями — «ворота Расёмон… на улице Судзаку», «с которой местами облупилась киноварь» (местные модели дали «Рождественская дверь»/«Рашимон», «улица Чжуцзянь», «красная краска»), заголовок 祝福 — «Новогоднее жертвоприношение» (правильно; локально — пропуск/«Слава»). Разрыв качественный, не градуальный: у API — уровень «черновик, пригодный редактору», у локальных — «черновик, требующий сверки с оригиналом».
Вердикт дока 06 подтверждён эмпирически с эталоном: локалка — «спецслужба», не переводчик. Для финальных ролей перевода непригодна (реалии/имена — ровно то, что убивает доверие читателя); для ролей скрининга «горячести», экстракции терминов, чернового NSFW-перевода под обязательную редактуру, дешёвого гейта — скорость и качество достаточны. Открытый вопрос: перевод NSFW-фрагментов (единственная переводческая роль локалки) — проверить на реальных 18+ фрагментах владельца, когда появятся в корпусе; пока все замеры — SFW-классика.
Расширение пула моделей (обсуждено с владельцем, план): все три чат-модели стенда — абляции одного семейства Qwen3, поэтому не измерена «цена абляции» и не представлено поколение 3.5. Очередь: (1) llama.cpp для 30b-a3b (×3 скорости уже установленной модели), (2) ванильный qwen3:8b — цена абляции на SFW-ролях, (3) Qwen3.5-9B + abliterated-вариант, (4) RuadaptQwen3 (до +100% скорости на ru-выходе — самой тяжёлой статье по эксперименту 01). Shisa V2 12B / Gemma 4 — отложены до методики пилота (задача 4).
Интеграция в бэкенд: что взять из vojo/ai-bot
Конспект локального контура ai-bot (файлы provider_local.go, failover.go, httpllm.go, config.go; план docs/plans/local_llm_backend.md):
- Адаптер: локалка = ещё один OpenAI-совместимый
/v1/chat/completionsбез стриминга; сэмплеры Qwen3 через/v1-слой ollama не пробрасываются (issue #11325) — запекаются в Modelfile (паттерн qwen3-vojo). Для llama-server — наоборот, расширить запрос полямиtop_k/min_p/repeat_penalty(omitempty). - Health-проба:
GET /v1/modelsкаждые 15 с, таймаут 3 с; старт в состоянии «нездорова»; down→up = 1 успешная проба, после сбоя запроса — 2 подряд (анти-флаппинг и защита от «cold-load abort loop»: аборт запроса отменяет загрузку модели). Проба не проверяет резидентность модели — это обязанностьOLLAMA_KEEP_ALIVE=-1; для llama-server есть/health,/slots. - Failover: breaker открывается с одной неудачи (transport/5xx/timeout/429), восстановление только пробами; терминальные 4xx не маскируются облаком; пустой 2xx (thinking съел бюджет токенов) → повтор на облаке без trip. Ретраи транспорта: 3 попытки, backoff 0.5→8 с + джиттер.
- Критично для TextMachine — тайминги: у vojo дедлайн локальной ноги 45 с (под короткие чат-ответы). Наши книжные чанки на локалке занимают 63–278 с — профиль таймаутов нужен свой (лег-дедлайн ~300–600 с по роли/модели) плюс стриминг в интерфейсе клиента (уже запланирован в Р1), иначе фейловер будет ложно ронять здоровую локалку.
max_tokensу ollama покрывает thinking+ответ вместе — для thinking-режимов задавать увеличенный потолок (у vojo 1024; для перевода главы нужно 4–8k) или выключать thinking (reasoning_effort: none).
Следующие шаги
- Поставить llama.cpp (llama-server), перемерить 30b-a3b с
--n-cpu-moe— проверка обещанных 25–30 tok/s (главное условие полезности «тяжёлой» локалки). Снять эталон DeepSeek— сделано (см. выше): разрыв качественный, локалка остаётся «спецслужбой».- Расширить пул по плану выше (ваниль 8b → Qwen3.5 → RuadaptQwen3), NSFW-фрагменты владельца — для проверки единственной переводческой роли локалки.
- Замер bge-m3 (скорость/качество retrieval) — в рамках задачи 5 (мини-бенчмарк эмбеддингов).