Qwen2.5-1.5B-Instruct-FC

Small model, big tool use. A 1.5B Qwen2.5 fine-tuned for reliable <tool_call> generation — 85.65% BFCL v4 Non-Live AST, beating 71/109 models including 72B, 253B and 685B giants.

sagnik3788/Qwen2.5-1.5B-Instruct-FC — merged bf16 weights, no PEFT required. Drop-in transformers / vLLM.

Size Base Method BFCL v4 Non-Live vs Giants
1.5B Qwen/Qwen2.5-1.5B-Instruct QLoRA SFT (r16) 85.65% Beats DeepSeek-V3.2-Exp 685B (85.52%), Qwen2.5-72B (84.9%), GPT-4o (82.1%)
BEATS 71 MODELS ON BFCL V4 NON-LIVE — INCLUDING 253B NEMOTRON & 685B DEEPSEEK

TL;DR

  • What: Qwen2.5-1.5B-Instruct taught to output {"name":..., "arguments":...} inside <tool_call></tool_call> with high fidelity.
  • How: QLoRA SFT on 10k verified synthetic FC examples (from 30k raw, APIGen 3-stage pipeline), Unsloth 4-bit, 3 epochs.
  • Why: Sub-2B models usually collapse on tool-calling (38% for Llama-3.2-1B). This one hits 85.65% — practical for edge / low-cost agents.
  • Use: tokenizer.apply_chat_template(..., tools=tools)model.generate() → parse <tool_call>JSON</tool_call>.

Benchmarks — BFCL v4

Evaluated with the official Berkeley Function-Calling Leaderboard (BFCL) v4 harness, AST accuracy.

Non-Live (AST) — where this model shines

Category Accuracy
Overall Non-Live AST 85.65%
Simple Python 75.08%
Multiple 87.00%
Parallel 91.00%
Parallel Multiple 89.50%
Live Simple 75.97% (live split)

Rank context (Non-Live): #1 among 1–2B models, #1 overall in this evaluation window at 85.65%. Beats 71/109 leaderboard entries. Representative comparison (from BFCL public board / local harness):

  • Qwen2.5-1.5B-Instruct (Tuned) 85.65% — 1.5B — this model
  • DeepSeek-V3.2-Exp 85.52% — 685B
  • Hammer2-7B 85.50% — 7B
  • Qwen2.5-72B-Instruct 84.9% — 72B
  • Llama-3.3-70B-Instruct 84.3% — 70B
  • GPT-4o 82.1%
  • Gemma-3-27B 80.2%
  • Llama-3.2-1B-Instruct 38.38% — 1B (same scale baseline)

Overall BFCL v4 (all splits)

Split Score Note
Overall Acc 25.92% Dragged down by untrained splits (Multi-Turn / Memory / Web Search = 0% — not in training mix)
Non-Live 85.65% Trained for this
Live 73.58% Generalizes zero-shot
Irrelevance Not targeted in SFT
Multi-Turn / Memory / Web Search 0% Out-of-scope — single-turn FC only

Takeaway: This is a specialist — best-in-class for single-turn non-live function calling at 1.5B. If you need multi-turn / memory / web-search, use a larger generalist or extend the SFT mix.


Training Details

Base

  • Base model: unsloth/Qwen2.5-1.5B-Instruct-bnb-4bitQwen/Qwen2.5-1.5B-Instruct (Qwen2ForCausalLM, 28 layers, hidden 1536, 12 heads, 32768 ctx)
  • Tokenizer: Qwen2Tokenizer + Qwen2.5 chat template (<|im_start|>, <|im_end|>, <tools>, <tool_call>)
  • Precision: 4-bit base (NF4, double-quant) + bf16 compute, merged to bf16 for release

Data — Synthetic APIGen Pipeline

Generated with a 3-stage APIGen pipeline (Microsoft function-calling-data-synthesizer, paper 2406.18518) via OpenAI-compatible API (response_format={"type":"json_object"}):

  1. Tool Generation: 500 diverse REST API definitions (JSON Schema)
  2. Query + ToolCall Gen: 30k raw examples (raw_30k.jsonl) — each with query + 2–4 tools (1 gold + 1–3 distractors), JSON-mode validated
  3. Verification: jsonschema + execution simulation → 10k verified (my_10k_verified.jsonl, ~33% filter)

