# Generation service: проверенный CPU-запуск и повторение на чистом pod ## Что было сделано и проверено 11 октября 2026 поднят локальный generation daemon на CPU с моделью `RT_ALTERNATIVE_TEXTS_GENERATION_BIBABOBA_FOUR_TITLES` из `~/models`. Использован бинарник prod-neuro application layer Sandbox 13662792630 и model config FOUR_TITLES из prod2 application layer 12691698810. В model config заменены GPU backend на CPU (1 backend), добавлено преобразование fp16 → fp32, batch_size=1, memory chunk=256 MiB, max_out_len=40, timeout=30 s и min_generation_time=1000 ms. Vocabulary `bpe.voc` извлечён из того же `model.npz`; ссылка на внешний YT vocabulary заменена на локальный файл. Сопоставление vocabulary с историческими байтами внешнего YT-объекта отдельно не проверялось. Модель реально загрузилась. `/ready`, `/v2/health/ready` и readiness модели вернули HTTP 200. Настоящий KServe inference вернул HTTP 200 примерно за 3.55 секунды. Ответ декодирован из UnitedNN protobuf/Base64 в текст. Это проверка загрузки и inference, а не качества/скорости production. REX/reloader не запускались, Deploy/stages/pods не изменялись. Веса и исходные архивы не менялись. Не использовалась dummy readiness. Локальный процесс оставлен на HTTP 18080. Модель по metadata: FOUR_TITLES, версия `2026-02-19T19:00:00`. Локальный repository номер `1` — технический номер версии, не версия production snapshot. ## Условия повторения Нужен чистый выделенный Linux x86_64 pod для диагностики, с writable диском, достаточной RAM и возможностью открыть TCP 18080. GPU/Docker для этого CPU-варианта не нужны. Нужны уже доступные `ya tool yt` со штатной авторизацией на чтение kolmogorov, Python 3, GNU tar, zstd, xz, sha256sum, md5sum и curl. Python-скрипты ниже используют только стандартную библиотеку; PyYAML/Arcadia checkout/пересборка не нужны. Если какого-то инструмента нет — используйте согласованный образ/способ установки; токены в команды не вставляйте. Сохранённый бинарник зависит от совместимого Linux/glibc/libstdc++; проверьте `ldd` до запуска. Архивы скачиваются целиком, поэтому оставьте достаточно диска для скачанных файлов, распакованного snapshot и daemon (размер daemon 6398274536 bytes). Допущение по просьбе владельца: нужная модель обязательно находится внутри `part_1/rephrase/archive.tar.xz` по пути `models/RT_ALTERNATIVE_TEXTS_GENERATION_BIBABOBA_FOUR_TITLES/`. Полное дерево этого архива на этапе написания инструкции не проверялось. Ниже её извлекает уже оператор чистого pod. Если путь/сумма не совпали — остановитесь, не подменяйте модель. ## 1. Новый рабочий каталог и скачивание ИЗ YT Все команды ниже выполнять на выделенном чистом pod; они создают только локальные рабочие файлы и диагностический сервер. Это не команды восстановления production. ```bash set -euo pipefail umask 077 workdir=$(mktemp -d "$PWD/rephrase-generation-cpu.XXXXXXXX") cd "$workdir" YT_ROOT='//home/ads/users/npytincev/backup/runtime/part_1/rephrase' ARCHIVE='yabs-rephrase-generation-service-all-stages-configs-resources-2026-10-11.tar.zst' MODEL='RT_ALTERNATIVE_TEXTS_GENERATION_BIBABOBA_FOUR_TITLES' ya tool yt --proxy kolmogorov read-file "$YT_ROOT/generation/$ARCHIVE" >"$ARCHIVE" printf '%s %s\n' 'f342c573888a4fa57eab8c1c4213072ca1735f2bb2d5a7f5621ddb31567e479d' "$ARCHIVE" | sha256sum -c - printf '%s %s\n' '1d235f8f2918fdee61e707955828a0b1' "$ARCHIVE" | md5sum -c - ya tool yt --proxy kolmogorov read-file "$YT_ROOT/archive.tar.xz" >models.archive.tar.xz printf '%s %s\n' 'bd8374f2bec462d9cad0044bb410e621' 'models.archive.tar.xz' | md5sum -c - ``` Архив сервиса: 14298411371 bytes. Архив моделей: 6628264024 bytes; MD5 взята из текущего YT-атрибута файла. Hash model.npz ниже соответствует весам, на которых выполнен успешный локальный запуск. При несовпадении считайте это другим/повреждённым артефактом и остановитесь. ## 2. Распаковка и выбор нужных артефактов Выполняется оператором только после проверки скачанных архивов: ```bash mkdir snapshot layer prod2-configs tar --zstd -tf "$ARCHIVE" >/dev/null tar --zstd -xf "$ARCHIVE" -C snapshot (cd snapshot && sha256sum -c SHA256SUMS) DAEMON_LAYER="$PWD/snapshot/sandbox/13662792630/payload/yabs-rephrase-transformer-service.layer.837ce4247535af89ac662d31b1863d21f5ced191.YABSRANKING-1111-cuda129-freeze.tar.zst" PROD2_LAYER="$PWD/snapshot/sandbox/12691698810/payload/yabs-rephrase-transformer-service.layer.e182ae2587bee4967621bf2df3c0a5850ad554eb.trunk.tar.zst" tar --zstd -xf "$DAEMON_LAYER" -C layer workloads/ml_inference/daemon tar --zstd -xf "$PROD2_LAYER" -C prod2-configs "workloads/ml_inference/model_configs/$MODEL/config.yaml" tar -xJf models.archive.tar.xz -C "$PWD" "models/$MODEL" printf '%s %s\n' 'ed16cb05521e31d9df5431f34073600ce90cb78b2c1dfb9de428e92d39134a1e' "models/$MODEL/model/model.npz" | sha256sum -c - chmod u+x layer/workloads/ml_inference/daemon ldd layer/workloads/ml_inference/daemon ./layer/workloads/ml_inference/daemon --help >daemon-help.txt ``` Не запускайте deployment init/start scripts на обычном pod: они предполагают системные `/workloads`, `/rex` и другие пути. Здесь daemon запускается напрямую с абсолютными путями в отдельной рабочей папке. ## 3. Подготовка CPU repository и config Сохраните следующий Python-скрипт и выполните из того же workdir. Он повторно проверяет hash/metadata модели, извлекает vocabulary из имеющихся весов, создаёт symlink на локальные данные и адаптирует архивный config. Файлы исходной модели не изменяются. Известный старый setup_repo не используется: он создал неподходящее для нашей структуры дерево; repository здесь строится явно. ```bash cat >prepare.py <<'PY_PREPARE' from pathlib import Path import json,zipfile,hashlib b=Path.cwd().resolve();model='RT_ALTERNATIVE_TEXTS_GENERATION_BIBABOBA_FOUR_TITLES' dump=b/'models'/model/'model';weight=dump/'model.npz' h=hashlib.sha256() with weight.open('rb') as f: for chunk in iter(lambda:f.read(8*1024*1024),b''):h.update(chunk) assert h.hexdigest()=='ed16cb05521e31d9df5431f34073600ce90cb78b2c1dfb9de428e92d39134a1e','Wrong/corrupted weights; stop' meta=json.loads((b/'models'/model/'meta/meta.json').read_text()) assert meta['model']['model_name']==model,meta['model'] with zipfile.ZipFile(weight) as z:(b/'vocab-from-dump.model').write_bytes(z.read('bpe.voc')) config=(b/'prod2-configs/workloads/ml_inference/model_configs'/model/'config.yaml').read_text() changes=[(' gpu_backend:\n gpu_ids:\n - 0\n use_cublas_lt: true\n use_decoder_fused_attention: true\n',' cpu_backend:\n num_backends: 1\n convert_float16_to_float32: true\n'),(' batch_size: 32\n',' batch_size: 1\n'),(' host_mem_chunk_size_mi_b: 2048\n',' host_mem_chunk_size_mi_b: 256\n'),(' response_timeout_ms: 70\n',' response_timeout_ms: 30000\n'),(' min_generation_time_ms: 10\n',' min_generation_time_ms: 1000\n'),(' max_out_len: 100\n',' max_out_len: 40\n'),(' vocab:\n yt_path:\n path: //home/gena/vocabs/sp_20230413_128k_vital__1533_span_tokens.model\n cluster: hahn\n',' vocab:\n local_path: '+json.dumps(str(b/'vocab-from-dump.model'))+'\n')] for old,new in changes: assert old in config,'Unexpected source config, stop: '+old.splitlines()[0] config=config.replace(old,new,1) version=b/'repo'/model/'1';version.mkdir(parents=True) (version/'model').symlink_to(dump,target_is_directory=True) configs=b/'configs'/model;configs.mkdir(parents=True) for path in [version/'config.yaml',version/'model_config.yaml',version.parent/'config.yaml',configs/'config.yaml',configs/'model_config.yaml']:path.write_text(config) required='models:\n - name: '+model+'\n' (b/'required_models.yaml').write_text(required);(b/'repo/required_models.yaml').write_text(required) (b/'local.cfg').write_text('''ProgramConfig: { ServerConfig: { Port: 18080 RequestThreads: 2 } YtServerConfig { Port: 18080 InflightLimit: 4 ServiceName: "rephrase-local" } CPUConfig: { InfraVCPUGuarantee: 1000 } } RuntimeConfig: { InferencePoolSize: 1 SharedBatcherConfig: { BatchingThreads: 1 } PinnedExecutorConfig: { MaxActiveInstances: 1 } ModelRepositoryConfig: { Path: '''+json.dumps(str(b/'repo'))+''' ConfigsPath: '''+json.dumps(str(b/'configs'))+''' } RequiredModelsManagerConfig: { ManagedByReloader: false ConfigPath: '''+json.dumps(str(b/'required_models.yaml'))+''' } } ''') request={'inputs':[{'name':'request','shape':[1],'datatype':'BYTES','data':['Переформулируй рекламное объявление. Оригинальное объявление: Магазин сантехники. Большой выбор, доставка по России. Запрос: купить смеситель. Переформулировка: ']}]} (b/'request.json').write_text(json.dumps(request,ensure_ascii=False,indent=2)+'\n') print('Prepared CPU repository from verified local dump:',meta['model']) PY_PREPARE python3 prepare.py ``` Скрипт подготовки проверен на уже имеющихся файлах: полученные server/model configs совпадают с конфигурациями успешного запуска после замены абсолютного корня. End-to-end скачивание и запуск в новом pod не выполнялись; исходное успешное испытание использовало локально сохранённые артефакты. Структура после подготовки: ```text layer/workloads/ml_inference/daemon models//model/model.npz models//meta/meta.json repo/required_models.yaml repo//config.yaml repo//1/config.yaml repo//1/model -> models//model configs//config.yaml vocab-from-dump.model required_models.yaml local.cfg request.json ``` ## 4. Запуск В первом терминале из workdir: ```bash ulimit -c 0 ./layer/workloads/ml_inference/daemon -c local.cfg -g 0 >daemon.stdout.log 2>daemon.stderr.log ``` Это foreground-процесс. `-g 0` отключает gRPC. HTTP port — 18080; YT HTTP server в этой версии слушает wildcard address, поэтому запускайте в изолированном диагностическом pod, не публикуйте его как внешний endpoint. Monitoring exporter может открыть отдельный порт 3401. Из другого терминала перейдите в тот же workdir и проверьте: ```bash curl --fail --noproxy '*' http://127.0.0.1:18080/ready curl --fail --noproxy '*' http://127.0.0.1:18080/v2/health/ready curl --fail --noproxy '*' http://127.0.0.1:18080/v2/models/RT_ALTERNATIVE_TEXTS_GENERATION_BIBABOBA_FOUR_TITLES/ready curl --fail --noproxy '*' http://127.0.0.1:18080/v2/models/RT_ALTERNATIVE_TEXTS_GENERATION_BIBABOBA_FOUR_TITLES >metadata.json ``` В логах должно быть `Loaded model successfully`, а readiness конкретной модели — HTTP 200. Readiness без реальной модели не засчитывается. ## 5. Настоящий inference и декодирование ```bash curl --fail --noproxy '*' --max-time 65 \ -H 'Content-Type: application/json' --data-binary @request.json \ http://127.0.0.1:18080/v2/models/RT_ALTERNATIVE_TEXTS_GENERATION_BIBABOBA_FOUR_TITLES/1/infer >response.json ``` Выход responses в этой версии имеет encoding=unitednn: JSON содержит Base64 protobuf, а не готовую строку. Следующий небольшой декодер извлекает CHAR values по offsets из tensor.proto (`yt/ml/unn/cpp_lib/tensor/serialization/proto/tensor.proto`). Он предназначен для этого проверенного ответа, не универсален для любых типов/батчей. ```bash cat >decode_response.py <<'PY_DECODE' import base64,json from pathlib import Path def varint(b,p): v=s=0 while True: c=b[p];p+=1;v|=(c&127)<63:raise ValueError('Invalid varint') def fields(b): p=0;result={} while p>3;wire=tag&7 if wire==0:v,p=varint(b,p) elif wire==2: size,p=varint(b,p);v=b[p:p+size];p+=size elif wire in (1,5):size=8 if wire==1 else 4;v=b[p:p+size];p+=size else:raise ValueError('Unsupported protobuf wire type') result.setdefault(n,[]).append(v) return result response=json.loads(Path('response.json').read_text());texts=[] for output in response['outputs']: if output.get('parameters',{}).get('encoding')!='unitednn':continue for encoded in output['data']: tensor=fields(base64.b64decode(encoded));header=fields(tensor[1][0]);values=fields(header[4][0]);assert values[3][0]==5,'Expected CHAR' offset=values.get(1,[0])[0];size=values[2][0];data=tensor[2][0] assert offset+size<=len(data) texts.append(data[offset:offset+size].decode('utf-8')) Path('decoded-response.txt').write_text('\n'.join(texts)+'\n');print('\n'.join(texts)) PY_DECODE python3 decode_response.py ``` В выполненном испытании модель выдала строку с несколькими вариантами, разделёнными `[SEP]`, например «Оригинальное объявление: магазин сантехники». Точный текст не обязан воспроизводиться побайтово; качество и формат production-запросов отдельно не проверялись. ## 6. Остановка и диагностика Остановка локального foreground-процесса — Ctrl+C в терминале запуска. Не используйте массовый pkill и не останавливайте существующие production workloads. - `Address already in use`: порт занят; найдите свой процесс, не убивайте чужой. После остановки дождитесь освобождения порта. - `Operation not permitted` при bind: runtime policy запрещает сокет; используйте согласованный pod/runtime с разрешённым локальным listener. - `bad model, should have ensemble or model config`: проверьте наличие именно `config.yaml` и сохранённые секции `model_config`/`backend_custom_config`; одного model_config.yaml в нашем случае не хватало. - Config parse error: в proto ServerConfig вложен в ProgramConfig. `Grpc` не является полем ServerConfig в этом бинарнике; используйте `-g 0`. Не задавайте одновременно InferencePoolSize и UseExternalPool: это один oneof. - Required-model manager ждёт модель: смотрите loader errors, symlink и путь к model.npz, а не включайте dummy readiness. - Ошибка library/ELF loader: сопоставьте ldd и совместимость Linux/glibc с сохранённым binary; текущий checkout не обязателен. - Проверка checksum не прошла: остановитесь и выясните происхождение артефакта; не обходите проверку. Не подменяйте FOUR_TITLES весами из папки NEURO: в исследованном локальном наборе metadata NEURO фактически описывала NEURO_USE_LINK. Другие модели не проверялись в этом запуске. Это диагностический CPU-вариант: prod-neuro binary + prod2 config + существующий dump FOUR_TITLES. Это не точное восстановление prod-neuro/prod2 stages и не гарантия качества production inference. Действия с Deploy, dctl put и восстановление файловых систем production pods этой инструкцией не предусмотрены.