ULLR โ€” Search-First, Execute-Verified Tool-Calling Model

ULLR (the Norse god of archery โ€” precision, focus) is a 2.6B tool-calling model fine-tuned from LiquidAI/LFM2.5-2.6B for the nolock coding assistant. It is trained to follow a strict policy: search the web before answering, execute code before claiming it works, and always work with tool feedback. When served inside an agent harness (such as nolock), this policy makes the system a precise, verified coding agent โ€” ULLR is the model that powers it.

Results

BFCL v4 AST accuracy โ€” full model ladder

All models evaluated with the identical harness (official bfcl_eval AST checker, greedy decoding, native tool-call parsing). The Qwen models went through the same pipeline as ULLR: SFT on the same 296-example dataset (Qwen-native format + no-tool chat mix), merge, GGUF, and โ€” for the 0.8B variants โ€” KTO rounds with harvested negatives.

Model Params Training simple multiple parallel parallel_multi Overall
LFM2-1.2B-Tool + SFT + KTO 1.2B SFT+KTO 0.492 0.375 0.425 0.113 0.351
Qwen3.5-0.8B (base) 0.8B โ€” 0.667 0.500 0.438 0.263 0.489
Qwen3.5-0.8B + SFT 0.8B SFT 0.667 0.500 0.438 0.263 0.489
Qwen3.5-0.8B + SFT + KTO 0.8B SFT+KTO 0.667 0.500 0.438 0.263 0.489
Qwen3.5-2B (base) 2B โ€” โ€” โ€” โ€” โ€” โ€”
Qwen3.5-2B + SFT (QLoRA) 2B SFT 0.783 0.575 0.688 0.338 0.617
Qwen3.5-2B + SFT + KTO 2B SFT+KTO 0.783 0.575 0.688 0.338 0.617
LFM2.5-2.6B (base) 2.6B โ€” 0.883 0.812 0.738 0.637 0.781
ULLR (LFM2.5-2.6B + KTO) 2.6B KTO 0.875 0.925 0.825 0.650 0.825
ULLR Q4_K_M (quantized) 2.6B KTO + Q4 0.908 0.838 0.775 0.537 0.781

Key takeaways:

  • ULLR (KTO) beats every Qwen variant at every size โ€” +33.6pp over the fine-tuned Qwen3.5-2B, +46.6pp over the fine-tuned Qwen3.5-0.8B
  • The KTO gain (+4.4pp) concentrated in multi-call categories: multiple +11.3pp, parallel +8.7pp over base
  • Even the 1.7GB Q4 quantization (0.781) matches the unquantized base โ€” the KTO gains survive quantization
  • Notably, KTO only improved the strongest base: on the Qwen models, identical KTO pipelines were neutral-to-harmful (see training details)

Multi-turn & in-domain

Benchmark Base LFM2.5-2.6B ULLR (KTO)
ฯ„-bench call validity (16 episodes) 100% 100%
ฯ„-bench env errors 0 0
In-domain validation (10 prompts) 5/10 6/10

In-domain failures are concentrated in multi-step completion (long verify loops exceeding the 8-step budget), not tool selection โ€” the model picks the right tools in the right order but doesn't always converge to a final answer.

How it was built

1. The policy (defined by the product, not the benchmark)

ULLR is trained to almost never answer from parametric knowledge:

User asks anything factual?     โ†’ web_search โ†’ (web_fetch) โ†’ cited summary
Answer contains math or code?  โ†’ rust_repl executes it FIRST โ†’ verified result
Search/fetch fails?            โ†’ refine query / different URL โ†’ retry
Local project question?        โ†’ grep / read_file / list_directory / edit
User gives a task?             โ†’ engage with tools immediately, never deflect

This is deliberately not the classic "don't over-call tools" alignment โ€” the model is supposed to search even when it probably knows the answer, and to run code before reporting results, accepting slower answers in exchange for verified ones.

2. GLM-5.3-Flash as the trajectory teacher

The policy training data was authored by GLM-5.3-Flash (Z.ai) acting as the teacher, rather than generated by a local model. This mattered for three behaviors that had zero coverage in the existing data:

  • Search-and-summarize loops: web_search โ†’ web_fetch โ†’ cited summary trajectories with real queries and real documentation URLs (Rust Book, MDN, React blog, Tokio docs), each answer citing its source and noting why verification mattered
  • Code-execution: math/code questions where rust_repl runs before answering โ€” prime sums, Fibonacci, JSON/serde parsing, even a deliberate compile-error check (E0308) โ€” with answers reporting the executed result
  • Tool-feedback recovery: search โ†’ fetch official docs โ†’ synthesize setup steps across sources

