Files
paddlepaddle--paddle/test/legacy_test/test_fp8_quant.py
T
2026-07-13 12:40:42 +08:00

310 lines
11 KiB
Python

# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import itertools
import unittest
import numpy as np
import paddle
from paddle.incubate.nn.functional import fp8
class TestFP8Quantization(unittest.TestCase):
def setUp(self):
paddle.seed(42)
self.m = 32768
self.n = 7168
self.x = paddle.randn((self.m, self.n), dtype=paddle.bfloat16)
self.rmse_threshold = 3e-2
self.quant_method_options = ["1x128", "128x128"]
self.input_transpose_options = [True] # return non-transpose afterall
self.output_scale_transpose_options = [True, False]
self.return_transpose_only_options = [True, False]
self.using_pow2_scale_options = [True, False]
self.using_ue8m0_scale_options = [True, False]
def cal_all_rmse(self, x, x_qdq, transposed: bool):
if transposed:
diff_squared = (x_qdq.T - x.to(paddle.float32)) ** 2
else:
diff_squared = (x_qdq - x.to(paddle.float32)) ** 2
rmse = paddle.sqrt(paddle.sum(diff_squared) / x.numel())
return rmse
def quant_verify_wrapper(
self,
x: paddle.Tensor,
quant_method: str = "1x128",
input_transpose: bool = False,
output_scale_transpose: bool = False,
return_transpose_only: bool = False,
using_pow2_scale=True,
using_ue8m0_scale=False,
):
x = x.contiguous()
x_q_valid = False
x_t_q_valid = False
if input_transpose:
if return_transpose_only:
x_t_q, scale_t = fp8.fp8_quant_blockwise(
x,
quant_method=quant_method,
input_transpose=input_transpose,
output_scale_transpose=output_scale_transpose,
using_pow2_scale=using_pow2_scale,
return_transpose_only=return_transpose_only,
using_ue8m0_scale=using_ue8m0_scale,
)
x_t_q_valid = True
else:
x_q, scale, x_t_q, scale_t = fp8.fp8_quant_blockwise(
x,
quant_method=quant_method,
input_transpose=input_transpose,
output_scale_transpose=output_scale_transpose,
using_pow2_scale=using_pow2_scale,
return_transpose_only=return_transpose_only,
using_ue8m0_scale=using_ue8m0_scale,
)
x_t_q_valid = True
x_q_valid = True
else:
x_q, scale = fp8.fp8_quant_blockwise(
x,
quant_method=quant_method,
input_transpose=input_transpose,
output_scale_transpose=output_scale_transpose,
using_pow2_scale=using_pow2_scale,
return_transpose_only=return_transpose_only,
using_ue8m0_scale=using_ue8m0_scale,
)
x_q_valid = True
valid_test_list = []
if x_q_valid:
valid_test_list.append((False, x_q, scale))
if x_t_q_valid:
valid_test_list.append((True, x_t_q, scale_t))
rmse = 0
for verify_transpose, x_q_in, scale_in in valid_test_list:
scale_in = scale_in.T if output_scale_transpose else scale_in
if using_ue8m0_scale:
# scale_in is int32 tensor packed with 4 float scales.
# Explicitly cast to int32 to ensure correct unpacking behavior (4 bytes per element)
# Ensure contiguous memory layout for view operation
scale_np = np.ascontiguousarray(scale_in.numpy()).astype(
np.int32
)
# Unpack: (M, N/4) int32 -> (M, N) uint8
scale_u8 = scale_np.view(np.uint8)
# Recover scale value: 2^(exponent - 127)
scale_float = 2.0 ** (scale_u8.astype(np.float32) - 127)
scale_in = paddle.to_tensor(scale_float)
scale_in = paddle.repeat_interleave(
(
paddle.repeat_interleave(scale_in, repeats=128, axis=0)
if quant_method == "128x128" and not using_ue8m0_scale
else scale_in
),
repeats=128,
axis=1,
)
scale_in = scale_in[: x_q_in.shape[0], : x_q_in.shape[1]]
self.assertEqual(scale_in.shape, x_q_in.shape)
x_qdq = x_q_in.astype('float32') * scale_in
rmse = rmse + self.cal_all_rmse(x, x_qdq, verify_transpose) / len(
valid_test_list
)
return rmse
def eval_all(
self,
x: paddle.Tensor,
):
rmses = []
for (
quant_method,
input_transpose,
output_scale_transpose,
using_pow2_scale,
return_transpose_only,
using_ue8m0_scale,
) in itertools.product(
self.quant_method_options,
self.input_transpose_options,
self.output_scale_transpose_options,
self.using_pow2_scale_options,
self.return_transpose_only_options,
self.using_ue8m0_scale_options,
):
rmse = self.quant_verify_wrapper(
x,
quant_method=quant_method,
input_transpose=input_transpose,
output_scale_transpose=output_scale_transpose,
return_transpose_only=return_transpose_only,
using_pow2_scale=using_pow2_scale,
using_ue8m0_scale=using_ue8m0_scale,
)
self.assertLessEqual(rmse, self.rmse_threshold)
rmses.append(rmse)
return rmses
def test_tensor_shapes(self):
self.assertEqual(self.x.shape, [self.m, self.n])
self.assertEqual(self.x.dtype, paddle.bfloat16)
def test_quantization_accuracy(self):
rmses = self.eval_all(self.x)
for r in rmses:
self.assertLessEqual(r, self.rmse_threshold)
def test_quantization_consistency(self):
rmses1 = self.eval_all(self.x)
rmses2 = self.eval_all(self.x)
for r1, r2 in zip(rmses1, rmses1):
self.assertEqual(r1, r2)
class TestFP8QuantizationFP16(TestFP8Quantization):
def setUp(self):
paddle.seed(42)
self.m = 128 * 12
self.n = 4096
self.x = paddle.randn((self.m, self.n), dtype=paddle.float16)
self.rmse_threshold = 3e-2
self.quant_method_options = ["1x128", "128x128"]
self.input_transpose_options = [True] # return non-transpose afterall
self.output_scale_transpose_options = [True, False]
self.return_transpose_only_options = [True, False]
self.using_pow2_scale_options = [True, False]
self.using_ue8m0_scale_options = [True, False]
def test_quantization_accuracy(self):
rmses = self.eval_all(self.x)
for r in rmses:
self.assertLessEqual(r, self.rmse_threshold)
def test_tensor_shapes(self):
self.assertEqual(self.x.shape, [self.m, self.n])
self.assertEqual(self.x.dtype, paddle.float16)
class TestFP8QuantizationUnalignedBF16(TestFP8Quantization):
def setUp(self):
paddle.seed(42)
self.m = 80
self.n = 4096
self.dtype_options = paddle.bfloat16
self.quant_method_options = ["1x128"]
self.rmse_threshold = 3e-2
self.using_ue8m0_scale_options = [True, False]
self.x = paddle.randn((self.m, self.n), dtype=self.dtype_options)
self.input_transpose_options = [False]
self.output_scale_transpose_options = [True, False]
self.return_transpose_only_options = [False]
self.using_pow2_scale_options = [True, False]
def test_quantization_accuracy(self):
rmses = self.eval_all(self.x)
for r in rmses:
self.assertLessEqual(r, self.rmse_threshold)
class TestFP8QuantizationUnalignedFP16(TestFP8Quantization):
def setUp(self):
paddle.seed(42)
self.m = 8184
self.n = 2560
self.dtype_options = paddle.float16
self.quant_method_options = ["1x128"]
self.rmse_threshold = 3e-2
self.x = paddle.randn((self.m, self.n), dtype=self.dtype_options)
self.input_transpose_options = [False]
self.output_scale_transpose_options = [True, False]
self.return_transpose_only_options = [False]
self.using_pow2_scale_options = [True, False]
self.using_ue8m0_scale_options = [True, False]
def test_quantization_accuracy(self):
rmses = self.eval_all(self.x)
for r in rmses:
self.assertLessEqual(r, self.rmse_threshold)
def test_tensor_shapes(self):
self.assertEqual(self.x.shape, [self.m, self.n])
self.assertEqual(self.x.dtype, paddle.float16)
class TestFP8QuantizatioUnalignedNBF16(TestFP8Quantization):
def setUp(self):
paddle.seed(42)
self.m = 129
self.n = 508
self.dtype_options = paddle.bfloat16
self.quant_method_options = ["1x128"]
self.rmse_threshold = 3e-2
self.x = paddle.randn((self.m, self.n), dtype=self.dtype_options)
self.input_transpose_options = [False]
self.return_transpose_only_options = [False]
self.output_scale_transpose_options = [True, False]
self.using_pow2_scale_options = [True, False]
self.using_ue8m0_scale_options = [True, False]
def test_quantization_accuracy(self):
rmses = self.eval_all(self.x)
for r in rmses:
self.assertLessEqual(r, self.rmse_threshold)
# 0 size
class TestFP8QuantizationZeroSizeBF16(unittest.TestCase):
def setUp(self):
paddle.seed(42)
self.m = 0
self.n = 0
self.dtype_options = paddle.bfloat16
self.x = paddle.randn((self.m, self.n), dtype=self.dtype_options)
def test_fp8_quant_zero_size_tensor(self):
x_q, scale = fp8.fp8_quant_blockwise(
self.x,
quant_method="1x128",
input_transpose=False,
output_scale_transpose=False,
using_pow2_scale=False,
return_transpose_only=False,
using_ue8m0_scale=False,
)
self.assertEqual(x_q.shape, [0, 0])
self.assertEqual(x_q.dtype, paddle.float8_e4m3fn)
self.assertEqual(scale.shape, [0, 0])
self.assertEqual(scale.dtype, paddle.float32)
if __name__ == '__main__':
unittest.main()