Udgam-384M-Chat · Udgam Labs

UDGAM LABS · Tool-calling model · 384M parameters

Udgam-384M-Chat

A 384M-parameter English model for on-device tool calling, trained from scratch by Udgam Labs. It is a decoder-only transformer compatible with the Gemma 4 architecture (Gemma4ForCausalLM), so it runs unmodified in Ollama, LM Studio, llama.cpp, MLX and transformers. Context: 4,096 tokens. The 4-bit GGUF is 238 MB. Built for four jobs inside an app:

  1. App actions: turn a request into a function call. "wake me at 6:45, label it gym" → set_alarm{time:"06:45", label:"gym"}.
  2. Document → JSON: fill a given JSON schema from a receipt, email, form or OCR text.
  3. Natural language → shell: propose one command (as a run_shell_command call with requires_confirmation, or as a plain command block) for a person to review.
  4. Relevance: notice when no offered tool fits (decline), when a required detail is missing (ask), or when a request should be refused.

What it is not: a knowledgeable general assistant (at 384M parameters it makes factual mistakes), or a safety-hardened open chatbot. Use it where your app decides which tools exist and confirms risky actions.

Model family: Udgam-384M-Base (2,048 context) · Udgam-384M-Base-4K · Udgam-384M-Chat · GGUF · MLX · MLX 4-bit · Technical report

Developer Udgam Labs
Parameters 384M (20 layers, hidden size 1,024)
Architecture decoder-only transformer, compatible with Gemma 4 (Gemma4ForCausalLM); sliding-window + global attention
Context 4,096 tokens
Language English
Training pretrained from scratch on roughly 8B tokens of licensed public and synthetic text, then post-trained with supervised fine-tuning for chat and tool use
Licence Apache-2.0

Quick start

Ollama (recommended)

Download a GGUF from the GGUF repo and create a model with its Modelfile (it uses Ollama's native Gemma 4 renderer and parser, so tool calls and the optional thinking mode work as trained):

FROM ./Udgam-384M-Chat-Q8_0.gguf
TEMPLATE {{ .Prompt }}
RENDERER gemma4
PARSER gemma4
PARAMETER temperature 0.1
PARAMETER top_p 0.9
PARAMETER top_k 40
PARAMETER repeat_penalty 1.1
PARAMETER repeat_last_n 64
PARAMETER num_predict 1024
PARAMETER num_ctx 4096
ollama create udgam -f Modelfile
ollama run udgam "Explain what a hash map is in two sentences."
curl http://localhost:11434/api/chat -d '{
  "model": "udgam", "stream": false, "think": false,
  "messages": [
    {"role": "system", "content": "Current date-time: Tuesday, 2027-08-17 09:12 (Europe/London, UTC+01:00)\nDevice: Android phone"},
    {"role": "user", "content": "Wake me up at 6:45 tomorrow, label it gym."}],
  "tools": [{"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm at a local time.",
     "parameters": {"type": "object", "properties": {"time": {"type": "string", "description": "HH:MM 24h"},
                    "label": {"type": "string", "description": "Optional label"}}, "required": ["time"]}}}]}'
# -> "tool_calls": [{"function": {"name": "set_alarm", "arguments": {"label": "Gym", "time": "06:45"}}}]

Thinking is on by default in Ollama for Gemma 4 models; send "think": false (or /set nothink) to turn it off. For this model thinking is optional and usually not better (see the tables).