The teacher also authored the engagement pairs: chat-deflection responses (the model's measured failure mode) as rejected completions, paired with acknowledge-and-call chosen completions reconstructed from ฯ„-bench ground-truth action lists.

3. Nemotron-Nano-9B as the local cross-validation judge

Every row was cross-validated by a locally-running Nemotron-Nano-9B acting as a policy judge before training. The judge was first validated itself on 4 crafted cases (4/4 discrimination, including a borderline math-without-REPL case).

Judging all 115 rows flagged 44 as mislabeled. Human categorization of the flags showed:

Flag reason Count Verdict
Judge expected full task/summary in a single completion 23 Judge wrong โ€” KTO completions are deliberately first-turn calls
Judge misapplied policy (ฯ„-bench tools โ‰  "local tools") 10 Judge wrong
list_directory used on a file path 7 Real error โ€” fixed (read_file substituted)
Ambiguous STOP-acknowledgment negatives 2 Dropped

The cross-validation caught 7 genuine tool-selection errors that would have taught the model to list directories instead of reading files โ€” while the 35 over-strict flags were overridden with documented reasoning. Automated judging + human categorization was the combination that worked.

4. nolock's KTO/DPO framework

Training used nolock's in-house preference framework โ€” the same .rlhf collection schema (thumbs-up/thumbs-down with user_correction metadata) that powers the editor's feedback loop. Human-labeled rows (including KTO-schema rows rescued from a mislabeled dpo/good/ directory, and LFM2-dialect tool calls converted to native format) were merged with on-policy negatives harvested from benchmark failures, then cross-validated. The framework's schema (TRL-compatible {prompt, completion, label}) fed directly into TRL's KTOTrainer.

Why KTO worked here (and failed on smaller models)

Three earlier KTO attempts (Qwen3.5-0.8B ร—2, Qwen3.5-2B) failed to improve their bases. This one succeeded (+4.4pp BFCL, validation 5โ†’6/10) because:

  • Strong base: LFM2.5-2.6B's agentic RL gave it capability to calibrate, not capability to create
  • On-policy negatives: the 11 ฯ„-bench non-engagement failures were the model's own outputs, not synthetic approximations
  • Low KL drift (6.5): the training nudged engagement calibration without damaging the underlying agentic competence โ€” margins stayed positive (+0.59 โ†’ +0.88) throughout

The policy (what this model is trained to do)

User asks anything factual?     โ†’ web_search โ†’ (web_fetch) โ†’ cited summary
Answer contains math or code?  โ†’ rust_repl executes it FIRST โ†’ verified result
Search/fetch fails?            โ†’ refine query / different URL โ†’ retry
Local project question?        โ†’ grep / read_file / list_directory / edit
User gives a task?             โ†’ engage with tools immediately, never deflect

Files

File Size Use
ullr-f16.gguf 5.4 GB Full precision โ€” local use with GPU headroom
ullr-q4_k_m.gguf 1.7 GB Edge/Railway deployment (BFCL 0.781, โˆ’4.4pp vs f16)

Usage

llama.cpp (recommended serving path)

llama-server -m ullr-q4_k_m.gguf --jinja -c 8192 --port 8081
# OpenAI-compatible endpoint with native tool_calls:
curl http://localhost:8081/v1/chat/completions -d '{
  "model": "ullr",
  "messages": [{"role": "user", "content": "What is 17 * 23?"}],
  "tools": [...],
  "tool_choice": "auto"
}'

Ollama

ollama create ullr -f Modelfile   # FROM ./ullr-q4_k_m.gguf
ollama run ullr "Find the official docs for the Tauri window-state plugin"

Note: serve via llama.cpp --jinja or Ollama โ‰ฅ 0.33. Earlier Ollama chat templates mishandle the reasoning_content field and can swallow tool calls after the thinking block.

Recommended sampling

temperature 0.1, top_k 50, top_p 0.9, repeat_penalty 1.1 (Liquid AI's official agentic settings).

Training details

Base LiquidAI/LFM2.5-2.6B (Lfm2ForCausalLM, hybrid 22 conv + 8 GQA)
Method KTO (TRL KTOTrainer), LoRA r=32 / ฮฑ=64, dropout 0.05
Targets q/k/v/o_proj + gate/up/down_proj
Data 106 rows (92 chosen / 14 rejected, 6.6:1) โ€” authored policy trajectories + on-policy harvested negatives + human preference rows
Hyperparams lr 1e-5, ฮฒ 0.1, undesirable_weight 1.5, 3 epochs, batch 2ร—4 accum, bf16, paged_adamw_8bit
Result KL 6.5 (anchored), rewards/margins +0.59 โ†’ +0.88, BFCL 0.781 โ†’ 0.825

Limitations

  • Multi-step completion: long verify loops (edit โ†’ build โ†’ test โ†’ fix) can exceed practical step budgets; the model selects correctly but doesn't always converge within ~8 rounds.
  • Reasoning overhead: always thinks before answering (reasoning_content); latency per turn is higher than non-reasoning models of the same size.
  • License: LFM Open License v1.0 โ€” royalty-free for non-commercial/research and businesses <$10M annual revenue.
  • Web dependency: per policy, factual answers require web access; offline use degrades to local-tools-only behavior.

Acknowledgments

Citation

@software{ullr2026,
  author = {Gabriel Amazonas},
  title  = {ULLR: A Search-First, Execute-Verified Tool-Calling Model fine-tuned from LFM2.5-2.6B},
  year   = {2026},
  url    = {https://huggingface.co/impacte/ullr-2.6B-GGUF}
}
Downloads last month
-
GGUF
Model size
3B params
Architecture
lfm2
Hardware compatibility
Log In to add your hardware

4-bit

16-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for impacte/ullr-2.6B-GGUF

Quantized
(91)
this model