cherenkov/qwen4_exp/gpu/prefill/
allocation.rs1use super::*;
4use crate::metal::PAGE_SIZE;
5use std::cell::Cell;
6
7fn scratch<T>(
10 c: &crate::qwen4_exp::Qwen4ExpConfig,
11 max_t: usize,
12 rows: usize,
13 all_logits: bool,
14 allocate: impl Fn(usize) -> Result<T>,
15) -> Result<PrefillScratch<T>> {
16 let h = c.hidden_size;
17 let hh = c.hc_hidden();
18 let kv_row = c.num_key_value_heads * c.head_dim;
19 let conv_dim = 2 * c.linear_num_key_heads * c.linear_key_head_dim
20 + c.linear_num_value_heads * c.linear_value_head_dim;
21 let v_dim = c.linear_num_value_heads * c.linear_value_head_dim;
22 let inter = c.moe_intermediate_size;
23 let k = c.num_experts_per_tok;
24 let rp = rows.div_ceil(32) * 32 + 32;
25 let max_blocks = max_t / c.indexer_compress_ratio + 1;
26 let kl_pad = max_t.div_ceil(32) * 32;
27 let n_rep = c.num_attention_heads / c.num_key_value_heads;
28 let f = |n: usize| allocate(n * 4);
29 let hf = |n: usize| allocate(n * 2);
30
31 Ok(PrefillScratch {
32 allocated_bytes: 0,
33 rows: rp,
34 ids: allocate(rp * 4)?,
35 e: f(rp * h)?,
36 hyper: f(rp * hh)?,
37 normed: f(rp * hh)?,
38 d: f(rp * c.hc_lowrank)?,
39 u: f(rp * hh)?,
40 mixed: f(rp * h)?,
41 inj: f(rp * c.hc_count)?,
42 mix_out: f(rp * h)?,
43 moe_out: f(rp * h)?,
44 qg: f(rp * c.num_attention_heads * 2 * c.head_dim)?,
45 k: f(rp * kv_row)?,
46 v: f(rp * kv_row)?,
47 attn_out: f(rp * c.num_attention_heads * c.head_dim)?,
48 iqk: f(rp * (c.indexer_n_heads + 1) * c.indexer_head_dim)?,
49 iq: f(rp * c.indexer_n_heads * c.indexer_head_dim)?,
50 bscore: f(rp * max_blocks)?,
51 vis: allocate(rp * (c.indexer_budget + c.indexer_compress_ratio) * 4)?,
52 nvis: allocate(rp * 4)?,
53 vmask: allocate(rp * max_blocks.div_ceil(32) * 4)?,
54 ag_qh: hf(c.num_attention_heads * QS * c.head_dim)?,
55 ag_kh: hf(kl_pad * c.head_dim)?,
56 ag_vt: hf(c.head_dim * kl_pad)?,
57 ag_s: f(n_rep * QS * kl_pad)?,
58 ag_p: hf(n_rep * QS * kl_pad)?,
59 ag_o: f(n_rep * QS * c.head_dim)?,
60 qkv: f(rp * conv_dim)?,
61 z: f(rp * v_dim)?,
62 a: f(rp * c.linear_num_value_heads)?,
63 b: f(rp * c.linear_num_value_heads)?,
64 kqn: f(rp * 2 * c.linear_num_key_heads * c.linear_key_head_dim)?,
65 gbuf: f(rp * c.linear_num_value_heads * 2)?,
66 delta_y: f(rp * v_dim)?,
67 router: f(rp * c.num_experts)?,
68 topk_idx: allocate(rp * k * 4)?,
69 topk_w: f(rp * k)?,
70 csr_rows: allocate(rp * k * 4)?,
71 csr_w: f(rp * k)?,
72 xg: f(rp * h)?,
73 ge: f(rp * inter)?,
74 ue: f(rp * inter)?,
75 hg: f(rp * inter)?,
76 ye: f(rp * h)?,
77 mtp_hyper: f(rp * hh)?,
78 fe: f(rp * h)?,
79 fh: f(rp * hh)?,
80 logits_all: if all_logits {
81 Some(f(rp * c.vocab_size)?)
82 } else {
83 None
84 },
85 })
86}
87
88pub(in crate::qwen4_exp::gpu) fn scratch_bytes(
89 c: &crate::qwen4_exp::Qwen4ExpConfig,
90 max_t: usize,
91 rows: usize,
92 all_logits: bool,
93) -> Result<usize> {
94 let total = Cell::new(0usize);
95
96 scratch(c, max_t, rows, all_logits, |bytes| {
97 let padded = bytes.div_ceil(PAGE_SIZE) * PAGE_SIZE;
99 let sum = total
100 .get()
101 .checked_add(padded)
102 .context("prefill scratch size overflow")?;
103
104 total.set(sum);
105
106 Ok(())
107 })?;
108
109 Ok(total.get())
110}
111
112fn rows_fit(
113 c: &crate::qwen4_exp::Qwen4ExpConfig,
114 max_t: usize,
115 available: usize,
116 all_logits: bool,
117) -> Result<usize> {
118 let (mut low, mut high) = (0, MAX_PREFILL_ROWS.min(max_t));
119
120 while low < high {
121 let mid = low + (high - low).div_ceil(2);
122
123 if scratch_bytes(c, max_t, mid, all_logits)? <= available {
124 low = mid;
125 } else {
126 high = mid - 1;
127 }
128 }
129
130 ensure!(
131 low > 0,
132 "insufficient memory for prefill scratch; reduce expert pool or context capacity"
133 );
134
135 Ok(low)
136}
137
138impl Gpu<'_> {
139 pub(super) fn pf_alloc(&self, rows: usize, all_logits: bool) -> Result<PrefillScratch> {
140 let before = self.allocated_bytes();
141 let mut buffers = scratch(&self.p.cfg, self.max_t, rows, all_logits, |bytes| {
142 self.ctx.new_buffer(bytes)
143 })?;
144 buffers.allocated_bytes = self.allocated_bytes().saturating_sub(before) as usize;
145
146 Ok(buffers)
147 }
148
149 pub fn prefill_rows_fit(&self, all_logits: bool) -> Result<usize> {
151 use objc2_metal::MTLDevice as _;
152
153 let device_limit = self.ctx.device.recommendedMaxWorkingSetSize() as usize;
154 let limit = self.ctx.allocation_limit.get().unwrap_or(device_limit);
155 let mut used = self.ctx.device.currentAllocatedSize();
156
157 if let Some(pf) = &self.pf {
159 used = used.saturating_sub(pf.allocated_bytes);
160 }
161
162 rows_fit(
163 &self.p.cfg,
164 self.max_t,
165 limit.saturating_sub(used),
166 all_logits,
167 )
168 }
169}
170
171#[cfg(test)]
172#[path = "../../../../tests/unit/qwen4_exp/gpu/prefill_allocation.rs"]
173mod tests;