1use metal::MTLSize;
12
13use crate::buffer::MlxBuffer;
14use crate::encoder::CommandEncoder;
15use crate::error::{MlxError, Result};
16use crate::kernel_registry::KernelRegistry;
17
18use super::encode_helpers::{encode_with_args, KernelArg};
19
20pub static KV_CACHE_COPY_SHADER_SOURCE: &str = include_str!("../shaders/kv_cache_copy.metal");
22
23pub fn register(registry: &mut KernelRegistry) {
25 registry.register_source("kv_cache_copy", KV_CACHE_COPY_SHADER_SOURCE);
26}
27
28#[allow(clippy::too_many_arguments)]
49pub fn dispatch_kv_cache_copy(
50 encoder: &mut CommandEncoder,
51 registry: &mut KernelRegistry,
52 device: &metal::DeviceRef,
53 src: &MlxBuffer,
54 cache: &MlxBuffer,
55 write_pos: u32,
56 row_size: u32,
57 n_new: u32,
58 cache_cap: u32,
59 is_sliding: bool,
60) -> Result<()> {
61 if n_new == 0 || row_size == 0 {
62 return Ok(()); }
64
65 let total_elements = (n_new as u64) * (row_size as u64);
66 let src_elements = src.element_count() as u64;
67 if src_elements < total_elements {
68 return Err(MlxError::InvalidArgument(format!(
69 "kv_cache_copy: src has {} elements but need {} (n_new={} * row_size={})",
70 src_elements, total_elements, n_new, row_size
71 )));
72 }
73
74 if !is_sliding && (write_pos as u64 + n_new as u64) > cache_cap as u64 {
76 return Err(MlxError::InvalidArgument(format!(
77 "kv_cache_copy: global cache overflow: write_pos({}) + n_new({}) > cache_cap({})",
78 write_pos, n_new, cache_cap
79 )));
80 }
81
82 let pipeline = registry.get_pipeline("kv_cache_copy", device)?;
83
84 let is_sliding_val: u32 = if is_sliding { 1 } else { 0 };
85
86 let write_pos_bytes = write_pos.to_ne_bytes();
88 let row_size_bytes = row_size.to_ne_bytes();
89 let n_new_bytes = n_new.to_ne_bytes();
90 let cache_cap_bytes = cache_cap.to_ne_bytes();
91 let is_sliding_bytes = is_sliding_val.to_ne_bytes();
92
93 encode_with_args(
94 encoder,
95 pipeline,
96 &[
97 (0, KernelArg::Buffer(src)),
98 (1, KernelArg::Buffer(cache)),
99 (2, KernelArg::Bytes(&write_pos_bytes)),
100 (3, KernelArg::Bytes(&row_size_bytes)),
101 (4, KernelArg::Bytes(&n_new_bytes)),
102 (5, KernelArg::Bytes(&cache_cap_bytes)),
103 (6, KernelArg::Bytes(&is_sliding_bytes)),
104 ],
105 MTLSize::new(total_elements, 1, 1),
106 MTLSize::new(std::cmp::min(256, total_elements), 1, 1),
107 );
108
109 Ok(())
110}
111
112#[allow(clippy::too_many_arguments)]
131pub fn dispatch_kv_cache_copy_batch_f32(
132 encoder: &mut CommandEncoder,
133 registry: &mut KernelRegistry,
134 device: &metal::DeviceRef,
135 src: &MlxBuffer,
136 cache: &MlxBuffer,
137 n_heads: u32,
138 head_dim: u32,
139 capacity: u32,
140 seq_pos: u32,
141) -> Result<()> {
142 if n_heads == 0 || head_dim == 0 {
143 return Ok(());
144 }
145
146 let total_src = (n_heads as u64) * (head_dim as u64);
147 if (src.element_count() as u64) < total_src {
148 return Err(MlxError::InvalidArgument(format!(
149 "kv_cache_copy_batch_f32: src has {} elements but need {} (n_heads={} * head_dim={})",
150 src.element_count(), total_src, n_heads, head_dim
151 )));
152 }
153
154 let pipeline = registry.get_pipeline("kv_cache_copy_batch_f32", device)?;
155
156 let n_heads_bytes = n_heads.to_ne_bytes();
157 let head_dim_bytes = head_dim.to_ne_bytes();
158 let capacity_bytes = capacity.to_ne_bytes();
159 let seq_pos_bytes = seq_pos.to_ne_bytes();
160
161 use super::encode_helpers::{encode_with_args, KernelArg};
162
163 encode_with_args(
164 encoder,
165 pipeline,
166 &[
167 (0, KernelArg::Buffer(src)),
168 (1, KernelArg::Buffer(cache)),
169 (2, KernelArg::Bytes(&n_heads_bytes)),
170 (3, KernelArg::Bytes(&head_dim_bytes)),
171 (4, KernelArg::Bytes(&capacity_bytes)),
172 (5, KernelArg::Bytes(&seq_pos_bytes)),
173 ],
174 MTLSize::new(head_dim as u64, n_heads as u64, 1),
175 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
176 );
177
178 Ok(())
179}
180
181#[allow(clippy::too_many_arguments)]
205pub fn dispatch_kv_cache_copy_f32(
206 encoder: &mut CommandEncoder,
207 registry: &mut KernelRegistry,
208 device: &metal::DeviceRef,
209 src: &MlxBuffer,
210 cache: &MlxBuffer,
211 write_pos: u32,
212 row_size: u32,
213 n_new: u32,
214 cache_cap: u32,
215 is_sliding: bool,
216) -> Result<()> {
217 if n_new == 0 || row_size == 0 {
218 return Ok(()); }
220
221 let total_elements = (n_new as u64) * (row_size as u64);
222 let src_elements = src.element_count() as u64;
223 if src_elements < total_elements {
224 return Err(MlxError::InvalidArgument(format!(
225 "kv_cache_copy_f32: src has {} elements but need {} (n_new={} * row_size={})",
226 src_elements, total_elements, n_new, row_size
227 )));
228 }
229
230 if !is_sliding && (write_pos as u64 + n_new as u64) > cache_cap as u64 {
232 return Err(MlxError::InvalidArgument(format!(
233 "kv_cache_copy_f32: global cache overflow: write_pos({}) + n_new({}) > cache_cap({})",
234 write_pos, n_new, cache_cap
235 )));
236 }
237
238 let pipeline = registry.get_pipeline("kv_cache_copy_f32", device)?;
239
240 let is_sliding_val: u32 = if is_sliding { 1 } else { 0 };
241
242 let write_pos_bytes = write_pos.to_ne_bytes();
243 let row_size_bytes = row_size.to_ne_bytes();
244 let n_new_bytes = n_new.to_ne_bytes();
245 let cache_cap_bytes = cache_cap.to_ne_bytes();
246 let is_sliding_bytes = is_sliding_val.to_ne_bytes();
247
248 encode_with_args(
249 encoder,
250 pipeline,
251 &[
252 (0, KernelArg::Buffer(src)),
253 (1, KernelArg::Buffer(cache)),
254 (2, KernelArg::Bytes(&write_pos_bytes)),
255 (3, KernelArg::Bytes(&row_size_bytes)),
256 (4, KernelArg::Bytes(&n_new_bytes)),
257 (5, KernelArg::Bytes(&cache_cap_bytes)),
258 (6, KernelArg::Bytes(&is_sliding_bytes)),
259 ],
260 MTLSize::new(total_elements, 1, 1),
261 MTLSize::new(std::cmp::min(256, total_elements), 1, 1),
262 );
263
264 Ok(())
265}
266
267#[allow(clippy::too_many_arguments)]
276pub fn dispatch_kv_cache_copy_batch_f32_to_f16(
277 encoder: &mut CommandEncoder,
278 registry: &mut KernelRegistry,
279 device: &metal::DeviceRef,
280 src: &MlxBuffer,
281 cache: &MlxBuffer,
282 n_heads: u32,
283 head_dim: u32,
284 capacity: u32,
285 seq_pos: u32,
286) -> Result<()> {
287 if n_heads == 0 || head_dim == 0 {
288 return Ok(());
289 }
290
291 let total_src = (n_heads as u64) * (head_dim as u64);
292 if (src.element_count() as u64) < total_src {
293 return Err(MlxError::InvalidArgument(format!(
294 "kv_cache_copy_batch_f32_to_f16: src has {} elements but need {} (n_heads={} * head_dim={})",
295 src.element_count(), total_src, n_heads, head_dim
296 )));
297 }
298
299 let pipeline = registry.get_pipeline("kv_cache_copy_batch_f32_to_f16", device)?;
300
301 let n_heads_bytes = n_heads.to_ne_bytes();
302 let head_dim_bytes = head_dim.to_ne_bytes();
303 let capacity_bytes = capacity.to_ne_bytes();
304 let seq_pos_bytes = seq_pos.to_ne_bytes();
305
306 use super::encode_helpers::{encode_with_args, KernelArg};
307
308 encode_with_args(
309 encoder,
310 pipeline,
311 &[
312 (0, KernelArg::Buffer(src)),
313 (1, KernelArg::Buffer(cache)),
314 (2, KernelArg::Bytes(&n_heads_bytes)),
315 (3, KernelArg::Bytes(&head_dim_bytes)),
316 (4, KernelArg::Bytes(&capacity_bytes)),
317 (5, KernelArg::Bytes(&seq_pos_bytes)),
318 ],
319 MTLSize::new(head_dim as u64, n_heads as u64, 1),
320 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
321 );
322
323 Ok(())
324}
325
326#[allow(clippy::too_many_arguments)]
333pub fn dispatch_kv_cache_copy_batch_f32_to_f16_batched(
334 encoder: &mut CommandEncoder,
335 registry: &mut KernelRegistry,
336 device: &metal::DeviceRef,
337 src: &MlxBuffer,
338 cache: &MlxBuffer,
339 slot_id: &MlxBuffer,
340 seq_pos: &MlxBuffer,
341 n_queries: u32,
342 n_heads: u32,
343 head_dim: u32,
344 capacity: u32,
345 is_ring: bool,
346) -> Result<()> {
347 if n_heads == 0 || head_dim == 0 || n_queries == 0 {
348 return Ok(());
349 }
350 let pipeline = registry.get_pipeline("kv_cache_copy_batch_f32_to_f16_batched", device)?;
351 let n_heads_bytes = n_heads.to_ne_bytes();
352 let head_dim_bytes = head_dim.to_ne_bytes();
353 let capacity_bytes = capacity.to_ne_bytes();
354 let is_ring_bytes = (if is_ring { 1u32 } else { 0u32 }).to_ne_bytes();
355
356 use super::encode_helpers::{encode_with_args, KernelArg};
357 encode_with_args(
358 encoder,
359 pipeline,
360 &[
361 (0, KernelArg::Buffer(src)),
362 (1, KernelArg::Buffer(cache)),
363 (2, KernelArg::Buffer(slot_id)),
364 (3, KernelArg::Buffer(seq_pos)),
365 (4, KernelArg::Bytes(&n_heads_bytes)),
366 (5, KernelArg::Bytes(&head_dim_bytes)),
367 (6, KernelArg::Bytes(&capacity_bytes)),
368 (7, KernelArg::Bytes(&is_ring_bytes)),
369 ],
370 MTLSize::new(head_dim as u64, n_heads as u64, n_queries as u64),
371 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
372 );
373
374 Ok(())
375}
376
377#[allow(clippy::too_many_arguments)]
389pub fn dispatch_kv_cache_copy_batch_f32_kv_dual(
390 encoder: &mut CommandEncoder,
391 registry: &mut KernelRegistry,
392 device: &metal::DeviceRef,
393 src_k: &MlxBuffer,
394 src_v: &MlxBuffer,
395 cache_k: &MlxBuffer,
396 cache_v: &MlxBuffer,
397 n_heads: u32,
398 head_dim: u32,
399 capacity: u32,
400 seq_pos: u32,
401) -> Result<()> {
402 if n_heads == 0 || head_dim == 0 {
403 return Ok(());
404 }
405
406 let total_src = (n_heads as u64) * (head_dim as u64);
407 if (src_k.element_count() as u64) < total_src {
408 return Err(MlxError::InvalidArgument(format!(
409 "kv_cache_copy_batch_f32_kv_dual: src_k has {} elements but need {} (n_heads={} * head_dim={})",
410 src_k.element_count(), total_src, n_heads, head_dim
411 )));
412 }
413 if (src_v.element_count() as u64) < total_src {
414 return Err(MlxError::InvalidArgument(format!(
415 "kv_cache_copy_batch_f32_kv_dual: src_v has {} elements but need {} (n_heads={} * head_dim={})",
416 src_v.element_count(), total_src, n_heads, head_dim
417 )));
418 }
419
420 let pipeline = registry.get_pipeline("kv_cache_copy_batch_f32_kv_dual", device)?;
421
422 let n_heads_bytes = n_heads.to_ne_bytes();
423 let head_dim_bytes = head_dim.to_ne_bytes();
424 let capacity_bytes = capacity.to_ne_bytes();
425 let seq_pos_bytes = seq_pos.to_ne_bytes();
426
427 encode_with_args(
428 encoder,
429 pipeline,
430 &[
431 (0, KernelArg::Buffer(src_k)),
432 (1, KernelArg::Buffer(src_v)),
433 (2, KernelArg::Buffer(cache_k)),
434 (3, KernelArg::Buffer(cache_v)),
435 (4, KernelArg::Bytes(&n_heads_bytes)),
436 (5, KernelArg::Bytes(&head_dim_bytes)),
437 (6, KernelArg::Bytes(&capacity_bytes)),
438 (7, KernelArg::Bytes(&seq_pos_bytes)),
439 ],
440 MTLSize::new(head_dim as u64, n_heads as u64, 1),
441 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
442 );
443
444 Ok(())
445}
446
447#[allow(clippy::too_many_arguments)]
452pub fn dispatch_kv_cache_copy_batch_f32_to_f16_kv_dual(
453 encoder: &mut CommandEncoder,
454 registry: &mut KernelRegistry,
455 device: &metal::DeviceRef,
456 src_k: &MlxBuffer,
457 src_v: &MlxBuffer,
458 cache_k: &MlxBuffer,
459 cache_v: &MlxBuffer,
460 n_heads: u32,
461 head_dim: u32,
462 capacity: u32,
463 seq_pos: u32,
464) -> Result<()> {
465 if n_heads == 0 || head_dim == 0 {
466 return Ok(());
467 }
468
469 let total_src = (n_heads as u64) * (head_dim as u64);
470 if (src_k.element_count() as u64) < total_src {
471 return Err(MlxError::InvalidArgument(format!(
472 "kv_cache_copy_batch_f32_to_f16_kv_dual: src_k has {} elements but need {} (n_heads={} * head_dim={})",
473 src_k.element_count(), total_src, n_heads, head_dim
474 )));
475 }
476 if (src_v.element_count() as u64) < total_src {
477 return Err(MlxError::InvalidArgument(format!(
478 "kv_cache_copy_batch_f32_to_f16_kv_dual: src_v has {} elements but need {} (n_heads={} * head_dim={})",
479 src_v.element_count(), total_src, n_heads, head_dim
480 )));
481 }
482
483 let pipeline = registry.get_pipeline("kv_cache_copy_batch_f32_to_f16_kv_dual", device)?;
484
485 let n_heads_bytes = n_heads.to_ne_bytes();
486 let head_dim_bytes = head_dim.to_ne_bytes();
487 let capacity_bytes = capacity.to_ne_bytes();
488 let seq_pos_bytes = seq_pos.to_ne_bytes();
489
490 encode_with_args(
491 encoder,
492 pipeline,
493 &[
494 (0, KernelArg::Buffer(src_k)),
495 (1, KernelArg::Buffer(src_v)),
496 (2, KernelArg::Buffer(cache_k)),
497 (3, KernelArg::Buffer(cache_v)),
498 (4, KernelArg::Bytes(&n_heads_bytes)),
499 (5, KernelArg::Bytes(&head_dim_bytes)),
500 (6, KernelArg::Bytes(&capacity_bytes)),
501 (7, KernelArg::Bytes(&seq_pos_bytes)),
502 ],
503 MTLSize::new(head_dim as u64, n_heads as u64, 1),
504 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
505 );
506
507 Ok(())
508}
509
510#[allow(clippy::too_many_arguments)]
528pub fn dispatch_kv_cache_copy_seq_f32(
529 encoder: &mut CommandEncoder,
530 registry: &mut KernelRegistry,
531 device: &metal::DeviceRef,
532 src: &MlxBuffer,
533 cache: &MlxBuffer,
534 n_heads: u32,
535 head_dim: u32,
536 capacity: u32,
537 seq_pos_start: u32,
538 n_tokens: u32,
539 src_tok_offset: u32,
540) -> Result<()> {
541 if n_heads == 0 || head_dim == 0 || n_tokens == 0 {
542 return Ok(());
543 }
544 let total_src = ((src_tok_offset as u64) + (n_tokens as u64))
545 * (n_heads as u64) * (head_dim as u64);
546 if (src.element_count() as u64) < total_src {
547 return Err(MlxError::InvalidArgument(format!(
548 "kv_cache_copy_seq_f32: src has {} elements, need {} ((src_tok_offset={} + n_tokens={}) * n_heads={} * head_dim={})",
549 src.element_count(), total_src, src_tok_offset, n_tokens, n_heads, head_dim
550 )));
551 }
552
553 let pipeline = registry.get_pipeline("kv_cache_copy_seq_f32", device)?;
554
555 let n_heads_bytes = n_heads.to_ne_bytes();
556 let head_dim_bytes = head_dim.to_ne_bytes();
557 let capacity_bytes = capacity.to_ne_bytes();
558 let seq_pos_start_bytes = seq_pos_start.to_ne_bytes();
559 let n_tokens_bytes = n_tokens.to_ne_bytes();
560 let src_tok_offset_bytes = src_tok_offset.to_ne_bytes();
561
562 use super::encode_helpers::{encode_with_args, KernelArg};
563
564 encode_with_args(
565 encoder,
566 pipeline,
567 &[
568 (0, KernelArg::Buffer(src)),
569 (1, KernelArg::Buffer(cache)),
570 (2, KernelArg::Bytes(&n_heads_bytes)),
571 (3, KernelArg::Bytes(&head_dim_bytes)),
572 (4, KernelArg::Bytes(&capacity_bytes)),
573 (5, KernelArg::Bytes(&seq_pos_start_bytes)),
574 (6, KernelArg::Bytes(&n_tokens_bytes)),
575 (7, KernelArg::Bytes(&src_tok_offset_bytes)),
576 ],
577 MTLSize::new(head_dim as u64, n_heads as u64, n_tokens as u64),
578 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
579 );
580
581 Ok(())
582}
583
584#[allow(clippy::too_many_arguments)]
593pub fn dispatch_kv_cache_copy_seq_f32_dual(
594 encoder: &mut CommandEncoder,
595 registry: &mut KernelRegistry,
596 device: &metal::DeviceRef,
597 src_k: &MlxBuffer,
598 src_v: &MlxBuffer,
599 cache_k: &MlxBuffer,
600 cache_v: &MlxBuffer,
601 n_heads: u32,
602 head_dim: u32,
603 capacity: u32,
604 seq_pos_start: u32,
605 n_tokens: u32,
606 src_tok_offset: u32,
607) -> Result<()> {
608 if n_heads == 0 || head_dim == 0 || n_tokens == 0 {
609 return Ok(());
610 }
611 let total_src = ((src_tok_offset as u64) + (n_tokens as u64))
612 * (n_heads as u64) * (head_dim as u64);
613 for (name, b) in [("src_k", src_k), ("src_v", src_v)] {
614 if (b.element_count() as u64) < total_src {
615 return Err(MlxError::InvalidArgument(format!(
616 "kv_cache_copy_seq_f32_dual: {} has {} elements, need {}",
617 name, b.element_count(), total_src
618 )));
619 }
620 }
621
622 let pipeline = registry.get_pipeline("kv_cache_copy_seq_f32_kv_dual", device)?;
623
624 let n_heads_bytes = n_heads.to_ne_bytes();
625 let head_dim_bytes = head_dim.to_ne_bytes();
626 let capacity_bytes = capacity.to_ne_bytes();
627 let seq_pos_start_bytes = seq_pos_start.to_ne_bytes();
628 let n_tokens_bytes = n_tokens.to_ne_bytes();
629 let src_tok_offset_bytes = src_tok_offset.to_ne_bytes();
630
631 use super::encode_helpers::{encode_with_args, KernelArg};
632
633 encode_with_args(
634 encoder,
635 pipeline,
636 &[
637 (0, KernelArg::Buffer(src_k)),
638 (1, KernelArg::Buffer(src_v)),
639 (2, KernelArg::Buffer(cache_k)),
640 (3, KernelArg::Buffer(cache_v)),
641 (4, KernelArg::Bytes(&n_heads_bytes)),
642 (5, KernelArg::Bytes(&head_dim_bytes)),
643 (6, KernelArg::Bytes(&capacity_bytes)),
644 (7, KernelArg::Bytes(&seq_pos_start_bytes)),
645 (8, KernelArg::Bytes(&n_tokens_bytes)),
646 (9, KernelArg::Bytes(&src_tok_offset_bytes)),
647 ],
648 MTLSize::new(head_dim as u64, n_heads as u64, n_tokens as u64),
649 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
650 );
651
652 Ok(())
653}
654
655#[allow(clippy::too_many_arguments)]
658pub fn dispatch_kv_cache_copy_seq_f32_to_f16_dual(
659 encoder: &mut CommandEncoder,
660 registry: &mut KernelRegistry,
661 device: &metal::DeviceRef,
662 src_k: &MlxBuffer,
663 src_v: &MlxBuffer,
664 cache_k: &MlxBuffer,
665 cache_v: &MlxBuffer,
666 n_heads: u32,
667 head_dim: u32,
668 capacity: u32,
669 seq_pos_start: u32,
670 n_tokens: u32,
671 src_tok_offset: u32,
672) -> Result<()> {
673 if n_heads == 0 || head_dim == 0 || n_tokens == 0 {
674 return Ok(());
675 }
676 let total_src = ((src_tok_offset as u64) + (n_tokens as u64))
677 * (n_heads as u64) * (head_dim as u64);
678 for (name, b) in [("src_k", src_k), ("src_v", src_v)] {
679 if (b.element_count() as u64) < total_src {
680 return Err(MlxError::InvalidArgument(format!(
681 "kv_cache_copy_seq_f32_to_f16_dual: {} has {} elements, need {}",
682 name, b.element_count(), total_src
683 )));
684 }
685 }
686
687 let pipeline = registry.get_pipeline("kv_cache_copy_seq_f32_to_f16_kv_dual", device)?;
688
689 let n_heads_bytes = n_heads.to_ne_bytes();
690 let head_dim_bytes = head_dim.to_ne_bytes();
691 let capacity_bytes = capacity.to_ne_bytes();
692 let seq_pos_start_bytes = seq_pos_start.to_ne_bytes();
693 let n_tokens_bytes = n_tokens.to_ne_bytes();
694 let src_tok_offset_bytes = src_tok_offset.to_ne_bytes();
695
696 use super::encode_helpers::{encode_with_args, KernelArg};
697
698 encode_with_args(
699 encoder,
700 pipeline,
701 &[
702 (0, KernelArg::Buffer(src_k)),
703 (1, KernelArg::Buffer(src_v)),
704 (2, KernelArg::Buffer(cache_k)),
705 (3, KernelArg::Buffer(cache_v)),
706 (4, KernelArg::Bytes(&n_heads_bytes)),
707 (5, KernelArg::Bytes(&head_dim_bytes)),
708 (6, KernelArg::Bytes(&capacity_bytes)),
709 (7, KernelArg::Bytes(&seq_pos_start_bytes)),
710 (8, KernelArg::Bytes(&n_tokens_bytes)),
711 (9, KernelArg::Bytes(&src_tok_offset_bytes)),
712 ],
713 MTLSize::new(head_dim as u64, n_heads as u64, n_tokens as u64),
714 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
715 );
716
717 Ok(())
718}
719
720#[allow(clippy::too_many_arguments)]
733pub fn dispatch_kv_cache_copy_seq_bf16(
734 encoder: &mut CommandEncoder,
735 registry: &mut KernelRegistry,
736 device: &metal::DeviceRef,
737 src: &MlxBuffer,
738 cache: &MlxBuffer,
739 n_heads: u32,
740 head_dim: u32,
741 capacity: u32,
742 seq_pos_start: u32,
743 n_tokens: u32,
744 src_tok_offset: u32,
745) -> Result<()> {
746 if n_heads == 0 || head_dim == 0 || n_tokens == 0 {
747 return Ok(());
748 }
749 let total_src = ((src_tok_offset as u64) + (n_tokens as u64))
751 * (n_heads as u64) * (head_dim as u64);
752 let src_bytes_needed = total_src * 2; if (src.byte_len() as u64) < src_bytes_needed {
754 return Err(MlxError::InvalidArgument(format!(
755 "kv_cache_copy_seq_bf16: src has {} bytes, need {} ((src_tok_offset={} + n_tokens={}) * n_heads={} * head_dim={} * 2)",
756 src.byte_len(), src_bytes_needed, src_tok_offset, n_tokens, n_heads, head_dim
757 )));
758 }
759
760 let pipeline = registry.get_pipeline("kv_cache_copy_seq_bf16", device)?;
761
762 let n_heads_bytes = n_heads.to_ne_bytes();
763 let head_dim_bytes = head_dim.to_ne_bytes();
764 let capacity_bytes = capacity.to_ne_bytes();
765 let seq_pos_start_bytes = seq_pos_start.to_ne_bytes();
766 let n_tokens_bytes = n_tokens.to_ne_bytes();
767 let src_tok_offset_bytes = src_tok_offset.to_ne_bytes();
768
769 use super::encode_helpers::{encode_with_args, KernelArg};
770
771 encode_with_args(
772 encoder,
773 pipeline,
774 &[
775 (0, KernelArg::Buffer(src)),
776 (1, KernelArg::Buffer(cache)),
777 (2, KernelArg::Bytes(&n_heads_bytes)),
778 (3, KernelArg::Bytes(&head_dim_bytes)),
779 (4, KernelArg::Bytes(&capacity_bytes)),
780 (5, KernelArg::Bytes(&seq_pos_start_bytes)),
781 (6, KernelArg::Bytes(&n_tokens_bytes)),
782 (7, KernelArg::Bytes(&src_tok_offset_bytes)),
783 ],
784 MTLSize::new(head_dim as u64, n_heads as u64, n_tokens as u64),
785 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
786 );
787
788 Ok(())
789}
790
791#[allow(clippy::too_many_arguments)]
797pub fn dispatch_kv_cache_copy_seq_f32_to_f16(
798 encoder: &mut CommandEncoder,
799 registry: &mut KernelRegistry,
800 device: &metal::DeviceRef,
801 src: &MlxBuffer,
802 cache: &MlxBuffer,
803 n_heads: u32,
804 head_dim: u32,
805 capacity: u32,
806 seq_pos_start: u32,
807 n_tokens: u32,
808 src_tok_offset: u32,
809) -> Result<()> {
810 if n_heads == 0 || head_dim == 0 || n_tokens == 0 {
811 return Ok(());
812 }
813 let total_src = ((src_tok_offset as u64) + (n_tokens as u64))
814 * (n_heads as u64) * (head_dim as u64);
815 if (src.element_count() as u64) < total_src {
816 return Err(MlxError::InvalidArgument(format!(
817 "kv_cache_copy_seq_f32_to_f16: src has {} elements, need {}",
818 src.element_count(), total_src
819 )));
820 }
821
822 let pipeline = registry.get_pipeline("kv_cache_copy_seq_f32_to_f16", device)?;
823
824 let n_heads_bytes = n_heads.to_ne_bytes();
825 let head_dim_bytes = head_dim.to_ne_bytes();
826 let capacity_bytes = capacity.to_ne_bytes();
827 let seq_pos_start_bytes = seq_pos_start.to_ne_bytes();
828 let n_tokens_bytes = n_tokens.to_ne_bytes();
829 let src_tok_offset_bytes = src_tok_offset.to_ne_bytes();
830
831 use super::encode_helpers::{encode_with_args, KernelArg};
832
833 encode_with_args(
834 encoder,
835 pipeline,
836 &[
837 (0, KernelArg::Buffer(src)),
838 (1, KernelArg::Buffer(cache)),
839 (2, KernelArg::Bytes(&n_heads_bytes)),
840 (3, KernelArg::Bytes(&head_dim_bytes)),
841 (4, KernelArg::Bytes(&capacity_bytes)),
842 (5, KernelArg::Bytes(&seq_pos_start_bytes)),
843 (6, KernelArg::Bytes(&n_tokens_bytes)),
844 (7, KernelArg::Bytes(&src_tok_offset_bytes)),
845 ],
846 MTLSize::new(head_dim as u64, n_heads as u64, n_tokens as u64),
847 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
848 );
849
850 Ok(())
851}
852
853#[allow(clippy::too_many_arguments)]
867pub fn dispatch_kv_cache_copy_seq_bf16_to_bf16_head_major(
868 encoder: &mut CommandEncoder,
869 registry: &mut KernelRegistry,
870 device: &metal::DeviceRef,
871 src: &MlxBuffer,
872 cache: &MlxBuffer,
873 n_heads: u32,
874 head_dim: u32,
875 capacity: u32,
876 seq_pos_start: u32,
877 n_tokens: u32,
878 src_tok_offset: u32,
879 src_seq_len: u32,
880) -> Result<()> {
881 if n_heads == 0 || head_dim == 0 || n_tokens == 0 {
882 return Ok(());
883 }
884 let total_src = (n_heads as u64) * (src_seq_len as u64) * (head_dim as u64);
885 if (src.element_count() as u64) < total_src {
886 return Err(MlxError::InvalidArgument(format!(
887 "kv_cache_copy_seq_bf16_to_bf16_head_major: src has {} elements, need {} \
888 ({} heads × {} src_seq_len × {} head_dim)",
889 src.element_count(), total_src, n_heads, src_seq_len, head_dim
890 )));
891 }
892 if src.dtype() != crate::DType::BF16 {
893 return Err(MlxError::InvalidArgument(format!(
894 "kv_cache_copy_seq_bf16_to_bf16_head_major: src must be BF16, got {:?}",
895 src.dtype()
896 )));
897 }
898 if cache.dtype() != crate::DType::BF16 {
899 return Err(MlxError::InvalidArgument(format!(
900 "kv_cache_copy_seq_bf16_to_bf16_head_major: cache must be BF16, got {:?}",
901 cache.dtype()
902 )));
903 }
904
905 let pipeline = registry.get_pipeline("kv_cache_copy_seq_bf16_to_bf16_head_major", device)?;
906
907 let n_heads_bytes = n_heads.to_ne_bytes();
908 let head_dim_bytes = head_dim.to_ne_bytes();
909 let capacity_bytes = capacity.to_ne_bytes();
910 let seq_pos_start_bytes = seq_pos_start.to_ne_bytes();
911 let n_tokens_bytes = n_tokens.to_ne_bytes();
912 let src_tok_offset_bytes = src_tok_offset.to_ne_bytes();
913 let src_seq_len_bytes = src_seq_len.to_ne_bytes();
914
915 use super::encode_helpers::{encode_with_args, KernelArg};
916
917 encode_with_args(
918 encoder,
919 pipeline,
920 &[
921 (0, KernelArg::Buffer(src)),
922 (1, KernelArg::Buffer(cache)),
923 (2, KernelArg::Bytes(&n_heads_bytes)),
924 (3, KernelArg::Bytes(&head_dim_bytes)),
925 (4, KernelArg::Bytes(&capacity_bytes)),
926 (5, KernelArg::Bytes(&seq_pos_start_bytes)),
927 (6, KernelArg::Bytes(&n_tokens_bytes)),
928 (7, KernelArg::Bytes(&src_tok_offset_bytes)),
929 (8, KernelArg::Bytes(&src_seq_len_bytes)),
930 ],
931 MTLSize::new(head_dim as u64, n_heads as u64, n_tokens as u64),
932 MTLSize::new(std::cmp::min(256, head_dim as u64), 1, 1),
933 );
934
935 Ok(())
936}