Onderwijstoren Thais: ons plan voor een TowerInstruct LoRA

geplaatst in: Uncategorized | 0

Bij 3DN voeren we al lokale inference uit op een lokale workstation GPU (RTX 3090) voor machinevertaling van familieportaalsites en voor hybride AI-engineering. Bulkvertalingen EN→NL en EN→ZH gaan via Tower op Ollama; Thais is de zwakke schakel, dus we vertrouwen vandaag op Typhoon voor TH. De volgende stap is niet “meer API’s kopen” — het is een opzettelijk LoRA-experiment op dezelfde infrastructuur, onder ons eigen dak, voor digitale soevereiniteit en lagere tokenkosten op de lange termijn.

Dit bericht is het openbare plan: waar we op trainen, waarom dat basismodel, hoe we de productie veilig houden en waar het werk van Unbabel past. De fragmenten hieronder zijn de vorm van het lab — geen kookboek waar je dingen in plakt en vergeet.

Waarom een LoRA?

Volledige fine-tuning van vertaalmodellen met miljarden parameters zijn duur en gemakkelijk te overfitten. Een LoRA (low-rank adapter) traint een dunne set matrices bovenop een bevroren basis. Voor een bureau dat al Tower-gewichten host, is dit de juiste leercurve: meetbare EN→TH-winst, een Hugging Face-adapter die we kunnen publiceren, en een optionele fusie later als we een speciale Ollama-pin willen.

We trainen niet de GGUF-blob die Ollama dagelijks serveert. Training gebruikt Hugging Face / PEFT-stijlgewichten; serveren kan op het huidige Tower-Plus Q4-pad blijven totdat eval anders zegt.

# Mentaal model: bevries de basis, train dunne adapters
# ΔW ≈ B @ A  met rank r << d_model
# Alleen A, B (en misschien biases) krijgen gradiënten.

from peft import LoraConfig, get_peft_model, TaskType

lora = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],  # begin klein; uitbreiden als onderfit
    lora_dropout=0.05,
    bias="none",
    task_type=TaskType.CAUSAL_LM,
)
# model = get_peft_model(base_model, lora)
# model.print_trainable_parameters()  # verwacht ~0.1–1% trainbaar

Basismodel: TowerInstruct-7B v0.2

Community-advies (en onze eigen lezing) wijst op het starten met TowerInstruct, niet een generiek chat LLM. De familie is gebouwd voor vertaalvormige prompts — hetzelfde patroon dat we al op het bureau gebruiken.

We installeren Unbabel/TowerInstruct-7B-v0.2 als het eerste trainingsbasis:

  • 7B past comfortabel op QLoRA op een desktop 3090 met ruimte om te itereren.
  • v0.2 boven v0.1 voor het schoner instruct/MT-gedrag.
  • 13B of Tower-Plus-9B alleen nadat het recept met 7B werkt — meer capaciteit, langzamere lussen.

De Tower-lijn van Unbabel is te vinden op unbabel.com. Openbare gewichten: Hugging Face Unbabel/TowerInstruct-7B-v0.2.

# Lab-indeling op een lokale werkstation (HF-gewichten, geen Ollama GGUF)
# .../tower-lora/
#   modellenTowerInstruct-7B-v0.2   # safetensors-fragmenten
#   datasetsen-th                 # jsonl-paren
#   adaptersen-th-r16             # PEFT-uitvoer
#   uit

# Fase downloaden op een build-host, vervolgens LAN-kopiëren (multi-GB, offline later trainen):
python -c "from huggingface_hub import snapshot_download; 
snapshot_download('Unbabel/TowerInstruct-7B-v0.2', 
local_dir='/var/tmp/hf-models/TowerInstruct-7B-v0.2')"

Gegevensvorm (programmeurs houden van schema’s)

Één rij, één taak. Stem overeen met de prompt-stijl die we al gebruiken in productie MT, zodat trainen ≈ serveren:

{
  "instruction": "Vertaal de volgende Engelse brontekst naar het Thais.",
  "input": "Every voter deserves a market.",
  "output": "ผู้มีสิทธิเลือกตั้งทุกคนสมควรได้รับตลาด"
}
# Tiny loader schets — holdout is heilig
import json
from pathlib import Path

def load_jsonl(path: Path):
    rijen = []
    for regel in path.read_text(encoding="utf-8").splitlines():
        if regel.strip():
            rijen.append(json.loads(regel))
    return rijen

def split_holdout(rijen, frac=0.1, seed=7):
    import random
    rng = random.Random(seed)
    idx = list(range(len(rijen)))
    rng.shuffle(idx)
    n = max(1, int(len(rijen) * frac))
    hold = {idx[i] voor i in range(n)}
    train = [rijen[i] voor i in idx als i niet in hold]
    test  = [rijen[i] voor i in idx als i in hold]
    return train, test

# Verkies: onze EN-tafelkopie + Typhoon-ontwerp + lichte menselijke gouden bewerking
# Vermijd: onbewerkte bulk MT-soep, HTML-fragmenten, gemengde tweetalige regels

Hoe we het werk zullen aanpakken

  1. Installeer de HF-basis op een lokale werkstation — volledige safetensors-boom onder ... / tower-lora / modellen (download in fasen op onze build-host, vervolgens LAN-kopiëren).
  2. Gegevens vóór rang — enkele honderden tot een paar duizend schone EN→TH-paren; houd ~10% voor altijd apart.
  3. Train QLoRA — rang 8–16, korte context, één zware taak op de 3090 tegelijk (laad concurrerende Ollama-modellen uit).
  4. Eerlijk evalueren — basis Tower vs LoRA (en later Typhoon); chrF/BLEU als richtlijn, mensen voor namen en producttermen. De eerste holdout-tabel staat hierboven.
  5. Publiceren wanneer het wint — Hugging Face-adapterkaart; pas dan samenvoegen→GGUF / optionele pin lokale werkstation-tower-th.
