110 lines
14 KiB
Markdown
110 lines
14 KiB
Markdown
# Эксперимент 03. Локальный стенд: скорость, память, качество
|
||
|
||
Дата: 2026-07-04. Задача 3 полигона. Основание: [research/06-local-models.md](../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)
|
||
|
||
1. **Вердикт research/06 подтверждён: локалка — «спецслужба», не переводчик.** Ни одна из 6 протестированных моделей не передаёт имена собственные и реалии правильно (羅生門 → «Расёмон», 送灶 → «проводы бога очага»), тогда как DeepSeek API берёт их за ~$0.0005/фрагмент. Разрыв качественный, не градуальный. Роли локалки: скрининг «горячести» 18+, извлечение терминов, эмбеддинги, черновой NSFW-перевод под обязательную редактуру — не финальный перевод.
|
||
2. **8–9B dense — рабочая лошадка стенда: ~24–27 tok/s, влезает в VRAM целиком (~6.6 ГБ).** Этого достаточно для «спецслужбных» ролей.
|
||
3. **30B-A3B (MoE) — потолок ~13.5 tok/s, «Расхождение» с research/06** (обещано 25–30). Узкое место — пропускная способность CPU-RAM, а не рантайм. Годен только для несрочных фоновых ролей (~2.5 мин на главу).
|
||
4. **Абляция (снятие цензуры) — бесплатна**: qwen3.5 обычная и abliterated идентичны по скорости и SFW-качеству. Для 18+-ролей берём abliterated без штрафа.
|
||
5. **RuAdapt — быстрый по русскому выходу (−40% токенов), но понимание исходника разрушено** → редактор ru-стиля, не переводчик.
|
||
6. **Грабли для бэкенда**: у моделей с 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 и abliterated с включённым thinking и достаточным бюджетом вывода (6000 токенов).
|
||
|
||
> _РЕЗУЛЬТАТ БУДЕТ ВПИСАН ПОСЛЕ ПРОГОНА (идёт)._
|
||
|
||
## Сравнение пула dense-моделей
|
||
|
||
Выводы из таблицы «Скорость и память» + качества:
|
||
|
||
1. **Абляция бесплатна.** qwen3.5 vanilla vs abliterated: скорость идентична (24.2 vs 24.5), SFW-качество — тоже (abliterated даже чуть точнее на 羅生門). → для 18+-скрининга/чернового перевода берём abliterated без штрафа. (Доказательство слабое — 3 SFW-фрагмента; на explicit перепроверить.)
|
||
2. **Поколение 3.5 > 3 умеренно**: лучше понимает исходник (Радзё против Розен), на ~25% экономнее по токенам (0.28 vs 0.37 ток/симв), ценой −10% скорости.
|
||
3. **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](../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`).
|