Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / quantization / ops / npu_mxfp8.py: 51%

114 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07:18 +0800

1# Copyright 2026 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""A5 MXFP8 quantization and matrix-multiplication operator adapters.""" 

16 

17from functools import lru_cache 

18from typing import Any, Optional 

19 

20import torch # pylint: disable=forbidden-backend-import 

21 

22from hyper_parallel.components.quantization.config import ( 

23 GMM_GROUP_K, 

24 GMM_GROUP_M, 

25 GMM_SPLIT_MXFP8, 

26 GROUP_LIST_COUNTS, 

27 GROUP_LIST_OFFSETS, 

28 MATRIX_NDIM, 

29 MX_BLOCK_SIZE_32, 

30 MX_SCALE_ALG_CUBALS, 

31) 

32from hyper_parallel.components.quantization.tensor import MXFP8Tensor 

33 

34_A5_DEVICE_MARKERS = ("Ascend950", "Ascend910_95") 

35_SUPPORTED_LAYOUTS = ("NN", "NT", "TN") 

36 

37 

38class LowPrecisionCapabilityError(RuntimeError): 

39 """Report that the active torch_npu stack lacks the A5 MXFP8 contract.""" 

40 

41 

42class MXFP8NpuOps: 

43 """Adapt the A5 MXFP8 torch_npu operator contract.""" 

44 

45 def __init__(self, torch_npu_module: Any) -> None: 

46 """Validate and retain an imported torch_npu module.""" 

47 

48 required = ( 

49 "npu_dynamic_mx_quant", 

50 "npu_dynamic_mx_quant_with_dual_axis", 

51 "npu_quant_matmul", 

52 "float8_e8m0fnu", 

53 ) 

54 missing = [ 

55 name for name in required if not hasattr(torch_npu_module, name) 

56 ] 

57 if missing: 

58 version = getattr(torch_npu_module, "__version__", "unknown") 

59 raise LowPrecisionCapabilityError( 

60 "The active torch_npu stack does not provide the A5 MXFP8 " 

61 f"operator contract; missing {missing}, torch_npu={version}." 

62 ) 

63 npu_handle = getattr(torch_npu_module, "npu", None) 

64 device_name = ( 

65 npu_handle.get_device_name() 

66 if npu_handle is not None 

67 and hasattr(npu_handle, "get_device_name") 

68 else "" 

69 ) 

70 if not any(marker in device_name for marker in _A5_DEVICE_MARKERS): 

71 raise LowPrecisionCapabilityError( 

72 "MXFP8 is supported only on Ascend 950PR/950DT (A5), " 

73 f"but the active device is {device_name or 'unknown'}." 

74 ) 

75 self._torch_npu = torch_npu_module 

76 

77 def validate_grouped_matmul(self) -> None: 

78 """Fail when the runtime lacks either MXFP8 GMM-specific operator.""" 

79 

80 required = ( 

81 "npu_grouped_dynamic_mx_quant", 

82 "npu_grouped_matmul", 

83 ) 

84 missing = [ 

85 name for name in required if not hasattr(self._torch_npu, name) 

86 ] 

87 if missing: 

88 version = getattr(self._torch_npu, "__version__", "unknown") 

89 raise LowPrecisionCapabilityError( 

90 "The active torch_npu stack does not provide the A5 MXFP8 " 

91 f"GMM contract; missing {missing}, torch_npu={version}." 

92 ) 

93 

94 def dynamic_mx_quant( 

95 self, 

96 tensor: torch.Tensor, 

97 *, 

98 axis: int, 

99 quant_dtype: Optional[torch.dtype] = None, 

100 ) -> tuple[torch.Tensor, torch.Tensor]: 

101 """Quantize one tensor along an MX block axis.""" 

102 

103 return self._torch_npu.npu_dynamic_mx_quant( 

104 tensor, 

105 axis=axis, 

106 dst_type=quant_dtype or torch.float8_e4m3fn, 

107 scale_alg=MX_SCALE_ALG_CUBALS, 

108 ) 

109 

