1use anyhow::{Context, Result};
14use metrale_gpu_runtime::gpu::GpuBackend;
15use metrale_model_weights::weights::WeightStore;
16
17use metrale_model_layers::weight_map::{DenseWeight, dense};
18
19mod config;
20pub use config::*;
21
22fn dense_bf16_or_nvfp4(
27 store: &WeightStore,
28 name: &str,
29 gpu: &dyn GpuBackend,
30) -> Result<DenseWeight> {
31 let t = store.get(name)?;
32 if t.dtype == metrale_model_weights::weights::WeightDtype::UInt8 {
33 let prefix = name.strip_suffix(".weight").unwrap_or(name);
34 anyhow::ensure!(
35 t.shape.len() == 2,
36 "NVFP4-packed drafter tensor {name} has shape {:?} (want 2-D)",
37 t.shape
38 );
39 let (n, half_k) = (t.shape[0], t.shape[1]);
40 tracing::info!(
41 "DFlash drafter: dequantizing NVFP4 {name} [{n}, {}] -> BF16",
42 half_k * 2
43 );
44 return metrale_model_layers::weight_map::fp8_lut::dequant_nvfp4_to_bf16(
45 store,
46 prefix,
47 n,
48 half_k * 2,
49 gpu,
50 );
51 }
52 Ok(DenseWeight { weight: t.ptr })
53}
54
55#[allow(dead_code)]
58pub struct DflashWeights {
59 pub config: DflashConfig,
60
61 pub fc: DenseWeight,
62 pub hidden_norm: DenseWeight,
63 pub norm: DenseWeight,
64
65 pub layers: Vec<DflashLayerWeights>,
66
67 pub draft_id_to_target_id: Option<Vec<i64>>,
70
71 pub markov_w1: Option<DenseWeight>,
74 pub markov_w2: Option<DenseWeight>,
76 pub confidence_proj: Option<DenseWeight>,
79 pub confidence_bias: Option<DenseWeight>,
81
82 pub selector_pred: Option<DenseWeight>,
86 pub selector_succ: Option<DenseWeight>,
88 pub selector_hidden_proj: Option<DenseWeight>,
90}
91
92#[allow(dead_code)]
95pub struct DflashLayerWeights {
96 pub input_layernorm: DenseWeight,
97 pub post_attention_layernorm: DenseWeight,
98 pub q_proj: DenseWeight,
99 pub k_proj: DenseWeight,
100 pub v_proj: DenseWeight,
101 pub o_proj: DenseWeight,
102 pub q_norm: DenseWeight,
103 pub k_norm: DenseWeight,
104 pub gate_proj: DenseWeight,
105 pub up_proj: DenseWeight,
106 pub down_proj: DenseWeight,
107
108 pub attention_conv_base: Option<DenseWeight>,
111 pub attention_conv_proj: Option<DenseWeight>,
113 pub mlp_conv_base: Option<DenseWeight>,
115 pub mlp_conv_proj: Option<DenseWeight>,
117}
118
119pub fn store_has_dflash_weights(store: &WeightStore) -> bool {
122 store.contains("fc.weight") || store.contains("model.fc.weight")
123}
124
125pub fn parse_dflash_config(json: &str) -> Result<DflashConfig> {
129 serde_json::from_str(json).context("Parsing DFlash drafter config.json")
130}
131
132pub fn load_dflash_weights(
141 drafter_store: &WeightStore,
142 drafter_config: &DflashConfig,
143 _gpu: &dyn GpuBackend,
144 _tp_size: usize,
145) -> Result<Option<DflashWeights>> {
146 if !store_has_dflash_weights(drafter_store) {
147 tracing::debug!("DFlash drafter store has no `fc.weight` — skipping");
148 return Ok(None);
149 }
150
151 let prefix = if drafter_store.contains("model.fc.weight") {
152 "model."
153 } else {
154 ""
155 };
156
157 let fc = dense_bf16_or_nvfp4(drafter_store, &format!("{prefix}fc.weight"), _gpu)
158 .context("DFlash drafter: load fc.weight")?;
159 let hidden_norm = dense(drafter_store, &format!("{prefix}hidden_norm.weight"))
160 .context("DFlash drafter: load hidden_norm.weight")?;
161 let norm = dense(drafter_store, &format!("{prefix}norm.weight"))
162 .context("DFlash drafter: load norm.weight")?;
163
164 let dflash2_conv = drafter_config
168 .dflash_config
169 .as_ref()
170 .map(|c| c.conv_kernel_size > 0 && c.conv_group_size > 0)
171 .unwrap_or(false)
172 && drafter_store.contains(&format!("{prefix}layers.0.attention_conv.base_kernel"));
173
174 let layer_count = drafter_config.num_hidden_layers;
175 let mut layers = Vec::with_capacity(layer_count);
176 for i in 0..layer_count {
177 let lp = format!("{prefix}layers.{i}");
178 let (attention_conv_base, attention_conv_proj, mlp_conv_base, mlp_conv_proj) =
179 if dflash2_conv {
180 (
181 Some(dense(
182 drafter_store,
183 &format!("{lp}.attention_conv.base_kernel"),
184 )?),
185 Some(dense(
186 drafter_store,
187 &format!("{lp}.attention_conv.kernel_projection.weight"),
188 )?),
189 Some(dense(drafter_store, &format!("{lp}.mlp_conv.base_kernel"))?),
190 Some(dense(
191 drafter_store,
192 &format!("{lp}.mlp_conv.kernel_projection.weight"),
193 )?),
194 )
195 } else {
196 (None, None, None, None)
197 };
198 let layer = DflashLayerWeights {
199 input_layernorm: dense(drafter_store, &format!("{lp}.input_layernorm.weight"))?,
200 post_attention_layernorm: dense(
201 drafter_store,
202 &format!("{lp}.post_attention_layernorm.weight"),
203 )?,
204 q_proj: dense_bf16_or_nvfp4(
205 drafter_store,
206 &format!("{lp}.self_attn.q_proj.weight"),
207 _gpu,
208 )?,
209 k_proj: dense_bf16_or_nvfp4(
210 drafter_store,
211 &format!("{lp}.self_attn.k_proj.weight"),
212 _gpu,
213 )?,
214 v_proj: dense_bf16_or_nvfp4(
215 drafter_store,
216 &format!("{lp}.self_attn.v_proj.weight"),
217 _gpu,
218 )?,
219 o_proj: dense_bf16_or_nvfp4(
220 drafter_store,
221 &format!("{lp}.self_attn.o_proj.weight"),
222 _gpu,
223 )?,
224 q_norm: dense(drafter_store, &format!("{lp}.self_attn.q_norm.weight"))?,
225 k_norm: dense(drafter_store, &format!("{lp}.self_attn.k_norm.weight"))?,
226 gate_proj: dense_bf16_or_nvfp4(
227 drafter_store,
228 &format!("{lp}.mlp.gate_proj.weight"),
229 _gpu,
230 )?,
231 up_proj: dense_bf16_or_nvfp4(drafter_store, &format!("{lp}.mlp.up_proj.weight"), _gpu)?,
232 down_proj: dense_bf16_or_nvfp4(
233 drafter_store,
234 &format!("{lp}.mlp.down_proj.weight"),
235 _gpu,
236 )?,
237 attention_conv_base,
238 attention_conv_proj,
239 mlp_conv_base,
240 mlp_conv_proj,
241 };
242 layers.push(layer);
243 }
244
245 let selector_key = format!("{prefix}candidate_selector.predecessor_codebook");
247 let (selector_pred, selector_succ, selector_hidden_proj) = if drafter_config
248 .dflash_config
249 .as_ref()
250 .map(|c| c.selector_rank > 0 && c.selector_top_k > 0)
251 .unwrap_or(false)
252 && drafter_store.contains(&selector_key)
253 {
254 (
255 Some(
256 dense(drafter_store, &selector_key)
257 .context("DFlash2: load candidate_selector.predecessor_codebook")?,
258 ),
259 Some(
260 dense(
261 drafter_store,
262 &format!("{prefix}candidate_selector.successor_codebook"),
263 )
264 .context("DFlash2: load candidate_selector.successor_codebook")?,
265 ),
266 Some(
267 dense(
268 drafter_store,
269 &format!("{prefix}candidate_selector.hidden_projection.weight"),
270 )
271 .context("DFlash2: load candidate_selector.hidden_projection.weight")?,
272 ),
273 )
274 } else {
275 (None, None, None)
276 };
277 if dflash2_conv || selector_pred.is_some() {
278 tracing::info!(
279 "DFlash2 heads loaded: convs={} (k={}, group={}), selector={} (rank={}, top_k={})",
280 dflash2_conv,
281 drafter_config
282 .dflash_config
283 .as_ref()
284 .map(|c| c.conv_kernel_size)
285 .unwrap_or(0),
286 drafter_config
287 .dflash_config
288 .as_ref()
289 .map(|c| c.conv_group_size)
290 .unwrap_or(0),
291 selector_pred.is_some(),
292 drafter_config
293 .dflash_config
294 .as_ref()
295 .map(|c| c.selector_rank)
296 .unwrap_or(0),
297 drafter_config
298 .dflash_config
299 .as_ref()
300 .map(|c| c.selector_top_k)
301 .unwrap_or(0),
302 );
303 }
304
305 let draft_id_to_target_id = if drafter_store.contains(&format!("{prefix}d2t"))
306 || drafter_store.contains(&format!("{prefix}draft_id_to_target_id"))
307 {
308 tracing::warn!(
309 "DFlash drafter has draft-id→target-id mapping; remapping path is not yet wired (Phase 2.5 follow-up)"
310 );
311 Some(Vec::new())
312 } else {
313 None
314 };
315
316 let markov_key = format!("{prefix}markov_head.markov_w1.weight");
317 let (markov_w1, markov_w2) =
318 if drafter_config.markov_rank > 0 && drafter_store.contains(&markov_key) {
319 if let Some(kind) = drafter_config.markov_head_type.as_deref()
320 && kind != "vanilla"
321 {
322 anyhow::bail!(
323 "DSpark drafter declares markov_head_type={kind:?}; only \"vanilla\" \
324 (low-rank bigram bias) is defined by the reference implementation"
325 );
326 }
327 let w1 = dense(drafter_store, &markov_key)
328 .context("DSpark drafter: load markov_head.markov_w1.weight")?;
329 let w2 = dense(
330 drafter_store,
331 &format!("{prefix}markov_head.markov_w2.weight"),
332 )
333 .context("DSpark drafter: load markov_head.markov_w2.weight")?;
334 (Some(w1), Some(w2))
335 } else {
336 if drafter_config.markov_rank > 0 {
337 tracing::warn!(
338 "DSpark drafter config declares markov_rank={} but the checkpoint has \
339 no {markov_key} — running as plain DFlash (Markov bias disabled)",
340 drafter_config.markov_rank,
341 );
342 }
343 (None, None)
344 };
345 let conf_key = format!("{prefix}confidence_head.proj.weight");
346 let (confidence_proj, confidence_bias) =
347 if drafter_config.enable_confidence_head && drafter_store.contains(&conf_key) {
348 let w = dense(drafter_store, &conf_key)
349 .context("DSpark drafter: load confidence_head.proj.weight")?;
350 let b = dense(drafter_store, &format!("{prefix}confidence_head.proj.bias"))
351 .context("DSpark drafter: load confidence_head.proj.bias")?;
352 (Some(w), Some(b))
353 } else {
354 (None, None)
355 };
356 if markov_w1.is_some() || confidence_proj.is_some() {
357 tracing::info!(
358 "DSpark heads loaded: markov={} (rank={}), confidence={} (with_markov={})",
359 markov_w1.is_some(),
360 drafter_config.markov_rank,
361 confidence_proj.is_some(),
362 drafter_config.confidence_head_with_markov,
363 );
364 }
365
366 tracing::info!(
367 "DFlash drafter loaded: {} layers, hidden={}, vocab={}, γ={}, target_layers={:?}",
368 layers.len(),
369 drafter_config.hidden_size,
370 drafter_config.vocab_size,
371 drafter_config.block_size,
372 drafter_config
373 .dflash_config
374 .as_ref()
375 .map(|c| c.target_layer_ids.as_slice())
376 .unwrap_or(&[]),
377 );
378
379 Ok(Some(DflashWeights {
380 config: drafter_config.clone(),
381 fc,
382 hidden_norm,
383 norm,
384 layers,
385 draft_id_to_target_id,
386 markov_w1,
387 markov_w2,
388 confidence_proj,
389 confidence_bias,
390 selector_pred,
391 selector_succ,
392 selector_hidden_proj,
393 }))
394}
395
396#[cfg(test)]
397#[path = "dflash_loader/loader_tests.rs"]
398mod loader_tests;