MiniCPM5-1B-Agentic-Tooluse

LoRA adapter for openbmb/MiniCPM5-1B, fine-tuned on ToolACE

for single-turn function calling: given a conversation and a set of tool schemas, emit the first tool call

with the correct name and correct argument values.

Trained on a single Kaggle T4 with Unsloth + TRL SFT.

Results

Evaluated on a held-out 300-example test slice drawn from a seeded shuffle of ToolACE (see Split integrity).

The base-model column is the same model with the same prompt and no adapter.

The published weights are SFT + GRPO (see GRPO / RLVR). The SFT column is kept because every

negative result below is measured against it.

metric v2 (previous release) SFT retrain (pre-GRPO) v3 = SFT + GRPO (published)
parseable — output is a well-formed call 0.9933 1.0000 1.0000
valid_name — name exists among the offered tools 0.9700 0.9867 0.9867
expected_name — name matches gold 0.9067 0.9567 0.9533
args_exactevery argument value matches gold 0.6133 0.7367 0.7467
arg_key_overlap — F1 over argument keys 0.8757 0.9422 0.9388
mean of 5 0.8718 0.9245 0.9251

v2 (previous release) = the previously published SFT adapter. An earlier draft of this card

mislabeled this column "base model (untrained)" -- that was wrong; it is NOT the raw base model.

The real untrained openbmb/MiniCPM5-1B, measured on this same test slice, scores parseable

0.9333, valid_name 0.9133, expected_name 0.8867, args_exact 0.6300, arg_key_overlap 0.8920.

SFT retrain = a fresh SFT pass from v2, prior to GRPO. v3 = what this repo currently serves.

Every "did it improve?" decision in this card is judged against v2, not against the untrained

base model — beating an untrained model is not evidence of anything.

GRPO buys +0.0100 on args_exact, the metric that matters here, and gives back 0.0034 (one test example

each) on expected_name and arg_key_overlap. That trade is reported rather than hidden: the mean moves

only +0.0006, so this is a targeted gain on the hardest metric, not a broad improvement.

Four of the five metrics are above 0.80. args_exact is not, and the next section explains how much of it is

actually reachable.

Reproducibility

Two independent training runs were performed. They converged to identical args_exact (0.7367) despite

different data ordering, and one differing in data composition.

metric run 1 run 2 (composite-oversampled)
parseable 1.0000 1.0000
valid_name 0.9900 0.9867
expected_name 0.9567 0.9567
args_exact 0.7367 0.7367
arg_key_overlap 0.9388 0.9422

The weights published here are run 2.

Honest limits of args_exact

args_exact is strict and all-or-nothing over every argument value. Its measured ceiling on this test slice is

not 1.0:

  • 9.33% of test cases are unwinnable. 28 of 300 gold calls contain a date that appears nowhere in the

    prompt. There is no anchor "today" to resolve them against — the gold dates span 1990–2027 across 75 distinct

    values, so no single assumed current date recovers them. This caps args_exact at 0.9067.

  • The dominant remaining error class is composite JSON arguments (~14–20% of argument values are nested

    objects or arrays), where the model must reproduce an entire nested structure exactly.

So 0.7367 sits against a practical ceiling of 0.9067, closing about 42% of the base-model-to-ceiling gap

(0.6133 → 0.7367, out of a possible 0.6133 → 0.9067).

The grader was deliberately not loosened. It does normalize formatting-only differences (key order,

whitespace, 70 vs 70.0) and is guarded at runtime by assertions in both directions: 10 must-differ pairs must

be rejected and 5 formatting-only pairs must be accepted. Relaxing date comparison, or dropping the unwinnable

cases from the denominator, would have raised the headline number without improving the model.

What did not work

Measured negative and null results, recorded so they need not be re-tried:

  • Oversampling composite-JSON examples — no effect on args_exact (0.7367 in both runs).

  • Thinking mode on — clearly harmful: validation args_exact fell 0.7367 → 0.5267. Consistent with

    TAFC (arXiv:2601.18282), which notes over-reasoning degrades simple

    single-parameter function calls.

  • Longer training — validation plateaued (run 1 at step 600, run 2 at step 750).

  • Self-consistency / majority voting — not used. ToolPRM (arXiv:2510.14703)

    measures majority voting degrading argument F1 on function calling (Hammer2.1-3B: 62.83 → 58.27), because

    structured output cannot recover from an early error, so non-greedy sampling ruins whole trajectories.

    Decoding here is greedy.

GRPO / RLVR — partial result

Because SFT was demonstrably saturated (two runs, identical args_exact, val plateaued), the next lever tried was

