Skip to main content

Training a Small Multilingual LLM

Training a Small Multilingual LLM for Structured Order Extraction (English, German, Arabic) on Google Colab, for CPU Deployment

Authors: Mostafa Othman (University Of Applied Science Lübeck)
Published on
Contents

Abstract

Online stores receive free-text order requests in several languages ("I want a red T-shert, size m, to be delivered to mainstreet 34, 23456 Hamburg, I would like to pay in mastercard/paypal"). This article describes a complete, reproducible recipe for turning such messages into a fixed JSON object (brand, product, color, size, quantity, deliver_address, payment_method) with a model that is small enough to run on a 2–4 core CPU server. We fine-tune Qwen3-0.6B, a 0.6-billion-parameter open-weight model with Apache-2.0 licence and 100+ language coverage, using Low-Rank Adaptation (LoRA) on the free Google Colab T4 GPU. Because no labelled data exists for a specific store, we generate training data synthetically from a catalogue configuration: a structured order is sampled first, then rendered into English, German or Arabic text with language-specific grammar (German adjective declension, Arabic gender agreement, Arabic-Indic digits) and realistic noise (typos, casing, missing fields, chit-chat). Labels are therefore correct by construction. The model is trained with a completion-only loss on 6,000 examples, evaluated with field-level exact match on a hand-written 45-message gold set, merged, converted to GGUF and quantized for llama.cpp. The pipeline was validated end-to-end in a 2-vCPU sandbox; the GPU training run itself is executed by the reader on Colab, and the article explains how to read its results. Article 2 covers serving the exported model on CPU with grammar-constrained JSON decoding.

Keywords: instruction fine-tuning, LoRA, synthetic data, information extraction, multilingual NLP, Qwen3, GGUF, llama.cpp, Google Colab

1.Introduction

1.1 The problem

A customer writes a message in their own words and language; the shop system needs a machine-readable order. The classical approach — regular expressions and keyword lists per language — breaks on the first typo ("T-shert"), on word order ("zwei schwarze Hoodies von Nordwind in XL"), on grammatical agreement ("سترة حمراء" versus "تيشيرت أحمر") and on mixed scripts (an Arabic sentence containing a German address in Latin letters).

1.2 Goals and constraints

The requirements set for this study are:

1. Three input languages

English, German, Arabic (including dialectal phrasing such as "أبغى", "بدي", "عايز").

2. One fixed output schema

Exactly seven keys, returned strictly formatted as valid JSON.

3. Store-specific behaviour

Product, colour and payment names normalised to the store's catalogue vocabulary.

4. Small hardware

CPU-only server with 2–4 cores & 4–8 GB RAM; training fits in free Google Colab.

5. A learning objective

The recipe teaches how to fine-tune and ship a model for a specific domain, justifying every design choice.

1.3 Contributions

The article contributes:

  • (i) Schema design with explicit normalisation rules.
  • (ii) Configuration-driven synthetic-data generator that produces grammatically plausible messages in three languages with correct labels.
  • (iii) LoRA fine-tuning recipe for Qwen3-0.6B running on a T4 GPU in well under an hour.
  • (iv) Evaluation protocol measuring what is actually shipped (quantized GGUF on CPU).
  • (v) Tested notebook and script set validated end-to-end.

2.Background and Related Work

Instruction fine-tuning

Fine-tuning a pre-trained language model on (instruction, answer) pairs makes it follow the instruction format reliably, including for unseen inputs of the same kind [1]. For a single narrow task, a few thousand high-quality examples are sufficient; Zhou et al. showed that 1,000 carefully curated examples can align a 65B model [2], and the task here is far narrower than open-ended chat.

Parameter-efficient fine-tuning

LoRA freezes pre-trained weights and learns a low-rank update ΔW = BA for selected weight matrices, cutting trainable parameters by orders of magnitude without quality loss [3]. QLoRA extends this to 4-bit quantized base models [4]; for a 0.6B model this is unnecessary as full-precision weights fit comfortably.

