17 KiB
Эксперимент 03. Локальный стенд: скорость, память, качество
Дата: 2026-07-04. Задача 3 полигона. Основание: research/06-local-models.md.
Скрипты: eval/local_bench.py (dense-модели через ollama), eval/llama_moe_bench.sh (MoE через llama.cpp). Сырые переводы: eval/data/local_bench*/.
Итог (TL;DR)
- Вердикт research/06 подтверждён: локалка — «спецслужба», не переводчик. Ни одна из 6 протестированных моделей не передаёт имена собственные и реалии правильно (羅生門 → «Расёмон», 送灶 → «проводы бога очага»), тогда как DeepSeek API берёт их за ~$0.0005/фрагмент. Разрыв качественный, не градуальный. Роли локалки: скрининг «горячести» 18+, извлечение терминов, эмбеддинги, черновой NSFW-перевод под обязательную редактуру — не финальный перевод.
- 8–9B dense — рабочая лошадка стенда: ~24–27 tok/s, влезает в VRAM целиком (~6.6 ГБ). Этого достаточно для «спецслужбных» ролей.
- 30B-A3B (MoE) — потолок ~13.5 tok/s, «Расхождение» с research/06 (обещано 25–30). Узкое место — пропускная способность CPU-RAM, а не рантайм. Годен только для несрочных фоновых ролей (~2.5 мин на главу).
- Абляция (снятие цензуры) — бесплатна: qwen3.5 обычная и abliterated идентичны по скорости и SFW-качеству. Для 18+-ролей берём abliterated без штрафа.
- RuAdapt — быстрый по русскому выходу (−40% токенов), но понимание исходника разрушено → редактор ru-стиля, не переводчик.
- Грабли для бэкенда: у моделей с thinking-режимом (Qwen3.5+, GLM, Gemini) роль переводчика обязана явно управлять reasoning — иначе пустой/обрезанный вывод.
Стенд
- Железо: GTX 1070 8GB VRAM; WSL2, RAM поднят до 26GB (
.wslconfig). - ollama 0.30.11 в боевом конфиге (
OLLAMA_KEEP_ALIVE=-1 OLLAMA_FLASH_ATTENTION=1 OLLAMA_KV_CACHE_TYPE=q8_0); скрипт запуска —eval/ollama-up.sh. - llama.cpp собран с CUDA (детали сборки — приложение А).
- Методика: 3 фрагмента ja/zh→ru (~1.4–1.5k знаков: Расёмон, «Беги, Мелос!», «Моление о счастье»), перевод на русский, temp 0.3; метрики tok/s из ответа ollama, пик VRAM — опрос nvidia-smi. Эталон — DeepSeek API (deepseek-chat) на тех же фрагментах.
- Грабли окружения: системный прокси перехватывает 127.0.0.1 (
NO_PROXY=<local>— WinINET-нотация, не работает в curl/urllib/Go) → локальные вызовы в обход прокси.
Скорость и память
Dense-модели (ollama, thinking off), генерация усреднена по 3 фрагментам:
| Модель | Генерация, tok/s | Prefill, tok/s | Пик VRAM | ток/симв (ru-выход) |
|---|---|---|---|---|
| qwen3:8b (ваниль) | 27.1 | ~520 | 6.6 GB | 0.37 |
| qwen3-abliterated:8b | 27.0 | ~530 | 6.6 GB | 0.37 |
| qwen3-vojo (8b + сэмплеры) | 26.6 | ~550 | 6.5 GB | 0.37 |
| ruadapt-qwen3:8b | 27.0 | ~500 | 6.6 GB | 0.22 |
| qwen3.5:9b | 24.2 | ~518 | ~6.8 GB | 0.28 |
| qwen3.5-abliterated:9b | 24.5 | ~520 | ~6.8 GB | 0.30 |
MoE-модель 30B-A3B (18 ГБ весов, 3B активных) — по рантаймам:
| Рантайм / конфиг | Генерация, tok/s | Prefill, tok/s | VRAM |
|---|---|---|---|
| ollama (авто-офлоад) | 10.0 | 73–302 | 7.6 GB |
llama.cpp --cpu-moe (все эксперты на CPU) |
11.2 | 105 | 2.9 GB |
llama.cpp --n-cpu-moe 36 (12/48 слоёв на GPU) |
12.8 | 171 | 7.3 GB |
llama.cpp --n-cpu-moe 33 (15/48 слоёв на GPU, потолок VRAM) |
13.5 | 208 | ~7.8 GB |
Замечания:
- 8b @ ~27 tok/s совпадает с референсом vojo-плана (~28 tok/s на этом железе). Холодная загрузка 8b ~14–20 с; 30b — заметно дольше (подкачка 18 ГБ с диска).
- Prefill 30b (73–302) в разы медленнее 8b (~520) — для ролей «прочитай много, выведи мало» (скрининг, экстракция) 8b выгоднее и по этой оси.
- След 30b по RAM через MemAvailable недооценён (веса mmap'ятся в page cache); фактически модель занимает почти весь свободный запас WSL.
Расхождение с research/06: скорость MoE
Док 06 обещает 25–30 tok/s для 35B-A3B через llama.cpp --n-cpu-moe. Фактический потолок на GTX 1070 — ~13.5 tok/s (таблица выше). llama.cpp честно лучше ollama (+35% генерация, ×2–3 prefill, полный контроль VRAM), но упирается не в рантайм, а в железо: при a3b каждый токен читает эксперты, и те, что в CPU-RAM (DDR4 ~40 ГБ/с на Pascal-платформе), — узкое место. Перенос слоёв экспертов на GPU (VRAM 2.9→7.8 ГБ) поднял генерацию лишь 11→13.5. Обещанные 25–30 — вероятно, RTX 3060 + DDR5 (процитированный Medium-пост платформу не уточнял).
Следствие для Р4: llama.cpp как рантайм подтверждён, но 30b-a3b на этом железе — только фоновые роли (13.5 tok/s ≈ 2.5 мин на главу вывода). Интерактив и объём — на 8–9B dense или API.
Качество перевода
Все замеры — на SFW-классике (Акутагава, Дадзай, Лу Синь). Общая картина: русский текст беглый и связный, но имена собственные и культурные реалии систематически ломаются — а это ровно то, что убивает доверие читателя.
Контрольная реалия 羅生門 (ворота Расёмон) по моделям:
| Источник | 羅生門 → | Оценка |
|---|---|---|
| DeepSeek API (эталон) | «ворота Расёмон… на улице Судзаку» | верно |
| qwen3.5-abliterated:9b | «Радзёмон» | ближе всех локальных, но мимо |
| qwen3.5:9b | «под Радзёном» | мимо |
| qwen3-abliterated:8b | «Рождественская дверь» (!) | грубая ошибка |
| qwen3:8b (ваниль) | «арка Розенмон» | мимо |
| qwen3-abliterated:30b-a3b | «Рашимон» | мимо |
| ruadapt-qwen3:8b | «дерево с цветами-колокольчиками» | галлюцинация |
Аналогично реалия 送灶 (проводы бога очага) локально либо теряется («проводили старый год»), либо галлюцинирует («петухи на кухне»); DeepSeek даёт «проводы бога очага». Простая динамичная проза («Беги, Мелос!») переводится приемлемо всеми — провалы концентрируются на именах, реалиях, идиомах.
Эталон DeepSeek (~2.2–2.9k токенов ≈ $0.0005/фрагмент, 13–22 с): уровень «черновик, пригодный редактору». Локальные: «черновик, требующий сверки с оригиналом». Разрыв качественный — не закрывается ни размером (30b не лучше 8b), ни поколением (3.5 лишь чуть точнее 3).
Влияние thinking-режима на качество
Гипотеза (владельца): reasoning может починить понимание — реалии, длинные конструкции. Проверка на qwen3.5:9b (ja→ru, «Расёмон»).
Мешает тесный контекст, не «зависание». При num_ctx 8192 (temp 0.3 и 0.6) think уходит в развёрнутый мета-анализ задачи (~7000 токенов рассуждений на английском: «Analyze the Request: Role… Task…»), забивает всё окно и обрывается по length до перевода — response пуст (детектор видел «0 символов»). Рассуждения при этом реально пишутся — просто ollama кладёт их в отдельное поле thinking.
С достаточным контекстом think завершается и улучшает реалии. num_ctx 16384, temp 0.6: 518 с, ~11k токенов рассуждений (39к символов) → перевод получен (2605 симв), done_reason: stop. Качество реалий заметно выше, чем без think:
| Реалия | без think | с think | эталон DeepSeek |
|---|---|---|---|
| 羅生門 | «Радзёмон» | «Рашо-мон» (узнаваемо) | «Расёмон» |
| 朱雀大路 | «улица Чжуцзянь» (кит. чтение) | «улица Сёкаку» (яп. чтение) | «улица Судзаку» |
Reasoning вытащил модель из грубых ошибок (унрекогнайзабл-транслит, китайское чтение кандзи) — гипотеза частично подтвердилась: think трогает реалии. Но: (1) даже с think локаль ниже API («Рашо-мон/Сёкаку» ≠ «Расёмон/Судзаку»); (2) цена запретительна — 8,6 мин и ~11k токенов рассуждений на 1400-знаковый фрагмент (вебновелла 500 глав ≈ 200 ч локального счёта против 15 с/$0.0005 у DeepSeek за более точный результат). Вывод: локально think для перевода нежизнеспособен по латентности.
Проброс гипотезы в облако (новая проверка для бэклога): раз reasoning чинит реалии, включить think на быстрых облачных черновике/редакторе (DeepSeek/GLM, где это секунды) может поднять качество — проверить thinking-ON vs OFF по качеству (не только скорости) на облачном контуре.
Сравнение пула dense-моделей
Выводы из таблицы «Скорость и память» + качества:
- Абляция бесплатна. qwen3.5 vanilla vs abliterated: скорость идентична (24.2 vs 24.5), SFW-качество — тоже (abliterated даже чуть точнее на 羅生門). → для 18+-скрининга/чернового перевода берём abliterated без штрафа. (Доказательство слабое — 3 SFW-фрагмента; на explicit перепроверить.)
- Поколение 3.5 > 3 умеренно: лучше понимает исходник (Радзё против Розен), на ~25% экономнее по токенам (0.28 vs 0.37 ток/симв), ценой −10% скорости.
- RuAdapt: токен-выигрыш подтверждён, качество перевода — нет. 0.22 ток/симв (−40% токенов на тот же русский текст против ваниль-8b — заявленный выигрыш; при равных 27 tok/s это ~40% меньше времени на главу русского вывода). Но 羅生門 → «дерево с колокольчиками», zh-фрагмент — бессвязица: ru-адаптация переучила токенизатор/эмбеддинги и деградировала понимание zh/ja. Роль RuAdapt (если будет) — финальная полировка русского стиля, где исходник уже не нужен, а не перевод.
Следствия для бэкенда
- Роли локалки (подтверждено): скрининг «горячести» 18+ (до отправки в облако), извлечение кандидатов в глоссарий, эмбеддинги, черновой NSFW-перевод под обязательную редактуру, дешёвый судья-гейт. Не финальный перевод.
- Модельный выбор: для понимающих ролей — qwen3.5-abliterated:9b (лучшее качество+свобода при равной цене); для скорости на массовых лёгких задачах — 8b; 30b-a3b — только фоновые несрочные задачи.
- Thinking-режим: роль переводчика на Qwen3.5+/GLM/Gemini обязана явно управлять reasoning (
think:falseили адекватныйnum_predict) — иначе пустой/обрезанный вывод и латентность ×3–5. Найдено дважды: здесь и в refusal-бенчмарке (эксп. 02). - Тайминги vojo: дедлайн локальной ноги 45 с (под чат-ответы) несовместим с книжными чанками (63–278 с). Нужен свой профиль лег-таймаутов (~300–600 с по роли) + стриминг как keep-alive транспорта (уже в Р1). Иначе фейловер ложно роняет здоровую локалку.
- Адаптер: локалка = OpenAI-совместимый
/v1/chat/completions; сэмплеры Qwen3 через/v1-слой ollama не пробрасываются (issue #11325) → запекать в Modelfile (паттерн qwen3-vojo) либо расширить запрос полямиtop_k/min_p/repeat_penaltyдля llama-server.max_tokensу ollama покрывает thinking+ответ вместе. - Health/failover (из vojo, architecture/03-implementation-notes.md): проба
GET /v1/modelsне проверяет резидентность модели (это обязанностьKEEP_ALIVE=-1); для llama-server есть/health,/slots.
Открытые вопросы / следующие шаги
- NSFW-перевод — единственная переводческая роль локалки — ещё не проверен (все замеры SFW). Ждёт explicit-фрагментов владельца.
- Замер bge-m3 (retrieval для банка памяти) — задача 5.
- При выходе abliterated Qwen3.6-35B-A3B — перемерить тяжёлую локалку.
Приложение А. Сборка llama.cpp (CUDA, без root)
/home/ubuntu/llama.cpp/build/bin/llama-server (+llama-cli), commit 2d973636. User-space CUDA toolkit 12.6 в /home/ubuntu/cuda-12.6 (nvcc из .deb-распаковки + cublas из PyPI-wheel, ~450MB), арх sm_61, rpath вшит (LD_LIBRARY_PATH не нужен). CPU-fallback: build-cpu/bin/.
GTX 1070 (Pascal) поддерживается только CUDA 12.x — на CUDA 13 не обновлять (Pascal выпилен).
Команда пересборки: cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_COMPILER=$HOME/cuda-12.6/bin/nvcc -DCMAKE_CUDA_ARCHITECTURES=61 -DCMAKE_BUILD_RPATH=$L -DCMAKE_EXE_LINKER_FLAGS="-Wl,-rpath-link,$L -Wl,-rpath,$L", где L=$HOME/cuda-12.6/targets/x86_64-linux/lib.
Модель 30b для llama.cpp берётся прямо из ollama blob-store (блоб = GGUF; путь через ollama show --modelfile).