520 lines
21 KiB
Python
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()
|