Includes earlier uncommitted work: Tomie and Unity adapters, benchmark, structured output, call metrics. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
339 lines
50 KiB
Markdown
339 lines
50 KiB
Markdown
# Local Translator
|
||
|
||
Локальный HTTP API для перевода игровых строк через **LM Studio**, OpenRouter и другие серверы с совместимым `POST /chat/completions`. Python 3.11+. По умолчанию перевод **English → Russian**, сервер слушает только `127.0.0.1:8787`.
|
||
|
||
## Запуск на Windows
|
||
|
||
1. Установите Python 3.11+ и LM Studio. Скачайте модель, загрузите её и запустите сервер в Developer → Start Server.
|
||
2. Дважды щёлкните `scripts/windows/start.bat`. При первом запуске создаётся `.venv`, скачиваются зависимости из PyPI и копируется `config.example.toml` в `config.toml`.
|
||
3. Документация с формами запросов: <http://127.0.0.1:8787/docs>. Состояние приложения: <http://127.0.0.1:8787/health>.
|
||
|
||
Все Windows-батники находятся в `scripts/windows/`: запуск API и бенчмарк — непосредственно в этой папке, подготовка/установка/откат Tomie — в `scripts/windows/tomie/`, внутренние общие команды — в `scripts/windows/lib/`. Они определяют корень проекта по своему расположению и работают при запуске из другого каталога.
|
||
|
||
После установки зависимостей локальный перевод не требует интернета. Чтобы обновить зависимости после изменения `pyproject.toml`, выполните `.venv\Scripts\python.exe -m pip install -e .`.
|
||
|
||
В `config.toml` задайте `providers.lmstudio.model` — **точный ID из `GET /providers/lmstudio/models`**, а не произвольное имя файла. Пустое значение допустимо, если `/models` возвращает ровно одну модель. При нескольких моделях приложение потребует явный выбор. Можно переопределить модель в отдельном проекте.
|
||
|
||
### Автозапуск LM Studio
|
||
|
||
В `config.toml`, внутри `[providers.lmstudio]`:
|
||
|
||
```toml
|
||
autostart = true
|
||
startup_timeout = 90
|
||
# Необязательно: точное имя уже скачанной модели, известное `lms ls`.
|
||
load_model = ""
|
||
```
|
||
|
||
Нужен доступный в PATH официальный `lms` CLI. Если выбранный ID уже доступен через API, работающий сервер используется сразу. Иначе приложение выполняет `lms daemon up`, затем `lms server start --port 1234 --bind 127.0.0.1`, при заданном `load_model` — `lms load ... --context-length 8192`, с `--identifier`, если задан `model`. Окно загрузки меняется через `load_context_length`. После этого приложение ждёт доступности выбранного ID в `/models` до `startup_timeout`. GUI открывать не требуется. Это запуск локального движка; модель автоматически не скачивается. Внешний CLI запускается без видимого окна на Windows.
|
||
|
||
Если автозапуск не удался, API всё равно доступен для управления проектами. Причина видна в консоли и `health.startup_errors` (результат именно последней попытки запуска, а не текущая готовность провайдера). Запустите LM Studio вручную и повторите неудачное задание.
|
||
|
||
Официальные инструкции: [сервер LM Studio](https://lmstudio.ai/docs/developer/core/server), [CLI](https://lmstudio.ai/docs/cli), [совместимый API](https://lmstudio.ai/docs/developer/openai-compat).
|
||
|
||
## Первый перевод из Python
|
||
|
||
В другой консоли из каталога проекта:
|
||
|
||
```powershell
|
||
python examples/translate_game.py --input examples/strings.json --output data/result.json
|
||
```
|
||
|
||
Скрипт не требует сторонних библиотек. Создаёт проект с контекстом и небольшим примером словаря, отправляет строки пачками по 10 и сохраняет полный результат. Вход — JSON-массив:
|
||
|
||
```json
|
||
[
|
||
{"id": "intro.001", "text": "Welcome, {player_name}!"},
|
||
{"id": "intro.002", "text": "Bring me the Moonstone."}
|
||
]
|
||
```
|
||
|
||
ID задаёт ваш скрипт извлечения; если их нет в игре, используйте порядковые номера в виде строк. ID уникальны внутри одного задания. Связь с файлами и обратную вставку обеспечивает клиентский скрипт.
|
||
|
||
Для создания проекта из своего JSON используйте `--project-file examples/tomie_project.json`. Существующий проект:
|
||
|
||
```powershell
|
||
python examples/translate_game.py --project PROJECT_ID --input examples/strings.json --output data/result.json
|
||
```
|
||
|
||
Рядом с результатом сохраняется `result.json.job.json` с ID задания. Повтор той же команды продолжит наблюдать это задание после остановки клиента. `--retry` повторяет только неудачные строки. `--new-job --project PROJECT_ID` создаёт новое задание с текущими настройками проекта. Состояние предыдущего задания доступно в API. При создании нового проекта можно указать `--context "описание игры"` и `--target Russian`.
|
||
|
||
## API
|
||
|
||
Полная схема: `/docs` и `/openapi.json`. Ключ модели не передаётся в запросах — используется переменная окружения, имя которой задано в конфигурации провайдера.
|
||
|
||
### Создать проект
|
||
|
||
`POST /projects`
|
||
|
||
```json
|
||
{
|
||
"name": "My RPG",
|
||
"source_language": "English",
|
||
"target_language": "Russian",
|
||
"context": "Мрачная фэнтези RPG. Реплики короткие, речь естественная.",
|
||
"instructions": "Сохраняй грубость и стиль оригинала. Обращение на ты.",
|
||
"glossary": {"Moonstone": "Лунный камень"},
|
||
"provider": "lmstudio",
|
||
"model": null,
|
||
"disabled_rules": [],
|
||
"protection_rules": []
|
||
}
|
||
```
|
||
|
||
Ответ содержит `id` и `revision`. Языки можно опустить: по умолчанию English → Russian. Контекст и словарь добавляются к каждой пачке. История разговоров между пачками не накапливается; словарь редактируется явно, автоматического извлечения терминов пока нет. Если **вся строка** в точности совпадает с ключом словаря (регистр учитывается), используется заданное значение без генерации моделью. Это обеспечивает точный перевод коротких кнопок и сокращений; защита синтаксиса проверяется и в этом случае. У таких строк `attempts: 0`. Для терминов внутри более длинных строк словарь служит инструкцией модели, без гарантии точного соблюдения.
|
||
|
||
`GET /projects`, `GET /projects/{id}` — просмотр. `PUT /projects/{id}` — **полная замена** настроек, без полей `id` и `revision`; для изменения одного значения сначала получите проект и отправьте все его настройки. Каждое обновление увеличивает `revision`.
|
||
|
||
### Перевести одну строку или пачку
|
||
|
||
`POST /projects/{id}/translate`
|
||
|
||
```json
|
||
{
|
||
"items": [
|
||
{"id": "1", "text": "Hello, {player}!"},
|
||
{"id": "2", "text": "You have %d seconds."}
|
||
],
|
||
"batch_size": 10,
|
||
"max_batch_chars": 12000,
|
||
"retries": 2,
|
||
"use_cache": true
|
||
}
|
||
```
|
||
|
||
Один элемент — перевод одной строки. Одна пачка — один запрос к модели с общим контекстом. Порядок результата соответствует входу, даже если модель переставила записи в JSON. `retries: 2` означает максимум три попытки для каждой строки; успешно переведённые строки повторно не отправляются. Ответ модели проверяется до сохранения перевода.
|
||
|
||
Сервер ждёт результат до 30 секунд: `200` с готовым заданием или `202` с текущим состоянием и `Location: /jobs/{id}`. После `202` опрашивайте этот адрес. Завершение задания с ошибкой тоже возвращает объект задания — проверяйте `status` и состояние каждой строки, одного HTTP-кода недостаточно.
|
||
|
||
### Вызов из браузера
|
||
|
||
Страница, открытая в браузере (например, библиотека игр), может работать с API напрямую, без посредника-сервера. Впишите её адрес в `config.toml` и перезапустите приложение:
|
||
|
||
```toml
|
||
allowed_origins = ["https://games.example", "http://localhost:5173"]
|
||
```
|
||
|
||
Допустимы только точные адреса вида `схема://хост[:порт]`, без пути и без `*`: иначе любой сайт мог бы пользоваться вашей моделью и читать проекты. Для Chrome сервер отвечает на предварительный запрос с `Access-Control-Request-Private-Network`. Сам браузер при первом обращении https-страницы к `127.0.0.1` спросит разрешение на доступ к локальной сети. `GET /health` возвращает `api_version` (сейчас 2), по которой клиент проверяет совместимость.
|
||
|
||
### Долговечные задания
|
||
|
||
- `POST /projects/{id}/jobs` — сразу возвращает `202` и ID. Предпочтительный вариант для скриптов.
|
||
- `GET /jobs/{id}` — прогресс и результаты всех строк.
|
||
- `GET /projects/{id}/jobs?limit=50&offset=0` — история заданий, новые первыми.
|
||
- `POST /jobs/{id}/retry` — повторить незавершённые/неудачные строки после ошибки. Сбрасывает их счётчик попыток. Готовые строки сохраняются.
|
||
- `POST /jobs/{id}/cancel` — остановить задание в очереди или в работе: невыполненные строки получают `failed` с ошибкой `Cancelled`, статус задания — `cancelled`; готовые строки сохраняются. Повторный запрос безвреден; `retry` возобновляет остановленное.
|
||
|
||
Статусы задания: `queued`, `running`, `completed`, `completed_with_errors`, `failed`, `cancelled`. Статусы строк: `pending`, `completed`, `failed`. В `items` возвращаются `id`, `source`, `translation`, `status`, `error`, `attempts`, `cached`, `position`. При общей ошибке подключения/выбора модели задание получает `failed`, ещё не обработанные строки могут остаться `pending`.
|
||
|
||
Задание хранит снимок контекста, словаря, правил и настроек провайдера. Обновление проекта не меняет уже созданные задания. Повтор старого задания использует старые настройки; для перевода по новым настройкам создайте новое.
|
||
|
||
SQLite сохраняет каждый успешный результат. После перезапуска незавершённые задания возвращаются в очередь, готовые строки не теряются. Запрос, прерванный непосредственно во время генерации или до записи результата, может быть отправлен модели повторно; это не гарантия ровно одного вызова. Счётчик попыток также сохраняется. После исчерпания попыток нужен явный `/retry`.
|
||
|
||
Кэш разделён по проекту, его ревизии, настройкам провайдера, выбранной модели и версии промпта. Изменение настроек не удаляет историю, но прежний кэш больше не используется. `use_cache: false` принудительно выполняет новый перевод. Замена весов модели под тем же ID автоматически не распознаётся: обновите проект или отключите кэш.
|
||
|
||
Один процесс последовательно обрабатывает задания, чтобы не перегружать локальную GPU. Пачки группируются по числу строк и сумме символов, а не по точному числу токенов. Предел одной строки — 30 000 символов; она не разрезается даже при превышении `max_batch_chars`. На задание допускается 1–500 строк, суммарно до 1 000 000 символов; более крупные входы делите на задания. Размер реального контекста модели настраивается в LM Studio. При переполнении уменьшите пачку/контекст или увеличьте окно модели.
|
||
|
||
## Плейсхолдеры и правила
|
||
|
||
`GET /protection-rules` возвращает имена и регулярные выражения встроенных правил:
|
||
|
||
- `braces`: `{player}`, `{0}`, `{{value}}`;
|
||
- `dollar`: `${quest}`, `$variable`;
|
||
- `printf`: `%s`, `%1$s`, `%02d`, `%NAME%`, `%%`;
|
||
- `tags`: `<color=red>`, `</color>`, HTML/XML-подобные теги;
|
||
- `square`: `[wait]` и другие конструкции в квадратных скобках;
|
||
- `escapes`: текстовые `\n`, `\t`, `\u1234` и подобные последовательности;
|
||
- `newlines`: настоящие LF/CRLF/CR;
|
||
- `entities`: `&`, `{`;
|
||
- `controls`: табуляция и другие управляющие символы.
|
||
|
||
До отправки модели фрагменты заменяются уникальными маркерами. После ответа проверяются их количество и порядок, восстанавливаются исходные байт-в-байт значения строковых фрагментов, проверяется отсутствие новых распознаваемых конструкций. Повреждённый ответ не сохраняется как успешный перевод. Строгий порядок также запрещает переставлять плейсхолдеры ради грамматики.
|
||
|
||
В проекте можно отключать встроенные правила и добавлять свои Python-regex:
|
||
|
||
```json
|
||
{
|
||
"disabled_rules": ["square"],
|
||
"protection_rules": [
|
||
{"name": "engine_codes", "pattern": "@[A-Z_]+@"},
|
||
{"name": "rpg_maker", "pattern": "\\\\[A-Za-z]+\\[[0-9]+\\]"}
|
||
]
|
||
}
|
||
```
|
||
|
||
Это часть полного объекта проекта. Пользовательские правила имеют приоритет над встроенными при совпадении начала фрагмента. При перекрытии защищается первое совпадение; вложенные языки/ICU/сложные шаблоны требуют правил для конкретного движка. `square` намеренно широкое: если в скобках обычный текст, отключите его. Универсального распознавания всех игровых форматов нет. Защита проверяет структуру, но не смысл или качество перевода.
|
||
|
||
## HY-MT2 и выбор модели
|
||
|
||
По умолчанию локальный провайдер использует `prompt_style = "hy_mt"`: один user-промпт с контекстом, словарём и JSON. `temperature=0.7`, `top_p=0.6`, `top_k=20`, `repetition_penalty=1.05`, `max_tokens=4096` соответствуют рекомендациям Tencent для 7B. Для обычных instruction-моделей поставьте `prompt_style = "generic"`; неподдерживаемые необязательные параметры удалите из TOML. Параметры и инструкции — [карточка Tencent](https://huggingface.co/tencent/Hy-MT2-7B).
|
||
|
||
Для RTX 3060 12 GB и 32 GB RAM разумно начать с **HY-MT2-7B Q6_K**, сравнив с Q4_K_M на своих строках. Файлы официальных квантовок: Q4_K_M — 4,62 GB, Q6_K — 6,16 GB, Q8_0 — 7,98 GB. Это размеры весов, не общий расход VRAM: контекст и runtime требуют дополнительную память. Стартовая проба — пачка 10 коротких строк и окно 8192, затем настройка по фактическому расходу LM Studio. Это ориентир, не измеренный тест на этой GPU. [Официальные GGUF](https://huggingface.co/tencent/Hy-MT2-7B-GGUF/tree/main).
|
||
|
||
Tencent не обещает отсутствие цензуры в карточке HY-MT2. Промпт просит сохранять грубость и откровенность, однако отсутствие отказов и смягчений нужно проверять на репликах игры. Дополнительный кандидат для сравнения — [Huihui Qwen3-8B abliterated](https://huggingface.co/huihui-ai/Qwen3-8B-abliterated), заявленный автором как модель с удалением отказов. Такая модификация сама по себе не доказывает качество EN→RU. Автоматической проверки отказов по смыслу в API нет.
|
||
|
||
Реальный перевод HY-MT2 через LM Studio пока не входит в автоматические проверки: тесты используют имитатор OpenAI-совместимого провайдера.
|
||
|
||
## Пример: Tomie Wants to Get Married
|
||
|
||
Для версии `Expansion-v1.3801-Win` подготовлен отдельный экстрактор словаря интерфейса Naninovel `DefaultUI`, хранящегося в Unity `TextAsset` внутри `TomieWGM_Data/data.unity3d`.
|
||
|
||
```powershell
|
||
.venv\Scripts\python.exe -m pip install -e ".[unity]"
|
||
.venv\Scripts\python.exe examples/extract_tomie_ui.py "D:\Torrents\Pgames\Tomie_Wants_to_Get_Married_Expansion-v1.3801-Win" --limit 15
|
||
.venv\Scripts\python.exe examples/translate_game.py --project-file examples/tomie_project.json --input data/tomie/input.json --output data/tomie/result.json
|
||
```
|
||
|
||
В словаре найдено 61 значение. По умолчанию извлекаются первые 15; `--limit 0` извлекает все. В manifest рядом с входным JSON сохраняются исходные ключи, номера строк, ID Unity-объекта и хеш исходного текста. Экстрактор только читает файлы игры и запрещает вывод внутрь её каталога. Для обратной записи интерфейса есть отдельный тестовый адаптер ниже.
|
||
|
||
На этой машине выполнен реальный прогон первых 15 строк через HY-MT2-7B Q4_K_M в LM Studio на RTX 3060 с окном 8192: все строки обработаны, цветовые теги сохранены. Проверены повторное использование кэша и сохранность задания после перезапуска API через батник. Проект содержит контекст игры и словарь интерфейсных терминов. Качество диалогов и поведение модели на откровенных сценах этим тестом не оценивались.
|
||
|
||
Этот короткий пример проверяет только `DefaultUI`. Для сценариев есть отдельный массовый прогон ниже.
|
||
|
||
### Установка тестового перевода в игру
|
||
|
||
1. **`scripts/windows/tomie/prepare_tomie_translation.bat`** — извлекает весь `DefaultUI` и выбранные статические надписи на кнопках, переводит через API и собирает пакет в `data/tomie/patches/`. Если API не запущен, пытается запустить его с `config.toml`. При первом запуске создаётся `tomie.json` из `tomie.example.json`; там можно изменить путь к игре. Игровые ресурсы на этом этапе не меняются.
|
||
2. Закройте игру и запустите **`scripts/windows/tomie/install_tomie_translation.bat`**. Он устанавливает последний успешно собранный пакет, сохраняя оригиналы внутри `.local-translator/backups/` в каталоге игры. Затем запустите игру обычным способом.
|
||
3. Для отката закройте игру и запустите **`scripts/windows/tomie/restore_tomie_original.bat`**. Сохранения игры эти инструменты не изменяют. Резервные копии после отката остаются на диске.
|
||
|
||
Тестовый адаптер меняет 61 запись словаря и выбранные статические UI-надписи в обоих наборах ресурсов: prefab-бандле и `data.unity3d`. Для Text-компонентов без сохранённой схемы используется проверяемая схема Unity UI Text из бандла той же игры. Переводятся также пять названий качества графики; порядок и значения самих графических настроек сохраняются. Короткие кнопки используют сокращения и подгонку размера шрифта. Декоративный шрифт без кириллицы заменяется встроенной Candara; статические TMPro SDF-атласы не перестраиваются. Сценарии, диалоги, текст на картинках и все остальные элементы игры этим патчем не переводятся. Отдельный пункт «Русский» в списке языков не добавляется: заменяются исходные записи интерфейса, а название исходного языка English остаётся корректным.
|
||
|
||
Названия качества добавляются как пять недостающих managed-text записей `GameSettingsGraphicsDropdown.GraphicOption1..5`: игра подставляет эти поля при открытии настроек. Для явной офлайн-пересборки можно передать `--result <путь к translations.json>` батнику подготовки. Такой файл должен полностью соответствовать текущему набору ID и исходных строк; неполные и несовместимые результаты отклоняются, а плейсхолдеры проверяются повторно. Если меняете контекст или словарь, выполняйте обычную подготовку без `--result`, чтобы получить новый перевод.
|
||
|
||
Пакет содержит `translations.json`, `ui-preview.json`, `DefaultUI.ru.txt`, `manifest.json` и готовые файлы `payload/`. После упаковки они повторно читаются через UnityPy: проверяются ID всех объектов и неизменность объектов, не относящихся к патчу. Для Addressables пересчитывается CRC несжатого бандла и обновляются размер и хеш его записи в каталоге с сохранением смещений бинарных данных.
|
||
|
||
Установка проверяет хеши исходников, пакета и резервных копий. При другой версии игры или наличии сторонних изменений она останавливается. Игра должна быть закрыта; параллельные установки блокируются. При ошибке частичной установки выполняется откат уже заменённых файлов, а журнал позволяет повторить восстановление после прерывания. Откат не перезаписывает неизвестные сторонние изменения. Новую сборку можно подготовить при установленном старом переводе: используются проверенные резервные копии оригиналов. Перед установкой новой сборки закройте игру, восстановите оригинал и запустите установку снова.
|
||
|
||
Размер тестового пакета — примерно 565 MiB; для резервных и временных копий при установке дополнительно требуется место на диске игры. Поддерживается конкретная локальная Windows-сборка Tomie, универсальной установки в другие Unity-игры пока нет. Проверка отображения и удобства в самой игре выполняется пользователем: проверьте настройки, загрузку/сохранение, русские буквы и помещаются ли надписи в кнопки.
|
||
|
||
### Массовый перевод сценариев и динамических реплик
|
||
|
||
После подготовки UI-пакета запустите **`scripts/windows/tomie/translate_all_tomie.bat`**. Он извлекает все 108 скомпилированных сценариев этой сборки, реплики, варианты выбора, всплывающие сообщения, оставшийся статический UI, словари `Tips`, `CharacterNames`, `Inventory` и динамические шаблоны из `StreamingAssets/text/`. Изображения с надписями и строки, формируемые непосредственно C#-кодом, не извлекаются. Для этой сборки получено 34 274 сценарных записи и ещё 5 128 записей других ресурсов; после удаления повторов и разбиения длинных значений модели передаются 28 002 уникальных фрагмента.
|
||
|
||
Работа идёт последовательно через локальный API: части по **400 строк**, внутри — до **20 строк за запрос**, с ограничением **2 000 исходных символов**. Настройки в `tomie.json`: `full_output`, `full_part_size`, `full_batch_size`, `full_max_batch_chars`. Размер частей ограничен 500 строками API; длинные значения разбиваются вне защищённых тегов и выражений. Общий контекст и словарь проекта берутся из `examples/tomie_project.json`. Между HTTP-запросами история чата модели не сохраняется; контекст передаётся заново с каждой пачкой.
|
||
|
||
**`scripts/windows/tomie/translation_status.bat`** показывает сохранённый прогресс. Результаты находятся в `data/tomie/full-translation/`: `dataset.json`, `state.json`, `progress.json`, `parts/*.json`; дополнительный набор — в подпапке `extra/`. При фоновом запуске логи пишутся в `run.log` и `run-error.log`. SQLite сохраняет каждую переведённую строку. Повторный запуск батника продолжает те же задания и повторяет ошибки; не меняйте входные файлы и параметры посреди прогона. Для новой модели или другого контекста выберите другой `full_output`. Если API остановлен, сначала снова запустите его; уже созданное задание сохраняется в БД.
|
||
|
||
После успешного завершения обоих наборов автоматически собирается общий пакет, а `data/tomie/patches/latest.json` указывает на него. Неполный перевод не устанавливается. Для замены ранее установленного UI-патча сначала закройте игру и выполните **`restore_tomie_original.bat`**, затем **`install_tomie_translation.bat`** из той же папки. Сам массовый прогон файлы игры не меняет и не запускает игру.
|
||
|
||
Сценарии разбираются по схемам из оригинальных DLL с поддержкой старого формата Unity SerializeReference. До перевода каждый объект проходит побайтный цикл чтения/записи; при упаковке сохраняются команды, условия, ссылки, номера строк и хеши исходных реплик. Упакованные сценарии повторно сравниваются целиком с ожидаемыми объектами; неизменяемые объекты и медиаресурсы тоже проверяются. Ключи словарей, разделители подсказок и таблицы синонимов сохраняются. Временные распакованные медиаданные хранятся на диске в `data/tomie/tmp/`, чтобы не создавать несколько больших копий в RAM; для сборки желательно иметь около 15 GiB свободного места на диске проекта. Поддержка кириллицы динамических реплик и естественность случайно комбинируемых шаблонов требуют проверки в игре.
|
||
|
||
Командная строка для диагностики: `.venv\Scripts\python.exe -X utf8 -m local_translator.tomie --config tomie.json status`. Чтобы повторно собрать перевод после изменения контекста/словаря, запустите `scripts/windows/tomie/prepare_tomie_translation.bat`; при ошибках API исходники и ID задания сохраняются в каталоге сборки.
|
||
|
||
## Проверка скорости на разных моделях
|
||
|
||
Запустите **`scripts/windows/benchmark.bat`**. Он создаёт `benchmark.json` из `benchmark.example.json`, устанавливает недостающий UnityPy, извлекает первые 30 строк интерфейса Tomie и прогоняет их на всех скачанных **текстовых** моделях LM Studio. Embedding-модели исключаются; новые модели автоматически не скачиваются. Для другого компьютера измените путь `game` в JSON или задайте `input` с готовым массивом `{id, text}`. Нужны Python 3.11+, LM Studio и доступный `lms` CLI.
|
||
|
||
По умолчанию проверяются пачки **1, 5 и 10 строк**, по **3 повтора**. Перед замерами выполняется отдельный прогрев на двух строках. Порядок размеров пачек меняется между повторами, чтобы уменьшить влияние порядка. Пример сокращённого прогона:
|
||
|
||
```powershell
|
||
.\scripts\windows\benchmark.bat --limit 20 --repeats 2 --batches 1 10
|
||
```
|
||
|
||
Выбор конкретных скачанных моделей (ключи и точные пути смотрите в `lms ls --json`):
|
||
|
||
```powershell
|
||
.\scripts\windows\benchmark.bat --models hy-mt2-7b another-model
|
||
```
|
||
|
||
Либо задайте `models` в `benchmark.json`. Пустой список означает все скачанные LLM. Точный путь позволяет различать несколько квантовок одной модели. Пример одного элемента:
|
||
|
||
```json
|
||
{
|
||
"model": "tencent/Hy-MT2-7B-GGUF/Hy-MT2-7B-Q4_K_M.gguf",
|
||
"label": "HY-MT2 Q4",
|
||
"prompt_style": "hy_mt",
|
||
"temperature": 0.7,
|
||
"top_p": 0.6,
|
||
"top_k": 20,
|
||
"repetition_penalty": 1.05
|
||
}
|
||
```
|
||
|
||
Для HY-MT автоматически выбирается профиль Tencent, для остальных — `generic` с температурой 0.2 и без специфичных параметров. В бенчмарке LM Studio по умолчанию включён `structured_output: true`; для отдельной модели можно явно отключить. Профиль можно переопределить в каждом элементе списка; фактические настройки сохраняются в отчёте. Сравнение разных профилей — сравнение готовых конфигураций перевода, а не строго одинаковых параметров декодирования.
|
||
|
||
### Что измеряется
|
||
|
||
Бенчмарк запускает отдельный экземпляр **нашего API** на `127.0.0.1:8788` с собственной SQLite в каталоге результата. Основной API на 8787 продолжает работать, но во время замеров не отправляйте в него задания и не запускайте другие генерации LM Studio. Одинаковые строки, контекст проекта и размер окна используются для всех моделей. Итоговые переводы каждого прогона сохранены для ручного сравнения.
|
||
|
||
- Кэш переводов отключён. Из тестового словаря удаляются ключи, совпадающие с целыми входными строками, чтобы все строки реально проходили через модель. Остальные термины остаются в промпте. Рабочий проект игры не меняется.
|
||
- Время загрузки модели и прогрева записывается отдельно и **не входит** в замер перевода. Нативный prompt cache LM Studio не очищается: это измерение прогретого сервиса.
|
||
- В полное время входят отправка задания по HTTP, ожидание очереди, генерация, проверки, SQLite и повторные попытки. Опрос готовности идёт раз в 50 мс, поэтому время включает небольшую погрешность опроса.
|
||
- Скорость — количество успешно переведённых моделью строк или исходных Unicode-символов за полное время. Неуспешный прогон не участвует в медиане скорости успешных прогонов; число ошибок и повторов показано отдельно.
|
||
- API сохраняет число запросов провайдеру, их суммарное время и usage-токены, если сервер их сообщает. Отсутствующие токены обозначаются `null`, а не нулём. `completion_tokens_per_request_second` делит выходные токены на **полное время запросов**, включая обработку промпта; это не чистые decode tokens/s. Токенизаторы у моделей различаются, поэтому для сравнения полезнее символы/с.
|
||
- Синтаксические проверки не доказывают качество перевода. Отдельно перечисляются строки, полностью совпавшие с оригиналом: часть из них может быть допустимыми именами/аббревиатурами. Отказы, смягчение смысла и качество русского требуют чтения результатов.
|
||
|
||
Модели переключаются последовательно. Занятые модели скрипт не выгружает и останавливается при обнаружении активной генерации. Для освобождения GPU он временно выгружает **простаивающие LLM**, оставляя embedding-модели. По умолчанию модель загружается заново с заданными `context_length` и `gpu`, без интерактивных вопросов, по `modelKey` установленного CLI; после загрузки проверяется путь фактически выбранного GGUF. `reuse_loaded: true` позволяет использовать уже загруженный экземпляр с нужным окном, но его offload скрипт не проверяет. После завершения или обычного Ctrl+C скрипт старается восстановить исходные ID моделей и размеры окон. Специальные настройки движка и offload исходных экземпляров полностью не воспроизводятся. Исходный список сохранён в `original-models.json`; если восстановление не удалось, это указано в отчёте. При принудительном завершении процесса восстановление не гарантируется.
|
||
|
||
Не запускайте несколько бенчмарков одновременно. Для одного каталога настроек действует файловая блокировка. Таймаут задания останавливает весь прогон, чтобы не переключать модель во время незавершённого запроса.
|
||
|
||
### Результаты
|
||
|
||
Каждый запуск создаёт отдельный каталог `data/benchmarks/<время>-<id>/`:
|
||
|
||
- `report.html` — обзор для браузера с медианами, ошибками и повторами;
|
||
- `report.json` — параметры, хеш набора, фактические модели/квантовки, usage и все замеры;
|
||
- `runs.csv` — замеры для Excel/анализа;
|
||
- `jobs/` — полные ответы API, исходники и переводы каждого прогона, включая прогрев;
|
||
- `raw-responses/` — исходные ответы модели до разбора JSON, для диагностики ошибок формата;
|
||
- `dataset.json` — точный использованный набор;
|
||
- `translations.sqlite3` — сохранённые проекты, задания и метрики;
|
||
- `active-job.json` — последнее созданное задание на случай прерывания.
|
||
|
||
При наличии `nvidia-smi` в отчёт также записываются название GPU, объём памяти и версия драйвера. Хеш исходников реализации позволяет отличать серии до и после изменений API. Диагностические файлы содержат переводы, а не только числовые показатели; весь каталог результатов исключён из Git.
|
||
|
||
Отчёт обновляется после каждого прогона. Ошибки моделей не скрываются: после обычной ошибки скрипт пытается проверить следующую модель и завершится с ненулевым кодом. Перезапуск создаёт новый замер, не переиспользует незавершённую серию. Для запуска без паузы батника:
|
||
|
||
```powershell
|
||
.venv\Scripts\python.exe -X utf8 -m local_translator.benchmark --config benchmark.json
|
||
```
|
||
|
||
`GET /jobs/{id}` теперь также содержит `metrics` с накопленными метриками вызовов провайдера. При `/retry` они остаются накопительными за всю жизнь задания. Метрики появляются после перезапуска обновлённого API; у старых заданий ранее выполненные вызовы восстановить нельзя.
|
||
|
||
## Другие провайдеры
|
||
|
||
Пример OpenRouter уже есть в `config.example.toml`. Задайте модель и `OPENROUTER_API_KEY` в окружении процесса, затем создайте проект с `provider: "openrouter"`. В PowerShell:
|
||
|
||
```powershell
|
||
$env:OPENROUTER_API_KEY = "ваш ключ"
|
||
.\scripts\windows\start.bat
|
||
```
|
||
|
||
Для другого сервера добавьте `[providers.my_server]` с `base_url`, `model`, при необходимости `api_key_env`. В `base_url` входит `/v1` или другой префикс, но не `/chat/completions`. После изменения TOML перезапустите приложение. Сервис поддерживает HTTP OpenAI Chat Completions, а не произвольные нативные API всех поставщиков.
|
||
|
||
Для LM Studio включён `structured_output = true`: ответ ограничен JSON Schema через `response_format`, дополнительно проверяются ID и защищённые фрагменты. Модели передаются короткие внутренние ID вместо длинных ключей игры; клиент получает свои исходные ключи. Это уменьшает объём ответа и не позволяет превратить ключи в переводимый текст. Для сервера/модели без JSON Schema оставьте `structured_output = false` (по умолчанию для произвольного провайдера): тогда формат запрашивается только промптом, с последующей проверкой. Автоматического скрытого переключения между этими режимами нет. [Structured output в LM Studio](https://lmstudio.ai/docs/developer/openai-compat/structured-output).
|
||
|
||
## Linux и systemd
|
||
|
||
Для ручного запуска: `bash start.sh` (нужны Python 3.11+, модуль venv и доступ к PyPI при первой установке).
|
||
|
||
Для системного сервиса пример рассчитан на `/opt/local-translator` и пользователя `translator`. Подготовьте каталог с проектом и виртуальное окружение **до** запуска systemd:
|
||
|
||
```bash
|
||
sudo useradd --system --home-dir /opt/local-translator --shell /usr/sbin/nologin translator
|
||
sudo python3 -m venv /opt/local-translator/.venv
|
||
sudo /opt/local-translator/.venv/bin/python -m pip install /opt/local-translator
|
||
sudo cp /opt/local-translator/config.example.toml /opt/local-translator/config.toml
|
||
sudo install -d -o translator -g translator /opt/local-translator/data
|
||
sudo cp /opt/local-translator/deploy/local-translator.service /etc/systemd/system/
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now local-translator
|
||
journalctl -u local-translator -f
|
||
```
|
||
|
||
Если пользователь/каталог уже существуют, адаптируйте команды. Путь к БД должен оставаться внутри доступного сервису `data/` либо нужно изменить `ReadWritePaths`. Ключи провайдеров можно положить в `/etc/local-translator.env` (`OPENROUTER_API_KEY=...`, права `600`, владелец root).
|
||
|
||
Пример systemd предполагает уже работающий локальный сервер модели или внешний провайдер. LM Studio, запущенная под вашим desktop-пользователем, доступна сервису через loopback. Автозапуск `lms` из этого защищённого systemd unit требует отдельной настройки пользователя, HOME, PATH и прав на модели; для него удобнее пользовательский unit с собственным LM Studio/llmster. Системный unit сам это не настраивает.
|
||
|
||
## Проверки и ограничения первой версии
|
||
|
||
```powershell
|
||
.venv\Scripts\python.exe -m unittest discover -s tests -v
|
||
```
|
||
|
||
Проверяются обработка пачек, защита синтаксиса, пользовательские правила, словарь, кэш и ревизии, ошибки ответов модели, повтор только неудачных строк и восстановление очереди. Провайдер в тестах подменяется HTTP-имитатором. Windows-батник и работающая LM Studio проверены также в реальном прогоне. Установка systemd на Linux пока не проверялась.
|
||
|
||
Используйте один процесс приложения: файловая блокировка запрещает двум экземплярам обрабатывать одну БД. Сервер запускается на loopback, без отдельной авторизации. CORS выключен, пока в `config.toml` пуст `allowed_origins` (см. «Вызов из браузера»). В SQLite сохраняются исходные строки, контекст и переводы. Для резервного копирования остановите сервер и скопируйте `data/` вместе с `config.toml`.
|
||
|
||
Пока нет веб-интерфейса управления, потоковой выдачи токенов, отмены заданий, автоматического пополнения словаря, точного подсчёта токенов и универсальных адаптеров игровых файлов. Адаптер Tomie поддерживает интерфейс и известные текстовые параметры скомпилированных сценариев этой сборки. Интерактивная `/docs` предназначена для работы с API.
|