metrale_model_layers/layers/mtp_head/
draft_proposer.rs1use super::*;
11
12impl DraftProposer for MtpHead {
13 fn circuit_draft(
14 &self,
15 config: &metrale_config::ModelConfig,
16 levers: &crate::layers::ops::ModelLevers,
17 ) -> Option<crate::circuit_exec::DraftBinding> {
18 Some(self.circuit_binding(config, levers))
19 }
20
21 fn set_circuit_draft(
22 &self,
23 runner: Option<std::sync::Arc<dyn crate::circuit_exec::DraftRunner>>,
24 ) {
25 self.install_circuit(runner);
26 }
27
28 fn alloc_state(&self, _gpu: &dyn GpuBackend) -> Result<Box<dyn ProposerState>> {
29 Ok(Box::new(MtpProposerState {
30 block_table: Vec::new(),
31 seq_len: 0,
32 last_num_drafted: 0,
33 last_pair_key: None,
34 last_drafts: Vec::new(),
35 pending_catchup: Vec::new(),
36 }))
37 }
38
39 fn propose(
40 &self,
41 last_token: u32,
42 target_hidden: DevicePtr,
43 position: usize,
44 num_drafts: usize,
45 state: &mut dyn ProposerState,
46 ctx: &ForwardContext,
47 stream: u64,
48 draft_embed_target: Option<DevicePtr>,
49 grammar_bitmask: Option<&[i32]>,
50 _target_hidden_stack: Option<DevicePtr>,
51 ) -> Result<Vec<u32>> {
52 let mtp_state = state
53 .as_any_mut()
54 .downcast_mut::<MtpProposerState>()
55 .ok_or_else(|| anyhow::anyhow!("Invalid MTP proposer state"))?;
56
57 let stop_lp = crate::speculative::draft_stop::draft_stop_logprob();
60 let mut drafts = Vec::with_capacity(num_drafts);
61 let mut current_token = last_token;
62 let mut current_hidden = target_hidden;
63
64 for i in 0..num_drafts {
65 let embed_target = if i == num_drafts - 1 {
66 draft_embed_target
67 } else {
68 None
69 };
70 if grammar_bitmask.is_some() && i > 0 {
73 tracing::warn!(
74 "MTP grammar-masked drafting called with num_drafts>1 (i={i}); \
75 mask held fixed across draft positions — acceptance may drop."
76 );
77 }
78 let mask_for_draft = grammar_bitmask;
79 let draft = self.forward_one(
80 current_token,
81 current_hidden,
82 position + i,
83 mtp_state,
84 ctx,
85 stream,
86 embed_target,
87 mask_for_draft,
88 i == 0,
89 )?;
90 tracing::debug!(
91 "MTP propose[{i}]: token={current_token} pos={} mtp_seq_len={} → draft={draft}",
92 position + i,
93 mtp_state.seq_len,
94 );
95 drafts.push(draft);
96 current_token = draft;
97 current_hidden = Self::chain_hidden(ctx);
99 if let Some(ln_tau) = stop_lp {
102 let lp = f32::from_bits(
103 self.last_conf_bits
104 .load(std::sync::atomic::Ordering::Relaxed),
105 );
106 if !lp.is_nan() && !crate::speculative::draft_stop::chain_continues(lp, ln_tau) {
107 break;
108 }
109 }
110 }
111
112 mtp_state.last_num_drafted = drafts.len();
113 mtp_state.last_drafts.clone_from(&drafts);
114 Ok(drafts)
115 }
116
117 fn propose_batch(
118 &self,
119 last_tokens: &[u32],
120 target_hiddens: &[DevicePtr],
121 positions: &[usize],
122 num_drafts: usize,
123 states: &mut [&mut dyn ProposerState],
124 ctx: &ForwardContext,
125 stream: u64,
126 out_conf: Option<&mut Vec<Vec<f32>>>,
127 ) -> Result<Option<Vec<Vec<u32>>>> {
128 if !self.can_propose_batch(last_tokens.len(), ctx.buffers, ctx.config) {
129 return Ok(None);
130 }
131 let circuit = self.circuit_draft.read().clone();
136 if let Some(r) = &circuit
137 && !(r.serves(last_tokens.len() as u64) && out_conf.is_some())
138 {
139 return Ok(None);
140 }
141 if let Some(moe) = self.moe_grouped.as_ref()
146 && !moe.any_grouped_decode_ok(last_tokens.len(), ctx)
147 {
148 static LOGGED: std::sync::Once = std::sync::Once::new();
149 LOGGED.call_once(|| {
150 tracing::warn!(
151 "MTP propose_batch: grouped FP8 MoE decode refused for n={} (kill switch \
152 METRALE_NO_FP8_MOE_GROUPED_DECODE, FP32 routing or EP) — the MoE drafter \
153 falls back to per-sequence propose",
154 last_tokens.len()
155 );
156 });
157 return Ok(None);
158 }
159 let mut mtp_states: Vec<&mut MtpProposerState> = Vec::with_capacity(states.len());
162 for s in states.iter_mut() {
163 match s.as_any_mut().downcast_mut::<MtpProposerState>() {
164 Some(st) => mtp_states.push(st),
165 None => return Ok(None),
166 }
167 }
168 self.propose_batch_impl(
169 circuit.as_deref(),
170 last_tokens,
171 target_hiddens,
172 positions,
173 num_drafts,
174 &mut mtp_states,
175 ctx,
176 stream,
177 out_conf,
178 )
179 .map(Some)
180 }
181
182 fn propose_batch_max(
183 &self,
184 buffers: &metrale_gpu_runtime::buffers::BufferArena,
185 config: &metrale_config::ModelConfig,
186 ) -> usize {
187 MtpHead::propose_batch_max(self, buffers, config)
188 }
189
190 fn prefill_drafter(
191 &self,
192 prompt_tokens: &[u32],
193 hiddens: DevicePtr,
194 state: &mut dyn ProposerState,
195 ctx: &ForwardContext,
196 stream: u64,
197 ) -> Result<usize> {
198 self.prefill_drafter_impl(prompt_tokens, hiddens, state, ctx, stream)
199 }
200
201 fn drafter_rows(&self, state: &mut dyn ProposerState) -> usize {
202 state
203 .as_any_mut()
204 .downcast_mut::<MtpProposerState>()
205 .map(|s| s.seq_len)
206 .unwrap_or(0)
207 }
208
209 fn last_pair_key(&self, state: &mut dyn ProposerState) -> Option<usize> {
210 state
211 .as_any_mut()
212 .downcast_mut::<MtpProposerState>()
213 .and_then(|s| s.last_pair_key)
214 }
215
216 fn take_drafter_kv(
217 &self,
218 state: &mut dyn ProposerState,
219 ) -> Option<(Vec<u32>, usize, Option<usize>)> {
220 let st = state.as_any_mut().downcast_mut::<MtpProposerState>()?;
221 if st.block_table.is_empty() || st.seq_len == 0 {
222 return None;
223 }
224 let blocks = std::mem::take(&mut st.block_table);
225 let rows = st.seq_len;
226 let key = st.last_pair_key;
227 st.seq_len = 0;
230 st.last_pair_key = None;
231 st.last_num_drafted = 0;
232 Some((blocks, rows, key))
233 }
234
235 fn install_drafter_kv(
236 &self,
237 state: &mut dyn ProposerState,
238 blocks: Vec<u32>,
239 rows: usize,
240 last_pair_key: Option<usize>,
241 ) -> bool {
242 let Some(st) = state.as_any_mut().downcast_mut::<MtpProposerState>() else {
243 return false;
244 };
245 if !st.block_table.is_empty() || st.seq_len != 0 {
247 return false;
248 }
249 st.block_table = blocks;
250 st.seq_len = rows;
251 st.last_pair_key = last_pair_key;
252 true
253 }
254
255 fn free_drafter_kv(&self, blocks: &[u32]) {
256 if !blocks.is_empty() {
257 self.kv_cache.lock().free_blocks(blocks);
258 }
259 }
260
261 fn catchup_drafter(
262 &self,
263 tokens: &[u32],
264 hiddens: DevicePtr,
265 row_base: usize,
266 pos_base: usize,
267 state: &mut dyn ProposerState,
268 ctx: &ForwardContext,
269 stream: u64,
270 ) -> Result<usize> {
271 self.drafter_rows_impl(tokens, hiddens, row_base, pos_base, state, ctx, stream)
272 }
273
274 fn read_deferred_draft_token(&self, gpu: &dyn GpuBackend) -> Result<u32> {
275 self.read_deferred_draft_token(gpu)
276 }
277
278 fn catchup_batch(
279 &self,
280 tokens: &[Vec<u32>],
281 hiddens: &[Vec<DevicePtr>],
282 first_pos: &[usize],
283 states: &mut [&mut dyn ProposerState],
284 ctx: &ForwardContext,
285 stream: u64,
286 ) -> Result<usize> {
287 self.catchup_batch_impl(tokens, hiddens, first_pos, states, ctx, stream)
288 }
289
290 fn after_verify(
291 &self,
292 num_accepted: usize,
293 state: &mut dyn ProposerState,
294 _stream: u64,
295 ) -> Result<()> {
296 let mtp_state = state
297 .as_any_mut()
298 .downcast_mut::<MtpProposerState>()
299 .ok_or_else(|| anyhow::anyhow!("Invalid MTP proposer state"))?;
300
301 let num_drafted = mtp_state.last_num_drafted.max(1);
302 let num_to_trim = if self.kv_exact {
306 num_drafted - 1
307 } else {
308 mtp_rows_to_trim(
309 num_drafted,
310 num_accepted,
311 crate::speculative::mtp_refeed_accepted_enabled(),
312 )
313 };
314 let old_sl = mtp_state.seq_len;
315 if num_to_trim > 0 {
316 mtp_state.seq_len = mtp_state.seq_len.saturating_sub(num_to_trim);
317 if let Some(k) = mtp_state.last_pair_key {
320 mtp_state.last_pair_key = Some(k.saturating_sub(num_to_trim));
321 }
322 }
323 tracing::debug!(
324 "MTP after_verify: accepted={num_accepted} drafted={num_drafted} trim={num_to_trim} mtp_seq_len: {old_sl} → {}",
325 mtp_state.seq_len,
326 );
327 Ok(())
328 }
329
330 fn free_state(&self, _gpu: &dyn GpuBackend, state: &mut dyn ProposerState) -> Result<()> {
331 let mtp_state = state
332 .as_any_mut()
333 .downcast_mut::<MtpProposerState>()
334 .ok_or_else(|| anyhow::anyhow!("Invalid MTP proposer state"))?;
335 if !mtp_state.block_table.is_empty() {
336 self.kv_cache.lock().free_blocks(&mtp_state.block_table);
337 mtp_state.block_table.clear();
338 }
339 mtp_state.seq_len = 0;
340 Ok(())
341 }
342}