metrale_model_arch/dflash_head/
proposer.rs1use super::*;
12
13impl DraftProposer for BlockDiffusionDraftHead {
14 fn block_gamma(&self) -> Option<usize> {
15 Some(self.gamma)
16 }
17
18 fn alloc_state(&self, gpu: &dyn GpuBackend) -> Result<Box<dyn ProposerState>> {
19 self.alloc_state_windowed(gpu, usize::MAX)
20 }
21
22 fn alloc_state_for(
23 &self,
24 gpu: &dyn GpuBackend,
25 budget_tokens: usize,
26 ) -> Result<Box<dyn ProposerState>> {
27 self.alloc_state_windowed(gpu, budget_tokens)
28 }
29
30 fn propose(
31 &self,
32 last_token: u32,
33 target_hidden: metrale_gpu_runtime::gpu::DevicePtr,
34 position: usize,
35 num_drafts: usize,
36 state: &mut dyn ProposerState,
37 ctx: &metrale_model_layers::layer::ForwardContext,
38 stream: u64,
39 draft_embed_target: Option<metrale_gpu_runtime::gpu::DevicePtr>,
40 grammar_bitmask: Option<&[i32]>,
41 target_hidden_stack: Option<metrale_gpu_runtime::gpu::DevicePtr>,
42 ) -> Result<Vec<u32>> {
43 self.propose_drafts(
44 last_token,
45 target_hidden,
46 position,
47 num_drafts,
48 state,
49 ctx,
50 stream,
51 draft_embed_target,
52 grammar_bitmask,
53 target_hidden_stack,
54 None,
55 )
56 }
57
58 fn propose_batch_max(
62 &self,
63 _buffers: &metrale_gpu_runtime::buffers::BufferArena,
64 _config: &metrale_config::ModelConfig,
65 ) -> usize {
66 if !self.dflash2_active() {
67 return 1;
68 }
69 let want = self.levers.batch_propose_width;
70 if want < 2 {
71 return 1;
72 }
73 want.min(self.max_batch.max(1))
74 }
75
76 fn propose_batch(
85 &self,
86 last_tokens: &[u32],
87 _target_hiddens: &[metrale_gpu_runtime::gpu::DevicePtr],
88 positions: &[usize],
89 num_drafts: usize,
90 states: &mut [&mut dyn ProposerState],
91 ctx: &metrale_model_layers::layer::ForwardContext,
92 stream: u64,
93 _out_conf: Option<&mut Vec<Vec<f32>>>,
94 ) -> Result<Option<Vec<Vec<u32>>>> {
95 let n = last_tokens.len();
96 if n < 2
97 || n > self.max_batch
98 || positions.len() != n
99 || states.len() != n
100 || !self.dflash2_active()
101 {
102 return Ok(None);
103 }
104
105 self.set_block_g(num_drafts);
108 let mut prep: Vec<(metrale_gpu_runtime::gpu::DevicePtr, u32)> = Vec::with_capacity(n);
109 for (i, st) in states.iter_mut().enumerate() {
110 let before = prep.len();
111 match self.propose_drafts(
112 last_tokens[i],
113 metrale_gpu_runtime::gpu::DevicePtr::NULL,
114 positions[i],
115 num_drafts,
116 *st,
117 ctx,
118 stream,
119 None,
120 None,
121 None,
122 Some(&mut prep),
123 ) {
124 Ok(_) if prep.len() == before + 1 => {}
125 Ok(_) => return Ok(None),
126 Err(e) => {
127 tracing::warn!("DFlash batched propose prep (seq {i}): {e:#} — per-seq path");
128 return Ok(None);
129 }
130 }
131 }
132
133 if batched_precompute_enabled()
138 && let Err(e) = self.precompute_ctx_kv_batched(states, ctx, stream)
139 {
140 tracing::warn!("DFlash batched ctx precompute: {e:#} — per-seq path");
141 return Ok(None);
142 }
143
144 let batch = DflashBatch {
145 last_tokens,
146 positions,
147 block_tables: prep.iter().map(|p| p.0).collect(),
148 ctx_counts: prep.iter().map(|p| p.1).collect(),
149 };
150 let all = match self.forward_block(
151 last_tokens[0],
152 positions[0],
153 ctx,
154 stream,
155 None,
156 Some(prep[0]),
157 Some(&batch),
158 ) {
159 Ok(v) => v,
160 Err(e) => {
161 tracing::warn!("DFlash batched forward_block: {e:#} — falling back to per-seq");
162 return Ok(None);
163 }
164 };
165 let g = self.block_g();
166 if all.len() < n * g {
167 tracing::warn!(
168 "DFlash batched forward returned {} rows, expected {} — per-seq path",
169 all.len(),
170 n * g
171 );
172 return Ok(None);
173 }
174
175 let cap = self.levers.draft_cap.unwrap_or(g);
178 let mut out: Vec<Vec<u32>> = Vec::with_capacity(n);
179 for (i, st) in states.iter_mut().enumerate() {
180 let band = &all[i * g..(i + 1) * g];
181 let drafts: Vec<u32> = if self.mask_token_id != 0 {
182 band.iter().skip(1).copied().take(cap).collect()
183 } else {
184 band.iter().copied().take(cap).collect()
185 };
186 if let Some(d) = st.as_any_mut().downcast_mut::<DflashProposerState>() {
187 d.last_num_drafted = drafts.len();
188 }
189 out.push(drafts);
190 }
191 Ok(Some(out))
192 }
193
194 fn after_verify(
195 &self,
196 num_accepted: usize,
197 state: &mut dyn ProposerState,
198 _stream: u64,
199 ) -> Result<()> {
200 let dstate = state
201 .as_any_mut()
202 .downcast_mut::<DflashProposerState>()
203 .ok_or_else(|| anyhow::anyhow!("Invalid DFlash proposer state"))?;
204 let _ = num_accepted;
209 dstate.last_num_drafted = 0;
210 Ok(())
211 }
212
213 fn free_state(&self, gpu: &dyn GpuBackend, state: &mut dyn ProposerState) -> Result<()> {
214 let dstate = match state.as_any_mut().downcast_mut::<DflashProposerState>() {
216 Some(s) => s,
217 None => return Ok(()),
218 };
219 if !dstate.block_table.is_empty() {
220 self.kv_cache.lock().free_blocks(&dstate.block_table);
221 dstate.block_table.clear();
222 }
223 if dstate.ctx_hidden_acc.0 != 0 {
227 gpu.free(dstate.ctx_hidden_acc)?;
228 dstate.ctx_hidden_acc = DevicePtr(0);
229 }
230 if let Some(bt) = dstate.block_table_dev.take() {
231 gpu.free(bt)?;
232 }
233 dstate.max_ctx_count_drafter = 0;
235 dstate.ctx_count_drafter = 0;
236 dstate.ctx_committed = 0;
237 dstate.ctx_positions.clear();
238 dstate.seq_len = 0;
239 dstate.ctx_len = 0;
240 dstate.prefill_done = false;
241 dstate.last_num_drafted = 0;
242 dstate.last_num_accepted = 0;
243 dstate.skip_next_decode_append = false;
244 Ok(())
245 }
246}
247
248impl BlockDiffusionDraftHead {
249 fn alloc_state_windowed(
253 &self,
254 gpu: &dyn GpuBackend,
255 budget_tokens: usize,
256 ) -> Result<Box<dyn ProposerState>> {
257 let bf16 = 2usize;
258 let ctx_slot_bytes = self.target_layer_ids.len() * self.target_hidden_size * bf16;
259 let cap = dflash_ctx_cap();
265 let ceiling = if cap == 0 {
266 self.max_seq_len
267 } else {
268 self.max_seq_len.min(cap)
269 };
270 let window = ceiling.min(budget_tokens.saturating_add(self.gamma + 1));
273 if ceiling < self.max_seq_len {
274 static LOGGED: std::sync::atomic::AtomicBool =
275 std::sync::atomic::AtomicBool::new(false);
276 if !LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
277 tracing::info!(
278 "DFlash ctx window capped to {} of --max-seq-len {} ({} MB/seq instead of \
279 {} MB): the accumulator is PER SEQUENCE, so the uncapped size is what \
280 OOMs a high-concurrency long-context serve. Override with \
281 METRALE_DFLASH_CTX_CAP=<tokens> (0 = uncapped).",
282 ceiling,
283 self.max_seq_len,
284 ceiling * ctx_slot_bytes / (1024 * 1024),
285 self.max_seq_len * ctx_slot_bytes / (1024 * 1024),
286 );
287 }
288 }
289 let total = window * ctx_slot_bytes;
290 let ctx_hidden_acc = gpu.alloc(total)?;
291 gpu.memset(ctx_hidden_acc, 0, total)?;
293 Ok(Box::new(DflashProposerState {
294 block_table: Vec::with_capacity(64),
295 seq_len: 0,
296 last_num_drafted: 0,
297 prefill_done: false,
298 ctx_hidden_acc,
299 ctx_len: 0,
300 last_num_accepted: 0,
301 skip_next_decode_append: false,
302 max_ctx_len: window,
303 ctx_slot_bytes,
304 block_table_dev: None,
306 ctx_count_drafter: 0,
307 max_ctx_count_drafter: 0,
308 ctx_committed: 0,
309 ctx_positions: Vec::new(),
310 }))
311 }
312}