8.7.18. dasLLAMA-17 — Translation
dasLLAMA translates with the small models Firefox ships for offline translation.
Each model goes one way: ru-en reads Russian and writes English, en-ru reads
English and writes Russian. Firefox publishes them as a Marian weight file
(model.<pair>.intgemm.alphas.bin, Marian is the C++ toolkit that trained
them) and a SentencePiece vocabulary (vocab.<pair>.spm) - the tokenizer
that cuts text into pieces the model knows.
Run it with the model file; its vocabulary sits beside it:
daslang.exe -jit tutorials/dasLLAMA/17_translate.das -- ru-en/model.ruen.intgemm.alphas.bin
daslang.exe -jit ... -- en-ru/model.enru.intgemm.alphas.bin "Where is the nearest pharmacy?"
daslang.exe -jit ... -- en-ru/model.enru.intgemm.alphas.bin --exact
Use -jit: the model runs the same tuned kernels the language models run.
8.7.18.1. Load a model
load_translator takes the path of the Marian file. The first load reads the
file, converts the weights and writes a .dlim image beside it; every later
load maps that image and starts at once. translator_langs reads the two
languages off the model’s own pair name.
var inscope tr <- load_translator("ru-en/model.ruen.intgemm.alphas.bin")
let langs = translator_langs(tr)
print("model reads '{langs.src}', writes '{langs.trg}'\n")
The file stores its weights as 8-bit integers. By default dasLLAMA serves them
as Q8_0 blocks - 32 integers and one scale each - which is the fast lane on the
CPU and on the GPU. load_translator(path, true) widens every weight to a
32-bit float instead: the reference lane, a little slower, and closest to the
float math the model was trained in. The two lanes agree on most sentences;
where two next words score almost the same, they can pick differently.
8.7.18.2. A translation LLM
Firefox’s models are small and fast, and they go one way. The other kind is a language model trained to translate: MiLMMT-46 is Gemma 3 1B trained further on 46 languages. The file is a GGUF of about 1 GB at Q4_K_M - thirty times the Firefox model - and a sentence takes tens of milliseconds instead of a few. It translates noticeably better, and one file covers every direction among its languages, so the program picks the direction:
var inscope tr <- load_translator("MiLMMT-46-1B-v0.1.Q4_K_M.gguf")
print("{join(translator_languages(tr), " ")}\n") // ar az bg ... zh zh-Hant
if (!set_translator_langs(tr, "ru", "en")) {
print("not a pair this model covers\n")
}
translator_languages lists the language codes the model knows,
translator_language_name reads a code as its English name (ru is
Russian), translator_serves asks whether a pair is covered without
changing anything, and
set_translator_langs answers false for a pair it does not cover. A Firefox
model accepts only its own pair. Under the hood the LLM reads a short prompt -
“Translate this from Russian to English:”, the sentence, and “English:” - and
writes the next line, greedy, until its end token. A general instruct model -
Qwen, Llama, Gemma - loads the same way: a GGUF that carries a chat template
gets an instruction through that template, with thinking off, and the reply is
the translation.
8.7.18.3. Translate
for (ln in lines) {
print("{ln}\n -> {translate(tr, ln)}\n")
}
translate takes one sentence. An encoder reads the whole sentence at once,
then a decoder writes the translation one piece at a time, each time the most
likely next piece, until it writes the end token or reaches twice the length of
the source. The model was trained on single sentences, so split a paragraph
into sentences first. Each call reuses the buffers the translator holds: a long
text is a loop of short calls, and a sentence takes a few milliseconds on a
laptop CPU. Where a GPU driver serves the model, translate runs the whole
sentence on the device; elsewhere it runs on the CPU, with the same result.
See also
Full source: tutorials/dasLLAMA/17_translate.das
Previous tutorial: dasLLAMA-16 — Text to Speech
Speech in, speech out: dasLLAMA-07 — Speech to Text reads the source language, dasLLAMA-16 — Text to Speech speaks the translation