1use std::collections::HashMap;
5use std::sync::{Mutex, OnceLock};
6
7use crate::wgsl_forge::execute::memory::BufferView;
8use crate::wgsl_forge::execute::{CapturedCudaGraph, CudaComputeContext};
9
10use super::weight_cache::weight_fingerprint;
11
12pub(crate) struct MultiWeightDevice {
31 pub ctx: CudaComputeContext,
32 pub permanent_end: u64,
34 pub weights: HashMap<u64, crate::wgsl_forge::execute::memory::BufferView>,
35 pub sticky_x_key: u64,
37 pub sticky_x_n_in: usize,
38 pub sticky_x_cap: usize,
39 pub sticky_x: Option<crate::wgsl_forge::execute::memory::BufferView>,
40 pub kv: Option<crate::wgsl_forge::execute::memory::BufferView>,
42 pub kv_block_table: Option<crate::wgsl_forge::execute::memory::BufferView>,
44 pub kv_block_size: u32,
45 pub kv_blocks_per_layer: u32,
46 pub kv_total_f32: usize,
47 pub kv_max_context: u32,
48 pub kv_n_layer: u32,
49 pub kv_n_kv_head: u32,
50 pub kv_head_dim: u32,
51 pub kv_slot_kv_elems: u32,
52 pub kv_layer_stride: u32,
53 pub mega_pass_arena: Option<MegaPassArena>,
55 pub decode_graph: Option<CapturedCudaGraph>,
57 pub decode_graph_key: u64,
58 pub decode_graph_node_count: u64,
60 pub decode_graph_h2d_bytes_per_token: u64,
62 pub mega_params_key: u64,
64}
65
66const CUDA_SOA_SLAB_BYTES: u64 = (5 * 1024 * 1024 * 1024) / 2;
68
69pub(crate) fn multi_weight_device() -> &'static Mutex<Option<MultiWeightDevice>> {
70 static C: OnceLock<Mutex<Option<MultiWeightDevice>>> = OnceLock::new();
71 C.get_or_init(|| Mutex::new(None))
72}
73
74pub fn q4k_device_weight_count() -> usize {
76 multi_weight_device()
77 .lock()
78 .ok()
79 .and_then(|g| g.as_ref().map(|d| d.weights.len()))
80 .unwrap_or(0)
81}
82
83pub fn q4k_weight_resident(key: u64) -> bool {
84 multi_weight_device()
85 .lock()
86 .ok()
87 .and_then(|guard| {
88 guard
89 .as_ref()
90 .map(|device| device.weights.contains_key(&key))
91 })
92 .unwrap_or(false)
93}
94
95pub(crate) fn decode_graph_key() -> Option<u64> {
99 multi_weight_device()
100 .lock()
101 .ok()
102 .and_then(|guard| {
103 guard.as_ref().and_then(|device| {
104 device
105 .decode_graph
106 .as_ref()
107 .map(|_| device.decode_graph_key)
108 })
109 })
110 .filter(|key| *key != 0)
111}
112
113pub(crate) fn decode_graph_node_count() -> Option<u64> {
118 multi_weight_device()
119 .lock()
120 .ok()
121 .and_then(|guard| {
122 guard.as_ref().and_then(|device| {
123 device
124 .decode_graph
125 .as_ref()
126 .map(|_| device.decode_graph_node_count)
127 })
128 })
129 .filter(|count| *count != 0)
130}
131
132pub(crate) fn decode_graph_h2d_bytes_per_token() -> Option<u64> {
134 multi_weight_device().lock().ok().and_then(|guard| {
135 guard.as_ref().and_then(|device| {
136 device
137 .decode_graph
138 .as_ref()
139 .map(|_| device.decode_graph_h2d_bytes_per_token)
140 })
141 })
142}
143
144pub fn preload_resident_blob(key: u64, bytes: &[u8]) -> bool {
149 use crate::wgsl_forge::dispatch::{caps, ensure_cuda_runtime_path};
150
151 if key == 0 || bytes.is_empty() || bytes.len() > 256 * 1024 * 1024 {
152 return false;
153 }
154 ensure_cuda_runtime_path();
155 if !caps().cuda {
156 return false;
157 }
158 let Ok(mut guard) = multi_weight_device().lock() else {
159 return false;
160 };
161 if !ensure_device(&mut guard) {
162 return false;
163 }
164 ensure_weight_resident(guard.as_mut().unwrap(), key, bytes)
165}
166
167pub fn preload_q4k_soa_weights(weights: &[(&[u8], usize, usize)]) -> usize {
171 use crate::ggml_quants::{ggml_row_bytes, GGML_TYPE_Q4_K_SOA};
172 use crate::wgsl_forge::dispatch::{caps, ensure_cuda_runtime_path};
173
174 if !crate::inference_modes::prefer_tensor_core_gemm() {
175 return 0;
176 }
177 ensure_cuda_runtime_path();
178 if !caps().cuda {
179 return 0;
180 }
181 let Ok(mut guard) = multi_weight_device().lock() else {
182 return 0;
183 };
184 if !ensure_device(&mut guard) {
185 return 0;
186 }
187 let dev = guard.as_mut().unwrap();
188 let mut added = 0usize;
189 for &(raw, n_in, n_out) in weights {
190 if n_in == 0 || n_out == 0 || n_out > 131_072 {
191 continue;
192 }
193 let Some(row_bytes) = ggml_row_bytes(GGML_TYPE_Q4_K_SOA, n_in) else {
194 continue;
195 };
196 let need = row_bytes.saturating_mul(n_out);
197 if raw.len() < need || need > 256 * 1024 * 1024 {
198 continue;
199 }
200 let key = weight_fingerprint(&raw[..need], n_in, n_out);
201 if dev.weights.contains_key(&key) {
202 continue;
203 }
204 if ensure_weight_resident(dev, key, &raw[..need]) {
205 added += 1;
206 }
207 }
208 if added > 0 {
209 log::info!(
210 "cuda_lane|q4k_soa|preload|added={added}|total={}",
211 dev.weights.len()
212 );
213 }
214 added
215}
216
217pub fn warm_cuda_context() -> bool {
221 use crate::wgsl_forge::dispatch::{caps, ensure_cuda_runtime_path};
222 ensure_cuda_runtime_path();
223 if !caps().cuda {
224 return false;
225 }
226 let Ok(mut guard) = multi_weight_device().lock() else {
227 return false;
228 };
229 let ok = ensure_device(&mut guard);
230 if ok {
231 log::info!("cuda_lane|warm_context|ok");
232 }
233 ok
234}
235
236fn empty_mw_device(ctx: CudaComputeContext) -> MultiWeightDevice {
237 MultiWeightDevice {
238 ctx,
239 permanent_end: 0,
240 weights: HashMap::new(),
241 sticky_x_key: 0,
242 sticky_x_n_in: 0,
243 sticky_x_cap: 0,
244 sticky_x: None,
245 kv: None,
246 kv_block_table: None,
247 kv_block_size: 0,
248 kv_blocks_per_layer: 0,
249 kv_total_f32: 0,
250 kv_max_context: 0,
251 kv_n_layer: 0,
252 kv_n_kv_head: 0,
253 kv_head_dim: 0,
254 kv_slot_kv_elems: 0,
255 kv_layer_stride: 0,
256 mega_pass_arena: None,
257 decode_graph: None,
258 decode_graph_key: 0,
259 decode_graph_node_count: 0,
260 decode_graph_h2d_bytes_per_token: 0,
261 mega_params_key: 0,
262 }
263}
264
265pub(crate) fn ensure_device(guard: &mut Option<MultiWeightDevice>) -> bool {
266 use crate::wgsl_forge::execute::CudaComputeContext;
267 if guard.is_some() {
268 return true;
269 }
270 let sizes: [u64; 3] = [
272 CUDA_SOA_SLAB_BYTES,
273 2 * 1024 * 1024 * 1024,
274 1024 * 1024 * 1024,
275 ];
276 let mut last_err = None;
277 for &bytes in &sizes {
278 match CudaComputeContext::new(bytes as usize) {
279 Ok(c) => {
280 log::info!(
281 "cuda_lane|q4k_soa|multi_weight_context|{}MiB",
282 bytes / (1024 * 1024)
283 );
284 *guard = Some(empty_mw_device(c));
285 return true;
286 }
287 Err(e) => {
288 last_err = Some(e);
289 }
290 }
291 }
292 log::warn!("cuda_lane|q4k_soa|ctx_fail|{last_err:?}");
293 false
294}
295
296pub fn ensure_device_kv_cache(
300 max_context: u32,
301 n_layer: u32,
302 n_kv_head: u32,
303 head_dim: u32,
304 slot_kv_elems: u32,
305 layer_stride: u32,
306 total_f32_elems: usize,
307) -> bool {
308 use crate::wgsl_forge::dispatch::{caps, ensure_cuda_runtime_path};
309
310 if !crate::inference_modes::prefer_tensor_core_gemm() {
311 return false;
312 }
313 ensure_cuda_runtime_path();
314 if !caps().cuda {
315 return false;
316 }
317 if max_context == 0
318 || n_layer == 0
319 || n_kv_head == 0
320 || head_dim == 0
321 || total_f32_elems == 0
322 || total_f32_elems > 128 * 1024 * 1024
323 {
324 return false;
325 }
326 let Ok(mut guard) = multi_weight_device().lock() else {
327 return false;
328 };
329 if !ensure_device(&mut guard) {
330 return false;
331 }
332 let dev = guard.as_mut().unwrap();
333 if let Some(v) = dev.kv {
334 if dev.kv_total_f32 == total_f32_elems
335 && dev.kv_max_context == max_context
336 && dev.kv_n_layer == n_layer
337 && dev.kv_n_kv_head == n_kv_head
338 && dev.kv_head_dim == head_dim
339 && dev.kv_layer_stride == layer_stride
340 && dev.kv_block_table.is_some()
341 {
342 let _ = v;
343 return true;
344 }
345 log::warn!("cuda_lane|kv|layout_mismatch|refuse_realloc");
347 return false;
348 }
349 let bytes = total_f32_elems.saturating_mul(4);
350 let zeros = vec![0u8; bytes];
351 let Some(config) = crate::inference::runtime::kv::paged::PagedKvConfig::new(
352 n_layer,
353 n_kv_head,
354 head_dim,
355 max_context,
356 ) else {
357 return false;
358 };
359 let Ok(table) = crate::inference::runtime::kv::paged::GpuBlockTablePlan::identity(config)
360 else {
361 return false;
362 };
363 let checkpoint = dev.permanent_end;
364 dev.ctx.restore_checkpoint(dev.permanent_end);
365 match dev.ctx.allocate_and_write(&zeros, 0, 0) {
366 Ok(v) => {
367 let block_table =
368 match dev
369 .ctx
370 .allocate_and_write(bytemuck::cast_slice(table.entries()), 0, 0)
371 {
372 Ok(table) => table,
373 Err(error) => {
374 dev.ctx.restore_checkpoint(checkpoint);
375 log::warn!("cuda_lane|kv|block_table_alloc_fail|{error:?}");
376 return false;
377 }
378 };
379 dev.permanent_end = dev.ctx.write_checkpoint();
380 dev.kv = Some(v);
381 dev.kv_block_table = Some(block_table);
382 dev.kv_block_size = config.block_size;
383 dev.kv_blocks_per_layer = config.logical_blocks_per_layer();
384 dev.kv_total_f32 = total_f32_elems;
385 dev.kv_max_context = max_context;
386 dev.kv_n_layer = n_layer;
387 dev.kv_n_kv_head = n_kv_head;
388 dev.kv_head_dim = head_dim;
389 dev.kv_slot_kv_elems = slot_kv_elems;
390 dev.kv_layer_stride = layer_stride;
391 log::info!(
392 "cuda_lane|kv|resident_paged|elems={total_f32_elems}|MiB={}|page_tokens={}|pages_per_layer={}",
393 bytes / (1024 * 1024),
394 dev.kv_block_size,
395 dev.kv_blocks_per_layer,
396 );
397 true
398 }
399 Err(e) => {
400 log::warn!("cuda_lane|kv|alloc_fail|bytes={bytes}|{e:?}");
401 false
402 }
403 }
404}
405
406pub fn device_kv_ready() -> bool {
408 multi_weight_device()
409 .lock()
410 .ok()
411 .and_then(|g| g.as_ref().map(|d| d.kv.is_some()))
412 .unwrap_or(false)
413}
414
415pub(crate) fn ensure_weight_resident(dev: &mut MultiWeightDevice, key: u64, raw: &[u8]) -> bool {
416 if dev.weights.contains_key(&key) {
417 return true;
418 }
419 dev.ctx.restore_checkpoint(dev.permanent_end);
421 match dev.ctx.allocate_and_write(raw, 1, 0) {
422 Ok(v) => {
423 dev.permanent_end = dev.ctx.write_checkpoint();
424 dev.weights.insert(key, v);
427 log::debug!(
428 "cuda_lane|resident_blob|resident+|key={key:#x}|bytes={}|count={}",
429 raw.len(),
430 dev.weights.len()
431 );
432 true
433 }
434 Err(e) => {
435 log::warn!(
438 "cuda_lane|q4k_soa|slab_full_skip|key={key:#x}|bytes={}|err={e:?}",
439 raw.len()
440 );
441 false
442 }
443 }
444}
445
446pub(crate) fn ensure_sticky_x(
449 dev: &mut MultiWeightDevice,
450 x: &[f32],
451) -> Option<crate::wgsl_forge::execute::memory::BufferView> {
452 let key = weight_fingerprint(bytemuck::cast_slice(x), x.len(), 0);
453 if let Some(v) = dev.sticky_x {
454 if dev.sticky_x_key == key && dev.sticky_x_n_in == x.len() {
455 return Some(v);
456 }
457 if dev.sticky_x_cap >= x.len() {
458 if let Err(e) = dev.ctx.write_view(&v, bytemuck::cast_slice(x)) {
460 log::warn!("cuda_lane|sticky_x|write_view|{e:?}");
461 return None;
462 }
463 dev.sticky_x_key = key;
464 dev.sticky_x_n_in = x.len();
465 return Some(v);
466 }
467 }
468 let cap = x.len().max(8192);
471 let zeros = vec![0u8; cap * 4];
472 dev.ctx.restore_checkpoint(dev.permanent_end);
473 match dev.ctx.allocate_and_write(&zeros, 0, 0) {
474 Ok(v) => {
475 dev.permanent_end = dev.ctx.write_checkpoint();
476 if let Err(e) = dev.ctx.write_view(&v, bytemuck::cast_slice(x)) {
477 log::warn!("cuda_lane|sticky_x|init_write|{e:?}");
478 return None;
479 }
480 dev.sticky_x_key = key;
481 dev.sticky_x_n_in = x.len();
482 dev.sticky_x_cap = cap;
483 dev.sticky_x = Some(v);
484 log::info!("cuda_lane|sticky_x|alloc|cap={cap}|n_in={}", x.len());
485 Some(v)
486 }
487 Err(e) => {
488 log::warn!("cuda_lane|sticky_x|alloc_fail|{e:?}");
489 None
490 }
491 }
492}
493
494pub(crate) struct MegaPassArena {
498 pub hidden_a: BufferView,
500 pub hidden_b: BufferView,
501 pub yq: BufferView,
503 pub yk: BufferView,
504 pub yv: BufferView,
505 pub attn_out: BufferView,
507 pub attn_partials: BufferView,
510 pub ffn_mid: BufferView,
512 pub q8_activation: BufferView,
514 pub q8_activation_scales: BufferView,
515 pub p_rms: BufferView,
518 pub p_qkv: BufferView,
519 pub p_rope: BufferView,
520 pub p_rope_k: BufferView,
521 pub p_kvw: BufferView,
522 pub p_sdpa: BufferView,
523 pub p_sdpa_scale: BufferView,
524 pub p_gemv_dims: BufferView,
525 pub p_ffn_dims: BufferView,
526 pub p_down_dims: BufferView,
527 pub logits: BufferView,
529 pub token: BufferView,
530 pub p_argmax: BufferView,
531 pub p_lm_dims: BufferView,
532 pub p_layer_ids: Vec<BufferView>,
534 pub p_step: BufferView,
536 pub n_embd: usize,
538 pub n_head: usize,
539 pub head_dim: usize,
540 pub q_dim: usize,
541 pub kv_dim: usize,
542 pub n_ffn: usize,
543 pub max_vocab: usize,
544 pub n_layer: usize,
545}
546
547pub(crate) fn ensure_mega_pass_arena(
551 dev: &mut MultiWeightDevice,
552 n_embd: usize,
553 n_head: usize,
554 head_dim: usize,
555 q_dim: usize,
556 kv_dim: usize,
557 n_ffn: usize,
558 max_vocab: usize,
559 n_layer: usize,
560) -> bool {
561 if let Some(ref arena) = dev.mega_pass_arena {
562 if arena.n_embd == n_embd
563 && arena.n_head == n_head
564 && arena.head_dim == head_dim
565 && arena.q_dim == q_dim
566 && arena.kv_dim == kv_dim
567 && arena.n_ffn == n_ffn
568 && arena.max_vocab == max_vocab
569 && arena.n_layer == n_layer
570 {
571 return true;
572 }
573 log::warn!(
575 "cuda_lane|mega_pass_arena|dim_mismatch|refuse_realloc|old embd={} q={} kv={} ffn={} vocab={} | new embd={} q={} kv={} ffn={} vocab={}",
576 arena.n_embd, arena.q_dim, arena.kv_dim, arena.n_ffn, arena.max_vocab,
577 n_embd, q_dim, kv_dim, n_ffn, max_vocab
578 );
579 return false;
580 }
581
582 dev.ctx.restore_checkpoint(dev.permanent_end);
584
585 let zeros_embd = vec![0.0f32; n_embd];
586 let zeros_q = vec![0.0f32; q_dim];
587 let zeros_kv = vec![0.0f32; kv_dim];
588 let zeros_attn_partials = vec![
589 0.0f32;
590 n_head
591 .saturating_mul(super::paged_attention::MAX_ATTENTION_SEGMENTS)
592 .saturating_mul(head_dim.saturating_add(2))
593 ];
594 let zeros_ffn = vec![0.0f32; n_ffn];
595 let zeros_vocab = vec![0.0f32; max_vocab];
596 let max_activation = n_embd.max(q_dim).max(n_ffn);
597 let zeros_q8_activation = vec![0u8; max_activation];
598 let zeros_q8_scales = vec![0.0f32; max_activation.div_ceil(32)];
599
600 let alloc = |ctx: &mut CudaComputeContext, data: &[u8]| -> Option<BufferView> {
601 ctx.allocate_and_write(data, 0, 0).ok()
602 };
603
604 macro_rules! try_alloc {
605 ($e:expr) => {
606 match $e {
607 Some(v) => v,
608 None => return false,
609 }
610 };
611 }
612
613 let hidden_a = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_embd)));
614 let hidden_b = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_embd)));
615 let yq = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_q)));
616 let yk = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_kv)));
617 let yv = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_kv)));
618 let attn_out = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_q)));
619 let attn_partials = try_alloc!(alloc(
620 &mut dev.ctx,
621 bytemuck::cast_slice(&zeros_attn_partials)
622 ));
623 let ffn_mid = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_ffn)));
624 let q8_activation = try_alloc!(alloc(&mut dev.ctx, &zeros_q8_activation));
625 let q8_activation_scales =
626 try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_q8_scales)));
627
628 let p_rms = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 2])));
630 let p_qkv = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 5])));
631 let p_rope = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 5])));
632 let p_rope_k = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 5])));
633 let p_kvw = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 5])));
634 let p_sdpa = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 9])));
635 let p_sdpa_scale = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 1])));
636 let p_gemv_dims = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 4])));
638 let p_ffn_dims = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 4])));
639 let p_down_dims = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 3])));
640
641 let logits = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&zeros_vocab)));
643 let token = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 1])));
644 let p_argmax = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 1])));
645 let p_lm_dims = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 3])));
646 let mut p_layer_ids = Vec::with_capacity(n_layer);
647 for layer in 0..n_layer {
648 p_layer_ids.push(try_alloc!(alloc(
649 &mut dev.ctx,
650 bytemuck::cast_slice(&[layer as u32]),
651 )));
652 }
653 let p_step = try_alloc!(alloc(&mut dev.ctx, bytemuck::cast_slice(&[0u32; 3]),));
654
655 dev.permanent_end = dev.ctx.write_checkpoint();
656
657 let total_floats = n_embd * 2 + q_dim * 2 + kv_dim * 2 + n_ffn + max_vocab;
658 let total_bytes = total_floats * 4
659 + zeros_q8_activation.len()
660 + zeros_q8_scales.len() * 4
661 + 3 * 4
662 + 5 * 4
663 + 5 * 4
664 + 5 * 4
665 + 7 * 4
666 + 9 * 4
667 + 4
668 + 4 * 4
669 + 4
670 + 3 * 4;
671 log::info!(
672 "cuda_lane|mega_pass_arena|alloc|embd={n_embd}|q={q_dim}|kv={kv_dim}|ffn={n_ffn}|vocab={max_vocab}|~{}KiB",
673 total_bytes / 1024
674 );
675
676 dev.mega_pass_arena = Some(MegaPassArena {
677 hidden_a,
678 hidden_b,
679 yq,
680 yk,
681 yv,
682 attn_out,
683 attn_partials,
684 ffn_mid,
685 q8_activation,
686 q8_activation_scales,
687 p_rms,
688 p_qkv,
689 p_rope,
690 p_rope_k,
691 p_kvw,
692 p_sdpa,
693 p_sdpa_scale,
694 p_gemv_dims,
695 p_ffn_dims,
696 p_down_dims,
697 logits,
698 token,
699 p_argmax,
700 p_lm_dims,
701 p_layer_ids,
702 p_step,
703 n_embd,
704 n_head,
705 head_dim,
706 q_dim,
707 kv_dim,
708 n_ffn,
709 max_vocab,
710 n_layer,
711 });
712 true
713}