已合并
feat(inductor): Add dispatcher-free _empty_strided_npu fast path allocation #41836
feat(inductor): Add dispatcher-free _empty_strided_npu fast path allocation #41836
已合并
liuyutong创建于 7月16日
4 个文件变更+340-2
Atest/npu/test_empty_strided_npu.py+222-0
@@ -0,0 +1,222 @@
1+# Copyright (c) 2026, Huawei Technologies Co., Ltd
2+"""
3+Empty_strided_npu fast path tests for NPU backend.
4+ 
5+This test file validates the torch_npu._C._empty_strided_npu fast path
6+implementation, which is used by inductor for dispatcher-free NPU memory allocation.
7+"""
8+ 
9+import torch
10+import torch_npu
11+from torch_npu.testing.testcase import TestCase, run_tests
12+from torch_npu.testing.decorator import Dtypes, instantiate_tests
13+ 
14+ 
15+@instantiate_tests
16+class TestEmptyStridedNpu(TestCase):
17+ """
18+ Test suite for torch_npu._C._empty_strided_npu fast path.
19+ 
20+ This fast path is used by inductor to bypass the dispatcher overhead
21+ when allocating strided NPU tensors during compilation.
22+ """
23+ 
24+ def test_empty_strided_npu_empty_tensor(self, device="npu"):
25+ """Test _empty_strided_npu with tensors containing zero dimensions."""
26+ # Test tensor with zero-sized dimensions
27+ sizes = (0, 3, 0)
28+ strides = (0, 0, 0)
29+ result = torch_npu._C._empty_strided_npu(sizes, strides, torch.float32)
30+ 
31+ self.assertEqual(result.shape, torch.Size([0, 3, 0]))
32+ self.assertEqual(result.stride(), (0, 0, 0))
33+ self.assertEqual(result.device.type, "npu")
34+ 
35+ def test_empty_strided_npu_various_dtypes(self, device="npu"):
36+ """Test _empty_strided_npu with different data types."""
37+ dtypes = [
38+ torch.float32,
39+ torch.float16,
40+ torch.int32,
41+ torch.int8,
42+ torch.uint8,
43+ torch.bool,
44+ ]
45+ 
46+ for dtype in dtypes:
47+ sizes = (2, 3)
48+ strides = (3, 1)
49+ result = torch_npu._C._empty_strided_npu(sizes, strides, dtype)
50+ 
51+ self.assertEqual(result.shape, torch.Size([2, 3]))
52+ self.assertEqual(result.stride(), (3, 1))
53+ self.assertEqual(result.dtype, dtype)
54+ self.assertEqual(result.device.type, "npu")
55+ 
56+ def test_empty_strided_npu_complex_strides(self, device="npu"):
57+ """Test _empty_strided_npu with non-contiguous stride patterns."""
58+ test_cases = [
59+ # (sizes, strides, description)
60+ ((4, 4), (8, 1), "non-contiguous strides"),
61+ ((3, 5), (10, 1), "larger stride"),
62+ ((2, 3, 4), (12, 4, 1), "3D contiguous"),
63+ ((2, 2, 2), (4, 2, 1), "3D non-contiguous"),
64+ ]
65+ 
66+ for sizes, strides, desc in test_cases:
67+ result = torch_npu._C._empty_strided_npu(sizes, strides, torch.float32)
68+ 
69+ self.assertEqual(result.shape, torch.Size(sizes),
70+ f"Failed for {desc}: shape mismatch")
71+ self.assertEqual(result.stride(), strides,
72+ f"Failed for {desc}: stride mismatch")
73+ self.assertEqual(result.device.type, "npu",
74+ f"Failed for {desc}: device mismatch")
75+ 
76+ def test_empty_strided_npu_broadcast_strides(self, device="npu"):
77+ """Test _empty_strided_npu with zero (broadcast) strides."""
78+ # Zero stride is used for broadcasting
79+ sizes = (3, 4)
80+ strides = (0, 1) # broadcast along first dimension
81+ result = torch_npu._C._empty_strided_npu(sizes, strides, torch.float32)
82+ 
83+ self.assertEqual(result.shape, torch.Size([3, 4]))
84+ self.assertEqual(result.stride(), (0, 1))
85+ 
86+ def test_empty_strided_npu_storage_size(self, device="npu"):
87+ """Test that _empty_strided_npu allocates correct storage size."""
88+ # Create a tensor with non-contiguous strides
89+ sizes = (3, 4)
90+ strides = (8, 1)
91+ 
92+ fast_result = torch_npu._C._empty_strided_npu(sizes, strides, torch.float32)
93+ normal_result = torch.empty_strided(sizes, strides, device=device, dtype=torch.float32)
94+ 
95+ # Storage sizes should match
96+ fast_storage = fast_result.storage().size()
97+ normal_storage = normal_result.storage().size()
98+ 
99+ self.assertEqual(fast_storage, normal_storage,
100+ "Storage size mismatch between fast and normal path")
101+ 
102+ # Storage should be large enough to hold the tensor
103+ expected_min_storage = 1 + (sizes[0] - 1) * strides[0] + (sizes[1] - 1) * strides[1]
104+ self.assertGreaterEqual(fast_storage, expected_min_storage,
105+ "Storage size too small for the given shape and strides")
106+ 
107+ def test_empty_strided_npu_with_new_empty_strided(self, device="npu"):
108+ """Test that new_empty_strided works correctly (it may use the fast path internally)."""
109+ x = torch.ones(()).to(device=device)
110+ x_new = x.new_empty_strided([2, 3], [3, 1], dtype=torch.float32)
111+ 
112+ self.assertEqual(x_new.shape, torch.Size([2, 3]))
113+ self.assertEqual(x_new.stride(), (3, 1))
114+ self.assertEqual(x_new.device.type, device)
115+ self.assertEqual(x_new.dtype, torch.float32)
116+ 
117+ @Dtypes(torch.float32, torch.float16, torch.int32)
118+ def test_empty_strided_npu_with_decorator(self, dtype, device="npu"):
119+ """Test _empty_strided_npu with @Dtypes decorator for multiple types."""
120+ sizes = (3, 4)
121+ strides = (4, 1)
122+ result = torch_npu._C._empty_strided_npu(sizes, strides, dtype)
123+ 
124+ self.assertEqual(result.shape, torch.Size([3, 4]))
125+ self.assertEqual(result.stride(), (4, 1))
126+ self.assertEqual(result.dtype, dtype)
127+ self.assertEqual(result.device.type, "npu")
128+ 
129+ def test_empty_strided_npu_deterministic_mode_consistency(self, device="npu"):
130+ """
131+ Test that _empty_strided_npu behaves identically to torch.empty_strided
132+ when deterministic algorithms are enabled.
133+ 
134+ This is critical for inductor correctness: if the fast path allocates
135+ memory differently than the normal path under deterministic mode,
136+ compiled results may differ from eager mode results.
137+ """
138+ # Save original state
139+ original_deterministic = torch.are_deterministic_algorithms_enabled()
140+ 
141+ try:
142+ # Enable deterministic algorithms
143+ torch.use_deterministic_algorithms(True)
144+ 
145+ test_cases = [
146+ ((2, 3), (3, 1), torch.float32, "contiguous 2D"),
147+ ((4, 5), (10, 1), torch.float16, "non-contiguous 2D"),
148+ ((3, 4, 5), (20, 5, 1), torch.float32, "3D contiguous"),
149+ ((0, 3), (0, 0), torch.float32, "empty tensor"),
150+ ((2, 2), (4, 1), torch.int32, "int32 type"),
151+ ]
152+ 
153+ for sizes, strides, dtype, desc in test_cases:
154+ # Allocate using fast path
155+ fast_result = torch_npu._C._empty_strided_npu(sizes, strides, dtype)
156+ 
157+ # Allocate using normal path
158+ normal_result = torch.empty_strided(sizes, strides, device=device, dtype=dtype)
159+ 
160+ # Verify metadata matches exactly
161+ self.assertEqual(fast_result.shape, normal_result.shape,
162+ f"[{desc}] Shape mismatch in deterministic mode")
163+ self.assertEqual(fast_result.stride(), normal_result.stride(),
164+ f"[{desc}] Stride mismatch in deterministic mode")
165+ self.assertEqual(fast_result.dtype, normal_result.dtype,
166+ f"[{desc}] Dtype mismatch in deterministic mode")
167+ self.assertEqual(fast_result.device, normal_result.device,
168+ f"[{desc}] Device mismatch in deterministic mode")
169+ 
170+ # Verify storage size matches (critical for deterministic memory usage)
171+ fast_storage_size = fast_result.storage().size()
172+ normal_storage_size = normal_result.storage().size()
173+ self.assertEqual(fast_storage_size, normal_storage_size,
174+ f"[{desc}] Storage size mismatch: fast={fast_storage_size}, "
175+ f"normal={normal_storage_size} (may cause nondeterministic memory usage)")
176+ 
177+ # Verify storage offset matches
178+ self.assertEqual(fast_result.storage_offset(), normal_result.storage_offset(),
179+ f"[{desc}] Storage offset mismatch in deterministic mode")
180+ 
181+ finally:
182+ # Restore original state
183+ torch.use_deterministic_algorithms(original_deterministic)
184+ 
185+ def test_empty_strided_npu_deterministic_repeatability(self, device="npu"):
186+ """
187+ Test that _empty_strided_npu produces repeatable results in deterministic mode.
188+ 
189+ Multiple allocations with the same parameters should yield tensors with
190+ identical metadata and storage characteristics.
191+ """
192+ original_deterministic = torch.are_deterministic_algorithms_enabled()
193+ 
194+ try:
195+ torch.use_deterministic_algorithms(True)
196+ 
197+ sizes = (3, 4)
198+ strides = (8, 1)
199+ dtype = torch.float32
200+ 
201+ # Allocate multiple times
202+ results = [
203+ torch_npu._C._empty_strided_npu(sizes, strides, dtype)
204+ for _ in range(5)
205+ ]
206+ 
207+ # All results should have identical metadata
208+ first = results[0]
209+ for i, result in enumerate(results[1:], 1):
210+ self.assertEqual(result.shape, first.shape,
211+ f"Result {i} shape differs from first allocation")
212+ self.assertEqual(result.stride(), first.stride(),
213+ f"Result {i} stride differs from first allocation")
214+ self.assertEqual(result.storage().size(), first.storage().size(),
215+ f"Result {i} storage size differs from first allocation")
216+ 
217+ finally:
218+ torch.use_deterministic_algorithms(original_deterministic)
219+ 
220+ 
221+if __name__ == "__main__":
222+ run_tests()
Mtorch_npu/csrc/InitNpuBindings.cpp+53-2
@@ -1,9 +1,14 @@
1#include <Python.h>1#include <Python.h>
2+#include <ATen/ATen.h>
2#include <ATen/Parallel.h>3#include <ATen/Parallel.h>
4+#include <c10/util/SmallVector.h>
5+#include <torch/csrc/Dtype.h>
3#include <torch/csrc/Exceptions.h>6#include <torch/csrc/Exceptions.h>
4#include <torch/csrc/Generator.h>7#include <torch/csrc/Generator.h>
8+#include <torch/csrc/autograd/python_variable.h>
5#include <torch/csrc/profiler/python/combined_traceback.h>9#include <torch/csrc/profiler/python/combined_traceback.h>
6 10 
11+#include "torch_npu/csrc/aten/common/TensorFactories.h"
7#include "torch_npu/csrc/npu/Event.h"12#include "torch_npu/csrc/npu/Event.h"
8#include "torch_npu/csrc/npu/DataParallelComm.h"13#include "torch_npu/csrc/npu/DataParallelComm.h"
9#include "torch_npu/csrc/core/npu/NPUCachingAllocator.h"14#include "torch_npu/csrc/core/npu/NPUCachingAllocator.h"
@@ -52,12 +57,12 @@ void AddPyMethodDefs(std::vector<PyMethodDef>& vector, PyMethodDef* methods)
52 57 
53PyObject* THPModule_npu_shutdown(PyObject* self, PyObject* arg)58PyObject* THPModule_npu_shutdown(PyObject* self, PyObject* arg)
54{59{
55- int check_error;
56 if (!PyBool_Check(arg)) {60 if (!PyBool_Check(arg)) {
57 PyErr_SetString(PyExc_TypeError, "Expected a boolean value");61 PyErr_SetString(PyExc_TypeError, "Expected a boolean value");
58 return NULL;62 return NULL;
59 }63 }
60- check_error = PyObject_IsTrue(arg);64+ int check_error = PyObject_IsTrue(arg);
65+ (void)check_error; // Suppress unused variable warning
61 66 
62 // cudaFree is blocking and will synchronize across all kernels executing67 // cudaFree is blocking and will synchronize across all kernels executing
63 // on the current device, while aclrtFree Free device memory immediately.68 // on the current device, while aclrtFree Free device memory immediately.
@@ -138,10 +143,56 @@ PyObject* THPModule_npu_shutdown_synchronize(PyObject* /* unused */)
138 }143 }
139}144}
140 145 
146+// Low-overhead NPU allocation for inductor-generated wrappers.
147+//
148+// at::empty_strided (and the device='npu' factory path) is dispatched through
149+// the operator dispatcher, which upstream measured as "surprisingly slow"
150+// (~2us/allocation, see torch/csrc/dynamo/guards.cpp). Inductor backward graphs
151+// allocate dozens-to-hundreds of buffers per step, so that overhead dominates
152+// the host side. This mirrors upstream's _empty_strided_<device> fast path
153+// (CUDA/XPU/MTIA): parse the (sizes, strides, dtype) 3-tuple directly and call
154+// the NPU-native factory, bypassing the dispatcher while still running the
155+// required NPU storage-descriptor setup inside NPUNativeFunctions::empty_strided.
156+static void _npu_unwrap_size_tuple(PyObject* obj, c10::SmallVector<int64_t, 8>& out)
atomgit-bot
atomgit-botatomgit-bot7月16日

