Instructions to use davidnichols-ops/adaptive-operator-v4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use davidnichols-ops/adaptive-operator-v4 with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("davidnichols-ops/adaptive-operator-v4") prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( messages, add_generation_prompt=True ) text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Pi
How to use davidnichols-ops/adaptive-operator-v4 with Pi:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "davidnichols-ops/adaptive-operator-v4"
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "mlx-lm": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "davidnichols-ops/adaptive-operator-v4" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Hermes Agent new
How to use davidnichols-ops/adaptive-operator-v4 with Hermes Agent:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "davidnichols-ops/adaptive-operator-v4"
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default davidnichols-ops/adaptive-operator-v4
Run Hermes
hermes
- OpenClaw new
How to use davidnichols-ops/adaptive-operator-v4 with OpenClaw:
Start the MLX server
# Install MLX LM: uv tool install mlx-lm # Start a local OpenAI-compatible server: mlx_lm.server --model "davidnichols-ops/adaptive-operator-v4"
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "davidnichols-ops/adaptive-operator-v4" \ --custom-provider-id mlx-lm \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
- MLX LM
How to use davidnichols-ops/adaptive-operator-v4 with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Interactive chat REPL mlx_lm.chat --model "davidnichols-ops/adaptive-operator-v4"
Run an OpenAI-compatible server
# Install MLX LM uv tool install mlx-lm # Start the server mlx_lm.server --model "davidnichols-ops/adaptive-operator-v4" # Calling the OpenAI-compatible server with curl curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "davidnichols-ops/adaptive-operator-v4", "messages": [ {"role": "user", "content": "Hello"} ] }'
- Adaptive Operator v4
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_thinkingin 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:
- Together AI hosted fine-tuning — abandoned due to $4 minimum credit requirement and limited hyperparameter control
- Google Colab T4 — abandoned due to 16GB VRAM (requires 4-bit QLoRA), fp16-only (no bf16), session instability, and one-GPU-per-account limit
- Vast.ai RTX 3090 — abandoned due to CUDA version mismatch (driver 12.8, PyTorch built for 13.0) and 24GB VRAM requiring QLoRA
- Vast.ai RTX PRO 4000 Blackwell — abandoned due to slow throughput (~40s/step, 4.3 hour ETA)
- 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
Quantized