AL1-model-B — Qwen3-0.6B fine-tuned for native tool-calling
A compact (0.6B-parameter) language model fine-tuned to
call tools reliably while staying a
capable general-chat assistant. It takes the Apache-2.0
Qwen/Qwen3-0.6B
base and applies a single
LoRA supervised fine-tune (SFT) targeting native, Hermes-style
<tool_call> emission over a small set of file/shell tools — then merges the adapter back into
the base for a self-contained model.
Part of the
Zynthetix AL-1 two-model lab (Model A = from-scratch NumPy LM;
Model B = improve an
open-source small model, this repo). Code:
https://github.com/GruheshKurra/AL1-model-B
Results — before → after
Tool-calling is scored by exact match against a held-out 12-case set: name_acc = correct tool
name chosen, full_acc = correct name and arguments. Both evaluated with greedy decoding, fp32,
on the same device (no blending with chat scores).
| Metric | Base Qwen3-0.6B | AL1-model-B (SFT) | Δ |
|---|
Tool name_acc | 0.500 | 1.000 | +0.500 |
Tool full_acc | 0.500 | 0.917 (11/12) | +0.417 |
| Chat coherence (10 probes) | strong | strong, terser | no regression |
Why the base scored only 0.5: it refused to call run_bash and edit_file (0/2 each) and
mis-handled one list_dir — its arguments were fine when it chose to call. The SFT data therefore
over-weighted edit_file + run_bash + explicit anti-refusal examples, which closed the entire gap
(name_acc → 1.0 = a valid tool is now always selected).
The merged fp16 model reproduces the adapter score exactly (full_acc 0.917) — merging lost nothing.
The tools
Five file/shell tools, described to the model via the OpenAI-style tools= schema and the Qwen3
native chat template (Hermes <tool_call> tags):
| Tool | Purpose | Required args |
|---|
read_file | Read a file's contents | path |
write_file | Create/overwrite a file | path, content |
edit_file | Replace an exact substring | path, old_string, new_string |
list_dir | List a directory | path |
run_bash | Run a shell command | command |
The model emits tool name + arguments; wiring those to a real executor (or an MCP client) is a
serving-layer concern, deliberately out of scope for the model itself.
Usage
1from transformers import AutoModelForCausalLM, AutoTokenizer
2import torch
3
4MODEL = "karthik-2905/AL1-model-B"
5tok = AutoTokenizer.from_pretrained(MODEL)
6model = AutoModelForCausalLM.from_pretrained(MODEL, dtype=torch.float16).to("mps") # or "cuda"/"cpu"
7
8TOOLS = [{"type": "function", "function": {
9 "name": "read_file",
10 "description": "Read and return the contents of a file.",
11 "parameters": {"type": "object",
12 "properties": {"path": {"type": "string"}}, "required": ["path"]}}}] # + the other 4
13
14msgs = [{"role": "user", "content": "Show me what's in config.py"}]
15text = tok.apply_chat_template(msgs, tools=TOOLS, tokenize=False,
16 add_generation_prompt=True, enable_thinking=False)
17ids = tok(text, return_tensors="pt").to(model.device)
18out = model.generate(**ids, max_new_tokens=128, do_sample=False)
19print(tok.decode(out[0][ids.input_ids.shape[1]:], skip_special_tokens=True))
20# -> <tool_call>{"name": "read_file", "arguments": {"path": "config.py"}}</tool_call>
How it was built
| Stage | Choice |
|---|
| Base | Qwen/Qwen3-0.6B (Apache-2.0) — GQA + RoPE + SwiGLU + RMSNorm |
| PEFT | LoRA r=16, α=32, dropout 0.05, on all 7 attention+MLP projections |
| Trainer | TRL SFTTrainer, assistant_only_loss=True, max_length=512, packing off |
| Schedule | lr 3e-4 (cosine, 3% warmup), 3 epochs, effective batch 32, bf16 |
| Hardware | RunPod RTX A6000 (train), Apple Silicon / MPS (merge + eval) |
| Merge | merge_and_unload() → standalone fp16 |
LR sweep (10% data, 1 epoch) picked 3e-4 (eval_loss: 1e-4=1.099, 2e-4=0.999, 3e-4=0.975).
The full 3-epoch run overfits by eval_loss after ~epoch 1 (0.91 → 0.99), but generation quality and
tool exact-match were best at 3 epochs — the task metric beat the proxy loss, so 3 epochs shipped.
Data
- 1,423 train / 74 val examples, chat-formatted to the Qwen3 template, ~2:1 tool:chat ratio.
- Tool examples teach the Hermes
<tool_call> format across all 5 tools (over-weighting the two the
base refused); chat examples guard against chat regression.
- Train/val split is dedup'd and leakage-checked against the eval set.
Evaluation
- Tool track (trust anchor): exact-match over a 12-case set, scored by
mlb/eval_tools.py.
- Chat track (regression guard): 10 probes, currently eyeballed for coherence (LLM-judge = future work).
Limitations
- Small model (0.6B): capable within its size, not a substitute for a large instruct model.
- Chat quality is eyeballed, not yet LLM-judge scored — no formal regression number.
- No general-capability guard (MMLU/GSM8K) was run; broad-knowledge regression is unmeasured.
- 4-bit quantization untested — a 0.6B model can degrade more than the usual −1–2% at 4-bit.
License
Apache-2.0, inherited from the Qwen/Qwen3-0.6B base.