🔴 Critical

文件 torch_npu/csrc/InitNpuBindings.cpp 第 156 行将 _npu_unwrap_size_tuple 声明为 static void,但第 165 行包含 return false;。在 C++ 中,void 函数不能返回表达式值,这是一个编译错误(GCC/Clang 会报 error: return-statement with a value)。

根据 PR 描述中的设计(参见 issue #2888),该函数本应返回 bool 以指示解析是否成功:

当前代码无法通过编译,阻塞所有后续使用。

建议:将第 156 行的返回类型从 void 改为 bool,与 return false; 和 PR 设计一致。

改动建议
156
- static void _npu_unwrap_size_tuple(PyObject* obj, c10::SmallVector<int64_t, 8>& out)
156
+ static bool _npu_unwrap_size_tuple(PyObject* obj, c10::SmallVector<int64_t, 8>& out)
应用建议
likedislike
157+{
158+ TORCH_CHECK(PyTuple_CheckExact(obj), "expected a tuple of ints");
159+ Py_ssize_t len = PyTuple_GET_SIZE(obj);
160+ out.reserve(len);
161+ for (Py_ssize_t i = 0; i < len; ++i) {
162+ // PyTuple_GET_ITEM returns a borrowed ref, no refcount needed.
163+ auto val = PyLong_AsSsize_t(PyTuple_GET_ITEM(obj, i));
164+ if (PyErr_Occurred()) {
165+ return;
166+ }
167+ out.emplace_back(val);
168+ }
169+}
atomgit-bot
atomgit-botatomgit-bot7月16日

🔴 Critical

_npu_unwrap_size_tuple 在 line 156 声明为 static void,但在 line 165 执行 return false;。C++ 标准不允许 void 函数返回任何值,任何 C++ 编译器(GCC/Clang/MSVC)都会将其视为编译错误。此外,如果修复为 bool 返回类型,函数在 for 循环成功结束后(如空 tuple 的场景)没有 return true;,会导致 undefined behavior。

PR 描述中的原始代码明确使用了 static bool 且有 return true;,证实这是实现错误而非有意设计。

建议:将返回类型从 void 改为 bool,并确保所有路径都有明确的返回值:for 循环成功结束后 return true;,PyLong_AsSsize_t 失败后 return false;。与 PR 描述中的设计一致。

改动建议
169
+ static bool _npu_unwrap_size_tuple(PyObject* obj, c10::SmallVector<int64_t, 8>& out)
170
+ {
171
+ TORCH_CHECK(PyTuple_CheckExact(obj), "expected a tuple of ints");
172
+ Py_ssize_t len = PyTuple_GET_SIZE(obj);
173
+ out.reserve(len);
174
+ for (Py_ssize_t i = 0; i < len; ++i) {
175
+ // PyTuple_GET_ITEM returns a borrowed ref, no refcount needed.
176
+ auto val = PyLong_AsSsize_t(PyTuple_GET_ITEM(obj, i));
177
+ if (PyErr_Occurred()) {
178
+ return false;
179
+ }
180
+ out.emplace_back(val);
181
+ }
182
+ return true;
169
183
  }
应用建议
likedislike
170+ 
171+PyObject* THPModule_empty_strided_npu(PyObject* /* unused */, PyObject* args)
172+{
173+ HANDLE_TH_ERRORS;
174+ TORCH_CHECK(PyTuple_CheckExact(args) && PyTuple_GET_SIZE(args) == 3,
175+ "_empty_strided_npu expects exactly 3 args: (sizes, strides, dtype)");
176+ 
177+ c10::SmallVector<int64_t, 8> sizes;
178+ c10::SmallVector<int64_t, 8> strides;
179+ _npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 0), sizes);
180+ _npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 1), strides);
atomgit-botatomgit-bot
atomgit-botatomgit-bot7月16日

