qwen3-4b-uzbek-v2

merged bf16 uzbek fine-tune of Qwen/Qwen3-4B. standalone — loadable without peft.

usage

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

tok = AutoTokenizer.from_pretrained("inspirebek/qwen3-4b-uzbek-v2")
model = AutoModelForCausalLM.from_pretrained(
    "inspirebek/qwen3-4b-uzbek-v2",
    dtype=torch.bfloat16,
    device_map="auto",
)

messages = [{"role": "user", "content": "O‘zbekiston poytaxti qayer?"}]
inputs = tok.apply_chat_template(messages, return_tensors="pt", add_generation_prompt=True).to(model.device)
out = model.generate(inputs, max_new_tokens=256, do_sample=True, temperature=0.7)
print(tok.decode(out[0][inputs.shape[1]:], skip_special_tokens=True))

training

a two-stage lora fine-tune of Qwen/Qwen3-4B. the hard parts weren't the training loop — they were figuring out why v1 failed, then engineering around a 16-hour compute timeout that couldn't fit the full run.

the v1 lesson: why plain lora collapsed to random

v1 scored 26.92% on mmlu-uz, statistically indistinguishable from 25% random baseline. the adapter was training and loss was descending, but the model learned nothing useful in uzbek. root cause: lora only touched the attention and mlp projections. for a base model with english-dominant pretraining, learning a new language requires re-mapping the vocabulary itself — which lives in embed_tokens (input embeddings) and lm_head (output projection). freezing both means the model can't reshape its token distribution over uzbek morphology, only nudge how it attends within the english-shaped geometry it already has.

v2 adds embed_tokens and lm_head to target_modules. this expands the lora to ~2 gb (vs ~200 mb in v1) but finally lets the model actually learn uzbek. result: mmlu-uz jumped to 40.50% (+13.58 pp over random).

recipe

lora configuration (unsloth + peft):

  • r=64, alpha=128 — alpha = 2·r (more aggressive updates than the conservative alpha=r default)
  • use_rslora=True — alpha scales by alpha/sqrt(r) instead of alpha/r; without this the per-parameter update magnitude at r=64 is too small and training stagnates
  • target modules: q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj, embed_tokens, lm_head
  • use_gradient_checkpointing="unsloth" to fit the expanded adapter on a single l4

dual learning rate (via UnslothTrainer):

  • base lr for projection layers, embedding_learning_rate at 1/10th for embed_tokens + lm_head
  • embeddings are the highest-leverage layer — a full-lr update can catastrophically drift the model's token geometry in the first few hundred steps

stage a — continued pretraining on native uzbek text (3 datasets, ~2m rows):

  • lr=5e-5, embedding_lr=5e-6, 1 epoch, packing=False
  • the goal is to reshape the token distribution so the model fluently predicts uzbek sequences, not to learn task behavior

stage b — supervised fine-tune on chat-formatted uzbek instructions (6 datasets, ~4.3m rows):

  • loaded from the stage a adapter (continues training, not a fresh start)
  • lr=1e-4, embedding_lr=1e-5, 1 epoch
  • train_on_responses_only=True: the loss is masked on the user side of the qwen3 chat template so gradient only flows through assistant responses; prevents the model from memorizing prompt patterns and gives +1–2% accuracy empirically

stable batching:

  • per_device_train_batch_size=1, gradient_accumulation_steps=16 — effective batch 16
  • at r=64 with embedding lora, batch 2 oom's on 24gb. the accumulation trick keeps gradient signal while staying inside the vram budget

optimization: adamw_8bit (bitsandbytes) + cosine schedule + 3% warmup, weight decay 0.01, seed 3407.

infra: surviving a 16-hour timeout on serverless gpu

modal functions have a 16-hour hard cap. stage b doesn't fit in 16 hours on a single l4, and a mid-epoch restart on a fresh container would lose everything (the /ckpts/ volume persists, but the container that owns the training state doesn't).

the fix: a TrainerCallback that pushes each checkpoint to a private hugging face repo (inspirebek/qwen3-4b-uzbek-ckpts) on every save_steps fire. when the container timed out at step 7258 / 8242 (88%), the next run loaded checkpoint-7000 from the hf backup and resumed — on a different modal account, after the first account's $30 credit ran out. account switches are invisible to huggingface; the backup was the portable state.

total stage b runtime: ~10 h first leg + ~3.2 h resume leg = ~13.2 h on a single l4.

evaluation

  • mmlu-uz (murodbek/MMLU-uz, zero-shot, logit-based multiple choice): 40.50% overall
    • social sciences 45.43%, other 45.07%, business 42.42%, stem 41.67%, medical 39.67%, humanities 35.60%
  • uzlib (tahrirchi/uzlib, uzbek linguistic benchmark, sampled generation t=1.0 p=0.95): 33.42% overall
    • correct_word 34.98%, fill_in 30.77%, meaning 26.69%, meaning_in_context 25.00%
    • 8.17% of responses didn't parse cleanly as A/B/C/D — a fraction of the score loss is format drift, not knowledge

datasets

stage a — fluency (continued pretraining):

stage b — instruct (sft):

⚠️ licensing note: saillab/alpaca_uzbek_taco is cc-by-nc-4.0, which restricts commercial use of derivative models. downstream users who need a fully permissive license should retrain without that subset.

sibling formats

intended use & limitations

uzbek-first chat assistant. capable in english as well. not aligned for safety — treat as a research artifact. knowledge cutoff inherits from Qwen/Qwen3-4B.

Downloads last month
90
Safetensors
Model size
4B params
Tensor type
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for inspirebek/qwen3-4b-uzbek-v2

Finetuned
Qwen/Qwen3-4B
Adapter
(1020)
this model
Quantizations
3 models

Datasets used to train inspirebek/qwen3-4b-uzbek-v2

Collection including inspirebek/qwen3-4b-uzbek-v2