docker / build (push) Failing after 39s
- Docker: Dockerfile (CPU), compose с FlareSolverr, настройка через DUB_* переменные, токен доступа к /predict, tools/pack_models.py, workflow Gitea Actions, docs/docker.md - Модель грузится офлайн (config.json рядом с весами); общий код вынесен в src/spans.py, сервису не нужны sklearn/matplotlib/pandas - Обучение: профили tiny2/base/large/modern, запуски в models/runs, реестр и честное сравнение на одной тестовой выборке (src/runs.py), пункт меню «Модели» - Датасет: язык и аудио-параметры дорожек, 18+ по строкам «Аудио», ручные метки по ссылкам, переиндексация сохранённого HTML, параллельная сборка, сбалансированная выборка - Тексты страниц (data/pages) больше не хранятся в git Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
89 lines
4.3 KiB
Python
89 lines
4.3 KiB
Python
"""Упаковка моделей для Docker: models/best + models/segmenter -> models-bundle.tar.gz.
|
|
|
|
python tools/pack_models.py # models-bundle.tar.gz в корне проекта
|
|
python tools/pack_models.py --out dist/m.tar.gz # другой путь
|
|
python tools/pack_models.py --stage docker-models # только каталог docker-models/models (для docker build)
|
|
|
|
В архив попадает только нужное сервису: веса, токенизатор, label_maps, конфиги. Графики, eval.json,
|
|
архивные копии и запуски обучения не берутся. У старых чекпоинтов без config.json конфиг основы
|
|
берётся из кэша Hugging Face (нужен один раз на машине, где лежит модель) — после этого сервис
|
|
работает полностью офлайн.
|
|
|
|
Распаковка: tar -xzf models-bundle.tar.gz (даёт каталог models/)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import shutil
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
MAIN_FILES = ("model.pt", "dub_config.json", "label_maps.json", "tokenizer.json", "tokenizer_config.json",
|
|
"config.json", "special_tokens_map.json", "vocab.txt")
|
|
SEG_FILES = ("config.json", "model.safetensors", "tokenizer.json", "tokenizer_config.json",
|
|
"special_tokens_map.json", "vocab.txt")
|
|
|
|
|
|
def stage(dest: Path, main: Path, seg: Path) -> list[str]:
|
|
"""Собирает dest/models/{best,segmenter}; возвращает список предупреждений."""
|
|
notes: list[str] = []
|
|
if not (main / "model.pt").exists():
|
|
raise SystemExit(f"нет {main / 'model.pt'}: сначала обучите и выберите модель (tools/collect.py, пункт 7)")
|
|
out_main, out_seg = dest / "models" / "best", dest / "models" / "segmenter"
|
|
out_main.mkdir(parents=True)
|
|
for name in MAIN_FILES:
|
|
if (main / name).exists():
|
|
shutil.copy2(main / name, out_main / name)
|
|
if not (out_main / "config.json").exists():
|
|
import json
|
|
|
|
from transformers import AutoConfig
|
|
backbone = json.loads((main / "dub_config.json").read_text(encoding="utf-8"))["backbone"]
|
|
AutoConfig.from_pretrained(backbone).save_pretrained(out_main)
|
|
notes.append(f"config.json основы {backbone} взят из кэша Hugging Face")
|
|
if seg.exists():
|
|
out_seg.mkdir(parents=True)
|
|
for name in SEG_FILES:
|
|
if (seg / name).exists():
|
|
shutil.copy2(seg / name, out_seg / name)
|
|
else:
|
|
notes.append(f"нет {seg}: сервис будет сегментировать страницы правилами, а не моделью")
|
|
return notes
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--model", default=str(ROOT / "models" / "best"))
|
|
ap.add_argument("--segmenter", default=str(ROOT / "models" / "segmenter"))
|
|
ap.add_argument("--out", default=str(ROOT / "models-bundle.tar.gz"))
|
|
ap.add_argument("--stage", help="только подготовить каталог <stage>/models и не архивировать")
|
|
args = ap.parse_args()
|
|
|
|
if args.stage:
|
|
dest = Path(args.stage)
|
|
shutil.rmtree(dest / "models", ignore_errors=True)
|
|
dest.mkdir(parents=True, exist_ok=True)
|
|
for n in stage(dest, Path(args.model), Path(args.segmenter)):
|
|
print("•", n)
|
|
(dest / "models" / ".gitkeep").touch() # каталог остаётся в git пустым
|
|
print(f"готово: {dest / 'models'}")
|
|
return
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
for n in stage(Path(tmp), Path(args.model), Path(args.segmenter)):
|
|
print("•", n)
|
|
out = Path(args.out)
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
with tarfile.open(out, "w:gz") as tar:
|
|
tar.add(Path(tmp) / "models", arcname="models")
|
|
print(f"готово: {out} ({out.stat().st_size / 1e6:.0f} МБ)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|