NDP Nemotron-3 Nano 4B — Tool Calling

Fine-tune of unsloth/NVIDIA-Nemotron-3-Nano-4B on synthetic tool-use traces for the National Data Platform (NDP) MCP server. The model emits <tool_call> blocks invoking three NDP catalog tools: list_organizations, search_datasets, and get_dataset_details.

Quick stats

Base model unsloth/NVIDIA-Nemotron-3-Nano-4B (hybrid Mamba+Attention, 4B params)
Training method LoRA (r=8, alpha=16) via Unsloth + TRL SFTTrainer
Training data 1299 synthetic NDP tool-use examples, generated with gpt-oss:120b as teacher and curated with LLM-as-judge (threshold 7.0)
Steps / epochs 980 steps (~3 epochs)
Final train loss 0.0266
Training time 2725 s on 1× NVIDIA GH200
Peak VRAM 43.9 GB
Max seq length 4096

Intended use

This model is for tool-call generation against the NDP MCP server. Given a natural-language NDP query plus the three-tool catalog as tools=... to apply_chat_template, the model produces a tool call in Nemotron's XML format:

<tool_call>
<function=search_datasets>
<parameter=search_terms>
["climate"]
</parameter>
<parameter=server>
global
</parameter>
</function>
</tool_call>

NDP tool surface trained on

tool purpose
list_organizations(name_filter?, server?) List data publishers, optionally filtered
search_datasets(...) Simple (search_terms[]) or advanced (owner_org, resource_format, filter_list, …) dataset search
get_dataset_details(dataset_identifier, identifier_type?, server?) Full metadata by UUID or name slug

Tool catalog source-of-truth: configs/tools/ndp_tools.json.

Inference

The Nemotron-3 Nano family needs:

  • transformers>=5.3,<=5.5.0 (uses TokenizersBackend introduced in v5; capped by unsloth-zoo)
  • mamba_ssm==2.2.5 + causal_conv1d==1.5.2 (CUDA kernels compiled for your arch)
  • use_cache=False in generate() (current Nemotron-H modeling has a bug with cache_position)
import torch
# COMPAT: mamba_ssm 2.2.5 imports a class removed in transformers v5
import transformers.generation as _g, transformers.generation.utils as _gu
for cls in ("GreedySearchDecoderOnlyOutput", "SampleDecoderOnlyOutput"):
    if not hasattr(_g, cls):
        setattr(_g, cls, getattr(_gu, "GenerateDecoderOnlyOutput", _gu.ModelOutput))

from unsloth import FastLanguageModel
model, tok = FastLanguageModel.from_pretrained("shazzadulimun/NDP-Nemotron-3-Nano-4B-tool-calling", max_seq_length=4096, trust_remote_code=True)
FastLanguageModel.for_inference(model)

import json
TOOLS = json.load(open("ndp_tools_for_chat_template.json"))   # the catalog
messages = [{"role":"user","content":"Find datasets about climate."}]
text = tok.apply_chat_template(messages, tools=TOOLS, tokenize=False, add_generation_prompt=True)
inputs = tok(text, return_tensors="pt").to("cuda")
out = model.generate(
    **inputs, max_new_tokens=512, do_sample=False, use_cache=False,
    eos_token_id=tok.convert_tokens_to_ids("<|im_end|>"),
    stop_strings=["</function>"], tokenizer=tok,
)
print(tok.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=False))

Known limitations

  1. Phantom None parameters: the model often emits ALL schema parameters with None values for the unused ones (e.g. a list_organizations call may include dataset_name=None, search_terms=None, etc.). This is a learned artifact of seeing the full schema in the system message during training. Use a post-parser to strip parameters whose value is None/null/empty before invoking the real MCP server. Reference parser: test_inference.py:parse_tool_call.
  2. Looping on some queries: rarely (≈ 1/8 in our smoke test) the model loops on <parameter> blocks without emitting </function>. The tolerant parser variant recovers args even from truncated output. Setting stop_strings=["</function>"] at generation time helps when the model does emit it.
  3. NDP-specific only: the model has seen exactly three tools. It is not a general tool-use model — it will not generalize to other MCP catalogs.

Training data generation pipeline

NDP MCP server (3 tools)
   │
   ├─ tool-generate-full           (gpt-oss:120b)   → 1879 raw examples
   ├─ schema-filter                                 → 1879 (no drops)
   ├─ tool-curate                  (gpt-oss:120b)   → 1712 kept @ threshold 7.0
   └─ prepare_data.py → fine-tune  (Unsloth + TRL)

Generator + curator: SIslamMun/Generator.

Files in this repo

file purpose
model.safetensors fp16 merged weights (~7.5 GB) — load with transformers
tokenizer.json + tokenizer_config.json + chat_template.jinja tokenizer + Nemotron tool-aware chat template
modeling_nemotron_h.py + configuration_nemotron_h.py dynamic remote code (required by trust_remote_code=True)
*.gguf (if uploaded) GGUF quantizations for llama.cpp / Ollama / LMStudio

License

Inherits the NVIDIA Open Model License of the base model.

Downloads last month
153
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 shazzadulimun/NDP-Nemotron-3-Nano-4B-tool-calling