Synthetic training data

Self-Instruct demonstrated instruction data can be generated rather than annotated [5]. We use a stricter variant: the label (structured order) is sampled first and text is generated from it, so labels are exact by construction with zero annotator error.

Constrained decoding

A fine-tuned model usually emits valid JSON, but "usually" is not a guarantee. Grammar-constrained decoding masks every token violating a formal grammar [6] and works even without fine-tuning [7]. llama.cpp converts a JSON Schema into such a grammar [8]. We combine both: fine-tuning gives correct content, the grammar gives correct structure.

Small multilingual models

Qwen3 family covers 119 languages with an Apache-2.0 0.6B dense model [9][10]. Gemma 3 270M is positioned by Google for task-specific fine-tuning [11]. Qwen3.5 adds 0.8B–9B hybrid Gated-DeltaNet/attention models [12]. Section 4.1 compares them for this task.

Quantization

Running on CPU requires integer quantization. For Qwen3, 8-bit quantization is reported as near-lossless, while 4-bit shows noticeable degradation, and smaller models are less stable under quantization than larger ones [13]. This drives the Q8_0 recommendation in Section 4.6.

Alternative: token classification

An encoder such as XLM-R [14] fine-tuned for span tagging would be faster, but extracts spans verbatim and cannot normalise "rot" to red or "Nachnahme" to cash_on_delivery without a second rule layer. A generative model does extraction and normalisation in one step.

3.Task Definition

3.1 Input and output

Input: one customer message (plain text, any of the three languages, mixed scripts allowed).
Output: one JSON object with exactly the seven keys below, always in this order.

FieldTypeNormalisation ruleExample
brandstring | nullSpelled as in the catalogue ("Nordwind"), null if not mentioned"Nordwind"
productstring | nullStore's English catalogue key, lowercase ("t-shirt", "hoodie", "sneakers")"t-shirt"
colorstring | nullEnglish catalogue colour, lowercase; rot, أحمر, red all become "red""red"
sizestring | nullLetter code XS…XXL in upper case, or a number as a string ("42"); Arabic-Indic digits converted"M"
quantityinteger | nullExplicit count; singular noun with article ("a T-shirt", "ein T-Shirt", "تيشيرت") = 1; plural without number = null1
deliver_addressstring | nullCopied verbatim from the message (whitespace collapsed), no translation or reformatting"mainstreet 34, 23456 Hamburg"
payment_methodlist of idsIds from closed set (mastercard, visa, paypal, credit_card, cash_on_delivery, bank_transfer, klarna, invoice); [] if none["mastercard", "paypal"]
Worked Example Mapping
{"brand": null, "product": "t-shirt", "color": "red", "size": "M", "quantity": 1,
 "deliver_address": "mainstreet 34, 23456 Hamburg", "payment_method": ["mastercard", "paypal"]}

3.2 Design decisions

A shop backend must know the difference between "the customer said size M" and "the customer said nothing about size", because the second case triggers a follow-up question. Guessing a default would hide that.

The example message says "mastercard/paypal" — the customer has not decided. A single string would force the model to pick one and silently drop information; a list keeps both and lets the application ask "which one?". A list type is also easy to constrain with a grammar (closed enum, at most three items).

Addresses are the field with the highest cost of error (a parcel goes to the wrong place). Copying the exact substring is a much easier skill for a small model than reformatting, it keeps Arabic-script addresses intact, and it makes evaluation exact. Parsing into street/postal code/city is left to deterministic post-processing.

The catalogue is the contract between the model and the shop. Normalising across languages is exactly the capability a generative model adds over span extraction.

Messages with several different items ("two red T-shirts and a blue cap") are out of scope for v1; Section 7 explains how to extend the schema to an items list once the basic recipe works.

4.Method

4.1 Base model selection

Selection criteria (in order of importance): Arabic and German quality, CPU footprint after quantization, licence, and tool chain maturity (transformers, PEFT/TRL, llama.cpp, Ollama).