🟠 High Priority

文件 torch_npu/csrc/InitNpuBindings.cpp 第 179–180 行对 _npu_unwrap_size_tuple 的两次调用均未检查返回值(即使将返回类型修正为 bool 后)。当输入中包含非整数元素时,PyLong_AsSsize_t 设置 Python 异常并返回 -1,_npu_unwrap_size_tuple 返回 false,但调用方未检测到此失败,继续使用可能不完整或被污染的 sizes/strides 数据调用 empty_strided_npu(第 187 行)。

失败模式:向 _empty_strided_npu 传入包含非整数的元组 → _npu_unwrap_size_tuple 返回 false(已设置 Python TypeError)但被忽略 → 继续执行 empty_strided_npu,使用错误数据分配 NPU 内存或触发更底层的崩溃,原始 Python 异常可能被后续操作覆盖或丢失。

建议:调用后检查返回值,若为 false 则返回 nullptr(Python 错误已由 PyLong_AsSsize_t 设置,调用方只需传播失败):添加 if (!_npu_unwrap_size_tuple(...)) { return nullptr; }

改动建议
180
+ if (!_npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 0), sizes)) {
181
+ return nullptr;
182
+ }
180
- _npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 1), strides);
183
+ if (!_npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 1), strides)) {
184
+ return nullptr;
185
+ }
应用建议
likedislike
atomgit-botatomgit-bot7月16日

