Skip to content

Commit 46b8df1

Browse files
[slimtensor] Add as_strided() view operation for tensor reinterpretation (#16838)
This PR was created by the merge bot to help merge the original PR into the main branch. ghstack PR number: #16443 by @Gasoonjia ^ Please use this as the source of truth for the PR details, comments, and reviews ghstack PR base: https://github.com/pytorch/executorch/tree/gh/gasoonjia/85/base ghstack PR head: https://github.com/pytorch/executorch/tree/gh/gasoonjia/85/head Merge bot PR base: https://github.com/pytorch/executorch/tree/gh/gasoonjia/84/orig Merge bot PR head: https://github.com/pytorch/executorch/tree/gh/gasoonjia/85/orig Differential Revision: [D89951036](https://our.internmc.facebook.com/intern/diff/D89951036/) @diff-train-skip-merge --------- Co-authored-by: gasoonjia <gasoonjia@icloud.com>
1 parent 1336645 commit 46b8df1

7 files changed

Lines changed: 678 additions & 19 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#pragma once
10+
11+
#include <cstdint>
12+
#include <utility>
13+
14+
#include <executorch/backends/aoti/slim/c10/macros/Macros.h>
15+
#include <executorch/runtime/platform/assert.h>
16+
17+
namespace executorch::backends::aoti::slim::c10 {
18+
19+
namespace detail {
20+
21+
/// Slow path for maybe_wrap_dim when dimension needs validation.
22+
template <typename T>
23+
inline T maybe_wrap_dim_slow(T dim, T dim_post_expr, bool wrap_scalar) {
24+
ET_CHECK_MSG(
25+
dim_post_expr >= 0,
26+
"Rank cannot be negative but got %ld",
27+
static_cast<long>(dim_post_expr));
28+
29+
if (dim_post_expr == 0) {
30+
ET_CHECK_MSG(
31+
wrap_scalar,
32+
"Dimension specified as %ld but tensor has no dimensions",
33+
static_cast<long>(dim));
34+
// Recursively call with dim_post_expr=1
35+
if (dim >= 0 && dim < 1) {
36+
return dim;
37+
} else if (dim >= -1 && dim < 0) {
38+
return dim + 1;
39+
}
40+
ET_CHECK_MSG(
41+
false,
42+
"Dimension out of range (expected to be in range of [-1, 0], but got %ld)",
43+
static_cast<long>(dim));
44+
}
45+
46+
T min = dim_post_expr * -1;
47+
T max = dim_post_expr - 1;
48+
ET_CHECK_MSG(
49+
min <= dim && dim <= max,
50+
"Dimension out of range (expected to be in range of [%ld, %ld], but got %ld)",
51+
static_cast<long>(min),
52+
static_cast<long>(max),
53+
static_cast<long>(dim));
54+
55+
// This should be unreachable if above check passes
56+
return dim < 0 ? dim + dim_post_expr : dim;
57+
}
58+
59+
} // namespace detail
60+
61+
/// Wraps a dimension index to handle negative indexing.
62+
/// For example, dim=-1 with dim_post_expr=3 returns 2.
63+
///
64+
/// @param dim The dimension index (may be negative).
65+
/// @param dim_post_expr The number of dimensions.
66+
/// @param wrap_scalar If true, allows wrapping for 0-dimensional tensors.
67+
/// @return The wrapped dimension index (always non-negative).
68+
template <typename T>
69+
inline T _maybe_wrap_dim(T dim, T dim_post_expr, bool wrap_scalar = true) {
70+
// Inline the fast paths
71+
if (SLIMTENSOR_LIKELY(dim_post_expr * -1 <= dim && dim < dim_post_expr)) {
72+
if (dim < 0) {
73+
return dim + dim_post_expr;
74+
}
75+
return dim;
76+
}
77+
// Check edge-cases out-of-line
78+
return detail::maybe_wrap_dim_slow<T>(
79+
std::move(dim), std::move(dim_post_expr), wrap_scalar);
80+
}
81+
82+
/// Wraps a dimension index for int64_t.
83+
inline int64_t
84+
maybe_wrap_dim(int64_t dim, int64_t dim_post_expr, bool wrap_scalar = true) {
85+
return _maybe_wrap_dim(dim, dim_post_expr, wrap_scalar);
86+
}
87+
88+
/// Wraps a dimension index for size_t.
89+
inline int64_t
90+
maybe_wrap_dim(int64_t dim, size_t dim_post_expr, bool wrap_scalar = true) {
91+
return _maybe_wrap_dim(dim, static_cast<int64_t>(dim_post_expr), wrap_scalar);
92+
}
93+
94+
} // namespace executorch::backends::aoti::slim::c10

backends/aoti/slim/c10/core/targets.bzl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ def define_common_targets():
6767
],
6868
)
6969

