spark_model/layers/mtp_head/
draft_proposer.rs1use super::*;
5
6impl DraftProposer for MtpHead {
7 fn alloc_state(&self, _gpu: &dyn GpuBackend) -> Result<Box<dyn ProposerState>> {
8 Ok(Box::new(MtpProposerState {
9 block_table: Vec::new(),
10 seq_len: 0,
11 last_num_drafted: 0,
12 last_pair_key: None,
13 }))
14 }
15
16 fn propose(
17 &self,
18 last_token: u32,
19 target_hidden: DevicePtr,
20 position: usize,
21 num_drafts: usize,
22 state: &mut dyn ProposerState,
23 ctx: &ForwardContext,
24 stream: u64,
25 draft_embed_target: Option<DevicePtr>,
26 grammar_bitmask: Option<&[i32]>,
27 _target_hidden_stack: Option<DevicePtr>,
28 ) -> Result<Vec<u32>> {
29 let mtp_state = state
30 .as_any_mut()
31 .downcast_mut::<MtpProposerState>()
32 .ok_or_else(|| anyhow::anyhow!("Invalid MTP proposer state"))?;
33
34 self.last_conf_bits
36 .store(1.0f32.to_bits(), std::sync::atomic::Ordering::Relaxed);
37 let mut drafts = Vec::with_capacity(num_drafts);
38 let mut current_token = last_token;
39 let mut current_hidden = target_hidden;
40
41 for i in 0..num_drafts {
42 let embed_target = if i == num_drafts - 1 {
45 draft_embed_target
46 } else {
47 None
48 };
49 if grammar_bitmask.is_some() && i > 0 {
55 tracing::warn!(
56 "MTP grammar-masked drafting called with num_drafts>1 (i={i}); \
57 mask held fixed across draft positions — acceptance may drop."
58 );
59 }
60 let mask_for_draft = grammar_bitmask;
61 let draft = self.forward_one(
62 current_token,
63 current_hidden,
64 position + i,
65 mtp_state,
66 ctx,
67 stream,
68 embed_target,
69 mask_for_draft,
70 )?;
71 tracing::debug!(
72 "MTP propose[{i}]: token={current_token} pos={} mtp_seq_len={} → draft={draft}",
73 position + i,
74 mtp_state.seq_len,
75 );
76 drafts.push(draft);
77 current_token = draft;
78 current_hidden = ctx.buffers.hidden_states();
80 }
81
82 mtp_state.last_num_drafted = drafts.len();
83 Ok(drafts)
84 }
85
86 fn propose_batch(
87 &self,
88 last_tokens: &[u32],
89 target_hiddens: &[DevicePtr],
90 positions: &[usize],
91 num_drafts: usize,
92 states: &mut [&mut dyn ProposerState],
93 ctx: &ForwardContext,
94 stream: u64,
95 out_conf: Option<&mut Vec<Vec<f32>>>,
96 ) -> Result<Option<Vec<Vec<u32>>>> {
97 if !self.can_propose_batch(last_tokens.len(), ctx.buffers, ctx.config) {
98 return Ok(None);
99 }
100 let mut mtp_states: Vec<&mut MtpProposerState> = Vec::with_capacity(states.len());
103 for s in states.iter_mut() {
104 match s.as_any_mut().downcast_mut::<MtpProposerState>() {
105 Some(st) => mtp_states.push(st),
106 None => return Ok(None),
107 }
108 }
109 self.propose_batch_impl(
110 last_tokens,
111 target_hiddens,
112 positions,
113 num_drafts,
114 &mut mtp_states,
115 ctx,
116 stream,
117 out_conf,
118 )
119 .map(Some)
120 }
121
122 fn propose_batch_max(
123 &self,
124 buffers: &spark_runtime::buffers::BufferArena,
125 config: &atlas_core::config::ModelConfig,
126 ) -> usize {
127 MtpHead::propose_batch_max(self, buffers, config)
128 }
129
130 fn prefill_drafter(
131 &self,
132 prompt_tokens: &[u32],
133 hiddens: DevicePtr,
134 state: &mut dyn ProposerState,
135 ctx: &ForwardContext,
136 stream: u64,
137 ) -> Result<usize> {
138 self.prefill_drafter_impl(prompt_tokens, hiddens, state, ctx, stream)
139 }
140
141 fn drafter_rows(&self, state: &mut dyn ProposerState) -> usize {
142 state
143 .as_any_mut()
144 .downcast_mut::<MtpProposerState>()
145 .map(|s| s.seq_len)
146 .unwrap_or(0)
147 }
148
149 fn last_pair_key(&self, state: &mut dyn ProposerState) -> Option<usize> {
150 state
151 .as_any_mut()
152 .downcast_mut::<MtpProposerState>()
153 .and_then(|s| s.last_pair_key)
154 }
155
156 fn take_drafter_kv(
157 &self,
158 state: &mut dyn ProposerState,
159 ) -> Option<(Vec<u32>, usize, Option<usize>)> {
160 let st = state.as_any_mut().downcast_mut::<MtpProposerState>()?;
161 if st.block_table.is_empty() || st.seq_len == 0 {
162 return None;
163 }
164 let blocks = std::mem::take(&mut st.block_table);
165 let rows = st.seq_len;
166 let key = st.last_pair_key;
167 st.seq_len = 0;
170 st.last_pair_key = None;
171 st.last_num_drafted = 0;
172 Some((blocks, rows, key))
173 }
174
175 fn install_drafter_kv(
176 &self,
177 state: &mut dyn ProposerState,
178 blocks: Vec<u32>,
179 rows: usize,
180 last_pair_key: Option<usize>,
181 ) -> bool {
182 let Some(st) = state.as_any_mut().downcast_mut::<MtpProposerState>() else {
183 return false;
184 };
185 if !st.block_table.is_empty() || st.seq_len != 0 {
187 return false;
188 }
189 st.block_table = blocks;
190 st.seq_len = rows;
191 st.last_pair_key = last_pair_key;
192 true
193 }
194
195 fn free_drafter_kv(&self, blocks: &[u32]) {
196 if !blocks.is_empty() {
197 self.kv_cache.lock().free_blocks(blocks);
198 }
199 }
200
201 fn catchup_drafter(
202 &self,
203 tokens: &[u32],
204 hiddens: DevicePtr,
205 row_base: usize,
206 pos_base: usize,
207 state: &mut dyn ProposerState,
208 ctx: &ForwardContext,
209 stream: u64,
210 ) -> Result<usize> {
211 self.drafter_rows_impl(tokens, hiddens, row_base, pos_base, state, ctx, stream)
212 }
213
214 fn read_deferred_draft_token(&self, gpu: &dyn GpuBackend) -> Result<u32> {
215 self.read_deferred_draft_token(gpu)
216 }
217
218 fn last_confidence(&self) -> Option<f32> {
219 if crate::speculative::draft_conf_tau() <= 0.0 {
220 return None;
221 }
222 Some(f32::from_bits(
223 self.last_conf_bits
224 .load(std::sync::atomic::Ordering::Relaxed),
225 ))
226 }
227
228 fn after_verify(
229 &self,
230 num_accepted: usize,
231 state: &mut dyn ProposerState,
232 _stream: u64,
233 ) -> Result<()> {
234 let mtp_state = state
235 .as_any_mut()
236 .downcast_mut::<MtpProposerState>()
237 .ok_or_else(|| anyhow::anyhow!("Invalid MTP proposer state"))?;
238
239 let num_drafted = mtp_state.last_num_drafted.max(1);
245 let num_to_trim = mtp_rows_to_trim(
246 num_drafted,
247 num_accepted,
248 crate::speculative::mtp_refeed_accepted_enabled(),
249 );
250 let old_sl = mtp_state.seq_len;
251 if num_to_trim > 0 {
252 mtp_state.seq_len = mtp_state.seq_len.saturating_sub(num_to_trim);
253 if let Some(k) = mtp_state.last_pair_key {
256 mtp_state.last_pair_key = Some(k.saturating_sub(num_to_trim));
257 }
258 }
259 tracing::debug!(
260 "MTP after_verify: accepted={num_accepted} drafted={num_drafted} trim={num_to_trim} mtp_seq_len: {old_sl} → {}",
261 mtp_state.seq_len,
262 );
263 Ok(())
264 }
265
266 fn free_state(&self, _gpu: &dyn GpuBackend, state: &mut dyn ProposerState) -> Result<()> {
267 let mtp_state = state
268 .as_any_mut()
269 .downcast_mut::<MtpProposerState>()
270 .ok_or_else(|| anyhow::anyhow!("Invalid MTP proposer state"))?;
271 if !mtp_state.block_table.is_empty() {
272 self.kv_cache.lock().free_blocks(&mtp_state.block_table);
273 mtp_state.block_table.clear();
274 }
275 mtp_state.seq_len = 0;
276 Ok(())
277 }
278}