Adaptive Operator v4

A Qwen3.5-9B model fine-tuned with a custom 5-token control system for adaptive compute allocation in agentic workflows. The model routes every task to one of five compute levels at inference time, emitting a control token at the start of each response before executing structured tool calls.


Nerd TL;DR

For engineers who want the raw facts, warts and all, before reading the rest.

What this is: A LoRA SFT + DPO fine-tune of Qwen3.5-9B that teaches the model to emit one of five control tokens ([FAST], [THINK], [VERIFY], [RECOVER], [ESCALATE]) and make structured XML+JSON tool calls. Trained on 4,992 SFT examples + 5,000 DPO pairs synthesized from Qwen v3.1 teacher responses, corrected by a pure-Python 5-reviewer orchestrator.

What this is NOT: A coding model. The fine-tune degraded raw coding ability. HumanEval+ pass@1 dropped from 64.0% (base) to 17.1% (fine-tuned, raw completion) to 13.4% (fine-tuned, chat template). The 95.1% / 81.1% HumanEval scores in the benchmark section are the base model with execution-based multi-sample selection (50 samples, T=0.2+0.8), not this fine-tuned model. Those benchmarks establish the ceiling of the base model's coding ability and validate our evaluation methodology.

Training at a glance:

Stage Steps Time Final Loss Key Metric
SFT (LoRA r=64, bf16) 1,887 105 min 0.060 99.2% token accuracy
DPO (β=0.1, 1 epoch) 630 77 min 3.9e-05 100% reward accuracy, margin ~43

The DPO loss went to near-zero. This means the chosen/rejected pairs were trivially separable — the teacher narrated tools (950 chars avg) while the synthesized responses emitted structured calls (185 chars avg). The DPO signal is dominated by format differences, not reasoning quality. This is a known characteristic, not a bug.

The control token system works but has routing edge cases. The model sometimes routes debugging tasks to [RECOVER] instead of [THINK] when the prompt contains failure-related keywords ("failing", "broken", "error"). This is a keyword-sensitivity issue from the training data distribution.

Coding SFT LoRA was attempted and abandoned. A separate coding LoRA trained on MBPP + synthetic data (1,256 examples) actually hurt performance (57.3% vs 64% base). Overfitting on simple patterns. The base model's coding ability was already strong; fine-tuning on narrow data degraded it.