RL with a verifiable reward: GRPO where the reward is this repo's own grader, so reward and reported metric

cannot drift apart. Reward design followed ToolRL (arXiv:2504.13958)

fine-grained decomposition (parseable / tool name / argument keys / argument values) rather than all-or-nothing,

correctness weighted 0.90 against format 0.10, and no length reward.

It works, and the published weights include it. With a validation ratchet that only keeps a checkpoint

beating the previous best, across several short sessions:


[ratchet] step 150   val args_exact 0.7467  (SFT 0.7367)   <-- kept

[ratchet] step  40   val args_exact 0.7533  (best 0.7467)  <-- kept   (higher-exploration config)

[ratchet] step  80   val args_exact 0.7500  (best 0.7533)  <-- rejected

The step-40 checkpoint was then measured once on the held-out test slice: args_exact 0.7367 → 0.7467.

Why GRPO stalls here, measured

The first configuration (4 generations, temperature 1.0) gained zero test examples over 100 steps. The cause

was instrumented rather than guessed — the fraction of prompt groups whose samples all receive identical

reward:


[collapse] 18/25 groups (72%) had identical rewards -> zero advantage -> no gradient

[collapse] 37/50 groups (74%) ...

[collapse] 55/75 groups (73%) ...

GRPO's advantage is reward minus the group mean, so a group where every sample scores the same contributes

exactly nothing. ~73% of the compute produced no gradient at all. This is the within-group reward collapse

described by RC-GRPO (arXiv:2602.03025), and it is a direct

consequence of the SFT model already being good: most prompts come back all-right (or all-wrong) across the

whole group.

The DPO run independently measured the same thing from a different angle — 289 of 400 prompts (72%) were

already correct and yielded no preference pair. Two unrelated experiments, the same ~72%.

Two consequences worth carrying forward:

  1. Raising exploration helps. Going to 8 generations at temperature 1.15 lifted the observed rate from

    +0.0033 to roughly +0.0165 per 100 steps. Sampling is raised for training only; evaluation and the

    shipped model remain strictly greedy, so this changes learning rather than the reported metric.

  2. Step count is a poor proxy for progress. Only ~27% of steps do anything, so "N steps" should be read

    as "~0.27N effective steps" when planning a budget.

DPO — also tried, also did not beat SFT

TinyLLM (arXiv:2511.22138) benchmarks sub-3B models on exactly this task