70+
# Header-only library for WrapDimMinimal
71+
runtime.cxx_library(
72+
name = "wrap_dim_minimal",
73+
headers = [
74+
"WrapDimMinimal.h",
75+
],
76+
visibility = ["@EXECUTORCH_CLIENTS"],
77+
exported_deps = [
78+
"//executorch/backends/aoti/slim/c10/macros:macros",
79+
"//executorch/runtime/platform:platform",
80+
],
81+
)
82+
7083
# Combined c10 core library
7184
runtime.cxx_library(
7285
name = "core",
@@ -77,5 +90,6 @@ def define_common_targets():
7790
":device_type",
7891
":scalar_type",
7992
":sizes_and_strides",
93+
":wrap_dim_minimal",
8094
],
8195
)

backends/aoti/slim/core/SlimTensor.h

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010

1111
#include <cstdint>
1212
#include <cstring>
13+
#include <optional>
1314
#include <utility>
1415
#include <vector>
1516

17+
#include <c10/util/safe_numerics.h>
18+
1619
#include <executorch/backends/aoti/slim/c10/core/Contiguity.h>
1720
#include <executorch/backends/aoti/slim/c10/core/Device.h>
1821
#include <executorch/backends/aoti/slim/c10/core/ScalarType.h>
@@ -254,22 +257,113 @@ class SlimTensor {
254257
}
255258

