1use super::cpu::CpuModel;
18use super::packed::Packed;
19use crate::kernels::{BATCH_MSL, FORWARD_MSL};
20use crate::metal::MetalContext;
21use crate::options::{Options, PoolBudget};
22use crate::units::BYTES_PER_GB;
23use anyhow::{Context, Result, ensure};
24use objc2::rc::Retained;
25use objc2::runtime::ProtocolObject;
26use objc2_metal::{
27 MTLBuffer, MTLCommandBuffer, MTLCommandEncoder, MTLCommandQueue, MTLComputeCommandEncoder,
28 MTLComputePipelineState, MTLSharedEvent, MTLSize,
29};
30use std::ffi::c_void;
31use std::ptr::NonNull;
32
33mod activity;
34mod attention;
35mod budget;
36mod decode;
37mod deltanet;
38mod dispatch;
39mod experts;
40mod hyperconnection;
41mod load;
42mod memory;
43mod mtp;
44mod params;
45mod phases;
46mod ple;
47mod sampling;
48mod state;
49
50pub use activity::layers::{PhaseStats, PredictionStats, QuantStats};
51pub use activity::reads::ReadStats;
52pub use activity::{ExpertActivity, ExpertCounters, LayerStats};
53pub use memory::MemoryStats;
54pub use phases::GpuTiming;
55
56use params::*;
57
58pub mod prefill;
59mod residency;
60mod streaming;
61
62use streaming::PendingRead;
63
64const ARGMAX_TGS: usize = 1024;
65const ATTN_TB: usize = 512;
66const ATTN_MAX_WG: usize = 256;
67pub const MAX_NB: usize = 4;
69pub const MAX_SNAP: usize = 3;
71const SLOT_STRIDE: usize = 64;
73const IDS_IN: usize = 0;
75const IDS_OUT: usize = 8;
76const IDS_MTP_IN: usize = 16;
77const IDS_MTP_OUT: usize = 24;
78
79type Buf = Retained<ProtocolObject<dyn MTLBuffer>>;
80type Pso = Retained<ProtocolObject<dyn MTLComputePipelineState>>;
81type Enc = ProtocolObject<dyn MTLComputeCommandEncoder>;
82
83pub type RouteStep = (Vec<u32>, Vec<Vec<u32>>, Vec<Vec<f32>>, Vec<Vec<u32>>);
86
87#[derive(Clone, Copy)]
89struct Q {
90 w: usize,
91 s: usize,
92 b: usize,
93 out: u32,
94 inp: u32,
95}
96
97impl Q {
98 fn at_offset(self, bytes: usize) -> Self {
100 Self {
101 w: self.w + bytes,
102 s: self.s + bytes,
103 b: self.b + bytes,
104 ..self
105 }
106 }
107}
108
109#[derive(Clone, Copy)]
111struct T(usize);
112
113struct Hc {
114 norm: T,
115 down: Q,
116 up: Q,
117 inject: Option<Q>,
118}
119
120struct Attn {
121 q: Q,
122 k: Q,
123 v: Q,
124 o: Q,
125 qn: T,
126 kn: T,
127 kc: Buf,
128 vc: Buf,
129 iqk: Q,
132 iqn: T,
133 ikn: T,
134 ikc: Buf,
135 blk: Buf,
136}
137
138struct Delta {
139 qkv: Q,
140 z: Q,
141 a: Q,
142 b: Q,
143 conv: T,
144 a_log: T,
145 dt_bias: T,
146 norm: T,
147 o: Q,
148 state: Buf,
149 hist: Buf,
150 mid: Buf,
152 mid_hist: Buf,
153}
154
155enum Mix {
156 Attn(Attn),
157 Delta(Delta),
158}
159
160struct Moe {
161 router: T,
162 shared_gate: T,
163 sg: Q,
164 su: Q,
165 sd: Q,
166 record_layer: usize,
167}
168
169struct Ple {
170 key: Q,
171 value: Q,
172 norm_key: T,
173 norm_query: T,
174 norm_conv: T,
175 conv: T,
176 kernel: u32,
177 dilation: u32,
178 span: u32,
179 hist: Buf,
180 e: Buf,
182 multipliers: Vec<i64>,
183 head_offsets: Vec<u64>,
184 head_sizes: Vec<u64>,
185}
186
187struct GLayer {
188 attn_hc: Hc,
189 mlp_hc: Hc,
190 mix: Mix,
191 moe: Moe,
192 ple: Option<Ple>,
193}
194
195struct Mtp {
197 enorm: T,
198 hnorm: T,
199 fc_e: Q,
200 fc_h: Q,
201 layer: GLayer,
202 mixer: Hc,
203}
204
205struct Pipes {
206 qmv_h: Pso,
208 qmv_hn: Pso,
209 prep_h: Pso,
210 embed_rows: Pso,
211 qk_norm_rope_b: Pso,
212 conv_b: Pso,
213 delta_norms: Pso,
214 delta_gates: Pso,
215 delta_scan2: Pso,
216 kv_append_q8: Pso,
217 attn_part2_q8: Pso,
218 attn_combine: Pso,
219 argmax_partial: Pso,
220 argmax_final: Pso,
221 add: Pso,
222 copy_f32: Pso,
223 zero: Pso,
225 replicate_b: Pso,
226 group_norm_b: Pso,
227 norm_prep_b: Pso,
228 qmv_silu_b: [Pso; MAX_NB],
229 hc_mix_b: Pso,
230 inject_b: Pso,
231 bf16_matvec_b: Pso,
232 topk_softmax_b: Pso,
233 moe_gate_up_b: [Pso; MAX_NB],
234 moe_act_b: Pso,
235 moe_down_b: [Pso; MAX_NB],
236 moe_combine_b: Pso,
237 ple_gate_b: Pso,
238 ple_conv_b: Pso,
239 mtp_fold: Pso,
240 gate_norm_sigmoid_b: Pso,
241 index_append: Pso,
242 index_blocks: Pso,
243 index_q: Pso,
244 index_score: Pso,
245 index_select: Pso,
246 attn_sel: Pso,
247 qmm_n8: Pso,
249 qmm_n16: Pso,
250 qmm_w: Pso,
251 expert_qmm: [[Pso; 3]; 2],
253 silu_mul: Pso,
254 attn_q_stage: Pso,
255 attn_kv_stage: Pso,
256 gemm_hh: Pso,
257 attn_o_scatter: Pso,
258 softmax_sel: Pso,
259 silu_rows: Pso,
260 gather_rows: Pso,
261 scatter_add_rows: Pso,
262 shared_add_rows: Pso,
263 qmv_small_b: Pso,
264}
265
266#[derive(Clone, Copy, serde::Serialize)]
268pub struct LaEntry {
269 pub step: u64,
270 pub layer: usize,
272 pub expert: u32,
273 pub weight: f32,
275 pub rank: u32,
276 pub resident: bool,
278 pub hit: bool,
280}
281
282struct HalfSet {
285 xe: Buf,
286 xo: Buf,
287 xsum: Buf,
288}
289
290struct HcBufs {
292 normed: Buf,
293 d: Buf,
294 u: Buf,
295 mixed: Buf,
296 inj: Buf,
297 h1: HalfSet,
298 h2: HalfSet,
299}
300
301struct Scratch {
302 ids: Buf,
303 e: Buf,
304 hyper: Buf,
305 hc: HcBufs,
306 la: HcBufs,
307 mix_out: Buf,
308 qg: Buf,
309 k: Buf,
310 v: Buf,
311 attn_out: Buf,
312 attn_parts: Buf,
313 qkv: Buf,
314 z: Buf,
315 a: Buf,
316 b: Buf,
317 kqn: Buf,
318 gbuf: Buf,
319 delta_y: Buf,
320 router: Buf,
321 topk_idx: Buf,
322 topk_w: Buf,
323 la_router: Buf,
324 la_idx: Buf,
325 la_w: Buf,
326 gate_e: Buf,
327 hx: HalfSet,
328 y_e: Buf,
329 moe_out: Buf,
330 ple_key: Buf,
331 ple_keyn: Buf,
332 ple_value: Buf,
333 ple_query: Buf,
334 ple_gated: Buf,
335 ple_gvn: Buf,
336 ple_out: Buf,
337 logits: Buf,
338 mtp_logits: Buf,
339 mtp_logits2: Buf,
342 partials: Buf,
343 mtp_hyper: Buf,
344 fe: Buf,
345 fh: Buf,
346 iqk: Buf,
349 iq: Buf,
350 bscore: Buf,
351 vis: Buf,
352 nvis: Buf,
353 vmask: Buf,
354}
355
356fn set_bytes<T>(enc: &Enc, index: usize, value: &T) {
357 unsafe {
358 enc.setBytes_length_atIndex(
359 NonNull::from(value).cast::<c_void>(),
360 std::mem::size_of::<T>(),
361 index,
362 )
363 };
364}
365
366fn kv_q8_side(max_t: usize, kv_row: usize) -> (usize, usize) {
367 let align = |v: usize| v.div_ceil(16384) * 16384;
368 let qs = align(max_t * kv_row);
369 let sc = align(max_t * kv_row / 32 * 2);
370
371 (qs, qs + sc)
372}
373
374fn layer_cap() -> usize {
377 static CAP: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
378
379 *CAP.get_or_init(|| {
380 std::env::var("CHERENKOV_LAYERS")
381 .ok()
382 .and_then(|v| v.parse().ok())
383 .unwrap_or(usize::MAX)
384 })
385}
386
387fn union_of(ids: &[u32]) -> Vec<u32> {
389 let mut u: Vec<u32> = Vec::with_capacity(ids.len());
390
391 for &e in ids {
392 if !u.contains(&e) {
393 u.push(e);
394 }
395 }
396
397 u
398}
399
400pub struct Gpu<'a> {
401 pub p: &'a Packed,
402 ctx: MetalContext,
403 pipes: Pipes,
404 dense: Buf,
405 res: residency::Pool,
409 activity: ExpertActivity,
410 read_tracker: activity::reads::ReadTracker,
411 activity_started: std::time::Instant,
412 phase_timer: Option<phases::PhaseTimer>,
413 pool_file: std::fs::File,
414 pool_file_nocache: std::fs::File,
415 ngram_file: std::fs::File,
417 ngram_prefetch: std::cell::RefCell<Option<std::thread::JoinHandle<()>>>,
418 step_no: u64,
419 pending: Option<PendingRead>,
422 low_bit_store: Option<super::lowbit::Layout>,
426 pub all_low_bits: bool,
430 event: Retained<ProtocolObject<dyn objc2_metal::MTLSharedEvent>>,
433 event_base: u64,
434 event_cpu: Retained<ProtocolObject<dyn objc2_metal::MTLSharedEvent>>,
437 event_res: Retained<ProtocolObject<dyn objc2_metal::MTLSharedEvent>>,
439 event_cpu_base: u64,
440 slot_tab: Buf,
445 wmap: Buf,
446 layers: Vec<GLayer>,
447 mtp: Option<Mtp>,
448 embed: Q,
449 final_mixer: Hc,
450 lm_head: Q,
451 scratch: Scratch,
452 pub max_t: usize,
453 pub pos: usize,
455 tokens: Vec<u32>,
457 batch_pos: usize,
459 batch_nb: usize,
460 batch_snap: bool,
461 mtp_len: usize,
463 pub last_experts: Vec<Vec<u32>>,
465 pub expert_history: Vec<Vec<Vec<u32>>>,
467 pub route_history: Vec<RouteStep>,
470 dump_states: bool,
474 pub state_history: Vec<Vec<(Vec<f32>, Vec<u32>)>>,
475 last_states: Vec<(Vec<f32>, Vec<u32>)>,
476 last_routes: Vec<Vec<u32>>,
477 last_route_w: Vec<Vec<f32>>,
478 last_miss: Vec<Vec<u32>>,
480 pub ngram_gather_s: std::cell::Cell<f64>,
483 pub ngram_ms: Vec<f64>,
484 pub step_ms: Vec<f64>,
485 pub gpu_ms: Vec<f64>,
487 pub io_ms: Vec<f64>,
489 pub misses: Vec<usize>,
491 pub miss_bytes: Vec<usize>,
492 step_misses: usize,
493 step_miss_bytes: usize,
494 step_set_s: f64,
497 step_read_s: f64,
498 folded_mtp: Option<Vec<u32>>,
501 step_warm: usize,
504 pub warm: Vec<usize>,
505 pub set_ms: Vec<f64>,
506 pub read_ms: Vec<f64>,
507 pub lookahead_hit: Vec<f64>,
511 pub lookahead_issued: Vec<usize>,
512 pub la_log: Vec<LaEntry>,
515 la_pending: Vec<LaEntry>,
516 log_la: bool,
517 cut_w: f32,
523 step_cut: usize,
524 pub cut: Vec<usize>,
525 inflight: Vec<(residency::Landed, usize)>,
526 lookahead: bool,
527 spin_wait: bool,
529 fake_experts: bool,
533 skip: Vec<String>,
534 pub dispatches: Vec<usize>,
537 pub gpu_idle_ms: Vec<f64>,
539 pub rows: Vec<usize>,
541 pub mtp_ms: Vec<f64>,
542 dispatch_count: std::cell::Cell<usize>,
543 pf: Option<prefill::PrefillScratch>,
547 ring: Buf,
548 prefill_reserved_bytes: usize,
552 pub prefill_stats: Vec<prefill::ChunkStats>,
553}
554
555#[cfg_attr(test, derive(PartialEq))]
557pub(crate) struct PrefixState {
558 pos: usize,
559 mtp_len: usize,
560 tokens: Vec<u32>,
561 data: Vec<Vec<u8>>,
562}
563
564impl<'a> Gpu<'a> {
565 pub fn has_mtp(&self) -> bool {
566 self.mtp.is_some()
567 }
568
569 fn skips(&self, stage: &str) -> bool {
570 self.skip.iter().any(|s| s == stage)
571 }
572
573 fn record_id(&self, record_layer: usize, expert: u32) -> usize {
574 record_layer * self.p.manifest.experts.experts + expert as usize
575 }
576
577 pub fn pool_resident(&self) -> usize {
579 self.res.resident()
580 }
581
582 pub fn pool_slots(&self) -> usize {
583 self.res.budget()
584 }
585
586 pub fn throttle_ms(&self) -> Result<f64> {
588 self.ctx.throttle_probe()
589 }
590
591 pub fn logits_row(&self, r: usize) -> &[f32] {
593 let v = self.p.cfg.vocab_size;
594 let ptr = self.scratch.logits.contents().cast::<f32>();
595
596 unsafe { std::slice::from_raw_parts(ptr.as_ptr().add(r * v), v) }
597 }
598
599 pub fn logits(&self) -> &[f32] {
600 self.logits_row(0)
601 }
602
603 pub(crate) fn last_logits_row(&self) -> usize {
605 self.batch_nb.saturating_sub(1)
606 }
607
608 pub fn mtp_logits_row(&self, r: usize) -> &[f32] {
610 let v = self.p.cfg.vocab_size;
611 let ptr = self.scratch.mtp_logits.contents().cast::<f32>();
612
613 unsafe { std::slice::from_raw_parts(ptr.as_ptr().add(r * v), v) }
614 }
615
616 pub fn allocated_gb(&self) -> f64 {
617 self.allocated_bytes() as f64 / BYTES_PER_GB as f64
618 }
619
620 pub fn pool_bytes(&self) -> usize {
622 self.res.bytes()
623 }
624
625 fn kv_layer_bytes(&self, n: usize) -> usize {
629 let c = &self.p.cfg;
630 let kv_row = c.num_key_value_heads * c.head_dim;
631 let ihd = c.indexer_head_dim;
632 let ratio = c.indexer_compress_ratio.max(1);
633
634 2 * (n * kv_row + n * kv_row / 32 * 2) + n * ihd * 4 + n.div_ceil(ratio) * ihd * 4
635 }
636
637 fn trunk_attn_layers(&self) -> usize {
639 self.layers
640 .iter()
641 .filter(|l| matches!(l.mix, Mix::Attn(_)))
642 .count()
643 }
644
645 fn mtp_attn_layers(&self) -> usize {
647 usize::from(
648 self.mtp
649 .as_ref()
650 .is_some_and(|m| matches!(m.layer.mix, Mix::Attn(_))),
651 )
652 }
653
654 pub fn kv_cache_bytes(&self) -> usize {
657 self.trunk_attn_layers() * self.kv_layer_bytes(self.pos)
658 + self.mtp_attn_layers() * self.kv_layer_bytes(self.mtp_len)
659 }
660
661 pub fn kv_cache_capacity(&self) -> usize {
663 (self.trunk_attn_layers() + self.mtp_attn_layers()) * self.kv_layer_bytes(self.max_t)
664 }
665
666 pub fn kv_cache_fullness(&self) -> f64 {
668 let cap = self.kv_cache_capacity();
669
670 self.kv_cache_bytes() as f64 / cap.max(1) as f64
671 }
672
673 pub fn context_fullness(&self) -> f64 {
675 self.pos as f64 / self.max_t.max(1) as f64
676 }
677
678 pub fn working_set_limit_gb(&self) -> f64 {
679 use objc2_metal::MTLDevice as _;
680
681 self.ctx.device.recommendedMaxWorkingSetSize() as f64 / BYTES_PER_GB as f64
682 }
683}
684
685#[cfg(test)]
686#[path = "../../tests/unit/qwen4_exp/gpu/mod.rs"]
687mod tests;