1#![deny(clippy::undocumented_unsafe_blocks)]
14
15use std::ffi::c_void;
16use std::fmt;
17use std::panic::{catch_unwind, AssertUnwindSafe};
18
19pub const MAX_TENSOR_SELECTORS: usize = 128;
21pub const MAX_TENSOR_NAME_BYTES: usize = 256;
23pub const MAX_TENSOR_ROWS: usize = 4_096;
25pub const MAX_TENSOR_ELEMENTS: usize = 16_777_216;
27pub const MAX_RETAINED_TENSOR_BYTES: usize = MAX_TENSOR_ELEMENTS * size_of::<f32>();
29pub const MAX_TENSOR_FAILURE_BYTES: usize = 1_024;
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
34pub enum TensorElementType {
35 F32,
37 I32,
39}
40
41impl TensorElementType {
42 const fn native(self) -> llama_cpp_sys_4::ggml_type {
43 match self {
44 Self::F32 => llama_cpp_sys_4::GGML_TYPE_F32,
45 Self::I32 => llama_cpp_sys_4::GGML_TYPE_I32,
46 }
47 }
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum TensorAccess {
53 ReadOnly,
55 ReadWriteF32,
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum TensorWriteback {
62 Unchanged,
64 Commit,
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum TensorRowMapping {
71 BatchTokens,
73}
74
75#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
82pub enum TensorFiniteValidation {
83 #[default]
86 Strict,
87 OutputOnly,
89 Trusted,
91}
92
93impl TensorFiniteValidation {
94 const fn checks_input(self) -> bool {
96 matches!(self, Self::Strict)
97 }
98
99 const fn checks_output(self) -> bool {
101 matches!(self, Self::Strict | Self::OutputOnly)
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
107pub struct TensorSelector {
108 name: String,
109 element_type: TensorElementType,
110 row_elements: usize,
111 maximum_rows: usize,
112 access: TensorAccess,
113 row_mapping: TensorRowMapping,
114 retain: bool,
115 finite: TensorFiniteValidation,
116}
117
118impl TensorSelector {
119 pub fn new(
127 name: impl Into<String>,
128 element_type: TensorElementType,
129 row_elements: usize,
130 maximum_rows: usize,
131 access: TensorAccess,
132 row_mapping: TensorRowMapping,
133 retain: bool,
134 ) -> Result<Self, TensorTransactionError> {
135 let selector = Self {
136 name: name.into(),
137 element_type,
138 row_elements,
139 maximum_rows,
140 access,
141 row_mapping,
142 retain,
143 finite: TensorFiniteValidation::default(),
144 };
145 selector.validate()?;
146 Ok(selector)
147 }
148
149 #[must_use]
155 pub const fn with_finite_validation(mut self, finite: TensorFiniteValidation) -> Self {
156 self.finite = finite;
157 self
158 }
159
160 #[must_use]
162 pub const fn finite_validation(&self) -> TensorFiniteValidation {
163 self.finite
164 }
165
166 pub fn layer_output(
176 layer: u32,
177 row_elements: usize,
178 maximum_rows: usize,
179 access: TensorAccess,
180 retain: bool,
181 ) -> Result<Self, TensorTransactionError> {
182 Self::new(
183 format!("l_out-{layer}"),
184 TensorElementType::F32,
185 row_elements,
186 maximum_rows,
187 access,
188 TensorRowMapping::BatchTokens,
189 retain,
190 )
191 }
192
193 #[must_use]
195 pub fn name(&self) -> &str {
196 &self.name
197 }
198
199 #[must_use]
201 pub const fn element_type(&self) -> TensorElementType {
202 self.element_type
203 }
204
205 #[must_use]
207 pub const fn row_elements(&self) -> usize {
208 self.row_elements
209 }
210
211 #[must_use]
213 pub const fn maximum_rows(&self) -> usize {
214 self.maximum_rows
215 }
216
217 #[must_use]
219 pub const fn access(&self) -> TensorAccess {
220 self.access
221 }
222
223 #[must_use]
225 pub const fn row_mapping(&self) -> TensorRowMapping {
226 self.row_mapping
227 }
228
229 #[must_use]
231 pub const fn retains_capture(&self) -> bool {
232 self.retain
233 }
234
235 fn validate(&self) -> Result<(), TensorTransactionError> {
236 if self.name.is_empty()
237 || self.name.len() > MAX_TENSOR_NAME_BYTES
238 || self.name.as_bytes().contains(&0)
239 {
240 return Err(TensorTransactionError::new(
241 "tensor name must be bounded, nonempty UTF-8 without NUL",
242 ));
243 }
244 let elements = self
245 .row_elements
246 .checked_mul(self.maximum_rows)
247 .ok_or_else(|| TensorTransactionError::new("tensor element bound overflowed"))?;
248 if self.row_elements == 0
249 || self.maximum_rows == 0
250 || self.maximum_rows > MAX_TENSOR_ROWS
251 || elements > MAX_TENSOR_ELEMENTS
252 {
253 return Err(TensorTransactionError::new(
254 "tensor row shape is outside the supported bound",
255 ));
256 }
257 if self.access == TensorAccess::ReadWriteF32 && self.element_type != TensorElementType::F32
258 {
259 return Err(TensorTransactionError::new(
260 "only f32 tensors support transactional write-back",
261 ));
262 }
263 Ok(())
264 }
265}
266
267#[derive(Clone, Debug, PartialEq, Eq)]
269pub struct TensorBatchRow {
270 pub batch_index: u32,
272 pub position: i32,
274 pub sequence_ids: Vec<i32>,
276}
277
278#[derive(Clone, Copy, Debug, PartialEq, Eq)]
280pub struct TensorShape {
281 pub row_elements: usize,
283 pub rows: usize,
285 pub elements: usize,
287}
288
289pub enum TensorDataMut<'a> {
291 F32(&'a mut [f32]),
293 I32(&'a mut [i32]),
295}
296
297impl fmt::Debug for TensorDataMut<'_> {
298 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
299 match self {
300 Self::F32(values) => formatter
301 .debug_tuple("F32")
302 .field(&format_args!("{} elements", values.len()))
303 .finish(),
304 Self::I32(values) => formatter
305 .debug_tuple("I32")
306 .field(&format_args!("{} elements", values.len()))
307 .finish(),
308 }
309 }
310}
311
312pub struct TensorTransaction<'a> {
314 pub name: &'a str,
316 pub shape: TensorShape,
318 pub rows: &'a [TensorBatchRow],
320 pub access: TensorAccess,
322 pub data: TensorDataMut<'a>,
324}
325
326impl fmt::Debug for TensorTransaction<'_> {
327 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
328 formatter
329 .debug_struct("TensorTransaction")
330 .field("name", &self.name)
331 .field("shape", &self.shape)
332 .field("rows", &self.rows)
333 .field("access", &self.access)
334 .field("data", &self.data)
335 .finish()
336 }
337}
338
339pub trait TensorTransactionHandler: Send {
341 fn apply(
351 &mut self,
352 transaction: TensorTransaction<'_>,
353 ) -> Result<TensorWriteback, TensorTransactionError>;
354}
355
356impl<F> TensorTransactionHandler for F
359where
360 F: FnMut(TensorTransaction<'_>) -> Result<TensorWriteback, TensorTransactionError> + Send,
361{
362 fn apply(
363 &mut self,
364 transaction: TensorTransaction<'_>,
365 ) -> Result<TensorWriteback, TensorTransactionError> {
366 self(transaction)
367 }
368}
369
370#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
372#[error("{message}")]
373pub struct TensorTransactionError {
374 message: String,
375}
376
377impl TensorTransactionError {
378 pub fn new(message: impl Into<String>) -> Self {
380 let mut message = message.into();
381 if message.len() > MAX_TENSOR_FAILURE_BYTES {
382 message.truncate(MAX_TENSOR_FAILURE_BYTES);
383 }
384 Self { message }
385 }
386
387 #[must_use]
389 pub fn message(&self) -> &str {
390 &self.message
391 }
392}
393
394#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
396#[error("tensor callback failed{tensor_suffix}: {message}")]
397pub struct TensorCallbackFailure {
398 tensor: Option<String>,
399 tensor_suffix: String,
400 panicked: bool,
401 message: String,
402}
403
404impl TensorCallbackFailure {
405 fn new(tensor: Option<&str>, panicked: bool, message: impl Into<String>) -> Self {
406 let mut message = message.into();
407 if message.len() > MAX_TENSOR_FAILURE_BYTES {
408 message.truncate(MAX_TENSOR_FAILURE_BYTES);
409 }
410 let tensor = tensor.map(ToOwned::to_owned);
411 let tensor_suffix = tensor
412 .as_deref()
413 .map_or_else(String::new, |name| format!(" for {name}"));
414 Self {
415 tensor,
416 tensor_suffix,
417 panicked,
418 message,
419 }
420 }
421
422 #[must_use]
424 pub fn tensor(&self) -> Option<&str> {
425 self.tensor.as_deref()
426 }
427
428 #[must_use]
430 pub const fn panicked(&self) -> bool {
431 self.panicked
432 }
433
434 #[must_use]
436 pub fn message(&self) -> &str {
437 &self.message
438 }
439}
440
441#[derive(Clone, Debug, PartialEq)]
443pub struct TransactionalTensorCapture {
444 pub name: String,
446 pub shape: TensorShape,
448 pub rows: Vec<TensorBatchRow>,
450 pub data: CapturedTensorData,
452}
453
454#[derive(Clone, Debug, PartialEq)]
456pub enum CapturedTensorData {
457 F32(Vec<f32>),
459 I32(Vec<i32>),
461}
462
463impl CapturedTensorData {
464 #[must_use]
466 pub fn len(&self) -> usize {
467 match self {
468 Self::F32(values) => values.len(),
469 Self::I32(values) => values.len(),
470 }
471 }
472
473 #[must_use]
475 pub fn is_empty(&self) -> bool {
476 self.len() == 0
477 }
478}
479
480pub struct TensorTransactions {
482 selectors: Vec<TensorSelector>,
483 handler: Option<Box<dyn TensorTransactionHandler>>,
484 captures: Vec<TransactionalTensorCapture>,
485 retained_bytes: usize,
486 pending_captures: Vec<TransactionalTensorCapture>,
487 pending_retained_bytes: usize,
488 batch_rows: Vec<TensorBatchRow>,
489 rows_seen: Vec<usize>,
492 rollback_f32: Vec<f32>,
495 failure: Option<TensorCallbackFailure>,
496 decode_active: bool,
497}
498
499impl fmt::Debug for TensorTransactions {
500 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
501 formatter
502 .debug_struct("TensorTransactions")
503 .field("selectors", &self.selectors)
504 .field("has_handler", &self.handler.is_some())
505 .field("captures", &self.captures.len())
506 .field("retained_bytes", &self.retained_bytes)
507 .field("pending_captures", &self.pending_captures.len())
508 .field("pending_retained_bytes", &self.pending_retained_bytes)
509 .field("failure", &self.failure)
510 .field("decode_active", &self.decode_active)
511 .finish_non_exhaustive()
512 }
513}
514
515impl TensorTransactions {
516 pub fn capture(selectors: Vec<TensorSelector>) -> Result<Self, TensorTransactionError> {
523 Self::build(selectors, None)
524 }
525
526 pub fn new(
533 selectors: Vec<TensorSelector>,
534 handler: impl TensorTransactionHandler + 'static,
535 ) -> Result<Self, TensorTransactionError> {
536 Self::build(selectors, Some(Box::new(handler)))
537 }
538
539 fn build(
540 selectors: Vec<TensorSelector>,
541 handler: Option<Box<dyn TensorTransactionHandler>>,
542 ) -> Result<Self, TensorTransactionError> {
543 if selectors.is_empty() || selectors.len() > MAX_TENSOR_SELECTORS {
544 return Err(TensorTransactionError::new(
545 "selector count is outside the supported bound",
546 ));
547 }
548 let mut total_elements = 0_usize;
549 let mut prior_name: Option<&str> = None;
550 let mut needs_handler = false;
551 for selector in &selectors {
552 selector.validate()?;
553 if prior_name.is_some_and(|prior| prior >= selector.name()) {
554 return Err(TensorTransactionError::new(
555 "selectors must have unique canonically ordered names",
556 ));
557 }
558 prior_name = Some(selector.name());
559 needs_handler |= selector.access == TensorAccess::ReadWriteF32;
560 total_elements = total_elements
561 .checked_add(
562 selector
563 .row_elements
564 .checked_mul(selector.maximum_rows)
565 .ok_or_else(|| {
566 TensorTransactionError::new("selector element bound overflowed")
567 })?,
568 )
569 .ok_or_else(|| {
570 TensorTransactionError::new("total selector element bound overflowed")
571 })?;
572 }
573 if total_elements > MAX_TENSOR_ELEMENTS {
574 return Err(TensorTransactionError::new(
575 "total selector element bound is excessive",
576 ));
577 }
578 if needs_handler && handler.is_none() {
579 return Err(TensorTransactionError::new(
580 "mutable selectors require a transaction handler",
581 ));
582 }
583 if !needs_handler && handler.is_some() {
584 return Err(TensorTransactionError::new(
585 "a transaction handler requires at least one mutable selector",
586 ));
587 }
588 let selector_count = selectors.len();
589 Ok(Self {
590 selectors,
591 handler,
592 captures: Vec::new(),
593 retained_bytes: 0,
594 pending_captures: Vec::new(),
595 pending_retained_bytes: 0,
596 batch_rows: Vec::new(),
597 rows_seen: vec![0; selector_count],
598 rollback_f32: Vec::new(),
599 failure: None,
600 decode_active: false,
601 })
602 }
603
604 #[must_use]
606 pub fn selectors(&self) -> &[TensorSelector] {
607 &self.selectors
608 }
609
610 #[must_use]
616 pub fn captures(&self) -> &[TransactionalTensorCapture] {
617 &self.captures
618 }
619
620 pub fn take_captures(&mut self) -> Vec<TransactionalTensorCapture> {
622 self.retained_bytes = 0;
623 std::mem::take(&mut self.captures)
624 }
625
626 #[must_use]
628 pub const fn failure(&self) -> Option<&TensorCallbackFailure> {
629 self.failure.as_ref()
630 }
631
632 fn begin_decode_raw(
633 &mut self,
634 batch: &llama_cpp_sys_4::llama_batch,
635 ) -> Result<(), TensorCallbackFailure> {
636 if let Some(failure) = self.failure.clone() {
637 return Err(failure);
638 }
639 if self.decode_active {
640 return Err(TensorCallbackFailure::new(
641 None,
642 false,
643 "tensor callback decode was already active",
644 ));
645 }
646 self.pending_captures.clear();
647 self.pending_retained_bytes = 0;
648 self.rows_seen.fill(0);
649 self.batch_rows = copy_batch_rows(batch)?;
650 self.decode_active = true;
651 Ok(())
652 }
653
654 pub(crate) fn finish_decode(
655 &mut self,
656 native_succeeded: bool,
657 ) -> Result<(), TensorCallbackFailure> {
658 self.decode_active = false;
659 let expected_rows = self.batch_rows.len();
660 self.batch_rows.clear();
661 if let Some(failure) = self.failure.clone() {
662 self.pending_captures.clear();
663 self.pending_retained_bytes = 0;
664 return Err(failure);
665 }
666 if !native_succeeded {
667 self.pending_captures.clear();
668 self.pending_retained_bytes = 0;
669 return Ok(());
670 }
671 for (index, selector) in self.selectors.iter().enumerate() {
672 let rows = self.rows_seen[index];
673 let complete = match selector.row_mapping {
674 TensorRowMapping::BatchTokens => rows == expected_rows,
675 };
676 if !complete {
677 let failure = TensorCallbackFailure::new(
678 Some(selector.name()),
679 false,
680 format!(
681 "selected tensor covered {rows} rows but the decode submitted \
682 {expected_rows}"
683 ),
684 );
685 self.failure = Some(failure.clone());
686 self.pending_captures.clear();
687 self.pending_retained_bytes = 0;
688 return Err(failure);
689 }
690 }
691 self.retained_bytes = self
692 .retained_bytes
693 .checked_add(self.pending_retained_bytes)
694 .ok_or_else(|| {
695 TensorCallbackFailure::new(None, false, "committed retained byte count overflowed")
696 })?;
697 self.captures.append(&mut self.pending_captures);
698 self.pending_retained_bytes = 0;
699 Ok(())
700 }
701
702 fn selected(&self, name: &[u8]) -> Option<usize> {
703 self.selectors
704 .binary_search_by(|selector| selector.name().as_bytes().cmp(name))
705 .ok()
706 }
707
708 fn process(
709 &mut self,
710 tensor: *mut llama_cpp_sys_4::ggml_tensor,
711 selector_index: usize,
712 ) -> Result<(), TensorTransactionError> {
713 let staged = {
716 let Self {
717 selectors,
718 handler,
719 batch_rows,
720 rows_seen,
721 rollback_f32,
722 ..
723 } = &mut *self;
724 let selector = &selectors[selector_index];
725 let shape = validate_tensor(tensor, selector)?;
726 let start = match selector.row_mapping {
727 TensorRowMapping::BatchTokens => rows_seen[selector_index],
728 };
729 let end = start
730 .checked_add(shape.rows)
731 .ok_or_else(|| TensorTransactionError::new("tensor row mapping overflowed"))?;
732 if end > batch_rows.len() {
733 return Err(TensorTransactionError::new(
734 "tensor rows exceed submitted decode batch",
735 ));
736 }
737
738 let captured: Option<CapturedTensorData> = match selector.element_type {
739 TensorElementType::F32 => {
740 let mut values = read_tensor::<f32>(tensor, shape.elements)?;
743 if selector.finite.checks_input() && !all_finite(&values) {
744 return Err(TensorTransactionError::new(
745 "selected f32 tensor contains a non-finite value",
746 ));
747 }
748 if selector.access == TensorAccess::ReadWriteF32 {
749 let rolled_back = selector.retain;
752 if rolled_back {
753 rollback_f32.clear();
754 rollback_f32.extend_from_slice(&values);
755 }
756 let handler = handler.as_deref_mut().ok_or_else(|| {
757 TensorTransactionError::new("mutable tensor handler is unavailable")
758 })?;
759 let writeback = handler.apply(TensorTransaction {
760 name: selector.name(),
761 shape,
762 rows: &batch_rows[start..end],
763 access: selector.access,
764 data: TensorDataMut::F32(&mut values),
765 })?;
766 match writeback {
767 TensorWriteback::Unchanged => {
768 if rolled_back {
769 values.clear();
770 values.extend_from_slice(rollback_f32);
771 }
772 }
773 TensorWriteback::Commit => {
774 if selector.finite.checks_output() && !all_finite(&values) {
775 return Err(TensorTransactionError::new(
776 "transaction produced a non-finite f32 value",
777 ));
778 }
779 copy_tensor_set(tensor, &values)?;
780 }
781 }
782 }
783 selector.retain.then_some(CapturedTensorData::F32(values))
784 }
785 TensorElementType::I32 => {
786 let values = read_tensor::<i32>(tensor, shape.elements)?;
787 selector.retain.then_some(CapturedTensorData::I32(values))
788 }
789 };
790
791 rows_seen[selector_index] = end;
792
793 captured.map(|data| {
795 (
796 selectors[selector_index].name().to_owned(),
797 shape,
798 batch_rows[start..end].to_vec(),
799 data,
800 )
801 })
802 };
803
804 if let Some((name, shape, rows, data)) = staged {
805 self.retain(name, shape, rows, data)?;
806 }
807 Ok(())
808 }
809
810 fn retain(
811 &mut self,
812 name: String,
813 shape: TensorShape,
814 rows: Vec<TensorBatchRow>,
815 data: CapturedTensorData,
816 ) -> Result<(), TensorTransactionError> {
817 let bytes = data
818 .len()
819 .checked_mul(size_of::<f32>())
820 .ok_or_else(|| TensorTransactionError::new("retained byte count overflowed"))?;
821 self.pending_retained_bytes = self
822 .pending_retained_bytes
823 .checked_add(bytes)
824 .ok_or_else(|| TensorTransactionError::new("retained byte count overflowed"))?;
825 let total_retained_bytes = self
826 .retained_bytes
827 .checked_add(self.pending_retained_bytes)
828 .ok_or_else(|| TensorTransactionError::new("retained byte count overflowed"))?;
829 if total_retained_bytes > MAX_RETAINED_TENSOR_BYTES {
830 return Err(TensorTransactionError::new(
831 "retained tensor bytes exceed the supported bound",
832 ));
833 }
834 self.pending_captures.push(TransactionalTensorCapture {
835 name,
836 shape,
837 rows,
838 data,
839 });
840 Ok(())
841 }
842
843 fn record_failure(&mut self, tensor: Option<&str>, panicked: bool, message: impl Into<String>) {
844 if self.failure.is_none() {
845 self.failure = Some(TensorCallbackFailure::new(tensor, panicked, message));
846 }
847 }
848}
849
850fn validate_tensor(
851 tensor: *mut llama_cpp_sys_4::ggml_tensor,
852 selector: &TensorSelector,
853) -> Result<TensorShape, TensorTransactionError> {
854 if tensor.is_null() {
855 return Err(TensorTransactionError::new(
856 "native tensor pointer was null",
857 ));
858 }
859 let tensor_ref = unsafe { &*tensor };
862 if tensor_ref.type_ != selector.element_type.native() {
863 return Err(TensorTransactionError::new(
864 "native tensor element type does not match selector",
865 ));
866 }
867 if tensor_ref.ne[2] != 1 || tensor_ref.ne[3] != 1 {
868 return Err(TensorTransactionError::new(
869 "selected tensor must be a two-dimensional row matrix",
870 ));
871 }
872 let row_elements = usize::try_from(tensor_ref.ne[0])
873 .map_err(|_| TensorTransactionError::new("native row width is negative or excessive"))?;
874 let rows = usize::try_from(tensor_ref.ne[1])
875 .map_err(|_| TensorTransactionError::new("native row count is negative or excessive"))?;
876 let elements = row_elements
877 .checked_mul(rows)
878 .ok_or_else(|| TensorTransactionError::new("native tensor element count overflowed"))?;
879 if row_elements != selector.row_elements
880 || rows == 0
881 || rows > selector.maximum_rows
882 || elements > MAX_TENSOR_ELEMENTS
883 {
884 return Err(TensorTransactionError::new(
885 "native tensor shape does not match selector",
886 ));
887 }
888 if !unsafe { llama_cpp_sys_4::ggml_is_contiguous(tensor) } {
890 return Err(TensorTransactionError::new(
891 "selected tensor is not contiguous",
892 ));
893 }
894 let expected_bytes = elements
895 .checked_mul(size_of::<f32>())
896 .ok_or_else(|| TensorTransactionError::new("native tensor byte count overflowed"))?;
897 if unsafe { llama_cpp_sys_4::ggml_nbytes(tensor) } != expected_bytes {
899 return Err(TensorTransactionError::new(
900 "native tensor byte size does not match selector",
901 ));
902 }
903 Ok(TensorShape {
904 row_elements,
905 rows,
906 elements,
907 })
908}
909
910fn read_tensor<T: Copy>(
916 tensor: *mut llama_cpp_sys_4::ggml_tensor,
917 elements: usize,
918) -> Result<Vec<T>, TensorTransactionError> {
919 let bytes = elements
920 .checked_mul(size_of::<T>())
921 .ok_or_else(|| TensorTransactionError::new("native tensor byte count overflowed"))?;
922 if bytes == 0 {
923 return Err(TensorTransactionError::new(
924 "cannot copy an empty native tensor",
925 ));
926 }
927 let mut values: Vec<T> = Vec::with_capacity(elements);
928 unsafe {
933 llama_cpp_sys_4::ggml_backend_tensor_get(
934 tensor,
935 values.as_mut_ptr().cast::<c_void>(),
936 0,
937 bytes,
938 );
939 values.set_len(elements);
940 }
941 Ok(values)
942}
943
944fn all_finite(values: &[f32]) -> bool {
948 const EXPONENT_MASK: u32 = 0x7F80_0000;
949 let mut non_finite = 0_u32;
950 for &value in values {
951 non_finite |= u32::from((value.to_bits() & EXPONENT_MASK) == EXPONENT_MASK);
952 }
953 non_finite == 0
954}
955
956fn copy_tensor_set<T>(
957 tensor: *mut llama_cpp_sys_4::ggml_tensor,
958 values: &[T],
959) -> Result<(), TensorTransactionError> {
960 let bytes = size_of_val(values);
961 if bytes == 0 {
962 return Err(TensorTransactionError::new(
963 "cannot write an empty native tensor",
964 ));
965 }
966 unsafe {
970 llama_cpp_sys_4::ggml_backend_tensor_set(
971 tensor,
972 values.as_ptr().cast::<c_void>(),
973 0,
974 bytes,
975 );
976 }
977 Ok(())
978}
979
980fn copy_batch_rows(
981 batch: &llama_cpp_sys_4::llama_batch,
982) -> Result<Vec<TensorBatchRow>, TensorCallbackFailure> {
983 let count = usize::try_from(batch.n_tokens).map_err(|_| {
984 TensorCallbackFailure::new(None, false, "decode batch token count is negative")
985 })?;
986 if count == 0 || count > MAX_TENSOR_ROWS {
987 return Err(TensorCallbackFailure::new(
988 None,
989 false,
990 "decode batch token count is outside the callback bound",
991 ));
992 }
993 if batch.pos.is_null() || batch.n_seq_id.is_null() || batch.seq_id.is_null() {
994 return Err(TensorCallbackFailure::new(
995 None,
996 false,
997 "decode batch metadata pointers are null",
998 ));
999 }
1000 let mut rows = Vec::with_capacity(count);
1001 for index in 0..count {
1002 let position = unsafe { *batch.pos.add(index) };
1006 let sequence_count = unsafe { *batch.n_seq_id.add(index) };
1008 let sequence_count = usize::try_from(sequence_count).map_err(|_| {
1009 TensorCallbackFailure::new(None, false, "decode batch sequence count is negative")
1010 })?;
1011 if sequence_count == 0 || sequence_count > MAX_TENSOR_ROWS {
1012 return Err(TensorCallbackFailure::new(
1013 None,
1014 false,
1015 "decode batch sequence count is outside the callback bound",
1016 ));
1017 }
1018 let sequence_ptr = unsafe { *batch.seq_id.add(index) };
1021 if sequence_ptr.is_null() {
1022 return Err(TensorCallbackFailure::new(
1023 None,
1024 false,
1025 "decode batch sequence pointer is null",
1026 ));
1027 }
1028 let sequence_ids =
1030 unsafe { std::slice::from_raw_parts(sequence_ptr, sequence_count) }.to_vec();
1031 rows.push(TensorBatchRow {
1032 batch_index: u32::try_from(index)
1033 .map_err(|_| TensorCallbackFailure::new(None, false, "batch index exceeds u32"))?,
1034 position,
1035 sequence_ids,
1036 });
1037 }
1038 Ok(rows)
1039}
1040
1041pub(crate) unsafe extern "C" fn tensor_transaction_decode_begin(
1042 batch: *const llama_cpp_sys_4::llama_batch,
1043 user_data: *mut c_void,
1044) -> bool {
1045 if batch.is_null() || user_data.is_null() {
1046 return false;
1047 }
1048 let state = unsafe { &mut *user_data.cast::<TensorTransactions>() };
1051 let batch = unsafe { &*batch };
1053 let result = catch_unwind(AssertUnwindSafe(|| state.begin_decode_raw(batch)));
1054 match result {
1055 Ok(Ok(())) => true,
1056 Ok(Err(error)) => {
1057 state.record_failure(None, false, error.to_string());
1058 false
1059 }
1060 Err(_) => {
1061 state.record_failure(None, true, "tensor decode-begin callback panicked");
1062 false
1063 }
1064 }
1065}
1066
1067pub(crate) unsafe extern "C" fn tensor_transaction_decode_end(
1068 native_succeeded: bool,
1069 user_data: *mut c_void,
1070) -> bool {
1071 if user_data.is_null() {
1072 return false;
1073 }
1074 let state = unsafe { &mut *user_data.cast::<TensorTransactions>() };
1077 let result = catch_unwind(AssertUnwindSafe(|| state.finish_decode(native_succeeded)));
1078 match result {
1079 Ok(Ok(())) => true,
1080 Ok(Err(error)) => {
1081 state.record_failure(error.tensor(), error.panicked(), error.message());
1082 false
1083 }
1084 Err(_) => {
1085 state.record_failure(None, true, "tensor decode-end callback panicked");
1086 false
1087 }
1088 }
1089}
1090
1091pub(crate) unsafe extern "C" fn tensor_transaction_callback(
1092 tensor: *mut llama_cpp_sys_4::ggml_tensor,
1093 ask: bool,
1094 user_data: *mut c_void,
1095) -> bool {
1096 if tensor.is_null() || user_data.is_null() {
1097 return false;
1098 }
1099 let state = unsafe { &mut *user_data.cast::<TensorTransactions>() };
1102 if !state.decode_active {
1103 state.record_failure(
1104 None,
1105 false,
1106 "tensor evaluation callback ran outside a decode lifecycle",
1107 );
1108 return false;
1109 }
1110 if state.failure.is_some() {
1111 return false;
1114 }
1115 let name_bytes = unsafe { &(*tensor).name };
1117 let length = name_bytes
1118 .iter()
1119 .position(|value| *value == 0)
1120 .unwrap_or(name_bytes.len());
1121 let raw_name = unsafe { std::slice::from_raw_parts(name_bytes.as_ptr().cast::<u8>(), length) };
1125 let Some(selector_index) = state.selected(raw_name) else {
1126 return false;
1127 };
1128 if ask {
1129 return true;
1130 }
1131
1132 let result = catch_unwind(AssertUnwindSafe(|| state.process(tensor, selector_index)));
1133 match result {
1134 Ok(Ok(())) => true,
1135 Ok(Err(error)) => {
1136 let name = state.selectors[selector_index].name().to_owned();
1138 state.record_failure(Some(&name), false, error.to_string());
1139 true
1140 }
1141 Err(payload) => {
1142 let message = payload
1143 .downcast_ref::<&str>()
1144 .map_or_else(
1145 || {
1146 payload
1147 .downcast_ref::<String>()
1148 .map_or("tensor handler panicked", String::as_str)
1149 },
1150 |message| *message,
1151 )
1152 .to_owned();
1153 let name = state.selectors[selector_index].name().to_owned();
1154 state.record_failure(Some(&name), true, message);
1155 true
1156 }
1157 }
1158}
1159
1160#[cfg(test)]
1161mod tests {
1162 use super::*;
1163
1164 struct AddOne;
1165
1166 impl TensorTransactionHandler for AddOne {
1167 fn apply(
1168 &mut self,
1169 mut transaction: TensorTransaction<'_>,
1170 ) -> Result<TensorWriteback, TensorTransactionError> {
1171 let TensorDataMut::F32(values) = &mut transaction.data else {
1172 return Err(TensorTransactionError::new("expected f32"));
1173 };
1174 for value in values.iter_mut() {
1175 *value += 1.0;
1176 }
1177 Ok(TensorWriteback::Commit)
1178 }
1179 }
1180
1181 #[test]
1182 fn selectors_are_bounded_and_canonical() {
1183 let selector = TensorSelector::layer_output(1, 4, 2, TensorAccess::ReadOnly, true).unwrap();
1184 assert_eq!(selector.name(), "l_out-1");
1185 assert!(TensorSelector::new(
1186 "bad\0name",
1187 TensorElementType::F32,
1188 4,
1189 2,
1190 TensorAccess::ReadOnly,
1191 TensorRowMapping::BatchTokens,
1192 true,
1193 )
1194 .is_err());
1195 assert!(TensorSelector::new(
1196 "integer",
1197 TensorElementType::I32,
1198 4,
1199 2,
1200 TensorAccess::ReadWriteF32,
1201 TensorRowMapping::BatchTokens,
1202 true,
1203 )
1204 .is_err());
1205 }
1206
1207 #[test]
1208 fn transaction_sets_require_a_handler_and_ordered_names() {
1209 let mutable =
1210 TensorSelector::layer_output(1, 4, 2, TensorAccess::ReadWriteF32, false).unwrap();
1211 assert!(TensorTransactions::capture(vec![mutable.clone()]).is_err());
1212 assert!(TensorTransactions::new(vec![mutable], AddOne).is_ok());
1213
1214 let later = TensorSelector::layer_output(2, 4, 2, TensorAccess::ReadOnly, true).unwrap();
1215 let earlier = TensorSelector::layer_output(1, 4, 2, TensorAccess::ReadOnly, true).unwrap();
1216 assert!(TensorTransactions::capture(vec![later, earlier]).is_err());
1217 }
1218
1219 #[test]
1220 fn errors_and_failure_messages_are_bounded() {
1221 let error = TensorTransactionError::new("x".repeat(MAX_TENSOR_FAILURE_BYTES + 10));
1222 assert_eq!(error.message().len(), MAX_TENSOR_FAILURE_BYTES);
1223 let failure = TensorCallbackFailure::new(
1224 Some("l_out-1"),
1225 true,
1226 "y".repeat(MAX_TENSOR_FAILURE_BYTES + 10),
1227 );
1228 assert!(failure.panicked());
1229 assert_eq!(failure.message().len(), MAX_TENSOR_FAILURE_BYTES);
1230 assert_eq!(failure.tensor(), Some("l_out-1"));
1231 }
1232
1233 #[test]
1234 fn successful_internal_decodes_accumulate_and_failed_staging_is_discarded() {
1235 let selector = TensorSelector::layer_output(1, 1, 1, TensorAccess::ReadOnly, true).unwrap();
1236 let mut transactions = TensorTransactions::capture(vec![selector]).unwrap();
1237
1238 let stage = |transactions: &mut TensorTransactions, value: f32, succeeded: bool| {
1239 transactions.decode_active = true;
1240 transactions.batch_rows = vec![TensorBatchRow {
1241 batch_index: 0,
1242 position: 0,
1243 sequence_ids: vec![0],
1244 }];
1245 transactions.rows_seen[0] = 1;
1246 transactions
1247 .retain(
1248 "l_out-1".to_owned(),
1249 TensorShape {
1250 row_elements: 1,
1251 rows: 1,
1252 elements: 1,
1253 },
1254 transactions.batch_rows.clone(),
1255 CapturedTensorData::F32(vec![value]),
1256 )
1257 .unwrap();
1258 transactions.finish_decode(succeeded).unwrap();
1259 };
1260
1261 stage(&mut transactions, 1.0, true);
1262 stage(&mut transactions, 2.0, true);
1263 stage(&mut transactions, 3.0, false);
1264 let captures = transactions.take_captures();
1265 assert_eq!(captures.len(), 2);
1266 assert!(matches!(
1267 captures[0].data,
1268 CapturedTensorData::F32(ref values) if values == &[1.0]
1269 ));
1270 assert!(matches!(
1271 captures[1].data,
1272 CapturedTensorData::F32(ref values) if values == &[2.0]
1273 ));
1274 assert!(transactions.captures().is_empty());
1275 }
1276
1277 #[test]
1278 fn closures_are_handlers() {
1279 let selector =
1281 TensorSelector::layer_output(1, 4, 2, TensorAccess::ReadWriteF32, false).unwrap();
1282 let transactions =
1283 TensorTransactions::new(vec![selector], |mut txn: TensorTransaction<'_>| {
1284 if let TensorDataMut::F32(values) = &mut txn.data {
1285 for value in values.iter_mut() {
1286 *value *= 2.0;
1287 }
1288 }
1289 Ok(TensorWriteback::Commit)
1290 });
1291 assert!(transactions.is_ok());
1292 }
1293
1294 #[test]
1295 fn all_finite_detects_non_finite() {
1296 assert!(all_finite(&[0.0, 1.0, -1.0, f32::MAX, f32::MIN, -0.0]));
1297 assert!(all_finite(&[]));
1298 assert!(!all_finite(&[1.0, f32::INFINITY]));
1299 assert!(!all_finite(&[f32::NEG_INFINITY]));
1300 assert!(!all_finite(&[f32::NAN]));
1301 }
1302
1303 #[test]
1304 fn finite_validation_policy() {
1305 assert!(TensorFiniteValidation::Strict.checks_input());
1306 assert!(TensorFiniteValidation::Strict.checks_output());
1307 assert!(!TensorFiniteValidation::OutputOnly.checks_input());
1308 assert!(TensorFiniteValidation::OutputOnly.checks_output());
1309 assert!(!TensorFiniteValidation::Trusted.checks_input());
1310 assert!(!TensorFiniteValidation::Trusted.checks_output());
1311
1312 let selector = TensorSelector::layer_output(1, 4, 2, TensorAccess::ReadOnly, true)
1313 .unwrap()
1314 .with_finite_validation(TensorFiniteValidation::Trusted);
1315 assert_eq!(
1316 selector.finite_validation(),
1317 TensorFiniteValidation::Trusted
1318 );
1319 }
1320}