256259
/**
257-
* Set sizes and strides together.
260+
* Set sizes, strides, and storage offset together.
258261
*/
259-
void set_sizes_and_strides(IntArrayRef sizes, IntArrayRef strides) {
262+
void set_sizes_and_strides(
263+
IntArrayRef sizes,
264+
IntArrayRef strides,
265+
std::optional<int64_t> storage_offset = std::nullopt) {
266+
const size_t new_dim = sizes.size();
260267
ET_CHECK_MSG(
261-
sizes.size() == strides.size(),
262-
"sizes (%zu) and strides (%zu) must have the same length",
263-
sizes.size(),
268+
new_dim == strides.size(),
269+
"dimensionality of sizes (%zu) must match dimensionality of strides (%zu)",
270+
new_dim,
264271
strides.size());
265272

266-
sizes_and_strides_.set_sizes(sizes);
267-
sizes_and_strides_.set_strides(strides);
273+
std::vector<int64_t> new_sizes = toVec(sizes);
274+
std::vector<int64_t> new_strides = toVec(strides);
275+
276+
// stride calculation logic
277+
bool overflowed = false;
278+
if (new_dim > 0) {
279+
for (int64_t dim = new_dim - 1; dim >= 0; dim--) {
280+
if (strides[dim] >= 0) {
281+
new_strides[dim] = strides[dim];
282+
} else {
283+
// for negative strides
284+
if (dim == new_dim - 1) {
285+
new_strides[dim] = 1;
286+
} else {
287+
overflowed |= ::c10::mul_overflows(
288+
new_strides[dim + 1],
289+
std::max<int64_t>(new_sizes[dim + 1], 1),
290+
&new_strides[dim]);
291+
}
292+
}
293+
}
294+
}
295+
ET_CHECK_MSG(!overflowed, "Stride calculation overflowed");
296+
297+
sizes_and_strides_.set_sizes(makeArrayRef(new_sizes));
298+
sizes_and_strides_.set_strides(makeArrayRef(new_strides));
299+
if (storage_offset.has_value()) {
300+
storage_offset_ = *storage_offset;
301+
}
268302

269303
refresh_numel();
270304
refresh_contiguous();
271305
}
272306

307+
/**
308+
* Set sizes to a contiguous layout (computes strides automatically).
309+
*/
310+
void set_sizes_contiguous(IntArrayRef sizes) {
311+
std::vector<int64_t> contig_strides = compute_contiguous_strides(sizes);
312+
set_sizes_and_strides(sizes, makeArrayRef(contig_strides));
313+
}
314+
315+
// =========================================================================
316+
// View Operations
317+
// =========================================================================
318+
319+
/**
320+
* Returns a view of the tensor with the specified sizes, strides, and
321+
* storage offset. The returned tensor shares the same underlying storage.
322+
*
323+
* @param sizes The sizes of the view.
324+
* @param strides The strides of the view.
325+
* @param storage_offset Offset into storage in number of elements.
326+
* @return A new SlimTensor that is a view of this tensor.
327+
*/
328+
inline SlimTensor as_strided(
329+
IntArrayRef sizes,
330+
IntArrayRef strides,
331+
int64_t storage_offset) const;
332+
333+
/**
334+
* Overload for initializer lists.
335+
*/
336+
inline SlimTensor as_strided(
337+
std::initializer_list<int64_t> sizes,
338+
std::initializer_list<int64_t> strides,
339+
int64_t storage_offset) const {
340+
return as_strided(
341+
makeArrayRef(sizes), makeArrayRef(strides), storage_offset);
342+
}
343+
344+
/**
345+
* Modifies this tensor in-place to have the specified sizes, strides, and
346+
* storage offset. The underlying storage remains unchanged.
347+
*
348+
* @param sizes The new sizes.
349+
* @param strides The new strides.
350+
* @param storage_offset New offset into storage in number of elements.
351+
* @return Reference to this tensor.
352+
*/
353+
inline SlimTensor&
354+
as_strided_(IntArrayRef sizes, IntArrayRef strides, int64_t storage_offset);
355+
356+
/**
357+
* Overload for initializer lists.
358+
*/
359+
inline SlimTensor& as_strided_(
360+
std::initializer_list<int64_t> sizes,
361+
std::initializer_list<int64_t> strides,
362+
int64_t storage_offset) {
363+
return as_strided_(
364+
makeArrayRef(sizes), makeArrayRef(strides), storage_offset);
365+
}
366+
273367
// =========================================================================
274368
// Copy Operation
275369
// =========================================================================
@@ -278,7 +372,7 @@ class SlimTensor {
278372
* Copy data from another tensor to this tensor.
279373
*
280374
* Both tensors must have the same numel and dtype.
281-
* Supports CPU-to-CPU and cross-device copies (CPU↔CUDA, CUDA↔CUDA).
375+
* Currently only supports CPU-to-CPU copy (contiguous tensors only).
282376
*
283377
* @param other The source tensor to copy from
284378
* @return Reference to this tensor
@@ -371,3 +465,7 @@ class SlimTensor {
371465
};
372466

373467
} // namespace executorch::backends::aoti::slim
468+
469+
// Include view operations implementations (must be after SlimTensor class
470+
// definition)
471+
#include <executorch/backends/aoti/slim/core/SlimTensorView-incl.h>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#pragma once
10+
11+
#include <executorch/backends/aoti/slim/c10/core/WrapDimMinimal.h>
12+
#include <executorch/backends/aoti/slim/util/ArrayRefUtil.h>
13+
14+
namespace executorch::backends::aoti::slim {
15+
16+
inline SlimTensor SlimTensor::as_strided(
17+
IntArrayRef sizes,
18+
IntArrayRef strides,
19+
int64_t storage_offset) const {
20+
SlimTensor result = *this;
21+
result.as_strided_(sizes, strides, storage_offset);
22+
return result;
23+
}
24+
25+
inline SlimTensor& SlimTensor::as_strided_(
26+
IntArrayRef sizes,
27+
IntArrayRef strides,
28+
int64_t storage_offset) {
29+
ET_CHECK_MSG(
30+
sizes.size() == strides.size(),
31+
"as_strided: number of sizes (%zu) must equal number of strides (%zu)",
32+
sizes.size(),
33+
strides.size());
34+
35+
for (size_t i = 0; i < sizes.size(); ++i) {
36+
ET_CHECK_MSG(
37+
sizes[i] >= 0,
38+
"as_strided: size at dimension %zu is negative: %ld",
39+
i,
40+
static_cast<long>(sizes[i]));
41+
}
42+
43+
ET_CHECK_MSG(
44+
storage_offset >= 0,
45+
"as_strided: storage_offset must be non-negative, got: %ld",
46+
static_cast<long>(storage_offset));
47+
48+
this->set_sizes_and_strides(sizes, strides, storage_offset);
49+
return *this;
50+
}
51+
52+
} // namespace executorch::backends::aoti::slim

backends/aoti/slim/core/targets.bzl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def define_common_targets():
2626
name = "slimtensor",
2727
headers = [
2828
"SlimTensor.h",
29+
"SlimTensorView-incl.h",
2930
],
3031
visibility = ["@EXECUTORCH_CLIENTS"],
3132
exported_deps = [
@@ -34,9 +35,10 @@ def define_common_targets():
3435
"//executorch/backends/aoti/slim/c10/core:device",
3536
"//executorch/backends/aoti/slim/c10/core:scalar_type",
3637
"//executorch/backends/aoti/slim/c10/core:sizes_and_strides",
38+
"//executorch/backends/aoti/slim/c10/core:wrap_dim_minimal",
3739
"//executorch/backends/aoti/slim/util:array_ref_util",
3840
"//executorch/backends/aoti/slim/util:size_util",
39-
"//executorch/backends/aoti/slim/c10/cuda:exception",
4041
"//executorch/runtime/platform:platform",
42+
"//executorch/backends/aoti/slim/c10/cuda:exception",
4143
],
4244
)

