1use anyhow::{Context, Result, bail, ensure};
16use metrale_gpu_runtime::gpu::DevicePtr;
17use metrale_model_layers::circuit_exec::{
18 BoundWeight, CircuitExec, DraftFixed, Fixed, Fusions, HeadBinding, MixerFacts, TargetModules,
19 policy,
20};
21use metrale_model_layers::layer::AttnMetadataDev;
22
23use super::super::types::TransformerModel;
24use crate::traits::{ForwardDisclosure, ForwardSelect, ModelCircuit};
25
26const DECODE_META_OFFSET: usize = 32768;
28
29impl TransformerModel {
30 pub(super) fn decode_meta(&self, max_blocks: u32, seq_slot: DevicePtr) -> AttnMetadataDev {
32 let base = self.buffers.scratch().offset(DECODE_META_OFFSET);
33 AttnMetadataDev {
34 positions: base,
35 positions_h: base,
36 positions_w: base,
37 slot: base.offset(8),
38 seq_len: base.offset(16),
39 block_table: base.offset(256),
40 max_blocks_per_seq: max_blocks,
41 num_seqs: 1,
42 seq_slot,
43 moe_row_adapter: DevicePtr::NULL,
44 }
45 }
46
47 fn circuit_unmodelled(&self) -> Vec<String> {
49 let kv_swap = self.kv_cache.lock().config().cache_blocks_per_seq.is_some();
50 [
51 (
52 self.config.tp_world_size > 1 || self.comm.is_some(),
53 "tensor or expert parallelism",
54 ),
55 (
56 !self.dflash_capture_layers.is_empty(),
57 "DFlash hidden capture",
58 ),
59 (self.lora.is_some(), "LoRA adapters"),
60 (
61 self.config.mamba_num_heads > 0,
62 "Mamba-2 state normalisation",
63 ),
64 (self.config.kv_lora_rank > 0, "latent attention"),
65 (kv_swap, "high-speed swap"),
66 (self.profile, "profiling"),
67 (
68 metrale_model_layers::ships_vanilla_norm_weights(&self.config),
69 "vanilla RMSNorm weights (the rules launch the 1 + w kernels)",
70 ),
71 ]
72 .into_iter()
73 .filter(|(present, _)| *present)
74 .map(|(_, what)| what.to_string())
75 .collect()
76 }
77
78 fn circuit_head(&self) -> (HeadBinding, &'static str) {
80 let (dtype, quantized) = if self.lm_head_q6k.is_some() {
81 ("q6k", true)
82 } else if self.lm_head_fp8.is_some() {
83 ("fp8", true)
84 } else if self.lm_head_nvfp4.is_some() {
85 ("nvfp4", true)
86 } else {
87 ("bf16", false)
88 };
89 let (batchm_max_rows, m16_tc) = self.bf16_head_route();
90 let unmodelled = [
91 (quantized, "a quantized lm_head"),
92 (m16_tc, "the tensor-core BF16 head (lm_head_m16_tc)"),
93 (self.use_fp32_logits, "FP32 logits"),
94 (
95 self.logit_softcap_kernel.0 != 0 || self.logit_softcap_fp32_kernel.0 != 0,
96 "logit softcapping",
97 ),
98 (self.overlays.is_some(), "token overlays"),
99 (
100 self.config.final_norm_identity,
101 "a checkpoint without a final norm",
102 ),
103 ]
104 .into_iter()
105 .filter(|(present, _)| *present)
106 .map(|(_, what)| what.to_string())
107 .collect();
108 let head = HeadBinding {
109 final_norm: self.final_norm,
110 lm_head: BoundWeight::Dense(self.lm_head_weight),
111 unmodelled,
112 batchm_max_rows,
113 };
114 (head, dtype)
115 }
116
117 fn build_circuit(
119 &self,
120 instance: &metrale_circuit::Instance,
121 fusions: Fusions,
122 modules: &TargetModules,
123 config_json: &str,
124 ) -> Result<CircuitExec> {
125 let unmodelled = self.circuit_unmodelled();
126 if !unmodelled.is_empty() {
127 bail!(
128 "the circuit does not model this model: {}",
129 unmodelled.join(", ")
130 );
131 }
132 let stream = self.gpu.default_stream();
133 for l in &self.layers {
134 l.circuit_prepare(self.gpu.as_ref(), &self.config, &self.levers, stream)?;
135 }
136 let verify_batch_rows = if self.proposer.is_some() && self.gdn_carry_bind_now()? {
141 Some(
142 (4 * metrale_model_layers::speculative::mtp_max_seqs())
143 .min(super::verify_e2::VERIFY_ROW_CAP) as u64,
144 )
145 } else {
146 None
147 };
148 let layers: Vec<_> = self
149 .layers
150 .iter()
151 .map(|l| l.circuit_layer(&self.config, &self.levers))
152 .collect();
153 let mut kv_dtypes = layers.iter().flatten().filter_map(|l| match l.mixer {
154 MixerFacts::Attention(a) => Some(a.kv_dtype),
155 MixerFacts::Gdn(_) => None,
156 });
157 let kv = kv_dtypes
158 .next()
159 .context("the model has no attention layer to read a KV dtype from")?;
160 ensure!(
161 kv_dtypes.all(|d| d == kv),
162 "attention layers use different KV-cache dtypes; the circuit states one"
163 );
164 let (head, lm_head_dtype) = self.circuit_head();
165 let draft = self
166 .proposer
167 .as_ref()
168 .and_then(|p| p.circuit_draft(&self.config, &self.levers));
169 let fixed = {
170 let cache = self.kv_cache.lock();
171 let n = cache.num_layers();
172 Fixed {
173 hidden: self.buffers.hidden_states(),
174 residual: self.buffers.residual(),
175 logits: self.buffers.logits(),
176 meta: self.decode_meta(0, DevicePtr::NULL),
177 batch_meta: self.batch_meta_at(
178 self.batch_meta_base(),
179 0,
180 0,
181 DevicePtr::NULL,
182 DevicePtr::NULL,
183 ),
184 ffn_act_q8: self.buffers.ffn_act_q8(),
185 tokens: self.buffers.scratch(),
186 draft: draft.as_ref().map(|d| DraftFixed {
187 embed: self.buffers.ssm_qkvz(),
188 meta: metrale_model_layers::layers::mtp_meta::mtp_attn_meta_dev(
189 self.buffers
190 .scratch()
191 .offset(metrale_model_layers::layers::mtp_meta::MTP_META_OFFSET),
192 0,
193 ),
194 k_pool: d.k_pool,
195 v_pool: d.v_pool,
196 block_size: d.block_size,
197 cache_stride: d.cache_stride,
198 vocab: d.vocab,
199 rows: d.rows.clone(),
200 }),
201 verify_meta: self.verify_meta(0, 0, DevicePtr::NULL),
202 verify_batch_meta: self.verify_batch_meta_at(),
203 verify_wy_tables: self.verify_wy_tables,
204 verify_batch_tokens: super::verify_e::mapped_argmax_host_dev(self.gpu.as_ref())
205 .map_or(self.buffers.scratch(), |(_, d)| d),
206 k_pools: (0..n).map(|i| cache.k_pool_ptr(i)).collect(),
207 v_pools: (0..n).map(|i| cache.v_pool_ptr(i)).collect(),
208 block_size: u32::try_from(cache.block_size())?,
209 cache_stride: cache.cache_stride() as u64,
210 }
211 };
212 CircuitExec::build(metrale_model_layers::circuit_exec::Boot {
213 gpu: self.gpu.as_ref(),
214 config: &self.config,
215 config_json,
216 levers: &self.levers,
217 instance,
218 policy: policy::live_policy(&self.levers, policy::kv_dtype_name(kv)?, lm_head_dtype),
219 layers,
220 head,
221 fixed,
222 fusions,
223 modules,
224 multi_seq_rows: self.circuit_widths(),
225 draft: draft.map(|d| d.layer),
228 verify_rows: if self.proposer.is_some() {
229 vec![2, 3, 4]
230 } else {
231 Vec::new()
232 },
233 verify_batch_rows,
234 draft_rows: self
236 .proposer
237 .as_ref()
238 .map(|p| p.propose_batch_max(&self.buffers, &self.config))
239 .filter(|&w| w >= 2)
240 .map(|w| w.min(metrale_model_layers::speculative::mtp_max_seqs()) as u64),
241 })
242 }
243
244 fn circuit_widths(&self) -> Vec<u64> {
247 let widest = crate::traits::padded_batch_n(self.levers.max_decode_seqs as usize);
248 crate::traits::DECODE_BATCH_LADDER
249 .iter()
250 .filter(|&&r| r <= widest)
251 .map(|&r| r as u64)
252 .collect()
253 }
254}
255
256impl ModelCircuit for TransformerModel {
257 fn set_forward(&self, sel: &ForwardSelect) -> Result<()> {
258 let next = match sel {
259 ForwardSelect::Legacy => None,
260 ForwardSelect::Circuit {
261 instance,
262 fusions,
263 modules,
264 config_json,
265 } => Some(self.build_circuit(instance, *fusions, modules, config_json)?),
266 };
267 self.destroy_lora_decode_graphs();
268 let runner = next.as_ref().and_then(CircuitExec::draft_runner);
271 if let Some(p) = self.proposer.as_ref() {
272 p.set_circuit_draft(None);
273 }
274 let prev = std::mem::replace(&mut *self.circuit.write(), next);
275 if let Some(prev) = prev {
276 prev.free(self.gpu.as_ref())?;
277 }
278 if let (Some(p), Some(r)) = (self.proposer.as_ref(), runner) {
279 p.set_circuit_draft(Some(r));
280 }
281 Ok(())
282 }
283
284 fn forward_disclosure(&self) -> ForwardDisclosure {
285 match &*self.circuit.read() {
286 None => ForwardDisclosure::legacy(),
287 Some(e) => ForwardDisclosure {
288 forward: match e.fusions {
289 Fusions::All => "circuit",
290 Fusions::ReferenceOnly => "circuit-reference",
291 },
292 plan_digest: Some(e.plans_digest()),
293 launches_per_step: Some(e.decode.launches.len()),
294 },
295 }
296 }
297}