Views
No views yet
<tool_call> blocks invoking three NDP catalog tools:
list_organizations, search_datasets, and get_dataset_details.| 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 |
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>| 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 |
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)1import torch
2# COMPAT: mamba_ssm 2.2.5 imports a class removed in transformers v5
3import transformers.generation as _g, transformers.generation.utils as _gu
4for cls in ("GreedySearchDecoderOnlyOutput", "SampleDecoderOnlyOutput"):
5 if not hasattr(_g, cls):
6 setattr(_g, cls, getattr(_gu, "GenerateDecoderOnlyOutput", _gu.ModelOutput))
7
8from unsloth import FastLanguageModel
9model, tok = FastLanguageModel.from_pretrained("shazzadulimun/NDP-Nemotron-3-Nano-4B-tool-calling", max_seq_length=4096, trust_remote_code=True)
10FastLanguageModel.for_inference(model)
11
12import json
13TOOLS = json.load(open("ndp_tools_for_chat_template.json")) # the catalog
14messages = [{"role":"user","content":"Find datasets about climate."}]
15text = tok.apply_chat_template(messages, tools=TOOLS, tokenize=False, add_generation_prompt=True)
16inputs = tok(text, return_tensors="pt").to("cuda")
17out = model.generate(
18 **inputs, max_new_tokens=512, do_sample=False, use_cache=False,
19 eos_token_id=tok.convert_tokens_to_ids("<|im_end|>"),
20 stop_strings=["</function>"], tokenizer=tok,
21)
22print(tok.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=False))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.<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.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)| 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 |