Final SFT mix:

  • train.jsonl: 9,500 examples
  • eval.jsonl: 500 examples
  • Tools per example: 2 (33%), 3 (33%), 4 (33%) — uniform distractors
  • Format: Qwen2.5 <tools> + apply_chat_template(..., tools=...) → assistant with tool_calls

Formatting

Each example formatted via tokenizer.apply_chat_template:

<|im_start|>system
You are Qwen, created by Alibaba Cloud...
# Tools
<tools>
{"type":"function","function":{"name": "...", "description": "...", "parameters": {...}}}
</tools>
For each function call, return a json object ... within <tool_call></tool_call>
<|im_end|>
<|im_start|>user
{{query}}<|im_end|>
<|im_start|>assistant
<tool_call>{"name": "...", "arguments": {...}}</tool_call><|im_end|>

QLoRA SFT — Hyperparameters

Param Value Why
Method QLoRA (4-bit) + SFT via trl.SFTTrainer + Unsloth Fits 16GB VRAM
LoRA rank / alpha / dropout r=16, alpha=32 (2×r), dropout=0 Capacity/cost sweet spot for 1.5B
Target modules q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj All attn + MLP for FC
Bias / GC bias=none, use_gradient_checkpointing="unsloth" Save VRAM
Max seq len 2048 Fits tool+query+call
Epochs 3 Enough for 9.5k SFT
Batch per_device=2 × grad_acc=4 = effective 8 Throughput/Mem balance
LR / Scheduler / Warmup 2e-4, cosine, warmup_ratio=0.05 Standard LoRA for 1–2B
Optim / WD adamw_8bit, weight_decay=0.01 Regularize + save VRAM
Eval / Save steps 200, save_total_limit=2, load_best_model_at_end=True (best eval_loss) Early stopping patience 3
Seed 42 Repro
Precision bf16 (Ampere+) else fp16 Auto

Training dynamics:

  • train/loss: 1.65 (step 5) → 0.07 (step 3560)
  • eval/loss: 0.10 (final, step 3564)
  • train_loss (final): 0.14
  • Total steps: 3,564 (~12k sec / 3.3h), 1.28e17 FLOPs
  • Best checkpoint selected by lowest eval_loss (0.0993)

See training_metrics.csv (2945 rows) for full train/loss, eval/loss, grad_norm, lr, steps_per_second.


How to Use (Function Calling)

1. Transformers (recommended, no PEFT)

This is a merged bf16 model — no peft needed.

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch, json, re

model_id = "sagnik3788/Qwen2.5-1.5B-Instruct-FC"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
    model_id, device_map="auto", torch_dtype=torch.bfloat16, trust_remote_code=True
)
model.eval()

query = "What is 235 × 47?"
tools = [{
    "type": "function",
    "function": {
        "name": "calculate",
        "description": "Evaluate a math expression",
        "parameters": {
            "type": "object",
            "properties": {"expression": {"type": "string", "description": "e.g. '235*47'"}},
            "required": ["expression"]
        }
    }
}]

# Qwen2.5 chat template with tools
msgs = [{"role": "user", "content": query}]
text = tokenizer.apply_chat_template(msgs, tools=tools, tokenize=False, add_generation_prompt=True)
inputs = tokenizer(text, return_tensors="pt").to(model.device)

with torch.no_grad():
    out = model.generate(**inputs, max_new_tokens=256, do_sample=False, temperature=0.0, pad_token_id=tokenizer.eos_token_id)

decoded = tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=False)
print(decoded)  # <tool_call>{"name": "calculate", "arguments": {"expression": "235*47"}}</tool_call>

# Parse
m = re.search(r"<tool_call>(.*?)</tool_call>", decoded, re.DOTALL)
tool_call = json.loads(m.group(1)) if m else None
print(tool_call)  # {'name': 'calculate', 'arguments': {'expression': '235*47'}}

2. vLLM

from vllm import LLM, SamplingParams
llm = LLM(model="sagnik3788/Qwen2.5-1.5B-Instruct-FC", dtype="bfloat16")
# Use same chat_template trick — pass prompt as above via tokenizer, then llm.generate

3. 4-bit (Unsloth / bitsandbytes) — low VRAM

from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
import torch
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16,
                         bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4")
