Stand: 2026-03-03
Diese Anleitung ist ein praxisnahes Playbook fuer DONUT/TroCR-Training in PechaBridge. Sie ist bewusst sehr detailliert und erklaert:
- wie du schnell einen stabilen Tiny-Run aufsetzt,
- warum dieser Tiny-Schritt gegen Empty-Predictions/Collapse hilft,
- wie du danach sauber in einen Full-Run gehst (gray oder rgb),
- wie du Logs richtig interpretierst.
Die Befehle beziehen sich auf python cli.py train-donut-ocr.
Beim DONUT OCR Training gibt es drei haeufige Probleme:
-
Empty Predictions: Das Modell gibt nach Decoding leere Strings aus (
empty_predhoch, CER oft 100%). -
Special-Token-Loop: Das Modell wiederholt fast nur
<s_ocr>/</s_ocr>/<pad>oder beendet sofort. -
Repetitive Text-Loops: Das Modell produziert lange Wiederholungen (z. B. immer dieselbe Silbe), CER kann > 100% werden.
Der Tiny-Schritt ist ein frueher Stresstest, der genau diese Failure-Modes sichtbar macht, bevor du sehr viel GPU-Zeit in einen Full-Run investierst.
Tiny-Pretraining ist kein "magischer Trick", sondern eine kontrollierte Diagnose- und Stabilitaetsphase:
- Du validierst Datenpfade, Manifest-Felder, Tokenizer und Label-Format auf kleinem Datensatz.
- Du siehst frueh, ob das Modell in Empty-/Special-Token-Loops kippt.
- Du erhaeltst einen warmen, bereits stabilen Start-Checkpoint fuer den Full-Run.
In der Praxis reduziert das das Risiko, dass Full-Runs frueh kollabieren oder stundenlang in unbrauchbaren Regimen trainieren.
- Installierte Abhaengigkeiten:
pip install -r requirements.txt- Lokale BoSentencePiece-Tokenizer-Dateien:
- In diesem Training ist BoSentencePiece verpflichtend.
- Fallback auf beliebige AutoTokenizer ist absichtlich deaktiviert.
- Wenn der Tokenizer nicht passt, bricht das Skript frueh mit Fehler ab (gewollt).
- Verfuegbare Manifeste:
- Train JSONL (line-image + text)
- Val JSONL
- Wichtige Eingabefelder in JSONL:
- Bild:
line_path,src__image,image,image_path, ... - Text:
text,src__label,label,transcription, ...
--image_preprocess_pipeline unterstuetzt:
gray:
- Graustufe via
min_rgb, ohne harte Binarisierung. - Oft ein robuster Baseline-Start.
rgb:
- RGB-Line-Scan Cleanup, farberhaltend (z. B. rot + schwarz).
- Keine harte Binarisierung per Default.
- Typisch fuer gemischte Tintenfarben.
bdrc:
- Graustufe + adaptive Binarisierung.
- Eher klassischer OCR-Preproc-Stil.
Empfehlung:
- Wenn rot/schwarz wichtig:
rgbtesten. - Parallel immer
grayals solide Vergleichsbasis laufen lassen.
Beispiel: aus grossen Manifests einen kleinen, reproduzierbaren Tiny-Split erzeugen.
export TRAIN_MANIFEST=/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/train/meta/lines.jsonl
export VAL_MANIFEST=/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/eval/meta/lines.jsonl
export TINY_DIR=/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines_tiny
mkdir -p "$TINY_DIR/train/meta" "$TINY_DIR/eval/meta"
python - <<'PY'
import random
from pathlib import Path
seed = 42
train_n = 512
val_n = 128
train_src = Path("/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/train/meta/lines.jsonl")
val_src = Path("/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/eval/meta/lines.jsonl")
train_dst = Path("/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines_tiny/train/meta/lines.jsonl")
val_dst = Path("/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines_tiny/eval/meta/lines.jsonl")
def sample_jsonl(src: Path, dst: Path, n: int, seed: int) -> None:
rows = [ln for ln in src.read_text(encoding="utf-8").splitlines() if ln.strip()]
rng = random.Random(seed)
if len(rows) <= n:
picked = rows
else:
idx = list(range(len(rows)))
rng.shuffle(idx)
picked = [rows[i] for i in sorted(idx[:n])]
dst.write_text("\n".join(picked) + "\n", encoding="utf-8")
sample_jsonl(train_src, train_dst, train_n, seed)
sample_jsonl(val_src, val_dst, val_n, seed + 1)
print("wrote", train_dst, val_dst)
PYZiel: pruefen, ob Setup stabil lernt und nicht kollabiert.
Beispiel gray:
CUDA_VISIBLE_DEVICES=0 python cli.py train-donut-ocr \
--train_manifest /home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines_tiny/train/meta/lines.jsonl \
--val_manifest /home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines_tiny/eval/meta/lines.jsonl \
--output_dir /dev/shm/donut_tiny_overfit_from_scratch_gray \
--model_name_or_path microsoft/trocr-base-stage1 \
--tokenizer_path /home/ubuntu/data/PechaBridge/ext/BoSentencePiece \
--image_preprocess_pipeline gray \
--image_size 384 \
--max_target_length 160 \
--generation_max_length 160 \
--generation_min_new_tokens 0 \
--per_device_train_batch_size 16 \
--per_device_eval_batch_size 16 \
--gradient_accumulation_steps 1 \
--learning_rate 4e-5 \
--weight_decay 0.01 \
--num_train_epochs 30 \
--warmup_steps 40 \
--eval_steps 20 \
--save_steps 20 \
--save_total_limit 10 \
--num_workers 8 \
--seed 42 \
--val_eval_max_samples 128Analog fuer rgb nur --image_preprocess_pipeline rgb und anderer --output_dir.
eval_cersinkt ueber Zeit.empty_pred=0.eval_pred_repetitive_ratiobleibt niedrig.eval_pred_avg_lennaehrt sicheval_ref_avg_len.eval_token_forensicszeigt:
- hohe Token-Diversitaet (
token_unique_nonpad), - Special-Token-Anteil in Pred nahe Label-Verteilung.
empty_pred_ratiohoch oder oszillierend Richtung 1.0.eval_pred_repetitive_ratiostark steigend.top_tokens_nonpadwird von wenigen Tokens dominiert.- CER bleibt lange bei ~100% oder springt auf sehr hohe Werte mit Loops.
Nimm den Checkpoint mit:
- niedrigster stabiler CER (nicht nur ein Ausreisser),
empty_pred=0,repetitive_rationahe 0,- plausibler Pred-Length (in der Naehe der Ref-Length).
Typisch:
/dev/shm/donut_tiny_overfit_from_scratch_gray/checkpoint-XXX/dev/shm/donut_tiny_overfit_from_scratch_rgb/checkpoint-YYY
export TRAIN_MANIFEST=/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/train/meta/lines.jsonl
export VAL_MANIFEST=/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/eval/meta/lines.jsonl
export TOKENIZER=/home/ubuntu/data/PechaBridge/ext/BoSentencePiece
export BEST_GRAY=/dev/shm/donut_tiny_overfit_from_scratch_gray/checkpoint-480
CUDA_VISIBLE_DEVICES=0 python cli.py train-donut-ocr \
--train_manifest "$TRAIN_MANIFEST" \
--val_manifest "$VAL_MANIFEST" \
--output_dir /home/ubuntu/data/PechaBridge/models/donut_full_gray_from_tiny \
--model_name_or_path "$BEST_GRAY" \
--image_processor_path /dev/shm/donut_tiny_overfit_from_scratch_gray/image_processor \
--tokenizer_path "$TOKENIZER" \
--image_preprocess_pipeline gray \
--image_size 384 \
--max_target_length 160 \
--generation_max_length 160 \
--generation_min_new_tokens 0 \
--per_device_train_batch_size 40 \
--per_device_eval_batch_size 40 \
--gradient_accumulation_steps 1 \
--learning_rate 3e-5 \
--weight_decay 0.01 \
--num_train_epochs 80 \
--warmup_steps 1000 \
--eval_steps 1000 \
--save_steps 1000 \
--save_total_limit 5 \
--num_workers 8 \
--seed 42 \
--val_eval_max_samples 300Nur diese Werte wechseln:
--model_name_or_path "$BEST_RGB"--image_processor_path /dev/shm/donut_tiny_overfit_from_scratch_rgb/image_processor--image_preprocess_pipeline rgb- eigener
--output_dir
export TRAIN_MANIFEST=/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/train/meta/lines.jsonl
export VAL_MANIFEST=/home/ubuntu/data/PechaBridge/datasets/openpecha_ocr_lines/eval/meta/lines.jsonl
export TOKENIZER=/home/ubuntu/data/PechaBridge/ext/BoSentencePiece
export BEST_RGB=/dev/shm/donut_tiny_overfit_from_scratch_rgb/checkpoint-480
CUDA_VISIBLE_DEVICES=1,2,3 torchrun --standalone --nproc_per_node=3 cli.py train-donut-ocr \
--train_manifest "$TRAIN_MANIFEST" \
--val_manifest "$VAL_MANIFEST" \
--output_dir /home/ubuntu/data/PechaBridge/models/donut_full_rgb_from_tiny \
--model_name_or_path "$BEST_RGB" \
--image_processor_path /dev/shm/donut_tiny_overfit_from_scratch_rgb/image_processor \
--tokenizer_path "$TOKENIZER" \
--image_preprocess_pipeline rgb \
--image_size 384 \
--max_target_length 160 \
--generation_max_length 160 \
--generation_min_new_tokens 0 \
--per_device_train_batch_size 40 \
--per_device_eval_batch_size 40 \
--gradient_accumulation_steps 1 \
--learning_rate 3e-5 \
--weight_decay 0.01 \
--num_train_epochs 80 \
--warmup_steps 1000 \
--eval_steps 1000 \
--save_steps 1000 \
--save_total_limit 5 \
--num_workers 8 \
--seed 42 \
--val_eval_max_samples 300Hinweis:
--save_total_limit 5 bedeutet, dass nur die 5 neuesten Checkpoints im Output-Verzeichnis behalten werden.
eval_cer:
- Hauptqualitaetsmetrik.
- kleiner ist besser.
eval_empty_pred_count:
- sollte 0 sein.
- hoher Wert ist akutes Warnsignal.
eval_pred_repetitive_ratio:
- misst Wiederholungsmodus.
- sollte niedrig bleiben.
eval_pred_avg_lenvseval_ref_avg_len:
- starke Laengendifferenz ist oft ein Fruehwarnzeichen fuer Decoder-Probleme.
eval_token_forensics:
- sehr wichtig fuer Diagnose.
- vergleiche Pred/Label bei:
token_special_ratio,token_unique_nonpad,- Top-Tokens,
- Start-Token-Verhalten.
Wenn du W&B vermeiden willst, kannst du ohne eigenes Hosting Trackio nutzen.
- Training mit Tracking starten:
export TRACKIO_SPACE_ID="dein-hf-user/dein-trackio-space"
export TRACKIO_PROJECT_NAME="donut-full-ocr"
CUDA_VISIBLE_DEVICES=0 python cli.py train-donut-ocr \
... \
--report_to trackio \
--run_name gray_gpu0_fullrun- Zweiten Run eindeutig benennen:
CUDA_VISIBLE_DEVICES=1 python cli.py train-donut-ocr \
... \
--report_to trackio \
--run_name rgb_gpu1_fullrun- Am Smartphone:
- HF Space URL oeffnen (
https://huggingface.co/spaces/<user>/<space>), - im gleichen Projekt die Runs
gray_gpu0_fullrunundrgb_gpu1_fullrundirekt vergleichen.
Wenn Training instabil wird, in dieser Reihenfolge pruefen:
- Tokenizer korrekt?
- Muss BoSentencePiece sein.
- Token audit im Log pruefen.
- Manifest-Felder/Paths korrekt?
- Keine leeren Texte.
- Bildpfade aufloesbar.
- Pipeline passend?
- Bei Farbtinte
rgbstatt harter Binarisierung.
- Tiny erneut laufen lassen:
- Reproduzierbar kleiner Datensatz,
- kurze Eval-Intervalle,
- Failure-Mode zuerst dort fixen.
- LR und Warmup:
- Bei instabilem Decoder ggf. niedrigere LR oder laengeres Warmup testen.
- Checkpoint-Wahl:
- Nicht nur nach niedrigster CER gehen,
- auch
empty_predundrepetitive_ratiobeachten.
- Immer zuerst Tiny-Run pro Pipeline (
gray,rgb). - Full-Runs aus stabilem Tiny-Checkpoint starten, nicht blind from scratch.
- Eval-Intervalle anfangs kuerzer halten (fruehe Fehler sichtbar machen).
val_eval_max_samplesfuer schnelle Iteration begrenzen, spaeter ggf. erhoehen.- Zwei parallele Full-Runs (gray vs rgb) sind sinnvoll, wenn Farbinformation relevant ist.
Tiny-Pretraining ist in diesem Setup die wichtigste Schutzmassnahme gegen:
- Empty-Prediction-Collapse,
- Special-Token-Schleifen,
- repetitive Decoder-Ausgaben.
Es kostet wenig Zeit, spart aber sehr viele Stunden fehlgeschlagenes Full-Training.