Instructions to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF # Run inference directly in the terminal: llama cli -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF # Run inference directly in the terminal: llama cli -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF # Run inference directly in the terminal: ./llama-cli -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF # Run inference directly in the terminal: ./build/bin/llama-cli -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
Use Docker
docker model run hf.co/cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
- LM Studio
- Jan
- vLLM
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
- Ollama
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with Ollama:
ollama run hf.co/cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
- Unsloth Studio
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF to start chatting
- Pi
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Hermes Agent new
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
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 cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
Run Hermes
hermes
- Atomic Chat new
- OpenClaw new
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
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 "cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
- Docker Model Runner
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with Docker Model Runner:
docker model run hf.co/cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
- Lemonade
How to use cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
Run and chat with the model
lemonade run user.qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF-{{QUANT_TAG}}List all available models
lemonade list
r3-crown GGUF: merged base+LoRA with full capability stack
Browse files|
@@ -1,7 +1,7 @@
|
|
| 1 |
---
|
| 2 |
base_model: Qwen/Qwen3.5-9B
|
| 3 |
datasets:
|
| 4 |
-
-
|
| 5 |
library_name: peft
|
| 6 |
license: apache-2.0
|
| 7 |
language:
|
|
@@ -15,220 +15,236 @@ tags:
|
|
| 15 |
- sft
|
| 16 |
- tool-use
|
| 17 |
- function-calling
|
|
|
|
|
|
|
|
|
|
| 18 |
---
|
| 19 |
|
| 20 |
-
# qwen3.5-9b-qwen3.6-
|
| 21 |
|
| 22 |
-
LoRA adapter distilled from
|
| 23 |
-
[`
|
| 24 |
-
on top of
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
Student model: **Qwen/Qwen3.5-9B** (instruct, text subnet via `Qwen3_5ForCausalLM`).
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
-
|
| 33 |
-
- **Domain coverage:** coding, mathematics, finance, medicine, economics.
|
| 34 |
-
- **Split:** deterministic (seed `42`), 85 / 7.5 / 7.5 = **425 train / 37 val / 38 test**.
|
| 35 |
-
- **Locked test set** β SHA256 recorded in `data/splits/manifest.json` and never touched by training.
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
- **Optimizer:** AdamW, `lr=0.0002`, cosine schedule, warmup `0.03`, weight decay `0.01`.
|
| 42 |
-
- **Batching:** per-device batch `1`, grad accum `8`, epochs `3`, max sequence length `8192`.
|
| 43 |
-
- **Precision:** `bf16` with gradient checkpointing.
|
| 44 |
-
- **Early stopping:** patience `3` on validation loss.
|
| 45 |
-
- **Hardware:** 0,1,2 β 3 (0,1,2).
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
##
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
|
| 59 |
|
| 60 |
-
|
|
| 61 |
-
|---
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
|
| 65 |
-
(`1337`). The **same** 100 questions are used for base
|
| 66 |
-
and tuned so the delta is directly comparable.
|
| 67 |
|
|
|
|
|
|
|
| 68 |
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
| 73 |
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
| HX1 | HARD: four-op precedence (12Β² β (5+3)) / 4 | β
| β
|
|
| 80 |
-
| H2 | nested object argument (recipient={name,address}) | β
| β
|
|
| 81 |
-
| HX2 | HARD: CβF conversion inside enum-constrained call | β
| β
|
|
| 82 |
-
| H3 | array argument (list of user_ids) | β
| β
|
|
| 83 |
-
| HX3 | HARD: Gauss formula vs brute force (sum 1..100) | β
| β
|
|
| 84 |
-
| H4 | enum normalization (user says 'cooling', enum is 'cool') | β
| β
|
|
| 85 |
-
| HX4 | HARD: nested-object + array (multi-attendee meeting) | β
| β
|
|
| 86 |
-
| H5 | numeric extraction from words ('fifty-two') | β
| β
|
|
| 87 |
-
| HX5 | HARD: world knowledge + calculator (c β v_sound) | β
| β
|
|
| 88 |
-
| H6 | negative + expression (calculator preserves parens) | β
| β
|
|
| 89 |
-
| HX6 | HARD: 5-element array from varied prose | β
| β
|
|
| 90 |
-
| H7 | chained sequential (convert + compute) | β
| β
|
|
| 91 |
-
| HX7 | HARD: schema min/max refusal or clamp (roll 5000 d20) | β | β |
|
| 92 |
-
| H8 | same tool, THREE different args (air_quality x 3) | β
| β
|
|
| 93 |
-
| HX8 | HARD: domain knowledge β 7 parallel calls (G7 capitals) | β
| β |
|
| 94 |
-
| H9 | tool non-existence (no tool fits β should NOT hallucinate) | β
| β
|
|
| 95 |
-
| HX9 | HARD: compound convert + chained compute (tax on EUR) | β
| β
|
|
| 96 |
-
| H10 | disambiguation (wiki vs news β user wants breaking news) | β
| β
|
|
| 97 |
-
| HX10 | HARD: deep-nested email with enum priority + body synthesis | β
| β
|
|
| 98 |
-
| H11 | enum strictness (user asks 'freezing' β not in enum) | β
| β
|
|
| 99 |
-
| H12 | reasoning-before-call (multi-step arithmetic β single call) | β
| β
|
|
| 100 |
|
| 101 |
-
|
| 102 |
|
| 103 |
-
|
| 104 |
-
-
|
| 105 |
-
|
|
|
|
|
|
|
| 106 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
##
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
|
| 113 |
```
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
```
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
### Verified with a small probe (3 rendered test images)
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
| `qwen3.5-9b-qwen3.6-distilled:q4km` (tuned) | 0 / 3 | [500, 500, 500] (no vision) |
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
(e.g. "HELLO" β "HELO", "42" β "44", "BANANA" β "barna"). Do not assume
|
| 131 |
-
the base model is a reliable OCR tool just because image requests return
|
| 132 |
-
HTTP 200.
|
| 133 |
|
| 134 |
-
##
|
| 135 |
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
|
|
|
| 138 |
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
-
|
| 142 |
-
Direct `ollama pull hf.co/...` will auto-derive a Modelfile from GGUF
|
| 143 |
-
metadata and may lose tool support. Using the Modelfile in this repo is
|
| 144 |
-
the reliable way to preserve `tools` + `thinking` capabilities:
|
| 145 |
|
| 146 |
```bash
|
| 147 |
-
#
|
| 148 |
-
hf download cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
ollama
|
| 153 |
-
|
|
|
|
|
|
|
| 154 |
|
| 155 |
-
|
|
|
|
| 156 |
curl -s http://localhost:11434/api/chat -d '{
|
| 157 |
-
"model": "qwen3.5-9b-qwen3.6-distilled:
|
| 158 |
-
"messages": [{"role": "user", "content": "
|
| 159 |
-
"tools": [{"type":"function","function":{"name":"get_weather","description":"
|
| 160 |
"stream": false
|
| 161 |
}' | jq .message.tool_calls
|
| 162 |
```
|
| 163 |
|
| 164 |
-
|
| 165 |
-
Modelfile's `FROM` line (or use one of the comment-suggested filenames):
|
| 166 |
-
|
| 167 |
-
- `qwen3.5-9b-qwen3.6-reasoning-distilled.q4km.gguf` (~5.6 GB) β recommended
|
| 168 |
-
- `qwen3.5-9b-qwen3.6-reasoning-distilled.q80.gguf` (~9.5 GB) β higher fidelity
|
| 169 |
-
- `qwen3.5-9b-qwen3.6-reasoning-distilled.f16.gguf` (~17.9 GB) β full precision
|
| 170 |
-
|
| 171 |
-
## How this adapter was trained
|
| 172 |
|
| 173 |
-
|
| 174 |
-
# Reproduce (end-to-end)
|
| 175 |
-
python app.py prepare
|
| 176 |
-
python app.py baseline
|
| 177 |
-
python app.py train
|
| 178 |
-
python app.py eval
|
| 179 |
-
python app.py export
|
| 180 |
-
```
|
| 181 |
-
|
| 182 |
-
All hyperparameters and the verification manifest live in the accompanying
|
| 183 |
-
`app.py` + `requirements.txt`. The held-out test set and GSM8K sample IDs are
|
| 184 |
-
locked in `data/splits/manifest.json` / `data/gsm8k_sample.jsonl`.
|
| 185 |
-
|
| 186 |
-
## Tool calling
|
| 187 |
-
|
| 188 |
-
This model was evaluated for tool calling through the **Ollama chat API**.
|
| 189 |
-
Tool-use preservation was checked on a locked held-out benchmark covering
|
| 190 |
-
single-tool, parallel-tool, no-tool, and post-tool-response turns.
|
| 191 |
-
|
| 192 |
-
The serving contract expected by downstream runtimes is:
|
| 193 |
-
|
| 194 |
-
- assistant emits **structured** `tool_calls` (not free-text `<tool_call>` blocks)
|
| 195 |
-
- tool results are returned as `tool`-role messages
|
| 196 |
-
- the model continues using those results correctly
|
| 197 |
-
|
| 198 |
-
### Side-by-side metrics (base vs tuned, same locked benchmark)
|
| 199 |
-
|
| 200 |
-
Base: `qwen3.5:9b`
|
| 201 |
-
Tuned: `qwen3.5-9b-qwen3.6-distilled:q4km`
|
| 202 |
-
|
| 203 |
-
| Metric | Base | Tuned | Ξ |
|
| 204 |
-
|---|---:|---:|---:|
|
| 205 |
-
| tool_calls_present_rate | 1.000 | 1.000 | +0.000 |
|
| 206 |
-
| tool_selection_accuracy | 1.000 | 1.000 | +0.000 |
|
| 207 |
-
| argument_schema_valid | 1.000 | 1.000 | +0.000 |
|
| 208 |
-
| argument_accuracy | 1.000 | 1.000 | +0.000 |
|
| 209 |
-
| parallel_tool_call_accuracy | 0.750 | 0.750 | +0.000 |
|
| 210 |
-
| no_call_when_unneeded | 0.800 | 1.000 | +0.200 |
|
| 211 |
-
| post_tool_response_accuracy | 1.000 | 1.000 | +0.000 |
|
| 212 |
-
|
| 213 |
-
Regression gate: **PASSED** (no metric regressed beyond epsilon=0.05).
|
| 214 |
-
Full per-row results, including the exact `tool_calls` returned by Ollama, are
|
| 215 |
-
in `final_report.json β tool_calling`.
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
## Limitations
|
| 219 |
|
| 220 |
-
-
|
| 221 |
-
|
| 222 |
-
-
|
| 223 |
-
|
| 224 |
-
-
|
| 225 |
-
|
| 226 |
-
- The LoRA targets only linear layers (not embeddings); the adapter preserves
|
| 227 |
-
the base model's tokenizer and vocabulary.
|
| 228 |
-
|
| 229 |
-
## Files
|
| 230 |
-
|
| 231 |
-
- `adapter_config.json`, `adapter_model.safetensors` β LoRA adapter (PEFT).
|
| 232 |
-
- `*.gguf` β merged + quantized weights for llama.cpp / Ollama (Q4_K_M, Q8_0).
|
| 233 |
-
- `final_report.json` β machine-readable eval report.
|
| 234 |
-
- `data/splits/manifest.json` β split manifest with SHA256 of each split.
|
|
|
|
| 1 |
---
|
| 2 |
base_model: Qwen/Qwen3.5-9B
|
| 3 |
datasets:
|
| 4 |
+
- Crownelius/Opus-4.6-Reasoning-3300x
|
| 5 |
library_name: peft
|
| 6 |
license: apache-2.0
|
| 7 |
language:
|
|
|
|
| 15 |
- sft
|
| 16 |
- tool-use
|
| 17 |
- function-calling
|
| 18 |
+
- vision
|
| 19 |
+
- multimodal
|
| 20 |
+
- thinking
|
| 21 |
---
|
| 22 |
|
| 23 |
+
# qwen3.5-9b-qwen3.6-distilled (r3-crown)
|
| 24 |
|
| 25 |
+
LoRA adapter distilled from reasoning traces in
|
| 26 |
+
[`Crownelius/Opus-4.6-Reasoning-3300x`](https://huggingface.co/datasets/Crownelius/Opus-4.6-Reasoning-3300x)
|
| 27 |
+
on top of [`Qwen/Qwen3.5-9B`](https://huggingface.co/Qwen/Qwen3.5-9B).
|
| 28 |
+
Packaged for Ollama via `llama-export-lora` so the base model's vision
|
| 29 |
+
tower, tool-calling handlers, and native Q4_K_M quantization are preserved
|
| 30 |
+
byte-for-byte β only the text-reasoning tensors our LoRA touched are
|
| 31 |
+
updated.
|
| 32 |
|
| 33 |
+
## What ships
|
|
|
|
| 34 |
|
| 35 |
+
| Artifact | Purpose |
|
| 36 |
+
|---|---|
|
| 37 |
+
| `adapter_model.safetensors` | PEFT LoRA weights (attention + MLP + linear_attn targets, 496 tensors total) |
|
| 38 |
+
| `adapter_config.json` | PEFT config |
|
| 39 |
+
| `final_report.json` | Machine-readable eval report (hard tool matrix, vision probe, OCR tool demo, training metrics) |
|
| 40 |
+
| `filtered_adapter/` | LoRA with `linear_attn.*` tensors removed β this is what converts cleanly to GGUF |
|
| 41 |
+
| GGUF repo | `llama-export-lora` merged into base `qwen3.5:9b` Q4_K_M GGUF + Modelfile |
|
| 42 |
|
| 43 |
+
## Build pipeline (the path that actually works)
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
The single-file combined-GGUF path via `convert_hf_to_gguf.py` is blocked
|
| 46 |
+
for Qwen3.5 multimodal today: it emits a separate `clip.*` mmproj GGUF
|
| 47 |
+
which is incompatible with Ollama's native `qwen35.vision.*` loader
|
| 48 |
+
format. We solved this by not rebuilding the base GGUF at all.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
```bash
|
| 51 |
+
# 1. Train LoRA (target_modules="all-linear" for completeness)
|
| 52 |
+
python app.py train # produces final_adapter/
|
| 53 |
+
|
| 54 |
+
# 2. Drop the Gated DeltaNet LoRA tensors β llama.cpp's lora-to-gguf
|
| 55 |
+
# converter can't reshape-reorder the v-heads inside a low-rank
|
| 56 |
+
# A@B decomposition. We keep attention + MLP targets, which are
|
| 57 |
+
# 256/496 of the tensors in the adapter.
|
| 58 |
+
.venv/bin/python -c "
|
| 59 |
+
from safetensors.torch import load_file, save_file
|
| 60 |
+
import json
|
| 61 |
+
w = load_file('final_adapter/adapter_model.safetensors')
|
| 62 |
+
save_file({k:v for k,v in w.items() if 'linear_attn' not in k},
|
| 63 |
+
'final_adapter_filtered/adapter_model.safetensors')
|
| 64 |
+
cfg = json.load(open('final_adapter/adapter_config.json'))
|
| 65 |
+
cfg['target_modules'] = ['q_proj','k_proj','v_proj','o_proj','gate_proj','up_proj','down_proj']
|
| 66 |
+
json.dump(cfg, open('final_adapter_filtered/adapter_config.json','w'), indent=2)
|
| 67 |
+
"
|
| 68 |
+
|
| 69 |
+
# 3. Convert filtered LoRA to GGUF
|
| 70 |
+
python vendor/llama.cpp/convert_lora_to_gguf.py final_adapter_filtered/ \
|
| 71 |
+
--outtype f16 --outfile lora.f16.gguf \
|
| 72 |
+
--base outputs/merged_vision/...
|
| 73 |
+
|
| 74 |
+
# 4. Merge LoRA into the base qwen3.5:9b Q4_K_M GGUF byte-for-byte
|
| 75 |
+
vendor/llama.cpp/build/bin/llama-export-lora \
|
| 76 |
+
--model /srv/ollama/models/blobs/sha256-dec52a... \
|
| 77 |
+
--lora lora.f16.gguf \
|
| 78 |
+
--output merged.q4km.gguf
|
| 79 |
+
|
| 80 |
+
# 5. Register with Ollama
|
| 81 |
+
cat > Modelfile <<'EOM'
|
| 82 |
+
FROM ./merged.q4km.gguf
|
| 83 |
+
RENDERER qwen3.5
|
| 84 |
+
PARSER qwen3.5
|
| 85 |
+
PARAMETER temperature 0.6
|
| 86 |
+
PARAMETER top_p 0.95
|
| 87 |
+
PARAMETER stop "<|im_end|>"
|
| 88 |
+
EOM
|
| 89 |
+
ollama create qwen3.5-9b-qwen3.6-distilled:crown -f Modelfile
|
| 90 |
+
```
|
| 91 |
|
| 92 |
+
`llama-export-lora` updates the **128 text tensors** the filtered LoRA
|
| 93 |
+
covers (q/k/v/o + gate/up/down in the standard attention/MLP layers) and
|
| 94 |
+
leaves the other **755 tensors** (all 441 vision + 15 MTP + the linear_attn
|
| 95 |
+
DeltaNet tensors + embeddings + norms) **exactly as base**. Vision is
|
| 96 |
+
therefore not "preserved via state-dict surgery"; it is **literally the
|
| 97 |
+
base model's vision**, bit-for-bit.
|
| 98 |
|
| 99 |
+
## Capabilities β verified via Ollama
|
| 100 |
|
| 101 |
+
```
|
| 102 |
+
$ ollama show qwen3.5-9b-qwen3.6-distilled:crown
|
| 103 |
+
architecture qwen35
|
| 104 |
+
parameters 9.7B
|
| 105 |
+
Capabilities
|
| 106 |
+
completion
|
| 107 |
+
vision
|
| 108 |
+
tools
|
| 109 |
+
thinking
|
| 110 |
+
```
|
| 111 |
|
| 112 |
+
Smoke-test results (deterministic, `temperature=0`):
|
| 113 |
|
| 114 |
+
| Path | Result |
|
| 115 |
+
|---|---|
|
| 116 |
+
| Completion | β
`7 * 23 - 45` β step-by-step β `116` (correct) |
|
| 117 |
+
| Thinking | β
706-char `<think>` block before the answer |
|
| 118 |
+
| Tool calling | β
`get_weather({"city": "Paris"})` β structured `tool_calls` |
|
| 119 |
+
| Vision | β
Base-level: 1/3 on 3-image rendered-text probe, identical failure pattern to base |
|
| 120 |
|
| 121 |
+
## Hard tool-calling stress test β 22 scenarios
|
|
|
|
|
|
|
| 122 |
|
| 123 |
+
Deterministic (`temperature=0, top_p=1, top_k=1, seed=42`) via the OpenAI-
|
| 124 |
+
compatible gateway at `http://localhost:11435/v1/chat/completions`.
|
| 125 |
|
| 126 |
+
| | Base `qwen3.5:9b` | Crown `:crown` |
|
| 127 |
+
|---|---:|---:|
|
| 128 |
+
| **Score** | **21 / 22** | **21 / 22** |
|
| 129 |
+
| HX8 (G7 capitals, 7 parallel calls) | PASS | **PASS** β fixed |
|
| 130 |
+
| HX7 (roll_dice count=5000 vs schema max=1000) | FAIL | FAIL β shared weakness |
|
| 131 |
+
| H1 many-tool distraction latency | 8.4 s | **0.8 s** (10Γ faster) |
|
| 132 |
|
| 133 |
+
**HX8 is the critical one.** Our first adapter (r1, trained on the
|
| 134 |
+
original 500-row dataset) failed HX8 by substituting Brussels for
|
| 135 |
+
Washington D.C. That was a factual regression from distillation on a
|
| 136 |
+
small dataset. The r3-crown pipeline fixes it two ways at once:
|
| 137 |
|
| 138 |
+
1. **Retrain on a larger, better-aligned dataset** (Crownelius has 2160
|
| 139 |
+
rows vs 500) β best val loss dropped from 0.7030 β **0.4439**.
|
| 140 |
+
2. **Merge into base rather than replacing weights** β 755/883 tensors
|
| 141 |
+
are bit-for-bit from base, so base's factual knowledge can't be
|
| 142 |
+
regressed by LoRA overfitting on the unmodified tensors.
|
| 143 |
|
| 144 |
+
**HX7 is a shared base-model weakness** (both models call
|
| 145 |
+
`roll_dice(count=5000)` ignoring the JSON Schema `maximum: 1000`). Not
|
| 146 |
+
introduced by distillation; documented honestly.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
+
## Image handling β base-level vision preserved
|
| 149 |
|
| 150 |
+
| Probe | Base | Crown |
|
| 151 |
+
|---|---|---|
|
| 152 |
+
| `HELLO` 72pt on black | "HILO" (wrong) | "HILO" (wrong) |
|
| 153 |
+
| `42` 72pt on red | **"42"** β | **"42"** β |
|
| 154 |
+
| `BANANA` 72pt on yellow | "BANA" (wrong) | "BANA" (wrong) |
|
| 155 |
|
| 156 |
+
Character-identical outputs confirm the vision tensors are not just
|
| 157 |
+
"preserved" but literally the base model's weights β because we merged
|
| 158 |
+
the LoRA into base, not the other way around. If you need OCR with higher
|
| 159 |
+
fidelity, use the OCR-via-tool pattern below.
|
| 160 |
|
| 161 |
+
## OCR-via-tool (recommended for small/dense text)
|
| 162 |
|
| 163 |
+
No 9B VLM reliably reads small dense text pixel-perfectly. The distilled
|
| 164 |
+
model's tool-calling strength makes it an excellent OCR orchestrator:
|
| 165 |
|
| 166 |
```
|
| 167 |
+
user: "What is the total on this receipt? /path/receipt.png"
|
| 168 |
+
β
|
| 169 |
+
crown model emits: extract_text_from_image({"image_path": "/path/receipt.png"})
|
| 170 |
+
β
|
| 171 |
+
tool runs Tesseract with 8 preprocessing variants Γ 3 PSM modes = 24 passes,
|
| 172 |
+
returns best-scored text (88-91% confidence typical)
|
| 173 |
+
β
|
| 174 |
+
crown model: "Based on the receipt, the total is EUR 34.76. Card: VISA ..."
|
| 175 |
```
|
| 176 |
|
| 177 |
+
On our 4-case hard benchmark (two images β a dense 19-line security log
|
| 178 |
+
and a coffee receipt β with four questions across them), crown scored
|
| 179 |
+
**4/4**, including:
|
|
|
|
|
|
|
| 180 |
|
| 181 |
+
- Quoting the exact error line verbatim from a 1126-char log extraction
|
| 182 |
+
- Distinguishing training loss from validation loss in the quoted text
|
| 183 |
+
- Extracting per-item costs from a monospace receipt
|
|
|
|
| 184 |
|
| 185 |
+
See `final_report.json β ocr_via_tool` for the full transcript with all
|
| 186 |
+
tool calls.
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
+
## Training recipe
|
| 189 |
|
| 190 |
+
| | Round 1 (superseded) | Round 3 (crown, shipped) |
|
| 191 |
+
|---|---|---|
|
| 192 |
+
| Dataset | `khazarai/qwen3.6-plus-high-reasoning-500x` | `Crownelius/Opus-4.6-Reasoning-3300x` |
|
| 193 |
+
| Rows | 500 | 2160 |
|
| 194 |
+
| Splits | 425 / 37 / 38 | 1836 / 162 / 162 |
|
| 195 |
+
| Epochs | 3 | early-stopped at 1.17 |
|
| 196 |
+
| Steps completed | 54 | 90 |
|
| 197 |
+
| LR | 2e-4 cosine | 2e-4 cosine |
|
| 198 |
+
| LoRA | r=32 Ξ±=64 all-linear | r=32 Ξ±=64 all-linear |
|
| 199 |
+
| Max seq length | 8192 | 5120 (capped for long tail) |
|
| 200 |
+
| **Best val loss** | 0.7030 | **0.4439** |
|
| 201 |
+
| Hardware | 3Γ A100 80GB PCIe DDP | 3Γ A100 80GB PCIe DDP |
|
| 202 |
+
| Training time | 49 min | 28 min |
|
| 203 |
|
| 204 |
+
## Limitations
|
| 205 |
|
| 206 |
+
- **HX7**: both crown and base ignore JSON Schema `maximum: 1000` on numeric
|
| 207 |
+
arguments. Don't rely on schema range enforcement as a safety mechanism.
|
| 208 |
+
- **`linear_attn` LoRA weights are not packaged in the GGUF** because
|
| 209 |
+
llama.cpp's LoRA converter can't reorder v-heads inside a low-rank A@B
|
| 210 |
+
decomposition. The HF PEFT adapter (`adapter_model.safetensors`) does
|
| 211 |
+
contain them, so if you use this via `transformers + peft` you get the
|
| 212 |
+
full LoRA; the Ollama GGUF gets ~52% of LoRA tensors.
|
| 213 |
+
- **Vision OCR fidelity** on small dense text is base-level (matches
|
| 214 |
+
`qwen3.5:9b`). Use `extract_text_from_image` tool for reliable reads.
|
| 215 |
+
- **Non-determinism** at default `temperature=0.6`. Published numbers are
|
| 216 |
+
at `temperature=0`.
|
| 217 |
|
| 218 |
+
## Running it
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
```bash
|
| 221 |
+
# Recommended: pull the merged GGUF + Modelfile from the GGUF repo
|
| 222 |
+
hf download cudabenchmarktest/qwen3.5-9b-qwen3.6-reasoning-distilled-GGUF \
|
| 223 |
+
Modelfile merged.q4km.gguf \
|
| 224 |
+
--local-dir ./qwen-crown
|
| 225 |
+
cd qwen-crown
|
| 226 |
+
ollama create qwen3.5-9b-qwen3.6-distilled:crown -f Modelfile
|
| 227 |
+
ollama show qwen3.5-9b-qwen3.6-distilled:crown # should list: completion, vision, tools, thinking
|
| 228 |
+
ollama run qwen3.5-9b-qwen3.6-distilled:crown
|
| 229 |
+
```
|
| 230 |
|
| 231 |
+
```bash
|
| 232 |
+
# Or hit /api/chat with tools + images
|
| 233 |
curl -s http://localhost:11434/api/chat -d '{
|
| 234 |
+
"model": "qwen3.5-9b-qwen3.6-distilled:crown",
|
| 235 |
+
"messages": [{"role": "user", "content": "Weather in Paris?"}],
|
| 236 |
+
"tools": [{"type":"function","function":{"name":"get_weather","description":"...","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}],
|
| 237 |
"stream": false
|
| 238 |
}' | jq .message.tool_calls
|
| 239 |
```
|
| 240 |
|
| 241 |
+
## Audit trail
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
+
All probe scripts + raw result JSONs are in this repo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
+
- `scripts/hard_tool_tests.py` β 22 deterministic tool-calling scenarios
|
| 246 |
+
- `scripts/ocr_tool_demo.py` β end-to-end OCR-as-tool agent loop
|
| 247 |
+
- `scripts/image_probe.py` β 3-image vision probe
|
| 248 |
+
- `tool_bench/` β raw per-row results for every probe
|
| 249 |
+
- `final_report.json` β everything merged, schema-stable
|
| 250 |
+
- `crown_manifest.json` β dataset split SHAs (seed 42)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|