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

520 lines
21 KiB
Python

# Copyright (c) 2024 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.
# [AUTO-GENERATED]
# Target file: python/paddle/nn/quant/quantized_linear.py
# Coverage target: weight_quantize, weight_dequantize, weight_only_linear,
# llm_int8_linear, apply_per_channel_scale
# 未覆盖行: static graph branches, unsupported arch assertions
import unittest
import numpy as np
import paddle
from paddle.nn.quant.quantized_linear import (
_get_arch_info,
apply_per_channel_scale,
llm_int8_linear,
weight_dequantize,
weight_only_linear,
weight_quantize,
)
class TestGetArchInfo(unittest.TestCase):
"""Test _get_arch_info helper function.
测试 _get_arch_info 辅助函数。"""
def test_returns_int_on_cuda(self):
"""_get_arch_info should return an int on CUDA.
在 CUDA 上 _get_arch_info 应返回整数。"""
try:
arch = _get_arch_info()
self.assertIsInstance(arch, int)
except (ValueError, RuntimeError):
# Expected when CUDA is not available
pass
def test_returns_zero_on_cpu(self):
"""_get_arch_info returns 0 when CUDA is not compiled.
当未编译 CUDA 时 _get_arch_info 返回 0。"""
if not paddle.is_compiled_with_cuda():
arch = _get_arch_info()
self.assertEqual(arch, 0)
class TestWeightQuantize(unittest.TestCase):
"""Test weight_quantize function.
测试 weight_quantize 函数。"""
def setUp(self):
paddle.disable_static()
@unittest.skipIf(
not paddle.is_compiled_with_cuda(), "CUDA required for weight_quantize"
)
def test_weight_quantize_int8(self):
"""Test weight_quantize with weight_only_int8 algo.
测试 weight_only_int8 算法的 weight_quantize。"""
try:
x = paddle.randn([64, 32], dtype=paddle.float16)
out, scale = weight_quantize(x, algo="weight_only_int8")
self.assertEqual(out.dtype, paddle.int8)
self.assertEqual(scale.dtype, paddle.float32)
# Output shape is transposed
self.assertEqual(out.shape, [32, 64])
self.assertEqual(scale.shape[0], 32)
except (AssertionError, RuntimeError) as e:
# May fail on unsupported architectures
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(), "CUDA required for weight_quantize"
)
def test_weight_quantize_int4(self):
"""Test weight_quantize with weight_only_int4 algo.
测试 weight_only_int4 算法的 weight_quantize。"""
try:
x = paddle.randn([64, 32], dtype=paddle.float16)
out, scale = weight_quantize(x, algo="weight_only_int4")
self.assertEqual(out.dtype, paddle.int8)
self.assertEqual(scale.dtype, paddle.float32)
self.assertEqual(out.shape, [32, 64])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(), "CUDA required for weight_quantize"
)
def test_weight_quantize_llm_int8(self):
"""Test weight_quantize with llm.int8 algo.
测试 llm.int8 算法的 weight_quantize。"""
try:
x = paddle.randn([64, 32], dtype=paddle.float16)
out, scale = weight_quantize(x, algo="llm.int8")
self.assertEqual(out.dtype, paddle.int8)
self.assertEqual(scale.dtype, paddle.float32)
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(), "CUDA required for weight_quantize"
)
def test_weight_quantize_with_group_size(self):
"""Test weight_quantize with group_size=128.
测试带有 group_size=128 的 weight_quantize。"""
try:
x = paddle.randn([128, 64], dtype=paddle.float16)
out, scale = weight_quantize(
x, algo="weight_only_int8", group_size=128
)
self.assertEqual(out.dtype, paddle.int8)
self.assertEqual(scale.dtype, paddle.float32)
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(), "CUDA required for weight_quantize"
)
def test_weight_quantize_bfloat16(self):
"""Test weight_quantize with bfloat16 input.
测试 bfloat16 输入的 weight_quantize。"""
try:
x = paddle.randn([64, 32], dtype=paddle.bfloat16)
out, scale = weight_quantize(x, algo="weight_only_int8")
self.assertEqual(out.dtype, paddle.int8)
self.assertEqual(scale.dtype, paddle.float32)
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
def test_weight_quantize_invalid_group_size(self):
"""Test weight_quantize with invalid group_size.
测试无效 group_size 的 weight_quantize。"""
if not paddle.is_compiled_with_cuda():
# On CPU, arch=0 will fail the arch assertion first
try:
x = paddle.randn([64, 32], dtype=paddle.float16)
weight_quantize(x, algo="weight_only_int8", group_size=32)
except AssertionError:
pass # Expected
class TestWeightDequantize(unittest.TestCase):
"""Test weight_dequantize function.
测试 weight_dequantize 函数。"""
def setUp(self):
paddle.disable_static()
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_dequantize",
)
def test_weight_dequantize_basic(self):
"""Test basic weight_dequantize.
测试基本 weight_dequantize。"""
try:
x = paddle.randn([64, 32], dtype=paddle.float16)
q_out, scale = weight_quantize(x, algo="weight_only_int8")
dq_out = weight_dequantize(q_out, scale, algo="weight_only_int8")
self.assertEqual(dq_out.dtype, paddle.float32)
# Output shape should be transposed back
self.assertEqual(dq_out.shape, [64, 32])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_dequantize",
)
def test_weight_dequantize_int4(self):
"""Test weight_dequantize with int4 algo.
测试 int4 算法的 weight_dequantize。"""
try:
x = paddle.randn([64, 32], dtype=paddle.float16)
q_out, scale = weight_quantize(x, algo="weight_only_int4")
dq_out = weight_dequantize(q_out, scale, algo="weight_only_int4")
self.assertEqual(dq_out.shape, [64, 32])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_dequantize",
)
def test_weight_dequantize_with_group_size(self):
"""Test weight_dequantize with group_size.
测试带有 group_size 的 weight_dequantize。"""
try:
x = paddle.randn([128, 64], dtype=paddle.float16)
q_out, scale = weight_quantize(
x, algo="weight_only_int8", group_size=128
)
dq_out = weight_dequantize(
q_out, scale, algo="weight_only_int8", group_size=128
)
self.assertEqual(dq_out.shape, [128, 64])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
def test_weight_dequantize_invalid_group_size(self):
"""Test weight_dequantize with invalid group_size raises AssertionError.
测试无效 group_size 会引发 AssertionError。"""
try:
x = paddle.ones([4, 4], dtype=paddle.int8)
scale = paddle.ones([4], dtype=paddle.float32)
weight_dequantize(x, scale, algo="weight_only_int8", group_size=32)
except AssertionError:
pass # Expected
except RuntimeError:
pass # May also fail on operator level
class TestWeightOnlyLinear(unittest.TestCase):
"""Test weight_only_linear function.
测试 weight_only_linear 函数。"""
def setUp(self):
paddle.disable_static()
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_only_linear",
)
def test_weight_only_linear_basic(self):
"""Test basic weight_only_linear without bias.
测试不带 bias 的基本 weight_only_linear。"""
try:
x = paddle.randn([1, 4, 64], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
out = weight_only_linear(
x, weight, weight_scale=scale, weight_dtype="int8"
)
self.assertEqual(out.shape, [1, 4, 32])
self.assertEqual(out.dtype, paddle.float16)
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_only_linear",
)
def test_weight_only_linear_with_bias(self):
"""Test weight_only_linear with bias.
测试带 bias 的 weight_only_linear。"""
try:
x = paddle.randn([2, 8, 64], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
bias = paddle.randn([32], dtype=paddle.float16)
out = weight_only_linear(
x,
weight,
bias=bias,
weight_scale=scale,
weight_dtype="int8",
)
self.assertEqual(out.shape, [2, 8, 32])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_only_linear",
)
def test_weight_only_linear_int4(self):
"""Test weight_only_linear with int4 weight_dtype.
测试 int4 weight_dtype 的 weight_only_linear。"""
try:
x = paddle.randn([1, 2, 64], dtype=paddle.float16)
weight = paddle.randint(-8, 7, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
out = weight_only_linear(
x, weight, weight_scale=scale, weight_dtype="int4"
)
self.assertEqual(out.shape, [1, 2, 32])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_only_linear",
)
def test_weight_only_linear_with_group_size(self):
"""Test weight_only_linear with group_size.
测试带有 group_size 的 weight_only_linear。"""
try:
x = paddle.randn([1, 4, 128], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [64, 128]).cast(paddle.int8)
scale = paddle.randn([64], dtype=paddle.float32)
out = weight_only_linear(
x,
weight,
weight_scale=scale,
weight_dtype="int8",
group_size=128,
)
self.assertEqual(out.shape, [1, 4, 64])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_only_linear",
)
def test_weight_only_linear_bfloat16(self):
"""Test weight_only_linear with bfloat16 input.
测试 bfloat16 输入的 weight_only_linear。"""
try:
x = paddle.randn([1, 4, 64], dtype=paddle.bfloat16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
out = weight_only_linear(
x, weight, weight_scale=scale, weight_dtype="int8"
)
self.assertEqual(out.shape, [1, 4, 32])
self.assertEqual(out.dtype, paddle.bfloat16)
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for weight_only_linear",
)
def test_weight_only_linear_2d_input(self):
"""Test weight_only_linear with 2D input.
测试二维输入的 weight_only_linear。"""
try:
x = paddle.randn([4, 64], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
out = weight_only_linear(
x, weight, weight_scale=scale, weight_dtype="int8"
)
self.assertEqual(out.shape, [4, 32])
except (AssertionError, RuntimeError, ValueError) as e:
self.skipTest(f"Unsupported arch or CUDA error: {e}")
def _is_ampere_or_above():
"""Check if GPU compute capability >= 8.0 (Ampere+).
llm_int8_linear requires Ampere or newer architecture."""
if not paddle.is_compiled_with_cuda():
return False
try:
arch = _get_arch_info()
return arch >= 80
except (ValueError, RuntimeError):
return False
class TestLlmInt8Linear(unittest.TestCase):
"""Test llm_int8_linear function.
测试 llm_int8_linear 函数。"""
def setUp(self):
paddle.disable_static()
@unittest.skipIf(
not _is_ampere_or_above(),
"llm_int8_linear requires Ampere+ (sm_80), skipped on CI V100 (sm_70)",
)
def test_llm_int8_linear_basic(self):
"""Test basic llm_int8_linear without bias.
测试不带 bias 的基本 llm_int8_linear。"""
try:
x = paddle.randn([1, 4, 64], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
out = llm_int8_linear(x, weight, weight_scale=scale, threshold=6.0)
self.assertEqual(out.shape, [1, 4, 32])
self.assertEqual(out.dtype, paddle.float16)
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
@unittest.skipIf(
not _is_ampere_or_above(),
"llm_int8_linear requires Ampere+ (sm_80), skipped on CI V100 (sm_70)",
)
def test_llm_int8_linear_with_bias(self):
"""Test llm_int8_linear with bias.
测试带 bias 的 llm_int8_linear。"""
try:
x = paddle.randn([1, 4, 64], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
bias = paddle.randn([32], dtype=paddle.float16)
out = llm_int8_linear(
x, weight, bias=bias, weight_scale=scale, threshold=6.0
)
self.assertEqual(out.shape, [1, 4, 32])
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
@unittest.skipIf(
not _is_ampere_or_above(),
"llm_int8_linear requires Ampere+ (sm_80), skipped on CI V100 (sm_70)",
)
def test_llm_int8_linear_different_threshold(self):
"""Test llm_int8_linear with different threshold.
测试不同阈值的 llm_int8_linear。"""
try:
x = paddle.randn([1, 4, 64], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
out = llm_int8_linear(x, weight, weight_scale=scale, threshold=3.0)
self.assertEqual(out.shape, [1, 4, 32])
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
@unittest.skipIf(
not _is_ampere_or_above(),
"llm_int8_linear requires Ampere+ (sm_80), skipped on CI V100 (sm_70)",
)
def test_llm_int8_linear_high_threshold(self):
"""Test llm_int8_linear with high threshold (fewer outliers).
测试高阈值(更少异常值)的 llm_int8_linear。"""
try:
x = paddle.randn([1, 2, 64], dtype=paddle.float16)
weight = paddle.randint(-127, 127, [32, 64]).cast(paddle.int8)
scale = paddle.randn([32], dtype=paddle.float32)
out = llm_int8_linear(x, weight, weight_scale=scale, threshold=10.0)
self.assertEqual(out.shape, [1, 2, 32])
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
class TestApplyPerChannelScale(unittest.TestCase):
"""Test apply_per_channel_scale function.
测试 apply_per_channel_scale 函数。"""
def setUp(self):
paddle.disable_static()
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for apply_per_channel_scale",
)
def test_apply_per_channel_scale_float16(self):
"""Test apply_per_channel_scale with float16 tensors.
测试 float16 张量的 apply_per_channel_scale。"""
try:
x = paddle.randn([64, 32], dtype=paddle.float16)
scales = paddle.randn([32], dtype=paddle.float16)
out = apply_per_channel_scale(x, scales)
self.assertEqual(out.shape, [64, 32])
self.assertEqual(out.dtype, paddle.float16)
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for apply_per_channel_scale",
)
def test_apply_per_channel_scale_bfloat16(self):
"""Test apply_per_channel_scale with bfloat16 tensors.
测试 bfloat16 张量的 apply_per_channel_scale。"""
try:
x = paddle.randn([64, 32], dtype=paddle.bfloat16)
scales = paddle.randn([32], dtype=paddle.bfloat16)
out = apply_per_channel_scale(x, scales)
self.assertEqual(out.shape, [64, 32])
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for apply_per_channel_scale",
)
def test_apply_per_channel_scale_values(self):
"""Test apply_per_channel_scale produces correctly scaled results.
测试 apply_per_channel_scale 产生正确的缩放结果。"""
try:
x = paddle.ones([4, 3], dtype=paddle.float16)
scales = paddle.to_tensor([2.0, 3.0, 4.0], dtype=paddle.float16)
out = apply_per_channel_scale(x, scales)
expected = paddle.to_tensor(
[[2.0, 3.0, 4.0]] * 4, dtype=paddle.float16
)
np.testing.assert_array_almost_equal(
out.cpu().numpy(), expected.cpu().numpy(), decimal=3
)
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
@unittest.skipIf(
not paddle.is_compiled_with_cuda(),
"CUDA required for apply_per_channel_scale",
)
def test_apply_per_channel_scale_3d(self):
"""Test apply_per_channel_scale requires 2D input (3D raises error).
测试 apply_per_channel_scale 需要 2D 输入(3D 会抛出异常)。"""
try:
x = paddle.randn([8, 16, 32], dtype=paddle.float16)
scales = paddle.randn([32], dtype=paddle.float16)
out = apply_per_channel_scale(x, scales)
# If it works, verify output
self.assertIsNotNone(out)
except ValueError:
# Expected: apply_per_channel_scale requires 2D input
pass
except (RuntimeError, AssertionError) as e:
self.skipTest(f"CUDA error: {e}")
if __name__ == "__main__":
unittest.main()