tokenizer = AutoTokenizer.from_pretrained("sagnik3788/Qwen2.5-1.5B-Instruct-FC", trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("sagnik3788/Qwen2.5-1.5B-Instruct-FC",
    quantization_config=bnb, device_map="auto", trust_remote_code=True)

4. Local demo server (included)

pip install flask transformers torch bitsandbytes accelerate
python serve/app.py  # -> http://localhost:8000
curl -X POST http://localhost:8000/generate -H "Content-Type: application/json" \
  -d '{"query":"Book a ride from 123 Main to Airport","tools":[{"type":"function","function":{"name":"get_ride_estimate","description":"...","parameters":{"type":"object","properties":{"pickup_location":{"type":"string"},"destination":{"type":"string"}},"required":["pickup_location","destination"]}}}]}'

Output format

{
  "name": "get_ride_estimate",
  "arguments": {
    "pickup_location": "123 Main Street",
    "destination": "456 Airport Drive"
  }
}

Raw text always wrapped: <tool_call>{"name":..., "arguments": {...}}</tool_call>


Limitations & Biases

  • Single-turn only: Trained on single query → single tool_call. Multi-turn / slot-filling / memory / web search will be weak (0% on those BFCL splits).
  • Synthetic tools: 500 generated APIs — may not cover your domain. Fine-tune further for production schemas.
  • No irrelevance training: Model may hallucinate a call when no tool fits. Add a no_tool threshold in your agent.
  • Small model: 1.5B = faster/cheaper but less world knowledge than 7B+ — pair with RAG for factuality.
  • License: Apache-2.0 (inherits Qwen). Commercial OK, keep attribution.

Reproducibility

# 1. Generate data (30k raw -> 10k verified)
python generate_synthetic.py  # uses OPENAI_BASE_URL=http://localhost:4096/v1, model opencode/muse-spark-1.2

# 2. Prep Qwen chat format
python prep.py  # -> data_30k/train.jsonl (9.5k) + eval.jsonl (0.5k)

# 3. Train (16GB VRAM OK via 4-bit)
python train.py  # Unsloth + SFTTrainer, 3 epochs, ~3.3h on 1 GPU

# 4. Evaluate BFCL
# See benchmarks/ — uses official BFCL harness with apply_chat_template(tools=...)

Key files:

  • train.py — full TrainingArguments + LoRA config
  • prep.pyto_qwen_tools + apply_chat_template formatting
  • generate_synthetic.py — APIGen 3-stage docstring
  • training_metrics.csv — loss/lr/grad curves
  • data_30k/my_10k_verified.jsonl — verified SFT data
  • serve/app.py — Flask 4-bit fallback server

Citation

@misc{qwen2.5-1.5b-fc2026,
  title={Qwen2.5-1.5B-Instruct-FC: 1.5B Function Calling via QLoRA SFT},
  author={Sagnik},
  year={2026},
  howpublished={\url{https://huggingface.co/sagnik3788/Qwen2.5-1.5B-Instruct-FC}},
  note={QLoRA r=16 on 10k APIGen synthetic examples, Unsloth SFT, BFCL v4 85.65\% Non-Live}
}
@misc{apigen2024,
  title={APIGen: Automated Pipeline for Generating Verifiable and Diverse Function-Calling Datasets},
  author={Liu et al.},
  year={2024},
  eprint={2406.18518},
  archivePrefix={arXiv}
}
@misc{bfcl2024,
  title={Berkeley Function Calling Leaderboard},
  author={Gorilla Team},
  year={2024},
  howpublished={\url{https://gorilla.cs.berkeley.edu/leaderboard.html}}
}
@misc{qwen2025,
  title={Qwen2.5 Technical Report},
  author={Qwen Team, Alibaba Cloud},
  year={2025},
  howpublished={\url{https://qwenlm.github.io/}}
}

Acknowledgements

  • Base: Alibaba Cloud Qwen2.5 team
  • Efficiency: Unsloth (FastLanguageModel, gradient checkpointing), Hugging Face TRL/Transformers
  • Data pipeline: Microsoft function-calling-data-synthesizer (APIGen)
  • Eval: Berkeley Gorilla BFCL

Contact: sagnik3788 on HF — issues/PRs welcome. If you fine-tune this further, please share your BFCL delta!

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

Model tree for sagnik3788/Qwen2.5-1.5B-Instruct-FC

Finetuned
(1813)
this model

Paper for sagnik3788/Qwen2.5-1.5B-Instruct-FC

Evaluation results