🟠 High Priority

THPModule_empty_strided_npu 在 lines 179-180 调用 _npu_unwrap_size_tuple 时未检查返回值。如果该函数因 PyLong_AsSsize_t 失败而返回 false(修复返回类型后),此时 Python 异常已设置但 sizes/strides 向量可能不完整(仅包含解析失败前的元素)。调用者继续执行并调用 empty_strided_npu(sizes, strides, dtype),此时 sizes 与 strides 的长度可能不一致,导致 empty_strided_npustride[d] 越界访问(line 526),产生未定义行为。此外,函数最终返回非 NULL 的 PyObject 指针(THPVariable_Wrap 成功包装了异常数据),导致 Python 层的异常被延迟/隐藏。

建议:检查 _npu_unwrap_size_tuple 的返回值,失败时返回 nullptr 让 Python 异常正确传播:

改动建议
180
+ if (!_npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 0), sizes) ||
180
- _npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 1), strides);
181
+ !_npu_unwrap_size_tuple(PyTuple_GET_ITEM(args, 1), strides)) {
182
+ return nullptr;
183
+ }
应用建议
likedislike
181+ 
182+ PyObject* py_dtype = PyTuple_GET_ITEM(args, 2);
183+ TORCH_CHECK(THPDtype_Check(py_dtype), "_empty_strided_npu: arg 3 must be a torch.dtype");
184+ at::ScalarType dtype = reinterpret_cast<THPDtype*>(py_dtype)->scalar_type;
185+ 
186+ return THPVariable_Wrap(
187+ at_npu::native::empty_strided_npu(sizes, strides, dtype));
188+ END_HANDLE_TH_ERRORS;
189+}
190+ 
141// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)191// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
142static PyMethodDef TorchNpuMethods[] = {192static PyMethodDef TorchNpuMethods[] = {
143 {"_npu_shutdown", (PyCFunction)THPModule_npu_shutdown, METH_O, nullptr},193 {"_npu_shutdown", (PyCFunction)THPModule_npu_shutdown, METH_O, nullptr},
144 {"_npu_shutdown_synchronize", (PyCFunction)THPModule_npu_shutdown_synchronize, METH_NOARGS, nullptr},194 {"_npu_shutdown_synchronize", (PyCFunction)THPModule_npu_shutdown_synchronize, METH_NOARGS, nullptr},
195+ {"_empty_strided_npu", (PyCFunction)THPModule_empty_strided_npu, METH_VARARGS, nullptr},
145 {nullptr, nullptr, 0, nullptr}196 {nullptr, nullptr, 0, nullptr}
146};197};
147 198 
Mtorch_npu/csrc/aten/common/TensorFactories.cpp+54-0
@@ -493,6 +493,60 @@ at::Tensor NPUNativeFunctions::new_empty_strided_symint(
493 return at::native::new_empty_strided_symint(self, size, stride, dtype, layout, device, pin_memory);493 return at::native::new_empty_strided_symint(self, size, stride, dtype, layout, device, pin_memory);
494}494}
495 495 
496+// Exported, dispatcher-free strided NPU allocation (see TensorFactories.h).
497+// Defined in the same TU as NPUNativeFunctions::empty_strided so it links to
498+// that (hidden) symbol internally, while TORCH_NPU_API re-exports THIS wrapper
499+// for torch_npu._C to call without going through the operator dispatcher.
500+at::Tensor empty_strided_npu(
501+ c10::IntArrayRef size,
502+ c10::IntArrayRef stride,
503+ at::ScalarType dtype)
504+{
505+ // Low-overhead strided NPU allocation for inductor wrappers.
506+ //
507+ // NPUNativeFunctions::empty_strided goes empty({0}) -> SetDesc ->
508+ // resize_impl_npu_, where empty() additionally runs RECORD_FUNCTION + an
509+ // NPURecordFunction profiler guard and allocates a 0-byte storage that is
510+ // then resized. For the hot inductor allocation path none of that is
511+ // needed, so we inline the essential steps once: compute the storage byte
512+ // size from (size, stride), allocate exactly that, build the tensor, set
513+ // sizes/strides, and set the NPU format descriptor a single time. This
514+ // mirrors the dispatcher-free intent of upstream empty_strided_cuda while
515+ // keeping the NPU-required descriptor setup.
516+ check_size_nonnegative(size);
517+ 
518+ // storage_size in elements: 1 + sum_d (size[d]-1)*stride[d], or 0 if any
519+ // dim is empty (matches resize_impl_npu_).
520+ int64_t storage_nelem = 1;
521+ for (size_t d = 0; d < size.size(); ++d) {
522+ if (size[d] == 0) {
523+ storage_nelem = 0;
524+ break;
525+ }
526+ storage_nelem += (size[d] - 1) * stride[d];
527+ }
atomgit-bot
atomgit-botatomgit-bot7月16日

🟡 Medium Priority

文件 torch_npu/csrc/aten/common/TensorFactories.cpp 第 520–527 行的循环以 size.size() 为界访问 stride[d],但函数未验证 size.size() == stride.size()。若两者长度不同(Python 绑定层未强制长度一致性,且用户在 _empty_strided_npu 中传入不同长度的 sizes 和 strides 元组),当 stride.size() < size.size() 时会越界读取 stride,导致未定义行为;当 stride.size() > size.size() 时,多余的 stride 维度被忽略,产生静默的逻辑错误。

失败模式:_empty_strided_npu((3, 4), (1,), float32) → sizes 长度为 2,strides 长度为 1 → stride[1] 越界访问。

建议:在循环前添加 TORCH_CHECK(size.size() == stride.size(), ...) 验证两者长度一致,与 PyTorch 上游 empty_strided 的行为保持一致。

likedislike
528+ 
529+ auto device = at::Device(c10::DeviceType::PrivateUse1, c10_npu::current_device());
530+ torch_npu::utils::maybe_initialize_npu(device);
531+ c10_npu::NPUGuard guard_(device);
532+ 
533+ auto dtype_meta = c10::scalarTypeToTypeMeta(dtype);
534+ int64_t size_bytes = storage_nelem * dtype_meta.itemsize();
535+ c10::Allocator* allocator = c10_npu::NPUCachingAllocator::get();
536+ c10::intrusive_ptr<c10::StorageImpl> storage_impl = torch_npu::make_npu_storage_impl(
537+ c10::StorageImpl::use_byte_size_t(),
538+ c10::SymInt(size_bytes),
539+ allocator->allocate(size_bytes),
540+ allocator,
541+ true);
542+ 
543+ auto tensor = at::detail::make_tensor<torch_npu::NPUTensorImpl>(storage_impl, dtype_meta);
544+ tensor.unsafeGetTensorImpl()->set_sizes_and_strides(size, stride);
545+ StorageDescHelper::SetDesc(tensor, size, stride);
546+ 
547+ return tensor;
548+}
549+ 
496at::Tensor &empty_out_npu(550at::Tensor &empty_out_npu(
497 at::Tensor &result,551 at::Tensor &result,
498 c10::IntArrayRef size,552 c10::IntArrayRef size,
Mtorch_npu/csrc/aten/common/TensorFactories.h+11-0
@@ -1,11 +1,22 @@
1#pragma once1#pragma once
2 2 
3+#include <ATen/ATen.h>
3#include <c10/core/TensorOptions.h>4#include <c10/core/TensorOptions.h>
4#include "torch_npu/csrc/core/npu/NPUException.h"5#include "torch_npu/csrc/core/npu/NPUException.h"
6+#include "torch_npu/csrc/core/npu/NPUMacros.h"
5 7 
6namespace at_npu {8namespace at_npu {
7namespace native {9namespace native {
8 10 
11+// Exported, dispatcher-free NPU strided allocation for inductor-generated
12+// wrappers. Wraps NPUNativeFunctions::empty_strided (whose symbol is hidden in
13+// libtorch_npu.so) and re-exports it via TORCH_NPU_API so torch_npu._C can call
14+// it directly, mirroring upstream's at::detail::empty_strided_<device> fast path.
15+TORCH_NPU_API at::Tensor empty_strided_npu(
16+ c10::IntArrayRef size,
17+ c10::IntArrayRef stride,
18+ at::ScalarType dtype);
19+ 
9inline void check_size_nonnegative(c10::IntArrayRef& size)20inline void check_size_nonnegative(c10::IntArrayRef& size)
10{21{
11 for (auto& x : size) {22 for (auto& x : size) {