What this is
A LoRA adapter for ibm-granite/granite-4.1-3b that teaches
the model to answer questions about Warhammer 40,000 10th-edition rules data by calling a fixed set of 15
read-only tools (the wh-mcp Model Context Protocol server from
warhammer-graph), to state only what the tools return, and to
say it cannot find something instead of answering with a similar-looking unit's numbers.
It is not a rules expert. Without the tools it knows nothing about the game beyond the base model. The facts
come from the tool results, and the adapter changes how the model uses the tools.
A quantized GGUF with this adapter merged in is in the companion repository
jressler/granite-4.1-3b-wh40k-tools-GGUF.
Results
Measured with the real agent loop and an automatic, rule-based grader (no language model judges anything),
served by llama-server b11476 (--jinja), context 16384, temperature 0, Q4_K_M quantization. The
"control" is the untrained base model put through the identical conversion and quantization, so the difference
is the training and not the quantization.
Golden set (60 questions, held out from training):
Table with columns: Answer exact match, Tool use correct, Declined correctly, Hallucination | Answer exact match | Tool use correct | Declined correctly | Hallucination |
|---|
| Untrained base (control) | 86.3% (44/51) | 92.9% | 11.1% (1/9) | 1.7% |
| This adapter | 98.0% (50/51) | 100% (56/56) | 66.7% (6/9) | 1.7% |
By question type for this adapter: lookup 22/22, comparison 10/10, counting 18/19, declining 6/9.
Dev set (204 generated questions, not used for training):
Table with columns: Answer exact match, Tool use correct, Declined correctly, Hallucination | Answer exact match | Tool use correct | Declined correctly | Hallucination |
|---|
| Untrained base (control) | 82.9% (121/146) | 85.6% | 19.0% (11/58) | 6.9% |
| This adapter | 97.3% (142/146) | 97.3% | 75.9% (44/58) | 0.0% |
Over-declining was low: 2 of 146 answerable dev questions were wrongly declined (1.4%).
Read these numbers with care. Each model was run once at temperature 0, so one golden question is 1.7
points. There are only 9 "should decline" questions on the golden set, so one answer moves that figure by 11
points; the 58 on the dev set are the steadier signal. The golden set and the dev set were written by the same
author, in the same style, as the training questions.
What it still gets wrong
- Invented stratagem names and characteristics that the data does not have (for example asking for a unit's
"Doom Rating") are the weakest cases on the dev set: 12 of its 18 misses.
- On the golden set it miscounted the factions (said 20, the right answer is 26); it answered an invented
keyword ("Cavalry") with a similar real one; and on two invented-unit questions it kept searching and gave no
answer.
- It can still state a number that no tool returned (1.7% of golden answers).
- Only the question styles it was trained on were tested. Free-form or multi-step questions were not.
This model does not know Warhammer 40,000 rules on its own. It was trained to answer by calling the 15 read-only
tools of the wh-mcp server from warhammer-graph, and to say
it cannot find something when the tools return nothing. Without that server it has nothing to look facts up in,
and its answers will be unreliable. One command sets up the tools and the model, then prints everything you
need to connect your own client.
You need Python 3.12 or newer, Rust 1.88 or newer (rustup), git, and llama-server from
llama.cpp on your PATH (tested with build b11476). No GPU is
required.
git clone https://github.com/joeressler/warhammer-graphcd warhammer-graphpython scripts/quickstart.py
It builds the tools (downloading the public Wahapedia data export, which is not included here or in that
repository, and compiling wh-mcp), downloads this model into ./models with a SHA-256 check, and saves the exact
system prompt the model was trained with. It then prints a connection sheet with your real paths and ready to
paste commands:
- the
llama-server command that starts the model (with --jinja, which tool calling needs) and its endpoint,
- the
wh-mcp command, as an mcpServers JSON block and as a claude mcp add line, to add the tools to your client,
- the settings the model expects: the system prompt file, temperature 0, a 16384-token context, the tool-call loop.
Print the sheet again any time with python scripts/quickstart.py --info. Add --ask "your question" to try the
model straight away, or --serve to keep the model server running. The model is trained for these tools and this
system prompt, so connect it through a client that passes both. See the
quickstart in the README
for all options.
How to use it
The adapter expects the wh-mcp tool list and system prompt it was trained with, and the base model's chat
template (tools are rendered by the template; tool calls are written as <tool_call> JSON blocks). Loading it
with PEFT:
import torchfrom peft import PeftModelfrom transformers import AutoModelForCausalLM, AutoTokenizer base = "ibm-granite/granite-4.1-3b" # trained on revision c0650403e44e78ec0262dab1c90914c65b196c4etokenizer = AutoTokenizer.from_pretrained(base)model = AutoModelForCausalLM.from_pretrained(base, dtype=torch.bfloat16, device_map="auto")model = PeftModel.from_pretrained(model, "jressler/granite-4.1-3b-wh40k-tools-lora")
To run the full agent (the model plus the tools), use the host in
warhammer-graph, which starts wh-mcp, shows the model the
tools, runs its tool calls and returns the final answer. For llama.cpp, use the GGUF repository.
Training
Table | |
|---|
| Method | QLoRA supervised fine-tuning: 4-bit NF4 base, LoRA rank 16, alpha 32, dropout 0.05, on every attention and MLP projection (q,k,v,o,gate,up,down) |
| Loss | on the assistant's own tokens only (tool calls, answers and the end-of-turn marker); never on the system prompt, tool list, question or tool results |
| Data | 1,757 conversations (about 9.5 million tokens per epoch), from 940 distinct generated questions |
| Schedule | 2 epochs, 879 steps of 4 sequences, learning rate 2e-4 with cosine decay, bf16 compute |
| Hardware | one RTX 5000 Ada laptop GPU (16 GB), 5 h 55 min, peak 8.4 GiB |
| Software | PyTorch 2.14.1, Transformers 5.19.0, TRL 1.14.2, PEFT 0.21.2, bitsandbytes 0.50.2 |
The training data is not released. The questions were generated from the Wahapedia-derived graph
(24 question families: typed lookups, counts, comparisons, and eight kinds of "cannot answer"). A larger
model (gpt-oss-20b) answered them through the real agent loop, and only conversations an automatic grader
passed were kept. While collecting, the teacher was given a short extra instruction about declining
correctly; it was removed from every saved conversation, so the student learned the behaviour without being
told. The training questions share no unit, detachment, keyword, faction or question wording with the golden
set; this is checked by code.
Limitations and responsible use
- 10th edition only, from one snapshot of Wahapedia's data. Rules change; this will go out of date.
- Not for tournament rulings or anything where a wrong number matters. Check the source.
- English only. Small model. Evaluated on one set of tools.
- The weights were trained on conversations containing Wahapedia-derived text, so the model may reproduce
fragments of it.
Licence and credits
Apache License 2.0, as the base model. Base model: IBM Granite 4.1 3B (Apache 2.0). Teacher for the training
conversations: gpt-oss-20b (OpenAI, Apache 2.0). Rules data: powered by Wahapedia. Full credits and the
reproduction details are in ATTRIBUTION.md and NOTICE.
Files
Table with columns: File, SHA-256| File | SHA-256 |
|---|
adapter_model.safetensors (62 MB) | a9c4207cef8fb24ed877adf5505934e5e6355ca04296f0d53334df73edfbd741 |
adapter_config.json | 258689c060a9b3e8448c9bdfb409086cfaa5db3dc3c2908d3e093a6348aa32a4 |