cdtio33 commited on
Commit
fb9613c
Β·
verified Β·
1 Parent(s): 6f4a19c

Add model card: DFlash drafter for Ornith 1.5 397B IQ2_XXS

Browse files
Files changed (1) hide show
  1. README.md +305 -0
README.md ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ pipeline_tag: text-generation
3
+ library_name: gguf
4
+ base_model: z-lab/Qwen3.5-397B-A17B-DFlash
5
+ base_model_relation: finetune
6
+ license: apache-2.0
7
+ inference: false
8
+ tags:
9
+ - dflash
10
+ - speculative-decoding
11
+ - speculative-decoding-draft
12
+ - draft-model
13
+ - block-diffusion
14
+ - gguf
15
+ - ik_llama.cpp
16
+ - qwen3.5
17
+ - ornith
18
+ - iq2_xxs
19
+ - moe
20
+ ---
21
+
22
+ # DFlash Drafter for Ornith 1.5 397B A17B IQ2_XXS (GGUF)
23
+
24
+ **This is a drafter, not a language model. On its own it produces nothing useful.** It is
25
+ loaded through `--model-draft` alongside Ornith-1.5-397B-A17B-IQ2_XXS and predicts that
26
+ model's next few tokens from its hidden states.
27
+
28
+ It runs on [ik_llama.cpp](https://github.com/ikawrakow/ik_llama.cpp) only. Mainline
29
+ llama.cpp and the poolside fork both reject it; see [Compatibility](#compatibility) before
30
+ downloading.
31
+
32
+ Against the previous drafter for this target, on code prompts it was never trained on, it
33
+ decodes **+6.5% faster** at `n_max=3` (95% CI +0.3% to +12.6%, four server boots,
34
+ counterbalanced). Acceptance rate rises from 0.47 to 0.52 at the same setting. Full numbers
35
+ and their weaknesses are in [Evaluation](#evaluation).
36
+
37
+ ## Files
38
+
39
+ | File | Size | Precision | SHA-256 |
40
+ |---|---|---|---|
41
+ | `Ornith15-DFLASH-codering-q4.gguf` | 704 MiB | mixed `iq4_kss` / `iq3_ks` | `5ee79024b8337367f222ceaa050739b03553d4669551db394aa21227e2f271f6` |
42
+
43
+ This is the measured artifact: every number below was produced with this exact file, and the
44
+ SHA-256 is there so you can confirm you are reading results about the bytes you downloaded.
45
+
46
+ A pure `q8_0` build of the same weights exists locally and is not published, because it was
47
+ never benchmarked and would only invite the assumption that a larger file is a better one.
48
+ Ask if you want it.
49
+
50
+ The `q4` mix was chosen to match the incumbent drafter's, so that an A/B between the two
51
+ compares weights rather than quantization:
52
+
53
+ ```
54
+ dflash_fc.weight=iq4_kss, ffn_down=iq4_kss, ffn_gate=iq3_ks, ffn_up=iq3_ks
55
+ ```
56
+
57
+ Attention projections and norms stay at `Q8_0`.
58
+
59
+ ## Usage
60
+
61
+ ```bash
62
+ llama-server \
63
+ --model Ornith-1.5-397B-IQ2_XXS-00001-of-00003.gguf \
64
+ --model-draft Ornith15-DFLASH-codering-q4.gguf \
65
+ --spec-type dflash:n_max=3,cross_ctx=512 \
66
+ --n-gpu-layers 99 --n-gpu-layers-draft 99 \
67
+ --draft-params "-wgt 17" -wgt 8 \
68
+ --flash-attn on --parallel 1
69
+ ```
70
+
71
+ All three target shards must sit in one directory; naming shard one loads the rest.
72
+
73
+ `--parallel 1` is a hard requirement, not a tuning choice. This ik_llama.cpp build **refuses
74
+ to start** with speculative decoding and more than one slot, because the accept path indexes
75
+ the batch absolutely. It does not degrade to serialized slots.
76
+
77
+ The two `-wgt` flags reserve worst-case graph space for the target and draft contexts. They
78
+ were set during measurement, so omitting them will not reproduce the numbers below.
79
+
80
+ `cross_ctx=512` is the drafter's ring depth and is fixed by the architecture. It also
81
+ constrains capture: exporting training features with a prefill batch larger than 512 evicts
82
+ ring rows before they are written, which silently corrupts the pack.
83
+
84
+ ### Choosing n_max
85
+
86
+ Swept 2026-08-28 on one boot at `N_MAX=6`, four unseen ~32k-token prompts, two repetitions,
87
+ `speculative.n_max` overridden per request so every depth shares a single model load:
88
+
89
+ | `n_max` | tok/s | vs spec-off | acceptance |
90
+ |---:|---:|---:|---:|
91
+ | 0 | 15.00 | β€” | β€” |
92
+ | 2 | 19.07 | +27.3% | 0.562 |
93
+ | 3 | 19.18 | +28.0% | 0.462 |
94
+ | **4** | **19.82** | **+32.4%** | 0.431 |
95
+ | 5 | 17.72 | +18.5% | 0.334 |
96
+
97
+ The curve rises to 4 and turns over at 5, so the optimum is bracketed and depths of 6 and
98
+ above need no test. Acceptance falls monotonically with depth, as expected.
99
+
100
+ **Depths 2, 3 and 4 are not statistically separable on this data.** Paired by prompt against
101
+ the shipped setting: `n_max=2` is βˆ’0.0% (t=0.00), `n_max=4` is +4.5% (t=0.75, 95% CI
102
+ [βˆ’14.5%, +23.4%]), `n_max=5` is βˆ’6.7% (t=βˆ’1.30).
103
+
104
+ The fall-off at 5 needs stating precisely, because which depth you compare it to changes the
105
+ answer. Against `n_max=4` it is βˆ’2.10 tok/s with t = 10.9 and every prompt agreeing, which is
106
+ about as clean as this rig produces. Against the shipped `n_max=3` it is βˆ’1.46 tok/s with
107
+ t = 1.45 and one prompt going the other way β€” not separable. So 5 is reliably worse than the
108
+ peak, and only ambiguously worse than what is shipped.
109
+
110
+ `n_max=3` ships unchanged. 4 is the point-estimate peak, but nothing here distinguishes it
111
+ from 3, and the previous drafter's optimum of 4 is a prior rather than evidence about this
112
+ one.
113
+
114
+ Worth knowing if you extend this: repetition-to-repetition spread within a cell had a median
115
+ of 0.06 tok/s across 20 cells (one outlier at 1.27), while prompt-to-prompt spread ran 2.7 to
116
+ 5.8 tok/s depending on depth, and one prompt (`fresh-03`) sat 3–5 tok/s below the others at
117
+ every depth. The uncertainty here is prompt heterogeneity, not measurement noise, so **more
118
+ prompts would sharpen this and more repetitions would not.**
119
+
120
+ Verify cost on this target is roughly 16.2 ms per extra draft position, and Ornith 1.5 is a
121
+ sparse MoE whose experts do not amortize across draft positions. Deeper drafting therefore
122
+ stops paying well before acceptance reaches zero.
123
+
124
+ ## Architecture
125
+
126
+ 1.29B parameters in 6 layers. The drafter reads the target's hidden states from 8 layers
127
+ (`1, 9, 17, 25, 33, 41, 49, 57`), concatenated to a 32768-wide input, and projects them
128
+ through `dflash_fc` into its own 4096-wide residual stream. A block of 16 mask slots seeded
129
+ with the anchor token is decoded in one forward pass, and the resulting hidden states are
130
+ pushed through **the target's frozen embedding and output head**.
131
+
132
+ | | |
133
+ |---|---|
134
+ | Layers | 6 (`sliding_attention` Γ—5, `full_attention` Γ—1) |
135
+ | Hidden / intermediate | 4096 / 12288 |
136
+ | Attention heads | 32 query, 8 KV, head_dim 128 |
137
+ | Sliding window | 4096 |
138
+ | RoPE theta | 1e7 |
139
+ | Block size | 16 (mask token 248077) |
140
+ | Vocab | 248320 |
141
+
142
+ RoPE theta must read `1e7` in the GGUF metadata. At the Qwen default of 1e6 acceptance
143
+ collapses, and the converter reads the top-level `rope_theta` key rather than the nested
144
+ `rope_parameters` block, so a config carrying only the nested value converts to a drafter
145
+ that loads cleanly and drafts badly.
146
+
147
+ **The file contains no target weights.** Its 69 tensors are the six blocks, `dflash_fc`,
148
+ `dflash_hidden_norm` and `output_norm`. There is no `token_embd` and no `output.weight`;
149
+ both come from the target at runtime. Redistributing this drafter therefore redistributes
150
+ only weights derived from the `z-lab` DFlash initialization.
151
+
152
+ ### Known trainer/runtime divergence
153
+
154
+ The training script forces all six layers to full attention, while the shipped `config.json`
155
+ correctly declares sliding attention on layers 0–4. The drafter was therefore trained able to
156
+ attend to later slots inside its block, and is served without that ability in five of six
157
+ layers.
158
+
159
+ This was measured rather than assumed. A fixture that reproduces both graphs on the same
160
+ weights and anchors puts the difference at **Β±0.8 pp** on per-position match, in both
161
+ directions, at every slot count tested. The window is 4096 while intra-block distances are at
162
+ most 15, so it never binds. The divergence is real and worth knowing about; it is not worth
163
+ correcting.
164
+
165
+ ## Training
166
+
167
+ Initialized from the Ornith-1.0 drafter (itself initialized from `z-lab/Qwen3.5-397B-A17B-DFlash`),
168
+ then fine-tuned on target features captured from the served IQ2_XXS model.
169
+
170
+ The motivating observation: three previous retrains had produced almost nothing at deployed
171
+ settings, and every one of them trained on anchors from long prose generations off short
172
+ prompts. Production's drafter ring holds **code** at absolute positions above 30,000. The
173
+ untested variable was the data distribution, not the loss or the schedule.
174
+
175
+ | | |
176
+ |---|---|
177
+ | Capture | 12 prompts Γ— ~128k chars of C/C++ from the ik_llama.cpp tree, 4096 generated tokens each |
178
+ | Pack | 457,790 rows, 30 GB, 12 segments, 0 malformed |
179
+ | Split | 9 segments train (348,025 rows) / 3 eval (109,765), split by captured request |
180
+ | Mixed with | the previous prose pack, pooling to 21,950 anchors |
181
+ | Schedule | 2 epochs, 2,742 steps at ~1.3 s/step, batch 16, lr 1e-4, stride 5 |
182
+ | Loss | position-weighted CE, gamma 1.5, weights normalized to sum 15 (w₁..wβ‚… = 7.30 / 3.75 / 1.92 / 0.99 / 0.51) |
183
+
184
+ The weighting concentrates on positions 1–3 because `tau@3 β‰ˆ 1 + p₁ + p₁pβ‚‚` β€” position 1 caps
185
+ everything downstream, and the deployed configuration only ever requests 3 or 4 slots.
186
+
187
+ Best `tau@15` on held-out code was 4.42 against 3.91 for the initialization.
188
+
189
+ ## Evaluation
190
+
191
+ Two things are worth separating: whether the drafter is better, and by how much at the
192
+ setting you will actually serve.
193
+
194
+ ### Offline, on the deployed graph
195
+
196
+ Measured with the runtime's mask and slot count rather than the trainer's, on 250 anchors,
197
+ against the previous drafter for this target. Only genuinely held-out segments are included β€”
198
+ see [Data contamination](#data-contamination).
199
+
200
+ | Pack | Metric | Previous | This | Ξ” |
201
+ |---|---|---:|---:|---:|
202
+ | Held-out code | `tau@3` (serving `n_max=2`) | 2.592 | 2.704 | +4.3% |
203
+ | Held-out code | `tau@4` (serving `n_max=3`) | 3.100 | 3.268 | +5.4% |
204
+ | Held-out code | position-1 match | 0.908 | 0.928 | +2.0 pp |
205
+ | Prose (old distribution) | `tau@3` / `tau@4` | 2.452 / 2.916 | 2.504 / 2.976 | +2.1% |
206
+
207
+ The prose row matters more than its size suggests: this drafter was trained on code, and it
208
+ did not get worse anywhere measured.
209
+
210
+ ### End-to-end throughput
211
+
212
+ Four server boots in A-B-B-A order, four unseen ~32k-token code prompts, three repetitions,
213
+ 256 tokens generated per request. Counterbalancing is necessary because boot-to-boot drift on
214
+ this hardware measured ~8%, larger than the effect. Deltas are paired within each half and
215
+ clustered by prompt, so the unit of analysis is the prompt rather than the request.
216
+
217
+ | `n_max` | Ξ” tok/s | 95% CI | t (df=3) |
218
+ |---:|---:|---|---:|
219
+ | 0 (control) | +0.6% | [+0.3%, +0.9%] | 6.70 |
220
+ | 2 | +3.9% | [βˆ’0.4%, +8.2%] | 2.88 |
221
+ | 3 | **+6.5%** | **[+0.3%, +12.6%]** | 3.34 |
222
+
223
+ Every prompt improved at `n_max=3` (+11.2 / +7.3 / +1.9 / +5.5%). Acceptance rate rose in
224
+ both boots independently β€” 0.472 / 0.474 to 0.508 / 0.531 β€” which is read from the server's
225
+ own counters rather than a wall clock.
226
+
227
+ The control row is the honest caveat. With speculation disabled the two arms should be
228
+ identical, and instead the candidate's boots ran 0.6% faster with a CI excluding zero. Some
229
+ small boot-level advantage was not fully cancelled. Subtracting it gives roughly +5.9% at
230
+ `n_max=3`.
231
+
232
+ `n_max=2` is directionally positive and not separable from zero.
233
+
234
+ ### Data contamination
235
+
236
+ The first version of this evaluation was partly contaminated, and the correction is
237
+ documented here rather than quietly fixed.
238
+
239
+ The prompt generator rotated a six-entry list of source files, so requesting 12 prompts
240
+ produced 12 prompts with only **9 unique bodies**. The four benchmark prompts were the first
241
+ four of that same generator's output, and three of them were in the training split. One of
242
+ the three offline evaluation segments duplicated a trained segment.
243
+
244
+ After rebuilding the evaluation on one distinct unseen source file per prompt, hash-guarded
245
+ against the training corpus at build time:
246
+
247
+ - Offline recomputed on clean segments only: +6.9% / +8.6% fell to **+4.3% / +5.4%**. Part of
248
+ the original gap was memorization.
249
+ - End-to-end on fresh prompts: +5.6% became **+6.5%**, with the CI excluding zero for the
250
+ first time.
251
+
252
+ The contaminated online number was not inflated. That is the outcome, not the justification β€”
253
+ the original measurement could not have told you either way.
254
+
255
+ ### What is not established
256
+
257
+ - **Anything outside code.** The code corpus is nine unique prompt bodies drawn from six
258
+ C/C++ source files in one project. The evidence for general text is the +2.1%
259
+ no-regression row above, which rests on two held-out prose segments.
260
+ - **The optimal `n_max`.** See [Choosing n_max](#choosing-n_max).
261
+ - **Other quantizations of the target.** The drafter is distilled against IQ2_XXS
262
+ specifically, using that quantization's embedding and output head. Behaviour on IQ2_KS,
263
+ Q4_K_M or bf16 is unmeasured.
264
+ - **Long-horizon serving.** All measurements are 256-token generations on a benchmark rig,
265
+ not production traffic over days.
266
+
267
+ Four prompts and four boots is a small experiment. Treat +6.5% as the center of a wide
268
+ interval.
269
+
270
+ ## Compatibility
271
+
272
+ | Runtime | Result |
273
+ |---|---|
274
+ | ik_llama.cpp (HEAD `f96eaddb` or later) | Works |
275
+ | mainline llama.cpp | `unknown model architecture: 'dflash-draft'` |
276
+ | poolside llama.cpp | `unknown model architecture: 'dflash-draft'` β€” its DFlash arch is registered as `dflash` |
277
+
278
+ The published `q4` file additionally uses ik-native quantization types that mainline ggml
279
+ does not define, so it fails at tensor load before even reaching the architecture check. The
280
+ quantization is not the obstacle, though: a pure `q8_0` build of these weights, using only
281
+ mainline types, still fails on the architecture name. The format is the wall, not the mix.
282
+
283
+ Both forks implement DFlash. Their drafter formats are not interchangeable, and running this
284
+ drafter on poolside would require retraining and reconverting to that format.
285
+
286
+ ## Reproducing
287
+
288
+ Measured on an NVIDIA GB10 (DGX Spark), 121.6 GiB unified memory shared between CPU and GPU.
289
+ The target at IQ2_XXS with a 131,072-token context and `q8_0` KV cache leaves a few GiB
290
+ free, so nothing else of size can run alongside it β€” including, on this hardware, an
291
+ evaluation script that expects to use "the CPU" as if that were a separate pool.
292
+
293
+ The full command sequence, the capture settings, the split, the training invocation and the
294
+ statistical analysis are in the source repository under `kernel-lab/dflash-retrain/`:
295
+ `CODERING-REPRO-RUNBOOK.md` for the commands in execution order,
296
+ `CODERING-AB-STATISTICAL-NOTES.md` for the A/B analysed with its weaknesses stated.
297
+
298
+ ## License
299
+
300
+ Apache 2.0, inherited from `z-lab/Qwen3.5-397B-A17B-DFlash`, of which these weights are a
301
+ fine-tune.
302
+
303
+ Training features were distilled from Ornith-1.5-397B outputs. If you intend to redistribute
304
+ or use this commercially, check Ornith 1.5's own license terms on distillation β€” that
305
+ question is not resolved by this card.