1use anyhow::Result;
15use metrale_cache::kv_cache::PagedKvCache;
16use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
17
18use super::Qwen3AttentionLayer;
19use crate::layer::{
20 BatchedAttnMetadata, EmptyLayerState, ForwardContext, LayerState, TransformerLayer,
21};
22use crate::layer::{
23 LayerAuxState, LayerCapabilities, LayerGraphHooks, LayerSplitPrefill, LayerWeightSetup,
24 LayerWriteOnAccept,
25};
26use crate::layers::FfnComponent;
27
28mod decode_inner;
29pub(in crate::layers::qwen3_attention) mod multi_seq;
30mod prefill_inner;
31
32pub(super) fn diag_norm(
35 gpu: &dyn GpuBackend,
36 ptr: DevicePtr,
37 n_elements: usize,
38 stream: u64,
39 label: &str,
40) {
41 let _ = gpu.synchronize(stream);
42 let mut buf = vec![0u16; n_elements];
43 let bytes =
49 unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, n_elements * 2) };
50 if gpu.copy_d2h(ptr, bytes).is_err() {
51 return;
52 }
53 let vals: Vec<f32> = buf
54 .iter()
55 .map(|&b| f32::from_bits((b as u32) << 16))
56 .collect();
57 let norm: f32 = vals.iter().map(|v| v * v).sum::<f32>().sqrt();
58 let max_abs: f32 = vals.iter().map(|v| v.abs()).fold(0.0f32, f32::max);
59 let f4 = if vals.len() >= 4 {
60 format!(
61 "[{:.4},{:.4},{:.4},{:.4}]",
62 vals[0], vals[1], vals[2], vals[3]
63 )
64 } else {
65 format!("{:?}", &vals[..vals.len().min(4)])
66 };
67 tracing::info!("DIAG {label}: norm={norm:.4} max={max_abs:.4} first4={f4} n={n_elements}");
68}
69
70pub fn diag_norm_f32(
74 gpu: &dyn GpuBackend,
75 ptr: DevicePtr,
76 n_elements: usize,
77 stream: u64,
78 label: &str,
79) {
80 let _ = gpu.synchronize(stream);
81 let mut buf = vec![0f32; n_elements];
82 let bytes =
88 unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, n_elements * 4) };
89 if gpu.copy_d2h(ptr, bytes).is_err() {
90 return;
91 }
92 let norm: f32 = buf.iter().map(|v| v * v).sum::<f32>().sqrt();
93 let max_abs: f32 = buf.iter().map(|v| v.abs()).fold(0.0f32, f32::max);
94 let f4 = if buf.len() >= 4 {
95 format!("[{:.4},{:.4},{:.4},{:.4}]", buf[0], buf[1], buf[2], buf[3])
96 } else {
97 format!("{:?}", &buf[..buf.len().min(4)])
98 };
99 tracing::info!(
100 "DIAG {label}: norm={norm:.4} max={max_abs:.4} first4={f4} n={n_elements} (FP32)"
101 );
102}
103
104impl TransformerLayer for Qwen3AttentionLayer {
105 fn decode(
106 &self,
107 hidden: DevicePtr,
108 residual: DevicePtr,
109 state: &mut dyn LayerState,
110 kv_cache: &mut PagedKvCache,
111 seq_len: usize,
112 block_table: &mut Vec<u32>,
113 disk_block_ids: &mut Vec<u32>,
114 disk_last_offloaded_per_layer: &mut Vec<u32>,
115 ctx: &ForwardContext,
116 stream: u64,
117 ) -> Result<()> {
118 self.decode_inner(
119 hidden,
120 residual,
121 state,
122 kv_cache,
123 seq_len,
124 block_table,
125 disk_block_ids,
126 disk_last_offloaded_per_layer,
127 ctx,
128 stream,
129 )
130 }
131
132 #[allow(clippy::too_many_arguments)]
133 fn prefill(
134 &self,
135 hidden: DevicePtr,
136 residual: DevicePtr,
137 num_tokens: usize,
138 state: &mut dyn LayerState,
139 kv_cache: &mut PagedKvCache,
140 seq_len_start: usize,
141 block_table: &mut Vec<u32>,
142 disk_block_ids: &mut Vec<u32>,
143 disk_last_offloaded_per_layer: &mut Vec<u32>,
144 kv_write_start: usize,
145 ctx: &ForwardContext,
146 stream: u64,
147 ) -> Result<()> {
148 self.prefill_inner(
149 hidden,
150 residual,
151 num_tokens,
152 state,
153 kv_cache,
154 seq_len_start,
155 block_table,
156 disk_block_ids,
157 disk_last_offloaded_per_layer,
158 kv_write_start,
159 None,
160 ctx,
161 stream,
162 )
163 }
164
165 #[allow(clippy::too_many_arguments)]
166 fn decode_multi_seq<'a, 'b: 'a>(
167 &self,
168 hidden: DevicePtr,
169 residual: DevicePtr,
170 num_seqs: usize,
171 states: &'a mut [&'b mut (dyn LayerState + 'static)],
172 kv_cache: &mut PagedKvCache,
173 seq_lens: &[usize],
174 block_tables: &[Vec<u32>],
175 ctx: &ForwardContext,
176 stream: u64,
177 ) -> Result<()> {
178 self.decode_multi_seq_inner(
179 hidden,
180 residual,
181 num_seqs,
182 states,
183 kv_cache,
184 seq_lens,
185 block_tables,
186 ctx,
187 stream,
188 )
189 }
190
191 fn alloc_state(&self, _gpu: &dyn GpuBackend) -> Result<Box<dyn LayerState>> {
192 Ok(Box::new(crate::layer::AttnLayerState::default()))
193 }
194
195 fn release_state(&self, state: &mut dyn LayerState, gpu: &dyn GpuBackend) -> Result<()> {
204 let Some(attn) = state
205 .as_any_mut()
206 .downcast_mut::<crate::layer::AttnLayerState>()
207 else {
208 return Ok(());
209 };
210 let Some(mut st) = attn.qsa.take() else {
211 return Ok(());
212 };
213 let Some(qsa) = self.qsa.as_ref() else {
214 anyhow::bail!("release_state: QSA seq state present but layer has no QSA indexer");
217 };
218 qsa.release_seq_state(&mut st, gpu)
219 }
220}
221
222impl LayerCapabilities for Qwen3AttentionLayer {
223 fn uses_local_mla_prefill(&self) -> bool {
224 self.mla.is_some()
225 }
226
227 fn supports_replay_tail_split(&self) -> bool {
230 self.hc.is_none() && self.mla.is_none() && self.qsa.is_none()
231 }
232
233 fn fp8_calibration_frozen(&self) -> Option<bool> {
234 self.fp8_calibration
235 .as_ref()
236 .map(|cal| !cal.is_calibrating())
237 }
238
239 fn decode_graph_unsupported(&self) -> bool {
244 self.qsa.is_some()
245 }
246}
247
248impl LayerWeightSetup for Qwen3AttentionLayer {
249 fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
250 Some(self)
251 }
252
253 fn transpose_moe_for_prefill(
254 &mut self,
255 gpu: &dyn GpuBackend,
256 config: &metrale_config::ModelConfig,
257 ) -> Result<()> {
258 if let FfnComponent::Moe(moe) = &mut self.ffn {
259 moe.transpose_for_prefill(gpu, config)?;
260 }
261 if let Some(FfnComponent::Moe(moe)) = self.moe_ffn.as_mut() {
262 moe.transpose_for_prefill(gpu, config)?;
263 }
264 Ok(())
265 }
266
267 fn transpose_moe_gate_up_for_prefill(
268 &mut self,
269 gpu: &dyn GpuBackend,
270 config: &metrale_config::ModelConfig,
271 ) -> Result<()> {
272 if let FfnComponent::Moe(moe) = &mut self.ffn {
273 moe.transpose_gate_up_for_prefill(gpu, config)?;
274 }
275 if let Some(FfnComponent::Moe(moe)) = self.moe_ffn.as_mut() {
276 moe.transpose_gate_up_for_prefill(gpu, config)?;
277 }
278 Ok(())
279 }
280
281 fn set_moe_down_transpose_scratch(
282 &mut self,
283 scratch_packed: DevicePtr,
284 scratch_scale: DevicePtr,
285 packed_ptrs_t: DevicePtr,
286 scale_ptrs_t: DevicePtr,
287 ) {
288 if let FfnComponent::Moe(moe) = &mut self.ffn {
289 moe.set_down_transpose_scratch(
290 scratch_packed,
291 scratch_scale,
292 packed_ptrs_t,
293 scale_ptrs_t,
294 );
295 }
296 if let Some(FfnComponent::Moe(moe)) = self.moe_ffn.as_mut() {
297 moe.set_down_transpose_scratch(
298 scratch_packed,
299 scratch_scale,
300 packed_ptrs_t,
301 scale_ptrs_t,
302 );
303 }
304 }
305
306 fn transpose_moe_for_prefill_unified(
307 &mut self,
308 gpu: &dyn GpuBackend,
309 config: &metrale_config::ModelConfig,
310 ) -> Result<()> {
311 if let FfnComponent::Moe(moe) = &mut self.ffn {
312 moe.transpose_for_prefill_unified(gpu, config)?;
313 }
314 if let Some(FfnComponent::Moe(moe)) = self.moe_ffn.as_mut() {
315 moe.transpose_for_prefill_unified(gpu, config)?;
316 }
317 Ok(())
318 }
319
320 fn transpose_moe_for_prefill_hybrid(
321 &mut self,
322 gpu: &dyn GpuBackend,
323 config: &metrale_config::ModelConfig,
324 ) -> Result<()> {
325 if let FfnComponent::Moe(moe) = &mut self.ffn {
326 moe.transpose_for_prefill_hybrid(gpu, config)?;
327 }
328 if let Some(FfnComponent::Moe(moe)) = self.moe_ffn.as_mut() {
329 moe.transpose_for_prefill_hybrid(gpu, config)?;
330 }
331 Ok(())
332 }
333}
334
335impl LayerWriteOnAccept for Qwen3AttentionLayer {}
336impl LayerGraphHooks for Qwen3AttentionLayer {}
337
338impl LayerAuxState for Qwen3AttentionLayer {
339 fn has_aux_state(&self) -> bool {
340 self.qsa.is_some()
341 }
342
343 fn snapshot_aux(
344 &self,
345 state: &dyn LayerState,
346 gpu: &dyn GpuBackend,
347 stream: u64,
348 ) -> Result<Option<Vec<u8>>> {
349 let Some(qsa) = self.qsa.as_ref() else {
350 return Ok(None);
351 };
352 let attn = state
353 .as_any()
354 .downcast_ref::<crate::layer::AttnLayerState>()
355 .ok_or_else(|| anyhow::anyhow!("QSA host layer state is not AttnLayerState"))?;
356 match attn.qsa.as_ref() {
357 Some(st) => Ok(Some(qsa.snapshot_aux(st, gpu, stream)?)),
358 None => Ok(None),
361 }
362 }
363
364 fn restore_aux(
365 &self,
366 state: &mut dyn LayerState,
367 blob: &[u8],
368 gpu: &dyn GpuBackend,
369 stream: u64,
370 ) -> Result<()> {
371 let qsa = self
372 .qsa
373 .as_ref()
374 .ok_or_else(|| anyhow::anyhow!("restore_aux: no QSA on this layer"))?;
375 let attn = state
376 .as_any_mut()
377 .downcast_mut::<crate::layer::AttnLayerState>()
378 .ok_or_else(|| anyhow::anyhow!("QSA host layer state is not AttnLayerState"))?;
379 if attn.qsa.is_none() {
380 attn.qsa = Some(qsa.new_seq_state(gpu)?);
381 }
382 qsa.restore_aux(attn.qsa.as_mut().expect("just created"), blob, gpu, stream)
383 }
384}
385
386impl LayerSplitPrefill for Qwen3AttentionLayer {
387 fn prefill_inner_batched_q12(
393 &self,
394 hidden_stacked: DevicePtr,
395 residual_stacked: DevicePtr,
396 num_tokens: usize,
397 kv_cache: &mut PagedKvCache,
398 seq_len_start: usize,
399 batched_meta: &BatchedAttnMetadata,
400 ctx: &ForwardContext,
401 stream: u64,
402 ) -> Result<()> {
403 let mut empty_state = EmptyLayerState;
404 let mut empty_block_table: Vec<u32> = Vec::new();
405 let mut empty_disk_block_ids: Vec<u32> = Vec::new();
406 let mut empty_disk_last: Vec<u32> = Vec::new();
407 self.prefill_inner(
408 hidden_stacked,
409 residual_stacked,
410 num_tokens,
411 &mut empty_state,
412 kv_cache,
413 seq_len_start,
414 &mut empty_block_table,
415 &mut empty_disk_block_ids,
416 &mut empty_disk_last,
417 0,
418 Some(batched_meta),
419 ctx,
420 stream,
421 )
422 }
423}