Files
xserv/crates/xserv-tensor/src/shape.rs
Gahow Wang 1ab6ca9c09 tensor: add narrow() view and relax is_contiguous for size-1 dims
narrow(dim, start, len) creates a zero-copy slice along any dimension.
is_contiguous() now ignores stride mismatches on dimensions of size 1,
since those dimensions are never stepped. This avoids unnecessary GPU
strided copies when slicing fused projection outputs at batch=1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 12:49:57 +08:00

115 lines
3.5 KiB
Rust

use smallvec::SmallVec;
pub type Dims = SmallVec<[usize; 4]>;
/// Compute contiguous strides for a given shape (row-major / C order).
/// Example: shape [2, 3, 4] => strides [12, 4, 1]
pub fn contiguous_strides(shape: &[usize]) -> Dims {
let mut strides = SmallVec::with_capacity(shape.len());
strides.resize(shape.len(), 0);
if shape.is_empty() {
return strides;
}
strides[shape.len() - 1] = 1;
for i in (0..shape.len() - 1).rev() {
strides[i] = strides[i + 1] * shape[i + 1];
}
strides
}
/// Check if the given strides represent contiguous (row-major) layout for the shape.
/// A stride mismatch on a dimension of size 1 is allowed because that
/// dimension is never stepped.
pub fn is_contiguous(shape: &[usize], strides: &[usize]) -> bool {
if shape.is_empty() {
return true;
}
let ndim = shape.len();
let mut expected_stride = 1usize;
for d in (0..ndim).rev() {
if shape[d] != 1 && strides[d] != expected_stride {
return false;
}
expected_stride *= shape[d];
}
true
}
/// Total number of elements given a shape.
pub fn num_elements(shape: &[usize]) -> usize {
shape.iter().product()
}
/// Compute the shape after broadcasting two shapes together (NumPy rules).
/// Returns None if shapes are not broadcastable.
pub fn broadcast_shape(a: &[usize], b: &[usize]) -> Option<Dims> {
let ndim = a.len().max(b.len());
let mut result = SmallVec::with_capacity(ndim);
for i in 0..ndim {
let da = if i < ndim - a.len() { 1 } else { a[i - (ndim - a.len())] };
let db = if i < ndim - b.len() { 1 } else { b[i - (ndim - b.len())] };
if da == db {
result.push(da);
} else if da == 1 {
result.push(db);
} else if db == 1 {
result.push(da);
} else {
return None;
}
}
Some(result)
}
/// Compute broadcast strides: for dimensions where size is 1 but output is >1, stride becomes 0.
pub fn broadcast_strides(shape: &[usize], strides: &[usize], target_shape: &[usize]) -> Dims {
let ndim = target_shape.len();
let offset = ndim - shape.len();
let mut result = SmallVec::with_capacity(ndim);
for i in 0..ndim {
if i < offset {
result.push(0);
} else {
let orig_idx = i - offset;
if shape[orig_idx] == 1 && target_shape[i] > 1 {
result.push(0);
} else {
result.push(strides[orig_idx]);
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contiguous_strides() {
assert_eq!(contiguous_strides(&[2, 3, 4]).as_slice(), &[12, 4, 1]);
assert_eq!(contiguous_strides(&[5]).as_slice(), &[1]);
assert_eq!(contiguous_strides(&[2, 3]).as_slice(), &[3, 1]);
}
#[test]
fn test_is_contiguous() {
assert!(is_contiguous(&[2, 3], &[3, 1]));
assert!(!is_contiguous(&[3, 2], &[1, 3])); // transposed
}
#[test]
fn test_broadcast_shape() {
assert_eq!(broadcast_shape(&[3, 1], &[1, 4]).unwrap().as_slice(), &[3, 4]);
assert_eq!(broadcast_shape(&[2, 3, 4], &[4]).unwrap().as_slice(), &[2, 3, 4]);
assert_eq!(broadcast_shape(&[1], &[5, 3]).unwrap().as_slice(), &[5, 3]);
assert!(broadcast_shape(&[3], &[4]).is_none());
}
#[test]
fn test_broadcast_strides() {
// [3,1] with strides [1,1] broadcast to [3,4]
assert_eq!(broadcast_strides(&[3, 1], &[1, 1], &[3, 4]).as_slice(), &[1, 0]);
}
}