110 def dynamic_mx_quant_dual_axis( 

111 self, 

112 tensor: torch.Tensor, 

113 *, 

114 quant_dtype: Optional[torch.dtype] = None, 

115 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: 

116 """Quantize one tensor into row-wise and column-wise MX storage.""" 

117 

118 return self._torch_npu.npu_dynamic_mx_quant_with_dual_axis( 

119 tensor, 

120 dst_type=quant_dtype or torch.float8_e4m3fn, 

121 scale_alg=MX_SCALE_ALG_CUBALS, 

122 ) 

123 

124 def validate_swiglu_fusion(self) -> None: 

125 """Require both fused kernels; never silently fall back to plain SwiGLU.""" 

126 names = ("npu_swiglu_mx_quant_with_dual_axis", "_npu_swiglu_backward_mx_quant_with_dual_axis") 

127 missing = [name for name in names if not hasattr(self._torch_npu, name)] 

128 if missing: 

129 raise LowPrecisionCapabilityError(f"MXFP8 SwiGLU fusion requires {missing}") 

130 

131 def swiglu_mx_quant_dual_axis( 

132 self, 

133 tensor: torch.Tensor, 

134 *, 

135 quant_dtype: torch.dtype = torch.float8_e4m3fn, 

136 group_index: Optional[torch.Tensor] = None, 

137 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: 

138 """Apply SiLU(gate) * up and quantize both MX directions. 

139 

140 Args: 

141 tensor: BF16/FP16 matrix containing [gate | up] along its last axis. 

142 quant_dtype: Payload dtype, E4M3 for the current MXFP8 recipe. 

143 group_index: Cumulative expert token boundaries, or None for Dense. 

144 

145 Returns: 

146 Row data, row scale, column data, and column scale. 

147 """ 

148 return self._torch_npu.npu_swiglu_mx_quant_with_dual_axis( 

149 tensor, group_index=group_index, activate_left=True, 

150 dst_type=quant_dtype, scale_alg=1, 

151 ) 

152 

153 def swiglu_backward_mx_quant_dual_axis( 

154 self, 

155 tensor: torch.Tensor, 

156 grad_output: torch.Tensor, 

157 *, 

158 quant_dtype: torch.dtype = torch.float8_e4m3fn, 

159 group_index: Optional[torch.Tensor] = None, 

160 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: 

161 """Compute the SwiGLU derivative and quantize both MX directions. 

162 

163 Args: 

164 tensor: Saved BF16/FP16 packed Gate/Up matrix. 

165 grad_output: BF16/FP16 gradient of the SwiGLU output. 

166 quant_dtype: Payload dtype, E4M3 for the current MXFP8 recipe. 

167 group_index: The same cumulative boundaries used by forward. 

168 

169 Returns: 

170 Row data, row scale, column data, and column scale of the gradient. 

171 """ 

172 return self._torch_npu._npu_swiglu_backward_mx_quant_with_dual_axis( # pylint: disable=protected-access 

173 tensor, grad_output, group_index=group_index, activate_left=True, 

174 dst_type=quant_dtype, scale_alg=1, 

175 ) 

176 

177 def quant_matmul( 

178 self, 

179 x1: torch.Tensor, 

180 x2: torch.Tensor, 

181 scale: torch.Tensor, 

182 *, 

183 pertoken_scale: torch.Tensor, 

184 output_dtype: torch.dtype, 

185 ) -> torch.Tensor: 

186 """Execute an A5 MXFP8 matrix multiplication.""" 

187 

188 return self._torch_npu.npu_quant_matmul( 

189 x1, 

190 x2, 

191 scale, 

192 pertoken_scale=pertoken_scale, 

193 output_dtype=output_dtype, 

194 scale_dtype=self._torch_npu.float8_e8m0fnu, 

195 pertoken_scale_dtype=self._torch_npu.float8_e8m0fnu, 

196 group_sizes=[1, 1, MX_BLOCK_SIZE_32], 

197 ) 

198 

199 def grouped_dynamic_mx_quant( 

200 self, 

201 tensor: torch.Tensor, 

202 group_list: torch.Tensor, 

203 *, 

204 quant_dtype: Optional[torch.dtype] = None, 

205 ) -> tuple[torch.Tensor, torch.Tensor]: 

206 """Quantize the column direction independently for each expert.""" 

207 

208 grouped_quant = getattr( 

209 self._torch_npu, 

210 "npu_grouped_dynamic_mx_quant", 

211 None, 

212 ) 

213 if grouped_quant is None: 

214 version = getattr(self._torch_npu, "__version__", "unknown") 

215 raise LowPrecisionCapabilityError( 

216 "The active torch_npu stack does not provide " 

217 "npu_grouped_dynamic_mx_quant, which MXFP8 GMM requires; " 

218 f"torch_npu={version}." 

219 ) 

220 return grouped_quant( 

221 tensor, 

222 # A5 GroupedDynamicMxQuant's groupIndex contract is int32. 

223 group_list.to(torch.int32), 

224 round_mode="rint", 

225 dst_type=quant_dtype or torch.float8_e4m3fn, 

226 blocksize=MX_BLOCK_SIZE_32, 

227 ) 

228 

229 def quant_grouped_matmul( 

230 self, 

231 x1: torch.Tensor, 

232 x2: torch.Tensor, 

233 x2_scale: torch.Tensor, 

234 *, 

235 x1_scale: torch.Tensor, 

236 group_list: torch.Tensor, 

237 group_type: int, 

238 group_list_type: int, 

239 output_dtype: torch.dtype, 

240 bias: Optional[list[torch.Tensor]] = None, 

241 ) -> torch.Tensor: 

242 """Execute one A5 MXFP8 grouped matrix multiplication.""" 

243 

244 grouped_matmul = getattr(self._torch_npu, "npu_grouped_matmul", None) 

245 if grouped_matmul is None: 

246 version = getattr(self._torch_npu, "__version__", "unknown") 

247 raise LowPrecisionCapabilityError( 

248 "The active torch_npu stack does not provide " 

249 f"npu_grouped_matmul; torch_npu={version}." 

250 ) 

251 # A5 GroupedMatmul's groupList contract is int64. 

252 group_list = group_list.to(torch.int64) 

253 return grouped_matmul( 

254 [x1], 

255 [x2], 

256 bias=[] if bias is None else bias, 

257 scale=[x2_scale], 

258 per_token_scale=[x1_scale], 

259 group_list=group_list, 

260 group_type=group_type, 

261 output_dtype=output_dtype, 

262 group_list_type=group_list_type, 

263 scale_dtype=self._torch_npu.float8_e8m0fnu, 

264 per_token_scale_dtype=self._torch_npu.float8_e8m0fnu, 

265 split_item=GMM_SPLIT_MXFP8, 

266 )[0] 

267 

268 def is_e8m0_dtype(self, dtype: torch.dtype) -> bool: 

269 """Return whether a scale dtype is this runtime's E8M0 representation.""" 

270 return dtype == self._torch_npu.float8_e8m0fnu 

271 

272 

273@lru_cache(maxsize=1) 

274def _get_npu_ops() -> MXFP8NpuOps: 

275 """Return the process-local A5 MXFP8 operator adapter.""" 

276 

277 try: 

278 import torch_npu # pylint: disable=C0415 

279 except ImportError as exc: 

280 raise LowPrecisionCapabilityError( 

281 "A5 MXFP8 requires torch_npu, but it is not importable." 

282 ) from exc 

283 return MXFP8NpuOps(torch_npu) 

284 

285 

286def validate_npu_runtime() -> None: 

287 """Fail during model setup when the MXFP8 runtime is unavailable.""" 

288 

289 _get_npu_ops() 

290 

291 

292def validate_npu_swiglu_runtime() -> None: 

293 """Validate fusion without storing runtime modules on a model's quantizer.""" 

294 _get_npu_ops().validate_swiglu_fusion() 

295 

296 

297def validate_npu_gmm_runtime() -> None: 

298 """Fail during expert setup when the MXFP8 GMM runtime is unavailable.""" 

299 

300 _get_npu_ops().validate_grouped_matmul() 

301 

302 

303def _transpose_scale(scale: torch.Tensor) -> torch.Tensor: 

304 """Transpose the two matrix dimensions of an MX scale tensor.""" 

305 

306 if scale.ndim < MATRIX_NDIM: 

307 raise ValueError( 

308 "MXFP8 scale transpose requires at least two dimensions, got " 

309 f"shape {tuple(scale.shape)}." 

310 ) 

311 if scale.ndim == MATRIX_NDIM: 

312 return scale.transpose(0, 1) 

313 return scale.transpose(-3, -2) 

314 

315 

316def _left_operand( 

317 tensor: MXFP8Tensor, 

318 *, 

319 transpose: bool, 

320) -> tuple[torch.Tensor, torch.Tensor]: 

321 """Select MX data and scales for the requested left-operand layout.""" 

322 if transpose: 

323 if not tensor.is_colwise(): 

324 raise ValueError( 

325 "Transposed MXFP8 left operand requires column-wise data." 

326 ) 

327 return ( 

328 tensor.col_data.transpose(-1, -2), 

329 _transpose_scale(tensor.col_scale), 

330 ) 

331 if not tensor.is_rowwise(): 

332 raise ValueError( 

333 "Non-transposed MXFP8 left operand requires row-wise data." 

334 ) 

335 return tensor.row_data, tensor.row_scale 

336 

337 

338def _right_operand( 

339 tensor: MXFP8Tensor, 

340 *, 

341 transpose: bool, 

342) -> tuple[torch.Tensor, torch.Tensor]: 

343 """Select MX data and scales for the requested right-operand layout.""" 

344 if transpose: 

345 if not tensor.is_rowwise(): 

346 raise ValueError( 

347 "Transposed MXFP8 right operand requires row-wise data." 

348 ) 

349 return ( 

350 tensor.row_data.transpose(-1, -2), 

351 _transpose_scale(tensor.row_scale), 

352 ) 

353 if not tensor.is_colwise(): 

354 raise ValueError( 

355 "Non-transposed MXFP8 right operand requires column-wise data." 

356 ) 

357 return tensor.col_data, tensor.col_scale 

358 

359 

360def mxfp8_matmul( 

361 left: MXFP8Tensor, 

362 right: MXFP8Tensor, 

363 *, 

364 layout: str, 

365 output_dtype: Optional[torch.dtype] = None, 

366) -> torch.Tensor: 

367 """Multiply typed MXFP8 operands and return a high-precision Tensor.""" 

368 

369 if not isinstance(left, MXFP8Tensor) or not isinstance( 

370 right, 

371 MXFP8Tensor, 

372 ): 

373 raise TypeError("mxfp8_matmul requires two MXFP8Tensor operands.") 

374 if layout not in _SUPPORTED_LAYOUTS: 

375 raise ValueError( 

376 f"Unsupported MXFP8 layout {layout!r}; " 

377 f"expected one of {_SUPPORTED_LAYOUTS}." 

378 ) 

379 

380 left_data, left_scale = _left_operand( 

381 left, 

382 transpose=layout[0] == "T", 

383 ) 

384 right_data, right_scale = _right_operand( 

385 right, 

386 transpose=layout[1] == "T", 

387 ) 

388 return left.quantizer.npu_ops.quant_matmul( 

389 left_data, 

390 right_data, 

391 right_scale, 

392 pertoken_scale=left_scale, 

393 output_dtype=output_dtype or left.dtype, 

394 ) 

395 

396 

397def mxfp8_grouped_matmul( 

398 left: MXFP8Tensor, 

399 right: MXFP8Tensor, 

400 *, 

401 layout: str, 

402 group_list: torch.Tensor, 

403 group_type: int = GMM_GROUP_M, 

404 group_list_type: int = GROUP_LIST_OFFSETS, 

405 output_dtype: Optional[torch.dtype] = None, 

406 bias: Optional[torch.Tensor] = None, 

407) -> torch.Tensor: 

408 """Multiply expert-grouped MXFP8 operands and return a high-precision Tensor. 

409 

410 The layout follows ``Y = op(left) @ op(right)``. Forward and input-gradient 

411 GMM split the M axis with ``group_type=0``; weight-gradient GMM splits the 

412 contracting axis with ``group_type=2``. 

413 

414 Args: 

415 left: Quantized left operand. 

416 right: Quantized right operand. 

417 layout: One of ``"NN"``, ``"NT"``, or ``"TN"``. 

418 group_list: Expert boundaries or per-expert token counts. 

419 group_type: Axis grouped by the NPU kernel. 

420 group_list_type: ``0`` for cumulative boundaries or ``1`` for counts. 

421 output_dtype: Optional high-precision output dtype. 

422 bias: Optional grouped bias tensor. 

423 

424 Returns: 

425 The grouped matrix-multiplication result. 

426 """ 

427 

428 if not isinstance(left, MXFP8Tensor) or not isinstance(right, MXFP8Tensor): 

429 raise TypeError("mxfp8_grouped_matmul requires two MXFP8Tensor operands.") 

430 if layout not in _SUPPORTED_LAYOUTS: 

431 raise ValueError( 

432 f"Unsupported MXFP8 GMM layout {layout!r}; " 

433 f"expected one of {_SUPPORTED_LAYOUTS}." 

434 ) 

435 if not isinstance(group_list, torch.Tensor) or group_list.ndim != 1: 

436 raise ValueError("MXFP8 GMM group_list must be a one-dimensional Tensor.") 

437 if group_type not in (GMM_GROUP_M, GMM_GROUP_K): 

438 raise ValueError( 

439 f"MXFP8 GMM group_type must be {GMM_GROUP_M} or {GMM_GROUP_K}, " 

440 f"but got {group_type}." 

441 ) 

442 if group_list_type not in (GROUP_LIST_OFFSETS, GROUP_LIST_COUNTS): 

443 raise ValueError( 

444 "MXFP8 GMM group_list_type must be " 

445 f"{GROUP_LIST_OFFSETS} or {GROUP_LIST_COUNTS}, " 

446 f"but got {group_list_type}." 

447 ) 

448 

449 left_data, left_scale = _left_operand( 

450 left, 

451 transpose=layout[0] == "T", 

452 ) 

453 right_data, right_scale = _right_operand( 

454 right, 

455 transpose=layout[1] == "T", 

456 ) 

457 bias_list = None if bias is None else [bias] 

458 return left.quantizer.npu_ops.quant_grouped_matmul( 

459 left_data, 

460 right_data, 

461 right_scale, 

462 x1_scale=left_scale, 

463 group_list=group_list, 

464 group_type=group_type, 

465 group_list_type=group_list_type, 

466 output_dtype=output_dtype or left.dtype, 

467 bias=bias_list, 

468 )