Infrastructure: Trained on a rented RTX PRO 5000 Blackwell (48GB VRAM) via vast.ai. Total cost ~$3.50 ($2-3 Together AI inference + $0.30 GPU rental). bf16, no quantization, SDPA attention (flash-attn wouldn't build), no packing (cross-contamination risk without flash attention).

Deployment formats: fp16 safetensors (17GB), MLX 4-bit (4.7GB, Apple Silicon), GGUF Q4_K_M (5.3GB) + Q8_0 (8.9GB). MLX conversion required manual config patching (Qwen3.5 text-only architecture wasn't in mlx-lm's auto-conversion list at time of writing).

Thinking mode gotcha: Qwen3.5 has built-in thinking mode. You MUST pass enable_thinking=False in the chat template, or the model generates a thinking block before the control token. This is why the chat-template benchmark (13.4%) was worse than raw completion (17.1%) — thinking tokens contaminated the code output.

Bottom line: Use this model for agentic tool-calling workflows where adaptive compute routing matters. Do NOT use it for raw code generation — use the base Qwen3.5-9B for that. The value is in the control token routing + structured tool calls, not in coding ability.


Model Details

Field Value
Base model Qwen/Qwen3.5-9B
Parameters 9B (4.7GB MLX 4-bit / 5.3GB GGUF Q4_K_M / 17GB fp16)
Training method LoRA SFT + DPO
LoRA rank 64 (alpha 128, dropout 0.05)
Training precision bf16 (no quantization during training)
Training data 4,992 SFT examples + 5,000 DPO pairs
Training hardware RTX PRO 5000 Blackwell (48GB VRAM)
Context length 2048 tokens
Attention implementation SDPA (PyTorch native)
Packing Disabled (no flash attention available)
Total training time ~3 hours (SFT 105 min + DPO 77 min)
Total cost ~$3.50

Control Token System

The model emits one of five control tokens at the start of every response, routing the task to the appropriate compute level:

Token Usage Distribution in training data
[FAST] Direct action, simple tasks 47.4% (2,366)
[THINK] Multi-step reasoning before acting 41.6% (2,078)
[VERIFY] Act then confirm result (destructive/irreversible) 2.9% (145)
[RECOVER] Reassess after failure, try different strategy 7.1% (354)
[ESCALATE] Surface for human decision (high-risk/security) 1.0% (49)

Tool Call Format

The model emits structured tool calls in XML+JSON format:

<tool_call>
{"name": "shell", "arguments": {"command": "uv run pytest"}}
</tool_call>

Supported tools: shell, file_read, file_write, file_edit, file_list, grep, find_file, git, web_search, web_fetch, todo_write, ask_user.

Usage

MLX (Apple Silicon)

import mlx_lm

model, tokenizer = mlx_lm.load("davidnichols-ops/adaptive-operator-v4-mlx-4bit")

SYSTEM = """You are an adaptive engineering operator. You inspect systems, reason through problems, take actions, verify results, recover from failures, and decide when deeper thinking is necessary. You work from evidence, not assumptions.

## Compute Modes (control tokens)
Begin every response with exactly one control token to signal how much reasoning the task needs:
  [FAST]     -- direct, low-latency action. Use for simple tool calls, file reads, status checks, and routine edits.
  [THINK]    -- extended reasoning before acting. Use for design, debugging, refactoring, multi-step planning, and ambiguous problems.
  [VERIFY]   -- act, then verify the result before declaring success. Use when a change must be confirmed (tests pass, file written, deploy succeeded).
  [RECOVER]  -- a previous attempt failed; reassess and try a different strategy. Use after errors, broken builds, or unexpected output.
  [ESCALATE] -- the task exceeds safe autonomous scope; stop and surface the situation for a human. Use for irreversible or high-risk actions.

## Tool Use
When a task requires a tool, emit a structured tool call in this exact format:
  <tool_call>
  {"name": "tool_name", "arguments": {"param": "value"}}
  </tool_call>

After each tool call you will receive the result. Use it to decide your next action. When the task is complete, give a concise final answer with no tool calls."""

messages = [
    {"role": "system", "content": SYSTEM},
    {"role": "user", "content": "Show me the last 10 git commits."},
]
chat = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
response = mlx_lm.generate(model, tokenizer, prompt=chat, max_tokens=256)
print(response)

HuggingFace Transformers (fp16)

from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained("davidnichols-ops/adaptive-operator-v4", torch_dtype="auto")
tokenizer = AutoTokenizer.from_pretrained("davidnichols-ops/adaptive-operator-v4")

# CRITICAL: disable thinking mode or control tokens won't appear at response start
chat = tokenizer.apply_chat_template(
    messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
)

Ollama (GGUF)

# Download the Modelfile and GGUF from the GGUF repo
ollama create adaptive-operator-v4 -f Modelfile
ollama run adaptive-operator-v4 "Show me the last 10 git commits."

Training Pipeline

Prompt Generator (5K unique prompts across 10 categories)
    -> Qwen v3.1 Teacher Inference (Together AI, 32 parallel workers, ~$2-3)
    -> 5-Reviewer Orchestrator (pure Python heuristics, 1,623 responses/sec)
       - Code Quality & Correctness
       - Tool Selection & Usage
       - Control Token Routing
       - Error Handling & Edge Cases
       - Response Format & Clarity
    -> Data Quality Filter (dedup + n-gram decontamination)
    -> SFT Export + DPO Pair Generation
    -> LoRA SFT Training (bf16, 3 epochs, lr=2e-4, r=64)
    -> LoRA DPO Training (bf16, 1 epoch, lr=5e-5, beta=0.1)
    -> Merge LoRA -> fp16 model
    -> Quantization (MLX 4-bit + GGUF Q4_K_M + Q8_0)

Training Metrics

SFT (3 epochs, 1,887 steps, 105 min)

Metric Value
Final train loss 0.060
Final token accuracy 99.2%
Convergence 2.09 -> 0.08 in 110 steps (6% of training)
Throughput 2.39 samples/sec, 0.30 steps/sec
Total tokens 5.9M

The loss converged rapidly — the format/routing behavior is easy to learn. The remaining 94% of training refined the token accuracy from 97.7% to 99.2%.

DPO (1 epoch, 630 steps, 77 min)

Metric Value
Final train loss 3.9e-05 (near-zero)
Reward accuracy 100%
Reward margin ~43 (chosen +8.5, rejected -35.5)
Throughput 1.09 samples/sec, 0.14 steps/sec

Note on near-zero DPO loss: The chosen/rejected pairs were trivially separable. The teacher responses narrated tool use in prose (950 chars avg), while the synthesized improved responses emitted structured XML+JSON tool calls (185 chars avg). The DPO signal is dominated by format differences, not reasoning quality. This is a known characteristic of cross-model DPO pairs with template-based synthesis — see Limitations.

Dataset Statistics

SFT Dataset (4,992 examples)

Category Count Percentage
tool_use_file_ops 889 17.8%
coding_write 828 16.6%
coding_debug 615 12.3%
tool_use_shell 543 10.9%
tool_use_git 435 8.7%
coding_test 404 8.1%
coding_refactor 376 7.5%
planning 374 7.5%
tool_use_search 298 6.0%
recovery 238 4.8%

Difficulty Distribution

Level Count Percentage
1 (simple) 2,374 47.5%
2 (moderate) 2,035 40.8%
3 (complex) 591 11.8%

DPO Dataset (5,000 pairs)

Cross-model preference pairs: original Qwen v3.1 teacher response (rejected) vs synthesized improved response (chosen). The rejected responses are 5x longer (950 chars vs 185 chars) — the teacher was verbose with narration. The improved responses are concise and structured.

Benchmark Results

HumanEval / HumanEval+ (EvalPlus)

We evaluated the base Qwen3.5-9B model using the DeepSeek-style benchmark methodology: raw completion (no chat template), greedy + multi-sample decoding, stop strings for function boundaries, and EvalPlus sanitization. These benchmarks establish the base model's coding ceiling and validate our evaluation methodology.

Method HumanEval pass@1 HumanEval+ pass@1
Greedy (temp=0) 70.7% 64.0%
20 samples (T=0.2), random pick 69.9% 62.1%
20 samples, execution-selected (base tests) 82.9% 70.7%
20 samples, execution-selected (base+plus tests) 82.9% 72.6%
50 samples (T=0.2+0.8), execution-selected 95.1% 81.1%
pass@10 (20 samples, T=0.2) 81.5% 74.6%

Methodology: The execution-based selection technique generates multiple samples per problem at different temperatures, runs the base test cases on each, and selects the first passing solution. This is the standard technique used by DeepSeek-Coder, CodeLlama, and other code generation models for reporting pass@1 with execution-based verification.

Key findings:

  • The base Qwen3.5-9B has strong coding ability (64% greedy HumanEval+)
  • High-temperature sampling (T=0.8) provides crucial diversity for solving harder problems that fail at T=0.2
  • 28 of 164 problems had no passing sample at T=0.2; 15 of those 28 were solved with T=0.8 samples
  • Raw completion mode (no chat template) is the correct evaluation format — the chat template injects thinking tokens that contaminate code output

Fine-tuned Model Benchmarks

The fine-tuned model was evaluated on its primary task: agentic tool-calling with adaptive compute routing.

Metric Value
SFT final loss 0.060
DPO reward accuracy 100%
Control token routing accuracy 86% FAST mode on simple tasks
Tool call format compliance >99%

Coding ability impact: The fine-tune degraded raw coding ability. This is expected — the model was trained on tool-calling format and routing, not code generation. The base model's coding ability is preserved in the base weights; the LoRA adapters add the agentic behavior on top.

Model HumanEval+ pass@1 (raw completion)
Base Qwen3.5-9B 64.0%
Fine-tuned (chat template) 13.4%
Fine-tuned (raw completion) 17.1%
Coding SFT LoRA merged 57.3% (abandoned — overfit)

Coding SFT Experiment (Abandoned)

A separate coding LoRA was trained on 1,256 MBPP + synthetic coding examples to try to recover coding ability post-fine-tuning. The result (57.3% vs 64% base) was worse than the base model — the LoRA overfit on simple patterns and degraded generalization. This approach was abandoned. The base model's coding ability is already strong; fine-tuning on narrow data degraded it.

Repository Contents

Path Description
model.safetensors Merged fp16 model (17GB)
sft_adapter/ SFT LoRA adapter (465MB)
dpo_adapter/ DPO LoRA adapter (908MB)
config.json Model configuration
tokenizer.json Tokenizer
chat_template.jinja Chat template

Related Repositories

Repository Description
adaptive-operator-v4-mlx-4bit MLX 4-bit quantized for Apple Silicon (4.7GB)
adaptive-operator-v4-gguf GGUF Q4_K_M (5.3GB) + Q8_0 (8.9GB) for llama.cpp / Ollama
adaptive-operator-v4-dataset SFT + DPO training data

Training Cost

Resource Usage Cost
Together AI inference (5K responses) 5.4M tokens ~$2-3
Google Drive storage ~25MB Free
Colab T4 (attempted, abandoned) ~10 min Free
Vast.ai RTX 3090 (abandoned — CUDA mismatch) ~20 min ~$0.04
Vast.ai RTX PRO 4000 (abandoned — slow) ~15 min ~$0.02
Vast.ai RTX PRO 5000 Blackwell ~3 hours ~$0.30
Total ~$3.50

Important: Thinking Mode

Qwen3.5 has a built-in thinking mode that prepends internal reasoning before the response. To get control tokens at the start of the output, you must pass enable_thinking=False when applying the chat template:

chat = tokenizer.apply_chat_template(
    messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
)

Without this, the model generates a thinking block first, and the control token appears later in the response. This is why the chat-template benchmark (13.4%) was worse than raw completion (17.1%) — thinking tokens contaminated the code output.

Limitations

Known Issues

  • Coding ability degradation: The fine-tune degraded raw coding ability (64% -> 17% HumanEval+). The model is designed for tool-calling workflows, not raw code generation. Use the base Qwen3.5-9B for coding tasks.
  • Context length: 2048 tokens (may truncate long conversations)
  • Tool call accuracy: The model emits correctly formatted tool calls but may select suboptimal tools for ambiguous tasks
  • Reasoning depth: SFT responses are structurally correct but semantically simple — the base model's coding ability provides semantic content, but the synthesis templates produce minimal stubs
  • Repetition: 4-bit quantization with greedy decoding can cause repetition loops; use temperature sampling (0.7-0.8) for better results
  • System prompt required: The control token system only activates when the system prompt is provided
  • Thinking mode: Must disable enable_thinking in the chat template to get control tokens at response start
  • THINK routing: The model sometimes routes debugging tasks to [RECOVER] instead of [THINK] when the prompt contains failure-related keywords ("failing", "broken", "error")
  • DPO overfitting: The DPO loss went to near-zero, indicating the chosen/rejected pairs were trivially separable. The DPO signal is dominated by format differences (narration vs structured calls), not reasoning quality. This means the model learned "don't narrate, emit structured calls" but may not have learned deeper preference signals.
  • Training data bias: 47.5% of training data is difficulty level 1 (simple). The model may underperform on complex, multi-step tasks.
  • MLX conversion: Required manual config patching (Qwen3.5 text-only architecture wasn't in mlx-lm's auto-conversion list at time of writing)

What Worked

  • Control token routing (86% accuracy on simple tasks)
  • Tool call format compliance (>99%)
  • SFT convergence (99.2% token accuracy in 105 min)
  • DPO preference learning (100% reward accuracy)
  • Multi-sample execution-based benchmarking (95.1% base / 81.1% plus on base model)
  • Cost-efficient training ($3.50 total)

What Didn't Work

  • Coding SFT LoRA (57.3% vs 64% base — overfit, abandoned)
  • Chat template evaluation (thinking tokens contaminate output)
  • Flash attention (wouldn't build on Python 3.12 + CUDA 13.0)
  • MLX auto-conversion (required manual config patching)
  • Ollama push (requires manual signin, not automatable via API)

Training Configuration

BASE_MODEL = "Qwen/Qwen3.5-9B"
LORA_R = 64
LORA_ALPHA = 128
LORA_DROPOUT = 0.05

# SFT
SFT_EPOCHS = 3
SFT_LEARNING_RATE = 2e-4
SFT_BATCH_SIZE = 4
SFT_GRADIENT_ACCUMULATION = 2  # effective batch = 8
MAX_SEQ_LENGTH = 2048

# DPO
DPO_EPOCHS = 1
DPO_LEARNING_RATE = 5e-5
DPO_BETA = 0.1

# Infrastructure
PRECISION = "bf16"
ATTENTION = "sdpa"
PACKING = False
GRADIENT_CHECKPOINTING = True
GPU = "RTX PRO 5000 Blackwell (48GB)"

Infrastructure Journey

The training infrastructure migrated through three platforms before settling on the final configuration:

  1. Together AI hosted fine-tuning — abandoned due to $4 minimum credit requirement and limited hyperparameter control
  2. Google Colab T4 — abandoned due to 16GB VRAM (requires 4-bit QLoRA), fp16-only (no bf16), session instability, and one-GPU-per-account limit
  3. Vast.ai RTX 3090 — abandoned due to CUDA version mismatch (driver 12.8, PyTorch built for 13.0) and 24GB VRAM requiring QLoRA
  4. Vast.ai RTX PRO 4000 Blackwell — abandoned due to slow throughput (~40s/step, 4.3 hour ETA)
  5. Vast.ai RTX PRO 5000 Blackwell (48GB) — final choice. Full bf16 training, batch size 4, LoRA r=64, ~2.6s/step

License

Apache 2.0 (derived from Qwen3.5-9B)

Citation

@misc{adaptive-operator-v4,
  title={Adaptive Operator v4: Qwen3.5-9B with Control Token Routing},
  author={David Nichols},
  year={2025},
  url={https://huggingface.co/davidnichols-ops/adaptive-operator-v4}
}
Downloads last month
28
Safetensors
Model size
9B params
Tensor type
F16
·
MLX
Hardware compatibility
Log In to add your hardware

Quantized

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for davidnichols-ops/adaptive-operator-v4

Finetuned
Qwen/Qwen3.5-9B
Finetuned
(569)
this model