OELLM 9B Anneal-300B Tool-Use SFT
An experimental full-parameter supervised fine-tune of
Neonkraft/oellm-9b-256k-theta64m-prelude-anneal300b-instruct-sft
for function calling. The training mix combines English tool-use conversations
with general instruction replay. This is an SFT checkpoint; it has not received
preference optimization or reinforcement learning. It is separate from the
Qwen3.5-4B-EU-Tool
checkpoint, which was a format reference and was not a training parent.
Status: Training completed on LUMI on 7 October 2026. Paired held-out tool-call evaluation completed on 9 October 2026. The model calls available tools accurately on this test but overcalls on unrelated requests; see the evaluation table below.
Model and format
The architecture is Qwen3ForCausalLM with 9,101,947,904 parameters and the
OpenEuroLLM/Qwen3-style ChatML token mapping inherited from the parent.
Available tools are described in <tools>...</tools> in the system turn; the
assistant emits <tool_call>{"name": ..., "arguments": {...}}</tool_call>.
Tool replies are represented as <tool_response>...</tool_response> in a user
turn. An empty <think>\n\n</think>\n\n block precedes assistant responses
after the latest user query. <|im_end|> (token ID 4) terminates assistant
turns. The packaged chat_template.jinja implements this format.
Context and RoPE caveat: The parent advertises a 256K context and RoPE
theta 64,000,000. This continuation was trained with Transformers 4.57.3,
which resolved the parent configuration to an effective RoPE theta of 10,000
because the 64M value was stored in rope_parameters while that version's
Qwen3 rotary implementation read rope_theta. The exported configuration
explicitly sets both fields to 10,000 to preserve the behavior used during
training. Training sequences were at most 4,096 tokens. Long-context
performance, including 256K tool use, has not been validated. The
max_position_embeddings architecture field remains 262,144; it is not a
validated context-length claim for this checkpoint.
Training used BF16 computation. The saved full-parameter weights are FP32 (about 36 GB), so plan memory accordingly or explicitly load in BF16 for inference.
Training data
| Component | Training rows | Approximate rendered tokens | Source and license |
|---|---|---|---|
| Tool use | 36,642 | 20,031,319 | birgermoell/oellm-eu-tooluse-v1, sft/train, Apache-2.0 |
| General instruction replay | 31,695 | 20,032,863 | Dolci Instruct slice from the existing Anneal-300B data artifact; allenai/Dolci-Instruct-SFT, ODC-By-1.0 |
| Total | 68,337 | 40,064,182 | Approximately 1:1 rendered-token mix |
The tool-use dataset has 55,077 source rows and is derived from
glaiveai/glaive-function-calling-v2
(50,000 rows), Team-ACE/ToolACE
(3,184), and
NousResearch/hermes-function-calling-v1
(1,893). Its dataset card describes all three as Apache-2.0 and documents
their normalization into <tool_call> JSON. No examples from its grpo
configuration were used.
Preparation trimmed trailing non-assistant turns, filtered 14 overlength tool rows, and removed 16,473 duplicate tool prompts keyed on both the system tool schema and the first user request. A deterministic 5% prompt split held out 1,948 tool conversations (about 1,048,852 rendered tokens). Dolci replay was selected from the parent training artifact until it matched the retained tool training tokens. There were only 10 no-call conversations in the entire tool-use source after normalization. This is a major limitation for learning when to abstain from calling a tool.
The counts and checksums above come from the saved
training_data_manifest.json. They were
computed during data construction with the earlier chat template; the final
tool-use template adds an empty think block to intermediate tool calls, so
these token counts are approximate for the actual training serialization.
Training procedure
| Item | Value |
|---|---|
| Method | Full-parameter SFT, assistant-only loss; system, user, and tool text are context |
| Packing | TRL best-fit decreasing, up to 4,096 tokens per packed sequence |
| Steps | 800 optimizer updates; approximately 104.09M packed tokens, 2.59 data epochs |
| Global batch | 32 packed sequences (32 GCDs × 1 sequence × 1 accumulation step) |
| Learning rate | Peak 2e-6; cosine schedule; 24 warmup steps |
| Optimizer | AdamW, β₁=0.9, β₂=0.95, ε=1e-8; no weight decay; gradient clip 1.0 |
| Runtime | 1 hour 28 minutes 51 seconds on 4 LUMI-G nodes, 8 AMD MI250X GCDs per node |
| Software | Python 3.12.3, PyTorch 2.9.1+ROCm 6.4, Transformers 4.57.3, TRL 1.4.0, Accelerate 1.12.0 |
| Final training loss | 0.5378; not a held-out evaluation metric |
The parent revision was
85bf18fb4f0bee6ac6270f06b1d1c6b3be200f31. The training data manifest
SHA-256 was caf837e99a93e2bcc9b71509980e60eba8895184216c541bc52548f5a92a3ac0.
The train Parquet SHA-256 was
01cbe9ecd71b7ef6f3b6c177ad06cc9692ff8489811eb13dc83b07a673d1f87f.
The repository state was based on
BirgerMoell/oellm-reasoning-training
commit 7b9f05a66bdfd6ca5b0d5e9c88cdabcc69098ca2 plus local uncommitted
tool-use changes. LUMI Slurm job: 22546173.
Usage
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
repo_id = "birgermoell/oellm-9b-anneal300b-tooluse-sft"
tokenizer = AutoTokenizer.from_pretrained(repo_id)
model = AutoModelForCausalLM.from_pretrained(
repo_id, torch_dtype=torch.bfloat16, device_map="auto"
).eval()
tools = [{"type": "function", "function": {
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {"type": "object", "properties": {
"city": {"type": "string"}}, "required": ["city"]}
}}]
messages = [{"role": "user", "content": "What is the weather in Helsinki?"}]
inputs = tokenizer.apply_chat_template(
messages, tools=tools, add_generation_prompt=True,
enable_thinking=False, return_dict=True, return_tensors="pt",
).to(model.device)
inputs.pop("token_type_ids", None) # needed with some Transformers versions
with torch.inference_mode():
output = model.generate(**inputs, max_new_tokens=192, do_sample=False)
print(tokenizer.decode(output[0, inputs["input_ids"].shape[-1]:],
skip_special_tokens=False))
The dataset embedded tool signatures directly in system messages. Supplying
tools= as above renders the same tool wrapper through the packaged template.
Execute any returned tool call in your own application, then append the result
as a tool message and generate again. The model weights do not execute tools.
With Transformers 4.57.3, the tokenizer's saved metadata may trigger a
TypeError in AutoTokenizer. A compatible fallback is to load
tokenizer.json with PreTrainedTokenizerFast and set its chat_template
from chat_template.jinja (both files are in this repository). The training
and evaluation code use that fallback.
Evaluation and limitations
The paired held-out evaluation compares this checkpoint with its direct parent
using greedy decoding and at most 192 new tokens. It uses 100 callable prompts
from the held-out tool split and 100 untouched Dolci instruction prompts paired
with an irrelevant calculate_tip tool. The latter is a synthetic relevance
probe; the tool dataset itself contained too few no-call conversations for a
balanced held-out sample.
| Metric (100 prompts per class) | Parent SFT | Tool-use SFT |
|---|---|---|
| Well-formed tool call on callable prompts | 0/100 (0%) | 99/100 (99%) |
| Correct function name | 0/100 (0%) | 99/100 (99%) |
| Exact function name and arguments | 0/100 (0%) | 88/100 (88%) |
| Correct abstention on unrelated prompts | 100/100 (100%) | 27/100 (27%) |
The continuation learned call syntax and argument selection on this test, but it called the irrelevant available tool on 73 of 100 unrelated prompts. The parent produced no well-formed calls on the callable prompts. The result supports improving no-call/relevance training before relying on this model in an agent that exposes optional tools. Training loss alone did not reveal this tradeoff.
The test is narrower than full BFCL: it has only 200 examples, the no-call
class uses an artificial unrelated tool, and it does not measure multi-call
planning, tool execution, long-context behavior, or real-world tool choice.
The tokenizer loaders used for the parent and candidate produced identical
input IDs on all 200 evaluation prompts despite a Transformers warning about
the candidate tokenizer regex. LUMI evaluation job 22605103 completed in
44 minutes 23 seconds; the machine-readable results are in the training
workspace at artifacts/eval/tooluse-anneal300b-v2/paired-heldout.json.
The tool-use training data is English-only. The model can produce malformed arguments, select the wrong function, or call a tool when none is needed. No safety or multilingual tool-use benchmark has been run for this checkpoint.
License and attribution
The parent model and tool-use dataset are Apache-2.0. The Dolci instruction replay is ODC-By-1.0; retain its attribution and review the upstream dataset card and responsible-use terms. Training was performed on LUMI, hosted by CSC in Finland, for OpenEuroLLM research.