1mod fp8_weights;
11mod kernel_handles;
12mod rope_table;
13
14use anyhow::Result;
15use metrale_cache::kv_cache::{KvCacheConfig, KvCacheDtype, PagedKvCache};
16use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
17use parking_lot::Mutex;
18
19use super::{BlockDiffusionDraftHead, DflashLayer, DflashQuantization, DflashScratch};
20use crate::weight_loader::DflashWeights;
21
22impl BlockDiffusionDraftHead {
23 pub fn from_weights(
24 weights: DflashWeights,
25 embed_tokens_shared: DevicePtr,
26 lm_head_shared: DevicePtr,
27 lm_head_nvfp4: Option<metrale_model_layers::weight_map::QuantizedWeight>,
28 lm_head_native_fp8: Option<(metrale_model_layers::weight_map::Fp8DenseWeight, usize)>,
29 target_hidden_size: usize,
30 gamma: Option<usize>,
31 window_size: Option<usize>,
32 gpu: &dyn GpuBackend,
33 max_seq_len: usize,
34 max_batch_size: usize,
38 ) -> Result<Self> {
39 let nb = max_batch_size.max(1);
40 let target_layer_ids = weights
41 .config
42 .dflash_config
43 .as_ref()
44 .map(|c| c.target_layer_ids.clone())
45 .unwrap_or_default();
46 let mask_token_id = weights
47 .config
48 .dflash_config
49 .as_ref()
50 .map(|c| c.mask_token_id)
51 .unwrap_or(0);
52
53 if target_layer_ids.is_empty() {
54 anyhow::bail!(
55 "DFlash drafter config.json has no `dflash_config.target_layer_ids` — \
56 cannot determine which target hidden states to capture"
57 );
58 }
59
60 let _ = target_hidden_size;
61
62 let num_layers = weights.config.num_hidden_layers;
63 let hidden_size = weights.config.hidden_size;
64 let intermediate_size = weights.config.intermediate_size;
65 let num_q_heads = weights.config.num_attention_heads;
66 let num_kv_heads = weights.config.num_key_value_heads;
67 let head_dim = weights.config.head_dim;
68 let vocab_size = weights.config.vocab_size;
69 let gamma_val = metrale_model_layers::layers::qwen3_ssm::resolve_dflash_gamma(
75 gamma,
76 Some(weights.config.effective_block_size()),
77 );
78
79 let block_size = 16;
82 let kv_config = KvCacheConfig {
83 block_size,
84 num_kv_heads,
85 head_dim,
86 num_layers,
87 dtype: KvCacheDtype::Bf16,
90 layer_dtypes: vec![],
91 layer_dims: vec![],
92 cache_blocks_per_seq: None,
93 };
94 let per_seq_blocks = (max_seq_len + gamma_val + 1).div_ceil(block_size);
100 let num_blocks = per_seq_blocks * max_batch_size.max(1) + 1;
101 tracing::info!(
102 "DFlash drafter paged KV pool: {} blocks ({} per-seq x max_batch_size {})",
103 num_blocks,
104 per_seq_blocks,
105 max_batch_size.max(1)
106 );
107 let kv_cache = PagedKvCache::new(kv_config, num_blocks, gpu)?;
108
109 let kernels = kernel_handles::load_kernels(gpu)?;
112
113 let bf16 = 2usize;
117 let g = gamma_val;
118 let ctx_window: usize = std::env::var("METRALE_DFLASH_CTX_WINDOW")
122 .ok()
123 .and_then(|s| s.parse().ok())
124 .unwrap_or(4096);
125 tracing::info!(
126 "DFlash ctx_window = {} (set METRALE_DFLASH_CTX_WINDOW to override; \
127 drafter trained on full captured prefix — larger is better, \
128 scratch grows linearly)",
129 ctx_window
130 );
131 let n_attn = g + ctx_window;
132 let rows_max = n_attn.max(nb * gamma_val);
133 let q_dim = num_q_heads * head_dim;
134 let kv_dim = num_kv_heads * head_dim;
135 let scratch = DflashScratch {
136 stream_buf: gpu.alloc(rows_max * hidden_size * bf16)?,
137 norm_buf: gpu.alloc(rows_max * hidden_size * bf16)?,
138 q_buf: gpu.alloc(rows_max * q_dim * bf16)?,
139 k_buf: gpu.alloc(rows_max * kv_dim * bf16)?,
140 v_buf: gpu.alloc(rows_max * kv_dim * bf16)?,
141 attn_out: gpu.alloc(rows_max * q_dim * bf16)?,
142 mlp_intermediate: gpu.alloc(rows_max * intermediate_size * bf16)?,
143 mlp_up: gpu.alloc(rows_max * intermediate_size * bf16)?,
144 stream_acc: gpu.alloc(rows_max * hidden_size * bf16)?,
145 fc_proj: gpu.alloc(ctx_window * hidden_size * bf16)?,
146 fused_kv_out: gpu
149 .alloc(ctx_window * num_layers * 2 * num_kv_heads * head_dim * bf16)?,
150 slot_mapping_dev: gpu.alloc(ctx_window * 8)?,
154 precompute_in: gpu.alloc(
155 super::PRECOMPUTE_BATCH_ROWS * target_layer_ids.len() * target_hidden_size * bf16,
156 )?,
157 option_b_indirect_args_dev: gpu.alloc(nb * 12)?,
160 draft_tokens_host_pinned: std::sync::atomic::AtomicPtr::new(
163 gpu.alloc_host_pinned(nb * gamma_val * 4)?,
164 ),
165 draft_tokens_event: gpu.create_event()?,
166 logits: gpu.alloc(nb * g * vocab_size * bf16)?,
169 draft_tokens_dev: gpu.alloc(rows_max * 4)?,
170 position_ids: gpu.alloc(rows_max * 4)?,
171 markov_embed: if weights.config.markov_rank > 0 {
174 gpu.alloc(weights.config.markov_rank * bf16)?
175 } else {
176 DevicePtr(0)
177 },
178 markov_bias: if weights.config.markov_rank > 0 {
179 gpu.alloc(vocab_size * bf16)?
180 } else {
181 DevicePtr(0)
182 },
183 conf_out: if weights.confidence_proj.is_some() {
184 gpu.alloc(g * bf16)?
185 } else {
186 DevicePtr(0)
187 },
188 conv_dyn: if weights.selector_pred.is_some() {
192 let cfg = weights.config.dflash_config.as_ref();
193 let ksz = cfg.map(|c| c.conv_kernel_size).unwrap_or(0).max(1);
194 let gsz = cfg.map(|c| c.conv_group_size).unwrap_or(0).max(1);
195 gpu.alloc(nb * g * 2 * ksz * (hidden_size / gsz) * bf16)?
196 } else {
197 DevicePtr(0)
198 },
199 conv_tmp: if weights.selector_pred.is_some() {
200 gpu.alloc(nb * g * hidden_size * bf16)?
201 } else {
202 DevicePtr(0)
203 },
204 sel_vals: if weights.selector_pred.is_some() {
205 gpu.alloc(nb * g * 16 * 4)?
206 } else {
207 DevicePtr(0)
208 },
209 sel_idx: if weights.selector_pred.is_some() {
210 gpu.alloc(nb * g * 16 * 4)?
211 } else {
212 DevicePtr(0)
213 },
214 sel_hproj: if weights.selector_pred.is_some() {
215 let rank = weights
216 .config
217 .dflash_config
218 .as_ref()
219 .map(|c| c.selector_rank)
220 .unwrap_or(256)
221 .max(1);
222 gpu.alloc(nb * g * rank * bf16)?
223 } else {
224 DevicePtr(0)
225 },
226 };
227
228 let rope_theta = weights.config.rope_theta;
233 let rotary_dim = head_dim;
234 let inv_freq_table = rope_table::rope_inv_freq_table(&weights, rope_theta, rotary_dim);
235
236 let inv_freq_bytes: Vec<u8> = inv_freq_table
237 .iter()
238 .flat_map(|v| v.to_le_bytes())
239 .collect();
240 let yarn_inv_freq = gpu.alloc(inv_freq_bytes.len())?;
241 gpu.copy_h2d(&inv_freq_bytes, yarn_inv_freq)?;
242
243 let kv_dim_bytes = num_kv_heads * head_dim * hidden_size * bf16;
248 let fused_total_bytes = num_layers * 2 * kv_dim_bytes;
249 let fused_kv_weight = gpu.alloc(fused_total_bytes)?;
250 for (l, layer) in weights.layers.iter().enumerate() {
251 let layer_base = l * 2 * kv_dim_bytes;
252 gpu.copy_d2d(
253 layer.k_proj.weight,
254 fused_kv_weight.offset(layer_base),
255 kv_dim_bytes,
256 )?;
257 gpu.copy_d2d(
258 layer.v_proj.weight,
259 fused_kv_weight.offset(layer_base + kv_dim_bytes),
260 kv_dim_bytes,
261 )?;
262 }
263 tracing::info!(
264 "DFlash fused_kv_weight: {} bytes ({} layers × 2 × kv_dim × h × bf16), \
265 layout [K0,V0,K1,V1,…] to match vLLM precompute_and_store_context_kv",
266 fused_total_bytes,
267 num_layers,
268 );
269
270 let mut head = Self {
271 levers: super::levers::DFlashLevers::from_env(),
274 num_layers,
275 hidden_size,
276 intermediate_size,
277 num_q_heads,
278 num_kv_heads,
279 head_dim,
280 vocab_size,
281 draft_vocab_size: weights.config.draft_vocab_size.unwrap_or(vocab_size),
282 gamma: gamma_val,
283 block_gamma: std::sync::atomic::AtomicUsize::new(gamma_val),
284 max_batch: nb,
285 mask_token_id,
286 window_size,
287 target_layer_ids,
288 target_hidden_size,
289
290 embed_tokens_shared,
291 lm_head_shared,
292 lm_head_nvfp4,
293 lm_head_shared_fp8: None,
294 hidden_norm: weights.hidden_norm,
295 norm: weights.norm,
296 fc: weights.fc,
297 draft_id_to_target_id: None,
298 layers: weights
299 .layers
300 .into_iter()
301 .map(|l| DflashLayer {
302 input_layernorm: l.input_layernorm,
303 post_attention_layernorm: l.post_attention_layernorm,
304 q_proj: l.q_proj,
305 k_proj: l.k_proj,
306 v_proj: l.v_proj,
307 o_proj: l.o_proj,
308 q_norm: l.q_norm,
309 k_norm: l.k_norm,
310 gate_proj: l.gate_proj,
311 up_proj: l.up_proj,
312 down_proj: l.down_proj,
313 q_proj_fp8: None,
315 k_proj_fp8: None,
316 v_proj_fp8: None,
317 o_proj_fp8: None,
318 gate_proj_fp8: None,
319 up_proj_fp8: None,
320 down_proj_fp8: None,
321 attention_conv_base: l.attention_conv_base,
322 attention_conv_proj: l.attention_conv_proj,
323 mlp_conv_base: l.mlp_conv_base,
324 mlp_conv_proj: l.mlp_conv_proj,
325 })
326 .collect(),
327 fused_kv_weight: Some(fused_kv_weight),
328 kv_cache: Mutex::new(kv_cache),
329 scratch,
330 kernels,
331 max_seq_len,
332 yarn_inv_freq,
333 rope_theta,
334 rotary_dim,
335 rms_norm_eps: 1e-6,
336 ctx_window,
337 propose_graphs: parking_lot::Mutex::new(super::ProposeGraphs::default()),
338 suppress_graphs: std::sync::atomic::AtomicBool::new(false),
339 propose_warmup_count: std::sync::atomic::AtomicUsize::new(0),
340 quant: DflashQuantization::Bf16,
341 markov_rank: if weights.markov_w1.is_some() {
343 weights.config.markov_rank
344 } else {
345 0
346 },
347 markov_w1: weights.markov_w1,
348 markov_w2: weights.markov_w2,
349 confidence_proj: weights.confidence_proj,
350 confidence_bias: weights.confidence_bias,
351 confidence_with_markov: weights.config.confidence_head_with_markov,
352 shifted_rows: weights
353 .config
354 .dflash_config
355 .as_ref()
356 .and_then(|c| c.projector_type.as_deref())
357 == Some("dspark"),
358 conv_kernel_size: weights
359 .config
360 .dflash_config
361 .as_ref()
362 .map(|c| c.conv_kernel_size)
363 .unwrap_or(0),
364 conv_group_size: weights
365 .config
366 .dflash_config
367 .as_ref()
368 .map(|c| c.conv_group_size)
369 .unwrap_or(0),
370 selector_rank: weights
371 .config
372 .dflash_config
373 .as_ref()
374 .map(|c| c.selector_rank)
375 .unwrap_or(0),
376 selector_top_k: weights
377 .config
378 .dflash_config
379 .as_ref()
380 .map(|c| c.selector_top_k)
381 .unwrap_or(0),
382 selector_pred: weights.selector_pred,
383 selector_succ: weights.selector_succ,
384 selector_hidden_proj: weights.selector_hidden_proj,
385 };
386 if head.selector_pred.is_some() {
387 tracing::info!(
388 "DFlash2 armed: conv k={} group={} selector rank={} top_k={} \
389 (kernels present: conv={} topk={} walk={})",
390 head.conv_kernel_size,
391 head.conv_group_size,
392 head.selector_rank,
393 head.selector_top_k,
394 head.kernels.dflash2_conv2.0 != 0,
395 head.kernels.dflash2_topk16.0 != 0,
396 head.kernels.dflash2_selector_walk.0 != 0,
397 );
398 }
399 if head.shifted_rows {
400 tracing::info!(
401 "DSpark drafter: SpecForge shifted row convention active \
402 (projector_type=dspark) — draft vector rotates right by 1"
403 );
404 }
405
406 tracing::info!(
407 "BlockDiffusionDraftHead loaded: {} layers, hidden={}, intermediate={}, \
408 GQA {}/{}, head_dim={}, γ={}, vocab={}, mask_token_id={}, target_layers={:?}",
409 head.num_layers,
410 head.hidden_size,
411 head.intermediate_size,
412 head.num_q_heads,
413 head.num_kv_heads,
414 head.head_dim,
415 head.gamma,
416 head.vocab_size,
417 head.mask_token_id,
418 head.target_layer_ids,
419 );
420
421 let fp8_requested =
427 std::env::var("METRALE_DFLASH_DRAFTER_FP8").ok().as_deref() != Some("0");
428 let fp8_kernels_present = head.kernels.fp8_gemm_n128_row_scaled.0 != 0
429 && head.kernels.fp8_gemm_n128_row_scaled_m16.0 != 0;
430 if fp8_requested && !fp8_kernels_present {
431 tracing::warn!(
432 "METRALE_DFLASH_DRAFTER_FP8=1 but fp8_gemm_t_row_scaled(_m16) kernels are \
433 not in this target's w4a16 PTX module — staying on the BF16 drafter path. \
434 Port the Phase G kernels from kernels/gb10/qwen3.6-27b/nvfp4/w4a16_gemm.cu."
435 );
436 }
437 if fp8_requested && fp8_kernels_present {
438 fp8_weights::quantize_drafter_fp8(&mut head, gpu, q_dim, kv_dim, lm_head_native_fp8)?;
439 }
440
441 Ok(head)
442 }
443
444 pub fn validate_against_target(&self, target_hidden_size: usize) -> Result<()> {
448 if self.target_hidden_size != target_hidden_size {
449 anyhow::bail!(
450 "DFlash drafter target_hidden_size mismatch: drafter expects {}, target is {}",
451 self.target_hidden_size,
452 target_hidden_size
453 );
454 }
455 Ok(())
456 }
457}