LM Studio

  1. Search for udgamlabs/Udgam-384M-Chat-GGUF (or drop the .gguf into LM Studio's models folder) and pick Q8_0 (default) or Q4_K_M (phones / low memory).
  2. The GGUF carries the Gemma 4 chat template, so the prompt format is set automatically. Set Context Length 4096, temperature 0.1, top-p 0.9, top-k 40, repeat penalty 1.1.
  3. For tool use, call LM Studio's OpenAI-compatible server (/v1/chat/completions with tools). If your version does not parse Gemma 4 tool calls into tool_calls, read them from the text: <|tool_call>call:NAME{key:<|"|>value<|"|>,...}<tool_call|>.

llama.cpp

llama-server -m Udgam-384M-Chat-Q8_0.gguf -c 4096 --jinja                  # OpenAI-compatible API, parsed tool_calls
llama-server -m Udgam-384M-Chat-Q8_0.gguf -c 4096 --jinja --reasoning off  # same, thinking off
llama-cli    -m Udgam-384M-Chat-Q8_0.gguf -c 4096 -cnv                     # interactive chat

With the set_alarm tool above, /v1/chat/completions returns tool_calls: [{"name": "set_alarm", "arguments": "{\"label\":\"Gym\",\"time\":\"06:45\"}"}] (tested with llama.cpp build d7a695e).

MLX (Apple silicon)

pip install mlx-lm
mlx_lm.generate --model udgamlabs/Udgam-384M-Chat-MLX-4bit --prompt "Give me three tips for sleeping better." --max-tokens 200

transformers

# tested with transformers 5.18
from transformers import AutoTokenizer, AutoModelForCausalLM
tok = AutoTokenizer.from_pretrained("udgamlabs/Udgam-384M-Chat")
model = AutoModelForCausalLM.from_pretrained("udgamlabs/Udgam-384M-Chat")
tools = [{"type": "function", "function": {"name": "set_alarm", "description": "Set an alarm at a local time.",
          "parameters": {"type": "object", "properties": {"time": {"type": "string", "description": "HH:MM 24h"},
                         "label": {"type": "string"}}, "required": ["time"]}}}]
msgs = [{"role": "user", "content": "Wake me up at 6:45, label it gym."}]
inputs = tok.apply_chat_template(msgs, tools=tools, add_generation_prompt=True, return_dict=True, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=128, do_sample=False)   # thinking off unless enable_thinking=True
print(tok.decode(out[0, inputs["input_ids"].shape[1]:], skip_special_tokens=False))
# <|tool_call>call:set_alarm{label:<|"|>Gym<|"|>,time:<|"|>06:45<|"|>}<tool_call|><|tool_response>

Use fp32 (or llama.cpp) if you need bit-exact greedy results; bf16 generation in transformers depends on batch shape.

Prompt format

The standard Gemma 4 chat format (the template is identical to the official one). A tool turn:

<bos><|turn>system
Current date-time: Tuesday, 2027-08-17 09:12 (Europe/London, UTC+01:00)
Device: Android phone<|tool>declaration:set_alarm{description:<|"|>Set an alarm at a local time.<|"|>,parameters:{properties:{label:{description:<|"|>Optional label<|"|>,type:<|"|>STRING<|"|>},time:{description:<|"|>HH:MM 24h<|"|>,type:<|"|>STRING<|"|>}},required:[<|"|>time<|"|>],type:<|"|>OBJECT<|"|>}}<tool|><turn|>
<|turn>user
Wake me up at 6:45 tomorrow, label it gym.<turn|>
<|turn>model
<|tool_call>call:set_alarm{label:<|"|>gym<|"|>,time:<|"|>06:45<|"|>}<tool_call|><|tool_response>

The model stops at <|tool_response>; your app runs the tool, sends the result back as a tool message, and the model writes the final reply. With thinking on, <|think|> is added after <|turn>system\n and the model may write <|channel>thought\n…<channel|> first.

Tips: put the current date-time, time zone and device in the system message (as above) so relative dates resolve; give one JSON schema (or field list) for document → JSON; offer only the tools the user may use right now.

Example outputs (Ollama, Q8_0, thinking off): "Text Priya that I'll be 10 minutes late." → send_message{contact:"Priya", text:"I'll be 10 minutes late"}; "Send a message to Rahul." → "What should the message to Rahul say?"; "Book me a table for two at 8pm." (only alarm / message / weather tools offered) → "I can't make restaurant bookings here. Want me to set an alarm for 8 pm so you can ring them?"; a receipt → {"vendor": "Corner Cafe", "total": 8.9, "date": "2027-08-14"}. A typical mistake: "What's the boiling point of water in Fahrenheit?" → "about 37 degrees Celsius, roughly 100 degrees Fahrenheit" (wrong).

Recommended quantization and sampling

Quantization: use Q8_0 by default (lossless within noise) and Q4_K_M on phones / low-memory devices (small loss, see below). Avoid Q4_0 unless your runtime lacks K-quants.

setting tool / app use (default Modelfile) open chat (Modelfile.chat)
temperature 0.1 0.5
top_p / top_k 0.9 / 40 0.9 / 40
repeat_penalty (last 64 tokens) 1.1 1.1
num_predict / num_ctx 1024 / 4096 1024 / 4096

Why: temperature is what costs tool accuracy (BFCL simple in Ollama: 60% greedy, 59% at T 0.1, 52% at 0.5, 46% at 0.7), while a mild repeat penalty removes repetition loops without hurting tool calls. In transformers, generation_config.json sets T 0.1 / top-p 0.9 / top-k 40 (no repetition_penalty: transformers applies it to the prompt too, which would penalise tool names). Use greedy decoding for reproducible tool calls.

Evaluation

Automatic checkers, greedy decoding with llama.cpp on a Mac. The held-out task evals are our own test sets (274 items) built on tools, document types and shell tasks that were excluded from all training data, plus a held-out date range. They follow this model's prompt conventions, so they favour it; BFCL v3, IFEval and XSTest are neutral public benchmarks.

Compared with other tiny open models (same harness, same prompts)

Each model used its own chat template and native tool-call format; outputs were scored by the same code.

metric (greedy, thinking off) Udgam-384M-Chat Q8_0 Udgam-384M-Chat Q4_K_M Granite 4.0 350M Granite 4.0 H 350M SmolLM2 360M Instruct Qwen3 0.6B (reference, ~1.6× params)
Held-out app actions (unseen tools): exact call 31.4 28.6 45.7 45.7 1.4 44.3
Held-out app actions: right decision (call / no call) 74 74 70 72 66 70
Held-out app actions: asks when a detail is missing 20 25 0 5 0 20
Held-out relevance: right decision 75 68.3 18.3 20 46.7 66.7
Held-out relevance: declines when no tool fits 74 66 2 4 44 66
Held-out document → JSON (unseen schemas): schema-valid 56 60 32 38 0 24
Held-out document → JSON: field F1 68.7 69.7 52.7 45 9.3 63.7
Held-out shell (unseen tasks): right behaviour (command / ask / refuse) 85.9 85.9 59.4 67.2 39.1 67.2
Held-out shell: bash syntax OK 96.6 96.6 97.7 98 100 95.1
Held-out shell: output matches when executed (n=26) 7.7 7.7 3.8 7.7 0 0
BFCL v3 simple (n=150) 60 62.7 90.7 88 4.7 80
BFCL v3 multiple (n=100) 67 64 88 90 5 76
BFCL v3 parallel (n=50) 46 50 82 76 0 64
BFCL v3 irrelevance (n=150) 68 65.3 55.3 66.7 75.3 88
IFEval prompt-level strict (n=541) 26.1 25.5 47 53.4 31.4 58
IFEval instruction-level strict 40.4 39.7 58.9 65.6 42.4 68.7
XSTest unsafe: declined (higher = safer) 54.0 54.0 80.0 92.5 27.5 17.0
XSTest safe: over-refused (lower is better) 14.8 14.8 37.2 44.8 10.0 3.2

Speed and memory (llama.cpp llama-bench, Q4_K_M, 512-token prompt, 128 generated tokens, Apple M6 Mac mini):

model (Q4_K_M GGUF) file params Mac GPU (Metal) prompt / generate, tok/s 4 CPU threads prompt / generate, tok/s RAM, CPU run
Udgam-384M-Chat 237 MB 384M 14,191 / 444 983 / 365 323 MB
Udgam-384M-Chat (Q8_0) 409 MB 384M 14,561 / 312 1,271 / 297 480 MB
Granite 4.0 350M 233 MB 352M 17,626 / 411 1,187 / 348 349 MB
Granite 4.0 H 350M 219 MB 340M 5,175 / 262 966 / 307 352 MB
SmolLM2 360M Instruct 269 MB 362M 14,175 / 358 1,378 / 253 238 MB
Qwen3 0.6B 478 MB 752M 9,774 / 282 657 / 213 579 MB

RAM = macOS peak memory footprint of the CPU run. Phones are slower; the relative order should be similar on CPU.

Where Udgam does well: deciding whether to act: declining when no offered tool fits (held-out relevance 75% vs 18–20% for both Granite models, which call a tool almost every time), asking for a missing detail (20–25% vs 0–5%), document → JSON against an unseen schema (schema-valid 56–60% vs 24–38%) and shell behaviour (86% vs 59–67%). It is also the fastest generator in the group on the Apple-silicon GPU (444 tokens/s at Q4_K_M).

Fairness notes:

  • The held-out task sets use this model's prompt conventions (date/device system headers, normalised argument values such as ISO dates); other models lose some points to convention rather than ability. BFCL, IFEval and XSTest are neutral.
  • On the neutral BFCL irrelevance test, Qwen3-0.6B (88%) and SmolLM2 (75%) beat Udgam (68%); Udgam's relevance advantage shows mainly on our own held-out relevance set.
  • Granite and Qwen3 used their native templates with tools; SmolLM2-360M's template has no tool support, so it got the function-calling system prompt from its model card. It mostly copied the example call from that prompt, so its tool scores (≈0–5%) mean "not a tool model", not "bad model".
  • Qwen3 ran with thinking off; Udgam with thinking off; Granite and SmolLM2 have no thinking mode. All greedy, Q8_0, same token limits, same scorer.
  • XSTest uses one refusal-phrase classifier for every model (first 300 characters); on this model it agreed with an LLM judge within 3 points.
  • Not compared: FunctionGemma-270M and Gemma 3 270M (gated licence terms on Hugging Face; not evaluated).

Quantization

metric (thinking off / on) f16 (reference) Q8_0 Q6_K Q4_K_M + imatrix Q4_K_M (no imatrix) Q4_0 + imatrix
Held-out app actions: exact call 32.9 / 25.7 31.4 / 24.3 32.9 / 22.9 28.6 / 28.6 30 / 18.6 28.6 / 21.4
Held-out app actions: right decision 74 / 69 74 / 67 76 / 67 74 / 74 77 / 71 74 / 76
Held-out app actions: asks when a detail is missing 20 / 25 20 / 20 30 / 25 25 / 25 30 / 20 25 / 45
Held-out relevance: right decision 76.7 / 63.3 75 / 65 76.7 / 61.7 68.3 / 63.3 78.3 / 68.3 71.7 / 71.7
Held-out relevance: declines when no tool fits 76 / 62 74 / 64 76 / 62 66 / 60 78 / 68 70 / 72
Held-out document → JSON: schema-valid 58 / 58 56 / 56 58 / 58 60 / 64 60 / 52 44 / 46
Held-out document → JSON: field F1 68.8 / 71.2 68.7 / 72.6 70.3 / 70.7 69.7 / 68.2 70 / 68.1 69.7 / 68.9
Held-out shell: right behaviour 85.9 / 82.8 85.9 / 79.7 87.5 / 76.6 85.9 / 85.9 85.9 / 79.7 84.4 / 82.8
Held-out shell: bash syntax OK 96.6 / 100 96.6 / 100 98.3 / 100 96.6 / 100 100 / 98.2 96.6 / 100
Held-out shell: output matches when executed (n=26) 7.7 / 7.7 7.7 / 7.7 7.7 / 7.7 7.7 / 7.7 7.7 / 7.7 7.7 / 7.7
BFCL v3 simple (n=150) 60 / 60.7 60 / 60.7 60 / 60.7 62.7 / 61.3 60 / 58.7 58 / 58
BFCL v3 multiple (n=100) 67 / 67 67 / 67 65 / 63 64 / 63 67 / 67 57 / 56
BFCL v3 parallel (n=50) 50 / 46 46 / 44 48 / 48 50 / 50 48 / 48 44 / 44
BFCL v3 irrelevance (n=150) 70 / 72 68 / 70 70.7 / 71.3 65.3 / 66 66 / 70 68.7 / 69.3
IFEval prompt-level strict (n=541) 26.2 / 26.2 26.1 / 26.1 27.2 / 27.5 25.5 / 26.2 24.6 / 24.4 27.4 / 28.8
IFEval instruction-level strict 40.5 / 40.2 40.4 / 40.5 40.9 / 41.5 39.7 / 40.5 38.4 / 38.6 41 / 42.2

Perplexity on held-out chat and tool-use text: f16 3.193, Q8_0 +0.07%, Q6_K +0.5%, Q4_K_M (imatrix) +4.2%, Q4_0 (imatrix) +8.3%.

Where it falls short

It is behind on exact tool calls and instruction following. Both Granite 4.0 350M models are far ahead on BFCL (simple 88–91% vs 60%, parallel 76–82% vs 46%), on held-out unseen-tool exact match (46% vs 31%) and on IFEval (47–53% vs 26%). Qwen3-0.6B (about 1.6 times the parameters) is also ahead on BFCL and IFEval. Shell commands are rarely correct when executed for any model here (0–8%).

On the neutral BFCL irrelevance test, Qwen3-0.6B and SmolLM2 also beat it (see the fairness notes above). For safety results and the tasks it is not meant for, see Safety and Intended use and limitations.

Safety

XSTest (200 clearly unsafe + 250 safe-but-alarming prompts), greedy, thinking off: the model declines 54% of clearly harmful prompts and over-refuses 15% of safe ones (refusal-phrase classifier, the same one used for every model compared; Q8_0 and Q4_K_M identical; an LLM judge gave 56.5% and 14.4%). It rarely declines XSTest's discriminatory-claim, privacy and historical-atrocity prompts, and it over-refuses idioms and homonyms ("kill time", "shoot a photo"). This is below what we would want for an open chatbot; treat the model as not safety-hardened.

  • For app actions: rely on your app's allowlist of tools, argument validation and user confirmation for anything with consequences (send, pay, delete, share, run); do not rely on the model's judgement.
  • For shell commands: always show the command and require confirmation; never auto-execute.
  • For user-facing open chat: put a moderation model or policy layer in front.

Intended use and limitations

Good fits: on-device assistants that map requests to a fixed set of app functions; form filling and receipt / email → JSON with a given schema; suggesting shell commands for a person to review; routing (call / ask / decline).

Use with care / not intended for:

  • Unsupervised actions. It picks a wrong tool or argument value often enough (exact match on unseen tools is about one in three) that any consequential action needs app-side allowlists, validation and confirmation.
  • Shell commands: syntax is usually valid, but on unfamiliar tasks only a small fraction produce the right output, and it does not reliably refuse destructive requests.
  • Unsupervised open-ended chat or advice: fluent but shallow and often factually wrong (capitals, arithmetic, unit conversions). Not for medical, legal or financial use.
  • Safety: see above (54% of harmful prompts declined, 15% over-refusal).
  • Context: 4,096 tokens. English only.
  • It prefers its own argument conventions (ISO dates, normalised values) and sometimes guesses a value instead of asking.

Training

Pretrained from scratch on roughly 8B tokens of licensed public and synthetic text (base: Udgam-384M-Base-4K), then post-trained with supervised fine-tuning for chat and tool use. See the technical report for an overview.

Licence

Apache-2.0 (weights and tokenizer); see LICENSE. The chat template is the official Gemma 4 template (Apache-2.0).

Citation

@misc{udgamlabs2026udgam384m,
  title        = {Udgam-384M: a small on-device tool-calling model trained from scratch},
  author       = {{Udgam Labs}},
  year         = {2026},
  howpublished = {\url{https://huggingface.co/udgamlabs/Udgam-384M-Chat}},
  note         = {Technical report: \url{https://udgamlabs.com/reports/udgam-384m}}
}

Contact

Udgam Labs. Please use the Community (discussions) tab of the model page for questions, issues and feedback.


About Udgam Labs. Udgam Labs is an independent AI research lab. We work from first principles: before we build, we understand why things work, whether we are training a model, fine-tuning one or applying AI to a new problem. Udgam (उद्गम) is Sanskrit for origin. udgamlabs.com

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

Model tree for udgamlabs/Udgam-384M-Chat

Finetuned
(1)
this model
Quantizations
4 models

Collection including udgamlabs/Udgam-384M-Chat