已合并
feat: adapt FRACTAL_NZ D2H/printing and serialization UT for Ascend950 #44315
wuyouqi1创建于 9 天前
feat: adapt FRACTAL_NZ D2H/printing and serialization UT for Ascend950 #44315
已合并
共 8 个文件变更+254-3
| @@ -114,6 +114,11 @@ class TestSerialization(TestCase): | |||
| 114 | 114 | ||
| 115 | with tempfile.TemporaryDirectory() as tmpdir: | 115 | with tempfile.TemporaryDirectory() as tmpdir: |
| 116 | path = os.path.join(tmpdir, "data.pt") | 116 | path = os.path.join(tmpdir, "data.pt") |
| 117 | + if x.is_npu: | ||
| 118 | + # legacy save of NPU storage reads an invalid NPUStorageDesc; expect a clear error. | ||
| 119 | + with self.assertRaisesRegex(RuntimeError, "NPUStorageImpl"): | ||
| 120 | + torch.serialization.save(x, path, _use_new_zipfile_serialization=False) | ||
| 121 | + return | ||
| 117 | torch.serialization.save(x, path, _use_new_zipfile_serialization=False) | 122 | torch.serialization.save(x, path, _use_new_zipfile_serialization=False) |
| 118 | y = torch.load( | 123 | y = torch.load( |
| 119 | path, | 124 | path, |
| @@ -1,5 +1,6 @@ | |||
| 1 | import os | 1 | import os |
| 2 | import tempfile | 2 | import tempfile |
| 3 | +import unittest | ||
| 3 | 4 | ||
| 4 | import torch | 5 | import torch |
| 5 | from torch.testing._internal.common_utils import TestCase, run_tests | 6 | from torch.testing._internal.common_utils import TestCase, run_tests |
| @@ -14,6 +15,8 @@ import torch_npu | |||
| 14 | # to test this case. | 15 | # to test this case. |
| 15 | 16 | ||
| 16 | 17 | ||
| 18 | +IS_ASCEND950 = torch_npu._C._npu_get_soc_version() >= 260 # Ascend950 = 260 | ||
| 19 | + | ||
| 17 | FORMAT_INFO = { | 20 | FORMAT_INFO = { |
| 18 | "NCHW": 0, | 21 | "NCHW": 0, |
| 19 | "NHWC": 1, | 22 | "NHWC": 1, |
| @@ -23,6 +26,26 @@ FORMAT_INFO = { | |||
| 23 | "FRACTAL_NZ": 29, | 26 | "FRACTAL_NZ": 29, |
| 24 | } | 27 | } |
| 25 | 28 | ||
| 29 | +NZ_ROUNDTRIP_DTYPES = [ | ||
| 30 | + (torch.float16, "fp16"), | ||
| 31 | + (torch.bfloat16, "bf16"), | ||
| 32 | + (torch.int8, "i8"), | ||
| 33 | + (torch.int32, "i32"), | ||
| 34 | + (torch.int64, "i64"), | ||
| 35 | +] | ||
| 36 | +D2H_TEST_DTYPES = [torch.float16, torch.bfloat16, torch.int8, torch.int32] | ||
| 37 | + | ||
| 38 | +# Ascend950 materializes a FRACTAL_NZ cast as an NZ_C0 variant (50-54) since C0 | ||
| 39 | +# variants are output-only; accept the NZ family when checking the cast result. | ||
| 40 | +NZ_FORMAT_FAMILY = ( | ||
| 41 | + FORMAT_INFO["FRACTAL_NZ"], | ||
| 42 | + torch_npu.Format.FRACTAL_NZ_C0_16, | ||
| 43 | + torch_npu.Format.FRACTAL_NZ_C0_32, | ||
| 44 | + torch_npu.Format.FRACTAL_NZ_C0_2, | ||
| 45 | + torch_npu.Format.FRACTAL_NZ_C0_4, | ||
| 46 | + torch_npu.Format.FRACTAL_NZ_C0_8, | ||
| 47 | +) | ||
| 48 | + | ||
| 26 | 49 | ||
| 27 | def save_tensor(tensor, path, acl_format): | 50 | def save_tensor(tensor, path, acl_format): |
| 28 | x = torch_npu.npu_format_cast(tensor.npu(), acl_format) | 51 | x = torch_npu.npu_format_cast(tensor.npu(), acl_format) |
| @@ -36,6 +59,8 @@ def load_tensor(tensor, path): | |||
| 36 | raise ValueError("load tensor not equal to save tensor.") | 59 | raise ValueError("load tensor not equal to save tensor.") |
| 37 | 60 | ||
| 38 | 61 | ||
| 62 | + | ||
| 63 | + "Ascend950 uses aclnn-only path; see TestSerializationFormatAscend950") | ||
| 39 | class TestSerializationFormat(TestCase): | 64 | class TestSerializationFormat(TestCase): |
| 40 | def test_save_load_format(self): | 65 | def test_save_load_format(self): |
| 41 | with tempfile.TemporaryDirectory() as tmpdir: | 66 | with tempfile.TemporaryDirectory() as tmpdir: |
| @@ -64,5 +89,158 @@ class TestSerializationFormat(TestCase): | |||
| 64 | self.assertEqual(process_load.exitcode, 0) | 89 | self.assertEqual(process_load.exitcode, 0) |
| 65 | 90 | ||
| 66 | 91 | ||
| 92 | + | ||
| 93 | +class TestSerializationFormatAscend950(TestCase): | ||
| 94 | + | ||
| 95 | + def test_save_load_nd_format(self): | ||
| 96 | + with tempfile.TemporaryDirectory() as tmpdir: | ||
| 97 | + path = os.path.join(tmpdir, 'data.pt') | ||
| 98 | + tensor = torch.rand(64, 3, 7, 7) | ||
| 99 | + | ||
| 100 | + proc = torch.multiprocessing.get_context("spawn").Process | ||
| 101 | + | ||
| 102 | + process_save = proc(target=save_tensor, name="save", | ||
| 103 | + args=(tensor, path, 2)) | ||
| 104 | + process_save.start() | ||
| 105 | + process_save.join() | ||
| 106 | + self.assertEqual(process_save.exitcode, 0) | ||
| 107 | + | ||
| 108 | + process_load = proc(target=load_tensor, name="load", | ||
| 109 | + args=(tensor, path)) | ||
| 110 | + process_load.start() | ||
| 111 | + process_load.join() | ||
| 112 | + self.assertEqual(process_load.exitcode, 0) | ||
| 113 | + | ||
| 114 | + def test_save_load_nz_round_trip_by_dtype(self): | ||
| 115 | + """Per-dtype FRACTAL_NZ save/load round-trip.""" | ||
| 116 | + for dt, name in NZ_ROUNDTRIP_DTYPES: | ||
| 117 | + try: | ||
| 118 | + x = torch.randn(64, 64, dtype=torch.float32).to(dt).npu() | ||
| 119 | + x = torch_npu.npu_format_cast(x, torch_npu.Format.FRACTAL_NZ) | ||
| 120 | + except Exception: | ||
| 121 | + if dt == torch.int64: | ||
| 122 | + continue # i64: not in CANN WEIGHT_DTYPE_SUPPORT_LIST | ||
| 123 | + raise | ||
| 124 | + | ||
| 125 | + fmt_before = torch_npu.get_npu_format(x) | ||
| 126 | + with tempfile.NamedTemporaryFile(delete=False, suffix=".pt") as f: | ||
| 127 | + path = f.name | ||
| 128 | + try: | ||
| 129 | + torch.save(x, path) | ||
| 130 | + y = torch.load(path) | ||
| 131 | + fmt_after = torch_npu.get_npu_format(y) | ||
| 132 | + | ||
| 133 | + self.assertEqual(fmt_before, fmt_after, | ||
| 134 | + f"{name}: format {fmt_before} != {fmt_after}") | ||
| 135 | + self.assertTrue(torch.equal(x.cpu(), y.cpu()), | ||
| 136 | + f"{name}: value mismatch") | ||
| 137 | + finally: | ||
| 138 | + os.unlink(path) | ||
| 139 | + | ||
| 140 | + def test_nz_d2h_and_repr(self): | ||
| 141 | + """D2H, repr, str, print on private-format tensor must not crash.""" | ||
| 142 | + for dt in D2H_TEST_DTYPES: | ||
| 143 | + x = torch.randn(64, 64, dtype=torch.float32).to(dt).npu() | ||
| 144 | + x = torch_npu.npu_format_cast(x, torch_npu.Format.FRACTAL_NZ) | ||
| 145 | + | ||
| 146 | + c = x.cpu() | ||
| 147 | + self.assertEqual(c.device.type, "cpu") | ||
| 148 | + | ||
| 149 | + self.assertIsInstance(repr(x), str) | ||
| 150 | + print(x) | ||
| 151 | + | ||
| 152 | + | ||
| 153 | +def cast_copy_tensor(tensor, acl_format): | ||
| 154 | + # Without allow_internal_format the cast silently downgrades internal formats to ND. | ||
| 155 | + if acl_format != FORMAT_INFO["ND"]: | ||
| 156 | + torch_npu.npu.config.allow_internal_format = True | ||
| 157 | + return torch_npu.npu_format_cast(tensor, acl_format) | ||
| 158 | + | ||
| 159 | + | ||
| 160 | + | ||
| 161 | + "Ascend950 copy behavior differs; see TestCopyFormatAscend950") | ||
| 162 | +class TestCopyFormat(TestCase): | ||
| 163 | + """A2/A3: copy_ across all FORMAT_INFO formats in H2D/D2H/D2D directions.""" | ||
| 164 | + | ||
| 165 | + def test_copy_formats_h2d_d2h_d2d(self): | ||
| 166 | + for fmt_name, fmt in FORMAT_INFO.items(): | ||
| 167 | + src_cpu = torch.randn(2, 3, 7, 7) | ||
| 168 | + | ||
| 169 | + # h2d: NPU dst (fmt) <- CPU src | ||
| 170 | + dst_h2d = cast_copy_tensor(torch.zeros(2, 3, 7, 7).npu(), fmt) | ||
| 171 | + dst_h2d.copy_(src_cpu) | ||
| 172 | + self.assertTrue(torch.equal(dst_h2d.cpu(), src_cpu), | ||
| 173 | + f"h2d dst={fmt_name}") | ||
| 174 | + | ||
| 175 | + # d2h: CPU dst <- NPU src (fmt) | ||
| 176 | + src_d2h = cast_copy_tensor(src_cpu.npu(), fmt) | ||
| 177 | + dst_d2h = torch.zeros(2, 3, 7, 7) | ||
| 178 | + dst_d2h.copy_(src_d2h) | ||
| 179 | + self.assertTrue(torch.equal(dst_d2h, src_cpu), | ||
| 180 | + f"d2h src={fmt_name}") | ||
| 181 | + | ||
| 182 | + # d2d: dst (fmt) <- src (other format) | ||
| 183 | + for other_name, other in FORMAT_INFO.items(): | ||
| 184 | + dst_d2d = cast_copy_tensor(torch.zeros(2, 3, 7, 7).npu(), fmt) | ||
| 185 | + fmt_before = torch_npu.get_npu_format(dst_d2d) | ||
| 186 | + src_d2d = cast_copy_tensor(src_cpu.npu(), other) | ||
| 187 | + dst_d2d.copy_(src_d2d) | ||
| 188 | + self.assertTrue(torch.equal(dst_d2d.cpu(), src_cpu), | ||
| 189 | + f"d2d dst={fmt_name} src={other_name}") | ||
| 190 | + self.assertEqual(torch_npu.get_npu_format(dst_d2d), fmt_before, | ||
| 191 | + f"d2d dst={fmt_name} format changed") | ||
| 192 | + | ||
| 193 | + | ||
| 194 | + | ||
| 195 | +class TestCopyFormatAscend950(TestCase): | ||
| 196 | + """Ascend950: internal-format copy_ is only supported device-to-host.""" | ||
| 197 | + | ||
| 198 | + COPY_TEST_DTYPE = torch.float16 | ||
| 199 | + | ||
| 200 | + def test_copy_base_format_h2d_d2h_d2d(self): | ||
| 201 | + src_cpu = torch.randn(8, 8, dtype=self.COPY_TEST_DTYPE) | ||
| 202 | + dst_h2d = torch.zeros(8, 8, dtype=self.COPY_TEST_DTYPE).npu() | ||
| 203 | + dst_h2d.copy_(src_cpu) | ||
| 204 | + self.assertTrue(torch.equal(dst_h2d.cpu(), src_cpu)) | ||
| 205 | + | ||
| 206 | + src_d2h = src_cpu.npu() | ||
| 207 | + dst_d2h = torch.zeros(8, 8, dtype=self.COPY_TEST_DTYPE) | ||
| 208 | + dst_d2h.copy_(src_d2h) | ||
| 209 | + self.assertTrue(torch.equal(dst_d2h, src_cpu)) | ||
| 210 | + | ||
| 211 | + dst_d2d = torch.zeros(8, 8, dtype=self.COPY_TEST_DTYPE).npu() | ||
| 212 | + dst_d2d.copy_(src_d2h) | ||
| 213 | + self.assertTrue(torch.equal(dst_d2d.cpu(), src_cpu)) | ||
| 214 | + | ||
| 215 | + def test_copy_nz_d2h(self): | ||
| 216 | + for dt in D2H_TEST_DTYPES: | ||
| 217 | + src_cpu = torch.randn(8, 8).to(dt) | ||
| 218 | + src_nz = cast_copy_tensor(src_cpu.npu(), FORMAT_INFO["FRACTAL_NZ"]) | ||
| 219 | + self.assertIn(torch_npu.get_npu_format(src_nz), NZ_FORMAT_FAMILY, | ||
| 220 | + f"expected FRACTAL_NZ family, got {torch_npu.get_npu_format(src_nz)}") | ||
| 221 | + | ||
| 222 | + dst_cpu = torch.zeros(8, 8, dtype=dt) | ||
| 223 | + dst_cpu.copy_(src_nz) | ||
| 224 | + self.assertTrue(torch.equal(dst_cpu, src_cpu), f"d2h {dt}") | ||
| 225 | + | ||
| 226 | + def test_copy_nz_h2d_not_supported(self): | ||
| 227 | + dst_nz = cast_copy_tensor(torch.zeros(8, 8, dtype=self.COPY_TEST_DTYPE).npu(), | ||
| 228 | + FORMAT_INFO["FRACTAL_NZ"]) | ||
| 229 | + with self.assertRaisesRegex(RuntimeError, "not supported on Ascend950"): | ||
| 230 | + dst_nz.copy_(torch.randn(8, 8, dtype=self.COPY_TEST_DTYPE)) | ||
| 231 | + | ||
| 232 | + def test_copy_nz_d2d_not_supported(self): | ||
| 233 | + src_nz = cast_copy_tensor(torch.randn(8, 8, dtype=self.COPY_TEST_DTYPE).npu(), | ||
| 234 | + FORMAT_INFO["FRACTAL_NZ"]) | ||
| 235 | + dst_nz = cast_copy_tensor(torch.zeros(8, 8, dtype=self.COPY_TEST_DTYPE).npu(), | ||
| 236 | + FORMAT_INFO["FRACTAL_NZ"]) | ||
| 237 | + with self.assertRaisesRegex(RuntimeError, "not supported on Ascend950"): | ||
| 238 | + dst_nz.copy_(src_nz) | ||
| 239 | + | ||
| 240 | + dst_nd = torch.zeros(8, 8, dtype=self.COPY_TEST_DTYPE).npu() | ||
| 241 | + with self.assertRaisesRegex(RuntimeError, "not supported on Ascend950"): | ||
| 242 | + dst_nd.copy_(src_nz) | ||
| 243 | + | ||
| 244 | + | ||
| 67 | if __name__ == "__main__": | 245 | if __name__ == "__main__": |
| 68 | run_tests() | 246 | run_tests() |
| @@ -61,6 +61,15 @@ static bool ShouldFallbackNzToNd(const at::Tensor& self, int64_t acl_format) | |||
| 61 | return false; | 61 | return false; |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | +// CANN only accepts FRACTAL_NZ(29) or ND(2) as dstFormat; NZ_C0 variants (50-54) | ||
| 65 | +// are output-only (returned as actualFormat). Normalize here for the load path. | ||
| 66 | +static bool IsNzC0Variant(int64_t fmt) | ||
| 67 | +{ | ||
| 68 | + return fmt == ACL_FORMAT_FRACTAL_NZ_C0_16 || fmt == ACL_FORMAT_FRACTAL_NZ_C0_32 || | ||
| 69 | + fmt == ACL_FORMAT_FRACTAL_NZ_C0_2 || fmt == ACL_FORMAT_FRACTAL_NZ_C0_4 || | ||
| 70 | + fmt == ACL_FORMAT_FRACTAL_NZ_C0_8; | ||
| 71 | +} | ||
| 72 | + | ||
| 64 | std::tuple<bool, int64_t, c10::SmallVector<int64_t, SIZE>> MaybeUseAclnnNpuFormatCast(const at::Tensor& src, | 73 | std::tuple<bool, int64_t, c10::SmallVector<int64_t, SIZE>> MaybeUseAclnnNpuFormatCast(const at::Tensor& src, |
| 65 | int64_t acl_format, c10::optional<int64_t> customize_dtype, c10::optional<int64_t> input_dtype) | 74 | int64_t acl_format, c10::optional<int64_t> customize_dtype, c10::optional<int64_t> input_dtype) |
| 66 | { | 75 | { |
| @@ -80,9 +89,15 @@ std::tuple<bool, int64_t, c10::SmallVector<int64_t, SIZE>> MaybeUseAclnnNpuForma | |||
| 80 | c10_npu::GetAclDataType(customize_dtype.value()) : srcAcltype; | 89 | c10_npu::GetAclDataType(customize_dtype.value()) : srcAcltype; |
| 81 | TensorWrapper srcWrapper = make_wrapper(src, input_dtype); | 90 | TensorWrapper srcWrapper = make_wrapper(src, input_dtype); |
| 82 | if (c10_npu::IsAclnnOnly()) { | 91 | if (c10_npu::IsAclnnOnly()) { |
| 92 | + // >=4-byte types (fp32/int32) need FLOAT16 additionalDtype for C0=16. | ||
| 93 | + if (!customize_dtype.has_value() && src.element_size() >= 4) { | ||
| 94 | + customizeAcltype = aclDataType::ACL_FLOAT16; | ||
| 95 | + } | ||
| 83 | if (aclnnNpuFormatCastExist) { | 96 | if (aclnnNpuFormatCastExist) { |
| 97 | + // Normalize NZ_C0 -> FRACTAL_NZ(29); C0 variants are output-only. | ||
| 98 | + int64_t cann_dst_format = IsNzC0Variant(acl_format) ? ACL_FORMAT_FRACTAL_NZ : acl_format; | ||
| 84 | auto acl_src = ConvertType(srcWrapper); | 99 | auto acl_src = ConvertType(srcWrapper); |
| 85 | - auto api_ret = GetFormat(acl_src, acl_format, customizeAcltype, &dstStorageShape, | 100 | + auto api_ret = GetFormat(acl_src, cann_dst_format, customizeAcltype, &dstStorageShape, |
| 86 | &dstShapeSize, &dstFormat); | 101 | &dstShapeSize, &dstFormat); |
| 87 | Release(acl_src); | 102 | Release(acl_src); |
| 88 | NPU_CHECK_ERROR(api_ret, "aclnnNpuFormatCastCalculateSizeAndFormat"); | 103 | NPU_CHECK_ERROR(api_ret, "aclnnNpuFormatCastCalculateSizeAndFormat"); |
| @@ -16,6 +16,7 @@ supported: | |||
| 16 | - contiguous | 16 | - contiguous |
| 17 | - func: copy_ | 17 | - func: copy_ |
| 18 | op_api: True | 18 | op_api: True |
| 19 | + internal_format_opapi: True | ||
| 19 | - copy_memory_ | 20 | - copy_memory_ |
| 20 | - empty.memory_format | 21 | - empty.memory_format |
| 21 | - empty_like | 22 | - empty_like |
| @@ -27,6 +27,8 @@ | |||
| 27 | 27 | ||
| 28 | 28 | ||
| 29 | 29 | ||
| 30 | + | ||
| 31 | + | ||
| 30 | 32 | ||
| 31 | 33 | ||
| 32 | 34 | ||
| @@ -238,6 +240,19 @@ at::Tensor& NPUNativeOpApiFunctions::copy_(at::Tensor& self, const at::Tensor& s | |||
| 238 | 240 | ||
| 239 | auto maybe_outnames = at::namedinference::compute_broadcast_outnames(self, src); | 241 | auto maybe_outnames = at::namedinference::compute_broadcast_outnames(self, src); |
| 240 | 242 | ||
| 243 | + // aclnnInplaceCopy corrupts internal-format storage: on A2/A3 fall back to | ||
| 244 | + // the native copy_, on Ascend950 only d2h is supported (cast to base first). | ||
| 245 | + const bool self_is_base = FormatHelper::IsOpInputBaseFormat(self); | ||
| 246 | + const bool src_is_base = FormatHelper::IsOpInputBaseFormat(src); | ||
| 247 | + if (!self_is_base || !src_is_base) { | ||
| 248 | + if (!c10_npu::IsAclnnOnly()) { | ||
| 249 | + return NPUNativeFunctions::copy_(self, src, non_blocking); | ||
| 250 | + } | ||
| 251 | + TORCH_CHECK(!torch_npu::utils::is_npu(self), | ||
| 252 | + "The copy_ operator with internal format tensors is not supported on Ascend950, " | ||
| 253 | + "only device-to-host copies are supported", OPS_ERROR(ErrCode::NOT_SUPPORT)); | ||
| 254 | + } | ||
| 255 | + | ||
| 241 | if (torch_npu::utils::is_npu(self)) { | 256 | if (torch_npu::utils::is_npu(self)) { |
| 242 | if (torch_npu::utils::is_npu(src)) { | 257 | if (torch_npu::utils::is_npu(src)) { |
| 243 | copy_d2d_baseformat_opapi(self, src, non_blocking); | 258 | copy_d2d_baseformat_opapi(self, src, non_blocking); |
| @@ -254,7 +269,10 @@ at::Tensor& NPUNativeOpApiFunctions::copy_(at::Tensor& self, const at::Tensor& s | |||
| 254 | } | 269 | } |
| 255 | } else { | 270 | } else { |
| 256 | if (torch_npu::utils::is_npu(src)) { | 271 | if (torch_npu::utils::is_npu(src)) { |
| 257 | - copy_d2h_baseformat_opapi(self, src, non_blocking); | 272 | + // Ascend950: aclnnInplaceCopy rejects internal format. Cast NZ->ND first. |
| 273 | + at::Tensor src_base = FormatHelper::IsBaseFormatType(src) | ||
| 274 | + ? src : FormatCastHelper::ApplyBaseFormatTensorBy(src); | ||
| 275 | + copy_d2h_baseformat_opapi(self, src_base, non_blocking); | ||
| 258 | if (src.is_complex() && src.is_conj()) { | 276 | if (src.is_complex() && src.is_conj()) { |
| 259 | self.conj_physical_(); | 277 | self.conj_physical_(); |
| 260 | } | 278 | } |
| @@ -1,3 +1,5 @@ | |||
| 1 | + | ||
| 2 | + | ||
| 1 | 3 | ||
| 2 | 4 | ||
| 3 | 5 | ||
| @@ -19,7 +21,12 @@ NPUStorageImpl *NPUBridge::GetNpuStorageImpl(const at::Tensor &tensor) | |||
| 19 | 21 | ||
| 20 | NPUStorageDesc &NPUBridge::GetNpuStorageImplDesc(const at::Tensor &tensor) | 22 | NPUStorageDesc &NPUBridge::GetNpuStorageImplDesc(const at::Tensor &tensor) |
| 21 | { | 23 | { |
| 22 | - return static_cast<NPUStorageImpl *>(tensor.storage().unsafeGetStorageImpl())->npu_desc_; | 24 | + // from_blob tensors (legacy serialization _write_file) carry a plain |
| 25 | + // c10::StorageImpl; reading npu_desc_ on them is out-of-bounds. | ||
| 26 | + auto *storage_impl = tensor.storage().unsafeGetStorageImpl(); | ||
| 27 | + TORCH_CHECK(typeid(*storage_impl) == typeid(NPUStorageImpl), | ||
| 28 | + "The npu storage desc is unavailable: the tensor's storage is not an NPUStorageImpl."); | ||
| 29 | + return static_cast<NPUStorageImpl *>(storage_impl)->npu_desc_; | ||
| 23 | } | 30 | } |
| 24 | 31 | ||
| 25 | 32 | ||
| @@ -13,6 +13,11 @@ std::unordered_map<std::string, aclFormat> FORMAT_INFO = { | |||
| 13 | { "NCHW", ACL_FORMAT_NCHW }, | 13 | { "NCHW", ACL_FORMAT_NCHW }, |
| 14 | { "NHWC", ACL_FORMAT_NHWC }, | 14 | { "NHWC", ACL_FORMAT_NHWC }, |
| 15 | { "FRACTAL_NZ", ACL_FORMAT_FRACTAL_NZ }, | 15 | { "FRACTAL_NZ", ACL_FORMAT_FRACTAL_NZ }, |
| 16 | + { "FRACTAL_NZ_C0_16", ACL_FORMAT_FRACTAL_NZ_C0_16 }, | ||
| 17 | + { "FRACTAL_NZ_C0_32", ACL_FORMAT_FRACTAL_NZ_C0_32 }, | ||
| 18 | + { "FRACTAL_NZ_C0_2", ACL_FORMAT_FRACTAL_NZ_C0_2 }, | ||
| 19 | + { "FRACTAL_NZ_C0_4", ACL_FORMAT_FRACTAL_NZ_C0_4 }, | ||
| 20 | + { "FRACTAL_NZ_C0_8", ACL_FORMAT_FRACTAL_NZ_C0_8 }, | ||
| 16 | { "FRACTAL_Z", ACL_FORMAT_FRACTAL_Z }, | 21 | { "FRACTAL_Z", ACL_FORMAT_FRACTAL_Z }, |
| 17 | { "NDHWC", ACL_FORMAT_NDHWC }, | 22 | { "NDHWC", ACL_FORMAT_NDHWC }, |
| 18 | { "NCDHW", ACL_FORMAT_NCDHW }, | 23 | { "NCDHW", ACL_FORMAT_NCDHW }, |
| @@ -81,6 +81,28 @@ def _npu_type(self, dtype=None, non_blocking=False, **kwargs): | |||
| 81 | return self.type_raw(dtype, non_blocking, **kwargs) | 81 | return self.type_raw(dtype, non_blocking, **kwargs) |
| 82 | 82 | ||
| 83 | 83 | ||
| 84 | +def _add_repr_patch(): | ||
| 85 | + # Private-format tensors (e.g. FRACTAL_NZ) hit internal-format guard in | ||
| 86 | + # _tensor_str cat/stack. Force .cpu() to trigger d2h + format cast first. | ||
| 87 | + _orig_repr = torch.Tensor.__repr__ | ||
| 88 | + | ||
| 89 | + def _npu_private_format_repr(self, *, tensor_contents=None): | ||
| 90 | + if self.device.type == "npu": | ||
| 91 | + try: | ||
| 92 | + is_private_format = ( | ||
| 93 | + torch_npu.get_npu_format(self) != int(torch_npu.Format.ND) | ||
| 94 | + ) | ||
| 95 | + except Exception: | ||
| 96 | + is_private_format = False | ||
| 97 | + if is_private_format: | ||
| 98 | + with torch.no_grad(): | ||
| 99 | + return _orig_repr(self.cpu(), tensor_contents=tensor_contents) | ||
| 100 | + return _orig_repr(self, tensor_contents=tensor_contents) | ||
| 101 | + | ||
| 102 | + torch.Tensor.__repr__ = _npu_private_format_repr | ||
| 103 | + | ||
| 104 | + | ||
| 84 | def _add_tensor_methods(): | 105 | def _add_tensor_methods(): |
| 85 | torch.Tensor.type_raw = torch.Tensor.type | 106 | torch.Tensor.type_raw = torch.Tensor.type |
| 86 | torch.Tensor.type = _npu_type | 107 | torch.Tensor.type = _npu_type |
| 108 | + _add_repr_patch() | ||