backends/aoti/slim/core/test/targets.bzl

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@ def get_backend_mode():
77

88
def define_common_targets():
99
"""Define test targets for SlimTensor core module."""
10+
runtime.cxx_test(
11+
name = "test_slimtensor_dtypes",
12+
srcs = [
13+
"test_slimtensor_dtypes.cpp",
14+
],
15+
deps = [
16+
"//executorch/backends/aoti/slim/factory:empty",
17+
],
18+
)
1019

11-
# GPU storage test with CUDA support
20+
# Backend mode specific tests
1221
for backend_mode in get_backend_mode():
1322
backend_suffix = "_" + backend_mode if backend_mode == "cuda" else ""
1423

@@ -57,12 +66,14 @@ def define_common_targets():
5766
**backend_kwargs
5867
)
5968

60-
runtime.cxx_test(
61-
name = "test_slimtensor_dtypes",
62-
srcs = [
63-
"test_slimtensor_dtypes.cpp",
64-
],
65-
deps = [
66-
"//executorch/backends/aoti/slim/factory:empty",
67-
],
68-
)
69+
runtime.cxx_test(
70+
name = "test_as_strided" + backend_suffix,
71+
srcs = [
72+
"test_as_strided.cpp",
73+
],
74+
deps = [
75+
"//executorch/backends/aoti/slim/core:slimtensor",
76+
"//executorch/backends/aoti/slim/factory:empty",
77+
],
78+
**backend_kwargs
79+
)

0 commit comments

Comments
 (0)