llm1b β a 1B-equivalent tool-using assistant trained in ~1 hour
Train a coherent, tool-calling LLM (warm-started from Qwen2.5-1.5B-Instruct, 1.54B params) from scratch on public SFT data in 61 minutes on a single Modal B300 β well under the 3-hour budget.
Final model: https://huggingface.co/KookiesXy/llm1b-1.5b-toolchat (public)
| Metric | Value |
|---|---|
| Training wall time | 61.2 min (5,478 steps) |
| Tokens trained | 179.5M (43,820 packed blocks Γ 4,096) |
| Throughput | ~48.9k train tok/s (bf16, SDPA, grad checkpointing) |
| Final holdout loss | 0.855 |
| Chat coherence (7 tasks, avg) | 1.0 |
| Tool tasks passed | 12/12 in 3 consecutive eval runs; correct tool + args in every run (12/12) |
What the model can do
- Chat: fluent multi-sentence answers, stories, haiku, explanations, plans.
- Tools: given 1β2 relevant tool schemas in the prompt, emits perfect
<tool_call>{"name": ..., "arguments": {...}}</tool_call>JSON, executes the tool in an agent loop, and answers from the result.
Example (from the eval transcript, weather_tokyo):
user: What's the weather in Tokyo?
model: <tool_call>{"name": "get_weather", "arguments": {"city": "Tokyo"}}</tool_call>
tool: {"city": "Tokyo", "temperature": 27, "condition": "rainy"}
model: The current temperature in Tokyo is 27 degrees Celsius and it's currently raining.
Please bring an umbrella if you go outside.
Why it's fast
The whole training run is a single tightly-packed SFT pass over a small, high-quality dataset. No RL, no alignment loop, no data scaling.
- Data: 201,754 unique examples
- Chat:
mlabonne/orpo-dpo-mix-40k(40k),databricks/databricks-dolly-15k(15k),HuggingFaceH4/ultrachat_200k(60k subsample) - Tools:
glaiveai/glaive-function-calling-v2(~11k), converted from the rawUSER/ASSISTANT/FUNCTION RESPONSEformat into native Qwenmessages+tools(OpenAI-style schemas) viaglaive_to_messages; plus handcrafted "golden" multi-round tool examples. Tool examples are repeated Γ2 so roughly 55% of training tokens are tool-use.
- Chat:
- Packing: examples are tokenized and packed into 4,096-token blocks with an
assistant-only label mask.
label[i] = ids[i+1] if assistant_mask[i+1] else -100β predicting the next token (not the current one) is what prevents the copy-collapse bug (loss β 0.001, newline-only generations). - Training: 1.5B params, bf16, SDPA attention, gradient checkpointing, fused AdamW, LR 1.5e-5 β 2e-6 cosine over 5,478 steps, batch 8 Γ 4096 = 32k tok/step, grad clip 1.0. On the B300 this sustains ~49k tok/s β the whole 179.5M-token pass finishes in ~61 min.
- Eval during training: holdout loss + chat samples + tool samples every 300
steps; best checkpoint saved; resume from
ckpt/last.pt.
Tool use: what worked and what didn't
- β
Native
<tools>format: training on Qwen's native tool-call rendering (schemas in the system message,<tool_call>XML output) makes the model emit clean JSON tool calls. - β
Small candidate toolset at inference: giving the model the relevant
1β2 schemas per request works reliably. The full 6-schema preamble degrades into
a
{"\n{"\n{...loop (the 1.5B model can't route among 6 similar schemas yet). In production this is standard "tool routing": pick the 1β2 likely tools first. - β
One tool call per agent round: the eval loop executes the first
<tool_call>and returns its result, like a real function-calling agent. The model sometimes emits extra speculative calls; executing only the first keeps the loop grounded. - β
Final eval (fixed harness): 12/12 tool tasks passed in three consecutive
runs, chat coherence 1.0. Earlier 10-11/12 runs were harness artifacts: the
loose-JSON fallback parsed example JSON printed in prose as a tool call, and
the mock
web_searchreturned NYC-pizza results for any query. The final harness parses only<tool_call>blocks and the mock echoes the query. - β οΈ Residual weaknesses: occasionally emits several calls in one turn, and on
rare long multi-round sessions can fall into the
{"\n{loop. Both are typical for a 1.5B model trained on only 180M tokens.
Reproduce
Prereqs: Modal account, hf-token secret, HF token in
C:\Users\User\.cache\huggingface\token (or HF_TOKEN).
$env:PYTHONUTF8 = "1" # Windows only
# 1. build the packed dataset (~9 min, CPU container)
modal run .\modal_train.py --step data --tag v2
# 2. train on a B300 (~61 min)
modal run .\modal_train.py --step train --run-id run-3
# 3. final eval on the exported model (~3 min, B300)
modal run .\modal_train.py --step eval --run-id run-3
The train step auto-exports hf_export/ (HF format) and pushes to
KookiesXy/llm1b-1.5b-toolchat. If the push ever fails (the pinned image's
push_to_hub rejects safe_serialization=), run:
modal run .\push_hf.py --run-id run-3
Inspect live checkpoints:
modal run .\diag.py --run-id run-3 --temperature 0.6 --one-tool
Files
data_build.pyβ dataset download, glaiveβQwen conversion, packing, holdout.train_core.pyβ packed SFT loop, eval, checkpointing, export, HF push.eval_core.pyβ functional eval: chat coherence + full tool loop with execution.modal_train.pyβ Modal app (data/train/eval steps).diag.pyβ raw generation diagnostics against a live checkpoint.push_hf.pyβ manual HF push of an exported run.smoke_b300.pyβ B300 throughput benchmark.iso.py/iso2.pyβ isolated syntax checks.
- Downloads last month
- -