File size: 2,454 Bytes
22de727
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
"""Fetch + convert the LinguaBridge many-to-many translation model (one-time setup).

Downloads facebook/nllb-200-distilled-600M from Hugging Face and converts it to
an int8 CTranslate2 model at models/nllb200-600M-ct2 (~600MB on disk, ~1GB RAM
at inference, ~100-300ms/sentence on CPU). The site's translator uses it for
all 110 language directions natively (no English pivot, no API key), with the
persistent translation_cache still freezing every output for determinism.

Setup step, like src/miniweb/scripts/fetch_brand_fonts.py — run once per machine/deploy:
    ~/.conda/envs/miniweb/bin/python src/miniweb/scripts/fetch_translation_model.py

The model directory is gitignored; on Railway either bake this into the image
build or put models/ on the persistent volume. Without the model the site
falls back to the LLM (if keyed) and then the built-in dictionaries.
"""
import pathlib
import shutil
import subprocess
import sys

ROOT = pathlib.Path(__file__).resolve().parents[3]   # the repository
MODEL_DIR = ROOT / "models" / "nllb200-600M-ct2"
HF_REPO = "facebook/nllb-200-distilled-600M"
# the weights every cached translation came from; with transformers 4.57.6 and ctranslate2 4.8.1 the conversion
# gives the same model.bin byte for byte (deploy/space/Dockerfile pins those)
HF_REVISION = "f8d333a098d19b4fd9a8b18f94170487ad3f821d"


def main():
    if (MODEL_DIR / "model.bin").exists() and (MODEL_DIR / "sentencepiece.model").exists():
        print(f"model already present at {MODEL_DIR}")
        return

    from huggingface_hub import snapshot_download
    print(f"downloading {HF_REPO} ...")
    src = snapshot_download(HF_REPO, revision=HF_REVISION, allow_patterns=[
        "*.json", "*.model", "pytorch_model.bin", "*.safetensors", "*.txt"])
    print("converting to CTranslate2 int8 ...")
    MODEL_DIR.parent.mkdir(exist_ok=True)
    tmp = str(MODEL_DIR) + ".tmp"
    subprocess.run([
        sys.executable, "-m", "ctranslate2.converters.transformers",
        "--model", src, "--output_dir", tmp,
        "--quantization", "int8", "--force",
    ], check=True)
    # keep the sentencepiece tokenizer next to the model
    sp = pathlib.Path(src) / "sentencepiece.bpe.model"
    shutil.copy2(sp, pathlib.Path(tmp) / "sentencepiece.model")
    shutil.move(tmp, MODEL_DIR)
    size = sum(f.stat().st_size for f in MODEL_DIR.rglob("*")) / 1e6
    print(f"done: {MODEL_DIR} ({size:.0f} MB)")


if __name__ == "__main__":
    main()