family and recommends preference optimization over RL for compute-constrained settings ("SFT offers limited

gains"; PPO is "computationally demanding — less ideal for edge deployment"). So DPO was tried, with preference

pairs built free from the model's own errors (chosen = gold call, rejected = what the model emitted).

Measured: val args_exact 0.7367 → 0.7333. Not an improvement, so no test pass was spent and nothing

was published.

Two findings worth recording, both of which make DPO less attractive here than the literature implies:

  1. Pair generation dominates the cost. 400 prompts took 21.5 min of generation on a T4; DPO training

    itself then took 6.9 min. DPO needs no generation during training, but building the dataset is expensive.

  2. Yield is low precisely because the SFT model is good. 289 of 400 training prompts (72%) were *already

    correct* and produce no pair. Only 111 usable pairs came out — far too few to move a 1B model. Collecting

    thousands of pairs means an hour or more of generation before a single training step.

Summary of every method tried

method val args_exact outcome
v2 (previous release) test 0.6133
base model (no adapter, untrained) test 0.6300
v2 = SFT (2 independent runs) 0.7367 superseded by v3
SFT + composite oversampling 0.7367 no change
thinking-on 0.6167 / 0.5267 clearly worse
GRPO, lr 2e-5 / β 0.01 0.6867 worse — too aggressive
GRPO, lr 5e-6 / β 0.04, 4 gen @ T=1.0 0.7467 better, then stalled (73% dead groups)
GRPO, lr 5e-6 / β 0.04, 8 gen @ T=1.15 0.7533 published (test 0.7467)
DPO, 111 pairs 0.7333 worse
constrained / grammar-guided decoding not run ruled out on evidence — see below
self-consistency / majority voting not run ruled out by ToolPRM

Constrained decoding was considered and rejected before spending GPU time on it. Grammar-constrained

decoding improves function calling mainly by eliminating malformed calls, but this model is already at

parseable 1.0000 and valid_name 0.9867 — there are essentially none left to eliminate. Its failures are

semantic (wrong argument values), which schema constraints do not fix. Two 2026 results also warn it can

cost accuracy: Constraint Tax (arXiv:2606.25605) finds structured

output constraints suppress tool calling, and The Format Tax (arXiv:2604.03616)

finds format constraints degrade the reasoning the model would otherwise do.

The remaining gap to 0.80 is 0.0533 (16 test examples) against a hard ceiling of 0.9067. GRPO is the only lever

that has moved it, and its effective rate depends far more on how many groups produce a non-zero advantage than

on raw step count.

A bug worth knowing about

The first long GRPO attempt died ~2.9h in inside the reward function:


schema_literal_fix → if v.lower() == ev.lower()

AttributeError: 'int' object has no attribute 'lower'

Tool schemas may declare non-string enums ("enum": [1, 2, 3]); the value was type-guarded but the enum

member was not. The 300-row eval slices contain no integer enum, so this never surfaced in any evaluation — it

took a 3000-row training pool to hit it. Fixed by comparing against str(ev) (for string enums str(ev) is ev,

so no previously measured number changes), with a regression assert that now fails in seconds on CPU rather than

hours into a GPU run.

Split integrity

ToolACE's data.json is grouped, not shuffled — contiguous slices land on very different distributions, so a

naive select(range(...)) split yields train/test sets that are not comparable. The dataset is therefore shuffled

with a fixed seed (SPLIT_SEED = 3407) and filtered to usable rows before slicing into test / validation / train.

Checkpoints were selected on validation only; the test slice was evaluated once, at the end.

Training configuration

base openbmb/MiniCPM5-1B, 4-bit
LoRA r=32, alpha=64, dropout=0.05
target modules q, k, v, o, gate, up, down
epochs 3
learning rate 1e-4, cosine, warmup ratio 0.1
batch size 16
max seq len 4096 (prompt cap 1536)
train / val / test 9000 / 300 / 300
hardware 1× Kaggle T4

Usage


from peft import PeftModel

from transformers import AutoModelForCausalLM, AutoTokenizer

base = "openbmb/MiniCPM5-1B"

tok = AutoTokenizer.from_pretrained(base, trust_remote_code=True)

model = AutoModelForCausalLM.from_pretrained(base, trust_remote_code=True, device_map="auto")

model = PeftModel.from_pretrained(model, "ewinregirgojr/MiniCPM5-1B-Agentic-Tooluse")

prompt = tok.apply_chat_template(

    messages, tools=tools, add_generation_prompt=True,

    enable_thinking=False,          # thinking OFF — see "What did not work"

    tokenize=False,

)

inputs = tok(prompt, return_tensors="pt").to(model.device)

out = model.generate(**inputs, max_new_tokens=256, do_sample=False)   # greedy

enable_thinking=False and greedy decoding are both load-bearing for the numbers above.

Framework versions

  • PEFT 0.19.1

  • transformers 4.57.3

  • torch 2.8.0

  • TRL 0.24.x, Unsloth

Base model architecture

MiniCPM5-1B uses a standard LlamaForCausalLM architecture:

Property Value
Parameters (total) 1,080,632,832
Parameters (non-embedding) 679,552,512
Architecture LlamaForCausalLM
Layers 24
Attention heads (GQA) 16 Q / 2 KV
Context length 131,072 tokens
Training SFT → RL (GRPO) fine-tune on openbmb/MiniCPM5-1B

Limitations

ModelScope

The base model is also available on ModelScope (for users in China and East Asia):

(The fine-tuned adapter/GGUF builds are currently HuggingFace-only.)

Citation

If you use this model, please cite the base model paper:

@article{minicpm4,
  title   = {MiniCPM4: Ultra-Efficient LLMs on End Devices},
  author  = {MiniCPM Team},
  journal = {arXiv preprint arXiv:2506.07900},
  year    = {2025}
}

And the ToolACE dataset used for fine-tuning:

@article{toolace,
  title   = {ToolACE: Winning the Points of LLM Function Calling},
  author  = {Liu, Ying and others},
  journal = {arXiv preprint arXiv:2409.00920},
  year    = {2024}
}
Downloads last month
165
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for ewinregirgojr/MiniCPM5-1B-Agentic-Tooluse

Adapter
(48)
this model

Dataset used to train ewinregirgojr/MiniCPM5-1B-Agentic-Tooluse

Papers for ewinregirgojr/MiniCPM5-1B-Agentic-Tooluse

Evaluation results

  • Parseable tool-call rate on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    1.000
  • Valid available-tool name rate on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    0.987
  • Expected tool-name rate on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    0.953
  • Exact-arguments rate on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    0.747
  • Argument-key overlap on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    0.939
  • No-schema-copy rate on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    0.997
  • No-repetition rate on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    0.340
  • Stopped-cleanly rate on External ToolACE-derived first-call evaluation (held-out 300 examples)
    self-reported
    0.000