| @@ -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 | + | ||
| 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 | + | ||
| 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() | ||
| @@ -1,9 +1,14 @@ | |||||||||||||||||||||||||||||||||
| 1 | 1 | ||||||||||||||||||||||||||||||||
| 2 | + | ||||||||||||||||||||||||||||||||
| 2 | 3 | ||||||||||||||||||||||||||||||||
| 4 | + | ||||||||||||||||||||||||||||||||
| 5 | + | ||||||||||||||||||||||||||||||||
| 3 | 6 | ||||||||||||||||||||||||||||||||
| 4 | 7 | ||||||||||||||||||||||||||||||||
| 8 | + | ||||||||||||||||||||||||||||||||
| 5 | 9 | ||||||||||||||||||||||||||||||||
| 6 | 10 | ||||||||||||||||||||||||||||||||
| 11 | + | ||||||||||||||||||||||||||||||||
| 7 | 12 | ||||||||||||||||||||||||||||||||
| 8 | 13 | ||||||||||||||||||||||||||||||||
| 9 | 14 | ||||||||||||||||||||||||||||||||
| @@ -52,12 +57,12 @@ void AddPyMethodDefs(std::vector<PyMethodDef>& vector, PyMethodDef* methods) | |||||||||||||||||||||||||||||||||
| 52 | 57 | ||||||||||||||||||||||||||||||||
| 53 | PyObject* THPModule_npu_shutdown(PyObject* self, PyObject* arg) | 58 | PyObject* 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 executing | 67 | // 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) | ||||||||||||||||||||||||||||||||
| 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 | +} | ||||||||||||||||||||||||||||||||
🔴 Critical
PR 描述中的原始代码明确使用了 建议:将返回类型从 改动建议
![]() ![]() | |||||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||||
🟠 High Priority 文件 torch_npu/csrc/InitNpuBindings.cpp 第 179–180 行对 失败模式:向 建议:调用后检查返回值,若为 false 则返回 nullptr(Python 错误已由 PyLong_AsSsize_t 设置,调用方只需传播失败):添加 改动建议
![]() ![]() 🟠 High Priority
建议:检查 改动建议
![]() ![]() | |||||||||||||||||||||||||||||||||
| 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) | ||||||||||||||||||||||||||||||
| 142 | static PyMethodDef TorchNpuMethods[] = { | 192 | static 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 | ||||||||||||||||||||||||||||||||
| @@ -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 | + } | ||
🟡 Medium Priority 文件 torch_npu/csrc/aten/common/TensorFactories.cpp 第 520–527 行的循环以 失败模式: 建议:在循环前添加 ![]() ![]() | |||
| 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 | + | ||
| 496 | at::Tensor &empty_out_npu( | 550 | at::Tensor &empty_out_npu( |
| 497 | at::Tensor &result, | 551 | at::Tensor &result, |
| 498 | c10::IntArrayRef size, | 552 | c10::IntArrayRef size, |
| @@ -1,11 +1,22 @@ | |||
| 1 | 1 | ||
| 2 | 2 | ||
| 3 | + | ||
| 3 | 4 | ||
| 4 | 5 | ||
| 6 | + | ||
| 5 | 7 | ||
| 6 | namespace at_npu { | 8 | namespace at_npu { |
| 7 | namespace native { | 9 | namespace 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 | + | ||
| 9 | inline void check_size_nonnegative(c10::IntArrayRef& size) | 20 | inline void check_size_nonnegative(c10::IntArrayRef& size) |
| 10 | { | 21 | { |
| 11 | for (auto& x : size) { | 22 | for (auto& x : size) { |


🔴 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 设计一致。void_npu_unwrap_size_tuple(PyObject* obj, c10::SmallVector<int64_t, 8>& out)