metrale_model_layers/layers/
mtp_multi.rs1use std::any::Any;
14
15use anyhow::Result;
16use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend};
17
18use crate::layer::ForwardContext;
19use crate::layers::mtp_head::{MtpHead, MtpProposerState};
20use crate::speculative::{DraftProposer, ProposerState};
21
22pub struct MultiModuleMtpState {
25 pub per_module: Vec<MtpProposerState>,
28 pub last_num_drafted: usize,
31}
32
33impl ProposerState for MultiModuleMtpState {
34 fn as_any(&self) -> &dyn Any {
35 self
36 }
37 fn as_any_mut(&mut self) -> &mut dyn Any {
38 self
39 }
40}
41
42pub struct MultiModuleMtpHead {
44 modules: Vec<MtpHead>,
47}
48
49impl std::fmt::Debug for MultiModuleMtpHead {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("MultiModuleMtpHead")
52 .field("modules", &self.modules.len())
53 .finish()
54 }
55}
56
57impl MultiModuleMtpHead {
58 pub fn new(modules: Vec<MtpHead>) -> Result<Self> {
61 anyhow::ensure!(
62 !modules.is_empty(),
63 "MultiModuleMtpHead requires at least one module (got 0); \
64 caller should not construct this type for single-module MTP"
65 );
66 Ok(Self { modules })
67 }
68
69 pub fn num_modules(&self) -> usize {
71 self.modules.len()
72 }
73}
74
75impl DraftProposer for MultiModuleMtpHead {
76 fn alloc_state(&self, _gpu: &dyn GpuBackend) -> Result<Box<dyn ProposerState>> {
77 let per_module = (0..self.modules.len())
78 .map(|_| MtpProposerState {
79 block_table: Vec::new(),
80 seq_len: 0,
81 last_num_drafted: 0,
82 last_pair_key: None,
83 last_drafts: Vec::new(),
84 pending_catchup: Vec::new(),
85 })
86 .collect();
87 Ok(Box::new(MultiModuleMtpState {
88 per_module,
89 last_num_drafted: 0,
90 }))
91 }
92
93 fn propose(
94 &self,
95 last_token: u32,
96 target_hidden: DevicePtr,
97 position: usize,
98 num_drafts: usize,
99 state: &mut dyn ProposerState,
100 ctx: &ForwardContext,
101 stream: u64,
102 draft_embed_target: Option<DevicePtr>,
103 grammar_bitmask: Option<&[i32]>,
104 _target_hidden_stack: Option<DevicePtr>,
105 ) -> Result<Vec<u32>> {
106 let mm_state = state
107 .as_any_mut()
108 .downcast_mut::<MultiModuleMtpState>()
109 .ok_or_else(|| anyhow::anyhow!("Invalid MultiModuleMtp state"))?;
110
111 let k = num_drafts.min(self.modules.len());
114
115 let mut drafts = Vec::with_capacity(k);
116 let mut current_token = last_token;
117 let mut current_hidden = target_hidden;
118
119 for i in 0..k {
120 let embed_target = if i == k - 1 { draft_embed_target } else { None };
124
125 let mask_for_draft = grammar_bitmask;
128
129 let draft = self.modules[i].forward_one(
130 current_token,
131 current_hidden,
132 position + i,
133 &mut mm_state.per_module[i],
134 ctx,
135 stream,
136 embed_target,
137 mask_for_draft,
138 i == 0,
139 )?;
140
141 tracing::debug!(
142 "MultiMTP propose[{i}/{k}]: token={current_token} pos={} module_seq_len={} → draft={draft}",
143 position + i,
144 mm_state.per_module[i].seq_len,
145 );
146
147 drafts.push(draft);
148 current_token = draft;
149 current_hidden = ctx.buffers.hidden_states();
153 }
154
155 mm_state.last_num_drafted = drafts.len();
156 Ok(drafts)
157 }
158
159 fn read_deferred_draft_token(&self, gpu: &dyn GpuBackend) -> Result<u32> {
160 self.modules
164 .last()
165 .expect("MultiModuleMtpHead::new enforces non-empty")
166 .read_deferred_draft_token(gpu)
167 }
168
169 fn after_verify(
170 &self,
171 num_accepted: usize,
172 state: &mut dyn ProposerState,
173 stream: u64,
174 ) -> Result<()> {
175 let mm_state = state
176 .as_any_mut()
177 .downcast_mut::<MultiModuleMtpState>()
178 .ok_or_else(|| anyhow::anyhow!("Invalid MultiModuleMtp state"))?;
179
180 let k = mm_state.last_num_drafted;
184 for (i, per) in mm_state.per_module.iter_mut().take(k).enumerate() {
185 per.last_num_drafted = 1;
186 let trim = if i < num_accepted { 0 } else { 1 };
187 if trim > 0 {
188 per.seq_len = per.seq_len.saturating_sub(trim);
189 }
190 }
191 let _ = stream;
192 tracing::debug!(
193 "MultiMTP after_verify: accepted={num_accepted} of {k}; per-module trim done"
194 );
195 Ok(())
196 }
197
198 fn free_state(&self, _gpu: &dyn GpuBackend, state: &mut dyn ProposerState) -> Result<()> {
199 let mm_state = state
200 .as_any_mut()
201 .downcast_mut::<MultiModuleMtpState>()
202 .ok_or_else(|| anyhow::anyhow!("Invalid MultiModuleMtp state"))?;
203 for (i, per) in mm_state.per_module.iter_mut().enumerate() {
205 let head = &self.modules[i];
206 if !per.block_table.is_empty() {
207 head.kv_cache_lock().free_blocks(&per.block_table);
208 per.block_table.clear();
209 }
210 per.seq_len = 0;
211 }
212 Ok(())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_empty_modules_rejected() {
222 let err = MultiModuleMtpHead::new(vec![]).unwrap_err();
223 assert!(
224 err.to_string().contains("at least one module"),
225 "unexpected error: {err}"
226 );
227 }
228}