From 7877ab37cbe79124a495152dd3d06461265dfaeb Mon Sep 17 00:00:00 2001 From: cangtianhuang <1903374751@qq.com> Date: Thu, 16 Jul 2026 13:49:51 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Fix=20wrong=20classify?= =?UTF-8?q?,=20use=20report=5Fcompare=5Ferror?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tester/accuracy.py | 14 +------- tester/accuracy_stable.py | 56 +++----------------------------- tester/base.py | 8 +++++ tester/paddle_cinn_vs_dygraph.py | 13 ++------ tester/paddle_device_vs_cpu.py | 12 +++---- tester/paddle_device_vs_gpu.py | 25 ++++---------- 6 files changed, 27 insertions(+), 101 deletions(-) diff --git a/tester/accuracy.py b/tester/accuracy.py index cb13385e..3c1be418 100644 --- a/tester/accuracy.py +++ b/tester/accuracy.py @@ -334,20 +334,8 @@ def compare_paddle_and_torch(paddle_tensor, torch_tensor, idx=0) -> bool: paddle_tensor, torch_tensor, atol=self.get_atol(), rtol=self.get_rtol() ) except Exception as err: - err_str = str(err) phase = "backward" if self.is_backward else "forward" - if err_str.startswith("[torch_assert_OOM]"): - print( - f"[oom] phase={phase} idx={idx} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("oom", self.api_config.config) - else: - print( - f"[paddle_accuracy] phase={phase} idx={idx} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(err, f"{phase} idx={idx}") if self.exit_on_error: raise return False diff --git a/tester/accuracy_stable.py b/tester/accuracy_stable.py index 21c72190..9f331dad 100644 --- a/tester/accuracy_stable.py +++ b/tester/accuracy_stable.py @@ -403,19 +403,7 @@ def compare(self, input1, input2, comp): try: self.assert_accuracy(input1, input2, comp) except Exception as err: - err_str = str(err) - if err_str.startswith("[torch_assert_OOM]"): - print( - f"[oom] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("oom", self.api_config.config) - else: - print( - f"[paddle_accuracy] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(err, f"comp={comp}") return else: print( @@ -450,19 +438,7 @@ def compare(self, input1, input2, comp): try: self.assert_accuracy(item1, item2, comp, idx) except Exception as err: - err_str = str(err) - if err_str.startswith("[torch_assert_OOM]"): - print( - f"[oom] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("oom", self.api_config.config) - else: - print( - f"[paddle_accuracy] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(err, f"comp={comp} idx={idx}") return elif not isinstance(item1, (paddle.Tensor, torch.Tensor)) and not isinstance( item2, (paddle.Tensor, torch.Tensor) @@ -470,19 +446,7 @@ def compare(self, input1, input2, comp): try: self.assert_accuracy(torch.tensor(item1), torch.tensor(item2), comp, idx) except Exception as err: - err_str = str(err) - if err_str.startswith("[torch_assert_OOM]"): - print( - f"[oom] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("oom", self.api_config.config) - else: - print( - f"[paddle_accuracy] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(err, f"comp={comp} idx={idx}") return else: print( @@ -496,19 +460,7 @@ def compare(self, input1, input2, comp): try: self.assert_accuracy(torch.tensor(input1), torch.tensor(input2), comp) except Exception as err: - err_str = str(err) - if err_str.startswith("[torch_assert_OOM]"): - print( - f"[oom] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("oom", self.api_config.config) - else: - print( - f"[paddle_accuracy] comp={comp} {self.api_config.config}\n{err_str}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(err, f"comp={comp}") return def assert_accuracy(self, tensor1, tensor2, comp, idx=0): diff --git a/tester/base.py b/tester/base.py index 285d9f61..6b34675d 100644 --- a/tester/base.py +++ b/tester/base.py @@ -68,6 +68,8 @@ def __getattr__(self, name): def classify_runtime_error(error_msg): error_msg_lower = error_msg.lower() + if error_msg.startswith("[torch_assert_OOM]"): + return "oom", False oom_markers = tuple(marker.lower() for marker in CUDA_OOM) + ( "cannot allocate memory", "std::bad_alloc", @@ -256,6 +258,12 @@ def report_runtime_error(self, err, default_log_type, phase="", allow_ignore_pad write_to_log(log_type, self.api_config.config) return log_type, fatal + def report_compare_error(self, err, phase="", default_log_type="paddle_accuracy"): + log_type, fatal = self.report_runtime_error(err, default_log_type, phase) + if fatal: + raise err + return log_type, fatal + def need_skip(self, paddle_only=False): # not support if "sparse" in self.api_config.api_name: diff --git a/tester/paddle_cinn_vs_dygraph.py b/tester/paddle_cinn_vs_dygraph.py index c5180b09..ee190539 100644 --- a/tester/paddle_cinn_vs_dygraph.py +++ b/tester/paddle_cinn_vs_dygraph.py @@ -298,11 +298,7 @@ def compare(self, dygraph_output, static_output, is_backward=False): try: self.paddle_assert_accuracy(dygraph_output, static_output) except Exception as err: - print( - f"[accuracy error] {backward_str}{self.api_config.config}\n{err!s}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(err, backward_str.strip()) return False elif isinstance(dygraph_output, (list, tuple)): if not isinstance(static_output, (list, tuple)): @@ -341,11 +337,8 @@ def compare(self, dygraph_output, static_output, is_backward=False): try: self.paddle_assert_accuracy(dygraph_item, static_item) except Exception as err: - print( - f"[accuracy error] {backward_str}{self.api_config.config}\n{err!s}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + phase = f"{backward_str.strip()} idx={i}" if backward_str else f"idx={i}" + self.report_compare_error(err, phase) return False elif dygraph_output is None and static_output is None: pass diff --git a/tester/paddle_device_vs_cpu.py b/tester/paddle_device_vs_cpu.py index 5e3e39ef..30e58cff 100644 --- a/tester/paddle_device_vs_cpu.py +++ b/tester/paddle_device_vs_cpu.py @@ -3,7 +3,7 @@ import paddle import torch -from .api_config.log_writer import write_to_log +from .api_config.log_writer import has_terminal_log, write_to_log from .base import APITestBase @@ -125,11 +125,8 @@ def _compare_single_tensor(self, cpu_tensor, custom_tensor, tensor_name=""): return True except Exception as err: - error_msg = "[accuracy error]" - if tensor_name: - error_msg += f" {tensor_name}" - error_msg += f"\n{self.api_config.config}\n{err!s}" - print(error_msg, flush=True) + phase = f"compare {tensor_name}" if tensor_name else "compare" + self.report_compare_error(err, phase) return False def compare_outputs(self, cpu_output, custom_output): @@ -355,7 +352,8 @@ def test(self): write_to_log("pass", self.api_config.config) else: print("[Fail]", self.api_config.config, flush=True) - write_to_log("paddle_accuracy", self.api_config.config) + if not has_terminal_log(self.api_config.config): + write_to_log("paddle_accuracy", self.api_config.config) # 生成可复现的单测文件 if self.generate_failed_tests: try: diff --git a/tester/paddle_device_vs_gpu.py b/tester/paddle_device_vs_gpu.py index 202d27f4..57b748a6 100644 --- a/tester/paddle_device_vs_gpu.py +++ b/tester/paddle_device_vs_gpu.py @@ -185,11 +185,9 @@ def _run_paddle(self, device_type: str): return paddle_output, paddle_grads except Exception as e: - print( - f"[paddle {paddle_device_type} error] {self.api_config.config}: {e}", - flush=True, - ) - write_to_log("paddle_error", self.api_config.config) + _, fatal = self.report_runtime_error(e, "paddle_error", f"paddle {paddle_device_type}") + if fatal: + raise return None, None def _compare_with_downloaded(self, local_output, local_grads, downloaded_tensor): @@ -258,11 +256,7 @@ def _compare_with_downloaded(self, local_output, local_grads, downloaded_tensor) flush=True, ) except Exception as e: - print( - f"[compare] Forward accuracy check failed for {self.api_config.config}, error: {e}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(e, "download forward") return False # 对比Backward梯度(如果存在且Forward通过) @@ -306,10 +300,7 @@ def _compare_with_downloaded(self, local_output, local_grads, downloaded_tensor) flush=True, ) except Exception as e: - print( - f"[compare] Backward gradient check failed for {self.api_config.config}, error: {e}", - flush=True, - ) + self.report_compare_error(e, "download backward") return False print( @@ -320,11 +311,7 @@ def _compare_with_downloaded(self, local_output, local_grads, downloaded_tensor) return True except Exception as e: - print( - f"[compare] Comparison failed for {self.api_config.config}, error: {e}", - flush=True, - ) - write_to_log("paddle_accuracy", self.api_config.config) + self.report_compare_error(e, "download compare") return False def test(self): From ffa16477f5265a80ffe7aefa12350e1db8623892 Mon Sep 17 00:00:00 2001 From: cangtianhuang <1903374751@qq.com> Date: Thu, 16 Jul 2026 13:50:17 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20Fix=20numpy=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tester/api_config/config_analyzer.py | 158 ++++++++++++++++----------- 1 file changed, 93 insertions(+), 65 deletions(-) diff --git a/tester/api_config/config_analyzer.py b/tester/api_config/config_analyzer.py index 319eff6e..9da8ebce 100644 --- a/tester/api_config/config_analyzer.py +++ b/tester/api_config/config_analyzer.py @@ -1738,9 +1738,12 @@ def get_padding_offset(bsz, max_seq_len, seq_lens_this_time): vocab_size = numpy.random.randint(10, 1000) if isinstance(weight_config, TensorConfig) and weight_config.shape: vocab_size = weight_config.shape[0] - self.numpy_tensor = numpy.random.randint(0, vocab_size, size=self.shape).astype( - self.dtype - ) + if vocab_size == 0: + self.numpy_tensor = numpy.zeros(self.shape, dtype=self.dtype) + else: + self.numpy_tensor = numpy.random.randint( + 0, vocab_size, size=self.shape + ).astype(self.dtype) elif self.check_arg(api_config, 1, "weight"): if self.dtype.startswith("complex"): real_dtype = "float32" if self.dtype == "complex64" else "float64" @@ -1778,9 +1781,12 @@ def get_padding_offset(bsz, max_seq_len, seq_lens_this_time): soft_labels = soft_labels / soft_labels.sum(axis=1, keepdims=True) self.numpy_tensor = soft_labels.astype(self.dtype) else: - self.numpy_tensor = numpy.random.randint( - 0, num_classes, size=self.shape - ).astype(self.dtype) + if num_classes == 0: + self.numpy_tensor = numpy.zeros(self.shape, dtype=self.dtype) + else: + self.numpy_tensor = numpy.random.randint( + 0, num_classes, size=self.shape + ).astype(self.dtype) elif self.check_arg(api_config, 3, "weight"): self.numpy_tensor = numpy.random.random(size=self.shape) self.numpy_tensor = self.numpy_tensor / self.numpy_tensor.sum() @@ -2635,9 +2641,12 @@ def get_padding_offset(bsz, max_seq_len, seq_lens_this_time): if axis is None: axis = 0 inputs = self.get_arg(api_config, 0, "x") - self.numpy_tensor = numpy.random.randint( - 0, inputs.shape[axis], size=self.shape - ).astype(self.dtype) + if inputs.shape[axis] == 0: + self.numpy_tensor = numpy.zeros(self.shape, dtype=self.dtype) + else: + self.numpy_tensor = numpy.random.randint( + 0, inputs.shape[axis], size=self.shape + ).astype(self.dtype) elif api_config.api_name in {"paddle.Tensor.index_put", "paddle.index_put"}: if self.check_arg(api_config, 1, "indices") and not self.get_arg( @@ -2981,38 +2990,50 @@ def get_exponent_max(value, dtype_max, default_max=5): seqlen, topk = self.shape[0], self.shape[1] # Generate valid routemap vectorized for large seqlen routemap = numpy.full(self.shape, -1, dtype="int32") - # Each row randomly assigns 1~topk experts to random positions - n_assigned = numpy.random.randint(1, topk + 1, size=seqlen) - # For each possible n_assigned value, batch process all rows with that count - for n in range(1, topk + 1): - mask = n_assigned == n - count = int(mask.sum()) - if count == 0: - continue - # Generate random expert indices for these rows - expert_indices = numpy.array( - [ - numpy.random.choice(num_experts, size=n, replace=False) - for _ in range(count) - ], - dtype="int32", - ) - # Generate random positions for these rows - position_indices = numpy.array( - [ - numpy.random.choice(topk, size=n, replace=False) - for _ in range(count) - ], - dtype="int32", - ) - row_indices = numpy.where(mask)[0] - for j in range(n): - routemap[row_indices, position_indices[:, j]] = expert_indices[:, j] - self.numpy_tensor = routemap - # Update tokens_per_expert to match the generated routemap - tokens_count = [int(numpy.sum(routemap == e)) for e in range(num_experts)] - tokens_per_expert = self.get_arg(api_config, 5, "tokens_per_expert") - tokens_per_expert[:] = tokens_count + if topk == 0: + # 0-size topk dimension: routemap stays all -1 + self.numpy_tensor = routemap + tokens_per_expert = self.get_arg(api_config, 5, "tokens_per_expert") + if tokens_per_expert is not None: + tokens_per_expert[:] = [0] * num_experts + else: + # Each row randomly assigns 1~min(topk, num_experts) experts + max_assign = min(topk, num_experts) + n_assigned = numpy.random.randint(1, max_assign + 1, size=seqlen) + # For each possible n_assigned value, batch process all rows with that count + for n in range(1, max_assign + 1): + mask = n_assigned == n + count = int(mask.sum()) + if count == 0: + continue + # Generate random expert indices for these rows + expert_indices = numpy.array( + [ + numpy.random.choice(num_experts, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + # Generate random positions for these rows + position_indices = numpy.array( + [ + numpy.random.choice(topk, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + row_indices = numpy.where(mask)[0] + for j in range(n): + routemap[row_indices, position_indices[:, j]] = expert_indices[ + :, j + ] + self.numpy_tensor = routemap + # Update tokens_per_expert to match the generated routemap + tokens_count = [ + int(numpy.sum(routemap == e)) for e in range(num_experts) + ] + tokens_per_expert = self.get_arg(api_config, 5, "tokens_per_expert") + tokens_per_expert[:] = tokens_count # expert_prob_topk (arg3): float32, shape [seqlen, topk], value in [0, 1] elif self.check_arg(api_config, 3, "expert_prob_topk"): routemap_config = self.get_arg(api_config, 2, "expert_routemap_topk") @@ -3047,30 +3068,37 @@ def get_exponent_max(value, dtype_max, default_max=5): seqlen, topk = self.shape[0], self.shape[1] # Generate valid routemap vectorized for large seqlen routemap = numpy.full(self.shape, -1, dtype="int32") - n_assigned = numpy.random.randint(1, topk + 1, size=seqlen) - for n in range(1, topk + 1): - mask = n_assigned == n - count = int(mask.sum()) - if count == 0: - continue - expert_indices = numpy.array( - [ - numpy.random.choice(num_experts, size=n, replace=False) - for _ in range(count) - ], - dtype="int32", - ) - position_indices = numpy.array( - [ - numpy.random.choice(topk, size=n, replace=False) - for _ in range(count) - ], - dtype="int32", - ) - row_indices = numpy.where(mask)[0] - for j in range(n): - routemap[row_indices, position_indices[:, j]] = expert_indices[:, j] - self.numpy_tensor = routemap + if topk == 0: + # 0-size topk dimension: routemap stays all -1 + self.numpy_tensor = routemap + else: + max_assign = min(topk, num_experts) + n_assigned = numpy.random.randint(1, max_assign + 1, size=seqlen) + for n in range(1, max_assign + 1): + mask = n_assigned == n + count = int(mask.sum()) + if count == 0: + continue + expert_indices = numpy.array( + [ + numpy.random.choice(num_experts, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + position_indices = numpy.array( + [ + numpy.random.choice(topk, size=n, replace=False) + for _ in range(count) + ], + dtype="int32", + ) + row_indices = numpy.where(mask)[0] + for j in range(n): + routemap[row_indices, position_indices[:, j]] = expert_indices[ + :, j + ] + self.numpy_tensor = routemap # zipped_expertwise_rowmap (arg1): int32, shape [seqlen, num_experts] # Needs to be valid rowmap based on routemap elif self.check_arg(api_config, 1, "zipped_expertwise_rowmap"): From 62a6360ec28eeb570c84cf52b8cc01aa489e2f14 Mon Sep 17 00:00:00 2001 From: cangtianhuang <1903374751@qq.com> Date: Thu, 16 Jul 2026 13:51:06 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20Impl=20TE=20moe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tester/paddle_to_torch/rules.py | 171 +++++++++++++++++++++++++++++--- 1 file changed, 155 insertions(+), 16 deletions(-) diff --git a/tester/paddle_to_torch/rules.py b/tester/paddle_to_torch/rules.py index 62be9bd7..a8aa2c63 100644 --- a/tester/paddle_to_torch/rules.py +++ b/tester/paddle_to_torch/rules.py @@ -1906,13 +1906,116 @@ class Fp8QuantBlockwiseRule(BaseRule): Aligns with phi fp8_quant_blockwise kernel: quant_scale = fp8_max / amax (optionally power-of-2), stored scale = 1/quant_scale, quantized = cast(x * quant_scale, float8_e4m3fn). + + Reference implementation is selected via PADDLEAPITEST_FLPGA_IMPL env var: + - "te" (default): Transformer Engine Float8BlockQuantizer + - "torch": Manual torch scatter-op implementation """ def apply(self, paddle_api: str) -> ConvertResult: + import os + defaults_code, _map_code = self.apply_generic() - # Single function body: converter exec uses separate globals/locals, so - # nested defs cannot close over sibling locals. - core = """ + impl = os.environ.get("PADDLEAPITEST_FLPGA_IMPL", "te") + if impl == "torch": + core = self._torch_code() + else: + core = self._te_code() + code = Code( + preprocess=defaults_code, + core=core.splitlines(), + ) + return ConvertResult.success(paddle_api, code, is_torch_corresponding=False) + + @staticmethod + def _te_code() -> str: + return """ +import os as _os +_fp8_max = 448.0 +_eps = float(epsilon) if epsilon is not None else 0.0 +_input_transpose = bool(input_transpose) +_output_scale_transpose = bool(output_scale_transpose) +_return_transpose_only = bool(return_transpose_only) +_using_pow2_scale = bool(using_pow2_scale) +_using_ue8m0_scale = bool(using_ue8m0_scale) +_quant_method = quant_method +if _quant_method not in ("1x128", "128x128"): + raise ValueError(f"Unsupported quantization method: {_quant_method}") +if output_type != "e4m3": + raise ValueError(f"Unsupported output type: {output_type}") + +try: + import transformer_engine.pytorch as _te + from transformer_engine.pytorch.quantization import DType as _teDType +except Exception as _err: + raise RuntimeError( + "PADDLEAPITEST_FLPGA_IMPL=te: cannot import transformer_engine; " + f"error={type(_err).__name__}: {_err}" + ) from _err + +def _te_fp8_quant_blockwise(inp, eps, power2, scale_transpose, ue8m0, method): + import transformer_engine.pytorch as _te + from transformer_engine.pytorch.quantization import DType as _teDType + m, n = inp.shape + _block_dim = 1 if method == "1x128" else 2 + _quantizer = _te.Float8BlockQuantizer( + fp8_dtype=_teDType.kFloat8E4M3, + rowwise=True, + columnwise=False, + force_pow_2_scales=power2, + amax_epsilon=eps, + block_scaling_dim=_block_dim, + ) + _result = _quantizer.quantize(inp.to(torch.bfloat16) if inp.dtype not in (torch.bfloat16, torch.float16, torch.float32) else inp) + q = _result._rowwise_data.view(torch.float8_e4m3fn) + deq_scale = _result._rowwise_scale_inv # float32 + + # Handle scale transpose: + # TE 1D: already (scale_cols, m) = transposed format + # TE 2D: (sm, sn) = non-transposed format + if method == "1x128": + # TE gives (scale_cols, m) which IS the transposed layout + scale = deq_scale if scale_transpose else deq_scale.t().contiguous() + else: + # TE gives (sm, sn) which is NON-transposed layout + scale = deq_scale.t().contiguous() if scale_transpose else deq_scale + + # Handle ue8m0 packing: 4 float32 exponents -> 1 packed int32 + if ue8m0: + base = deq_scale.contiguous() if method == "1x128" else (deq_scale.t().contiguous() if scale_transpose else deq_scale.contiguous()) + # base is in the final layout orientation already; pack along last dim + # Actually re-derive from the non-transposed deq_scale for consistent packing + if method == "1x128": + _base = deq_scale.t().contiguous() # (m, scale_cols) + else: + _base = deq_scale.contiguous() # (sm, sn) + cols_s = _base.shape[-1] + pad_c = (4 - (cols_s % 4)) % 4 + if pad_c: + _base = torch.nn.functional.pad(_base, (0, pad_c)) + b = _base.reshape(*_base.shape[:-1], -1, 4) + safe = torch.clamp(b, min=torch.finfo(torch.float32).tiny) + exp = torch.floor(torch.log2(safe)).to(torch.int32) + 127 + exp = torch.clamp(exp, 0, 255) + packed = (exp[..., 0] | (exp[..., 1] << 8) | (exp[..., 2] << 16) | (exp[..., 3] << 24)).to(torch.int32) + scale = packed.transpose(0, 1).contiguous() if scale_transpose else packed.contiguous() + return q, scale + +if not _input_transpose: + result = _te_fp8_quant_blockwise(x, _eps, _using_pow2_scale, _output_scale_transpose, _using_ue8m0_scale, _quant_method) +else: + x_t = x.transpose(0, 1).contiguous() + q_t, s_t = _te_fp8_quant_blockwise(x_t, _eps, _using_pow2_scale, _output_scale_transpose, _using_ue8m0_scale, _quant_method) + if _return_transpose_only: + result = (q_t, s_t) + else: + q, s = _te_fp8_quant_blockwise(x, _eps, _using_pow2_scale, _output_scale_transpose, _using_ue8m0_scale, _quant_method) + result = (q, s, q_t, s_t) +""" + + @staticmethod + def _torch_code() -> str: + return """ _fp8_max = 448.0 _eps = float(epsilon) if epsilon is not None else 0.0 _input_transpose = bool(input_transpose) @@ -1932,12 +2035,27 @@ def _fp8_quant_blockwise_impl(inp, eps, power2, scale_transpose, ue8m0, method, q = torch.empty((m, n), dtype=torch.float8_e4m3fn, device=inp.device) if method == "1x128": scale_cols = (n + 127) // 128 - scale = torch.empty((scale_cols, m) if scale_transpose else (m, scale_cols), dtype=torch.float32, device=inp.device) else: sm, sn = (m + 127) // 128, (n + 127) // 128 - scale = torch.empty((sn, sm) if scale_transpose else (sm, sn), dtype=torch.float32, device=inp.device) + scale_cols = sn if scale_transpose else sm + # for 128x128: scale shape before transpose is (sm, sn) + scale_rows_128 = sm if ue8m0: - scale = torch.empty_like(scale, dtype=torch.int32) + if method == "1x128": + packed_cols = (scale_cols + 3) // 4 + scale = torch.empty((packed_cols, m) if scale_transpose else (m, packed_cols), dtype=torch.int32, device=inp.device) + else: + packed_sm = (sm + 3) // 4 if not scale_transpose else sm + packed_sn = (sn + 3) // 4 if scale_transpose else sn + if scale_transpose: + scale = torch.empty((packed_sn, sm), dtype=torch.int32, device=inp.device) + else: + scale = torch.empty((sm, packed_sn), dtype=torch.int32, device=inp.device) + else: + if method == "1x128": + scale = torch.empty((scale_cols, m) if scale_transpose else (m, scale_cols), dtype=torch.float32, device=inp.device) + else: + scale = torch.empty((sn, sm) if scale_transpose else (sm, sn), dtype=torch.float32, device=inp.device) return q, scale _need_chunk = (m * n * 4) > (2 * 1024 * 1024 * 1024) @@ -2087,10 +2205,7 @@ def _fp8_quant_blockwise_impl(inp, eps, power2, scale_transpose, ue8m0, method, q, s = _fp8_quant_blockwise_impl(x, _eps, _using_pow2_scale, _output_scale_transpose, _using_ue8m0_scale, _quant_method, _fp8_max) result = (q, s, q_t, s_t) """ - code = Code( - preprocess=defaults_code, - core=core.splitlines(), - ) + return ConvertResult.success(paddle_api, code, is_torch_corresponding=False) return ConvertResult.success(paddle_api, code, is_torch_corresponding=False) @@ -4303,10 +4418,16 @@ def apply(self, paddle_api: str) -> ConvertResult: if scale is None: scale_unzipped = torch.empty(0, dtype=torch.float32, device=_dev) else: - scale_fp = scale.float() if using_ue8m0_scale else scale - scale_unzipped = torch.zeros(total_rows, scale_fp.shape[1], dtype=scale_fp.dtype, device=_dev) - _valid_rows_mask = (_gather_src >= 0) - scale_unzipped[_valid_rows_mask] = scale_fp[_gather_src[_valid_rows_mask]] + if using_ue8m0_scale: + # ue8m0 scale is int32 packed; just scatter the packed rows as-is + scale_unzipped = torch.zeros(total_rows, scale.shape[1], dtype=scale.dtype, device=_dev) + _valid_rows_mask = (_gather_src >= 0) + scale_unzipped[_valid_rows_mask] = scale[_gather_src[_valid_rows_mask]] + else: + scale_fp = scale.float() + scale_unzipped = torch.zeros(total_rows, scale_fp.shape[1], dtype=scale_fp.dtype, device=_dev) + _valid_rows_mask = (_gather_src >= 0) + scale_unzipped[_valid_rows_mask] = scale_fp[_gather_src[_valid_rows_mask]] # build hidden_states_unzipped via index_select if do_gather: hidden_states_unzipped = torch.zeros(total_rows, token_dim, dtype=hidden_states.dtype, device=_dev) @@ -7931,7 +8052,19 @@ def apply(self, paddle_api: str) -> ConvertResult: _scale_out[_rs:_re, _cb] = _inv_scale del _act - result = [_output_fp8, _scale_out] + if _use_ue8m0: + # Pack 4 exponent columns into 1 int32 (ue8m0 format) + _log2_inv = torch.log2(_scale_out).round().to(torch.int32) + 127 + _log2_inv = _log2_inv.clamp(0, 254) + _pack_cols = _log2_inv.shape[-1] + _pad_c = (4 - (_pack_cols % 4)) % 4 + if _pad_c: + _log2_inv = torch.nn.functional.pad(_log2_inv, (0, _pad_c)) + _log2_inv = _log2_inv.reshape(_log2_inv.shape[0], -1, 4) + _scale_packed = (_log2_inv[..., 0] | (_log2_inv[..., 1] << 8) | (_log2_inv[..., 2] << 16) | (_log2_inv[..., 3] << 24)).to(torch.int32) + result = [_output_fp8, _scale_packed] + else: + result = [_output_fp8, _scale_out] elif op_name in ("fuse_stack_fp8_quant", "fuse_stack_transpose_fp8_quant"): # fuse_stack[_transpose]_fp8_quant(X_list, using_pow2_scaling, using_ue8m0_scale, output_scale_transpose) @@ -7984,9 +8117,15 @@ def apply(self, paddle_api: str) -> ConvertResult: _log2_inv = torch.log2(_inv_scale).round().to(torch.int32) + 127 _log2_inv = _log2_inv.clamp(0, 254) _scale_out = _log2_inv.unsqueeze(1).expand(-1, _TILE, -1).reshape(_out_rows, _num_col_blocks) + # Pack 4 exponent columns into 1 int32 (ue8m0 format) + _pack_cols = _scale_out.shape[-1] + _pad_c = (4 - (_pack_cols % 4)) % 4 + if _pad_c: + _scale_out = torch.nn.functional.pad(_scale_out, (0, _pad_c)) + _scale_out = _scale_out.reshape(_scale_out.shape[0], -1, 4) + _scale_out = (_scale_out[..., 0] | (_scale_out[..., 1] << 8) | (_scale_out[..., 2] << 16) | (_scale_out[..., 3] << 24)).to(torch.int32) if _output_scale_transpose: _scale_out = _scale_out.t().contiguous() - _scale_out = _scale_out.to(torch.int32) else: _scale_out = _inv_scale if _output_scale_transpose: From 6520810df538e1eb1d71891fc30916bf9bfbcc17 Mon Sep 17 00:00:00 2001 From: cangtianhuang <1903374751@qq.com> Date: Thu, 16 Jul 2026 15:31:38 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=9A=B8=20Rename=20PADDLEAPITEST=5FIMP?= =?UTF-8?q?L?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tester/paddle_to_torch/rules.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/tester/paddle_to_torch/rules.py b/tester/paddle_to_torch/rules.py index a8aa2c63..49fd3d3c 100644 --- a/tester/paddle_to_torch/rules.py +++ b/tester/paddle_to_torch/rules.py @@ -1907,7 +1907,7 @@ class Fp8QuantBlockwiseRule(BaseRule): quant_scale = fp8_max / amax (optionally power-of-2), stored scale = 1/quant_scale, quantized = cast(x * quant_scale, float8_e4m3fn). - Reference implementation is selected via PADDLEAPITEST_FLPGA_IMPL env var: + Reference implementation is selected via PADDLEAPITEST_IMPL env var: - "te" (default): Transformer Engine Float8BlockQuantizer - "torch": Manual torch scatter-op implementation """ @@ -1916,7 +1916,7 @@ def apply(self, paddle_api: str) -> ConvertResult: import os defaults_code, _map_code = self.apply_generic() - impl = os.environ.get("PADDLEAPITEST_FLPGA_IMPL", "te") + impl = os.environ.get("PADDLEAPITEST_IMPL", "te") if impl == "torch": core = self._torch_code() else: @@ -1949,7 +1949,7 @@ def _te_code() -> str: from transformer_engine.pytorch.quantization import DType as _teDType except Exception as _err: raise RuntimeError( - "PADDLEAPITEST_FLPGA_IMPL=te: cannot import transformer_engine; " + "PADDLEAPITEST_IMPL=te: cannot import transformer_engine; " f"error={type(_err).__name__}: {_err}" ) from _err @@ -1957,6 +1957,27 @@ def _te_fp8_quant_blockwise(inp, eps, power2, scale_transpose, ue8m0, method): import transformer_engine.pytorch as _te from transformer_engine.pytorch.quantization import DType as _teDType m, n = inp.shape + if inp.numel() == 0: + q = torch.empty((m, n), dtype=torch.float8_e4m3fn, device=inp.device) + if method == "1x128": + scale_cols = (n + 127) // 128 + if ue8m0: + packed_cols = (scale_cols + 3) // 4 + scale_shape = (packed_cols, m) if scale_transpose else (m, packed_cols) + scale = torch.empty(scale_shape, dtype=torch.int32, device=inp.device) + else: + scale_shape = (scale_cols, m) if scale_transpose else (m, scale_cols) + scale = torch.empty(scale_shape, dtype=torch.float32, device=inp.device) + else: + sm, sn = (m + 127) // 128, (n + 127) // 128 + if ue8m0: + packed_sn = (sn + 3) // 4 + scale_shape = (packed_sn, sm) if scale_transpose else (sm, packed_sn) + scale = torch.empty(scale_shape, dtype=torch.int32, device=inp.device) + else: + scale_shape = (sn, sm) if scale_transpose else (sm, sn) + scale = torch.empty(scale_shape, dtype=torch.float32, device=inp.device) + return q, scale _block_dim = 1 if method == "1x128" else 2 _quantizer = _te.Float8BlockQuantizer( fp8_dtype=_teDType.kFloat8E4M3,