Skip to main content

cortiq_engine/
dflash.rs

1//! DFlash 2: a drafter that proposes a block of tokens in one pass.
2//!
3//! The drafter is a few Qwen3-shaped layers beside its target. It never reads
4//! the target's tokens before the anchor: what it knows of the text comes from
5//! several of the target's layer outputs, joined a row at a time and projected
6//! into every draft layer's keys and values.
7//!
8//! ```text
9//! context   t = norm(FC · [target layer outputs of a row])
10//!           K_l = rot(norm(Wk_l · t)), V_l = Wv_l · t          kept per position
11//!           (a layer may be without rotation, and each has a window of its own)
12//! block     rows = [anchor, MASK, …, MASK] at positions a, a + 1, …
13//!           x = norm(h); x, d = conv_in(x)                     grouped causal conv over the rows
14//!           q, k, v = W · x; attention over K_l, V_l before a and the block's own rows (not causal)
15//!           h += conv_out(O · y, d); the MLP is wrapped the same way
16//! tokens    logits = head · norm(h) (the target's head), its top candidates re-scored
17//!           in order by the selector: unary + (P[prev] ⊙ S·h) · C[candidate]
18//! ```
19//!
20//! Row j ≥ 1 of the block drafts the token at position a + j. The sidecar
21//! (`<stem>.dflash.cmf`, namespace `dflash.`) is written by `cortiq convert
22//! --dflash-sidecar`; the embedding and the head are the target's.
23
24use crate::pool::Pool;
25use crate::qtensor::{QTensor, sgemm_public};
26use cortiq_core::quant::f16_to_f32;
27use cortiq_core::format::SelectionDescriptor;
28use cortiq_core::{CmfModel, TensorDtype};
29use serde::{Deserialize, Serialize};
30use std::path::{Path, PathBuf};
31use std::sync::Arc;
32
33pub const SIDECAR_ARCH: &str = "dflash2_sidecar";
34/// Where a sidecar's header keeps its router (`RouteFit`), under `provenance`.
35pub const ROUTE_KEY: &str = "dflash_route";
36pub const NAMESPACE: &str = "dflash.";
37/// Positions past a layer's window a drafter still keeps the context for:
38/// the rounds the next block is anchored further on by.
39pub const BLOCK_SLACK: usize = 256;
40/// Context rows projected per pass: bounds the joined-taps buffer.
41const CONTEXT_CHUNK: usize = 64;
42/// Context rows that may wait unsettled (`queue_context`): 100 KB a row on
43/// the 27B's five tapped layers.
44const PENDING_ROWS: usize = 512;
45
46/// `<stem>.dflash.cmf` beside the target `<stem>.cmf`.
47pub fn sidecar_path(main: &Path) -> PathBuf {
48    let name = main.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
49    if name.ends_with(".dflash.cmf") {
50        return main.to_path_buf();
51    }
52    let stem = name.strip_suffix(".cmf").unwrap_or(&name);
53    main.with_file_name(format!("{stem}.dflash.cmf"))
54}
55
56/// What a DFlash 2 checkpoint's config fixes.
57#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
58pub struct Geometry {
59    pub hidden: usize,
60    pub intermediate: usize,
61    pub layers: usize,
62    pub heads: usize,
63    pub kv_heads: usize,
64    pub head_dim: usize,
65    pub vocab: usize,
66    /// Rows of a block: the anchor and `block − 1` drafts.
67    pub block: usize,
68    pub mask_token: u32,
69    pub conv_group: usize,
70    pub conv_kernel: usize,
71    pub selector_rank: usize,
72    pub selector_top: usize,
73    /// The target's layers whose outputs a context row joins, zero-based:
74    /// the output of layer `id`, before any norm.
75    pub taps: Vec<usize>,
76    pub target_layers: usize,
77    /// Positions a block row sees back; 0 is all of them.
78    pub window: usize,
79    /// The same per layer where the layers differ (a full-attention layer
80    /// among sliding ones: 0). Empty: `window` for every layer.
81    #[serde(default, skip_serializing_if = "Vec::is_empty")]
82    pub windows: Vec<usize>,
83    /// Layers whose queries and keys carry no rotary position.
84    #[serde(default, skip_serializing_if = "Vec::is_empty")]
85    pub no_rope: Vec<usize>,
86    pub rope_theta: f64,
87    pub eps: f64,
88}
89
90impl Geometry {
91    /// The geometry a sidecar declares.
92    pub fn of(model: &CmfModel) -> Result<Self, String> {
93        if model.arch().arch_name != SIDECAR_ARCH {
94            return Err(format!("{} is not a DFlash sidecar", model.arch().arch_name));
95        }
96        let v = model.header.provenance.as_ref().and_then(|p| p.get("dflash")).ok_or("the sidecar's header has no dflash record")?;
97        let g: Self = serde_json::from_value(v.clone()).map_err(|e| format!("the sidecar's dflash record: {e}"))?;
98        if g.layers == 0
99            || g.block < 2
100            || g.taps.is_empty()
101            || g.kv_heads == 0
102            || g.heads % g.kv_heads != 0
103            || g.head_dim % 2 != 0
104            || g.conv_group == 0
105            || g.hidden % g.conv_group != 0
106            || !(g.windows.is_empty() || g.windows.len() == g.layers)
107            || g.no_rope.iter().any(|&l| l >= g.layers)
108        {
109            return Err(format!("unsupported DFlash geometry {g:?}"));
110        }
111        Ok(g)
112    }
113
114    /// Width of a context row as the target hands it over.
115    pub fn tap_width(&self) -> usize {
116        self.taps.len() * self.hidden
117    }
118
119    /// Positions a block row of `layer` sees back; 0 is all of them.
120    pub fn window_of(&self, layer: usize) -> usize {
121        self.windows.get(layer).copied().unwrap_or(self.window)
122    }
123
124    /// Whether `layer` rotates its queries and keys by position.
125    pub fn rotary(&self, layer: usize) -> bool {
126        !self.no_rope.contains(&layer)
127    }
128}
129
130struct Conv {
131    /// `[stage][offset][hidden]`: stage 0 goes before the sub-layer, 1 after.
132    base: Vec<f32>,
133    proj: Small,
134}
135
136/// A small matrix (a kernel projection, the selector's) unpacked to f32: the
137/// host has it applied to a block's rows before a submission to the device
138/// would come back.
139struct Small {
140    w: Vec<f32>,
141    rows: usize,
142    cols: usize,
143}
144
145impl Small {
146    fn of(t: QTensor) -> Self {
147        let (rows, cols) = (t.rows(), t.cols());
148        let mut w = vec![0f32; rows * cols];
149        for (r, row) in w.chunks_exact_mut(cols).enumerate() {
150            t.row_f32(r, row);
151        }
152        Self { w, rows, cols }
153    }
154
155    /// `[b][rows]` from `x`, `[b][cols]`. The rows go across the pool's
156    /// threads: `sgemm_public` runs one thread on x86, and a block's ten
157    /// kernel projections took 260 ms of a 340 ms block that way.
158    fn apply(&self, x: &[f32], b: usize, pool: Option<&Pool>) -> Vec<f32> {
159        let (rows, cols) = (self.rows, self.cols);
160        let mut out = vec![0f32; b * rows];
161        let Some(pool) = pool.filter(|_| b * rows * cols >= 1 << 20 && rows >= 64) else {
162            sgemm_public(b, rows, cols, 1.0, x, cols, &self.w, cols, true, &mut out, rows);
163            return out;
164        };
165        struct SendPtr(*mut f32);
166        unsafe impl Send for SendPtr {}
167        unsafe impl Sync for SendPtr {}
168        impl SendPtr {
169            fn at(&self, i: usize) -> *mut f32 {
170                // SAFETY: callers index inside the buffer the pointer came from.
171                unsafe { self.0.add(i) }
172            }
173        }
174        let dst = SendPtr(out.as_mut_ptr());
175        pool.run_rows(rows, &|r0, r1| {
176            let n = r1 - r0;
177            let mut part = vec![0f32; b * n];
178            sgemm_public(b, n, cols, 1.0, x, cols, &self.w[r0 * cols..r1 * cols], cols, true, &mut part, n);
179            for bi in 0..b {
180                // SAFETY: the chunks' row ranges are disjoint, so no two
181                // workers write the same element; `out` outlives the run.
182                unsafe {
183                    std::ptr::copy_nonoverlapping(part[bi * n..].as_ptr(), dst.at(bi * rows + r0), n);
184                }
185            }
186        });
187        out
188    }
189}
190
191struct Layer {
192    in_norm: Vec<f32>,
193    post_norm: Vec<f32>,
194    q: QTensor,
195    k: QTensor,
196    v: QTensor,
197    o: QTensor,
198    q_norm: Vec<f32>,
199    k_norm: Vec<f32>,
200    gate: QTensor,
201    up: QTensor,
202    down: QTensor,
203    attn_conv: Conv,
204    mlp_conv: Conv,
205}
206
207/// A layer's keys and values of the context positions, per KV head, a row of
208/// `head_dim` per position.
209#[derive(Default)]
210struct Ctx {
211    k: Vec<Vec<f32>>,
212    v: Vec<Vec<f32>>,
213    /// Rows that fell out of the layer's window and were let go: the layer
214    /// holds positions `first + dropped .. first + len`.
215    dropped: usize,
216}
217
218pub struct Drafter {
219    pub g: Geometry,
220    /// The sidecar's router, when it carries one.
221    pub route: Option<Route>,
222    model: Arc<CmfModel>,
223    fc: QTensor,
224    hidden_norm: Vec<f32>,
225    layers: Vec<Layer>,
226    norm: Vec<f32>,
227    sel: Small,
228    /// Directory indices of the predecessor and successor codebooks.
229    books: [usize; 2],
230    inv_freq: Vec<f32>,
231    ctx: Vec<Ctx>,
232    /// The context covers positions `first .. first + len`; a layer holds
233    /// the part of them its window still reaches (`Ctx::dropped`).
234    first: usize,
235    len: usize,
236    /// Rows that wait to become context (`queue_context`): their layer
237    /// outputs, from position `first + len` on.
238    pending: Vec<f32>,
239    /// Matrices go to the device when it takes them.
240    device: bool,
241    /// Matrices that read the same rows go to the device in one submission
242    /// (`CMF_DFLASH_FUSED=0`: a submission each).
243    fused: bool,
244    /// The block as one submission on wgpu (`gpu_wgpu::dflash_dev`), built
245    /// at the first block; `Err` once it declined.
246    #[cfg(feature = "gpu")]
247    dev: std::sync::Mutex<Option<Result<crate::gpu_wgpu::dflash_dev::Dev, ()>>>,
248    /// The same on CUDA (`gpu_cuda::DflashDev`), its context made on the card.
249    #[cfg(feature = "cuda")]
250    cdev: std::sync::Mutex<Option<Result<crate::gpu_cuda::DflashDev, ()>>>,
251}
252
253fn vector(model: &CmfModel, name: &str, len: usize) -> Result<Vec<f32>, String> {
254    let name = format!("{NAMESPACE}{name}");
255    let e = model.tensor(&name).ok_or_else(|| format!("the sidecar has no {name}"))?;
256    if e.dtype != TensorDtype::F32 || e.shape.iter().product::<usize>() != len {
257        return Err(format!("{name}: {:?} {:?}, expected {len} f32", e.dtype, e.shape));
258    }
259    Ok(model.entry_bytes(e).chunks_exact(4).map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect())
260}
261
262/// Where a block's time goes (`CMF_DFLASH_TIME=1`): microseconds in the kernel
263/// projections, the attention projections, the attention itself, its output
264/// projection, the MLP, and the blocks counted.
265static STAGE_US: [std::sync::atomic::AtomicU64; 6] = [const { std::sync::atomic::AtomicU64::new(0) }; 6];
266
267fn stage_timed() -> bool {
268    static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
269    *ON.get_or_init(|| std::env::var("CMF_DFLASH_TIME").is_ok())
270}
271
272/// Run `f`, its time added to stage `i` when the stages are timed.
273fn stage<T>(i: usize, f: impl FnOnce() -> T) -> T {
274    if !stage_timed() {
275        return f();
276    }
277    let t = std::time::Instant::now();
278    let r = f();
279    STAGE_US[i].fetch_add(t.elapsed().as_micros() as u64, std::sync::atomic::Ordering::Relaxed);
280    r
281}
282
283/// A line of the stages' average milliseconds a block, when they are timed.
284pub fn stage_report() -> Option<String> {
285    let v: Vec<f64> = STAGE_US.iter().map(|a| a.load(std::sync::atomic::Ordering::Relaxed) as f64).collect();
286    (stage_timed() && v[5] > 0.0).then(|| {
287        let ms = |i: usize| v[i] / 1e3 / v[5];
288        format!("a block: kernel projections {:.1} ms, q k v {:.1}, attention {:.1}, o {:.1}, MLP {:.1}", ms(0), ms(1), ms(2), ms(3), ms(4))
289    })
290}
291
292/// Which drafter a round is for, as the sidecar keeps it: a subspace of the
293/// target's rows at the tip for the rounds this block won and one for the
294/// rounds the target's own MTP chain won, each fitted on its own rows alone
295/// (`router::fit_descriptor`, the engine's recon-argmin routing). `cortiq
296/// dflash-route-fit` writes it.
297#[derive(Clone, Serialize, Deserialize)]
298pub struct RouteFit {
299    pub block: SelectionDescriptor,
300    pub chain: SelectionDescriptor,
301    /// Rounds each was fitted on.
302    pub block_rows: usize,
303    pub chain_rows: usize,
304}
305
306struct Subspace {
307    mean: Vec<f32>,
308    basis: Vec<f32>,
309    rank: usize,
310}
311
312impl Subspace {
313    fn of(d: &SelectionDescriptor, hidden: usize) -> Result<Self, String> {
314        let (mean, basis) = (crate::router::decode_f16(&d.mean), crate::router::decode_f16(&d.basis));
315        match (mean, basis) {
316            (Some(mean), Some(basis)) if mean.len() == hidden && basis.len() == d.rank * hidden => Ok(Self { mean, basis, rank: d.rank }),
317            _ => Err(format!("a router subspace is not {hidden} wide at rank {}", d.rank)),
318        }
319    }
320}
321
322/// A sidecar's router, ready to read rows.
323pub struct Route {
324    block: Subspace,
325    chain: Subspace,
326}
327
328impl Route {
329    pub fn of(fit: &RouteFit, hidden: usize) -> Result<Self, String> {
330        Ok(Self { block: Subspace::of(&fit.block, hidden)?, chain: Subspace::of(&fit.chain, hidden)? })
331    }
332
333    /// How much better the block's subspace holds the target's row `row`
334    /// than the chain's does: above zero the round is the block's.
335    pub fn margin(&self, row: &[f32]) -> f32 {
336        let n = row.iter().map(|v| v * v).sum::<f32>().sqrt().max(1e-12);
337        let unit: Vec<f32> = row.iter().map(|v| v / n).collect();
338        let err = |s: &Subspace| crate::router::recon_error(&unit, &s.mean, &s.basis, s.rank);
339        err(&self.chain) - err(&self.block)
340    }
341}
342
343fn matrix(model: &Arc<CmfModel>, name: &str, rows: usize, cols: usize) -> Result<QTensor, String> {
344    let name = format!("{NAMESPACE}{name}");
345    let t = QTensor::from_model(model, &name)?;
346    if t.rows() != rows || t.cols() != cols {
347        return Err(format!("{name}: [{}, {}], expected [{rows}, {cols}]", t.rows(), t.cols()));
348    }
349    Ok(t)
350}
351
352/// `x · rsqrt(mean(x²) + eps) ⊙ w`, a row of `w.len()` at a time.
353fn rms(x: &mut [f32], w: &[f32], eps: f32) {
354    for row in x.chunks_exact_mut(w.len()) {
355        let ms = row.iter().map(|v| (*v as f64) * (*v as f64)).sum::<f64>() / w.len() as f64;
356        let s = 1.0 / (ms as f32 + eps).sqrt();
357        for (v, g) in row.iter_mut().zip(w) {
358            *v = *v * s * g;
359        }
360    }
361}
362
363impl Drafter {
364    /// The drafter of `main`'s sidecar, when it has one.
365    pub fn beside(main: &Path) -> Option<Result<Self, String>> {
366        let p = sidecar_path(main);
367        p.exists().then(|| CmfModel::open(&p).map_err(|e| format!("{}: {e}", p.display())).and_then(|m| Self::new(Arc::new(m))))
368    }
369
370    /// The drafter of `main`'s sidecar when there is one: it must have been
371    /// converted for this very trunk.
372    pub fn for_target(main: &CmfModel) -> Option<Result<Self, String>> {
373        let r = Self::beside(&main.path)?;
374        Some(r.and_then(|d| {
375            let a = main.arch();
376            if a.num_layers != d.g.target_layers || a.hidden_size != d.g.hidden || a.vocab_size != d.g.vocab {
377                return Err(format!("it drafts for {} layers of {} over {} tokens", d.g.target_layers, d.g.hidden, d.g.vocab));
378            }
379            let want = d.model.header.provenance.as_ref().and_then(|p| p["base_binding"]["trunk_hash64"].as_str().map(str::to_string));
380            let have = format!("{:016x}", main.trunk_hash());
381            if want.as_deref() != Some(have.as_str()) {
382                return Err(format!("it was converted for the trunk {}, this one is {have}", want.as_deref().unwrap_or("?")));
383            }
384            Ok(d)
385        }))
386    }
387
388    pub fn new(model: Arc<CmfModel>) -> Result<Self, String> {
389        let g = Geometry::of(&model)?;
390        let (hs, ffn, qd, kd) = (g.hidden, g.intermediate, g.heads * g.head_dim, g.kv_heads * g.head_dim);
391        let conv = |p: &str| -> Result<Conv, String> {
392            Ok(Conv {
393                base: vector(&model, &format!("{p}.base_kernel"), 2 * g.conv_kernel * hs)?,
394                proj: Small::of(matrix(&model, &format!("{p}.kernel_projection.weight"), 2 * g.conv_kernel * (hs / g.conv_group), hs)?),
395            })
396        };
397        let layers = (0..g.layers)
398            .map(|i| {
399                let p = format!("layers.{i}");
400                Ok(Layer {
401                    in_norm: vector(&model, &format!("{p}.input_layernorm.weight"), hs)?,
402                    post_norm: vector(&model, &format!("{p}.post_attention_layernorm.weight"), hs)?,
403                    q: matrix(&model, &format!("{p}.self_attn.q_proj.weight"), qd, hs)?,
404                    k: matrix(&model, &format!("{p}.self_attn.k_proj.weight"), kd, hs)?,
405                    v: matrix(&model, &format!("{p}.self_attn.v_proj.weight"), kd, hs)?,
406                    o: matrix(&model, &format!("{p}.self_attn.o_proj.weight"), hs, qd)?,
407                    q_norm: vector(&model, &format!("{p}.self_attn.q_norm.weight"), g.head_dim)?,
408                    k_norm: vector(&model, &format!("{p}.self_attn.k_norm.weight"), g.head_dim)?,
409                    gate: matrix(&model, &format!("{p}.mlp.gate_proj.weight"), ffn, hs)?,
410                    up: matrix(&model, &format!("{p}.mlp.up_proj.weight"), ffn, hs)?,
411                    down: matrix(&model, &format!("{p}.mlp.down_proj.weight"), hs, ffn)?,
412                    attn_conv: conv(&format!("{p}.attention_conv"))?,
413                    mlp_conv: conv(&format!("{p}.mlp_conv"))?,
414                })
415            })
416            .collect::<Result<Vec<_>, String>>()?;
417        let book = |name: &str| -> Result<usize, String> {
418            let name = format!("{NAMESPACE}candidate_selector.{name}");
419            let i = model.tensor_index(&name).ok_or_else(|| format!("the sidecar has no {name}"))?;
420            let e = &model.tensors[i];
421            if e.dtype != TensorDtype::F16 || e.shape != [g.vocab, g.selector_rank] {
422                return Err(format!("{name}: {:?} {:?}, expected f16 [{}, {}]", e.dtype, e.shape, g.vocab, g.selector_rank));
423            }
424            Ok(i)
425        };
426        let half = g.head_dim / 2;
427        // a router that does not read is a sidecar without one
428        let route = model
429            .header
430            .provenance
431            .as_ref()
432            .and_then(|p| p.get(ROUTE_KEY))
433            .and_then(|v| serde_json::from_value::<RouteFit>(v.clone()).ok())
434            .and_then(|fit| Route::of(&fit, hs).map_err(|e| tracing::warn!("DFlash sidecar: {e}")).ok());
435        Ok(Self {
436            route,
437            fc: matrix(&model, "fc.weight", hs, g.tap_width())?,
438            hidden_norm: vector(&model, "hidden_norm.weight", hs)?,
439            norm: vector(&model, "norm.weight", hs)?,
440            sel: Small::of(matrix(&model, "candidate_selector.hidden_projection.weight", g.selector_rank, hs)?),
441            books: [book("predecessor_codebook")?, book("successor_codebook")?],
442            inv_freq: (0..half).map(|i| (g.rope_theta.powf(-(2.0 * i as f64) / g.head_dim as f64)) as f32).collect(),
443            ctx: (0..g.layers).map(|_| Ctx { k: vec![Vec::new(); g.kv_heads], v: vec![Vec::new(); g.kv_heads], dropped: 0 }).collect(),
444            first: 0,
445            len: 0,
446            pending: Vec::new(),
447            device: crate::gpu::enabled(),
448            fused: std::env::var("CMF_DFLASH_FUSED").as_deref() != Ok("0"),
449            #[cfg(feature = "gpu")]
450            dev: std::sync::Mutex::new(None),
451            #[cfg(feature = "cuda")]
452            cdev: std::sync::Mutex::new(None),
453            layers,
454            model,
455            g,
456        })
457    }
458
459    /// The device block, built on first use where wgpu runs the model and
460    /// `CMF_DFLASH_DEV` is not 0. The context held so far goes to the card
461    /// with it. The loader calls this once the drafter exists so the build
462    /// (kernels, 2.3 GB of sidecar weights) is paid at load, not in the
463    /// first round (measured 480 ms there).
464    #[cfg(feature = "gpu")]
465    pub fn dev_ready(&self) -> bool {
466        #[cfg(feature = "cuda")]
467        if crate::gpu::cuda_active() {
468            return self.cuda_ready();
469        }
470        use crate::gpu_wgpu::dflash_dev::{Dev, LayerSpec, Spec};
471        let mut slot = self.dev.lock().unwrap();
472        if slot.is_none() {
473            // Not yet a wgpu context, or the device not wanted: nothing is
474            // decided, the next call asks again.
475            if !(self.device && crate::gpu_wgpu::selected() && crate::gpu_wgpu::enabled()) {
476                return false;
477            }
478            let built = if crate::gpu_wgpu::dflash_dev::wanted() {
479                let g = &self.g;
480                let idx = |name: &str| self.model.tensor_index(&format!("{NAMESPACE}{name}"));
481                let layers: Option<Vec<LayerSpec>> = self
482                    .layers
483                    .iter()
484                    .enumerate()
485                    .map(|(i, l)| {
486                        let p = format!("layers.{i}");
487                        Some(LayerSpec {
488                            in_norm: l.in_norm.clone(),
489                            post_norm: l.post_norm.clone(),
490                            q_norm: l.q_norm.clone(),
491                            k_norm: l.k_norm.clone(),
492                            attn_base: l.attn_conv.base.clone(),
493                            mlp_base: l.mlp_conv.base.clone(),
494                            window: g.window_of(i),
495                            rotary: g.rotary(i),
496                            q: self.tiled(&l.q)?,
497                            k: self.tiled(&l.k)?,
498                            v: self.tiled(&l.v)?,
499                            o: self.tiled(&l.o)?,
500                            gate: self.tiled(&l.gate)?,
501                            up: self.tiled(&l.up)?,
502                            down: self.tiled(&l.down)?,
503                            attn_proj: idx(&format!("{p}.attention_conv.kernel_projection.weight"))?,
504                            mlp_proj: idx(&format!("{p}.mlp_conv.kernel_projection.weight"))?,
505                        })
506                    })
507                    .collect();
508                let spec = layers.and_then(|layers| {
509                    Some(Spec {
510                        hs: g.hidden,
511                        ffn: g.intermediate,
512                        hd: g.head_dim,
513                        nh: g.heads,
514                        kv: g.kv_heads,
515                        kn: g.conv_kernel,
516                        gs: g.conv_group,
517                        block: g.block,
518                        eps: g.eps as f32,
519                        layers,
520                        norm: self.norm.clone(),
521                        inv_freq: self.inv_freq.clone(),
522                        sel: idx("candidate_selector.hidden_projection.weight")?,
523                        sel_rank: g.selector_rank,
524                    })
525                });
526                match spec {
527                    Some(spec) => match Dev::new(self.model.clone(), spec) {
528                        Ok(mut dev) => {
529                            for li in 0..self.layers.len() {
530                                self.dev_refill(&mut dev, li);
531                            }
532                            tracing::info!("DFlash drafter: the block runs on the device as one submission");
533                            Ok(dev)
534                        }
535                        Err(e) => {
536                            tracing::info!("DFlash drafter: the block stays on the host ({e})");
537                            Err(())
538                        }
539                    },
540                    None => Err(()),
541                }
542            } else {
543                Err(())
544            };
545            *slot = Some(built);
546        }
547        matches!(slot.as_ref(), Some(Ok(_)))
548    }
549
550    /// The CUDA block (`gpu_cuda::DflashDev`), built once the hybrid arm's
551    /// kernels are: the sidecar's matrices on the card and the context held
552    /// so far with them. `CMF_DFLASH_DEV=0` keeps the block on the host.
553    #[cfg(feature = "cuda")]
554    fn cuda_ready(&self) -> bool {
555        let mut slot = self.cdev.lock().unwrap();
556        if slot.is_none() {
557            if !self.device || std::env::var("CMF_DFLASH_DEV").as_deref() == Ok("0") {
558                *slot = Some(Err(()));
559            } else if !crate::gpu_cuda::hybrid_kernels_ready() {
560                // the trunk's first call builds them: ask again then
561                return false;
562            } else {
563                let t0 = std::time::Instant::now();
564                let built = self.cuda_build();
565                match built {
566                    Ok(mut dev) => {
567                        let (hd, kv) = (self.g.head_dim, self.g.kv_heads);
568                        for (li, c) in self.ctx.iter().enumerate() {
569                            let rows = c.k.first().map(|h| h.len() / hd).unwrap_or(0);
570                            if rows == 0 {
571                                continue;
572                            }
573                            let (mut k, mut v) = (vec![0f32; rows * kv * hd], vec![0f32; rows * kv * hd]);
574                            for r in 0..rows {
575                                for h in 0..kv {
576                                    k[(r * kv + h) * hd..][..hd].copy_from_slice(&c.k[h][r * hd..][..hd]);
577                                    v[(r * kv + h) * hd..][..hd].copy_from_slice(&c.v[h][r * hd..][..hd]);
578                                }
579                            }
580                            let _ = dev.push_rows(li, c.dropped, &k, &v);
581                        }
582                        tracing::info!("DFlash drafter: the block runs on the CUDA card (built in {:.1} s)", t0.elapsed().as_secs_f64());
583                        *slot = Some(Ok(dev));
584                    }
585                    Err(e) => {
586                        tracing::info!("DFlash drafter: the block stays on the host ({e})");
587                        *slot = Some(Err(()));
588                    }
589                }
590            }
591        }
592        matches!(slot.as_ref(), Some(Ok(_)))
593    }
594
595    #[cfg(feature = "cuda")]
596    fn cuda_build(&self) -> Result<crate::gpu_cuda::DflashDev, String> {
597        use crate::gpu::{GraphPrismOp, GraphW};
598        fn gw(t: &QTensor) -> Result<GraphW<'_>, String> {
599            let (_, idx, kind, rs) = t.graph_weight().ok_or("a matrix without a graph weight")?;
600            Ok(GraphW { idx, kind, row_scale: rs, data: &[], prism: GraphPrismOp::None, affine: false })
601        }
602        // a matrix to f32 rows, the rows across the host's threads
603        fn dense(t: &QTensor) -> Vec<f32> {
604            let (rows, cols) = (t.rows(), t.cols());
605            let mut w = vec![0f32; rows * cols];
606            let threads = std::thread::available_parallelism().map_or(4, |n| n.get()).clamp(1, 16);
607            let per = rows.div_ceil(threads);
608            std::thread::scope(|sc| {
609                for (ci, chunk) in w.chunks_mut(per * cols).enumerate() {
610                    sc.spawn(move || {
611                        for (r, row) in chunk.chunks_exact_mut(cols).enumerate() {
612                            t.row_f32(ci * per + r, row);
613                        }
614                    });
615                }
616            });
617            w
618        }
619        let g = &self.g;
620        let layers = self
621            .layers
622            .iter()
623            .enumerate()
624            .map(|(i, l)| {
625                Ok(crate::gpu_cuda::DflashLayerIn {
626                    in_norm: &l.in_norm,
627                    post_norm: &l.post_norm,
628                    q_norm: &l.q_norm,
629                    k_norm: &l.k_norm,
630                    attn_base: &l.attn_conv.base,
631                    mlp_base: &l.mlp_conv.base,
632                    attn_proj: &l.attn_conv.proj.w,
633                    mlp_proj: &l.mlp_conv.proj.w,
634                    window: g.window_of(i),
635                    rotary: g.rotary(i),
636                    q: gw(&l.q)?,
637                    k: gw(&l.k)?,
638                    v: gw(&l.v)?,
639                    o: gw(&l.o)?,
640                    gate: gw(&l.gate)?,
641                    up: gw(&l.up)?,
642                    down: gw(&l.down)?,
643                })
644            })
645            .collect::<Result<Vec<_>, String>>()?;
646        let t0 = std::time::Instant::now();
647        let fc = dense(&self.fc);
648        tracing::debug!("DFlash drafter: fc to f32 in {:.0} ms", t0.elapsed().as_secs_f64() * 1e3);
649        let spec = crate::gpu_cuda::DflashSpecIn {
650            hs: g.hidden,
651            ffn: g.intermediate,
652            hd: g.head_dim,
653            nh: g.heads,
654            kv: g.kv_heads,
655            kn: g.conv_kernel,
656            gs: g.conv_group,
657            block: g.block,
658            eps: g.eps as f32,
659            sel_rank: g.selector_rank,
660            tw: g.tap_width(),
661            layers,
662            norm: &self.norm,
663            hidden_norm: &self.hidden_norm,
664            inv_freq: &self.inv_freq,
665            fc: &fc,
666            sel: &self.sel.w,
667        };
668        crate::gpu_cuda::DflashDev::new(&self.model, &spec)
669    }
670
671    /// Every context row of `layer` the host holds, to the device.
672    #[cfg(feature = "gpu")]
673    fn dev_refill(&self, dev: &mut crate::gpu_wgpu::dflash_dev::Dev, layer: usize) {
674        let hd = self.g.head_dim;
675        let c = &self.ctx[layer];
676        let rows = c.k.first().map(|h| h.len() / hd).unwrap_or(0);
677        // grown or not, the rows go up as they are
678        let _ = dev.reserve(layer, c.dropped + rows);
679        self.dev_refill_held(dev, layer);
680    }
681
682    /// The rows the host holds for `layer`, into a device buffer that has
683    /// room for them.
684    #[cfg(feature = "gpu")]
685    fn dev_refill_held(&self, dev: &mut crate::gpu_wgpu::dflash_dev::Dev, layer: usize) {
686        let (hd, kv) = (self.g.head_dim, self.g.kv_heads);
687        let c = &self.ctx[layer];
688        let rows = c.k.first().map(|h| h.len() / hd).unwrap_or(0);
689        if rows == 0 {
690            return;
691        }
692        let mut k = vec![0f32; rows * kv * hd];
693        let mut v = vec![0f32; rows * kv * hd];
694        for r in 0..rows {
695            for h in 0..kv {
696                k[(r * kv + h) * hd..][..hd].copy_from_slice(&c.k[h][r * hd..][..hd]);
697                v[(r * kv + h) * hd..][..hd].copy_from_slice(&c.v[h][r * hd..][..hd]);
698            }
699        }
700        dev.push_rows(layer, c.dropped, &k, &v);
701    }
702
703    /// Keep every matrix on the host (a parity check, a device that is busy).
704    pub fn host_only(&mut self) {
705        self.device = false;
706    }
707
708    /// The position the next context row belongs to.
709    pub fn next_position(&self) -> usize {
710        self.first + self.len + self.pending.len() / self.g.tap_width()
711    }
712
713    /// The positions the context covers: its first, and one past its last
714    /// (rows that wait to be settled included).
715    pub fn span(&self) -> (usize, usize) {
716        (self.first, self.next_position())
717    }
718
719    pub fn reset(&mut self) {
720        self.truncate(0);
721        self.first = 0;
722    }
723
724    /// Forget the context from position `pos` on (a rewind of the target).
725    pub fn truncate(&mut self, pos: usize) {
726        let held = self.first + self.len;
727        self.pending.truncate(pos.saturating_sub(held) * self.g.tap_width());
728        let keep = pos.saturating_sub(self.first).min(self.len);
729        let hd = self.g.head_dim;
730        for c in &mut self.ctx {
731            let rows = keep.saturating_sub(c.dropped);
732            for h in c.k.iter_mut().chain(c.v.iter_mut()) {
733                h.truncate(rows * hd);
734            }
735            c.dropped = c.dropped.min(keep);
736        }
737        self.len = keep;
738    }
739
740    /// A layer's keys and values of the positions it holds, for one KV head:
741    /// rows of `head_dim`, the last of them the last settled position's.
742    pub fn context(&self, layer: usize, kv_head: usize) -> (&[f32], &[f32]) {
743        (&self.ctx[layer].k[kv_head], &self.ctx[layer].v[kv_head])
744    }
745
746    fn mm(&self, w: &QTensor, x: &[f32], b: usize, pool: Option<&Pool>) -> Vec<f32> {
747        let mut out = vec![0f32; b * w.rows()];
748        if !(self.device && w.device_matmat(x, b, &mut out)) {
749            w.matmat(x, b, &mut out, pool);
750        }
751        out
752    }
753
754
755    /// The sidecar's directory index of a four-bit matrix the device can take.
756    fn tiled(&self, w: &QTensor) -> Option<usize> {
757        w.mapped_q4tp().filter(|(m, _)| Arc::ptr_eq(m, &self.model)).map(|(_, i)| i)
758    }
759
760    /// Several matrices over the same rows: one submission where the device
761    /// takes them together (four-bit matrices on Metal), a call each otherwise.
762    fn many(&self, ws: &[&QTensor], x: &[f32], b: usize, pool: Option<&Pool>) -> Vec<Vec<f32>> {
763        let mut outs: Vec<Vec<f32>> = ws.iter().map(|w| vec![0f32; b * w.rows()]).collect();
764        if self.device && self.fused {
765            let jobs: Option<Vec<(usize, usize, usize, usize)>> = ws.iter().map(|w| self.tiled(w).map(|i| (i, w.rows(), w.cols(), w.rows()))).collect();
766            if let Some(jobs) = jobs {
767                let mut refs: Vec<&mut [f32]> = outs.iter_mut().map(|o| o.as_mut_slice()).collect();
768                // a block's rows on the narrow kernel, a prompt's on the wide one
769                if b <= 8 && crate::gpu::q4tp_rows8_many(&self.model, &jobs, x, b, &mut refs) {
770                    return outs;
771                }
772                let wide: Vec<(usize, usize, usize)> = jobs.iter().map(|j| (j.0, j.1, j.2)).collect();
773                if crate::gpu::q4tp_matmat_many(&self.model, &wide, x, b, &mut refs) {
774                    return outs;
775                }
776            }
777        }
778        for (w, o) in ws.iter().zip(outs.iter_mut()) {
779            if !(self.device && w.device_matmat(x, b, o)) {
780                w.matmat(x, b, o, pool);
781            }
782        }
783        outs
784    }
785
786    /// A layer's MLP, `down · (silu(gate · x) ⊙ up · x)`: one submission where
787    /// the device takes the three matrices together.
788    fn mlp(&self, l: &Layer, x: &[f32], b: usize, pool: Option<&Pool>) -> Vec<f32> {
789        if self.device && self.fused {
790            if let (Some(g), Some(u), Some(d)) = (self.tiled(&l.gate), self.tiled(&l.up), self.tiled(&l.down)) {
791                let (hs, ffn) = (self.g.hidden, self.g.intermediate);
792                let mut out = vec![0f32; b * hs];
793                if (b <= 8 && crate::gpu::q4tp_rows8_ffn(&self.model, g, u, d, x, b, hs, ffn, &mut out))
794                    || crate::gpu::q4tp_ffn(&self.model, g, u, d, x, b, hs, ffn, &mut out)
795                {
796                    return out;
797                }
798            }
799        }
800        let mut gu = self.many(&[&l.gate, &l.up], x, b, pool);
801        let u = gu.pop().unwrap();
802        let mut a = gu.pop().unwrap();
803        for (a, u) in a.iter_mut().zip(&u) {
804            *a = *a / (1.0 + (-*a).exp()) * u;
805        }
806        self.mm(&l.down, &a, b, pool)
807    }
808
809    /// Rotate `x` (rows of `head_dim`, `heads` per position) to the position
810    /// of each row: `pos(r)` for the r-th group of `heads` rows.
811    fn rotate(&self, x: &mut [f32], heads: usize, pos: impl Fn(usize) -> usize) {
812        let (hd, half) = (self.g.head_dim, self.g.head_dim / 2);
813        let mut cs = vec![(0f32, 0f32); half];
814        for (r, row) in x.chunks_exact_mut(heads * hd).enumerate() {
815            let p = pos(r) as f32;
816            for (c, f) in cs.iter_mut().zip(&self.inv_freq) {
817                let a = p * f;
818                *c = (a.cos(), a.sin());
819            }
820            for h in row.chunks_exact_mut(hd) {
821                let (lo, hi) = h.split_at_mut(half);
822                for i in 0..half {
823                    let (c, s) = cs[i];
824                    let (a, b) = (lo[i], hi[i]);
825                    lo[i] = a * c - b * s;
826                    hi[i] = b * c + a * s;
827                }
828            }
829        }
830    }
831
832    /// Add the target's rows of positions `pos ..` to the context: `taps` is
833    /// `n` rows of the tapped layers' outputs, joined in the order of
834    /// `Geometry::taps`. Rows at positions already held replace them and
835    /// what follows; a gap starts the context over.
836    pub fn push_context(&mut self, pos: usize, taps: &[f32], pool: Option<&Pool>) {
837        self.queue_context(pos, taps, pool);
838        self.settle(pool);
839    }
840
841    /// `push_context` put off: the rows wait until a block is about to read
842    /// them (`settle`), so a target that decodes a stretch without this
843    /// drafter pays nothing for them. At most `PENDING_ROWS` wait; past that
844    /// the oldest go through the drafter's matrices a batch at a time.
845    pub fn queue_context(&mut self, pos: usize, taps: &[f32], pool: Option<&Pool>) {
846        let tw = self.g.tap_width();
847        assert!(taps.len() % tw == 0, "dflash: a context row is {tw} wide");
848        if pos > self.next_position() || pos < self.first {
849            self.truncate(0);
850            self.first = pos;
851        } else {
852            self.truncate(pos);
853        }
854        self.pending.extend_from_slice(taps);
855        // past the limit the oldest rows go through at once (in chunks of
856        // `CONTEXT_CHUNK` inside): one move of what stays, not one a chunk
857        let held = self.pending.len() / tw;
858        // CUDA: as many as the widest window reads wait (a text the chain
859        // keeps never pays for them; the first block settles them at once)
860        let limit = if crate::gpu::cuda_active() && (0..self.g.layers).all(|l| self.g.window_of(l) > 0) {
861            PENDING_ROWS.max((0..self.g.layers).map(|l| self.g.window_of(l)).max().unwrap_or(0) + BLOCK_SLACK)
862        } else {
863            PENDING_ROWS
864        };
865        if held > limit {
866            let over = (held - limit).next_multiple_of(CONTEXT_CHUNK).min(held);
867            let rows: Vec<f32> = self.pending.drain(..over * tw).collect();
868            self.settle_rows(self.first + self.len, &rows, pool);
869        }
870    }
871
872    /// Make every waiting row context.
873    pub fn settle(&mut self, pool: Option<&Pool>) {
874        if self.pending.is_empty() {
875            return;
876        }
877        let rows = std::mem::take(&mut self.pending);
878        self.settle_rows(self.first + self.len, &rows, pool);
879    }
880
881    fn settle_rows(&mut self, pos: usize, taps: &[f32], pool: Option<&Pool>) {
882        let t_settle = std::time::Instant::now();
883        let (hs, tw, hd, kv) = (self.g.hidden, self.g.tap_width(), self.g.head_dim, self.g.kv_heads);
884        let eps = self.g.eps as f32;
885        // the CUDA block's context takes rows a quarter of a thousand at a time
886        #[cfg(feature = "cuda")]
887        let per = if crate::gpu::cuda_active() { crate::gpu_cuda::DFLASH_SETTLE_MAX } else { CONTEXT_CHUNK };
888        #[cfg(not(feature = "cuda"))]
889        let per = CONTEXT_CHUNK;
890        for (ci, chunk) in taps.chunks(per * tw).enumerate() {
891            let (n, at) = (chunk.len() / tw, pos + ci * per);
892            // CUDA: the rows made on the card, the host keeping a copy
893            #[cfg(feature = "cuda")]
894            if crate::gpu::cuda_active() && self.cuda_ready() {
895                let rows = match self.cdev.lock().unwrap().as_mut() {
896                    Some(Ok(dev)) => dev.settle(at - self.first, at, chunk, n).map_err(|e| tracing::warn!("DFlash context on the card: {e}")).ok(),
897                    _ => None,
898                };
899                if let Some(rows) = rows {
900                    for (li, (k, v)) in rows.into_iter().enumerate() {
901                        let c = &mut self.ctx[li];
902                        for r in 0..n {
903                            for h in 0..kv {
904                                let o = (r * kv + h) * hd;
905                                c.k[h].extend_from_slice(&k[o..o + hd]);
906                                c.v[h].extend_from_slice(&v[o..o + hd]);
907                            }
908                        }
909                    }
910                    self.len += n;
911                    continue;
912                }
913            }
914            let mut t = self.mm(&self.fc, chunk, n, pool);
915            rms(&mut t, &self.hidden_norm, eps);
916            debug_assert_eq!(t.len(), n * hs);
917            let ws: Vec<&QTensor> = self.layers.iter().flat_map(|l| [&l.k, &l.v]).collect();
918            let mut kvs = self.many(&ws, &t, n, pool).into_iter();
919            for li in 0..self.layers.len() {
920                let l = &self.layers[li];
921                let (mut k, v) = (kvs.next().unwrap(), kvs.next().unwrap());
922                rms(&mut k, &l.k_norm, eps);
923                if self.g.rotary(li) {
924                    self.rotate(&mut k, kv, |r| at + r);
925                }
926                #[cfg(feature = "gpu")]
927                if let Some(Ok(dev)) = self.dev.lock().unwrap().as_mut() {
928                    let at = at - self.first;
929                    if dev.reserve(li, at + n) {
930                        dev.push_rows(li, at, &k, &v);
931                    } else {
932                        // the buffer grew and is empty: everything held so far, then these rows
933                        self.dev_refill_held(dev, li);
934                        dev.push_rows(li, at, &k, &v);
935                    }
936                }
937                let c = &mut self.ctx[li];
938                for r in 0..n {
939                    for h in 0..kv {
940                        let o = (r * kv + h) * hd;
941                        c.k[h].extend_from_slice(&k[o..o + hd]);
942                        c.v[h].extend_from_slice(&v[o..o + hd]);
943                    }
944                }
945            }
946            self.len += n;
947        }
948        if std::env::var_os("CMF_DFLASH_TIME").is_some() {
949            eprintln!("dflash context: {} rows in {:.1} ms", taps.len() / tw, t_settle.elapsed().as_secs_f64() * 1e3);
950        }
951        // no row of a layer sees further back than its window: drop what fell out of it
952        for (li, c) in self.ctx.iter_mut().enumerate() {
953            let (w, held) = (self.g.window_of(li), self.len - c.dropped);
954            if w > 0 && held > 2 * w {
955                let cut = held - w;
956                for h in c.k.iter_mut().chain(c.v.iter_mut()) {
957                    h.drain(..cut * hd);
958                }
959                c.dropped += cut;
960            }
961        }
962    }
963
964    /// The first stage of a row convolution: the rows convolved, and the
965    /// second stage's kernels (computed from the same rows).
966    fn conv_in(&self, c: &Conv, x: &[f32], rows: usize, pool: Option<&Pool>) -> (Vec<f32>, Vec<f32>) {
967        let d = stage(0, || c.proj.apply(x, rows, pool));
968        (self.conv(c, 0, x, &d, rows), d)
969    }
970
971    /// `y[r] = Σ_o (base[stage][o] + d[r][stage][o][group]) ⊙ x[r − o]`.
972    fn conv(&self, c: &Conv, stage: usize, x: &[f32], d: &[f32], rows: usize) -> Vec<f32> {
973        let (hs, gs, kn) = (self.g.hidden, self.g.conv_group, self.g.conv_kernel);
974        let groups = hs / gs;
975        let mut y = vec![0f32; rows * hs];
976        for r in 0..rows {
977            let out = &mut y[r * hs..(r + 1) * hs];
978            for o in 0..kn.min(r + 1) {
979                let src = &x[(r - o) * hs..(r - o + 1) * hs];
980                let base = &c.base[(stage * kn + o) * hs..(stage * kn + o + 1) * hs];
981                let dyn_ = &d[r * 2 * kn * groups + (stage * kn + o) * groups..][..groups];
982                for g in 0..groups {
983                    let k = dyn_[g];
984                    for i in g * gs..(g + 1) * gs {
985                        out[i] += (base[i] + k) * src[i];
986                    }
987                }
988            }
989        }
990        y
991    }
992
993    /// One block: the rows `[anchor, MASK…]` at positions `anchor_pos ..`,
994    /// from their embeddings `h` (`block` rows of `hidden`). Returns every
995    /// row after the last norm; row j ≥ 1 drafts the token at `anchor_pos + j`.
996    pub fn block(&self, anchor_pos: usize, mut h: Vec<f32>, pool: Option<&Pool>) -> Vec<f32> {
997        let g = &self.g;
998        let (hs, hd, nh, kv, rows) = (g.hidden, g.head_dim, g.heads, g.kv_heads, g.block);
999        assert_eq!(h.len(), rows * hs, "dflash: a block is {rows} rows");
1000        debug_assert!(self.pending.is_empty(), "dflash: rows wait to become context (settle)");
1001        let (rep, eps, scale) = (nh / kv, g.eps as f32, (hd as f32).powf(-0.5));
1002        let m = rep * rows;
1003        let (mut qg, mut sc, mut og) = (vec![0f32; m * hd], Vec::new(), vec![0f32; m * hd]);
1004        let (mut kb, mut vb) = (vec![0f32; rows * hd], vec![0f32; rows * hd]);
1005        for (li, l) in self.layers.iter().enumerate() {
1006            // the layer's context rows before the anchor, as far back as its first row sees
1007            let (c, window) = (&self.ctx[li], g.window_of(li));
1008            let base = self.first + c.dropped;
1009            let hi = anchor_pos.min(self.first + self.len).max(base);
1010            let lo = if window > 0 { (anchor_pos + 1).saturating_sub(window).clamp(base, hi) } else { base };
1011            let n = hi - lo;
1012            let (clo, chi) = ((lo - base) * hd, (hi - base) * hd);
1013            sc.clear();
1014            sc.resize(m * (n + rows), 0f32);
1015            let mut x = h.clone();
1016            rms(&mut x, &l.in_norm, eps);
1017            let (x, ad) = self.conv_in(&l.attn_conv, &x, rows, pool);
1018            let mut qkv = stage(1, || self.many(&[&l.q, &l.k, &l.v], &x, rows, pool));
1019            let t_attn = stage_timed().then(std::time::Instant::now);
1020            let v = qkv.pop().unwrap();
1021            let mut k = qkv.pop().unwrap();
1022            let mut q = qkv.pop().unwrap();
1023            rms(&mut q, &l.q_norm, eps);
1024            rms(&mut k, &l.k_norm, eps);
1025            if g.rotary(li) {
1026                self.rotate(&mut q, nh, |r| anchor_pos + r);
1027                self.rotate(&mut k, kv, |r| anchor_pos + r);
1028            }
1029            let mut y = vec![0f32; rows * nh * hd];
1030            for gi in 0..kv {
1031                for qi in 0..rep {
1032                    for r in 0..rows {
1033                        let src = (r * nh + gi * rep + qi) * hd;
1034                        qg[(qi * rows + r) * hd..][..hd].copy_from_slice(&q[src..src + hd]);
1035                    }
1036                }
1037                for r in 0..rows {
1038                    kb[r * hd..][..hd].copy_from_slice(&k[(r * kv + gi) * hd..][..hd]);
1039                    vb[r * hd..][..hd].copy_from_slice(&v[(r * kv + gi) * hd..][..hd]);
1040                }
1041                // scores of every (head, row) over the context and the block's rows
1042                let w = n + rows;
1043                if n > 0 {
1044                    sgemm_public(m, n, hd, scale, &qg, hd, &c.k[gi][clo..chi], hd, true, &mut sc, w);
1045                }
1046                sgemm_public(m, rows, hd, scale, &qg, hd, &kb, hd, true, &mut sc[n..], w);
1047                for i in 0..m {
1048                    let row = &mut sc[i * w..(i + 1) * w];
1049                    // a row further on sees less far back
1050                    if window > 0 {
1051                        let from = (anchor_pos + i % rows + 1).saturating_sub(window);
1052                        row[..from.saturating_sub(lo).min(n)].fill(f32::NEG_INFINITY);
1053                    }
1054                    let top = row.iter().copied().fold(f32::NEG_INFINITY, f32::max);
1055                    let mut sum = 0f32;
1056                    for s in row.iter_mut() {
1057                        *s = (*s - top).exp();
1058                        sum += *s;
1059                    }
1060                    row.iter_mut().for_each(|s| *s /= sum);
1061                }
1062                sgemm_public(m, hd, rows, 1.0, &sc[n..], w, &vb, hd, false, &mut og, hd);
1063                if n > 0 {
1064                    let mut oc = vec![0f32; m * hd];
1065                    sgemm_public(m, hd, n, 1.0, &sc, w, &c.v[gi][clo..chi], hd, false, &mut oc, hd);
1066                    og.iter_mut().zip(&oc).for_each(|(a, b)| *a += b);
1067                }
1068                for qi in 0..rep {
1069                    for r in 0..rows {
1070                        let dst = (r * nh + gi * rep + qi) * hd;
1071                        y[dst..dst + hd].copy_from_slice(&og[(qi * rows + r) * hd..][..hd]);
1072                    }
1073                }
1074            }
1075            if let Some(t) = t_attn {
1076                STAGE_US[2].fetch_add(t.elapsed().as_micros() as u64, std::sync::atomic::Ordering::Relaxed);
1077            }
1078            let o = stage(3, || self.many(&[&l.o], &y, rows, pool).pop().unwrap());
1079            let o = self.conv(&l.attn_conv, 1, &o, &ad, rows);
1080            h.iter_mut().zip(&o).for_each(|(a, b)| *a += b);
1081
1082            let mut x = h.clone();
1083            rms(&mut x, &l.post_norm, eps);
1084            let (x, md) = self.conv_in(&l.mlp_conv, &x, rows, pool);
1085            let d = stage(4, || self.mlp(l, &x, rows, pool));
1086            let d = self.conv(&l.mlp_conv, 1, &d, &md, rows);
1087            h.iter_mut().zip(&d).for_each(|(a, b)| *a += b);
1088        }
1089        rms(&mut h, &self.norm, eps);
1090        if stage_timed() {
1091            STAGE_US[5].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1092        }
1093        h
1094    }
1095
1096    /// The block on the device as one submission, the head and the selector's
1097    /// projection in it: the logits of the `k` rows after the anchor under
1098    /// `head` (its whole table) and the selector's rows of them. None where
1099    /// the device declines; the host block runs then.
1100    #[cfg(feature = "gpu")]
1101    pub fn block_dev(&self, anchor_pos: usize, h: &[f32], k: usize, head: &QTensor) -> Option<(Vec<f32>, Vec<f32>)> {
1102        use crate::gpu_wgpu::dflash_dev::{CtxRange, kind_of};
1103        if k == 0 || k >= self.g.block || !self.dev_ready() {
1104            return None;
1105        }
1106        // CUDA: the head over its first rows (the drafts' shortlist,
1107        // `CMF_DFLASH_VOCAB`, 65536 by default)
1108        #[cfg(feature = "cuda")]
1109        if crate::gpu::cuda_active() {
1110            let (hmodel, hidx, hkind, hrs) = head.graph_weight()?;
1111            let hgw = crate::gpu::GraphW { idx: hidx, kind: hkind, row_scale: hrs, data: &[], prism: crate::gpu::GraphPrismOp::None, affine: false };
1112            let width = std::env::var("CMF_DFLASH_VOCAB").ok().and_then(|v| v.parse::<usize>().ok()).unwrap_or(65536).clamp(self.g.selector_top, head.rows());
1113            let g = &self.g;
1114            let ranges: Vec<(usize, usize, usize)> = (0..self.layers.len())
1115                .map(|li| {
1116                    let (c, window) = (&self.ctx[li], g.window_of(li));
1117                    let base = self.first + c.dropped;
1118                    let hi = anchor_pos.min(self.first + self.len).max(base);
1119                    let lo = if window > 0 { (anchor_pos + 1).saturating_sub(window).clamp(base, hi) } else { base };
1120                    (lo - self.first, hi - lo, lo)
1121                })
1122                .collect();
1123            let mut slot = self.cdev.lock().unwrap();
1124            let Some(Ok(dev)) = slot.as_mut() else { return None };
1125            return match dev.block(anchor_pos, h, &ranges, hmodel, &hgw, head.rows(), width, k) {
1126                Ok(out) => {
1127                    if stage_timed() {
1128                        STAGE_US[5].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1129                    }
1130                    Some(out)
1131                }
1132                Err(e) => {
1133                    tracing::warn!("DFlash block on the card: {e}");
1134                    None
1135                }
1136            };
1137        }
1138        let (hmodel, hidx) = head.mapped_device_gemm()?;
1139        let kind = kind_of(hmodel, hidx)?;
1140        let g = &self.g;
1141        let ranges: Vec<CtxRange> = (0..self.layers.len())
1142            .map(|li| {
1143                let (c, window) = (&self.ctx[li], g.window_of(li));
1144                let base = self.first + c.dropped;
1145                let hi = anchor_pos.min(self.first + self.len).max(base);
1146                let lo = if window > 0 { (anchor_pos + 1).saturating_sub(window).clamp(base, hi) } else { base };
1147                CtxRange { lo: lo - self.first, n: hi - lo }
1148            })
1149            .collect();
1150        let mut slot = self.dev.lock().unwrap();
1151        let Some(Ok(dev)) = slot.as_mut() else { return None };
1152        let out = dev.block(anchor_pos, self.first, h, &ranges, (hmodel, hidx, head.rows(), head.cols(), kind), head.rows(), k)?;
1153        if stage_timed() {
1154            STAGE_US[5].fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1155        }
1156        Some((out.logits, out.hp))
1157    }
1158
1159    fn book_row(&self, book: usize, token: u32, dst: &mut [f32]) {
1160        let e = &self.model.tensors[self.books[book]];
1161        let rank = self.g.selector_rank;
1162        let bytes = &self.model.entry_bytes(e)[token as usize * rank * 2..][..rank * 2];
1163        for (d, b) in dst.iter_mut().zip(bytes.chunks_exact(2)) {
1164            *d = f16_to_f32(u16::from_le_bytes([b[0], b[1]]));
1165        }
1166    }
1167
1168    /// The drafted tokens of the rows `hidden` (rows of the block after the
1169    /// anchor's, after the last norm) from their logits under the target's
1170    /// head: each row's top candidates re-scored against the token chosen
1171    /// before it, starting from the anchor. The logits may cover only the
1172    /// head's first rows (a shortlist of the vocabulary). Returns the chosen
1173    /// tokens and, for reference, every row's own top token.
1174    pub fn select(&self, hidden: &[f32], logits: &[f32], anchor: u32) -> (Vec<u32>, Vec<u32>) {
1175        let n = hidden.len() / self.g.hidden;
1176        let hp = self.sel.apply(hidden, n, None);
1177        self.select_with(&hp, logits, anchor, n)
1178    }
1179
1180    /// `select` with the selector's projection of the rows already made
1181    /// (`hp`, `n` rows of `selector_rank`).
1182    pub fn select_with(&self, hp: &[f32], logits: &[f32], anchor: u32, n: usize) -> (Vec<u32>, Vec<u32>) {
1183        let (rank, top, vocab) = (self.g.selector_rank, self.g.selector_top, self.g.vocab);
1184        let width = logits.len() / n.max(1);
1185        assert!(logits.len() == n * width && (top..=vocab).contains(&width), "dflash: logits of {n} rows over the head's rows");
1186        assert!(hp.len() >= n * rank, "dflash: the selector's rows");
1187        let (mut prev, mut chosen, mut plain) = (anchor, Vec::with_capacity(n), Vec::with_capacity(n));
1188        let (mut pc, mut sc) = (vec![0f32; rank], vec![0f32; rank]);
1189        for j in 0..n {
1190            let cand = top_k(&logits[j * width..(j + 1) * width], top);
1191            plain.push(cand[0].1);
1192            self.book_row(0, prev, &mut pc);
1193            pc.iter_mut().zip(&hp[j * rank..(j + 1) * rank]).for_each(|(p, h)| *p *= h);
1194            let mut best = (f32::NEG_INFINITY, cand[0].1);
1195            for &(unary, tok) in &cand {
1196                self.book_row(1, tok, &mut sc);
1197                let s = unary + pc.iter().zip(&sc).map(|(a, b)| a * b).sum::<f32>();
1198                if s > best.0 {
1199                    best = (s, tok);
1200                }
1201            }
1202            prev = best.1;
1203            chosen.push(prev);
1204        }
1205        (chosen, plain)
1206    }
1207}
1208
1209/// The `k` largest of `x` with their indices, largest first.
1210pub fn top_k(x: &[f32], k: usize) -> Vec<(f32, u32)> {
1211    let mut best: Vec<(f32, u32)> = Vec::with_capacity(k + 1);
1212    for (i, &v) in x.iter().enumerate() {
1213        if best.len() == k && v <= best[k - 1].0 {
1214            continue;
1215        }
1216        let at = best.partition_point(|b| b.0 >= v);
1217        best.insert(at, (v, i as u32));
1218        best.truncate(k);
1219    }
1220    best
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225    use super::*;
1226
1227    #[test]
1228    fn top_k_orders_and_keeps_first_of_equals() {
1229        let x = [0.5, 2.0, -1.0, 2.0, 3.5, 0.0];
1230        assert_eq!(top_k(&x, 3), vec![(3.5, 4), (2.0, 1), (2.0, 3)]);
1231        assert_eq!(top_k(&x, 1), vec![(3.5, 4)]);
1232    }
1233
1234    #[test]
1235    fn sidecar_name() {
1236        assert_eq!(sidecar_path(Path::new("/m/a.cmf")), PathBuf::from("/m/a.dflash.cmf"));
1237        assert_eq!(sidecar_path(Path::new("/m/a.dflash.cmf")), PathBuf::from("/m/a.dflash.cmf"));
1238    }
1239}