Fine-Tuning Qwen3-0.6B for Tool Calling with XYZ-Aquila-SFT
In this article
Supervised fine-tuning LLMs for reliable tool-calling remains one of the more finicky corners of applied ML — existing chat templates silently mutate training data, and naive JSON extraction breaks on nested argument objects. A new end-to-end pipeline walks through every failure mode explicitly, using the XYZAILab/XYZ-Aquila-SFT dataset and Qwen/Qwen3-0.6B as concrete targets. The result is a reproducible workflow a practitioner can clone, adapt, and scale. Given that agentic deployment patterns are reshaping how models interact with external systems, getting tool-calling supervision right at the data-preparation stage has compounding downstream value.
Dataset Streaming and Trajectory Parsing
The pipeline streams 400 rows from XYZAILab/XYZ-Aquila-SFT (English split) using Hugging Face datasets>=3.0.0 with streaming=True, avoiding a full download. Each row exposes a question, answer, number of tool calls integer, and a trajectory list of role-tagged messages.
A critical implementation decision is the JSON extractor. The pipeline explicitly rejects re pattern matching — citing breakage on nested arguments objects present in every real tool call — in favour of a nesting-safe scanner that walks the raw string character by character using json.JSONDecoder.raw_decode. The parse_row function assembles a Trajectory dataclass, splitting the system message on a # Tools header, extracting embedded tool schemas with a <tools>...</tools> regex, harvesting all <tool_call> blocks from assistant turns, and separately counting <think> reasoning blocks and <tool_response> observations. Parser agreement with the dataset's declared number of tool calls field is logged at inference time as a direct validation check.
ChatML Rendering and Loss Masking
Rather than calling tok.apply_chat_template(), the pipeline manually renders every trajectory into ChatML using <|im_start|>, <|im_end|>, and newline delimiters. The rationale is explicit: Qwen3's built-in template deletes <think>...</think> blocks from every assistant turn except the last, silently destroying the reasoning supervision signal for multi-step trajectories.
The manual renderer applies label = -100 to all header and non-assistant tokens, preserving loss only on assistant-generated body and tail tokens. Sequences exceeding MAX_SEQ_LEN = 2048 are truncated by default (LENGTH_POLICY = "truncate"), with a drop option available. Examples where all labels remain -100 after truncation are filtered out. The supervised-token ratio (mean, p10, p90) is reported across the encoded corpus as a dataset-health diagnostic — a low ratio indicates most sequence budget is consumed by prompt context rather than learnable output.
LoRA Configuration and Training Loop
The model is loaded as Qwen/Qwen3-0.6B, initialised with attn_implementation="sdpa" and BF16 dtype where the GPU supports it. Gradient checkpointing is enabled before LoRA attachment. The LoRA config sets r=16, lora_alpha=32 (2×r), lora_dropout=0.05, bias="none", and task_type="CAUSAL_LM". Training runs for MAX_STEPS=30 with batch size 1 and GRAD_ACCUM=8, yielding an effective 8 trajectories per step. The optimiser is AdamW with lr=1e-4, weight_decay=0.0, and betas=(0.9, 0.95). A cosine schedule with 5 warmup steps governs the learning rate. Loss, EMA loss, learning rate, and perplexity are logged every 5 steps. The tutorial frames this 30-step run explicitly as a smoke test on roughly 350 trajectories, not a publishable result, and recommends scaling N_STREAM and MAX_STEPS for production use.
Evaluation Protocol
Post-training evaluation uses teacher-forced probes: the trajectory is cut immediately before an assistant turn containing a tool call, the prefix is fed to the model with max_new_tokens=160 and greedy decoding (do_sample=False), and the generation is parsed for tool calls. Three metrics are reported: parseable rate, tool-name accuracy, and argument-key F1. The pipeline runs evaluation both before and after LoRA fine-tuning to produce a delta, using up to 24 probes (N_EVAL_PROBES=24) constructed from the held-out split.
| Parameter | Value | Role in Pipeline |
|---|---|---|
| Model | Qwen/Qwen3-0.6B | Base causal LM for SFT |
| Dataset rows streamed | 400 | Training corpus size (smoke test) |
| Max sequence length | 2048 tokens | Truncation ceiling |
| LoRA rank (r) | 16 | Adapter parameter budget |
| LoRA alpha | 32 | Scaling factor (2 × r) |
| LoRA dropout | 0.05 | Regularisation |
| Learning rate | 1e-4 | AdamW base LR |
| Gradient accumulation | 8 | Effective batch = 8 trajectories/step |
| Max training steps | 30 | Smoke-test run length |
| Warmup steps | 5 | Cosine schedule ramp |
| Evaluation probes | 24 | Teacher-forced tool-call assessment |
| Max new tokens (eval) | 160 | Generation budget per probe |
The most consequential decisions in this pipeline — preserving <think> blocks by bypassing apply_chat_template, using nesting-safe JSON extraction, and verifying parser agreement against ground-truth counts — are exactly the steps that get skipped when teams move fast. As tool-calling models become standard infrastructure for AI agents interacting with external data sources, these low-level correctness guarantees become load-bearing. The XYZ-Aquila-SFT + Qwen3-0.6B pairing is Colab-accessible; the architecture of the data pipeline is what scales to larger models and datasets.
Related Reading
Qwen2.5-0.5B Fine-Tuned With DPO After Auditing HH-RLHF Length Bias
A reproducible pipeline audits Anthropic HH-RLHF for lexical shortcuts, then fine-tunes Qwen2.5-0.5B-Instruct with DPO, TRL, and LoRA in one notebook.
NVIDIA Nemotron 3.5 Super VL: 120B MoE Runs on a Single GPU
NVIDIA's Nemotron 3.5 Super VL combines 120B total parameters with 12B active and runs the NVFP4 checkpoint on one DGX Spark, H200, or GB200.
Nemotron Scores 535.4 IOI and 30/42 IMO, Passing Gold in Both
NVIDIA reports one Nemotron family reached 535.4/600 on IOI 2026 and 30/42 on IMO 2026, above both gold thresholds.