# QLoRA load sketch (4-bit basis + LoRA) — 3090-vriendelijk
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

bnb = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

base_id = r"…/tower-lora/modelsTowerInstruct-7B-v0.2"
tok = AutoTokenizer.from_pretrained(base_id, use_fast=True)
model = AutoModelForCausalLM.from_pretrained(
    base_id,
    quantization_config=bnb,
    device_map="auto",
    torch_dtype=torch.bfloat16,
)
# dan get_peft_model(model, lora_config) en Trainer / aangepaste lus
# Vorm van de productie MT-prompt die we al gebruiken (train moet rijmen)
def tower_prompt(en: str, lang_name: str = "Thai") -> str:
    return (
        f"Vertaal de volgende Engelse brontekst naar {lang_name}.n"
        f"Engels: {en.strip()}n"
        f"{lang_name}:n"
    )

# Regel voor de werkplek totdat LoRA de holdout wint:
#   NL/ZH  -> lokale werkstation-toren (Tower-Plus)
#   TH     -> lokale Typhoon-vertaling
#   oordeel / SEO -> grens Grok (niet bulk-MT)
# Shipper: vertrouw nooit op vibes
def beter_dan_baseline(scores: dict) -> bool:
    # scores = {"tower": chrF, "lora": chrF, "typhoon": chrF}
    return scores["lora"] > scores["tower"] + 0.5  # marge TBD op echte holdout

# als niet beter_dan_baseline(...): houd Typhoon in productie; LoRA blijft lab

Het productie MT-beleid blijft dual-track totdat de evaluatie anders zegt: Tower voor NL/ZH, Typhoon voor TH, grens Grok voor oordeel — niet voor bulkvertaling.

Hoe succes eruitziet

  • Een reproduceerbaar trainscript en datasetschema op een lokale werkplek.
  • Een adapter die de standaard TowerInstruct verslaat op onze Thai holdout zonder paren te breken die we later toevoegen.
  • Een Hugging Face-kaart die anderen (en toekomstige wij) kunnen laden met PEFT op dezelfde basis.
  • Geen romantiek: als Typhoon nog steeds wint, blijft de TH-productie op Typhoon.
# Na train — wat we verwachten op schijf
# adapters/en-th-r16/
#   adapter_config.json
#   adapter_model.safetensors
#   README.md   # basis-id, paar, r, data-grootte, licentie, evaluatietabel

Compute en ops

Dit is managed hosting en lab compute op metaal dat we controleren — dezelfde filosofie als hybride codering agenten en lokale Flux-duimen. Lange sessies profiteren nog steeds van zorgvuldige promptcache en sessiecontinuïteit op het director-model; de LoRA-taak zelf is een begrensde GPU-batch, geen altijd-aan API.

Op de financiële ruggengraat van het gezin is dezelfde eerlijkheid van toepassing: virtuele tegoeden en DutchBud / fintech-sporen werken alleen wanneer de staat de claims ondersteunt. Een vertaaladapter wordt alleen “verzonden” wanneer de evaluatie overeenkomt met de kaart.

# Eén zware taak op de 3090 — niet co-hosten van train + Flux + Ollama-14B
# (ops-checklist, geen poëzie)
# 1) stop concurrerende GPU-gebruikers
# 2) train QLoRA
# 3) schrijf adapter
# 4) maak VRAM vrij, herstel Ollama-bureaubladdraadjes

Status — eerste labnummers (rook, geen productie)

De eerste QLoRA-adapter is op schijf. Trainset: 110 samengestelde EN→TH-paren; holdout: 20 ongezien regels. Receptuur: TowerInstruct-7B-v0.2-basis, rang r=16 op q/k/v/o, ChatML-prompts, 3 epochen, trainverlies ≈ 1,77 (~3,5 minuten na gewichtsbelasting op een enkele desktop 3090).

Holdout automatische scores (sacrebleu-corpus BLEU / chrF, hebzuchtige decode, EOS-stop):

Model BLEU chrF
Basis TowerInstruct-7B-v0.2 (4-bit) 2,06 14,54
Basis + EN→TH LoRA r16 2,88 16,22
Delta +0,82 +1,68

Lees dit koud: de adapter verslaat de standaard Tower op deze kleine holdout met een kleine marge. Absolute scores zijn nog steeds laag — de basis was zwak op onze Thaise desktop en 110 rijen is een pijplijnbewijs, geen productiecorpus. We flippen het Thaise MT van de familieplek niet van Typhoon op basis van deze nummers. De scheepspoort uit het plan blijft gelden: LoRA vervangt Typhoon alleen wanneer holdout + menselijke beoordeling dit zeggen op een grotere gouden set.

Wat we wel bewezen hebben: QLoRA train → PEFT-adaptersave → basis-vs-adapter-evaluatie met BLEU/chrF op dezelfde werkstationsstapel, offline nadat de HF-gewichtsboom lokaal is. De volgende labstappen zijn meer EN→TH goud (en zorgvuldige Typhoon-ontwerp + bewerken), geen romantische claims.

Verder lezen: Unbabel · TowerInstruct-7B-v0.2 op Hugging Face.

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *