.wmlm training and evaluation
Prepare, distill, mask and evaluate the neural context model, then export a .wmlm with a verifiable hash.
The context model the keyboard downloads per language is a small word-level
transformer that reads the sentence before the caret and says what comes next.
Suggestions describes what it does in the app; this page
covers the training and evaluation tools in tools/wmlm/. That directory’s
README.md is the command reference; the steps below are the end-to-end path.
Prerequisites: Python 3.10+, numpy and torch; distill.py also needs
transformers. Run from the repo root. en_sentences.tsv comes from the
corpus step in tools/wmlm/README.md (or an existing prepared TSV).
Prepare the arrays
Section titled “Prepare the arrays”prep.py turns a sentence TSV into the train.npz, heldout.npz,
heldout.tsv and vocab.txt a run needs. Reserving piece slots lets the model
spell a word the vocabulary does not list instead of emitting <unk>:
python tools/wmlm/prep.py en_sentences.tsv en_lm vocab=24576 pieces=2048 ctx=40 \ seed_words=app/dictionaries-src/en.txtDistill a teacher
Section titled “Distill a teacher”distill.py runs a causal language model over a sample of the training
sentences and maps its top-k next-word distribution onto the student’s
vocabulary. The result is a soft-target file that train.py distill= reads:
python tools/wmlm/distill.py en_lm en_soft.npz teacher=Qwen/Qwen2.5-1.5B sentences=100000 topk=24Add masks and alignment
Section titled “Add masks and alignment”soft_mask.py derives per-row data from the student’s tokens and the teacher’s
soft targets. --emit-align writes the word-aware position map, --emit-keep
writes a per-row KD/CE gate, and --weight cand writes a candidate-count
weight. build_kd_weight.py turns a mask file into a three-level weight (empty 0, piece <1, word 1):
python tools/wmlm/soft_mask.py build en_lm en_soft.npz masks \ --weight cand --emit-keep kd --emit-alignpython tools/wmlm/build_kd_weight.py masks.masks.npz masks.w03.npz piece_weight=0.3Train and evaluate
Section titled “Train and evaluate”train.py keeps the plain cross-entropy target where the teacher said nothing,
mixes the distillation loss on the rows the teacher filled, and evaluates up to
20,000 held-out sentences:
python tools/wmlm/train.py en_lm en_run steps=16000 batch=512 d=256 layers=6 ff=768 ctx=40 \ distill=en_soft.npz distill_alpha=0.3 distill_align=masks.align.npz \ distill_mask=masks.keep.kd.npz distill_weight=masks.w03.npzFor a quick screen, slice_data.py cuts a hermetic slice of the data dir and
its soft targets. average_checkpoints.py averages saved checkpoints before
export; evaluate the soup on heldout first.
Compare two runs
Section titled “Compare two runs”The typing harness writes a stable per-item dump with ITEMS=items.tsv; two
runs’ dumps go into paired_eval.py, which reports McNemar and a bootstrap CI.
Holm is opt-in: pass the metric family’s p-values to --holm (the tool evaluates
one --metric per run).
cd native/wm-engine && cargo build --release --example typing_evalITEMS=items_a.tsv PACK=pack MODES=base,lm \ ./target/release/examples/typing_eval ../../app/dictionaries-src/en.txt \ ../../en_lm/heldout.tsv ../../en_context.wmlmITEMS=items_b.tsv PACK=pack MODES=base,lm \ ./target/release/examples/typing_eval ../../app/dictionaries-src/en.txt \ ../../en_lm/heldout.tsv ../../en_context_v2.wmlmcd ../.. && python tools/wmlm/paired_eval.py \ native/wm-engine/items_a.tsv native/wm-engine/items_b.tsv --metric lm_strip_top1LANG=de selects QWERTZ and turns off english_sources, but the repo ships
only en_bigrams.txt; gunzip the data repo’s de_bigrams.txt.gz and
de_trigrams.txt.gz into a PACK directory for a German run. SEEDS is only
read when english_sources=true, so German runs have no seed pairs. LANG is
normalized from locales such as de_DE.UTF-8 and de-DE; other locales fall
back to English. The glide harness
(native/wm-engine/examples/glide_lm_blend_eval.rs) writes the same kind of
per-item dump for a stroke set. Its records.jsonl schema is word,
sentence, word_idx, canvas_width, canvas_height, and data (an array
of {x, y, t} points normalized to the canvas).
Export
Section titled “Export”export.py writes the int8 .wmlm and a .sha256 sidecar next to it:
python tools/wmlm/export.py en_run/best.pt en_lm/vocab.txt en_context.wmlm \ --langs=en --name=Englishsha256sum -c en_context.wmlm.sha256The app-side reader and the catalog entry are described in
Suggestions; the Rust reader is
native/wm-engine/src/lm.rs.