ModelParamsLanguagesLicenceArchitecture / NotesFit for this task
Qwen3-0.6B [10]0.6B (0.44B non-embed)100+Apache-2.0Dense decoder, text-only, thinking mode switchable off; universal tool supportSelected
Qwen3.5-0.8B [12]0.8B201Apache-2.0Hybrid Gated DeltaNet + attention, native vision; needs source transformersSecond Iteration
Gemma 3 270M [11][23]0.27B (0.17B embed)140+Gemma terms, gatedDesigned for fine-tuning; smallest CPU footprint; weaker free-form ArabicEdge / Pi
SmolLM2-360M [24]0.36BPrimarily EnglishApache-2.0English-centric pre-trainingNot suitable
Llama 3.2 1B [25]1.2B8 official (no Arabic)Llama 3.2 licenceArabic not officially supportedNot suitable

Decision: Qwen3-0.6B

Confidence: 80%

The remaining 20% accounts for Qwen3.5-0.8B giving potentially better Arabic at a similar CPU footprint once its toolchain matures. Qwen3-0.6B's thinking mode must be turned off via enable_thinking=False to prevent unwanted <think> prefix tokens.

4.2 Prompt format: identical at training and inference

Chat models are trained on specific token layouts; using a different layout at inference degrades them badly [15]. We freeze the prompt format string in extractor_common.py:

Prompt Format Layout
<|im_start|>system
{system prompt}<|im_end|>
<|im_start|>user
{customer message}<|im_end|>
<|im_start|>assistant
<think>

</think>

The system prompt is 148 tokens and never changes, which enables llama.cpp to cache its KV state across all customer requests.

4.3 Synthetic data generation

Principle: Sample the answer first, render the question from it. Because text is derived from the label, labels are correct by construction.

Catalogue as Configuration

catalog.json holds brands, products with surface forms per language, colours, sizes, payment methods, German cities and street names. Replacing this file adapts the model to another shop.

Noise & Negative Examples

30% typos, 20% lowercased, shuffled clauses. 7% chit-chat negative examples (all null) prevent hallucinating an order from casual inquiries ("Wo ist mein Paket?").

Linguistic Nuances Encoded in the Generator
  • German adjective declension: Handles gender and article ("ein rotes T-Shirt", "einen roten Hoodie", "eine rote Jacke").
  • Arabic gender agreement: Color adjective follows noun gender ("تيشيرت أحمر" vs "سترة حمراء") and non-human plurals take feminine singular ("جوارب سوداء").
  • Arabic-Indic digits: Sizes/quantities like "مقاس ٤٤" mapped to ASCII "42" while verbatim address preserves original digits.
  • Mixed scripts: 70% of Arabic orders feature Latin-script German delivery addresses.
  • Dialects: Openers such as "أبغى", "بدي", "عايز", "محتاج" in addition to Modern Standard Arabic.

4.4 Fine-tuning with LoRA

LoRA trains matrices A and B with rank r=16 on all linear attention and MLP projections, yielding ~10M trainable parameters (≈1.7% of the model).

Hyper-parameterValueReason
Rank r / α / dropout16 / 32 / 0.05Standard; α = 2r keeps update scale moderate
Target modulesall attention & MLP projectionsBest quality per parameter in QLoRA study [4]
Learning rate2e-4 (cosine, 3% warmup)LoRA tolerates higher learning rate than full FT
Epochs / batch3 epochs, batch 16 (1,125 steps)Conservative budget for 6,000 short examples; keeps best checkpoint
Max sequence length512 tokens99th percentile is 309 tokens, max 341; avoids truncation
Losscompletion-onlyLearn to answer, not predict customer text
Precisionfp32 + fp16 autocast (T4) / bf16 (Ampere+)Prevents fp16 overflow without full memory penalty
Seed42Reproducibility

4.5 Evaluation protocol

