From 00e54afc02c6c19e877c62f0aab1507bba1d1e34 Mon Sep 17 00:00:00 2001 From: Younes Date: Sun, 18 Oct 2026 11:15:29 +0200 Subject: [PATCH 1/2] test(cutile): cover view of an offset slice with wide tiles TensorView::view rebuilds its specialization from the allocation base and drops the slice offset, so a view over a slice that starts 4 bytes past a 16-byte boundary advertises base_ptr_div=16. The JIT then emits vectorized loads and the launch fails with CUDA error 716 (misaligned address). The host-only test checks base_ptr_div directly; the GPU test runs the reported repro (arange(128*128+16).slice(1..).view([128,128]) copied with 32x32 tiles). Signed-off-by: Younes --- cutile-rs/cutile/tests/tensor_views.rs | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/cutile-rs/cutile/tests/tensor_views.rs b/cutile-rs/cutile/tests/tensor_views.rs index 6629a4381d..921ea20f4c 100644 --- a/cutile-rs/cutile/tests/tensor_views.rs +++ b/cutile-rs/cutile/tests/tensor_views.rs @@ -48,6 +48,15 @@ mod tensor_views_module { let tile: Tile = load_tile_like(input, output); output.store(tile); } + + /// Copies a 32x32 tile. Tiles this wide get vectorized loads when the + /// specialization claims a 16-byte-aligned base, so a wrong + /// `base_ptr_div` on an offset view faults instead of silently working. + #[cutile::entry()] + fn copy_32x32(output: &mut Tensor, input: &Tensor) { + let tile: Tile = load_tile_like(input, output); + output.store(tile); + } } #[test] @@ -267,6 +276,47 @@ fn slice_chained() { }); } +#[test] +fn view_of_offset_slice_keeps_measured_alignment() { + // Slicing one f32 in moves the data pointer 4 bytes off the 16-aligned + // allocation. Re-viewing that slice must keep the slice's alignment, not + // revert to the allocation's. + common::with_test_stack(|| { + let tensor = api::arange::(64).sync().expect("Failed."); + let sliced = tensor.slice(&[1..33]).expect("Failed."); + let viewed = sliced.view(&[4, 8]).expect("Failed."); + + assert_eq!(sliced.spec().base_ptr_div.divisor, 4); + assert_eq!( + viewed.spec().base_ptr_div, + sliced.spec().base_ptr_div, + "view of an offset slice must keep the slice's base_ptr_div" + ); + }); +} + +#[test] +fn view_of_offset_slice_launches_with_wide_tiles() { + // arange(128*128 + 16), slice one element in, view as [128, 128], copy + // with 32x32 tiles. The view's data pointer is 4 bytes past a 16-byte + // boundary, so a spec that claims 16-byte alignment makes the JIT emit + // vectorized loads and the launch fails with a misaligned address. + common::with_test_stack(|| { + let n = 128 * 128; + let tensor = api::arange::(n + 16).sync().expect("Failed."); + let sliced = tensor.slice(&[1..1 + n]).expect("Failed."); + let viewed = sliced.view(&[128, 128]).expect("Failed."); + + let output = api::zeros::(&[128, 128]).sync().expect("Failed."); + let (result, _) = tensor_views_module::copy_32x32(output.partition([32, 32]), &viewed) + .sync() + .expect("copy of a view over an offset slice failed"); + let host: Vec = result.unpartition().to_host_vec().sync().expect("Failed."); + let want: Vec = (1..1 + n).map(|v| v as f32).collect(); + assert_eq!(host, want); + }); +} + #[test] #[allow(clippy::reversed_empty_ranges)] // `5..3` is the rejected input under test fn slice_out_of_bounds_rejected() { From 1268fe0b157685c3751842cde18776a8d77a7e52 Mon Sep 17 00:00:00 2001 From: Younes Date: Sun, 18 Oct 2026 11:18:42 +0200 Subject: [PATCH 2/2] fix(cutile): keep the slice offset in TensorView::view specialization TensorView::view recomputed the specialization from the allocation base and dropped offset_bytes, unlike TensorView::slice and Tensor::slice. The kernel receives base + offset_bytes, so a view over a slice that starts 4 bytes past a 16-byte boundary claimed base_ptr_div=16, the JIT emitted vectorized loads, and the launch failed with CUDA error 716 (misaligned address). Add offset_bytes to the pointer passed to compute_spec. The other compute_spec sites build specs for Tensor, which has no offset, or already add it. Signed-off-by: Younes --- cutile-rs/CHANGELOG.md | 8 ++++++++ cutile-rs/cutile/src/tensor.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cutile-rs/CHANGELOG.md b/cutile-rs/CHANGELOG.md index 4b7ec6eff9..2dd5f9bb5e 100644 --- a/cutile-rs/CHANGELOG.md +++ b/cutile-rs/CHANGELOG.md @@ -17,6 +17,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/). and release tags are namespaced `cutile-rs/vX.Y.Z`. Crate names and versions are unchanged. +### Fixed + +- `TensorView::view` keeps the slice offset when it recomputes the + specialization, so a view over a slice that starts off a 16-byte boundary + no longer advertises the allocation's alignment. Before, the JIT emitted + vectorized loads for such views and launches failed with CUDA error 716 + (misaligned address). + ## [0.4.0] - 2026-09-25 Debug builds support cuda-gdb and Nsight. DGX Spark is now supported. diff --git a/cutile-rs/cutile/src/tensor.rs b/cutile-rs/cutile/src/tensor.rs index 68cf53b8ce..0c29dbba68 100644 --- a/cutile-rs/cutile/src/tensor.rs +++ b/cutile-rs/cutile/src/tensor.rs @@ -1479,7 +1479,7 @@ impl<'a, T: DType> TensorView<'a, T> { let new_shape: Vec = shape.iter().map(|&x| x as i32).collect(); let new_strides = contiguous_strides(&new_shape); let spec = compute_spec( - self.base.storage.spec_ptr(), + self.base.storage.spec_ptr() + self.offset_bytes as u64, &new_shape, &new_strides, size_of::() as i32,