Downloads · 30 days
18
27% of all-time downloads
davidnichols-ops/adaptive-operator-v4-mlx-4bit
adaptive-operator-v4-mlx-4bit is a text generation model from davidnichols-ops. Use it when you need the model to write or continue text. It is set up for mlx. The card lists the license as apache-2.0.
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…
Downloads · 30 days
18
27% of all-time downloads
All-time downloads
66
Public
Parameters
9B
5.1 GB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors5 GB · 100%
How the weights are stored.
U329B · 100%
From the Hugging Face model README
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.
This is a research artifact, not a production model. The fine-tune successfully taught control token routing and tool call formatting, but it degraded the base model's coding ability by 47 percentage points. The lessons learned from this project are more valuable than the model itself. Read on for the full honest breakdown.
For engineers who want the raw facts, warts and all.
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 the evaluation methodology.
Should you use this? Probably not for coding. Use the base Qwen3.5-9B for code generation. This model is useful if you need a standalone model that emits structured tool calls without a Python routing layer. If you already have a routing layer (like MAOS, LangChain, etc.), the base model with a good system prompt is better.
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 (beta=0.1, 1 epoch) | 630 | 77 min | 3.9e-05 | 100% reward accuracy, margin ~43 |
The DPO loss went to near-zero. 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. The model learned "don't narrate, emit structured calls" but did not learn deeper preference signals.
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"). Keyword sensitivity from the training data distribution.
Coding SFT LoRA was attempted and abandoned. A separate coding LoRA trained on MBPP + synthetic data (1,256 examples) hurt performance (57.3% vs 64% base). Overfitting on simple patterns. The base model 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: bf16 GGUF (17GB), Q8_0 (8.9GB), Q6_K (6.9GB), Q5_K_M (6.0GB), Q5_K_S (5.9GB), Q4_K_M (5.2GB), Q4_K_S (5.0GB), Q4_0 (4.9GB), Q3_K_M (4.3GB), MLX 4-bit (4.7GB). fp8 is NOT supported by llama.cpp.
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.
This project succeeded at what it set out to do (teach control tokens + tool call format) and failed at what we hoped it would do (maintain coding ability while adding agentic behavior). Both outcomes are instructive.
[RECOVER] instead of [THINK] when the prompt contains "failing", "broken", "error". The training data distribution biased the routing.These lessons cost $3.50 and 2 days to learn. They're worth more than the model:
Fine-tuning is a trade-off, not an upgrade. If your base model is already good at something, fine-tuning on different data will degrade that ability. The LoRA adapters don't add capability -- they redirect capacity. Know what you're trading away.
DPO is only as good as your pairs. If your chosen/rejected pairs differ in format (not reasoning), the model learns format (not reasoning). Near-zero DPO loss is a red flag, not a success. It means the task was trivial.
Execution-based benchmarking is the correct methodology. Greedy pass@1 underestimates model ability. Multi-sample with execution selection (50 samples, T=0.2+0.8) gives the true ceiling. 28/164 problems had no passing sample at T=0.2; 15 of those were solved at T=0.8. High-temperature diversity is critical.
Test the base model first. We spent time trying to "recover" coding ability with a coding SFT LoRA, not realizing the base model was already strong (64% HumanEval+). If we'd benchmarked the base model first, we'd have known the ceiling and not wasted time on the coding LoRA.
Template-based synthesis is deterministic but shallow. The 5-reviewer orchestrator + template synthesis produced correctly formatted responses at 1,623 responses/sec for $0. But the improved responses were structurally correct and semantically simple. The base model provides the reasoning; the templates provide the format.
$3.50 is enough for a 9B LoRA fine-tune. Together AI inference ($2-3 for 5K responses) + vast.ai RTX PRO 5000 Blackwell ($0.30 for 3 hours). You don't need expensive infrastructure for experimentation.
| Field | Value |
|---|---|
| Base model | Qwen/Qwen3.5-9B |
| Parameters | 9B |
| 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 |
The model emits one of five control tokens at the start of every response:
| 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>
{"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)
```python
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)
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 create adaptive-operator-v4 -f Modelfile
ollama run adaptive-operator-v4 "Show me the last 10 git commits."
All quantizations are in the GGUF repo, generated from the bf16 source with llama.cpp:
| Format | Size | Bits/weight | Best for |
|---|---|---|---|
| bf16 | 17 GB | 16.0 | Maximum fidelity, CPU inference |
| Q8_0 | 8.9 GB | 8.5 | Near-lossless, good speed/quality balance |
| Q6_K | 6.9 GB | 6.6 | High quality, moderate size |
| Q5_K_M | 6.0 GB | 5.8 | Good quality/size trade-off |
| Q5_K_S | 5.9 GB | 5.6 | Slightly smaller than Q5_K_M |
| Q4_K_M | 5.2 GB | 5.0 | Recommended default for 4-bit |
| Q4_K_S | 5.0 GB | 4.8 | Smaller 4-bit variant |
| Q4_0 | 4.9 GB | 4.7 | Fastest 4-bit, legacy format |
| Q3_K_M | 4.3 GB | 4.1 | Smallest viable, quality degradation |
fp8 is not supported by llama.cpp. The highest-fidelity 16-bit option is bf16.
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 + 9 GGUF variants)
| 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 |
| Metric | Value |
|---|---|
| Final train loss | 3.9e-05 (near-zero -- see lessons above) |
| Reward accuracy | 100% |
| Reward margin | ~43 (chosen +8.5, rejected -35.5) |
| Throughput | 1.09 samples/sec, 0.14 steps/sec |
These benchmarks were run on the base model to establish the coding ceiling and validate the evaluation methodology. The fine-tuned model's coding ability is lower (see below).
| 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% |
| 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) |
| 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% |
| 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% |
| Level | Count | Percentage |
|---|---|---|
| 1 (simple) | 2,374 | 47.5% |
| 2 (moderate) | 2,035 | 40.8% |
| 3 (complex) | 591 | 11.8% |
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.
| 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 |
TRAINING_REPORT.md | Full training report with 14 issues encountered |
PIPELINE_ARCHITECTURE.md | Pipeline architecture reference |
benchmark/ | Benchmark scripts (multi-sample, execution selection, etc.) |
training/ | Training scripts (SFT, DPO) |
| Repository | Description |
|---|---|
| adaptive-operator-v4-mlx-4bit | MLX 4-bit quantized for Apple Silicon (4.7GB) |
| adaptive-operator-v4-gguf | 9 GGUF quantizations (bf16, Q8_0, Q6_K, Q5_K_M, Q5_K_S, Q4_K_M, Q4_K_S, Q4_0, Q3_K_M) |
| adaptive-operator-v4-dataset | SFT + DPO training data |
| Resource | Usage | Cost |
|---|---|---|
| Together AI inference (5K responses) | 5.4M tokens | ~$2-3 |
| Vast.ai RTX PRO 5000 Blackwell | ~3 hours | ~$0.30 |
| Total | ~$3.50 |
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.
enable_thinking in the chat template[RECOVER] instead of [THINK] when the prompt contains failure-related keywordsBASE_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)"
Apache 2.0 (derived from Qwen3.5-9B)
@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}
}