Greedy decoding (temperature 0) evaluates outputs on five metrics:

  1. JSON validity
    Share of outputs that parse and contain all seven required keys.
  2. Field accuracy
    Exact match per field (null counts as a valid value).
  3. Exact match (Overall)
    All seven fields correct simultaneously — the core business metric.
  4. Exact match per language
    Reveals whether Arabic or German lags behind English.
  5. Latency
    Mean and 95th percentile inference time per message.

4.6 Export for CPU

Adapter weights are merged via merge_and_unload(), saved in bf16, converted to GGUF using llama.cpp, and quantized.

Recommendation: Ship Q8_0

Confidence: 85%

8-bit quantization is near-lossless (~0.6 GB file). The 240 MB savings of Q4_K_M (~0.4 GB) introduces measurable accuracy degradation and is only recommended for systems with < 2 GB RAM.

5.Step-by-Step on Google Colab

The companion notebook train_order_extractor.ipynb executes these steps in 45–60 minutes on free tier T4 GPU:

0

Runtime Setup

Select Runtime → Change runtime type → T4 GPU. Verify in Colab:

Verify in Colab
!nvidia-smi -L
1

Pinned Libraries

Pinned Libraries
pip install -q "transformers==5.19.0" "trl==1.15.0" "peft==0.21.2" "datasets==5.1.0" "accelerate==1.15.0" "pydantic>=2.7"
2

Project Files

All modular scripts written to disk via %%writefile.

Listing 1: extractor_common.py (excerpt)
Listing 1: extractor_common.py (excerpt)
FIELDS = ["brand", "product", "color", "size", "quantity", "deliver_address", "payment_method"]

PROMPT_TEMPLATE = (
    "<|im_start|>system\n{system}<|im_end|>\n"
    "<|im_start|>user\n{message}<|im_end|>\n"
    "<|im_start|>assistant\n<think>\n\n</think>\n\n"
)
EOS_TOKEN = "<|im_end|>"

def format_prompt(message, system_prompt=SYSTEM_PROMPT):
    return PROMPT_TEMPLATE.format(system=system_prompt, message=message.strip())

def target_json(output):
    ordered = {k: output.get(k) for k in FIELDS}            # fixed key order
    return json.dumps(ordered, ensure_ascii=False, separators=(", ", ": "))
3

Generate Synthetic Data & Gold Test Set

Generate Synthetic Data & Gold Test Set
python generate_data.py --n_train 6000 --n_val 600 --out data --seed 42
python make_gold.py

Sample Generated Data:

[de] Guten Tag, Könnt ihr mir einen blauen Hoodie schicken in Größe XS - Versand an Mozartstrasse 79 90462 Nürnberg, Deutschland Vielen Dank.
→ {"brand": null, "product": "hoodie", "color": "blue", "size": "XS", "quantity": 1, "deliver_address": "Mozartstrasse 79 90462 Nürnberg, Deutschland", "payment_method": []}
[ar] صباح الخير، عايز 3 تي شيرتات بيضاء من ماركة elbe & co; الدفع: كلارنا; أرسلوه إلى شارع شيلر 66، 12367 برلين مع التحية
→ {"brand": "Elbe & Co", "product": "t-shirt", "color": "white", "size": null, "quantity": 3, "deliver_address": "شارع شيلر 66، 12367 برلين", "payment_method": ["klarna"]}
[en] Hello, Order: 3x yelaow hoodies from Al Noor size: L. delivery address Bahnhofstr. 162, 90474 Nürnberg thx
→ {"brand": "Al Noor", "product": "hoodie", "color": "yellow", "size": "L", "quantity": 3, "deliver_address": "Bahnhofstr. 162, 90474 Nürnberg", "payment_method": []}
4

Zero-Shot Baseline Evaluation

Zero-Shot Baseline Evaluation
python evaluate.py --backend hf --model Qwen/Qwen3-0.6B --data data/gold_test.jsonl --report reports/baseline_gold.json
5

LoRA Training & Adapter Merge

