1use anyhow::Result;
16use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
17
18use super::quant_weights::QuantWeights;
19
20mod full_attention;
21mod linear_attention;
22
23pub use full_attention::forward_full_attention;
24pub use linear_attention::forward_linear_attention;
25
26#[derive(Debug, Clone, Copy)]
28pub struct Qwen35ForwardConfig {
29 pub hidden: u32,
30 pub intermediate: u32,
31 pub num_layers: u32,
32 pub vocab: u32,
33 pub group_size: u32,
34 pub rms_eps: f32,
35
36 pub num_heads: u32,
37 pub num_kv_heads: u32,
38 pub head_dim: u32,
39 pub rope_theta: f32,
40 pub rotary_dim: u32,
43
44 pub num_k_heads_lin: u32,
45 pub num_v_heads_lin: u32,
46 pub k_head_dim_lin: u32,
47 pub v_head_dim_lin: u32,
48 pub conv_kernel_size: u32,
49}
50
51impl Qwen35ForwardConfig {
52 pub const fn qwen3_5_4b_mlx_int8() -> Self {
55 Self {
56 hidden: 2560,
57 intermediate: 9216,
58 num_layers: 32,
59 vocab: 248_320,
60 group_size: 64,
61 rms_eps: 1e-6,
62 num_heads: 16,
63 num_kv_heads: 4,
64 head_dim: 256,
65 rope_theta: 10_000_000.0,
66 rotary_dim: 64,
67 num_k_heads_lin: 16,
68 num_v_heads_lin: 32,
69 k_head_dim_lin: 128,
70 v_head_dim_lin: 128,
71 conv_kernel_size: 4,
72 }
73 }
74
75 #[inline]
79 pub const fn q_total(&self) -> u32 {
80 self.num_heads * self.head_dim * 2
81 }
82 #[inline]
84 pub const fn q_only(&self) -> u32 {
85 self.num_heads * self.head_dim
86 }
87 #[inline]
88 pub const fn kv_dim(&self) -> u32 {
89 self.num_kv_heads * self.head_dim
90 }
91 #[inline]
92 pub const fn z_dim_lin(&self) -> u32 {
93 self.num_v_heads_lin * self.v_head_dim_lin
94 }
95 #[inline]
96 pub const fn qkv_total_lin(&self) -> u32 {
97 2 * self.num_k_heads_lin * self.k_head_dim_lin + self.num_v_heads_lin * self.v_head_dim_lin
98 }
99 #[inline]
102 pub const fn num_state_heads(&self) -> u32 {
103 self.num_v_heads_lin
104 }
105}
106
107pub struct Qwen35Kernels {
110 pub rms: KernelHandle,
111 pub rope: KernelHandle,
112 pub kvap: KernelHandle,
113 pub attn: KernelHandle,
114 pub sg: KernelHandle,
115 pub add_rms: KernelHandle,
116 pub qkv_split: KernelHandle,
117 pub conv1d: KernelHandle,
118 pub gdn_gate: KernelHandle,
119 pub sigmoid: KernelHandle,
120 pub gdn_dec: KernelHandle,
121 pub kvap_turbo8: KernelHandle,
125 pub attn_turbo8: KernelHandle,
126 pub kvap_turbo4: KernelHandle,
127 pub attn_turbo4: KernelHandle,
128 pub kvap_turbo3: KernelHandle,
129 pub attn_turbo3: KernelHandle,
130 pub kvap_turbo2: KernelHandle,
131 pub attn_turbo2: KernelHandle,
132 pub kvap_bf16k_turbo4v: KernelHandle,
133 pub attn_bf16k_turbo4v: KernelHandle,
134 pub kvap_bf16k_turbo3v: KernelHandle,
135 pub attn_bf16k_turbo3v: KernelHandle,
136 pub kvap_bf16k_turbo2v: KernelHandle,
137 pub attn_bf16k_turbo2v: KernelHandle,
138 pub wht: KernelHandle,
139 pub wht_inv: KernelHandle,
140}
141
142impl Qwen35Kernels {
143 pub fn resolve(gpu: &dyn GpuBackend) -> Result<Self> {
146 Ok(Self {
147 rms: gpu.kernel("rms_norm", "rms_norm")?,
148 rope: gpu.kernel("rope_apply", "rope_apply")?,
149 kvap: gpu.kernel("kv_cache_append", "kv_cache_append")?,
150 attn: gpu.kernel("attention_decode", "attention_decode")?,
151 sg: gpu.kernel("sigmoid_gate", "sigmoid_gate")?,
152 add_rms: gpu.kernel("add_rms_norm", "add_rms_norm")?,
153 qkv_split: gpu.kernel("qwen35_qkv_split", "qwen35_qkv_split")?,
154 conv1d: gpu.kernel("causal_conv1d_update_l2norm", "causal_conv1d_update_l2norm")?,
155 gdn_gate: gpu.kernel("gdn_helpers", "gdn_compute_gate")?,
156 sigmoid: gpu.kernel("gdn_helpers", "sigmoid_bf16_to_f32")?,
157 gdn_dec: gpu.kernel("gated_delta_rule_decode", "gated_delta_rule_decode")?,
158 kvap_turbo8: gpu.kernel("kv_cache_append_turbo8", "kv_cache_append_turbo8")?,
159 attn_turbo8: gpu.kernel("attention_decode_turbo8", "attention_decode_turbo8")?,
160 kvap_turbo4: gpu.kernel("kv_cache_append_turbo4", "kv_cache_append_turbo4")?,
161 attn_turbo4: gpu.kernel("attention_decode_turbo4", "attention_decode_turbo4")?,
162 kvap_turbo3: gpu.kernel("kv_cache_append_turbo3", "kv_cache_append_turbo3")?,
163 attn_turbo3: gpu.kernel("attention_decode_turbo3", "attention_decode_turbo3")?,
164 kvap_turbo2: gpu.kernel("kv_cache_append_turbo2", "kv_cache_append_turbo2")?,
165 attn_turbo2: gpu.kernel("attention_decode_turbo2", "attention_decode_turbo2")?,
166 kvap_bf16k_turbo4v: gpu.kernel(
167 "kv_cache_append_bf16k_turbov",
168 "kv_cache_append_bf16k_turbo4v",
169 )?,
170 attn_bf16k_turbo4v: gpu.kernel(
171 "attention_decode_bf16k_turbov",
172 "attention_decode_bf16k_turbo4v",
173 )?,
174 kvap_bf16k_turbo3v: gpu.kernel(
175 "kv_cache_append_bf16k_turbov",
176 "kv_cache_append_bf16k_turbo3v",
177 )?,
178 attn_bf16k_turbo3v: gpu.kernel(
179 "attention_decode_bf16k_turbov",
180 "attention_decode_bf16k_turbo3v",
181 )?,
182 kvap_bf16k_turbo2v: gpu.kernel(
183 "kv_cache_append_bf16k_turbov",
184 "kv_cache_append_bf16k_turbo2v",
185 )?,
186 attn_bf16k_turbo2v: gpu.kernel(
187 "attention_decode_bf16k_turbov",
188 "attention_decode_bf16k_turbo2v",
189 )?,
190 wht: gpu.kernel("wht_bf16", "wht_bf16_inplace")?,
191 wht_inv: gpu.kernel("wht_bf16", "wht_bf16_inplace_inv")?,
192 })
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub enum MetalKvDtype {
201 Bf16,
203 Turbo8,
206 Turbo4,
210 Turbo3,
213 Turbo2,
216 Bf16KTurbo4V,
218 Bf16KTurbo3V,
220 Bf16KTurbo2V,
222}
223
224impl MetalKvDtype {
225 pub fn k_is_rotated(self) -> bool {
228 matches!(
229 self,
230 Self::Turbo8 | Self::Turbo4 | Self::Turbo3 | Self::Turbo2
231 )
232 }
233 pub fn v_is_rotated(self) -> bool {
237 self != Self::Bf16
238 }
239}
240
241impl std::str::FromStr for MetalKvDtype {
242 type Err = anyhow::Error;
243 fn from_str(s: &str) -> Result<Self> {
244 match s {
245 "bf16" => Ok(Self::Bf16),
246 "turbo8" => Ok(Self::Turbo8),
247 "turbo4" => Ok(Self::Turbo4),
248 "turbo3" => Ok(Self::Turbo3),
249 "turbo2" => Ok(Self::Turbo2),
250 "bf16k_turbo4v" => Ok(Self::Bf16KTurbo4V),
251 "bf16k_turbo3v" => Ok(Self::Bf16KTurbo3V),
252 "bf16k_turbo2v" => Ok(Self::Bf16KTurbo2V),
253 other => {
254 anyhow::bail!(
255 "kv dtype {other:?} not supported on metal (bf16 | turbo8 | turbo4 | turbo3 | turbo2 | bf16k_turbo4v/3v/2v)"
256 )
257 }
258 }
259 }
260}
261
262pub struct LayerKvCache {
268 pub k: DevicePtr,
269 pub v: DevicePtr,
270 #[allow(dead_code)]
272 pub capacity: u32,
273 pub dtype: MetalKvDtype,
274 pub k_scales: Option<DevicePtr>,
278 pub v_scales: Option<DevicePtr>,
279}
280
281impl LayerKvCache {
282 pub fn alloc(
286 gpu: &dyn GpuBackend,
287 dtype: MetalKvDtype,
288 max_seq: u32,
289 kv_dim: u32,
290 ) -> Result<Self> {
291 assert!(
292 dtype == MetalKvDtype::Bf16 || kv_dim.is_multiple_of(16),
293 "turbo dtypes need KV_DIM divisible by 16"
294 );
295 let n = (max_seq * kv_dim) as usize;
296 let scale_bytes_e4m3 = (max_seq * kv_dim / 16) as usize;
297 let (kb, vb, ksb, vsb) = match dtype {
299 MetalKvDtype::Bf16 => (n * 2, n * 2, 0, 0),
300 MetalKvDtype::Turbo8 => (n, n, scale_bytes_e4m3 * 2, scale_bytes_e4m3 * 2),
302 MetalKvDtype::Turbo4 => (n / 2, n / 2, scale_bytes_e4m3, scale_bytes_e4m3),
303 MetalKvDtype::Turbo3 => (n * 3 / 8, n * 3 / 8, scale_bytes_e4m3, scale_bytes_e4m3),
304 MetalKvDtype::Turbo2 => (n / 4, n / 4, scale_bytes_e4m3, scale_bytes_e4m3),
305 MetalKvDtype::Bf16KTurbo4V => (n * 2, n / 2, 0, scale_bytes_e4m3),
306 MetalKvDtype::Bf16KTurbo3V => (n * 2, n * 3 / 8, 0, scale_bytes_e4m3),
307 MetalKvDtype::Bf16KTurbo2V => (n * 2, n / 4, 0, scale_bytes_e4m3),
308 };
309 let alloc_opt = |bytes: usize| -> Result<Option<DevicePtr>> {
310 Ok(if bytes > 0 {
311 Some(gpu.alloc(bytes)?)
312 } else {
313 None
314 })
315 };
316 Ok(Self {
317 k: gpu.alloc(kb)?,
318 v: gpu.alloc(vb)?,
319 capacity: max_seq,
320 dtype,
321 k_scales: alloc_opt(ksb)?,
322 v_scales: alloc_opt(vsb)?,
323 })
324 }
325}
326
327pub struct FullAttentionLayer<'a, Q: QuantWeights> {
328 pub input_ln: DevicePtr,
329 pub q_norm: DevicePtr,
330 pub k_norm: DevicePtr,
331 pub post_ln: DevicePtr,
332 pub q_proj: &'a Q,
333 pub k_proj: &'a Q,
334 pub v_proj: &'a Q,
335 pub o_proj: &'a Q,
336 pub gate_proj: &'a Q,
337 pub up_proj: &'a Q,
338 pub down_proj: &'a Q,
339}
340
341pub struct FullAttentionScratch {
343 pub x_norm: DevicePtr,
344 pub q_full: DevicePtr,
345 pub q_split: DevicePtr,
346 pub gate_split: DevicePtr,
347 pub k: DevicePtr,
348 pub v: DevicePtr,
349 pub q_norm_out: DevicePtr,
350 pub k_norm_out: DevicePtr,
351 pub attn_out: DevicePtr,
352 pub gated_attn: DevicePtr,
353 pub o: DevicePtr,
354 pub x_resid: DevicePtr,
355 pub x_norm2: DevicePtr,
356 pub gate_act: DevicePtr,
357 pub up_act: DevicePtr,
358 pub x_out: DevicePtr,
359}
360
361pub struct LinearAttentionLayer<'a, Q: QuantWeights> {
362 pub input_ln: DevicePtr,
363 pub a_log: DevicePtr,
365 pub dt_bias: DevicePtr,
367 pub conv1d_weight: DevicePtr,
369 pub in_proj_a: &'a Q,
370 pub in_proj_b: &'a Q,
371 pub in_proj_qkv: &'a Q,
372 pub in_proj_z: &'a Q,
373 pub norm_weight: DevicePtr,
375 pub out_proj: &'a Q,
376 pub post_ln: DevicePtr,
377 pub gate_proj: &'a Q,
378 pub up_proj: &'a Q,
379 pub down_proj: &'a Q,
380}
381
382pub struct LinearAttentionState {
385 pub conv1d_state: DevicePtr,
387 pub gdn_state: DevicePtr,
389}
390
391pub struct LinearAttentionScratch {
393 pub x_norm: DevicePtr,
394 pub dt_raw: DevicePtr,
395 pub b_raw: DevicePtr,
396 pub qkv: DevicePtr,
397 pub qkv_smooth: DevicePtr,
398 pub z: DevicePtr,
399 pub gate: DevicePtr,
401 pub beta: DevicePtr,
403 pub y: DevicePtr,
404 pub y_norm: DevicePtr,
405 pub out: DevicePtr,
406 pub x_resid: DevicePtr,
407 pub x_norm2: DevicePtr,
408 pub gate_act: DevicePtr,
409 pub up_act: DevicePtr,
410 pub x_final: DevicePtr,
411}