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