已合并
trans npuformat cast from aclop to aclnn and fix 1d npu format cast fallback #38058
shawnylee233创建于 6月10日
trans npuformat cast from aclop to aclnn and fix 1d npu format cast fallback #38058
已合并
共 3 个文件变更+723-4
| @@ -3,10 +3,644 @@ import torch | |||
| 3 | 3 | ||
| 4 | import torch_npu | 4 | import torch_npu |
| 5 | from torch_npu.testing.testcase import TestCase, run_tests | 5 | from torch_npu.testing.testcase import TestCase, run_tests |
| 6 | -from torch_npu.testing.common_utils import create_common_tensor | 6 | +from torch_npu.testing.common_utils import create_common_tensor, SupportedDevices |
| 7 | + | ||
| 8 | +torch.npu.config.allow_internal_format = True | ||
| 9 | + | ||
| 10 | +# ACL format constants | ||
| 11 | +ACL_FORMAT_ND = 2 | ||
| 12 | +ACL_FORMAT_FRACTAL_NZ = 29 | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +class TestNpuFormatCastAclnn(TestCase): | ||
| 16 | + """ | ||
| 17 | + Verify npu_format_cast aclnn implementation for ND -> FRACTAL_NZ. | ||
| 18 | + | ||
| 19 | + Test groups: | ||
| 20 | + 1. ND -> NZ: format ID and data correctness | ||
| 21 | + 2. Inplace API (npu_format_cast_) | ||
| 22 | + 3. Tensor overload (npu_format_cast(src, dst_tensor)) | ||
| 23 | + 4. Same-format no-op | ||
| 24 | + 5. Autograd / backward (requires_grad propagation) | ||
| 25 | + 6. Error cases | ||
| 26 | + """ | ||
| 27 | + | ||
| 28 | + # ------------------------------------------------------------------ # | ||
| 29 | + # Group 1: ND -> NZ format conversion | ||
| 30 | + # ------------------------------------------------------------------ # | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + def test_nd_to_nz_format_id_fp16(self): | ||
| 34 | + t = torch.rand(8, 16).half().npu() | ||
| 35 | + out = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 36 | + self.assertEqual(torch_npu.get_npu_format(out), ACL_FORMAT_FRACTAL_NZ) | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + def test_nd_to_nz_format_id_bf16(self): | ||
| 40 | + t = torch.rand(32, 64).bfloat16().npu() | ||
| 41 | + out = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 42 | + self.assertEqual(torch_npu.get_npu_format(out), ACL_FORMAT_FRACTAL_NZ) | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + def test_nd_to_nz_format_id_float32(self): | ||
| 46 | + t = torch.rand(16, 32).float().npu() | ||
| 47 | + out = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 48 | + self.assertEqual(torch_npu.get_npu_format(out), ACL_FORMAT_FRACTAL_NZ) | ||
| 49 | + | ||
| 50 | + | ||
| 51 | + def test_nd_to_nz_shape_preserved_fp16(self): | ||
| 52 | + t = torch.rand(64, 128).half().npu() | ||
| 53 | + out = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 54 | + self.assertEqual(out.shape, t.shape) | ||
| 55 | + | ||
| 56 | + | ||
| 57 | + def test_nd_to_nz_non_aligned_shape(self): | ||
| 58 | + t = torch.rand(15, 17).half().npu() | ||
| 59 | + out = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 60 | + self.assertEqual(torch_npu.get_npu_format(out), ACL_FORMAT_FRACTAL_NZ) | ||
| 61 | + self.assertEqual(out.shape, t.shape) | ||
| 62 | + | ||
| 63 | + # ------------------------------------------------------------------ # | ||
| 64 | + # Group 1b: NZ -> ND format conversion (reverse) | ||
| 65 | + # ------------------------------------------------------------------ # | ||
| 66 | + | ||
| 67 | + | ||
| 68 | + def test_nz_to_nd_format_id_fp16(self): | ||
| 69 | + """NZ -> ND: format changes back to ND.""" | ||
| 70 | + t = torch.rand(8, 16).half().npu() | ||
| 71 | + nz = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 72 | + back = torch_npu.npu_format_cast(nz, ACL_FORMAT_ND) | ||
| 73 | + self.assertEqual(torch_npu.get_npu_format(back), ACL_FORMAT_ND) | ||
| 74 | + | ||
| 75 | + | ||
| 76 | + def test_nz_to_nd_format_id_bf16(self): | ||
| 77 | + """NZ -> ND bf16.""" | ||
| 78 | + t = torch.rand(32, 64).bfloat16().npu() | ||
| 79 | + nz = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 80 | + back = torch_npu.npu_format_cast(nz, ACL_FORMAT_ND) | ||
| 81 | + self.assertEqual(torch_npu.get_npu_format(back), ACL_FORMAT_ND) | ||
| 82 | + | ||
| 83 | + | ||
| 84 | + def test_nz_to_nd_format_id_float32(self): | ||
| 85 | + """NZ -> ND float32.""" | ||
| 86 | + t = torch.rand(16, 32).float().npu() | ||
| 87 | + nz = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 88 | + back = torch_npu.npu_format_cast(nz, ACL_FORMAT_ND) | ||
| 89 | + self.assertEqual(torch_npu.get_npu_format(back), ACL_FORMAT_ND) | ||
| 90 | + | ||
| 91 | + | ||
| 92 | + def test_nz_to_nd_shape_preserved(self): | ||
| 93 | + """NZ -> ND: logical shape unchanged.""" | ||
| 94 | + t = torch.rand(64, 128).half().npu() | ||
| 95 | + nz = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 96 | + back = torch_npu.npu_format_cast(nz, ACL_FORMAT_ND) | ||
| 97 | + self.assertEqual(back.shape, (64, 128)) | ||
| 98 | + | ||
| 99 | + # ------------------------------------------------------------------ # | ||
| 100 | + # Group 2: Inplace API (npu_format_cast_) | ||
| 101 | + # ------------------------------------------------------------------ # | ||
| 102 | + | ||
| 103 | + | ||
| 104 | + def test_inplace_nd_to_nz_format_id(self): | ||
| 105 | + t = torch.rand(16, 32).half().npu() | ||
| 106 | + torch_npu.npu_format_cast_(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 107 | + self.assertEqual(torch_npu.get_npu_format(t), ACL_FORMAT_FRACTAL_NZ) | ||
| 108 | + | ||
| 109 | + | ||
| 110 | + def test_inplace_nz_to_nd_format_id(self): | ||
| 111 | + """Inplace NZ -> ND: format changes back.""" | ||
| 112 | + t = torch.rand(15, 17).half().npu() | ||
| 113 | + torch_npu.npu_format_cast_(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 114 | + torch_npu.npu_format_cast_(t, ACL_FORMAT_ND) | ||
| 115 | + self.assertEqual(torch_npu.get_npu_format(t), ACL_FORMAT_ND) | ||
| 116 | + | ||
| 117 | + | ||
| 118 | + def test_inplace_roundtrip_data(self): | ||
| 119 | + """Inplace ND -> NZ -> ND: data preserved.""" | ||
| 120 | + t = torch.rand(15, 17).half().npu() | ||
| 121 | + expected = t.cpu().clone() | ||
| 122 | + torch_npu.npu_format_cast_(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 123 | + torch_npu.npu_format_cast_(t, ACL_FORMAT_ND) | ||
| 124 | + self.assertEqual(t.cpu(), expected) | ||
| 125 | + | ||
| 126 | + | ||
| 127 | + def test_inplace_same_format_noop(self): | ||
| 128 | + """Inplace to same format: storage unchanged.""" | ||
| 129 | + t = torch.rand(8, 16).half().npu() | ||
| 130 | + ptr_before = t.storage().data_ptr() | ||
| 131 | + torch_npu.npu_format_cast_(t, ACL_FORMAT_ND) | ||
| 132 | + self.assertEqual(t.storage().data_ptr(), ptr_before) | ||
| 133 | + | ||
| 134 | + # ------------------------------------------------------------------ # | ||
| 135 | + # Group 3: Tensor overload npu_format_cast(src, dst_tensor) | ||
| 136 | + # ------------------------------------------------------------------ # | ||
| 137 | + | ||
| 138 | + | ||
| 139 | + def test_tensor_overload_adopts_dst_format(self): | ||
| 140 | + """npu_format_cast(src, dst) converts src to dst's format.""" | ||
| 141 | + src = torch.rand(8, 16).half().npu() | ||
| 142 | + dst_ref = torch_npu.npu_format_cast(torch.rand(8, 16).half().npu(), ACL_FORMAT_FRACTAL_NZ) | ||
| 143 | + result = torch_npu.npu_format_cast(src, dst_ref) | ||
| 144 | + self.assertEqual(torch_npu.get_npu_format(result), ACL_FORMAT_FRACTAL_NZ) | ||
| 145 | + | ||
| 146 | + | ||
| 147 | + def test_tensor_overload_roundtrip_data(self): | ||
| 148 | + """npu_format_cast(src, dst) round-trip: data preserved.""" | ||
| 149 | + src = torch.rand(15, 17).half().npu() | ||
| 150 | + expected = src.cpu().clone() | ||
| 151 | + nz_ref = torch_npu.npu_format_cast(torch.rand(15, 17).half().npu(), ACL_FORMAT_FRACTAL_NZ) | ||
| 152 | + nd_ref = torch.rand(15, 17).half().npu() | ||
| 153 | + nz = torch_npu.npu_format_cast(src, nz_ref) | ||
| 154 | + back = torch_npu.npu_format_cast(nz, nd_ref) | ||
| 155 | + self.assertEqual(back.cpu(), expected) | ||
| 156 | + | ||
| 157 | + # ------------------------------------------------------------------ # | ||
| 158 | + # Group 4: Same-format no-op | ||
| 159 | + # ------------------------------------------------------------------ # | ||
| 160 | + | ||
| 161 | + | ||
| 162 | + def test_same_format_nd_noop(self): | ||
| 163 | + """ND -> ND: same storage returned.""" | ||
| 164 | + t = torch.rand(4, 8).half().npu() | ||
| 165 | + ptr_before = t.storage().data_ptr() | ||
| 166 | + out = torch_npu.npu_format_cast(t, ACL_FORMAT_ND) | ||
| 167 | + self.assertEqual(out.storage().data_ptr(), ptr_before) | ||
| 168 | + | ||
| 169 | + | ||
| 170 | + def test_same_format_nz_noop(self): | ||
| 171 | + """NZ -> NZ: same storage returned.""" | ||
| 172 | + t = torch_npu.npu_format_cast(torch.rand(8, 16).half().npu(), ACL_FORMAT_FRACTAL_NZ) | ||
| 173 | + ptr_before = t.storage().data_ptr() | ||
| 174 | + out = torch_npu.npu_format_cast(t, ACL_FORMAT_FRACTAL_NZ) | ||
| 175 | + self.assertEqual(out.storage().data_ptr(), ptr_before) | ||
| 176 | + | ||
| 177 | + | ||
| 178 | + def test_same_format_get_format_consistent(self): | ||
| 179 | + """After same-format cast, get_npu_format unchanged.""" | ||
| 180 | + t = torch.rand(8, 16).half().npu() | ||
| 181 | + fmt = torch_npu.get_npu_format(t) | ||
| 182 | + out = torch_npu.npu_format_cast(t, fmt) | ||
| 183 | + self.assertEqual(torch_npu.get_npu_format(out), fmt) | ||
| 184 | + | ||
| 185 | + # ------------------------------------------------------------------ # | ||
| 186 | + # Group 5: Autograd / backward | ||
| 187 | + # ------------------------------------------------------------------ # | ||
| 188 | + | ||
| 189 | + | ||
| 190 | + def test_backward_requires_grad_preserved(self): | ||
| 191 | + """requires_grad propagates through format cast.""" | ||
| 192 | + a = torch.rand(4, 8).half().npu().requires_grad_(True) | ||
| 193 | + b = torch_npu.npu_format_cast(a, ACL_FORMAT_FRACTAL_NZ) | ||
| 194 | + self.assertTrue(b.requires_grad) | ||
| 195 | + | ||
| 196 | + | ||
| 197 | + def test_backward_same_format_noop(self): | ||
| 198 | + """Same-format cast (no-op path) also supports backward.""" | ||
| 199 | + a = torch.rand(4, 8).float().npu().requires_grad_(True) | ||
| 200 | + ori_fmt = torch_npu.get_npu_format(a) | ||
| 201 | + b = torch_npu.npu_format_cast(a, ori_fmt) | ||
| 202 | + b.sum().backward() | ||
| 203 | + self.assertIsNotNone(a.grad) | ||
| 204 | + | ||
| 205 | + # ------------------------------------------------------------------ # | ||
| 206 | + # Group 6: Error cases | ||
| 207 | + # ------------------------------------------------------------------ # | ||
| 208 | + | ||
| 209 | + | ||
| 210 | + def test_noncontiguous_with_internal_format_raises(self): | ||
| 211 | + """Non-contiguous tensor with internal format raises RuntimeError.""" | ||
| 212 | + nz = torch_npu.npu_format_cast(torch.rand(16, 32).half().npu(), ACL_FORMAT_FRACTAL_NZ) | ||
| 213 | + nz_t = nz.transpose(0, 1) | ||
| 214 | + with self.assertRaises(RuntimeError): | ||
| 215 | + torch_npu.npu_format_cast(nz_t, ACL_FORMAT_ND) | ||
| 216 | + | ||
| 217 | + | ||
| 218 | +class TestNpuFormatCastDtypeParam(TestCase): | ||
| 219 | + """ | ||
| 220 | + Tests for npu_format_cast with customize_dtype parameter. | ||
| 221 | + Default C0=16; customize_dtype=INT32 overrides to C0=8. | ||
| 222 | + """ | ||
| 223 | + | ||
| 224 | + ACL_FORMAT_ND = 2 | ||
| 225 | + ACL_FORMAT_FRACTAL_NZ = 29 | ||
| 226 | + | ||
| 227 | + # dtype enum values (matching npu_native_functions.yaml / aclDataType) | ||
| 228 | + DTYPE_INT32 = 3 | ||
| 229 | + | ||
| 230 | + def _expected_nz_storage_bytes(self, shape, c0, element_size): | ||
| 231 | + m = shape[-2] | ||
| 232 | + n = shape[-1] | ||
| 233 | + batch = int(np.prod(shape[:-2])) if len(shape) > 2 else 1 | ||
| 234 | + return batch * ((n + c0 - 1) // c0) * ((m + 15) // 16) * 16 * c0 * element_size | ||
| 235 | + | ||
| 236 | + # ------------------------------------------------------------------ # | ||
| 237 | + # Baseline: fp16/float32 default to C0=16; int8 defaults to C0=32. | ||
| 238 | + # ------------------------------------------------------------------ # | ||
| 239 | + | ||
| 240 | + | ||
| 241 | + def test_npu_format_cast_fp16_nd_to_nz(self): | ||
| 242 | + """fp16 ND -> NZ, default C0=16.""" | ||
| 243 | + t = torch.rand(16, 32).half().npu() | ||
| 244 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 245 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 246 | + self.assertEqual(out.shape, t.shape) | ||
| 247 | + | ||
| 248 | + | ||
| 249 | + def test_npu_format_cast_float32_nd_to_nz(self): | ||
| 250 | + """float32 ND -> NZ, default C0=16.""" | ||
| 251 | + t = torch.rand(16, 32).float().npu() | ||
| 252 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 253 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 254 | + self.assertEqual(out.shape, t.shape) | ||
| 255 | + | ||
| 256 | + | ||
| 257 | + def test_npu_format_cast_int8_nd_to_nz(self): | ||
| 258 | + """int8 ND -> NZ, default C0=32.""" | ||
| 259 | + t = torch.randint(-128, 127, (32, 64), dtype=torch.int8).npu() | ||
| 260 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 261 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 262 | + self.assertEqual(out.shape, t.shape) | ||
| 263 | + | ||
| 264 | + | ||
| 265 | + def test_npu_format_cast_int8_non_aligned(self): | ||
| 266 | + """int8 non-aligned shape, default C0=32.""" | ||
| 267 | + t = torch.randint(-128, 127, (15, 17), dtype=torch.int8).npu() | ||
| 268 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 269 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 270 | + self.assertEqual(out.shape, t.shape) | ||
| 271 | + | ||
| 272 | + | ||
| 273 | + def test_npu_format_cast_fp16_default_c0_16_storage_size(self): | ||
| 274 | + """fp16 non-aligned shape uses default C0=16.""" | ||
| 275 | + shape = (15, 17) | ||
| 276 | + t = torch.rand(*shape).half().npu() | ||
| 277 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 278 | + self.assertEqual(out.untyped_storage().size(), | ||
| 279 | + self._expected_nz_storage_bytes(shape, 16, 2)) | ||
| 280 | + | ||
| 281 | + | ||
| 282 | + def test_npu_format_cast_int8_default_c0_32_storage_size(self): | ||
| 283 | + """int8 non-aligned shape uses default C0=32 in infer shape.""" | ||
| 284 | + shape = (15, 33) | ||
| 285 | + t = torch.randint(-128, 127, shape, dtype=torch.int8).npu() | ||
| 286 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 287 | + self.assertEqual(out.untyped_storage().size(), | ||
| 288 | + self._expected_nz_storage_bytes(shape, 32, 1)) | ||
| 289 | + | ||
| 290 | + | ||
| 291 | + def test_npu_format_cast_int8_default_c0_32_storage_size_3d(self): | ||
| 292 | + """3D int8 infer shape preserves batch dims and uses C0=32.""" | ||
| 293 | + shape = (3, 15, 33) | ||
| 294 | + t = torch.randint(-128, 127, shape, dtype=torch.int8).npu() | ||
| 295 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 296 | + self.assertEqual(out.untyped_storage().size(), | ||
| 297 | + self._expected_nz_storage_bytes(shape, 32, 1)) | ||
| 298 | + | ||
| 299 | + # ------------------------------------------------------------------ # | ||
| 300 | + # int32 with customize_dtype=INT32 to override default C0=16 to C0=8: | ||
| 301 | + # Without customize_dtype, 4-bit types default to C0=16 (FORMAT_REAL_TO_FAKE). | ||
| 302 | + # Explicitly passing customize_dtype=INT32 bypasses that and uses C0=8. | ||
| 303 | + # (910B only: 950 does not support customize_dtype) | ||
| 304 | + # ------------------------------------------------------------------ # | ||
| 305 | + | ||
| 306 | + | ||
| 307 | + def test_npu_format_cast_int32_customize_dtype_nd_to_nz(self): | ||
| 308 | + """int32 with explicit customize_dtype=INT32: C0=8 ND -> FRACTAL_NZ.""" | ||
| 309 | + t = torch.randint(0, 100, (32, 32), dtype=torch.int32).npu() | ||
| 310 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 311 | + customize_dtype=self.DTYPE_INT32) | ||
| 312 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 313 | + self.assertEqual(out.shape, t.shape) | ||
| 314 | + | ||
| 315 | + | ||
| 316 | + def test_npu_format_cast_int32_customize_dtype_non_aligned(self): | ||
| 317 | + """int32 with explicit customize_dtype=INT32: C0=8 non-aligned shape.""" | ||
| 318 | + t = torch.randint(0, 100, (15, 17), dtype=torch.int32).npu() | ||
| 319 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 320 | + customize_dtype=self.DTYPE_INT32) | ||
| 321 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 322 | + self.assertEqual(out.shape, t.shape) | ||
| 323 | + | ||
| 324 | + | ||
| 325 | + def test_npu_format_cast_int32_customize_dtype_3d(self): | ||
| 326 | + """int32 with explicit customize_dtype=INT32: C0=8 3D shape.""" | ||
| 327 | + t = torch.randint(0, 100, (4, 16, 32), dtype=torch.int32).npu() | ||
| 328 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 329 | + customize_dtype=self.DTYPE_INT32) | ||
| 330 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 331 | + self.assertEqual(out.shape, t.shape) | ||
| 332 | + | ||
| 333 | + # ------------------------------------------------------------------ # | ||
| 334 | + # Storage size verification: explicitly verify C0 via storage bytes. | ||
| 335 | + # FRACTAL_NZ storage shape = (ceil(N/C0), ceil(M/16), 16, C0) | ||
| 336 | + # Different C0 values produce different storage sizes for non-aligned shapes. | ||
| 337 | + # ------------------------------------------------------------------ # | ||
| 338 | + | ||
| 339 | + | ||
| 340 | + def test_npu_format_cast_c0_8_storage_size_2d(self): | ||
| 341 | + """Verify C0=8 storage size for (15, 17) int32. | ||
| 342 | + | ||
| 343 | + FRACTAL_NZ with C0=8: (ceil(17/8), ceil(15/16), 16, 8) = (3,1,16,8) | ||
| 344 | + = 384 elements * 4 bytes = 1536 bytes. | ||
| 345 | + """ | ||
| 346 | + t = torch.randint(0, 100, (15, 17), dtype=torch.int32).npu() | ||
| 347 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 348 | + customize_dtype=self.DTYPE_INT32) | ||
| 349 | + # C0=8: ceil(17/8)*ceil(15/16)*16*8 * 4bytes = 3*1*128 * 4 = 1536 | ||
| 350 | + expected_bytes = self._expected_nz_storage_bytes((15, 17), 8, 4) | ||
| 351 | + self.assertEqual(out.untyped_storage().size(), expected_bytes) | ||
| 352 | + | ||
| 353 | + | ||
| 354 | + def test_npu_format_cast_c0_8_vs_default_c0_16_storage_differs(self): | ||
| 355 | + """C0=8 (customize_dtype=INT32) vs default C0=16 produce different storage. | ||
| 356 | + | ||
| 357 | + For (15, 17) int32: | ||
| 358 | + Default C0=16: (ceil(17/16), ceil(15/16), 16, 16) = 512 elements * 4 = 2048 bytes | ||
| 359 | + customize_dtype=INT32 C0=8: (ceil(17/8), ceil(15/16), 16, 8) = 384 elements * 4 = 1536 bytes | ||
| 360 | + """ | ||
| 361 | + t = torch.randint(0, 100, (15, 17), dtype=torch.int32).npu() | ||
| 362 | + out_default = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 363 | + out_c0_8 = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 364 | + customize_dtype=self.DTYPE_INT32) | ||
| 365 | + # Default C0=16 should be larger than C0=8 | ||
| 366 | + self.assertGreater(out_default.untyped_storage().size(), | ||
| 367 | + out_c0_8.untyped_storage().size()) | ||
| 368 | + | ||
| 369 | + | ||
| 370 | + def test_npu_format_cast_default_c0_16_storage_size(self): | ||
| 371 | + """Verify default C0=16 storage size for (15, 17) int32 without customize_dtype. | ||
| 372 | + | ||
| 373 | + Default C0=16: (ceil(17/16), ceil(15/16), 16, 16) = (2,1,16,16) | ||
| 374 | + = 512 elements * 4 bytes = 2048 bytes. | ||
| 375 | + """ | ||
| 376 | + t = torch.randint(0, 100, (15, 17), dtype=torch.int32).npu() | ||
| 377 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 378 | + # Default C0=16: ceil(17/16)*ceil(15/16)*16*16 * 4bytes = 2*1*256*4 = 2048 | ||
| 379 | + expected_bytes = self._expected_nz_storage_bytes((15, 17), 16, 4) | ||
| 380 | + self.assertEqual(out.untyped_storage().size(), expected_bytes) | ||
| 381 | + | ||
| 382 | + | ||
| 383 | + def test_npu_format_cast_c0_8_storage_size_3d(self): | ||
| 384 | + """Verify C0=8 storage size for 3D (4, 15, 17) int32. | ||
| 385 | + | ||
| 386 | + 3D FRACTAL_NZ: batch dims preserved, last 2 dims follow NZ layout. | ||
| 387 | + C0=8: 4 * ceil(17/8) * ceil(15/16) * 16 * 8 = 4*3*1*16*8 = 1536 elements * 4 = 6144 bytes. | ||
| 388 | + """ | ||
| 389 | + t = torch.randint(0, 100, (4, 15, 17), dtype=torch.int32).npu() | ||
| 390 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 391 | + customize_dtype=self.DTYPE_INT32) | ||
| 392 | + # 4 * ceil(17/8) * ceil(15/16) * 16 * 8 * 4bytes = 4*3*1*128*4 = 6144 | ||
| 393 | + expected_bytes = self._expected_nz_storage_bytes((4, 15, 17), 8, 4) | ||
| 394 | + self.assertEqual(out.untyped_storage().size(), expected_bytes) | ||
| 7 | 395 | ||
| 8 | 396 | ||
| 9 | class TestFormatCast(TestCase): | 397 | class TestFormatCast(TestCase): |
| 398 | + ACL_FORMAT_ND = 2 | ||
| 399 | + ACL_FORMAT_FRACTAL_NZ = 29 | ||
| 400 | + | ||
| 401 | + # ------------------------------------------------------------------ # | ||
| 402 | + # 2D: ND -> FRACTAL_NZ | ||
| 403 | + # ------------------------------------------------------------------ # | ||
| 404 | + | ||
| 405 | + | ||
| 406 | + def test_2d_nd_to_nz_format_id(self): | ||
| 407 | + """2D fp16 ND -> FRACTAL_NZ format ID.""" | ||
| 408 | + t = torch.rand(16, 32).half().npu() | ||
| 409 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 410 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 411 | + self.assertEqual(out.shape, t.shape) | ||
| 412 | + | ||
| 413 | + | ||
| 414 | + def test_2d_nd_to_nz_bf16(self): | ||
| 415 | + """2D bf16 ND -> FRACTAL_NZ.""" | ||
| 416 | + t = torch.rand(16, 32).bfloat16().npu() | ||
| 417 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 418 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 419 | + self.assertEqual(out.shape, t.shape) | ||
| 420 | + | ||
| 421 | + | ||
| 422 | + def test_2d_nd_to_nz_int8(self): | ||
| 423 | + """2D int8 ND -> FRACTAL_NZ.""" | ||
| 424 | + t = torch.randint(-128, 127, (32, 64), dtype=torch.int8).npu() | ||
| 425 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 426 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 427 | + self.assertEqual(out.shape, t.shape) | ||
| 428 | + | ||
| 429 | + | ||
| 430 | + def test_2d_nd_to_nz_large_shape(self): | ||
| 431 | + """2D large shape ND -> FRACTAL_NZ.""" | ||
| 432 | + t = torch.rand(256, 512).half().npu() | ||
| 433 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 434 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 435 | + self.assertEqual(out.shape, t.shape) | ||
| 436 | + | ||
| 437 | + # ------------------------------------------------------------------ # | ||
| 438 | + # 3D: ND -> FRACTAL_NZ | ||
| 439 | + # ------------------------------------------------------------------ # | ||
| 440 | + | ||
| 441 | + | ||
| 442 | + def test_3d_nd_to_nz_format_id(self): | ||
| 443 | + """3D fp16 ND -> FRACTAL_NZ format ID.""" | ||
| 444 | + t = torch.rand(4, 16, 32).half().npu() | ||
| 445 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 446 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 447 | + self.assertEqual(out.shape, t.shape) | ||
| 448 | + | ||
| 449 | + | ||
| 450 | + def test_3d_nd_to_nz_non_aligned(self): | ||
| 451 | + """3D non-aligned shape ND -> FRACTAL_NZ.""" | ||
| 452 | + t = torch.rand(3, 15, 17).half().npu() | ||
| 453 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 454 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 455 | + self.assertEqual(out.shape, t.shape) | ||
| 456 | + | ||
| 457 | + | ||
| 458 | + def test_3d_nd_to_nz_bf16(self): | ||
| 459 | + """3D bf16 ND -> FRACTAL_NZ.""" | ||
| 460 | + t = torch.rand(2, 16, 32).bfloat16().npu() | ||
| 461 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 462 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 463 | + self.assertEqual(out.shape, t.shape) | ||
| 464 | + | ||
| 465 | + | ||
| 466 | + def test_3d_nd_to_nz_float32(self): | ||
| 467 | + """3D float32 ND -> FRACTAL_NZ.""" | ||
| 468 | + t = torch.rand(2, 16, 32).float().npu() | ||
| 469 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 470 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 471 | + self.assertEqual(out.shape, t.shape) | ||
| 472 | + | ||
| 473 | + | ||
| 474 | + def test_3d_nd_to_nz_int8(self): | ||
| 475 | + """3D int8 ND -> FRACTAL_NZ.""" | ||
| 476 | + t = torch.randint(-128, 127, (4, 16, 32), dtype=torch.int8).npu() | ||
| 477 | + out = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 478 | + self.assertEqual(torch_npu.get_npu_format(out), self.ACL_FORMAT_FRACTAL_NZ) | ||
| 479 | + self.assertEqual(out.shape, t.shape) | ||
| 480 | + | ||
| 481 | + | ||
| 482 | +class TestNpuFormatCastPrecision(TestCase): | ||
| 483 | + """ | ||
| 484 | + Precision tests for npu_format_cast via round-trip verification. | ||
| 485 | + | ||
| 486 | + ND -> FRACTAL_NZ -> ND: compare round-trip result against original data. | ||
| 487 | + (Direct storage comparison is not possible because untyped_storage().copy_() | ||
| 488 | + triggers internal TransData which fails on current CANN versions.) | ||
| 489 | + """ | ||
| 490 | + | ||
| 491 | + def setUp(self): | ||
| 492 | + try: | ||
| 493 | + torch.npu.synchronize() | ||
| 494 | + except RuntimeError: | ||
| 495 | + pass | ||
| 496 | + | ||
| 497 | + ACL_FORMAT_ND = 2 | ||
| 498 | + ACL_FORMAT_FRACTAL_NZ = 29 | ||
| 499 | + | ||
| 500 | + DTYPE_INT32 = 3 | ||
| 501 | + | ||
| 502 | + def _verify_roundtrip_nz(self, t_npu): | ||
| 503 | + """ND -> NZ -> ND round-trip, compare with original. | ||
| 504 | + | ||
| 505 | + Note: save expected data BEFORE format_cast because aclnnNpuFormatCast | ||
| 506 | + may corrupt the source tensor's buffer (CANN receives a non-const pointer | ||
| 507 | + via const_cast in ConvertType and writes to it during conversion). | ||
| 508 | + """ | ||
| 509 | + expected = t_npu.cpu().clone() | ||
| 510 | + nz = torch_npu.npu_format_cast(t_npu, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 511 | + back = torch_npu.npu_format_cast(nz, self.ACL_FORMAT_ND) | ||
| 512 | + self.assertEqual(torch_npu.get_npu_format(back), self.ACL_FORMAT_ND) | ||
| 513 | + self.assertEqual(back.shape, t_npu.shape) | ||
| 514 | + self.assertEqual(back.cpu(), expected) | ||
| 515 | + | ||
| 516 | + # ------------------------------------------------------------------ # | ||
| 517 | + # fp16 round-trip | ||
| 518 | + # ------------------------------------------------------------------ # | ||
| 519 | + | ||
| 520 | + | ||
| 521 | + def test_roundtrip_nz_fp16_2d_aligned(self): | ||
| 522 | + t = torch.rand(16, 32).half().npu() | ||
| 523 | + self._verify_roundtrip_nz(t) | ||
| 524 | + | ||
| 525 | + | ||
| 526 | + def test_roundtrip_nz_fp16_2d_non_aligned(self): | ||
| 527 | + t = torch.rand(15, 17).half().npu() | ||
| 528 | + self._verify_roundtrip_nz(t) | ||
| 529 | + | ||
| 530 | + | ||
| 531 | + def test_roundtrip_nz_fp16_3d(self): | ||
| 532 | + t = torch.rand(4, 16, 32).half().npu() | ||
| 533 | + self._verify_roundtrip_nz(t) | ||
| 534 | + | ||
| 535 | + | ||
| 536 | + def test_roundtrip_nz_fp16_3d_non_aligned(self): | ||
| 537 | + t = torch.rand(3, 15, 17).half().npu() | ||
| 538 | + self._verify_roundtrip_nz(t) | ||
| 539 | + | ||
| 540 | + | ||
| 541 | + def test_roundtrip_nz_fp16_large(self): | ||
| 542 | + t = torch.rand(256, 512).half().npu() | ||
| 543 | + self._verify_roundtrip_nz(t) | ||
| 544 | + | ||
| 545 | + # ------------------------------------------------------------------ # | ||
| 546 | + # bf16 round-trip | ||
| 547 | + # ------------------------------------------------------------------ # | ||
| 548 | + | ||
| 549 | + | ||
| 550 | + def test_roundtrip_nz_bf16_2d(self): | ||
| 551 | + t = torch.rand(16, 32).bfloat16().npu() | ||
| 552 | + self._verify_roundtrip_nz(t) | ||
| 553 | + | ||
| 554 | + | ||
| 555 | + def test_roundtrip_nz_bf16_2d_non_aligned(self): | ||
| 556 | + t = torch.rand(15, 17).bfloat16().npu() | ||
| 557 | + self._verify_roundtrip_nz(t) | ||
| 558 | + | ||
| 559 | + | ||
| 560 | + def test_roundtrip_nz_bf16_3d(self): | ||
| 561 | + t = torch.rand(3, 15, 17).bfloat16().npu() | ||
| 562 | + self._verify_roundtrip_nz(t) | ||
| 563 | + | ||
| 564 | + # ------------------------------------------------------------------ # | ||
| 565 | + # float32 round-trip | ||
| 566 | + # ------------------------------------------------------------------ # | ||
| 567 | + | ||
| 568 | + | ||
| 569 | + def test_roundtrip_nz_float32_2d(self): | ||
| 570 | + t = torch.rand(16, 32).float().npu() | ||
| 571 | + self._verify_roundtrip_nz(t) | ||
| 572 | + | ||
| 573 | + | ||
| 574 | + def test_roundtrip_nz_float32_3d(self): | ||
| 575 | + t = torch.rand(2, 16, 32).float().npu() | ||
| 576 | + self._verify_roundtrip_nz(t) | ||
| 577 | + | ||
| 578 | + # ------------------------------------------------------------------ # | ||
| 579 | + # int8 round-trip | ||
| 580 | + # ------------------------------------------------------------------ # | ||
| 581 | + | ||
| 582 | + | ||
| 583 | + def test_roundtrip_nz_int8_2d(self): | ||
| 584 | + t = torch.randint(-128, 127, (32, 64), dtype=torch.int8).npu() | ||
| 585 | + self._verify_roundtrip_nz(t) | ||
| 586 | + | ||
| 587 | + | ||
| 588 | + def test_roundtrip_nz_int8_2d_non_aligned(self): | ||
| 589 | + t = torch.randint(-128, 127, (15, 17), dtype=torch.int8).npu() | ||
| 590 | + self._verify_roundtrip_nz(t) | ||
| 591 | + | ||
| 592 | + | ||
| 593 | + def test_roundtrip_nz_int8_3d(self): | ||
| 594 | + t = torch.randint(-128, 127, (4, 16, 32), dtype=torch.int8).npu() | ||
| 595 | + self._verify_roundtrip_nz(t) | ||
| 596 | + | ||
| 597 | + | ||
| 598 | + def test_roundtrip_nz_int8_3d_non_aligned(self): | ||
| 599 | + t = torch.randint(-128, 127, (3, 15, 17), dtype=torch.int8).npu() | ||
| 600 | + self._verify_roundtrip_nz(t) | ||
| 601 | + | ||
| 602 | + | ||
| 603 | + def test_roundtrip_nz_reverse_direction_uses_format_sensitive_cache(self): | ||
| 604 | + t = torch.arange(16 * 32, dtype=torch.float16).reshape(16, 32).npu() | ||
| 605 | + self._verify_roundtrip_nz(t) | ||
| 606 | + | ||
| 607 | + # ------------------------------------------------------------------ # | ||
| 608 | + # int32 default C0=16 round-trip | ||
| 609 | + # ------------------------------------------------------------------ # | ||
| 610 | + | ||
| 611 | + | ||
| 612 | + def test_roundtrip_nz_int32_default_c0_16(self): | ||
| 613 | + t = torch.randint(0, 100, (15, 17), dtype=torch.int32).npu() | ||
| 614 | + expected = t.cpu().clone() | ||
| 615 | + nz = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ) | ||
| 616 | + back = torch_npu.npu_format_cast(nz, self.ACL_FORMAT_ND) | ||
| 617 | + self.assertEqual(back.cpu(), expected) | ||
| 618 | + | ||
| 619 | + # ------------------------------------------------------------------ # | ||
| 620 | + # int32 with customize_dtype=INT32 (C0=8) round-trip on 910B | ||
| 621 | + # ------------------------------------------------------------------ # | ||
| 622 | + | ||
| 623 | + | ||
| 624 | + def test_roundtrip_nz_int32_c0_8_aligned(self): | ||
| 625 | + t = torch.randint(0, 100, (16, 8), dtype=torch.int32).npu() | ||
| 626 | + expected = t.cpu().clone() | ||
| 627 | + nz = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 628 | + customize_dtype=self.DTYPE_INT32) | ||
| 629 | + back = torch_npu.npu_format_cast(nz, self.ACL_FORMAT_ND) | ||
| 630 | + self.assertEqual(back.cpu(), expected) | ||
| 631 | + | ||
| 632 | + | ||
| 633 | + def test_roundtrip_nz_int32_c0_8_non_aligned(self): | ||
| 634 | + t = torch.randint(0, 100, (15, 17), dtype=torch.int32).npu() | ||
| 635 | + expected = t.cpu().clone() | ||
| 636 | + nz = torch_npu.npu_format_cast(t, self.ACL_FORMAT_FRACTAL_NZ, | ||
| 637 | + customize_dtype=self.DTYPE_INT32) | ||
| 638 | + back = torch_npu.npu_format_cast(nz, self.ACL_FORMAT_ND) | ||
| 639 | + self.assertEqual(back.cpu(), expected) | ||
| 640 | + | ||
| 641 | + | ||
| 642 | +class TestZFormatCastOriginal(TestCase): | ||
| 643 | + ACL_FORMAT_NC1HWC0 = 3 | ||
| 10 | 644 | ||
| 11 | def supported_op_exec(self, input1): | 645 | def supported_op_exec(self, input1): |
| 12 | m = torch.nn.Identity(54, unused_argument1=0.1, unused_argument2=False) | 646 | m = torch.nn.Identity(54, unused_argument1=0.1, unused_argument2=False) |
| @@ -17,13 +651,15 @@ class TestFormatCast(TestCase): | |||
| 17 | output = torch_npu.npu_format_cast(input1, acl_format) | 651 | output = torch_npu.npu_format_cast(input1, acl_format) |
| 18 | return output.cpu().detach() | 652 | return output.cpu().detach() |
| 19 | 653 | ||
| 654 | + # Ascend950 does not support this 4D NC1HWC0 format_cast case. | ||
| 655 | + | ||
| 20 | def test_npu_format_cast(self, device="npu"): | 656 | def test_npu_format_cast(self, device="npu"): |
| 657 | + """Original test: NCHW -> NC1HWC0 data correctness.""" | ||
| 21 | item = [np.float16, 0, (2, 2, 4, 4)] | 658 | item = [np.float16, 0, (2, 2, 4, 4)] |
| 22 | _, npu_input = create_common_tensor(item, -1, 1) | 659 | _, npu_input = create_common_tensor(item, -1, 1) |
| 23 | - acl_format = 3 | ||
| 24 | 660 | ||
| 25 | supported_output = self.supported_op_exec(npu_input) | 661 | supported_output = self.supported_op_exec(npu_input) |
| 26 | - custom_output = self.custom_op_exec(npu_input, acl_format) | 662 | + custom_output = self.custom_op_exec(npu_input, self.ACL_FORMAT_NC1HWC0) |
| 27 | self.assertRtolEqual(supported_output, custom_output) | 663 | self.assertRtolEqual(supported_output, custom_output) |
| 28 | 664 | ||
| 29 | 665 | ||
| @@ -1 +1 @@ | |||
| 1 | -Subproject commit d9c8fb04f4dfed684940182add0634bd5cbef7f7 | 1 | +Subproject commit 1ddd4d0aec25f1dfd4a85f66a93793a6097abcf6 |
C | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | 8 | ||
| 9 | 9 | ||
| 10 | + | ||
| 10 | 11 | ||
| 11 | 12 | ||
| 12 | 13 | ||
| @@ -22,6 +23,38 @@ static std::unordered_map<int, int> FORMAT_REAL_TO_FAKE { | |||
| 22 | using tensor_list = std::vector<at::Tensor>; | 23 | using tensor_list = std::vector<at::Tensor>; |
| 23 | using GetFormatFunc = int (*)(const aclTensor *, const int, const int, int64_t **, uint64_t *, int *); | 24 | using GetFormatFunc = int (*)(const aclTensor *, const int, const int, int64_t **, uint64_t *, int *); |
| 24 | 25 | ||
| 26 | +// Check if current CANN version supports aclnn format_cast with customize_dtype. | ||
| 27 | +// Only 910B and 910C series support aclnnNpuFormatCastCalculateSizeAndFormat. | ||
| 28 | +// 950 goes through IsAclnnOnly() path separately; 910A/310 series do not support this API. | ||
| 29 | +static bool IsAclnnFormatCastSupported() | ||
| 30 | +{ | ||
| 31 | + static const auto soc = c10_npu::GetSocVersion(); | ||
| 32 | + static const bool supported = IsGteCANNVersion("9.1.0", "CANN") && | ||
| 33 | + ((soc >= c10_npu::SocVersion::Ascend910B1 && soc < c10_npu::SocVersion::Ascend310B1) || | ||
| 34 | + (soc >= c10_npu::SocVersion::Ascend910_9391 && soc < c10_npu::SocVersion::Ascend950)); | ||
| 35 | + return supported; | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +static bool ShouldFallbackNzToNd(const at::Tensor& self, int64_t acl_format) | ||
| 39 | +{ | ||
| 40 | + if (acl_format != ACL_FORMAT_FRACTAL_NZ) { | ||
| 41 | + return false; | ||
| 42 | + } | ||
| 43 | + auto src_desc = torch_npu::NPUBridge::GetNpuStorageImpl(self)->npu_desc_; | ||
| 44 | + if (src_desc.npu_format_ != ACL_FORMAT_ND) { | ||
| 45 | + return false; | ||
| 46 | + } | ||
| 47 | + if (src_desc.base_sizes_.size() != self.sizes().size()) { | ||
| 48 | + return true; | ||
| 49 | + } | ||
| 50 | + for (size_t i = 0; i < src_desc.base_sizes_.size(); ++i) { | ||
| 51 | + if (src_desc.base_sizes_[i] != self.sizes()[i]) { | ||
| 52 | + return true; | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + return false; | ||
| 56 | +} | ||
| 57 | + | ||
| 25 | std::tuple<bool, int64_t, c10::SmallVector<int64_t, SIZE>> MaybeUseAclnnNpuFormatCast(const at::Tensor& src, | 58 | std::tuple<bool, int64_t, c10::SmallVector<int64_t, SIZE>> MaybeUseAclnnNpuFormatCast(const at::Tensor& src, |
| 26 | int64_t acl_format, c10::optional<int64_t> customize_dtype, c10::optional<int64_t> input_dtype) | 59 | int64_t acl_format, c10::optional<int64_t> customize_dtype, c10::optional<int64_t> input_dtype) |
| 27 | { | 60 | { |
| @@ -71,6 +104,51 @@ std::tuple<bool, int64_t, c10::SmallVector<int64_t, SIZE>> MaybeUseAclnnNpuForma | |||
| 71 | "aclnnNpuFormatCast does not exist, Current soc version only support aclnn operators.", | 104 | "aclnnNpuFormatCast does not exist, Current soc version only support aclnn operators.", |
| 72 | PTA_ERROR(ErrCode::NOT_SUPPORT)); | 105 | PTA_ERROR(ErrCode::NOT_SUPPORT)); |
| 73 | } | 106 | } |
| 107 | + // Non-aclnn-only path (910B and older chips). | ||
| 108 | + // CANN >= 9.1.0 supports aclnn format_cast with customize_dtype on 910B. | ||
| 109 | + // Match original aclop behavior: 4-byte types (float32/int32) default to | ||
| 110 | + // ACL_FLOAT16 (C0=16); smaller types keep their natural C0 (int8→32, fp16→16). | ||
| 111 | + // See FormatHelper.cpp InferShapeNDToNZ: (itemsize > 2) ? 2 : itemsize. | ||
| 112 | + if (!customize_dtype.has_value()) { | ||
| 113 | + if (src.element_size() >= 4) { | ||
| 114 | + customizeAcltype = aclDataType::ACL_FLOAT16; | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + // else: customizeAcltype stays as user's customize_dtype value (set at top of function) | ||
| 118 | + if (IsAclnnFormatCastSupported() && aclnnNpuFormatCastExist) { | ||
| 119 | + auto acl_src = ConvertType(srcWrapper); | ||
| 120 | + auto api_ret = GetFormat(acl_src, acl_format, customizeAcltype, &dstStorageShape, | ||
| 121 | + &dstShapeSize, &dstFormat); | ||
| 122 | + Release(acl_src); | ||
| 123 | + // CANN aclnn does not yet support all format pairs (e.g. NC1HWC0->ND, | ||
| 124 | + // 2D->NC1HWC0, 4D->NZ). Fall back to aclop for unsupported pairs. | ||
| 125 | + if (api_ret != 0) { | ||
| 126 | + return std::make_tuple(false, dstFormat, outputShape); | ||
| 127 | + } | ||
| 128 | + for (uint64_t i = 0; i < dstShapeSize; i++) { | ||
| 129 | + outputShape.push_back(dstStorageShape[i]); | ||
| 130 | + } | ||
| 131 | + // For 4-bit types: when customize_dtype=INT32 (8 packed int4, C0=8), | ||
| 132 | + // CANN already computes correct storage shape, skip halving. | ||
| 133 | + // Otherwise default C0=16: halve last dim and remap format. | ||
| 134 | + if (customizeAcltype != aclDataType::ACL_INT32 && | ||
| 135 | + (srcAcltype == aclDataType::ACL_FLOAT4_E2M1 || srcAcltype == aclDataType::ACL_FLOAT4_E1M2 || | ||
| 136 | + srcAcltype == aclDataType::ACL_INT4)) { | ||
| 137 | + if (FORMAT_REAL_TO_FAKE.find(dstFormat) == FORMAT_REAL_TO_FAKE.end() || outputShape.empty()) { | ||
| 138 | + delete[] dstStorageShape; | ||
| 139 | + dstStorageShape = nullptr; | ||
| 140 | + TORCH_CHECK(false, | ||
| 141 | + "aclnnNpuFormatCast not support recovery format.", | ||
| 142 | + PTA_ERROR(ErrCode::NOT_SUPPORT)); | ||
| 143 | + } | ||
| 144 | + outputShape.back() = outputShape.back() >> 1; | ||
| 145 | + dstFormat = FORMAT_REAL_TO_FAKE[dstFormat]; | ||
| 146 | + } | ||
| 147 | + delete[] dstStorageShape; | ||
| 148 | + dstStorageShape = nullptr; | ||
| 149 | + return std::make_tuple(true, static_cast<int64_t>(dstFormat), outputShape); | ||
| 150 | + } | ||
| 151 | + // CANN < 9.1.0: customize_dtype not supported, fall back to aclop path. | ||
| 74 | if (C10_UNLIKELY(customize_dtype.has_value())) { | 152 | if (C10_UNLIKELY(customize_dtype.has_value())) { |
| 75 | TORCH_CHECK(false, | 153 | TORCH_CHECK(false, |
| 76 | "customize_dtype is not supported by the current soc version.", | 154 | "customize_dtype is not supported by the current soc version.", |
| @@ -324,6 +402,11 @@ at::Tensor NPUNativeFunctions::npu_format_cast(const at::Tensor& self, int64_t a | |||
| 324 | c10::optional<int64_t> input_dtype) | 402 | c10::optional<int64_t> input_dtype) |
| 325 | { | 403 | { |
| 326 | torch_npu::utils::torch_check_npu(self); | 404 | torch_npu::utils::torch_check_npu(self); |
| 405 | + // Match aclop behavior for manually set storage tensors whose NPU desc | ||
| 406 | + // base shape does not match current logical shape. | ||
| 407 | + if (ShouldFallbackNzToNd(self, acl_format)) { | ||
| 408 | + acl_format = ACL_FORMAT_ND; | ||
C acl_format回退ND,建议给出提示 ![]() ![]() | |||
| 409 | + } | ||
| 327 | if (NPUNativeFunctions::get_npu_format(self) == acl_format) { | 410 | if (NPUNativeFunctions::get_npu_format(self) == acl_format) { |
| 328 | ASCEND_LOGD("no need to do format cast"); | 411 | ASCEND_LOGD("no need to do format cast"); |
| 329 | return self; | 412 | return self; |


不需要手动刷新op-plugin的commit-id