1use std::sync::Arc;
10
11use onnx_runtime_ir::{DataType, TensorLayout, compute_contiguous_strides};
12
13use crate::tensor::{SharedTensorBuffer, host_bytes};
14use crate::{SessionError, Tensor};
15
16pub type SequenceResult<T> = std::result::Result<T, SequenceError>;
18
19#[derive(Debug, thiserror::Error)]
21pub enum SequenceError {
22 #[error(
23 "SequenceConstruct requires at least one tensor; use SequenceValue::empty(dtype) for an empty sequence"
24 )]
25 EmptyConstruct,
26
27 #[error(
28 "{op} element{index_suffix} dtype {actual:?} does not match expected {expected:?}; ONNX sequences are homogeneous. To fix: Cast the tensor to {expected:?}",
29 index_suffix = index.map(|i| format!(" {i}")).unwrap_or_default()
30 )]
31 DtypeMismatch {
32 op: &'static str,
33 index: Option<usize>,
34 expected: DataType,
35 actual: DataType,
36 },
37
38 #[error(
39 "{op} index {index} is out of bounds for a sequence of length {len} (valid range {range})",
40 range = if *insertion {
41 format!("[{}, {}]", -(*len as i128), *len)
42 } else if *len == 0 {
43 "empty (no valid indices)".to_string()
44 } else {
45 format!("[{}, {}]", -(*len as i128), *len as i128 - 1)
46 }
47 )]
48 IndexOutOfBounds {
49 op: &'static str,
50 index: i64,
51 len: usize,
52 insertion: bool,
53 },
54
55 #[error("SequenceErase cannot erase from an empty sequence")]
56 EmptyErase,
57
58 #[error("{op} cannot represent sequence length {len} as an ONNX index")]
59 LengthOverflow { op: &'static str, len: usize },
60
61 #[error(
62 "{op} axis {axis} is invalid for rank {rank}{new_axis_suffix}",
63 new_axis_suffix = if *new_axis { " with new_axis=1" } else { "" }
64 )]
65 InvalidAxis {
66 op: &'static str,
67 axis: i64,
68 rank: usize,
69 new_axis: bool,
70 },
71
72 #[error("{op} has invalid split specification: {reason}")]
73 InvalidSplit { op: &'static str, reason: String },
74
75 #[error(
76 "{op} element {index} has shape {actual:?}, incompatible with {expected:?}: {requirement}"
77 )]
78 ShapeMismatch {
79 op: &'static str,
80 index: usize,
81 expected: Vec<usize>,
82 actual: Vec<usize>,
83 requirement: &'static str,
84 },
85
86 #[error("{op} does not support byte operations for sub-byte dtype {dtype:?}")]
87 UnsupportedDtype { op: &'static str, dtype: DataType },
88
89 #[error("{op} requires host-accessible sequence tensors, but element {index} is on {device}")]
90 NonHostTensor {
91 op: &'static str,
92 index: usize,
93 device: String,
94 },
95
96 #[error(
97 "SequenceTensor cannot borrow contiguous bytes for shape {shape:?} on {device}: {reason}; use contiguous_bytes() to materialize the view"
98 )]
99 ByteBorrowUnavailable {
100 shape: Vec<usize>,
101 device: String,
102 reason: &'static str,
103 },
104
105 #[error(
106 "{op} received {actual} bytes for shape {shape:?} dtype {dtype:?}, expected {expected}"
107 )]
108 ByteLengthMismatch {
109 op: &'static str,
110 dtype: DataType,
111 shape: Vec<usize>,
112 expected: usize,
113 actual: usize,
114 },
115
116 #[error("{op} shape/offset overflow while computing {context} for shape {shape:?}")]
117 ShapeOverflow {
118 op: &'static str,
119 context: &'static str,
120 shape: Vec<usize>,
121 },
122
123 #[error("{op} cannot allocate {bytes} bytes for {context}")]
124 Allocation {
125 op: &'static str,
126 context: &'static str,
127 bytes: usize,
128 },
129
130 #[error("{op} could not create a tensor: {source}")]
131 TensorCreation {
132 op: &'static str,
133 #[source]
134 source: SessionError,
135 },
136}
137
138impl SequenceError {
139 pub(crate) fn op(&self) -> &'static str {
140 match self {
141 Self::EmptyConstruct => "SequenceConstruct",
142 Self::DtypeMismatch { op, .. }
143 | Self::IndexOutOfBounds { op, .. }
144 | Self::LengthOverflow { op, .. }
145 | Self::InvalidAxis { op, .. }
146 | Self::InvalidSplit { op, .. }
147 | Self::ShapeMismatch { op, .. }
148 | Self::UnsupportedDtype { op, .. }
149 | Self::NonHostTensor { op, .. }
150 | Self::ByteLengthMismatch { op, .. }
151 | Self::ShapeOverflow { op, .. }
152 | Self::Allocation { op, .. }
153 | Self::TensorCreation { op, .. } => op,
154 Self::EmptyErase => "SequenceErase",
155 Self::ByteBorrowUnavailable { .. } => "SequenceTensor",
156 }
157 }
158}
159
160impl From<SequenceError> for SessionError {
161 fn from(error: SequenceError) -> Self {
162 if let SequenceError::ShapeOverflow { context, shape, .. } = error {
163 return SessionError::ShapeOverflow {
164 value: context.to_string(),
165 dims: shape,
166 };
167 }
168 let op = error.op().to_string();
169 SessionError::SequenceOp {
170 op,
171 reason: error.to_string(),
172 }
173 }
174}
175
176#[derive(Clone, Debug)]
182pub struct SeqTensor {
183 storage: Arc<SharedTensorBuffer>,
184 pub dtype: DataType,
185 pub shape: Vec<usize>,
186 pub layout: TensorLayout,
187 byte_offset: usize,
188}
189
190impl SeqTensor {
191 pub fn new(tensor: Tensor) -> Self {
193 let (storage, dtype, shape, layout) = tensor.into_shared_parts();
194 Self {
195 storage,
196 dtype,
197 shape,
198 layout,
199 byte_offset: 0,
200 }
201 }
202
203 pub fn from_raw(dtype: DataType, shape: Vec<usize>, bytes: &[u8]) -> SequenceResult<Self> {
205 validate_tensor_bytes("SequenceTensor", bytes, dtype, &shape)?;
206 let mut storage = SharedTensorBuffer::allocate_cpu(bytes.len()).map_err(|source| {
207 SequenceError::TensorCreation {
208 op: "SequenceTensor",
209 source,
210 }
211 })?;
212 let allocator = Arc::clone(storage.allocator());
213 allocator
214 .copy_from_host(
215 bytes,
216 Arc::get_mut(&mut storage)
217 .expect("fresh sequence storage is uniquely owned")
218 .buffer_mut(),
219 )
220 .map_err(|source| SequenceError::TensorCreation {
221 op: "SequenceTensor",
222 source: source.into(),
223 })?;
224 Ok(Self {
225 storage,
226 dtype,
227 shape,
228 layout: TensorLayout::contiguous(),
229 byte_offset: 0,
230 })
231 }
232
233 pub(crate) fn from_shared(
234 storage: Arc<SharedTensorBuffer>,
235 dtype: DataType,
236 shape: Vec<usize>,
237 layout: TensorLayout,
238 byte_offset: usize,
239 ) -> SequenceResult<Self> {
240 let strides = layout.resolved_strides(&shape);
241 validate_view_bounds(
242 "SequenceTensor",
243 &shape,
244 &strides,
245 byte_offset,
246 dtype,
247 storage.buffer().len(),
248 )?;
249 Ok(Self {
250 storage,
251 dtype,
252 shape,
253 layout,
254 byte_offset,
255 })
256 }
257
258 pub fn shares_storage_with(&self, other: &Self) -> bool {
260 Arc::ptr_eq(&self.storage, &other.storage)
261 }
262
263 pub fn storage_strong_count(&self) -> usize {
265 Arc::strong_count(&self.storage)
266 }
267
268 pub(crate) fn storage(&self) -> &Arc<SharedTensorBuffer> {
269 &self.storage
270 }
271
272 pub fn as_ptr(&self) -> *const std::ffi::c_void {
274 self.storage.buffer().as_ptr()
275 }
276
277 pub fn byte_offset(&self) -> usize {
279 self.byte_offset
280 }
281
282 pub fn device(&self) -> onnx_runtime_ir::DeviceId {
283 self.storage.buffer().device()
284 }
285
286 pub fn numel(&self) -> usize {
287 self.shape.iter().product()
288 }
289
290 pub(crate) fn root_len(&self) -> usize {
291 self.storage.buffer().len()
292 }
293
294 pub fn contiguous_bytes(&self) -> SequenceResult<Vec<u8>> {
296 const OP: &str = "SequenceTensor";
297 let esize = self.dtype.byte_size();
298 if esize == 0 {
299 return Err(SequenceError::UnsupportedDtype {
300 op: OP,
301 dtype: self.dtype,
302 });
303 }
304 let mut copied;
305 let root = if self.device().is_host_accessible() {
306 host_bytes(self.storage.buffer())
307 } else {
308 copied = zeroed_bytes(OP, "device tensor download", self.root_len(), &self.shape)?;
309 self.storage
310 .allocator()
311 .copy_to_host(self.storage.buffer(), &mut copied)
312 .map_err(|source| SequenceError::TensorCreation {
313 op: OP,
314 source: source.into(),
315 })?;
316 &copied
317 };
318 let strides = self.layout.resolved_strides(&self.shape);
319 if onnx_runtime_ir::is_contiguous(&self.shape, &strides) {
320 let bytes = self
321 .dtype
322 .checked_storage_bytes(self.numel())
323 .ok_or_else(|| overflow(OP, "tensor byte count", &self.shape))?;
324 let end = checked_add(
325 OP,
326 "tensor byte range",
327 self.byte_offset,
328 bytes,
329 &self.shape,
330 )?;
331 return Ok(root[self.byte_offset..end].to_vec());
332 }
333 gather_strided(
334 root,
335 &self.shape,
336 &strides,
337 self.byte_offset,
338 self.dtype,
339 esize,
340 )
341 }
342
343 fn write_contiguous_range<F>(
344 &self,
345 logical_offset: usize,
346 bytes: usize,
347 destination_offset: usize,
348 scratch: &mut [u8],
349 write: &mut F,
350 stats: &mut ConcatCopyStats,
351 ) -> crate::Result<()>
352 where
353 F: FnMut(usize, &[u8]) -> crate::Result<()>,
354 {
355 const OP: &str = "ConcatFromSequence";
356 let esize = self.dtype.byte_size();
357 if esize == 0 {
358 return Err(SequenceError::UnsupportedDtype {
359 op: OP,
360 dtype: self.dtype,
361 }
362 .into());
363 }
364 let logical_bytes = self
365 .dtype
366 .checked_storage_bytes(checked_product(OP, "source element count", &self.shape)?)
367 .ok_or_else(|| overflow(OP, "source byte count", &self.shape))?;
368 let logical_end = checked_add(
369 OP,
370 "source logical byte range",
371 logical_offset,
372 bytes,
373 &self.shape,
374 )?;
375 if logical_end > logical_bytes
376 || !logical_offset.is_multiple_of(esize)
377 || !bytes.is_multiple_of(esize)
378 {
379 return Err(SequenceError::ByteLengthMismatch {
380 op: OP,
381 dtype: self.dtype,
382 shape: self.shape.clone(),
383 expected: logical_bytes,
384 actual: logical_end,
385 }
386 .into());
387 }
388 if bytes == 0 {
389 return Ok(());
390 }
391
392 let root = if self.device().is_host_accessible() {
393 host_bytes(self.storage.buffer())
394 } else {
395 let destination =
396 scratch
397 .get_mut(..self.root_len())
398 .ok_or_else(|| SequenceError::Allocation {
399 op: OP,
400 context: "device source materialization",
401 bytes: self.root_len(),
402 })?;
403 self.storage
404 .allocator()
405 .copy_to_host(self.storage.buffer(), destination)
406 .map_err(|source| SequenceError::TensorCreation {
407 op: OP,
408 source: source.into(),
409 })?;
410 stats.source_materializations += 1;
411 destination
412 };
413 let strides = self.layout.resolved_strides(&self.shape);
414 validate_view_bounds(
415 OP,
416 &self.shape,
417 &strides,
418 self.byte_offset,
419 self.dtype,
420 root.len(),
421 )?;
422 if onnx_runtime_ir::is_contiguous(&self.shape, &strides) {
423 let source_offset = checked_add(
424 OP,
425 "contiguous source byte offset",
426 self.byte_offset,
427 logical_offset,
428 &self.shape,
429 )?;
430 let source_end = checked_add(
431 OP,
432 "contiguous source byte range",
433 source_offset,
434 bytes,
435 &self.shape,
436 )?;
437 let source = root
438 .get(source_offset..source_end)
439 .ok_or_else(|| overflow(OP, "contiguous source byte range", &self.shape))?;
440 write(destination_offset, source)?;
441 stats.destination_writes += 1;
442 return Ok(());
443 }
444
445 let logical_strides = compute_contiguous_strides(&self.shape);
446 let start_element = logical_offset / esize;
447 let elements = bytes / esize;
448 for element_offset in 0..elements {
449 let linear = checked_add(
450 OP,
451 "strided source logical index",
452 start_element,
453 element_offset,
454 &self.shape,
455 )?;
456 let mut remainder = linear;
457 let mut source_element = 0i128;
458 for dimension in 0..self.shape.len() {
459 let coordinate = if self.shape[dimension] == 0 {
460 0
461 } else {
462 remainder / logical_strides[dimension] as usize
463 };
464 if self.shape[dimension] != 0 {
465 remainder %= logical_strides[dimension] as usize;
466 }
467 source_element += coordinate as i128 * strides[dimension] as i128;
468 }
469 let source_offset = (self.byte_offset as i128)
470 .checked_add(
471 source_element
472 .checked_mul(esize as i128)
473 .ok_or_else(|| overflow(OP, "strided source byte offset", &self.shape))?,
474 )
475 .and_then(|offset| usize::try_from(offset).ok())
476 .ok_or_else(|| overflow(OP, "strided source byte offset", &self.shape))?;
477 let source_end = checked_add(
478 OP,
479 "strided source byte range",
480 source_offset,
481 esize,
482 &self.shape,
483 )?;
484 let destination = checked_add(
485 OP,
486 "strided destination byte offset",
487 destination_offset,
488 checked_mul(
489 OP,
490 "strided destination element offset",
491 element_offset,
492 esize,
493 &self.shape,
494 )?,
495 &self.shape,
496 )?;
497 let source = root
498 .get(source_offset..source_end)
499 .ok_or_else(|| overflow(OP, "strided source byte range", &self.shape))?;
500 write(destination, source)?;
501 stats.destination_writes += 1;
502 }
503 Ok(())
504 }
505
506 pub fn as_bytes(&self) -> SequenceResult<&[u8]> {
508 const OP: &str = "SequenceTensor";
509 if !self.device().is_host_accessible() {
510 return Err(SequenceError::ByteBorrowUnavailable {
511 shape: self.shape.clone(),
512 device: format!("{:?}", self.device()),
513 reason: "the storage is not host-accessible",
514 });
515 }
516 let strides = self.layout.resolved_strides(&self.shape);
517 if !onnx_runtime_ir::is_contiguous(&self.shape, &strides) {
518 return Err(SequenceError::ByteBorrowUnavailable {
519 shape: self.shape.clone(),
520 device: format!("{:?}", self.device()),
521 reason: "the view is strided",
522 });
523 }
524 validate_view_bounds(
525 OP,
526 &self.shape,
527 &strides,
528 self.byte_offset,
529 self.dtype,
530 self.root_len(),
531 )?;
532 let bytes = self
533 .dtype
534 .checked_storage_bytes(checked_product(OP, "tensor element count", &self.shape)?)
535 .ok_or_else(|| overflow(OP, "tensor byte count", &self.shape))?;
536 let end = checked_add(
537 OP,
538 "tensor byte range",
539 self.byte_offset,
540 bytes,
541 &self.shape,
542 )?;
543 host_bytes(self.storage.buffer())
544 .get(self.byte_offset..end)
545 .ok_or_else(|| overflow(OP, "tensor byte range", &self.shape))
546 }
547}
548
549#[derive(Clone, Debug)]
551pub struct SequenceValue {
552 pub(crate) elem_dtype: DataType,
553 pub(crate) items: Vec<SeqTensor>,
554}
555
556impl SequenceValue {
557 pub fn empty(elem_dtype: DataType) -> Self {
559 Self {
560 elem_dtype,
561 items: Vec::new(),
562 }
563 }
564
565 pub fn construct(items: Vec<SeqTensor>) -> SequenceResult<Self> {
567 let elem_dtype = items
568 .first()
569 .map(|tensor| tensor.dtype)
570 .ok_or(SequenceError::EmptyConstruct)?;
571 for (index, tensor) in items.iter().enumerate() {
572 if tensor.dtype != elem_dtype {
573 return Err(SequenceError::DtypeMismatch {
574 op: "SequenceConstruct",
575 index: Some(index),
576 expected: elem_dtype,
577 actual: tensor.dtype,
578 });
579 }
580 }
581 Ok(Self { elem_dtype, items })
582 }
583
584 pub fn insert(&self, value: SeqTensor, at: Option<i64>) -> SequenceResult<Self> {
589 if value.dtype != self.elem_dtype {
590 return Err(SequenceError::DtypeMismatch {
591 op: "SequenceInsert",
592 index: None,
593 expected: self.elem_dtype,
594 actual: value.dtype,
595 });
596 }
597 let index = match at {
598 None => self.items.len(),
599 Some(index) => resolve_index("SequenceInsert", index, self.items.len(), true)?,
600 };
601 let capacity = self
602 .items
603 .len()
604 .checked_add(1)
605 .ok_or(SequenceError::LengthOverflow {
606 op: "SequenceInsert",
607 len: self.items.len(),
608 })?;
609 let mut items = Vec::new();
610 items
611 .try_reserve_exact(capacity)
612 .map_err(|_| SequenceError::Allocation {
613 op: "SequenceInsert",
614 context: "sequence handles",
615 bytes: capacity.saturating_mul(std::mem::size_of::<SeqTensor>()),
616 })?;
617 items.extend_from_slice(&self.items[..index]);
618 items.push(value);
619 items.extend_from_slice(&self.items[index..]);
620 Ok(Self {
621 elem_dtype: self.elem_dtype,
622 items,
623 })
624 }
625
626 pub fn erase(&self, at: Option<i64>) -> SequenceResult<Self> {
630 if self.items.is_empty() {
631 return Err(SequenceError::EmptyErase);
632 }
633 let index = match at {
634 None => self.items.len() - 1,
635 Some(index) => resolve_index("SequenceErase", index, self.items.len(), false)?,
636 };
637 let capacity = self.items.len() - 1;
638 let mut items = Vec::new();
639 items
640 .try_reserve_exact(capacity)
641 .map_err(|_| SequenceError::Allocation {
642 op: "SequenceErase",
643 context: "sequence handles",
644 bytes: capacity.saturating_mul(std::mem::size_of::<SeqTensor>()),
645 })?;
646 items.extend_from_slice(&self.items[..index]);
647 items.extend_from_slice(&self.items[index + 1..]);
648 Ok(Self {
649 elem_dtype: self.elem_dtype,
650 items,
651 })
652 }
653
654 pub fn at(&self, index: i64) -> SequenceResult<SeqTensor> {
656 let index = resolve_index("SequenceAt", index, self.items.len(), false)?;
657 Ok(self.items[index].clone())
658 }
659
660 pub fn length(&self) -> usize {
662 self.items.len()
663 }
664
665 pub fn elem_dtype(&self) -> DataType {
667 self.elem_dtype
668 }
669
670 pub fn elements(&self) -> &[SeqTensor] {
672 &self.items
673 }
674}
675
676#[derive(Clone, Copy, Debug)]
678pub enum SplitSpec<'a> {
679 Each,
681 Chunk(i64),
683 Sizes(&'a [i64]),
685}
686
687pub fn split(
692 data: &[u8],
693 dtype: DataType,
694 shape: &[usize],
695 axis: i64,
696 split: SplitSpec<'_>,
697 keepdims: bool,
698) -> SequenceResult<SequenceValue> {
699 let input = SeqTensor::from_raw(dtype, shape.to_vec(), data)?;
700 split_tensor(&input, axis, split, keepdims)
701}
702
703pub fn split_tensor(
708 input: &SeqTensor,
709 axis: i64,
710 split: SplitSpec<'_>,
711 keepdims: bool,
712) -> SequenceResult<SequenceValue> {
713 const OP: &str = "SplitToSequence";
714 let rank = input.shape.len();
715 if rank == 0 {
716 return Err(SequenceError::InvalidAxis {
717 op: OP,
718 axis,
719 rank,
720 new_axis: false,
721 });
722 }
723 let axis = normalize_axis(OP, axis, rank, false)?;
724 let axis_dim = input.shape[axis];
725 let (sizes, squeeze) = split_sizes(OP, axis, axis_dim, split, keepdims, &input.shape)?;
726 let input_strides = input.layout.resolved_strides(&input.shape);
727 let esize = input.dtype.byte_size();
728 if esize == 0 {
729 return Err(SequenceError::UnsupportedDtype {
730 op: OP,
731 dtype: input.dtype,
732 });
733 }
734
735 let mut items = Vec::new();
736 items
737 .try_reserve_exact(sizes.len())
738 .map_err(|_| SequenceError::Allocation {
739 op: OP,
740 context: "sequence handles",
741 bytes: sizes.len().saturating_mul(std::mem::size_of::<SeqTensor>()),
742 })?;
743 let mut start = 0usize;
744 for size in sizes {
745 let delta_elements = (start as i128)
746 .checked_mul(input_strides[axis] as i128)
747 .ok_or_else(|| overflow(OP, "split view element offset", &input.shape))?;
748 let delta_bytes = delta_elements
749 .checked_mul(esize as i128)
750 .ok_or_else(|| overflow(OP, "split view byte offset", &input.shape))?;
751 let byte_offset = (input.byte_offset as i128)
752 .checked_add(delta_bytes)
753 .and_then(|offset| usize::try_from(offset).ok())
754 .ok_or_else(|| overflow(OP, "split view byte offset", &input.shape))?;
755
756 let mut shape = clone_shape(OP, &input.shape)?;
757 shape[axis] = size;
758 let mut strides = input_strides.clone();
759 if squeeze {
760 shape.remove(axis);
761 strides.remove(axis);
762 }
763 items.push(SeqTensor::from_shared(
764 Arc::clone(input.storage()),
765 input.dtype,
766 shape,
767 TensorLayout::strided(strides),
768 byte_offset,
769 )?);
770 start = checked_add(OP, "split axis cursor", start, size, &input.shape)?;
771 }
772 Ok(SequenceValue {
773 elem_dtype: input.dtype,
774 items,
775 })
776}
777
778fn split_sizes(
779 op: &'static str,
780 axis: usize,
781 axis_dim: usize,
782 split: SplitSpec<'_>,
783 keepdims: bool,
784 shape: &[usize],
785) -> SequenceResult<(Vec<usize>, bool)> {
786 match split {
787 SplitSpec::Each => {
788 let mut sizes = Vec::new();
789 sizes
790 .try_reserve_exact(axis_dim)
791 .map_err(|_| SequenceError::Allocation {
792 op,
793 context: "split sizes",
794 bytes: axis_dim.saturating_mul(std::mem::size_of::<usize>()),
795 })?;
796 sizes.resize(axis_dim, 1);
797 Ok((sizes, !keepdims))
798 }
799 SplitSpec::Chunk(chunk) => {
800 if chunk <= 0 {
801 return Err(SequenceError::InvalidSplit {
802 op,
803 reason: format!("scalar chunk size {chunk} must be positive"),
804 });
805 }
806 let chunk = usize::try_from(chunk).map_err(|_| SequenceError::InvalidSplit {
807 op,
808 reason: format!("scalar chunk size {chunk} cannot be represented"),
809 })?;
810 let count = axis_dim
811 .checked_add(chunk - 1)
812 .ok_or_else(|| overflow(op, "split chunk count", shape))?
813 / chunk;
814 let mut sizes = Vec::new();
815 sizes
816 .try_reserve_exact(count)
817 .map_err(|_| SequenceError::Allocation {
818 op,
819 context: "split sizes",
820 bytes: count.saturating_mul(std::mem::size_of::<usize>()),
821 })?;
822 let mut remaining = axis_dim;
823 while remaining != 0 {
824 let size = remaining.min(chunk);
825 sizes.push(size);
826 remaining -= size;
827 }
828 Ok((sizes, false))
829 }
830 SplitSpec::Sizes(values) => {
831 let mut sizes = Vec::new();
832 sizes
833 .try_reserve_exact(values.len())
834 .map_err(|_| SequenceError::Allocation {
835 op,
836 context: "split sizes",
837 bytes: values.len().saturating_mul(std::mem::size_of::<usize>()),
838 })?;
839 let mut sum = 0usize;
840 for &value in values {
841 let value = usize::try_from(value).map_err(|_| SequenceError::InvalidSplit {
842 op,
843 reason: format!("size {value} must be non-negative"),
844 })?;
845 sum = sum
846 .checked_add(value)
847 .ok_or_else(|| overflow(op, "split size sum", shape))?;
848 sizes.push(value);
849 }
850 if sum != axis_dim {
851 return Err(SequenceError::InvalidSplit {
852 op,
853 reason: format!("sizes sum to {sum}, but axis {axis} has extent {axis_dim}"),
854 });
855 }
856 Ok((sizes, false))
857 }
858 }
859}
860
861#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
862pub(crate) struct ConcatCopyStats {
863 pub destination_writes: usize,
864 pub source_materializations: usize,
865}
866
867pub(crate) struct ConcatPlan {
869 pub dtype: DataType,
870 pub shape: Vec<usize>,
871 pub bytes: usize,
872 axis: usize,
873 new_axis: bool,
874 outer: usize,
875 inner: usize,
876 total_axis: usize,
877}
878
879impl ConcatPlan {
880 pub(crate) fn new(sequence: &SequenceValue, axis: i64, new_axis: bool) -> SequenceResult<Self> {
881 const OP: &str = "ConcatFromSequence";
882 let first = sequence.items.first().ok_or(SequenceError::InvalidSplit {
883 op: OP,
884 reason: "cannot concatenate an empty sequence".to_string(),
885 })?;
886 let dtype = sequence.elem_dtype;
887 let esize = dtype.byte_size();
888 if esize == 0 {
889 return Err(SequenceError::UnsupportedDtype { op: OP, dtype });
890 }
891 let rank = first.shape.len();
892 let output_rank = rank
893 .checked_add(usize::from(new_axis))
894 .ok_or_else(|| overflow(OP, "concat output rank", &first.shape))?;
895 let axis = normalize_axis(OP, axis, output_rank, new_axis)?;
896
897 for (index, item) in sequence.items.iter().enumerate() {
898 if item.dtype != dtype {
899 return Err(SequenceError::DtypeMismatch {
900 op: OP,
901 index: Some(index),
902 expected: dtype,
903 actual: item.dtype,
904 });
905 }
906 let mismatch = if new_axis {
907 item.shape != first.shape
908 } else {
909 item.shape.len() != rank
910 || item.shape.iter().enumerate().any(|(dimension, &extent)| {
911 dimension != axis && extent != first.shape[dimension]
912 })
913 };
914 if mismatch {
915 return Err(SequenceError::ShapeMismatch {
916 op: OP,
917 index,
918 expected: clone_shape(OP, &first.shape)?,
919 actual: clone_shape(OP, &item.shape)?,
920 requirement: if new_axis {
921 "new_axis=1 requires identical shapes"
922 } else {
923 "all dimensions except the concat axis must match"
924 },
925 });
926 }
927 validate_view_bounds(
928 OP,
929 &item.shape,
930 &item.layout.resolved_strides(&item.shape),
931 item.byte_offset,
932 item.dtype,
933 item.root_len(),
934 )?;
935 }
936
937 if new_axis {
938 let outer = checked_product(OP, "stack outer element count", &first.shape[..axis])?;
939 let inner_elements =
940 checked_product(OP, "stack inner element count", &first.shape[axis..])?;
941 let inner = checked_mul(
942 OP,
943 "stack inner byte count",
944 inner_elements,
945 esize,
946 &first.shape,
947 )?;
948 let mut shape = Vec::new();
949 shape
950 .try_reserve_exact(output_rank)
951 .map_err(|_| SequenceError::Allocation {
952 op: OP,
953 context: "stack output shape",
954 bytes: output_rank.saturating_mul(std::mem::size_of::<usize>()),
955 })?;
956 shape.extend_from_slice(&first.shape[..axis]);
957 shape.push(sequence.items.len());
958 shape.extend_from_slice(&first.shape[axis..]);
959 let bytes = checked_mul(
960 OP,
961 "stack output byte count",
962 checked_mul(
963 OP,
964 "stack output row count",
965 outer,
966 sequence.items.len(),
967 &shape,
968 )?,
969 inner,
970 &shape,
971 )?;
972 Ok(Self {
973 dtype,
974 shape,
975 bytes,
976 axis,
977 new_axis,
978 outer,
979 inner,
980 total_axis: sequence.items.len(),
981 })
982 } else {
983 let outer = checked_product(OP, "concat outer element count", &first.shape[..axis])?;
984 let inner_elements =
985 checked_product(OP, "concat inner element count", &first.shape[axis + 1..])?;
986 let inner = checked_mul(
987 OP,
988 "concat inner byte count",
989 inner_elements,
990 esize,
991 &first.shape,
992 )?;
993 let mut total_axis = 0usize;
994 for item in &sequence.items {
995 total_axis = checked_add(
996 OP,
997 "concat axis extent",
998 total_axis,
999 item.shape[axis],
1000 &first.shape,
1001 )?;
1002 }
1003 let mut shape = clone_shape(OP, &first.shape)?;
1004 shape[axis] = total_axis;
1005 let bytes = checked_mul(
1006 OP,
1007 "concat output byte count",
1008 checked_mul(OP, "concat output row count", outer, total_axis, &shape)?,
1009 inner,
1010 &shape,
1011 )?;
1012 Ok(Self {
1013 dtype,
1014 shape,
1015 bytes,
1016 axis,
1017 new_axis,
1018 outer,
1019 inner,
1020 total_axis,
1021 })
1022 }
1023 }
1024
1025 pub(crate) fn write<F>(
1026 &self,
1027 sequence: &SequenceValue,
1028 mut write: F,
1029 ) -> crate::Result<ConcatCopyStats>
1030 where
1031 F: FnMut(usize, &[u8]) -> crate::Result<()>,
1032 {
1033 const OP: &str = "ConcatFromSequence";
1034 let max_root = sequence
1035 .items
1036 .iter()
1037 .filter(|item| !item.device().is_host_accessible())
1038 .map(SeqTensor::root_len)
1039 .max()
1040 .unwrap_or(0);
1041 let mut scratch = if max_root == 0 {
1042 Vec::new()
1043 } else {
1044 zeroed_bytes(OP, "device source materialization", max_root, &self.shape)?
1045 };
1046 let mut stats = ConcatCopyStats::default();
1047 if self.new_axis {
1048 for outer_index in 0..self.outer {
1049 for (item_index, item) in sequence.items.iter().enumerate() {
1050 let source_offset = checked_mul(
1051 OP,
1052 "stack source offset",
1053 outer_index,
1054 self.inner,
1055 &item.shape,
1056 )?;
1057 let destination_row = checked_add(
1058 OP,
1059 "stack destination row",
1060 checked_mul(
1061 OP,
1062 "stack destination outer offset",
1063 outer_index,
1064 self.total_axis,
1065 &self.shape,
1066 )?,
1067 item_index,
1068 &self.shape,
1069 )?;
1070 let destination_offset = checked_mul(
1071 OP,
1072 "stack destination byte offset",
1073 destination_row,
1074 self.inner,
1075 &self.shape,
1076 )?;
1077 item.write_contiguous_range(
1078 source_offset,
1079 self.inner,
1080 destination_offset,
1081 &mut scratch,
1082 &mut write,
1083 &mut stats,
1084 )?;
1085 }
1086 }
1087 } else {
1088 for outer_index in 0..self.outer {
1089 let mut axis_cursor = 0usize;
1090 for item in &sequence.items {
1091 let copy_bytes = checked_mul(
1092 OP,
1093 "concat copy width",
1094 item.shape[self.axis],
1095 self.inner,
1096 &item.shape,
1097 )?;
1098 let source_offset = checked_mul(
1099 OP,
1100 "concat source byte offset",
1101 outer_index,
1102 copy_bytes,
1103 &item.shape,
1104 )?;
1105 let destination_row = checked_add(
1106 OP,
1107 "concat destination row",
1108 checked_mul(
1109 OP,
1110 "concat destination outer offset",
1111 outer_index,
1112 self.total_axis,
1113 &self.shape,
1114 )?,
1115 axis_cursor,
1116 &self.shape,
1117 )?;
1118 let destination_offset = checked_mul(
1119 OP,
1120 "concat destination byte offset",
1121 destination_row,
1122 self.inner,
1123 &self.shape,
1124 )?;
1125 item.write_contiguous_range(
1126 source_offset,
1127 copy_bytes,
1128 destination_offset,
1129 &mut scratch,
1130 &mut write,
1131 &mut stats,
1132 )?;
1133 axis_cursor = checked_add(
1134 OP,
1135 "concat axis cursor",
1136 axis_cursor,
1137 item.shape[self.axis],
1138 &self.shape,
1139 )?;
1140 }
1141 }
1142 }
1143 Ok(stats)
1144 }
1145}
1146
1147pub fn concat(sequence: &SequenceValue, axis: i64, new_axis: bool) -> SequenceResult<SeqTensor> {
1149 const OP: &str = "ConcatFromSequence";
1150 let plan = ConcatPlan::new(sequence, axis, new_axis)?;
1151 let mut tensor = Tensor::allocate_cpu(plan.dtype, plan.shape.clone())
1152 .map_err(|source| SequenceError::TensorCreation { op: OP, source })?;
1153 plan.write(sequence, |offset, bytes| {
1154 tensor.copy_from_host_at(offset, bytes)
1155 })
1156 .map_err(|source| SequenceError::TensorCreation { op: OP, source })?;
1157 Ok(SeqTensor::new(tensor))
1158}
1159
1160fn resolve_index(
1161 op: &'static str,
1162 index: i64,
1163 len: usize,
1164 insertion: bool,
1165) -> SequenceResult<usize> {
1166 let length = i64::try_from(len).map_err(|_| SequenceError::LengthOverflow { op, len })?;
1167 let resolved = if index < 0 {
1168 length.checked_add(index)
1169 } else {
1170 Some(index)
1171 };
1172 let valid = resolved.is_some_and(|value| {
1173 value >= 0
1174 && if insertion {
1175 value <= length
1176 } else {
1177 value < length
1178 }
1179 });
1180 if !valid {
1181 return Err(SequenceError::IndexOutOfBounds {
1182 op,
1183 index,
1184 len,
1185 insertion,
1186 });
1187 }
1188 usize::try_from(resolved.unwrap_or_default())
1189 .map_err(|_| SequenceError::LengthOverflow { op, len })
1190}
1191
1192fn normalize_axis(
1193 op: &'static str,
1194 axis: i64,
1195 rank: usize,
1196 new_axis: bool,
1197) -> SequenceResult<usize> {
1198 let rank_i64 =
1199 i64::try_from(rank).map_err(|_| SequenceError::LengthOverflow { op, len: rank })?;
1200 let normalized = if axis < 0 {
1201 rank_i64.checked_add(axis)
1202 } else {
1203 Some(axis)
1204 };
1205 match normalized {
1206 Some(axis) if axis >= 0 && axis < rank_i64 => Ok(axis as usize),
1207 _ => Err(SequenceError::InvalidAxis {
1208 op,
1209 axis,
1210 rank: rank - usize::from(new_axis),
1211 new_axis,
1212 }),
1213 }
1214}
1215
1216fn overflow(op: &'static str, context: &'static str, shape: &[usize]) -> SequenceError {
1217 SequenceError::ShapeOverflow {
1218 op,
1219 context,
1220 shape: shape.to_vec(),
1221 }
1222}
1223
1224fn checked_product(
1226 op: &'static str,
1227 context: &'static str,
1228 shape: &[usize],
1229) -> SequenceResult<usize> {
1230 let mut product = 1usize;
1231 let mut has_zero = false;
1232 for &dimension in shape {
1233 if dimension == 0 {
1234 has_zero = true;
1235 } else {
1236 product = product
1237 .checked_mul(dimension)
1238 .ok_or_else(|| overflow(op, context, shape))?;
1239 }
1240 }
1241 Ok(if has_zero { 0 } else { product })
1242}
1243
1244fn checked_mul(
1245 op: &'static str,
1246 context: &'static str,
1247 lhs: usize,
1248 rhs: usize,
1249 shape: &[usize],
1250) -> SequenceResult<usize> {
1251 lhs.checked_mul(rhs)
1252 .ok_or_else(|| overflow(op, context, shape))
1253}
1254
1255fn checked_add(
1256 op: &'static str,
1257 context: &'static str,
1258 lhs: usize,
1259 rhs: usize,
1260 shape: &[usize],
1261) -> SequenceResult<usize> {
1262 lhs.checked_add(rhs)
1263 .ok_or_else(|| overflow(op, context, shape))
1264}
1265
1266fn addressable(
1267 op: &'static str,
1268 context: &'static str,
1269 bytes: usize,
1270 shape: &[usize],
1271) -> SequenceResult<usize> {
1272 if bytes > isize::MAX as usize {
1273 return Err(overflow(op, context, shape));
1274 }
1275 Ok(bytes)
1276}
1277
1278fn zeroed_bytes(
1279 op: &'static str,
1280 context: &'static str,
1281 bytes: usize,
1282 shape: &[usize],
1283) -> SequenceResult<Vec<u8>> {
1284 addressable(op, context, bytes, shape)?;
1285 let mut output = Vec::new();
1286 output
1287 .try_reserve_exact(bytes)
1288 .map_err(|_| SequenceError::Allocation { op, context, bytes })?;
1289 output.resize(bytes, 0);
1290 Ok(output)
1291}
1292
1293fn clone_shape(op: &'static str, shape: &[usize]) -> SequenceResult<Vec<usize>> {
1294 let bytes = shape
1295 .len()
1296 .checked_mul(std::mem::size_of::<usize>())
1297 .ok_or_else(|| overflow(op, "shape allocation", shape))?;
1298 let mut cloned = Vec::new();
1299 cloned
1300 .try_reserve_exact(shape.len())
1301 .map_err(|_| SequenceError::Allocation {
1302 op,
1303 context: "shape",
1304 bytes,
1305 })?;
1306 cloned.extend_from_slice(shape);
1307 Ok(cloned)
1308}
1309
1310fn validate_tensor_bytes(
1311 op: &'static str,
1312 data: &[u8],
1313 dtype: DataType,
1314 shape: &[usize],
1315) -> SequenceResult<()> {
1316 if dtype.byte_size() == 0 {
1317 return Err(SequenceError::UnsupportedDtype { op, dtype });
1318 }
1319 let numel = checked_product(op, "tensor element count", shape)?;
1320 let expected = dtype
1321 .checked_storage_bytes(numel)
1322 .ok_or_else(|| overflow(op, "tensor byte count", shape))?;
1323 addressable(op, "tensor byte count", expected, shape)?;
1324 if data.len() != expected {
1325 return Err(SequenceError::ByteLengthMismatch {
1326 op,
1327 dtype,
1328 shape: clone_shape(op, shape)?,
1329 expected,
1330 actual: data.len(),
1331 });
1332 }
1333 Ok(())
1334}
1335
1336fn validate_view_bounds(
1337 op: &'static str,
1338 shape: &[usize],
1339 strides: &[i64],
1340 byte_offset: usize,
1341 dtype: DataType,
1342 root_len: usize,
1343) -> SequenceResult<()> {
1344 if shape.len() != strides.len() {
1345 return Err(SequenceError::InvalidSplit {
1346 op,
1347 reason: format!(
1348 "view rank mismatch: shape has {} dims but strides has {}",
1349 shape.len(),
1350 strides.len()
1351 ),
1352 });
1353 }
1354 let esize = dtype.byte_size();
1355 if esize == 0 {
1356 return Err(SequenceError::UnsupportedDtype { op, dtype });
1357 }
1358 if shape.contains(&0) {
1359 return Ok(());
1360 }
1361 let mut min_element = 0i128;
1362 let mut max_element = 0i128;
1363 for (&dim, &stride) in shape.iter().zip(strides) {
1364 let span = (dim.saturating_sub(1) as i128)
1365 .checked_mul(stride as i128)
1366 .ok_or_else(|| overflow(op, "view stride span", shape))?;
1367 if span < 0 {
1368 min_element = min_element
1369 .checked_add(span)
1370 .ok_or_else(|| overflow(op, "view minimum offset", shape))?;
1371 } else {
1372 max_element = max_element
1373 .checked_add(span)
1374 .ok_or_else(|| overflow(op, "view maximum offset", shape))?;
1375 }
1376 }
1377 let origin = byte_offset as i128;
1378 let min_byte = origin
1379 .checked_add(
1380 min_element
1381 .checked_mul(esize as i128)
1382 .ok_or_else(|| overflow(op, "view minimum byte offset", shape))?,
1383 )
1384 .ok_or_else(|| overflow(op, "view minimum byte offset", shape))?;
1385 let end_byte = origin
1386 .checked_add(
1387 max_element
1388 .checked_mul(esize as i128)
1389 .ok_or_else(|| overflow(op, "view maximum byte offset", shape))?,
1390 )
1391 .and_then(|offset| offset.checked_add(esize as i128))
1392 .ok_or_else(|| overflow(op, "view byte range", shape))?;
1393 if min_byte < 0 || end_byte > root_len as i128 {
1394 return Err(SequenceError::InvalidSplit {
1395 op,
1396 reason: format!(
1397 "view byte range [{min_byte}, {end_byte}) exceeds backing allocation of {root_len} bytes"
1398 ),
1399 });
1400 }
1401 Ok(())
1402}
1403
1404fn gather_strided(
1405 root: &[u8],
1406 shape: &[usize],
1407 strides: &[i64],
1408 byte_offset: usize,
1409 dtype: DataType,
1410 esize: usize,
1411) -> SequenceResult<Vec<u8>> {
1412 const OP: &str = "SequenceTensor";
1413 validate_view_bounds(OP, shape, strides, byte_offset, dtype, root.len())?;
1414 let numel = checked_product(OP, "view element count", shape)?;
1415 let bytes = checked_mul(OP, "view byte count", numel, esize, shape)?;
1416 let mut output = zeroed_bytes(OP, "strided tensor materialization", bytes, shape)?;
1417 if numel == 0 {
1418 return Ok(output);
1419 }
1420 let logical_strides = compute_contiguous_strides(shape);
1421 for linear in 0..numel {
1422 let mut remainder = linear;
1423 let mut source_element = 0i128;
1424 for dimension in 0..shape.len() {
1425 let coordinate = if shape[dimension] == 0 {
1426 0
1427 } else {
1428 remainder / logical_strides[dimension] as usize
1429 };
1430 if shape[dimension] != 0 {
1431 remainder %= logical_strides[dimension] as usize;
1432 }
1433 source_element += coordinate as i128 * strides[dimension] as i128;
1434 }
1435 let source = (byte_offset as i128 + source_element * esize as i128) as usize;
1436 output[linear * esize..(linear + 1) * esize].copy_from_slice(&root[source..source + esize]);
1437 }
1438 Ok(output)
1439}
1440
1441pub(crate) fn stack_new_axis(
1443 elements: &[&[u8]],
1444 elem_shape: &[usize],
1445 axis: usize,
1446 esize: usize,
1447) -> SequenceResult<(Vec<usize>, Vec<u8>)> {
1448 const OP: &str = "ConcatFromSequence";
1449 if axis > elem_shape.len() || esize == 0 {
1450 return Err(SequenceError::InvalidSplit {
1451 op: OP,
1452 reason: "invalid new axis or element byte size".to_string(),
1453 });
1454 }
1455 let outer = checked_product(OP, "stack outer element count", &elem_shape[..axis])?;
1456 let inner_elements = checked_product(OP, "stack inner element count", &elem_shape[axis..])?;
1457 let inner = checked_mul(
1458 OP,
1459 "stack inner byte count",
1460 inner_elements,
1461 esize,
1462 elem_shape,
1463 )?;
1464 let source_bytes = checked_mul(OP, "stack source byte count", outer, inner, elem_shape)?;
1465 addressable(OP, "stack source byte count", source_bytes, elem_shape)?;
1466 for element in elements {
1467 if element.len() != source_bytes {
1468 return Err(SequenceError::ByteLengthMismatch {
1469 op: OP,
1470 dtype: DataType::Uint8,
1471 shape: clone_shape(OP, elem_shape)?,
1472 expected: source_bytes,
1473 actual: element.len(),
1474 });
1475 }
1476 }
1477 let output_rows = checked_mul(
1478 OP,
1479 "stacked tensor output row count",
1480 elements.len(),
1481 outer,
1482 elem_shape,
1483 )?;
1484 let output_bytes = checked_mul(
1485 OP,
1486 "stack output byte count",
1487 output_rows,
1488 inner,
1489 elem_shape,
1490 )?;
1491 let mut bytes = zeroed_bytes(OP, "stack output", output_bytes, elem_shape)?;
1492 if inner != 0 {
1493 for (element_index, element) in elements.iter().enumerate() {
1494 for outer_index in 0..outer {
1495 let source_offset = checked_mul(
1496 OP,
1497 "stack source byte offset",
1498 outer_index,
1499 inner,
1500 elem_shape,
1501 )?;
1502 let source_end = checked_add(
1503 OP,
1504 "stack source byte range",
1505 source_offset,
1506 inner,
1507 elem_shape,
1508 )?;
1509 let destination_row = checked_add(
1510 OP,
1511 "stack destination row",
1512 checked_mul(
1513 OP,
1514 "stack destination outer offset",
1515 outer_index,
1516 elements.len(),
1517 elem_shape,
1518 )?,
1519 element_index,
1520 elem_shape,
1521 )?;
1522 let destination_offset = checked_mul(
1523 OP,
1524 "stack destination byte offset",
1525 destination_row,
1526 inner,
1527 elem_shape,
1528 )?;
1529 let destination_end = checked_add(
1530 OP,
1531 "stack destination byte range",
1532 destination_offset,
1533 inner,
1534 elem_shape,
1535 )?;
1536 bytes[destination_offset..destination_end]
1537 .copy_from_slice(&element[source_offset..source_end]);
1538 }
1539 }
1540 }
1541 let shape_capacity = elem_shape
1542 .len()
1543 .checked_add(1)
1544 .ok_or_else(|| overflow(OP, "stack output rank", elem_shape))?;
1545 let mut output_shape = Vec::new();
1546 output_shape
1547 .try_reserve_exact(shape_capacity)
1548 .map_err(|_| SequenceError::Allocation {
1549 op: OP,
1550 context: "stack output shape",
1551 bytes: shape_capacity.saturating_mul(std::mem::size_of::<usize>()),
1552 })?;
1553 output_shape.extend_from_slice(&elem_shape[..axis]);
1554 output_shape.push(elements.len());
1555 output_shape.extend_from_slice(&elem_shape[axis..]);
1556 Ok((output_shape, bytes))
1557}
1558
1559#[cfg(test)]
1560mod tests {
1561 use super::*;
1562
1563 fn elem(dtype: DataType, shape: &[usize], bytes: &[u8]) -> SeqTensor {
1564 SeqTensor::from_raw(dtype, shape.to_vec(), bytes).expect("valid test tensor")
1565 }
1566
1567 #[test]
1568 fn value_ops_share_tensor_arcs_without_copying() {
1569 let original = elem(DataType::Uint8, &[1], &[7]);
1570 let sequence = SequenceValue::construct(vec![original.clone()]).expect("construct");
1571 assert!(original.shares_storage_with(&sequence.elements()[0]));
1572
1573 let inserted = elem(DataType::Uint8, &[1], &[9]);
1574 let sequence = sequence.insert(inserted.clone(), Some(-1)).expect("insert");
1575 assert!(inserted.shares_storage_with(&sequence.at(0).expect("at")));
1576 assert!(original.shares_storage_with(&sequence.at(1).expect("at")));
1577
1578 let erased = sequence.erase(Some(0)).expect("erase");
1579 assert!(original.shares_storage_with(&erased.at(-1).expect("negative at")));
1580 }
1581
1582 #[test]
1583 fn moving_tensor_into_sequence_preserves_allocation_pointer() {
1584 let tensor = Tensor::from_raw(DataType::Uint8, vec![2], &[4, 5]).unwrap();
1585 let pointer = tensor.device_ptr();
1586 let element = SeqTensor::new(tensor);
1587 assert_eq!(element.as_ptr(), pointer);
1588 let sequence = SequenceValue::construct(vec![element.clone()]).unwrap();
1589 assert_eq!(sequence.at(0).unwrap().as_ptr(), pointer);
1590 assert_eq!(element.storage_strong_count(), 2);
1591 }
1592
1593 #[test]
1594 fn split_produces_shared_strided_views_without_copying() {
1595 let input = elem(DataType::Uint8, &[2, 3], &[0, 1, 2, 3, 4, 5]);
1596 let sequence = split_tensor(&input, 1, SplitSpec::Sizes(&[1, 2]), true).expect("split");
1597 assert_eq!(sequence.length(), 2);
1598 assert!(input.shares_storage_with(&sequence.elements()[0]));
1599 assert!(input.shares_storage_with(&sequence.elements()[1]));
1600 assert_eq!(sequence.elements()[0].byte_offset(), 0);
1601 assert_eq!(sequence.elements()[1].byte_offset(), 1);
1602 assert_eq!(
1603 sequence.elements()[0].contiguous_bytes().unwrap(),
1604 vec![0, 3]
1605 );
1606 assert_eq!(
1607 sequence.elements()[1].contiguous_bytes().unwrap(),
1608 vec![1, 2, 4, 5]
1609 );
1610 }
1611
1612 #[test]
1613 fn strided_split_element_byte_borrow_is_fallible_without_panicking() {
1614 let input = elem(DataType::Uint8, &[2, 3], &[0, 1, 2, 3, 4, 5]);
1615 let sequence = split_tensor(&input, 1, SplitSpec::Sizes(&[1, 2]), true).expect("split");
1616 let element = &sequence.elements()[0];
1617 assert!(matches!(
1618 element.as_bytes(),
1619 Err(SequenceError::ByteBorrowUnavailable {
1620 reason: "the view is strided",
1621 ..
1622 })
1623 ));
1624 assert_eq!(element.contiguous_bytes().unwrap(), vec![0, 3]);
1625 }
1626
1627 #[test]
1628 fn empty_construct_insert_erase_at_and_length() {
1629 let empty = SequenceValue::empty(DataType::Uint8);
1630 assert_eq!(empty.length(), 0);
1631 let one = empty
1632 .insert(elem(DataType::Uint8, &[1], &[1]), None)
1633 .unwrap();
1634 let two = one
1635 .insert(elem(DataType::Uint8, &[1], &[2]), Some(-1))
1636 .unwrap();
1637 let three = two
1638 .insert(elem(DataType::Uint8, &[1], &[3]), Some(2))
1639 .unwrap();
1640 assert_eq!(three.length(), 3);
1641 assert_eq!(three.at(0).unwrap().as_bytes().unwrap(), &[2]);
1642 assert_eq!(three.at(-1).unwrap().as_bytes().unwrap(), &[3]);
1643 let erased = three.erase(Some(-2)).unwrap();
1644 assert_eq!(erased.length(), 2);
1645 assert_eq!(erased.at(0).unwrap().as_bytes().unwrap(), &[2]);
1646 assert_eq!(erased.at(1).unwrap().as_bytes().unwrap(), &[3]);
1647 }
1648
1649 #[test]
1650 fn empty_sequence_edges_are_clean_errors() {
1651 let empty = SequenceValue::empty(DataType::Uint8);
1652 assert!(matches!(empty.erase(None), Err(SequenceError::EmptyErase)));
1653 assert!(matches!(
1654 empty.at(0),
1655 Err(SequenceError::IndexOutOfBounds { len: 0, .. })
1656 ));
1657 assert!(matches!(
1658 concat(&empty, 0, false),
1659 Err(SequenceError::InvalidSplit { .. })
1660 ));
1661 }
1662
1663 #[test]
1664 fn homogeneity_violation_is_typed_error() {
1665 let error = SequenceValue::construct(vec![
1666 elem(DataType::Uint8, &[1], &[1]),
1667 elem(DataType::Int64, &[1], &1i64.to_le_bytes()),
1668 ])
1669 .unwrap_err();
1670 assert!(matches!(
1671 error,
1672 SequenceError::DtypeMismatch {
1673 op: "SequenceConstruct",
1674 index: Some(1),
1675 ..
1676 }
1677 ));
1678 }
1679
1680 #[test]
1681 fn split_concat_roundtrip_existing_axes_and_keepdims() {
1682 let data: Vec<u8> = (0..12).collect();
1683 for (shape, axis, keepdims) in [
1684 (vec![3, 4], 0, true),
1685 (vec![3, 4], 1, true),
1686 (vec![3, 4], 0, false),
1687 ] {
1688 let input = elem(DataType::Uint8, &shape, &data);
1689 let sequence = split_tensor(&input, axis, SplitSpec::Each, keepdims).unwrap();
1690 let concat_axis = if keepdims { axis } else { 0 };
1691 let rebuilt = concat(&sequence, concat_axis, !keepdims).unwrap();
1692 assert_eq!(rebuilt.shape, shape);
1693 assert_eq!(rebuilt.as_bytes().unwrap(), data);
1694 }
1695 }
1696
1697 #[test]
1698 fn split_concat_roundtrip_explicit_sizes() {
1699 let data: Vec<u8> = (0..12).collect();
1700 let input = elem(DataType::Uint8, &[3, 4], &data);
1701 let sequence = split_tensor(&input, 1, SplitSpec::Sizes(&[1, 3]), false).unwrap();
1702 let rebuilt = concat(&sequence, 1, false).unwrap();
1703 assert_eq!(rebuilt.shape, vec![3, 4]);
1704 assert_eq!(rebuilt.as_bytes().unwrap(), data);
1705 }
1706
1707 #[test]
1708 fn stack_new_axis_variants() {
1709 let a = elem(DataType::Uint8, &[2], &[1, 2]);
1710 let b = elem(DataType::Uint8, &[2], &[3, 4]);
1711 let sequence = SequenceValue::construct(vec![a, b]).unwrap();
1712 let front = concat(&sequence, 0, true).unwrap();
1713 assert_eq!(front.shape, vec![2, 2]);
1714 assert_eq!(front.as_bytes().unwrap(), &[1, 2, 3, 4]);
1715 let back = concat(&sequence, 1, true).unwrap();
1716 assert_eq!(back.shape, vec![2, 2]);
1717 assert_eq!(back.as_bytes().unwrap(), &[1, 3, 2, 4]);
1718 }
1719
1720 #[test]
1721 fn concat_plan_uses_one_final_destination_without_source_materialization() {
1722 let a = elem(DataType::Uint8, &[2], &[1, 2]);
1723 let b = elem(DataType::Uint8, &[2], &[3, 4]);
1724 let sequence = SequenceValue::construct(vec![a, b]).unwrap();
1725 let plan = ConcatPlan::new(&sequence, 0, false).unwrap();
1726 let mut destination_allocations = 0;
1727 let mut destination = {
1728 destination_allocations += 1;
1729 vec![0; plan.bytes]
1730 };
1731 let stats = plan
1732 .write(&sequence, |offset, bytes| {
1733 destination[offset..offset + bytes.len()].copy_from_slice(bytes);
1734 Ok(())
1735 })
1736 .unwrap();
1737 assert_eq!(destination_allocations, 1);
1738 assert_eq!(stats.source_materializations, 0);
1739 assert_eq!(stats.destination_writes, 2);
1740 assert_eq!(destination, vec![1, 2, 3, 4]);
1741 }
1742
1743 #[test]
1744 fn byte_count_above_isize_max_is_rejected() {
1745 let error = validate_tensor_bytes(
1746 "SplitToSequence",
1747 &[],
1748 DataType::Uint8,
1749 &[isize::MAX as usize + 1, 1],
1750 )
1751 .unwrap_err();
1752 assert!(matches!(error, SequenceError::ShapeOverflow { .. }));
1753 }
1754
1755 #[test]
1756 fn sequence_values_are_send_sync() {
1757 fn assert_send_sync<T: Send + Sync>() {}
1758 assert_send_sync::<SeqTensor>();
1759 assert_send_sync::<SequenceValue>();
1760 }
1761}