LoRA Training & Adapter Merge
python train.py --base_model Qwen/Qwen3-0.6B --data data --out models/order-extractor \
                --epochs 3 --batch 16 --lr 2e-4 --rank 16 --alpha 32
Expected duration: 15–30 minutes on Colab T4. Loss should drop well below 0.5 within Epoch 1.
6

Evaluation (GPU, Hugging Face format)

Evaluation (GPU, Hugging Face format)
python evaluate.py --backend hf --model models/order-extractor/merged --data data/val.jsonl --limit 300 --report reports/finetuned_val.json
python evaluate.py --backend hf --model models/order-extractor/merged --data data/gold_test.jsonl --report reports/finetuned_gold.json
7

Export to GGUF and Quantize

Export to GGUF and Quantize
bash export_gguf.sh models/order-extractor/merged models order-extractor
8

Evaluate CPU Shipping Candidate

Evaluate CPU Shipping Candidate
pip install -q llama-cpp-python
python evaluate.py --backend gguf --model models/order-extractor-Q8_0.gguf   --data data/gold_test.jsonl --threads 2 --report reports/gguf_q8_gold.json
python evaluate.py --backend gguf --model models/order-extractor-Q4_K_M.gguf --data data/gold_test.jsonl --threads 2 --report reports/gguf_q4_gold.json
9

Preserve Artefacts

Download or sync to Google Drive: order-extractor-Q8_0.gguf (~600 MB), lora_adapter/, and reports/.

6.Results

6.1 How to report

Fill the evaluation table from the generated reports/*.json files:

System JSON valid Exact match (45 gold) EN DE AR Latency (mean)
Qwen3-0.6B zero-shot (GPU) — — — — — —
Fine-tuned HF bf16 (GPU) — — — — — —
Fine-tuned GGUF Q8_0 + grammar fallback (2-core CPU) 100 % — — — — —
Fine-tuned GGUF Q4_K_M + grammar fallback (2-core CPU) 100 % — — — — —

Performance Targets:

  • Field accuracy > 95% for catalogue items (product, color, size, payment).
  • Errors concentrate in deliver_address and quantity (e.g. plural counting).
  • GGUF Q8_0 performance should track HF bf16 within 1–2 percentage points.

6.2 Pipeline validation (measured)

The full pipeline was verified end-to-end on 2 vCPUs using a proxy model. Key validation fact: with the grammar fallback enabled (--grammar always), the model returned a parsable JSON object with all seven keys and valid catalogue payment IDs in 100% of requests — proving that structure comes from the grammar and content comes from training.

7.Discussion and Limitations

Synthetic-to-real gap

Template text is cleaner than customer reality. Gold set acts as defense; post-launch active learning mitigates drift.

Catalogue drift

Serving layer checks values against the live DB; unrecognized products trigger retraining in minutes.

Hallucination

Constrained by negative training examples, explicit null labels, and grammar validation.

Address handling

Verbatim copying preserves leading zeros in German postal codes and Arabic street strings.

Multiple items

Schema v1 supports 1 item. v2 can switch item keys to an items: [] array of objects.

Privacy & GDPR

No customer data used for training. CPU local inference ensures zero customer addresses are sent to 3rd party APIs.

8.Conclusion

A store-specific, trilingual order-extraction model can be built in an afternoon without any annotated data:

  • Catalogue configuration defines vocabulary.
  • Grammar-aware generator produces training pairs with correct labels by construction.
  • LoRA fine-tuning of Qwen3-0.6B on free Colab GPU finishes in < 30 minutes.
  • Result is a ~0.6 GB GGUF file running locally on a 2–4 core CPU with guaranteed JSON schema conformity.

References

  1. J. Wei et al, “Finetuned Language Models Are Zero-Shot Learners,”ICLR 2022. arXiv:2109.01652
  2. C. Zhou et al., “LIMA: Less Is More for Alignment,”NeurIPS 2023. arXiv:2305.11206