metrale_model_engine/model/lm_head_q6k.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// provenance-id: 526f6e616c6420522e205374657369616b
3
4//! 2026-09-25: The output head served from raw Q6_K blocks (DeepSeek-V4.1 GGUF).
5//!
6//! The blocks stay as the store holds them. For each group of up to `MAX_M` hidden
7//! rows, `kquant_q8_1_rows_bf16` quantises the rows to `block_q8_1`, then
8//! `kquant_mmvq_q6_k_w` computes their logits over the whole vocab. The factory
9//! installs this head only when the store holds `lm_head.weight` as
10//! [`WeightDtype::Q6K`].
11//!
12//! Owner: model-engine.
13//! Invariants: none beyond the types.
14//!
15//! [`WeightDtype::Q6K`]: metrale_model_weights::weights::WeightDtype::Q6K
16
17use anyhow::Result;
18use metrale_gpu_runtime::gpu::{DevicePtr, KernelHandle};
19
20use super::types::TransformerModel;
21use metrale_model_layers::layers::ops;
22
23/// 2026-09-25: Rows of hidden state one `kquant_mmvq_*_w` launch takes (`KQ_MAX_M` in kquant_moe.cu).
24const MAX_M: u32 = 8;
25
26/// 2026-09-25: The resident Q6_K head and the two kernels that serve it.
27pub(crate) struct LmHeadQ6k {
28 /// 2026-09-25: `[vocab][hidden / 256]` raw 210-byte super-blocks.
29 blocks: DevicePtr,
30 mmvq_w: KernelHandle,
31 q8_rows: KernelHandle,
32 /// 2026-09-25: `block_q8_1 [MAX_M][hidden / 32]` scratch for the quantised hidden rows.
33 q8_scratch: DevicePtr,
34}
35
36impl TransformerModel {
37 /// 2026-09-25: Install the raw Q6_K head. `blocks` is the store's
38 /// `lm_head.weight` pointer, `[vocab, hidden]` in Q6_K super-blocks. Fails
39 /// when `hidden_size` is not a multiple of 256.
40 pub fn set_lm_head_q6k(&mut self, blocks: DevicePtr) -> Result<()> {
41 let h = self.config.hidden_size as u32;
42 let v = self.config.vocab_size as u32;
43 anyhow::ensure!(
44 h.is_multiple_of(256),
45 "Q6_K lm_head: hidden {h} is not a multiple of 256"
46 );
47 let mmvq_w = self.gpu.kernel(ops::KQUANT_MODULE, "kquant_mmvq_q6_k_w")?;
48 let q8_rows = self
49 .gpu
50 .kernel(ops::KQUANT_MODULE, "kquant_q8_1_rows_bf16")?;
51 let q8_scratch = self.gpu.alloc(ops::kquant_q8_1_rows_bytes(MAX_M, h))?;
52 tracing::info!(
53 "LM head served from raw Q6_K blocks (K-quant GEMV, vocab={v}, {} MB resident)",
54 ops::kquant_weight_bytes(v, h, ops::Q6K_BLOCK_BYTES) >> 20
55 );
56 self.lm_head_q6k = Some(LmHeadQ6k {
57 blocks,
58 mmvq_w,
59 q8_rows,
60 q8_scratch,
61 });
62 Ok(())
63 }
64
65 /// 2026-09-25: Logits for `m` bf16 hidden rows (`[m, hidden]`, contiguous)
66 /// into `logits` (`[m, vocab]` bf16) on the Q6_K head. `Ok(false)` when no
67 /// Q6_K head is installed, so the caller falls through to its own paths.
68 pub(super) fn lm_head_q6k_run(
69 &self,
70 hidden: DevicePtr,
71 m: u32,
72 logits: DevicePtr,
73 stream: u64,
74 ) -> Result<bool> {
75 let Some(ref q6) = self.lm_head_q6k else {
76 return Ok(false);
77 };
78 let h = self.config.hidden_size as u32;
79 let v = self.config.vocab_size as u32;
80 let gpu = self.gpu.as_ref();
81 let mut done = 0u32;
82 while done < m {
83 let mm = (m - done).min(MAX_M);
84 let x = hidden.offset(done as usize * h as usize * 2);
85 let out = logits.offset(done as usize * v as usize * 2);
86 ops::kquant_q8_1_rows(gpu, q6.q8_rows, x, q6.q8_scratch, mm, h, stream)?;
87 ops::kquant_mmvq_w(
88 gpu,
89 q6.mmvq_w,
90 q6.blocks,
91 q6.q8_scratch,
92 out,
93 v,
94 h,
95 mm,
96 stream,
97 )?;
98 done += mm;
99 }
100 Ok(true)
101 }
102}