1use super::*;
4use crate::units::BYTES_PER_GB;
5use objc2_metal::MTLDevice;
6
7impl<'a> Gpu<'a> {
8 pub fn load(p: &'a Packed, max_t: usize, options: &Options) -> Result<Self> {
9 Self::load_bounded(p, max_t, options, 0, None, prefill::MAX_PREFILL_ROWS)
10 }
11
12 pub(crate) fn load_bounded(
13 p: &'a Packed,
14 max_t: usize,
15 options: &Options,
16 reserve: usize,
17 memory_bytes: Option<usize>,
18 prefill_rows: usize,
19 ) -> Result<Self> {
20 options.validate()?;
21 p.manifest.experts.validate_dimensions()?;
22
23 let c = &p.cfg;
24
25 anyhow::ensure!(
26 c.hidden_size.is_multiple_of(256),
27 "fn_norm_prep_b needs hidden % 256 == 0"
28 );
29 anyhow::ensure!(
30 c.indexer_head_dim <= 256 && c.indexer_head_dim.is_multiple_of(32),
31 "indexer head dim must be a multiple of 32 up to 256"
32 );
33 anyhow::ensure!(
34 c.indexer_n_heads * c.indexer_head_dim <= 1024,
35 "indexer query heads too wide for fn_index_score"
36 );
37 anyhow::ensure!(
38 c.indexer_budget.is_multiple_of(c.indexer_compress_ratio),
39 "indexer budget must be a multiple of the block size"
40 );
41 anyhow::ensure!(
42 c.moe_intermediate_size.is_multiple_of(64),
43 "expert width must be a multiple of the 64-code quantization group"
44 );
45 anyhow::ensure!(
46 c.num_experts_per_tok * MAX_NB < SLOT_STRIDE,
47 "slot table too narrow"
48 );
49 anyhow::ensure!(
50 c.output_gate_type == "sigmoid",
51 "only the sigmoid DeltaNet output gate is implemented"
52 );
53
54 let ctx = MetalContext::new()?;
55 let allocation_limit = memory_bytes
56 .map(|bytes| {
57 bytes
58 .checked_sub(reserve)
59 .context("cache exceeds memory budget")
60 })
61 .transpose()?;
62
63 ctx.allocation_limit.set(allocation_limit);
64
65 let lib = ctx.compile_library(FORWARD_MSL)?;
66 let blib = ctx.compile_library(BATCH_MSL)?;
67 let per_nb = |base: &str| -> Result<[Pso; MAX_NB]> {
68 Ok([
69 ctx.pipeline(&blib, &format!("{base}1"))?,
70 ctx.pipeline(&blib, &format!("{base}2"))?,
71 ctx.pipeline(&blib, &format!("{base}3"))?,
72 ctx.pipeline(&blib, &format!("{base}4"))?,
73 ])
74 };
75 let expert_qmm = |bits| -> Result<[Pso; 3]> {
76 Ok([
77 ctx.pipeline(&blib, &format!("fn_expert_qmm_q{bits}_n8"))?,
78 ctx.pipeline(&blib, &format!("fn_expert_qmm_q{bits}_n16"))?,
79 ctx.pipeline(&blib, &format!("fn_expert_qmm_q{bits}_n32"))?,
80 ])
81 };
82 let pipes = Pipes {
83 expert_qmm: [expert_qmm(2)?, expert_qmm(3)?],
84 qmv_h: ctx.pipeline(&lib, "qmv_multi_h")?,
85 qmv_hn: ctx.pipeline(&lib, "qmv_multi_hn")?,
86 prep_h: ctx.pipeline(&lib, "deinterleave_bh")?,
87 embed_rows: ctx.pipeline(&lib, "embed_rows")?,
88 qk_norm_rope_b: ctx.pipeline(&lib, "qk_norm_rope_b")?,
89 conv_b: ctx.pipeline(&lib, "conv_b")?,
90 delta_norms: ctx.pipeline(&lib, "delta_norms")?,
91 delta_gates: ctx.pipeline(&lib, "delta_gates")?,
92 delta_scan2: ctx.pipeline(&lib, "delta_scan2")?,
93 kv_append_q8: ctx.pipeline(&lib, "kv_append_q8")?,
94 attn_part2_q8: ctx.pipeline(&lib, "attn_part2_q8")?,
95 attn_combine: ctx.pipeline(&lib, "attn_combine")?,
96 argmax_partial: ctx.pipeline(&lib, "argmax_partial")?,
97 argmax_final: ctx.pipeline(&lib, "argmax_final")?,
98 add: ctx.pipeline(&lib, "add_inplace")?,
99 copy_f32: ctx.pipeline(&lib, "copy_f32")?,
100 zero: ctx.pipeline(&blib, "fn_zero")?,
101 replicate_b: ctx.pipeline(&blib, "fn_replicate_b")?,
102 group_norm_b: ctx.pipeline(&blib, "fn_group_norm_b")?,
103 norm_prep_b: ctx.pipeline(&blib, "fn_norm_prep_b")?,
104 qmv_silu_b: per_nb("fn_qmv_silu_b")?,
105 hc_mix_b: ctx.pipeline(&blib, "fn_hc_mix_b")?,
106 inject_b: ctx.pipeline(&blib, "fn_inject_b")?,
107 bf16_matvec_b: ctx.pipeline(&blib, "fn_bf16_matvec_b")?,
108 topk_softmax_b: ctx.pipeline(&blib, "fn_topk_softmax_b")?,
109 moe_gate_up_b: per_nb("fn_moe_gate_up_b")?,
110 moe_act_b: ctx.pipeline(&blib, "fn_moe_act_b")?,
111 moe_down_b: per_nb("fn_moe_down_b")?,
112 moe_combine_b: ctx.pipeline(&blib, "fn_moe_combine_b")?,
113 ple_gate_b: ctx.pipeline(&blib, "fn_ple_gate_b")?,
114 ple_conv_b: ctx.pipeline(&blib, "fn_ple_conv_b")?,
115 mtp_fold: ctx.pipeline(&blib, "fn_mtp_fold")?,
116 gate_norm_sigmoid_b: ctx.pipeline(&blib, "fn_delta_gate_norm_sigmoid_b")?,
117 index_append: ctx.pipeline(&blib, "fn_index_append")?,
118 index_blocks: ctx.pipeline(&blib, "fn_index_blocks")?,
119 index_q: ctx.pipeline(&blib, "fn_index_q")?,
120 index_score: ctx.pipeline(&blib, "fn_index_score")?,
121 index_select: ctx.pipeline(&blib, "fn_index_select")?,
122 attn_sel: ctx.pipeline(&blib, "fn_attn_part2_q8_sel")?,
123 qmm_n8: ctx.pipeline(&lib, "qmm_af4_n8")?,
124 qmm_n16: ctx.pipeline(&lib, "qmm_af4_n16")?,
125 qmm_w: ctx.pipeline(&lib, "qmm_af4_w")?,
126 silu_mul: ctx.pipeline(&lib, "silu_mul")?,
127 attn_q_stage: ctx.pipeline(&lib, "attn_q_stage")?,
128 attn_kv_stage: ctx.pipeline(&lib, "attn_kv_stage")?,
129 gemm_hh: ctx.pipeline(&lib, "gemm_hh")?,
130 attn_o_scatter: ctx.pipeline(&lib, "attn_o_scatter")?,
131 softmax_sel: ctx.pipeline(&blib, "fn_attn_softmax_sel")?,
132 silu_rows: ctx.pipeline(&blib, "fn_silu_rows")?,
133 gather_rows: ctx.pipeline(&blib, "fn_gather_rows")?,
134 scatter_add_rows: ctx.pipeline(&blib, "fn_scatter_add_rows")?,
135 shared_add_rows: ctx.pipeline(&blib, "fn_shared_add_rows")?,
136 qmv_small_b: ctx.pipeline(&blib, "fn_qmv_small_b")?,
137 };
138 let dense = unsafe { ctx.wrap_mmap(&p.dense)? };
141
142 let q = |prefix: &str| -> Result<Q> {
143 let w = p.manifest.dense(&format!("{prefix}.weight"))?;
144 let s = p.manifest.dense(&format!("{prefix}.scales"))?;
145 let b = p.manifest.dense(&format!("{prefix}.biases"))?;
146
147 anyhow::ensure!(w.dtype == "U32", "{prefix}: not a Q4 weight");
148 anyhow::ensure!(w.offset % 16 == 0, "{prefix}: weight offset not 16-aligned");
149
150 Ok(Q {
151 w: w.offset as usize,
152 s: s.offset as usize,
153 b: b.offset as usize,
154 out: w.shape[0] as u32,
155 inp: (w.shape[1] * 8) as u32,
156 })
157 };
158 let t = |name: &str| -> Result<T> {
159 let e = p.manifest.dense(name)?;
160
161 anyhow::ensure!(e.dtype == "BF16", "{name}: expected BF16");
162
163 Ok(T(e.offset as usize))
164 };
165 let hc = |prefix: &str, inject: bool| -> Result<Hc> {
166 Ok(Hc {
167 norm: t(&format!("{prefix}.hc_norm.weight"))?,
168 down: q(&format!("{prefix}.input_mix_weight_down"))?,
169 up: q(&format!("{prefix}.input_mix_weight_up"))?,
170 inject: if inject {
171 Some(q(&format!("{prefix}.block_inject_weight"))?)
172 } else {
173 None
174 },
175 })
176 };
177
178 let h = c.hidden_size;
179 let hh = c.hc_hidden();
180 let kv_row = c.num_key_value_heads * c.head_dim;
181 let kv_side = kv_q8_side(max_t, kv_row).1;
182 let conv_dim = 2 * c.linear_num_key_heads * c.linear_key_head_dim
183 + c.linear_num_value_heads * c.linear_value_head_dim;
184 let v_dim = c.linear_num_value_heads * c.linear_value_head_dim;
185 let state_len = c.linear_num_value_heads * c.linear_key_head_dim * c.linear_value_head_dim;
186 let hist_len = conv_dim * (c.linear_conv_kernel_dim - 1);
187
188 let load_layer =
189 |lp: &str, linear: bool, record_layer: usize, with_ple: bool| -> Result<GLayer> {
190 let mix = if linear {
191 let d = format!("{lp}.linear_attn");
192
193 Mix::Delta(Delta {
194 qkv: q(&format!("{d}.in_proj_qkv"))?,
195 z: q(&format!("{d}.in_proj_z"))?,
196 a: q(&format!("{d}.in_proj_a"))?,
197 b: q(&format!("{d}.in_proj_b"))?,
198 conv: t(&format!("{d}.conv1d.weight"))?,
199 a_log: t(&format!("{d}.A_log"))?,
200 dt_bias: t(&format!("{d}.dt_bias"))?,
201 norm: t(&format!("{d}.norm.weight"))?,
202 o: q(&format!("{d}.out_proj"))?,
203 state: ctx.new_buffer(state_len * 4)?,
204 hist: ctx.new_buffer(hist_len * 4)?,
205 mid: ctx.new_buffer(MAX_SNAP * state_len * 4)?,
206 mid_hist: ctx.new_buffer(MAX_SNAP * hist_len * 4)?,
207 })
208 } else {
209 let a = format!("{lp}.self_attn");
210
211 Mix::Attn(Attn {
212 q: q(&format!("{a}.q_proj"))?,
213 k: q(&format!("{a}.k_proj"))?,
214 v: q(&format!("{a}.v_proj"))?,
215 o: q(&format!("{a}.o_proj"))?,
216 qn: t(&format!("{a}.q_norm.weight"))?,
217 kn: t(&format!("{a}.k_norm.weight"))?,
218 kc: ctx.new_buffer(kv_side)?,
219 vc: ctx.new_buffer(kv_side)?,
220 iqk: q(&format!("{a}.indexer.index_qk_proj"))?,
221 iqn: t(&format!("{a}.indexer.q_layernorm.weight"))?,
222 ikn: t(&format!("{a}.indexer.k_layernorm.weight"))?,
223 ikc: ctx.new_buffer(max_t * c.indexer_head_dim * 4)?,
224 blk: ctx.new_buffer(
225 (max_t / c.indexer_compress_ratio + 1) * c.indexer_head_dim * 4,
226 )?,
227 })
228 };
229 let moe = Moe {
230 router: t(&format!("{lp}.mlp.gate.weight"))?,
231 shared_gate: t(&format!("{lp}.mlp.shared_expert_gate.weight"))?,
232 sg: q(&format!("{lp}.mlp.shared_expert.gate_proj"))?,
233 su: q(&format!("{lp}.mlp.shared_expert.up_proj"))?,
234 sd: q(&format!("{lp}.mlp.shared_expert.down_proj"))?,
235 record_layer,
236 };
237 let ple = if with_ple {
238 let pp = format!("{lp}.ple");
239 let ngram = p.ngram_metadata(&format!("{pp}.ple_embedding"))?;
240 let kernel = p.shape(&format!("{pp}.conv1d.weight"))?[2] as u32;
241 let span = (kernel - 1) * c.ngram_size as u32;
242
243 Some(Ple {
244 key: q(&format!("{pp}.key_proj"))?,
245 value: q(&format!("{pp}.value_proj"))?,
246 norm_key: t(&format!("{pp}.norm_key.weight"))?,
247 norm_query: t(&format!("{pp}.norm_query.weight"))?,
248 norm_conv: t(&format!("{pp}.norm_conv.weight"))?,
249 conv: t(&format!("{pp}.conv1d.weight"))?,
250 kernel,
251 dilation: c.ngram_size as u32,
252 span,
253 hist: ctx.new_buffer(span as usize * hh * 4)?,
254 e: ctx.new_buffer(MAX_NB * c.ple_embed_dim * 4)?,
255 multipliers: ngram.multipliers,
256 head_offsets: ngram.head_offsets,
257 head_sizes: ngram.head_sizes,
258 })
259 } else {
260 None
261 };
262
263 Ok(GLayer {
264 attn_hc: hc(&format!("{lp}.attn_hyper_connection"), true)?,
265 mlp_hc: hc(&format!("{lp}.mlp_hyper_connection"), true)?,
266 mix,
267 moe,
268 ple,
269 })
270 };
271
272 let m = "language_model.model";
273 let mut layers = Vec::with_capacity(c.num_hidden_layers);
274
275 for l in 0..c.num_hidden_layers {
276 layers.push(load_layer(
277 &format!("{m}.layers.{l}"),
278 c.is_linear(l),
279 p.expert_layer(l),
280 c.ple_layer() == Some(l),
281 )?);
282 }
283
284 let mtp = if c.mtp_num_hidden_layers > 0 && options.drafts > 0 {
285 let layer = load_layer("mtp.layers.0", false, p.mtp_expert_layer(0)?, false)?;
286
287 anyhow::ensure!(
288 matches!(layer.mix, Mix::Attn(_)),
289 "MTP layer must be full attention"
290 );
291
292 Some(Mtp {
293 enorm: t("mtp.pre_fc_norm_embedding.weight")?,
294 hnorm: t("mtp.pre_fc_norm_hidden.weight")?,
295 fc_e: q("mtp.fc_embedding")?,
296 fc_h: q("mtp.fc_hidden")?,
297 layer,
298 mixer: hc("mtp.hyper_connection_mixer", false)?,
299 })
300 } else {
301 None
302 };
303
304 let max_blk = max_t.div_ceil(ATTN_TB).max(ATTN_MAX_WG);
305 let vocab = c.vocab_size;
306 let inter = c.moe_intermediate_size;
307
308 anyhow::ensure!(
309 c.shared_expert_intermediate_size == inter,
310 "shared expert width differs from routed experts"
311 );
312
313 let max_in = [
316 hh,
317 c.ple_embed_dim,
318 2 * h,
319 c.hc_lowrank,
320 v_dim,
321 c.num_attention_heads * c.head_dim,
322 inter,
323 ]
324 .into_iter()
325 .max()
326 .unwrap();
327 let half_set = |rows: usize, in_dim: usize| -> Result<HalfSet> {
328 Ok(HalfSet {
329 xe: ctx.new_buffer(rows * in_dim / 2 * 2)?,
330 xo: ctx.new_buffer(rows * in_dim / 2 * 2)?,
331 xsum: ctx.new_buffer(rows * in_dim / 32 * 4)?,
332 })
333 };
334 let hc_bufs = || -> Result<HcBufs> {
335 Ok(HcBufs {
336 normed: ctx.new_buffer(MAX_NB * hh * 4)?,
337 d: ctx.new_buffer(MAX_NB * c.hc_lowrank * 4)?,
338 u: ctx.new_buffer(MAX_NB * hh * 4)?,
339 mixed: ctx.new_buffer(MAX_NB * h * 4)?,
340 inj: ctx.new_buffer(MAX_NB * c.hc_count * 4)?,
341 h1: half_set(MAX_NB, max_in)?,
342 h2: half_set(MAX_NB, max_in)?,
343 })
344 };
345 let n_u_max = c.num_experts_per_tok * MAX_NB + 1;
346 let scratch = Scratch {
347 ids: ctx.new_buffer(64 * 4)?,
348 e: ctx.new_buffer(MAX_NB * h * 4)?,
349 hyper: ctx.new_buffer(MAX_NB * hh * 4)?,
350 hc: hc_bufs()?,
351 la: hc_bufs()?,
352 mix_out: ctx.new_buffer(MAX_NB * h * 4)?,
353 qg: ctx.new_buffer(MAX_NB * c.num_attention_heads * c.head_dim * 2 * 4)?,
354 k: ctx.new_buffer(MAX_NB * kv_row * 4)?,
355 v: ctx.new_buffer(MAX_NB * kv_row * 4)?,
356 attn_out: ctx.new_buffer(MAX_NB * c.num_attention_heads * c.head_dim * 4)?,
357 attn_parts: ctx.new_buffer(c.num_attention_heads * max_blk * (2 + c.head_dim) * 4)?,
358 qkv: ctx.new_buffer(MAX_NB * conv_dim * 4)?,
359 z: ctx.new_buffer(MAX_NB * v_dim * 4)?,
360 a: ctx.new_buffer(MAX_NB * c.linear_num_value_heads * 4)?,
361 b: ctx.new_buffer(MAX_NB * c.linear_num_value_heads * 4)?,
362 kqn: ctx.new_buffer(MAX_NB * 2 * c.linear_num_key_heads * c.linear_key_head_dim * 4)?,
363 gbuf: ctx.new_buffer(MAX_NB * c.linear_num_value_heads * 2 * 4)?,
364 delta_y: ctx.new_buffer(MAX_NB * v_dim * 4)?,
365 router: ctx.new_buffer(MAX_NB * c.num_experts * 4)?,
366 topk_idx: ctx.new_buffer(MAX_NB * c.num_experts_per_tok * 4)?,
367 topk_w: ctx.new_buffer(MAX_NB * c.num_experts_per_tok * 4)?,
368 la_router: ctx.new_buffer(MAX_NB * c.num_experts * 4)?,
369 la_idx: ctx.new_buffer(MAX_NB * 32 * 4)?,
370 la_w: ctx.new_buffer(MAX_NB * 32 * 4)?,
371 gate_e: ctx.new_buffer(n_u_max * MAX_NB * 2 * inter * 4)?,
372 hx: half_set(n_u_max * MAX_NB, inter)?,
373 y_e: ctx.new_buffer(n_u_max * MAX_NB * h * 4)?,
374 moe_out: ctx.new_buffer(MAX_NB * h * 4)?,
375 ple_key: ctx.new_buffer(MAX_NB * hh * 4)?,
376 ple_keyn: ctx.new_buffer(MAX_NB * hh * 4)?,
377 ple_value: ctx.new_buffer(MAX_NB * h * 4)?,
378 ple_query: ctx.new_buffer(MAX_NB * hh * 4)?,
379 ple_gated: ctx.new_buffer(MAX_NB * hh * 4)?,
380 ple_gvn: ctx.new_buffer(MAX_NB * hh * 4)?,
381 ple_out: ctx.new_buffer(MAX_NB * hh * 4)?,
382 logits: ctx.new_buffer(MAX_NB * vocab * 4)?,
383 mtp_logits: ctx.new_buffer(MAX_NB * vocab * 4)?,
384 mtp_logits2: ctx.new_buffer(vocab * 4)?,
385 partials: ctx.new_buffer(ARGMAX_TGS * 8)?,
386 mtp_hyper: ctx.new_buffer(MAX_NB * hh * 4)?,
387 fe: ctx.new_buffer(MAX_NB * h * 4)?,
388 fh: ctx.new_buffer(MAX_NB * hh * 4)?,
389 iqk: ctx.new_buffer(MAX_NB * (c.indexer_n_heads + 1) * c.indexer_head_dim * 4)?,
390 iq: ctx.new_buffer(MAX_NB * c.indexer_n_heads * c.indexer_head_dim * 4)?,
391 bscore: ctx.new_buffer(MAX_NB * (max_t / c.indexer_compress_ratio + 1) * 4)?,
392 vis: ctx.new_buffer(MAX_NB * (c.indexer_budget + c.indexer_compress_ratio) * 4)?,
393 nvis: ctx.new_buffer(MAX_NB * 4)?,
394 vmask: ctx
395 .new_buffer(MAX_NB * (max_t / c.indexer_compress_ratio + 1).div_ceil(32) * 4)?,
396 };
397 let n_records = p.manifest.experts.layers * p.manifest.experts.experts;
398 let stride = p.manifest.experts.record_stride as usize;
399 let n_rows = c.num_hidden_layers + 1;
400 let slot_tab = ctx.new_buffer(n_rows * SLOT_STRIDE * 8)?;
401 let wmap = ctx.new_buffer(n_rows * MAX_NB * SLOT_STRIDE * 4)?;
402 let ring = ctx.new_buffer(prefill::RING * stride)?;
403
404 let (phase_timer, gpu_timing) = super::phases::PhaseTimer::initialize(
406 &ctx,
407 p.manifest.experts.layers,
408 layer_cap() >= layers.len(),
409 );
410 let mut activity = ExpertActivity::new(&p.manifest.experts);
411 activity.gpu_timestamps_available = phase_timer.is_some();
412 activity.gpu_timing = gpu_timing;
413
414 let prefill_bytes =
415 prefill::allocation::scratch_bytes(c, max_t, prefill_rows.min(max_t).max(1), false)?;
416 let memory = budget::PoolMemory {
417 device: ctx.device.recommendedMaxWorkingSetSize() as usize,
418 fixed: ctx.device.currentAllocatedSize(),
419 host: reserve,
420 allocation_limit,
421 prefill: prefill_bytes,
422 };
423 let mut pool_bytes = memory.bytes(options.pool_gb)?;
424 let shrink = matches!(options.pool_gb, PoolBudget::Max);
425
426 let copy = std::env::var("CHERENKOV_POOL").as_deref() != Ok("set");
430 let all_bits = options.experts;
433 let miss_bits = options.miss_bits();
434
435 anyhow::ensure!(
436 copy || (all_bits == 4 && miss_bits == 4),
437 "the residency-set pool supports only 4-bit experts"
438 );
439
440 let low_bits = if all_bits == 3 || all_bits == 2 {
441 all_bits
442 } else {
443 miss_bits
444 };
445 let all_low_bits = all_bits == 3 || all_bits == 2;
446 let mut low_bit_store = None;
447
448 if (low_bits == 3 || low_bits == 2) && copy {
449 let l = crate::qwen4_exp::lowbit::ensure_with_policy(
453 &p.dir,
454 &p.manifest.experts,
455 low_bits,
456 options.repack,
457 options.build_missing_store,
458 )?;
459 let low_path = p.dir.join(format!("experts{low_bits}.bin"));
460 let low_file = std::fs::File::open(&low_path)
461 .with_context(|| format!("opening {}", low_path.display()))?;
462 let st = l;
463
464 anyhow::ensure!(
465 st.stride > 0 && st.stride <= stride,
466 "{low_bits}-bit record stride {} does not fit a 4-bit slot",
467 st.stride
468 );
469
470 {
471 use std::os::unix::io::AsRawFd as _;
472
473 unsafe { libc::fcntl(low_file.as_raw_fd(), libc::F_NOCACHE, 1) };
474 }
475
476 low_bit_store = Some((st, low_file, low_path));
477 }
478
479 let slot_stride = match (&low_bit_store, all_low_bits) {
481 (Some((st, _, _)), true) => st.stride,
482 _ => stride,
483 };
484 let mut res = loop {
485 let minimum = 64.min(n_records);
486 let slots = (pool_bytes / slot_stride).min(n_records);
487
488 ensure!(
489 slots >= minimum,
490 "expert pool budget cannot hold {minimum} records"
491 );
492
493 match residency::Pool::new(
494 &ctx,
495 p.experts.as_ptr(),
496 stride,
497 slot_stride,
498 n_records,
499 slots,
500 copy,
501 ) {
502 Ok(res) => break res,
503 Err(_) if shrink && pool_bytes > BYTES_PER_GB / 2 => {
504 pool_bytes -= BYTES_PER_GB / 2;
505 }
506 Err(e) => {
507 return Err(e.context(format!("allocating a {pool_bytes}-byte expert pool")));
508 }
509 }
510 };
511 let pool_file = std::fs::File::open(p.dir.join("experts.bin"))?;
515 let pool_file_nocache = std::fs::File::open(p.dir.join("experts.bin"))?;
516 let low_bit_store = low_bit_store.map(|(st, low_file, low_path)| {
517 res.set_low_bit_store(
518 low_file,
519 st.stride,
520 if all_low_bits {
521 if st.bits == 2 { 2 } else { 1 }
522 } else {
523 0
524 },
525 );
526 eprintln!(
527 "{}-bit store ({}): {} bytes per record, {:.0}% of 4-bit, for {}",
528 st.bits,
529 low_path.display(),
530 st.stride,
531 100.0 * st.stride as f64 / stride as f64,
532 if all_low_bits {
533 "every record"
534 } else {
535 "synchronous misses"
536 }
537 );
538
539 st
540 });
541
542 {
543 use std::os::unix::io::AsRawFd as _;
544
545 unsafe { libc::fcntl(pool_file_nocache.as_raw_fd(), libc::F_NOCACHE, 1) };
546 }
547
548 let event_res = {
549 use objc2_metal::MTLDevice as _;
550
551 ctx.device.newSharedEvent().context("shared event")?
552 };
553 let (event, event_cpu) = {
554 use objc2_metal::MTLDevice as _;
555
556 (
557 ctx.device.newSharedEvent().context("shared event")?,
558 ctx.device.newSharedEvent().context("shared event")?,
559 )
560 };
561
562 Ok(Gpu {
563 embed: q(&format!("{m}.embed_tokens"))?,
564 final_mixer: hc(&format!("{m}.hyper_connection_mixer"), false)?,
565 lm_head: q("language_model.lm_head")?,
566 p,
567 ctx,
568 pipes,
569 dense,
570 res,
571 activity,
572 read_tracker: super::activity::reads::ReadTracker::new(p.manifest.experts.layers),
573 activity_started: std::time::Instant::now(),
574 phase_timer,
575 pool_file,
576 pool_file_nocache,
577 low_bit_store,
578 all_low_bits,
579 ngram_file: std::fs::File::open(p.dir.join("ngram.bin"))?,
580 ngram_prefetch: std::cell::RefCell::new(None),
581 step_no: 0,
582 pending: None,
583 event,
584 event_base: 0,
585 event_cpu,
586 event_res,
587 event_cpu_base: 0,
588 slot_tab,
589 wmap,
590 layers,
591 mtp,
592 scratch,
593 max_t,
594 pos: 0,
595 tokens: Vec::new(),
596 batch_pos: 0,
597 batch_nb: 0,
598 batch_snap: false,
599 mtp_len: 0,
600 last_experts: Vec::new(),
601 expert_history: Vec::new(),
602 route_history: Vec::new(),
603 dump_states: std::env::var_os("CHERENKOV_DUMP_STATES").is_some(),
604 state_history: Vec::new(),
605 last_states: Vec::new(),
606 last_routes: Vec::new(),
607 last_route_w: Vec::new(),
608 last_miss: Vec::new(),
609 ngram_gather_s: std::cell::Cell::new(0.0),
610 ngram_ms: Vec::new(),
611 step_ms: Vec::new(),
612 gpu_ms: Vec::new(),
613 io_ms: Vec::new(),
614 step_set_s: 0.0,
615 step_read_s: 0.0,
616 step_warm: 0,
617 warm: Vec::new(),
618 folded_mtp: None,
619 set_ms: Vec::new(),
620 read_ms: Vec::new(),
621 misses: Vec::new(),
622 miss_bytes: Vec::new(),
623 step_misses: 0,
624 step_miss_bytes: 0,
625 lookahead_hit: Vec::new(),
626 lookahead_issued: Vec::new(),
627 la_log: Vec::new(),
628 la_pending: Vec::new(),
629 log_la: std::env::var_os("CHERENKOV_DUMP_LA").is_some(),
630 cut_w: options.cut_weak,
631 step_cut: 0,
632 cut: Vec::new(),
633 inflight: Vec::new(),
634 lookahead: std::env::var("CHERENKOV_LOOKAHEAD").as_deref() != Ok("0"),
635 spin_wait: std::env::var("CHERENKOV_SPIN").as_deref() != Ok("0"),
638 fake_experts: std::env::var("CHERENKOV_FAKE").as_deref() == Ok("experts"),
639 skip: std::env::var("CHERENKOV_SKIP")
640 .map(|v| v.split(',').map(|s| s.trim().to_string()).collect())
641 .unwrap_or_default(),
642 dispatches: Vec::new(),
643 gpu_idle_ms: Vec::new(),
644 rows: Vec::new(),
645 mtp_ms: Vec::new(),
646 dispatch_count: std::cell::Cell::new(0),
647 pf: None,
648 ring,
649 prefill_reserved_bytes: prefill_bytes,
650 prefill_stats: Vec::new(),
651 })
652 }
653}