已合并
【feature】新增适配QuantMatmulGelu融合算子,支持A4W4/A8W8输入 #4186
【feature】新增适配QuantMatmulGelu融合算子,支持A4W4/A8W8输入 #4186
已合并
Sunshine_Youngster创建于 2月3日
11 个文件变更+1286-1
Mcodegen/templates/_op_plugin_docs.py+162-0
@@ -4476,6 +4476,168 @@ else:
4476"""4476"""
4477)4477)
4478 4478 
4479+_add_torch_npu_docstr(
4480+ "npu_quant_matmul_gelu",
4481+ """
4482+功能描述:
4483+完成量化矩阵乘和GELU激活函数的融合计算, 支持A8W8和A4W4量化. 该接口融合了量化矩阵乘和GELU激活, 减少内存访问, 提升性能.
4484+ 
4485+接口原型:
4486+torch_npu.npu_quant_matmul_gelu(Tensor x1, Tensor x2, Tensor x1_scale, Tensor x2_scale, *, Tensor? bias=None, str? approximate="gelu_erf") -> Tensor
4487+ 
4488+参数说明:
4489+x1: Tensor类型, 输入激活值, 数据格式支持ND, shape需要在2-6维范围.
4490+数据类型支持int8(A8W8量化)、int32(A4W4量化, 每个int32数据存放8个int4数据)、quint4x2(A4W4量化, 直接INT4类型).
4491+x2: Tensor类型(权重), 数据格式支持ND或NZ, shape需要在2-6维范围.
4492+数据类型与x1的数据类型保持一致. 支持昇腾亲和的NZ数据排布格式, 可通过torch_npu.npu_format_cast转换为NZ格式以提升性能(仅A8W8场景).
4493+x1_scale: Tensor类型, x1的量化scale参数, 数据格式支持ND.
4494+数据类型支持float32. shape需要是1维(m,), 其中m与x1的m一致. 采用per-token量化方式, 每个token(行)有一个独立的scale值.
4495+x2_scale: Tensor类型, x2的量化scale参数, 数据格式支持ND.
4496+数据类型支持float32或bfloat16. shape需要是1维(n,)或(1,), 其中n与x2的n一致. 采用per-channel量化方式, 每个输出通道有一个独立的scale值, 或使用per-tensor量化(shape为(1,)).
4497+bias: Tensor类型, 可选参数, 默认值为None, 偏置项, 数据格式支持ND.
4498+数据类型支持int32、float32、bfloat16、float16.
4499+A4W4量化场景下: shape仅支持1维(n,), n与x2的n一致.
4500+A8W8量化场景下: shape支持1维(n,)或3维(batch, 1, n), n与x2的n一致.
4501+approximate: str类型, 可选参数, 默认值为"gelu_erf". 指定GELU激活函数的类型.
4502+支持"gelu_tanh"(GELU的tanh近似版本)和"gelu_erf"(GELU的erf精确版本).
4503+ 
4504+输出说明:
4505+result: Tensor类型, 代表量化矩阵乘融合GELU激活的计算结果.
4506+如果x2_scale的数据类型为float32, 输出的数据类型为float16.
4507+如果x2_scale的数据类型为bfloat16, 输出的数据类型为bfloat16.
4508+输出shape为(batch, m, n), 其中batch根据x1和x2的batch维度广播得到.
4509+ 
4510+约束说明:
4511+该接口支持推理场景下使用.
4512+x1、x2、x1_scale、x2_scale不能为空.
4513+x1、x2的数据类型和数据格式需要在支持的范围之内.
4514+x1、x2最后一维的shape大小不能超过65535.
4515+approximate必须为"gelu_tanh""gelu_erf".
4516+对于A4W4量化(INT4/INT32类型输入):
4517+ A4W4量化场景支持两种输入类型: quint4x2(直接INT4类型)和int32(打包存储, 每个int32数据存放8个int4数据).
4518+ x1和x2的内轴(k轴)必须为偶数.
4519+ 当x2为int32类型时, x2的shape为(k, n//8), n必须是8的倍数.
4520+ 当x2为quint4x2类型时, x2的shape为(k, n), n必须是8的倍数.
4521+ A4W4量化仅支持ND格式, 不支持NZ格式.
4522+ 转置信息由算子内部根据tensor的stride自动推导, 无需手动指定.
4523+对于A8W8量化:
4524+ 支持ND格式和NZ格式.
4525+ 当x2为ND格式时, 自动调用aclnnFusedQuantMatmul接口.
4526+ 当x2为NZ格式时, 自动调用aclnnFusedQuantMatmulWeightNz接口.
4527+ 如果需要使用NZ格式以提升性能, 可以手动调用torch_npu.npu_format_cast完成输入x2(weight)的NZ格式转换.
4528+ 转置信息由算子内部根据tensor的stride自动推导, 无需手动指定.
4529+ 
4530+支持的PyTorch版本
4531+PyTorch 2.10
4532+PyTorch 2.9
4533+PyTorch 2.8
4534+PyTorch 2.7
4535+PyTorch 2.6
4536+ 
4537+支持的型号:
4538+Atlas A2 训练系列产品/Atlas 800I A2 推理产品
4539+ 
4540+调用示例:
4541+单算子调用(A8W8量化, ND格式, gelu_tanh激活)
4542+import torch
4543+import torch_npu
4544+ 
4545+# 准备输入数据
4546+m, k, n = 128, 256, 512
4547+x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
4548+x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
4549+x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
4550+x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
4551+ 
4552+# 调用融合算子
4553+output = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
4554+print(output.shape) # torch.Size([128, 512])
4555+print(output.dtype) # torch.float16
4556+ 
4557+单算子调用(A8W8量化, ND格式, gelu_erf激活, 带bias)
4558+import torch
4559+import torch_npu
4560+ 
4561+m, k, n = 128, 256, 512
4562+x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
4563+x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
4564+x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
4565+x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
4566+bias = torch.randn(n, dtype=torch.float32).npu() * 0.1
4567+ 
4568+# 使用gelu_erf激活并添加bias
4569+output = torch_npu.npu_quant_matmul_gelu(
4570+ x1, x2, x1_scale, x2_scale, bias=bias, approximate="gelu_erf"
4571+)
4572+ 
4573+单算子调用(A8W8量化, NZ格式, gelu_tanh激活)
4574+import torch
4575+import torch_npu
4576+ 
4577+m, k, n = 128, 256, 512
4578+x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
4579+x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
4580+ 
4581+# 将x2转换为NZ格式以提升性能
4582+x2_nz = torch_npu.npu_format_cast(x2.contiguous(), 29) # 29为ACL_FORMAT_FRACTAL_NZ
4583+ 
4584+x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
4585+x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
4586+ 
4587+# 自动识别NZ格式并调用对应接口
4588+output = torch_npu.npu_quant_matmul_gelu(x1, x2_nz, x1_scale, x2_scale, approximate="gelu_tanh")
4589+ 
4590+单算子调用(A8W8量化, BF16输出)
4591+import torch
4592+import torch_npu
4593+ 
4594+m, k, n = 64, 128, 256
4595+x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
4596+x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
4597+x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
4598+x2_scale = torch.randn(n, dtype=torch.bfloat16).abs().npu() * 0.01 # BF16 scale
4599+ 
4600+# 输出数据类型由x2_scale的类型决定, 此处输出为bfloat16
4601+output = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
4602+print(output.dtype) # torch.bfloat16
4603+ 
4604+单算子调用(A4W4量化)
4605+import torch
4606+import torch_npu
4607+ 
4608+m, k, n = 128, 256, 512
4609+# 生成INT4数据(以INT32格式存储)
4610+x1_fp = torch.randn(m, k, dtype=torch.float32).npu()
4611+x2_fp = torch.randn(k, n, dtype=torch.float32).npu()
4612+ 
4613+# 量化为INT4
4614+scale_tmp = torch.ones(1, dtype=torch.float32).npu()
4615+x1 = torch_npu.npu_quantize(x1_fp, scale_tmp, None, torch.quint4x2, -1, False)
4616+x2 = torch_npu.npu_quantize(x2_fp, scale_tmp, None, torch.quint4x2, -1, False)
4617+ 
4618+x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
4619+x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
4620+ 
4621+# A4W4量化仅支持ND格式, 不支持NZ格式
4622+output = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
4623+ 
4624+单算子调用(使用默认approximate="gelu_erf")
4625+import torch
4626+import torch_npu
4627+ 
4628+m, k, n = 64, 128, 256
4629+x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
4630+x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
4631+x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
4632+x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
4633+ 
4634+# 不指定approximate参数, 使用默认值"gelu_erf"
4635+output = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale)
4636+print(output.dtype) # torch.float16
4637+ 
4638+"""
4639+)
4640+ 
4479_add_torch_npu_docstr(4641_add_torch_npu_docstr(
4480 "npu_weight_quant_batchmatmul",4642 "npu_weight_quant_batchmatmul",
4481 """4643 """
Mdocs/zh/custom_APIs/menu_Pytorch_API.md+1-0
@@ -108,6 +108,7 @@
108 - [torch_npu.npu_prompt_flash_attention](./torch_npu/torch_npu-npu_prompt_flash_attention.md)108 - [torch_npu.npu_prompt_flash_attention](./torch_npu/torch_npu-npu_prompt_flash_attention.md)
109 - [torch_npu.npu_quant_lightning_indexer](./torch_npu/torch_npu-npu_quant_lightning_indexer.md)109 - [torch_npu.npu_quant_lightning_indexer](./torch_npu/torch_npu-npu_quant_lightning_indexer.md)
110 - [torch_npu.npu_quant_matmul](./torch_npu/torch_npu-npu_quant_matmul.md)110 - [torch_npu.npu_quant_matmul](./torch_npu/torch_npu-npu_quant_matmul.md)
111+ - [torch_npu.npu_quant_matmul_gelu](./torch_npu/torch_npu-npu_quant_matmul_gelu.md)
111 - [torch_npu.npu_quant_matmul_reduce_sum](./torch_npu/torch_npu-npu_quant_matmul_reduce_sum.md)112 - [torch_npu.npu_quant_matmul_reduce_sum](./torch_npu/torch_npu-npu_quant_matmul_reduce_sum.md)
112 - [torch_npu.npu_quant_scatter](./torch_npu/torch_npu-npu_quant_scatter.md)113 - [torch_npu.npu_quant_scatter](./torch_npu/torch_npu-npu_quant_scatter.md)
113 - [torch_npu.npu_quant_scatter_](./torch_npu/torch_npu-npu_quant_scatter_.md)114 - [torch_npu.npu_quant_scatter_](./torch_npu/torch_npu-npu_quant_scatter_.md)
Adocs/zh/custom_APIs/torch_npu/torch_npu-npu_quant_matmul_gelu.md+293-0
@@ -0,0 +1,293 @@
1+# torch_npu.npu_quant_matmul_gelu
2+ 
3+## 产品支持情况
4+ 
5+| 产品 | 是否支持 |
6+|-----------------------------------------------|:----:|
7+| <term>Atlas A3 训练系列产品/Atlas A3 推理系列产品</term> | √ |
8+| <term>Atlas A2 训练系列产品/Atlas A2 推理系列产品</term> | √ |
9+ 
10+## 功能说明
11+ 
12+- API功能:完成量化的矩阵乘和GELU激活函数的融合计算,支持A8W8和A4W4量化。该接口融合了量化矩阵乘和GELU激活,减少内存访问,提升性能。
13+ 
14+- 计算公式:
15+ 
16+ - x1Scale, bias int32(此场景无offset):
17+ 
18+ $$
19+ qbmmout = (x1@x2 + bias) * x2Scale * x1Scale
20+ $$
21+ 
22+ - x1Scale, bias BFLOAT16/FLOAT16/FLOAT32(此场景无offset):
23+ 
24+ $$
25+ qbmmout = x1@x2 * x2scale * x1Scale + bias
26+ $$
27+ 
28+ - x1Scale无bias:
29+ 
30+ $$
31+ qbmmout = x1@x2 * x2Scale * x1Scale
32+ $$
33+ 
34+ - gelu类型由approximate输入指定,默认为"gelu_erf",支持如下:
35+ 
36+ - gelu_tanh运算:
37+ 
38+ $$
39+ out = gelu\_tanh(qbmmout)
40+ $$
41+ 
42+ - gelu_erf运算:
43+ 
44+ $$
45+ out = gelu\_erf(qbmmout)
46+ $$
47+ 
48+## 函数原型
49+ 
50+```
51+npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, *, bias=None, approximate="gelu_erf") -> Tensor
52+```
53+ 
54+## 参数说明
55+ 
56+- **x1** (`Tensor`):必选参数,输入张量,表示矩阵乘法中的左矩阵(激活值),数据格式支持$ND$,shape需要在2-6维范围。
57+ - 数据类型支持`int8`(A8W8量化)、`int32`(A4W4量化,每个`int32`数据存放8个`int4`数据)和`int4`(A4W4量化,直接int4类型)。
58+ 
59+- **x2** (`Tensor`):必选参数,输入张量,表示矩阵乘法中的右矩阵(权重),其与`x1`的数据类型须保持一致。数据格式支持$ND$或$NZ$(昇腾亲和排布格式),shape需要在2-6维范围。
60+ - 数据类型支持`int8`(A8W8量化)、`int32`(A4W4量化,每个`int32`数据存放8个`int4`数据)和`int4`(A4W4量化,直接int4类型)。
61+ - 支持昇腾亲和的NZ数据排布格式,可通过`torch_npu.npu_format_cast`转换为NZ格式以提升性能(仅A8W8场景)。
62+ 
63+- **x1_scale** (`Tensor`):必选参数,`x1`的量化缩放因子,数据格式支持$ND$。
64+ - 数据类型支持`float32`
65+ - shape需要是1维$(m,)$,其中$m$与`x1`的$m$一致。采用per-token量化方式,每个token(行)有一个独立的scale值。
66+ 
67+- **x2_scale** (`Tensor`):必选参数,`x2`的量化缩放因子,数据格式支持$ND$。
68+ - 数据类型支持`float32``bfloat16`
69+ - shape需要是1维$(n,)$或$(1,)$,其中$n$与`x2`的$n$一致。采用per-channel量化方式,每个输出通道有一个独立的scale值,或使用per-tensor量化(shape为$(1,)$)。
70+ 
71+- <strong>*</strong>:必选参数,代表其之前的变量是位置相关的,必须按照顺序输入;之后的变量是可选参数,位置无关,需要使用键值对赋值,不赋值会使用默认值。
72+ 
73+- **bias** (`Tensor`):可选参数,默认值为`None`,偏置项,数据格式支持$ND$。
74+ - 数据类型支持`int32``float32``bfloat16``float16`
75+ - A4W4量化场景下:shape仅支持1维$(n,)$,$n$与`x2`的$n$一致。
76+ - A8W8量化场景下:shape支持1维$(n,)$或3维$(batch, 1, n)$,$n$与`x2`的$n$一致,同时$batch$值需要等于`x1``x2` broadcast后推导出的$batch$值。
77+ 
78+- **approximate** (`str`):可选参数,默认值为`"gelu_erf"`。指定GELU激活函数的类型。
79+ - 支持`"gelu_tanh"`(GELU的tanh近似版本)和`"gelu_erf"`(GELU的erf精确版本)。
80+ 
81+## 返回值说明
82+ 
83+`Tensor`
84+ 
85+代表量化矩阵乘融合GELU激活的计算结果。
86+- 输出数据类型的确定规则:
87+ - 如果`x2_scale`的数据类型为`float32`,输出的数据类型为`float16`
88+ - 如果`x2_scale`的数据类型为`bfloat16`,输出的数据类型为`bfloat16`
89+ - 如果`bias`的数据类型为`bfloat16`,输出的数据类型强制为`bfloat16`(优先级高于`x2_scale`)。
90+- 输出shape为$(batch, m, n)$,其中$batch$根据`x1``x2`的batch通过broadcast得到。
91+ 
92+## 约束说明
93+ 
94+- 该接口支持推理场景下使用。
95+- 该接口支持图模式。
96+- 传入的`x1``x2``x1_scale``x2_scale`不能是空。
97+- `x1``x2``bias``x1_scale``x2_scale`的数据类型和数据格式需要在支持的范围之内。
98+- `x1``x2`最后一维的shape大小不能超过65535。
99+- `approximate`必须为`"gelu_tanh"``"gelu_erf"`
100+ 
101+- **A4W4量化(int4/int32类型输入)的额外约束**
102+ 
103+ A4W4量化场景支持两种输入类型:
104+ - **int4类型**:直接使用int4数据类型
105+ - **int32类型**:每个`int32`数据存放8个`int4`数据(打包存储)
106+
107+ 当使用`int32`类型时,输入的`int32` shape需要将数据原本`int4`类型时shape的最后一维缩小8倍。`int4`数据的shape最后一维应为8的倍数。
108+ 
109+ - `x1``x2`的内轴(k轴)必须为偶数。
110+ -`x2``int32`类型时,`x2`的shape为$(k, n//8)$,$n$必须是8的倍数。
111+ -`x2``int4`类型时,`x2`的shape为$(k, n)$,$n$必须是8的倍数。
112+ - A4W4量化仅支持ND格式,不支持NZ格式。
113+ - 转置信息由算子内部根据tensor的stride自动推导,无需手动指定。
114+ 
115+- **A8W8量化的约束**
116+ 
117+ - 支持ND格式和NZ格式。
118+ - 如果需要使用NZ格式以提升性能,可以手动调用`torch_npu.npu_format_cast`完成输入`x2`(weight)的NZ格式转换。
119+ - 转置信息由算子内部根据tensor的stride自动推导,无需手动指定。
120+ 
121+- 输入参数间支持的数据类型组合情况如下:
122+ 
123+ **表 1** <term>Atlas A2 训练系列产品/Atlas A2 推理系列产品</term>、<term>Atlas A3 训练系列产品/Atlas A3 推理系列产品</term>
124+ 
125+ | x1 | x2 | x1_scale | x2_scale | bias | 输出数据类型 |
126+ |-------|-------|----------|-----------|-------------------------------------|-----------|
127+ | int8 | int8 | float32 | float32 | int32/float32/bfloat16/float16/None | float16 |
128+ | int8 | int8 | float32 | bfloat16 | int32/float32/bfloat16/float16/None | bfloat16 |
129+ | int32 | int32 | float32 | float32 | int32/None | float16 |
130+ | int32 | int32 | float32 | bfloat16 | int32/None | bfloat16 |
131+ | int4 | int4 | float32 | float32 | int32/None | float16 |
132+ | int4 | int4 | float32 | bfloat16 | int32/None | bfloat16 |
133+ 
134+## 调用示例
135+ 
136+- 单算子调用(A8W8量化,ND格式,gelu_tanh激活)
137+ 
138+ ```python
139+ >>> import torch
140+ >>> import torch_npu
141+ >>>
142+ >>> m, k, n = 128, 256, 512
143+ >>> x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
144+ >>> x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
145+ >>> x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
146+ >>> x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
147+ >>>
148+ >>> output = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
149+ >>> print(output.shape) # torch.Size([128, 512])
150+ >>> print(output.dtype) # torch.float16
151+ ```
152+ 
153+- 单算子调用(A8W8量化,ND格式,gelu_erf激活,带bias)
154+ 
155+ ```python
156+ >>> import torch
157+ >>> import torch_npu
158+ >>>
159+ >>> m, k, n = 128, 256, 512
160+ >>> x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
161+ >>> x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
162+ >>> x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
163+ >>> x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
164+ >>> bias = torch.randn(n, dtype=torch.float32).npu() * 0.1
165+ >>>
166+ >>> # 使用gelu_erf激活并添加bias
167+ >>> output = torch_npu.npu_quant_matmul_gelu(
168+ ... x1, x2, x1_scale, x2_scale, bias=bias, approximate="gelu_erf"
169+ ... )
170+ >>> print(output.shape) # torch.Size([128, 512])
171+ >>> print(output.dtype) # torch.float16
172+ ```
173+ 
174+- 单算子调用(A8W8量化,NZ格式,gelu_tanh激活)
175+ 
176+ ```python
177+ >>> import torch
178+ >>> import torch_npu
179+ >>>
180+ >>> m, k, n = 128, 256, 512
181+ >>> x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
182+ >>> x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
183+ >>>
184+ >>> # 将x2转换为NZ格式以提升性能
185+ >>> x2_nz = torch_npu.npu_format_cast(x2.contiguous(), 29) # 29为ACL_FORMAT_FRACTAL_NZ
186+ >>>
187+ >>> x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
188+ >>> x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
189+ >>>
190+ >>> # 自动识别NZ格式并调用对应接口
191+ >>> output = torch_npu.npu_quant_matmul_gelu(x1, x2_nz, x1_scale, x2_scale, approximate="gelu_tanh")
192+ >>> print(output.shape) # torch.Size([128, 512])
193+ >>> print(output.dtype) # torch.float16
194+ ```
195+ 
196+- 单算子调用(A8W8量化,bfloat16输出)
197+ 
198+ ```python
199+ >>> import torch
200+ >>> import torch_npu
201+ >>>
202+ >>> m, k, n = 64, 128, 256
203+ >>> x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
204+ >>> x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
205+ >>> x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
206+ >>> x2_scale = torch.randn(n, dtype=torch.bfloat16).abs().npu() * 0.01 # bfloat16 scale
207+ >>>
208+ >>> # 输出数据类型由x2_scale的类型决定,此处输出为bfloat16
209+ >>> output = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
210+ >>> print(output.dtype) # torch.bfloat16
211+ ```
212+ 
213+- 单算子调用(A4W4量化)
214+ 
215+ ```python
216+ >>> import torch
217+ >>> import torch_npu
218+ >>>
219+ >>> m, k, n = 128, 256, 512
220+ >>> # 生成int4数据(以int32格式存储)
221+ >>> # 注意:实际使用时需要通过量化接口将float32数据量化为int4并打包为int32
222+ >>> x1 = torch.randint(-8, 8, (m, k // 8), dtype=torch.int32).npu()
223+ >>> x2 = torch.randint(-8, 8, (k, n // 8), dtype=torch.int32).npu()
224+ >>>
225+ >>> x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
226+ >>> x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
227+ >>>
228+ >>> # A4W4量化仅支持ND格式,不支持NZ格式
229+ >>> # 转置信息由算子内部根据tensor的stride自动推导
230+ >>> output = torch_npu.npu_quant_matmul_gelu(
231+ ... x1, x2, x1_scale, x2_scale,
232+ ... approximate="gelu_tanh"
233+ ... )
234+ >>> print(output.shape) # torch.Size([128, 512])
235+ >>> print(output.dtype) # torch.float16
236+ ```
237+ 
238+- 单算子调用(使用默认approximate="gelu_erf")
239+ 
240+ ```python
241+ >>> import torch
242+ >>> import torch_npu
243+ >>>
244+ >>> m, k, n = 64, 128, 256
245+ >>> x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
246+ >>> x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
247+ >>> x1_scale = torch.randn(m, dtype=torch.float32).abs().npu() * 0.01
248+ >>> x2_scale = torch.randn(n, dtype=torch.float32).abs().npu() * 0.01
249+ >>>
250+ >>> # 不指定approximate参数,使用默认值"gelu_erf"
251+ >>> output = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale)
252+ >>> print(output.dtype) # torch.float16
253+ ```
254+ 
255+- 图模式调用(A8W8量化,ND格式)
256+ 
257+ ```python
258+ >>> import torch
259+ >>> import torch_npu
260+ >>> import torchair as tng
261+ >>> from torchair.configs.compiler_config import CompilerConfig
262+ >>>
263+ >>> os.environ["ENABLE_ACLNN"] = "true"
264+ >>> config = CompilerConfig()
265+ >>> npu_backend = tng.get_npu_backend(compiler_config=config)
266+ >>>
267+ >>> class MyModel(torch.nn.Module):
268+ ... def __init__(self):
269+ ... super().__init__()
270+ ...
271+ ... def forward(self, x1, x2, x1_scale, x2_scale, bias):
272+ ... return torch_npu.npu_quant_matmul_gelu(
273+ ... x1, x2, x1_scale, x2_scale,
274+ ... bias=bias, approximate="gelu_erf"
275+ ... )
276+ >>>
277+ >>> cpu_model = MyModel()
278+ >>> model = cpu_model.npu()
279+ >>> cpu_x1 = torch.randint(-1, 1, (15, 1, 512), dtype=torch.int8)
280+ >>> cpu_x2 = torch.randint(-1, 1, (15, 512, 128), dtype=torch.int8)
281+ >>> x1_scale = torch.randn(15, dtype=torch.float32).abs() * 0.01
282+ >>> x2_scale = torch.randn(128, dtype=torch.float32).abs() * 0.01
283+ >>> bias = torch.randint(-1, 1, (15, 1, 128), dtype=torch.int32)
284+ >>>
285+ >>> model = torch.compile(model, backend=npu_backend, dynamic=True)
286+ >>> npu_out = model(
287+ ... cpu_x1.npu(), cpu_x2.npu(),
288+ ... x1_scale.npu(), x2_scale.npu(),
289+ ... bias.npu()
290+ ... )
291+ >>> print(npu_out.shape) # torch.Size([15, 1, 128])
292+ >>> print(npu_out.dtype) # torch.float16
293+ ```
Mdocs/zh/custom_APIs/torch_npu/torch_npu_list.md+5-0
@@ -531,6 +531,11 @@
531<td class="cellrowborder" valign="top" width="61.39%" headers="mcps1.2.3.1.2 "><p id="zh-cn_topic_0000001814195101_p156512056161014"><a name="zh-cn_topic_0000001814195101_p156512056161014"></a><a name="zh-cn_topic_0000001814195101_p156512056161014"></a>完成量化的矩阵乘计算,最小支持输入维度为2维,最大支持输入维度为6维。</p>531<td class="cellrowborder" valign="top" width="61.39%" headers="mcps1.2.3.1.2 "><p id="zh-cn_topic_0000001814195101_p156512056161014"><a name="zh-cn_topic_0000001814195101_p156512056161014"></a><a name="zh-cn_topic_0000001814195101_p156512056161014"></a>完成量化的矩阵乘计算,最小支持输入维度为2维,最大支持输入维度为6维。</p>
532</td>532</td>
533</tr>533</tr>
534+<tr id="row9719124019219"><td class="cellrowborder" valign="top" width="38.61%" headers="mcps1.2.3.1.1 "><p id="p104326218132920"><a name="p104326218132920"></a><a name="p104326218132920"></a><a href="torch_npu-npu_quant_matmul_gelu.md">torch_npu.npu_quant_matmul_gelu</a></p>
535+</td>
536+<td class="cellrowborder" valign="top" width="61.39%" headers="mcps1.2.3.1.2 "><p id="p2026022511100607"><a name="p2026022511100607"></a><a name="p2026022511100607"></a>完成量化的矩阵乘和GELU激活函数的融合计算,支持A8W8和A4W4量化。</p>
537+</td>
538+</tr>
534<tr id="row202508121056216"><td class="cellrowborder" valign="top" width="38.61%" headers="mcps1.2.3.1.1 "><p id="p202508121056216"><a name="p202508121056216"></a><a name="p202508121056216"></a><a href="torch_npu-npu_quant_matmul_reduce_sum.md">torch_npu.npu_quant_matmul_reduce_sum</a></p>539<tr id="row202508121056216"><td class="cellrowborder" valign="top" width="38.61%" headers="mcps1.2.3.1.1 "><p id="p202508121056216"><a name="p202508121056216"></a><a name="p202508121056216"></a><a href="torch_npu-npu_quant_matmul_reduce_sum.md">torch_npu.npu_quant_matmul_reduce_sum</a></p>
535</td>540</td>
536<td class="cellrowborder" valign="top" width="61.39%" headers="mcps1.2.3.1.2 "><p id="p2025081210578767"><a name="p2025081210578767"></a><a name="p2025081210578767"></a>完成量化的分组矩阵计算,然后所有组的矩阵计算结果相加后输出。</p>541<td class="cellrowborder" valign="top" width="61.39%" headers="mcps1.2.3.1.2 "><p id="p2025081210578767"><a name="p2025081210578767"></a><a name="p2025081210578767"></a>完成量化的分组矩阵计算,然后所有组的矩阵计算结果相加后输出。</p>
Mop_plugin/config/op_plugin_functions.yaml+5-0
@@ -6445,6 +6445,11 @@ custom:
6445 op_api: [v2.1, newest]6445 op_api: [v2.1, newest]
6446 internal_format_opapi: [v2.1, newest]6446 internal_format_opapi: [v2.1, newest]
6447 6447 
6448+ - func: npu_quant_matmul_gelu(Tensor x1, Tensor x2, Tensor x1_scale, Tensor x2_scale, *, Tensor? bias=None, str? approximate='gelu_erf') -> Tensor
6449+ op_api: all_version
6450+ internal_format_opapi: all_version
6451+ exposed: all_version
6452+ 
6448 - func: npu_fused_matmul(Tensor x1, Tensor x2, *, Tensor? bias=None, Tensor? x3=None, str fused_op_type='') -> Tensor6453 - func: npu_fused_matmul(Tensor x1, Tensor x2, *, Tensor? bias=None, Tensor? x3=None, str fused_op_type='') -> Tensor
6449 op_api: all_version6454 op_api: all_version
6450 6455 
Aop_plugin/ops/opapi/QuantMatmulGeluKernelNpuOpApi.cpp+138-0
@@ -0,0 +1,138 @@
1+// Copyright (c) 2026 Huawei Technologies Co., Ltd
2+// All rights reserved.
3+//
4+// Licensed under the BSD 3-Clause License (the "License");
5+// you may not use this file except in compliance with the License.
6+//
7+// Unless required by applicable law or agreed to in writing, software
8+// distributed under the License is distributed on an "AS IS" BASIS,
9+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+// See the License for the specific language governing permissions and
11+// limitations under the License.
12+ 
13+#include <vector>
14+#include "op_plugin/OpApiInterface.h"
15+#include "op_plugin/utils/op_api_common.h"
16+#include "torch_npu/csrc/framework/utils/InternalFormatOpAdapter.h"
17+ 
18+namespace op_api {
19+ 
20+// 常量定义
21+constexpr size_t LAST_SECOND_DIM_INDEX = 2;
22+constexpr int64_t INT4_NUMS_IN_INT32 = 8;
23+constexpr size_t FUSED_TYPE_ARRAY_SIZE = 100;
24+using npu_preparation = at_npu::native::OpPreparation;
25+ 
26+// 检查权重是否为 NZ 格式
27+static bool is_nz_format(const at::Tensor& x2)
28+{
29+ const torch_npu::NPUStorageDesc &tensor_desc = torch_npu::NPUBridge::GetNpuStorageImpl(x2)->npu_desc_;
30+ 
31+ return (tensor_desc.npu_format_ == ACL_FORMAT_FRACTAL_NZ ||
32+ tensor_desc.npu_format_ == ACL_FORMAT_FRACTAL_NZ_C0_4);
33+}
34+ 
35+// 推导输出 batch shape
36+static uint64_t infer_out_batch_shape_gelu(const at::Tensor &x1, const at::Tensor &x2, std::vector<uint64_t> &batch_record)
37+{
38+ TORCH_CHECK(at_npu::native::FormatHelper::IsBaseFormatType(x2) || is_nz_format(x2),
39+ "x2 should be in the original format or nz format, but it is ",
40+ npu_preparation::get_tensor_npu_format(x2), OPS_ERROR(ErrCode::PARAM));
41+ 
42+ uint64_t batch_val = 1;
43+ auto x1_dim_num = x1.dim();
44+ auto x2_dim_num = x2.dim();
45+ auto out_dim_num = std::max(x1_dim_num, x2_dim_num);
46+ auto &shape_long = x1_dim_num > x2_dim_num ? x1 : x2;
47+ auto &shape_short = x1_dim_num > x2_dim_num ? x2 : x1;
48+ int64_t valid_offset = out_dim_num - std::min(x1_dim_num, x2_dim_num);
49+ 
50+ for (int64_t i = 0; i < out_dim_num - LAST_SECOND_DIM_INDEX; i++) {
51+ auto short_dim = i < valid_offset ? 1 : shape_short.size(i - valid_offset);
52+ auto long_dim = shape_long.size(i);
53+ TORCH_CHECK(!(short_dim > 1 && long_dim > 1 && short_dim != long_dim),
54+ "the x1 shape and x2 shape not supported for broadcast, the short_dim is ",
55+ short_dim, " and the long_dim is ", long_dim, OPS_ERROR(ErrCode::PARAM));
56+ uint64_t cur_batch_value = static_cast<uint64_t>(std::max(short_dim, long_dim));
57+ batch_val = batch_val * cur_batch_value;
58+ batch_record.push_back(cur_batch_value);
59+ }
60+ 
61+ return batch_val;
62+}
63+ 
64+// 算子接口
65+at::Tensor npu_quant_matmul_gelu(
66+ const at::Tensor &x1,
67+ const at::Tensor &x2,
68+ const at::Tensor &x1_scale,
69+ const at::Tensor &x2_scale,
70+ const c10::optional<at::Tensor> &bias,
71+ const c10::optional<c10::string_view> approximate)
72+{
73+ // 1. 校验输入场景,确定支持的量化场景(A4W4(INT4/INT32)或A8W8)
74+ bool is_a4w4 = (x1.dtype() == at::ScalarType::QUInt4x2 && x2.dtype() == at::ScalarType::QUInt4x2);
75+ bool is_a4w4_int32 = (x1.dtype() == at::kInt && x2.dtype() == at::kInt);
76+ bool is_a8w8 = (x1.dtype() == at::kChar && x2.dtype() == at::kChar);
77+ TORCH_CHECK(is_a4w4 || is_a4w4_int32 || is_a8w8,
78+ "Only A4W4 (int4/int32) or A8W8 (int8) quantization is supported, "
79+ "but got x1.dtype=", x1.dtype(), ", x2.dtype=", x2.dtype(),
80+ OPS_ERROR(ErrCode::TYPE));
81+ 
82+ // 2. 处理并校验 approximate 参数
83+ c10::string_view approximate_value = approximate.value_or("gelu_erf"); // 默认值为 "gelu_erf"
84+ TORCH_CHECK(approximate_value == "gelu_tanh" || approximate_value == "gelu_erf",
85+ "approximate must be 'gelu_tanh' or 'gelu_erf', but got: ",
86+ approximate_value, OPS_ERROR(ErrCode::PARAM));
87+ 
88+ // 3. 推导输出 size
89+ int64_t x1_m_dim = x1.size(x1.dim() - LAST_SECOND_DIM_INDEX);
90+ int64_t x1_k_dim = x1.size(x1.dim() - 1);
91+ int64_t x2_k_dim = x2.size(x2.dim() - LAST_SECOND_DIM_INDEX);
92+ int64_t x2_n_dim = x2.size(x2.dim() - 1);
93+ // A4W4场景:当 x1: (m, k1 // 8); x2: (k2, n // 8) 时,需要对推导的 n * 8(INT32存储打包的8个INT4)
94+ if (x1_k_dim * INT4_NUMS_IN_INT32 == x2_k_dim) {
95+ x2_n_dim = x2_n_dim * INT4_NUMS_IN_INT32;
96+ }
97+ 
98+ // 推导输出 shape
99+ std::vector<uint64_t> batch_record;
100+ infer_out_batch_shape_gelu(x1, x2, batch_record);
101+ const at::Tensor long_tensor = x1.dim() > x2.dim() ? x1 : x2;
102+ auto output_size = op_infer::array_to_small_vector(long_tensor.sizes());
103+ output_size[long_tensor.dim() - LAST_SECOND_DIM_INDEX] = x1_m_dim;
104+ output_size[long_tensor.dim() - 1] = x2_n_dim;
105+ for (int64_t i = 0; i < long_tensor.dim() - LAST_SECOND_DIM_INDEX; i++) {
106+ output_size[i] = static_cast<int64_t>(batch_record[i]);
107+ }
108+ 
109+ // 4. 分配输出张量
110+ at::ScalarType output_dtype = (x2_scale.dtype() == at::kBFloat16) ? at::kBFloat16 : at::kHalf;
111+ const at::Tensor &bias_real = bias.value_or(at::Tensor());
112+ if (bias_real.dtype() == at::kBFloat16) {
113+ output_dtype = at::kBFloat16;
114+ }
115+ c10::TensorOptions options = x1.options().dtype(output_dtype);
116+ at::Tensor result = npu_preparation::apply_tensor_without_format(output_size, options);
117+ 
118+ // 5. 调用 aclnn 接口
119+ // 默认参数处理
120+ int64_t group_size = 0;
121+ char *approximate_str_ptr = const_cast<char *>(approximate_value.data());
122+ const at::Tensor empty_tensor = at::Tensor();
123+ 
124+ // 根据 x2 格式选择接口
125+ if (is_nz_format(x2)) {
126+ EXEC_NPU_CMD(aclnnFusedQuantMatmulWeightNz, x1, x2, x1_scale, x2_scale,
127+ empty_tensor, empty_tensor, empty_tensor, empty_tensor, bias_real, empty_tensor,
128+ approximate_str_ptr, group_size, result);
129+ } else {
130+ EXEC_NPU_CMD(aclnnFusedQuantMatmul, x1, x2, x1_scale, x2_scale,
131+ empty_tensor, empty_tensor, empty_tensor, empty_tensor, bias_real, empty_tensor,
132+ approximate_str_ptr, group_size, result);
133+ }
134+ 
135+ return result;
136+}
137+ 
138+} // namespace op_api
Mop_plugin/python/meta/_meta_registrations.py+152-0
@@ -3533,6 +3533,158 @@ def npu_quant_matmul_reduce_sum_meta(x1, x2, *, x1_scale=None, x2_scale=None):
3533 return torch.empty(dst_shape, dtype=torch.bfloat16, device=x1.device)3533 return torch.empty(dst_shape, dtype=torch.bfloat16, device=x1.device)
3534 3534 
3535 3535 
3536+@impl(m, "npu_quant_matmul_gelu")
3537+def npu_quant_matmul_gelu_meta(x1, x2, x1_scale, x2_scale, *, bias=None, approximate="gelu_erf"):
3538+ INT4_IN_INT32 = 8
3539+ LAST_SECOND_DIM_INDEX = 2
3540+
3541+ # 校验approximate参数
3542+ torch._check(
3543+ approximate in ["gelu_tanh", "gelu_erf"],
3544+ lambda: f"approximate must be 'gelu_tanh' or 'gelu_erf', but got {approximate} {ops_error(ErrCode.PARAM)}",
3545+ )
3546+
3547+ # 校验量化场景(A4W4或A8W8)
3548+ is_a4w4 = ((x1.dtype == torch.int32 or x1.dtype == torch.quint4x2) and
3549+ (x2.dtype == torch.int32 or x2.dtype == torch.quint4x2))
3550+ is_a8w8 = (x1.dtype == torch.int8 and x2.dtype == torch.int8)
3551+
3552+ torch._check(
3553+ is_a4w4 or is_a8w8,
3554+ lambda: f"Only A4W4 (int4/int32) or A8W8 (int8) quantization is supported, "
3555+ f"but got x1.dtype={x1.dtype}, x2.dtype={x2.dtype} {ops_error(ErrCode.TYPE)}",
3556+ )
3557+
3558+ # 校验x1_scale和x2_scale
3559+ torch._check(x1_scale is not None, lambda: f"x1_scale should not be None.")
3560+ torch._check(x2_scale is not None, lambda: f"x2_scale should not be None.")
3561+ torch._check(x1_scale.dim() == 1, lambda: f"x1_scale dim must be 1, but got {x1_scale.dim()}.")
3562+ torch._check(x2_scale.dim() == 1, lambda: f"x2_scale dim must be 1, but got {x2_scale.dim()}.")
3563+
3564+ # 推导输出shape
3565+ x1_dim_num = x1.dim()
3566+ x2_dim_num = x2.dim()
3567+ out_dim_num = max(x1_dim_num, x2_dim_num)
3568+ shape_long = x1 if x1_dim_num > x2_dim_num else x2
3569+ shape_short = x2 if x1_dim_num > x2_dim_num else x1
3570+ valid_offset = out_dim_num - min(x1_dim_num, x2_dim_num)
3571+
3572+ # 计算batch维度
3573+ batch_val = 1
3574+ dim_list = []
3575+ for i in range(0, out_dim_num - LAST_SECOND_DIM_INDEX):
3576+ short_dim = 1 if i < valid_offset else shape_short.size(i - valid_offset)
3577+ long_dim = shape_long.size(i)
3578+ torch._check(
3579+ not (short_dim > 1 and long_dim > 1 and short_dim != long_dim),
3580+ lambda: "the batch shape cannot be broadcast" + ops_error(ErrCode.VALUE),
3581+ )
3582+ cur_batch_val = max(short_dim, long_dim)
3583+ batch_val = batch_val * cur_batch_val
3584+ dim_list.append(cur_batch_val)
3585+
3586+ # 计算x1的m维度和k维度
3587+ x1_m_dim = x1.size(x1_dim_num - LAST_SECOND_DIM_INDEX)
3588+ x1_k_dim = x1.size(x1_dim_num - 1)
3589+
3590+ # 计算x2的k维度和n维度(考虑INT4打包)
3591+ x2_k_dim = x2.size(x2_dim_num - LAST_SECOND_DIM_INDEX)
3592+ x2_n_dim = x2.size(x2_dim_num - 1)
3593+
3594+ # 校验k维度匹配
3595+ if is_a4w4:
3596+ # A4W4场景:k维度匹配检查
3597+ if x1.dtype == torch.int32 and x2.dtype == torch.int32:
3598+ # 两个都是int32(打包INT4),k维度应该相等
3599+ torch._check(
3600+ x1_k_dim == x2_k_dim,
3601+ lambda: f"A4W4 (int32): k dim of x1 ({x1_k_dim}) must equal k dim of x2 ({x2_k_dim}) {ops_error(ErrCode.VALUE)}",
3602+ )
3603+ elif x1.dtype == torch.quint4x2 and x2.dtype == torch.quint4x2:
3604+ # 两个都是quint4x2(直接INT4),k维度应该相等
3605+ torch._check(
3606+ x1_k_dim == x2_k_dim,
3607+ lambda: f"A4W4 (quint4x2): k dim of x1 ({x1_k_dim}) must equal k dim of x2 ({x2_k_dim}) {ops_error(ErrCode.VALUE)}",
3608+ )
3609+ elif x1.dtype == torch.int32 and x2.dtype == torch.quint4x2:
3610+ # x1是int32(打包),x2是quint4x2(直接),x1的k维度 * 8应该等于x2的k维度
3611+ torch._check(
3612+ x1_k_dim * INT4_IN_INT32 == x2_k_dim,
3613+ lambda: f"A4W4 (int32/quint4x2): k dim of x1 ({x1_k_dim}) * 8 must equal k dim of x2 ({x2_k_dim}) {ops_error(ErrCode.VALUE)}",
3614+ )
3615+ elif x1.dtype == torch.quint4x2 and x2.dtype == torch.int32:
3616+ # x1是quint4x2(直接),x2是int32(打包),x1的k维度应该等于x2的k维度 * 8
3617+ torch._check(
3618+ x1_k_dim == x2_k_dim * INT4_IN_INT32,
3619+ lambda: f"A4W4 (quint4x2/int32): k dim of x1 ({x1_k_dim}) must equal k dim of x2 ({x2_k_dim}) * 8 {ops_error(ErrCode.VALUE)}",
3620+ )
3621+
3622+ # A4W4场景:当x2为int32类型时,恢复实际的n维度(INT4打包)
3623+ if x2.dtype == torch.int32:
3624+ x2_n_dim = x2_n_dim * INT4_IN_INT32
3625+ else:
3626+ # A8W8场景:k维度应该相等
3627+ torch._check(
3628+ x1_k_dim == x2_k_dim,
3629+ lambda: f"A8W8: k dim of x1 ({x1_k_dim}) must equal k dim of x2 ({x2_k_dim}) {ops_error(ErrCode.VALUE)}",
3630+ )
3631+
3632+ dim_list.append(x1_m_dim)
3633+ dim_list.append(x2_n_dim)
3634+
3635+ # 校验x1_scale和x2_scale的shape
3636+ torch._check(
3637+ x1_scale.size(0) == x1_m_dim,
3638+ lambda: f"x1_scale size(0) must equal to x1's m dimension ({x1_m_dim}), but got {x1_scale.size(0)} {ops_error(ErrCode.VALUE)}",
3639+ )
3640+ torch._check(
3641+ x2_scale.size(0) == 1 or x2_scale.size(0) == x2_n_dim,
3642+ lambda: f"x2_scale size(0) must be 1 or equal to x2's n dimension ({x2_n_dim}), but got {x2_scale.size(0)} {ops_error(ErrCode.VALUE)}",
3643+ )
3644+
3645+ # 校验bias shape(如果提供)
3646+ if bias is not None:
3647+ if is_a4w4:
3648+ # A4W4场景:bias shape仅支持1维(n,)
3649+ torch._check(
3650+ bias.dim() == 1,
3651+ lambda: f"A4W4 quantization only supports 1D bias, but got bias.dim()={bias.dim()} {ops_error(ErrCode.VALUE)}",
3652+ )
3653+ torch._check(
3654+ bias.size(0) == x2_n_dim,
3655+ lambda: f"bias size(0) must equal to x2's n dimension ({x2_n_dim}), but got {bias.size(0)} {ops_error(ErrCode.VALUE)}",
3656+ )
3657+ else:
3658+ # A8W8场景:bias shape支持1维(n,)或3维(batch, 1, n)
3659+ torch._check(
3660+ bias.dim() == 1 or bias.dim() == 3,
3661+ lambda: f"A8W8 quantization supports 1D or 3D bias, but got bias.dim()={bias.dim()} {ops_error(ErrCode.VALUE)}",
3662+ )
3663+ if bias.dim() == 1:
3664+ torch._check(
3665+ bias.size(0) == x2_n_dim,
3666+ lambda: f"bias size(0) must equal to x2's n dimension ({x2_n_dim}), but got {bias.size(0)} {ops_error(ErrCode.VALUE)}",
3667+ )
3668+ else: # bias.dim() == 3
3669+ torch._check(
3670+ len(dim_list) == 3,
3671+ lambda: f"when bias dim is 3, out dim need to be 3 {ops_error(ErrCode.TYPE)}",
3672+ )
3673+ torch._check(
3674+ bias.size(0) == batch_val and bias.size(1) == 1 and bias.size(2) == x2_n_dim,
3675+ lambda: f"bias shape must be ({batch_val}, 1, {x2_n_dim}), but got {tuple(bias.shape)} {ops_error(ErrCode.VALUE)}",
3676+ )
3677+
3678+ # 确定输出数据类型(根据x2_scale的数据类型)
3679+ output_dtype = torch.bfloat16 if x2_scale.dtype == torch.bfloat16 else torch.float16
3680+
3681+ # 在FakeTensor模式下,使用meta设备;否则使用x1的设备
3682+ # 确保设备一致性,避免设备传播错误
3683+ output_device = 'meta' if x1.device.type == 'meta' else x1.device
3684+
3685+ return torch.empty(tuple(dim_list), dtype=output_dtype, device=output_device)
3686+ 
3687+ 
3536@impl(m, "npu_quant_grouped_matmul_dequant")3688@impl(m, "npu_quant_grouped_matmul_dequant")
3537def npu_quant_grouped_matmul_dequant_meta(x, quantized_weight, weight_scale, group_list, *,3689def npu_quant_grouped_matmul_dequant_meta(x, quantized_weight, weight_scale, group_list, *,
3538 bias=None, x_scale=None, x_offset=None, smooth_scale=None, quant_mode="pertoken"):3690 bias=None, x_scale=None, x_offset=None, smooth_scale=None, quant_mode="pertoken"):
Mtest/allowlist_for_publicAPI.json+2-1
@@ -143,7 +143,8 @@
143 "npu_confusion_transpose",143 "npu_confusion_transpose",
144 "npu_dense_lightning_indexer_grad_kl_loss",144 "npu_dense_lightning_indexer_grad_kl_loss",
145 "npu_dense_lightning_indexer_softmax_lse",145 "npu_dense_lightning_indexer_softmax_lse",
146- "npu_one_hot"146+ "npu_one_hot",
147+ "npu_quant_matmul_gelu"
147 ]148 ]
148 }149 }
149 150
Mtest/core_tests/test_fake_tensor.py+61-0
@@ -2731,6 +2731,67 @@ class TestQuantMatmulReduceSum(TestCase):
2731 self.assertTrue(y.dtype == torch.bfloat16)2731 self.assertTrue(y.dtype == torch.bfloat16)
2732 2732 
2733 2733 
2734+class TestQuantMatmulGelu(TestCase):
2735+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
2736+ def test_npu_quant_matmul_gelu_meta(self):
2737+ with FakeTensorMode():
2738+ # Test A8W8 scenario
2739+ m, k, n = 128, 256, 512
2740+ x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8).npu()
2741+ x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8).npu()
2742+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
2743+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
2744+
2745+ expect_ret = torch.randn((m, n), dtype=torch.float16).npu()
2746+ res = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale.npu(), x2_scale.npu(), approximate="gelu_tanh")
2747+ self.assertTrue(expect_ret.shape == res.shape)
2748+ self.assertTrue(expect_ret.dtype == res.dtype)
2749+
2750+ # Test A8W8 with bias
2751+ bias = torch.randn(n, dtype=torch.float32) * 0.1
2752+ res_bias = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale.npu(), x2_scale.npu(), bias=bias.npu(), approximate="gelu_erf")
2753+ self.assertTrue(expect_ret.shape == res_bias.shape)
2754+ self.assertTrue(expect_ret.dtype == res_bias.dtype)
2755+
2756+ # Test A8W8 with BF16 output
2757+ x2_scale_bf16 = torch.randn(n, dtype=torch.bfloat16).abs() * 0.01
2758+ expect_ret_bf16 = torch.randn((m, n), dtype=torch.bfloat16).npu()
2759+ res_bf16 = torch_npu.npu_quant_matmul_gelu(x1, x2, x1_scale.npu(), x2_scale_bf16.npu(), approximate="gelu_tanh")
2760+ self.assertTrue(expect_ret_bf16.shape == res_bf16.shape)
2761+ self.assertTrue(expect_ret_bf16.dtype == res_bf16.dtype)
2762+
2763+ # Test A8W8 with batch dimensions
2764+ batch, m, k, n = 4, 64, 128, 256
2765+ x1_batch = torch.randint(-5, 5, (batch, m, k), dtype=torch.int8).npu()
2766+ x2_batch = torch.randint(-5, 5, (batch, k, n), dtype=torch.int8).npu()
2767+ x1_scale_batch = torch.randn(m, dtype=torch.float32).abs() * 0.01
2768+ x2_scale_batch = torch.randn(n, dtype=torch.float32).abs() * 0.01
2769+ expect_ret_batch = torch.randn((batch, m, n), dtype=torch.float16).npu()
2770+ res_batch = torch_npu.npu_quant_matmul_gelu(x1_batch, x2_batch, x1_scale_batch.npu(), x2_scale_batch.npu())
2771+ self.assertTrue(expect_ret_batch.shape == res_batch.shape)
2772+ self.assertTrue(expect_ret_batch.dtype == res_batch.dtype)
2773+
2774+ # Test A4W4 with int32 (packed INT4)
2775+ m, k, n = 64, 128, 256
2776+ k_packed = k // 8
2777+ n_packed = n // 8
2778+ x1_int32 = torch.randint(-8, 8, (m, k_packed), dtype=torch.int32).npu()
2779+ x2_int32 = torch.randint(-8, 8, (k_packed, n_packed), dtype=torch.int32).npu()
2780+ x1_scale_int32 = torch.randn(m, dtype=torch.float32).abs() * 0.01
2781+ x2_scale_int32 = torch.randn(n, dtype=torch.float32).abs() * 0.01
2782+ expect_ret_int32 = torch.randn((m, n), dtype=torch.float16).npu()
2783+ res_int32 = torch_npu.npu_quant_matmul_gelu(x1_int32, x2_int32, x1_scale_int32.npu(), x2_scale_int32.npu(), approximate="gelu_tanh")
2784+ self.assertTrue(expect_ret_int32.shape == res_int32.shape)
2785+ self.assertTrue(expect_ret_int32.dtype == res_int32.dtype)
2786+
2787+ # Test A4W4 with int32 and bias
2788+ bias_int32 = torch.randint(-5, 5, (n,), dtype=torch.int32)
2789+ res_int32_bias = torch_npu.npu_quant_matmul_gelu(x1_int32, x2_int32, x1_scale_int32.npu(), x2_scale_int32.npu(),
2790+ bias=bias_int32.npu(), approximate="gelu_erf")
2791+ self.assertTrue(expect_ret_int32.shape == res_int32_bias.shape)
2792+ self.assertTrue(expect_ret_int32.dtype == res_int32_bias.dtype)
2793+ 
2794+ 
2734class TestRecurrentGatedDeltaRule(TestCase):2795class TestRecurrentGatedDeltaRule(TestCase):
2735 def test_recurrent_gated_delta_rule(self):2796 def test_recurrent_gated_delta_rule(self):
2736 with FakeTensorMode():2797 with FakeTensorMode():
Mtest/core_tests/torch_npu_OpApi_schema_all.json+9-0
@@ -308,6 +308,9 @@
308 "op_api: torch_npu.npu_quant_matmul(*args, **kwargs)": {308 "op_api: torch_npu.npu_quant_matmul(*args, **kwargs)": {
309 "version": ["v2.1", "v2.5", "v2.6", "v2.7"]309 "version": ["v2.1", "v2.5", "v2.6", "v2.7"]
310 },310 },
311+ "op_api: torch_npu.npu_quant_matmul_gelu(*args, **kwargs)": {
312+ "version": ["v2.1", "v2.5", "v2.6", "v2.7"]
313+ },
311 "op_api: torch_npu.npu_all_gather_base_mm(*args, **kwargs)": {314 "op_api: torch_npu.npu_all_gather_base_mm(*args, **kwargs)": {
312 "version": ["v2.1", "v2.5", "v2.6", "v2.7"]315 "version": ["v2.1", "v2.5", "v2.6", "v2.7"]
313 },316 },
@@ -493,6 +496,9 @@
493 "op_api: torch_npu.npu_quant_matmul(*args: _P.args, **kwargs: _P.kwargs) -> ~_T": {496 "op_api: torch_npu.npu_quant_matmul(*args: _P.args, **kwargs: _P.kwargs) -> ~_T": {
494 "version": ["v2.8", "newest"]497 "version": ["v2.8", "newest"]
495 },498 },
499+ "op_api: torch_npu.npu_quant_matmul_gelu(*args: _P.args, **kwargs: _P.kwargs) -> ~_T": {
500+ "version": ["v2.8", "newest"]
501+ },
496 "op_api: torch_npu.npu_quant_matmul_reduce_sum(*args, **kwargs)": {502 "op_api: torch_npu.npu_quant_matmul_reduce_sum(*args, **kwargs)": {
497 "version": ["v2.1", "v2.5", "v2.6", "v2.7"]503 "version": ["v2.1", "v2.5", "v2.6", "v2.7"]
498 },504 },
@@ -1477,6 +1483,9 @@
1477 "func: npu_quant_matmul(Tensor x1, Tensor x2, Tensor scale, *, Tensor? offset=None, Tensor? pertoken_scale=None, Tensor? bias=None, int? output_dtype=None, int? x1_dtype=None, int? x2_dtype=None, int? pertoken_scale_dtype=None, int? scale_dtype=None, int[]? group_sizes=None, Tensor? y_scale=None) -> Tensor": {1483 "func: npu_quant_matmul(Tensor x1, Tensor x2, Tensor scale, *, Tensor? offset=None, Tensor? pertoken_scale=None, Tensor? bias=None, int? output_dtype=None, int? x1_dtype=None, int? x2_dtype=None, int? pertoken_scale_dtype=None, int? scale_dtype=None, int[]? group_sizes=None, Tensor? y_scale=None) -> Tensor": {
1478 "version": ["all_version"]1484 "version": ["all_version"]
1479 },1485 },
1486+ "func: npu_quant_matmul_gelu(Tensor x1, Tensor x2, Tensor x1_scale, Tensor x2_scale, *, Tensor? bias=None, str? approximate='gelu_erf') -> Tensor": {
1487+ "version": ["all_version"]
1488+ },
1480 "func: npu_dual_level_quant_matmul(Tensor x1, Tensor x2, Tensor x1_level0_scale, Tensor x2_level0_scale, Tensor x1_level1_scale, Tensor x2_level1_scale, *, Tensor? bias=None, int output_dtype=15) -> Tensor": {1489 "func: npu_dual_level_quant_matmul(Tensor x1, Tensor x2, Tensor x1_level0_scale, Tensor x2_level0_scale, Tensor x1_level1_scale, Tensor x2_level1_scale, *, Tensor? bias=None, int output_dtype=15) -> Tensor": {
1481 "version": ["v2.1", "newest"]1490 "version": ["v2.1", "newest"]
1482 },1491 },
Atest/test_custom_ops/test_npu_quant_matmul_gelu.py+458-0
@@ -0,0 +1,458 @@
1+import math
2+import unittest
3+import numpy as np
4+import torch
5+import torch_npu
6+import torch.nn as nn
7+from torch_npu.testing.testcase import TestCase, run_tests
8+from torch_npu.testing.common_utils import SupportedDevices
9+ 
10+ 
11+class TestQuantMatmulGelu(TestCase):
12+ 
13+ def gelu_tanh(self, x):
14+ """GELU tanh approximation"""
15+ return 0.5 * x * (1.0 + torch.tanh(math.sqrt(2.0 / math.pi) * (x + 0.044715 * torch.pow(x, 3.0))))
16+ 
17+ def gelu_erf(self, x):
18+ """GELU erf"""
19+ m = nn.GELU()
20+ return m(x)
21+ 
22+ def cpu_quant_matmul_gelu(self, x1, x2, x1_scale, x2_scale, bias=None, approximate="gelu_erf"):
23+ """CPU reference implementation"""
24+ # Handle different input types
25+ if x1.dtype == torch.quint4x2:
26+ # For quint4x2 (direct INT4), convert to int32 for computation
27+ # quint4x2 stores 2 INT4 values per uint8, so we view as uint8 then to int32
28+ x1_int32 = x1.view(torch.uint8).to(torch.int32)
29+ x2_int32 = x2.view(torch.uint8).to(torch.int32)
30+ elif x1.dtype == torch.int32:
31+ # For int32 (packed INT4), each int32 stores 8 int4 values
32+ # We need to expand the dimensions: (m, k//8) -> (m, k) and (k, n//8) -> (k, n)
33+ # For simplicity, we'll use the packed dimensions directly
34+ # Note: This is a simplified CPU reference, actual unpacking would be more complex
35+ x1_int32 = x1.to(torch.int32)
36+ x2_int32 = x2.to(torch.int32)
37+ # For packed INT4 in INT32, we need to handle dimension expansion
38+ # Since CPU reference is simplified, we'll use the packed shape directly
39+ # The actual unpacking logic would expand each int32 to 8 int4 values
40+ else:
41+ # A8W8 case: int8
42+ x1_int32 = x1.to(torch.int32)
43+ x2_int32 = x2.to(torch.int32)
44+
45+ # Compute quantized matmul: x1 @ x2 * x1_scale * x2_scale
46+ result = torch.matmul(x1_int32, x2_int32).float()
47+ result = result * x1_scale.view(-1, 1) * x2_scale.view(1, -1)
48+ 
49+ # Add bias if provided
50+ if bias is not None:
51+ result = result + bias.view(1, -1)
52+ 
53+ # Apply GELU
54+ if approximate == "gelu_tanh":
55+ result = self.gelu_tanh(result)
56+ elif approximate == "gelu_erf":
57+ result = self.gelu_erf(result)
58+ else:
59+ raise ValueError(f"Unsupported approximate: {approximate}")
60+ 
61+ return result
62+ 
63+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
64+ @SupportedDevices(['Ascend910B'])
Sunshine_Youngster

增加跳过 @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")

likedislike
Sunshine_Youngster
Sunshine_Youngster
2月24日 评论:
65+ def test_npu_quant_matmul_gelu_a8w8_nd_gelu_tanh(self):
66+ """Test A8W8 with ND format and gelu_tanh"""
67+ torch.manual_seed(0)
68+ m, k, n = 128, 256, 512
69+ 
70+ x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8)
71+ x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8)
72+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
73+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
74+ 
75+ # CPU reference
76+ cpu_result = self.cpu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
77+ 
78+ # NPU custom op
79+ npu_result = torch_npu.npu_quant_matmul_gelu(
80+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(), approximate="gelu_tanh")
81+ 
82+ self.assertRtolEqual(cpu_result.numpy(), npu_result.cpu().float().numpy(), 0.01)
83+ 
84+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
85+ @SupportedDevices(['Ascend910B'])
86+ def test_npu_quant_matmul_gelu_a8w8_nd_gelu_erf(self):
87+ """Test A8W8 with ND format and gelu_erf"""
88+ torch.manual_seed(0)
89+ m, k, n = 128, 256, 512
90+ 
91+ x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8)
92+ x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8)
93+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
94+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
95+ 
96+ # CPU reference
97+ cpu_result = self.cpu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_erf")
98+ 
99+ # NPU custom op
100+ npu_result = torch_npu.npu_quant_matmul_gelu(
101+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(), approximate="gelu_erf")
102+ 
103+ self.assertRtolEqual(cpu_result.numpy(), npu_result.cpu().float().numpy(), 0.01)
104+ 
105+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
106+ @SupportedDevices(['Ascend910B'])
107+ def test_npu_quant_matmul_gelu_a8w8_nd_with_bias(self):
108+ """Test A8W8 with ND format, gelu_tanh and bias"""
109+ torch.manual_seed(0)
110+ m, k, n = 128, 256, 512
111+ 
112+ x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8)
113+ x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8)
114+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
115+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
116+ bias = torch.randn(n, dtype=torch.float32) * 0.1
117+ 
118+ # CPU reference
119+ cpu_result = self.cpu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh", bias=bias)
120+ 
121+ # NPU custom op
122+ npu_result = torch_npu.npu_quant_matmul_gelu(
123+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(),
124+ bias=bias.npu(), approximate="gelu_tanh")
125+ 
126+ self.assertRtolEqual(cpu_result.numpy(), npu_result.cpu().float().numpy(), 0.01)
127+ 
128+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
129+ @SupportedDevices(['Ascend910B'])
130+ def test_npu_quant_matmul_gelu_a8w8_nz_gelu_tanh(self):
131+ """Test A8W8 with NZ format and gelu_tanh"""
132+ torch.manual_seed(0)
133+ m, k, n = 128, 256, 512
134+ 
135+ x1 = torch.randint(-1, 1, (m, k), dtype=torch.int8)
136+ x2 = torch.randint(-1, 1, (k, n), dtype=torch.int8)
137+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
138+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
139+ 
140+ # CPU reference
141+ cpu_result = self.cpu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
142+ 
143+ # Convert x2 to NZ format
144+ x2_nz = torch_npu.npu_format_cast(x2.npu().contiguous(), 29) # 29 is ACL_FORMAT_FRACTAL_NZ
145+ 
146+ # NPU custom op with NZ format
147+ npu_result = torch_npu.npu_quant_matmul_gelu(
148+ x1.npu(), x2_nz, x1_scale.npu(), x2_scale.npu(), approximate="gelu_tanh")
149+ 
150+ self.assertRtolEqual(cpu_result.numpy(), npu_result.cpu().float().numpy(), 0.01)
151+ 
152+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
153+ @SupportedDevices(['Ascend910B'])
154+ def test_npu_quant_matmul_gelu_a8w8_nz_gelu_erf(self):
155+ """Test A8W8 with NZ format and gelu_erf"""
156+ torch.manual_seed(0)
157+ m, k, n = 128, 256, 512
158+ 
159+ x1 = torch.randint(-1, 1, (m, k), dtype=torch.int8)
160+ x2 = torch.randint(-1, 1, (k, n), dtype=torch.int8)
161+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
162+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
163+ 
164+ # CPU reference
165+ cpu_result = self.cpu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_erf")
166+ 
167+ # Convert x2 to NZ format
168+ x2_nz = torch_npu.npu_format_cast(x2.npu().contiguous(), 29) # 29 is ACL_FORMAT_FRACTAL_NZ
169+ 
170+ # NPU custom op with NZ format
171+ npu_result = torch_npu.npu_quant_matmul_gelu(
172+ x1.npu(), x2_nz, x1_scale.npu(), x2_scale.npu(), approximate="gelu_erf")
173+ 
174+ self.assertRtolEqual(cpu_result.numpy(), npu_result.cpu().float().numpy(), 0.01)
175+ 
176+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
177+ @SupportedDevices(['Ascend910B'])
178+ def test_npu_quant_matmul_gelu_a8w8_nz_simple(self):
179+ """Test A8W8 with NZ format - simple case with smaller dimensions"""
180+ torch.manual_seed(0)
181+ m, k, n = 32, 64, 128
182+ 
183+ x1 = torch.randint(-1, 1, (m, k), dtype=torch.int8)
184+ x2 = torch.randint(-1, 1, (k, n), dtype=torch.int8)
185+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
186+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
187+ 
188+ # CPU reference
189+ cpu_result = self.cpu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_tanh")
190+ 
191+ # Convert x2 to NZ format
192+ x2_nz = torch_npu.npu_format_cast(x2.npu().contiguous(), 29) # 29 is ACL_FORMAT_FRACTAL_NZ
193+ 
194+ # NPU custom op with NZ format
195+ npu_result = torch_npu.npu_quant_matmul_gelu(
196+ x1.npu(), x2_nz, x1_scale.npu(), x2_scale.npu(), approximate="gelu_tanh")
197+ 
198+ self.assertRtolEqual(cpu_result.numpy(), npu_result.cpu().float().numpy(), 0.01)
199+ 
200+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
201+ @SupportedDevices(['Ascend910B'])
202+ def test_npu_quant_matmul_gelu_a4w4_nd_gelu_tanh_int4(self):
203+ """Test A4W4 with ND format, gelu_tanh, using quint4x2 (direct INT4) type"""
204+ torch.manual_seed(0)
205+ m, k, n = 128, 256, 512
206+ 
207+ # Generate INT4 data using quint4x2 type
208+ x1_fp = torch.randn(m, k, dtype=torch.float32)
209+ x2_fp = torch.randn(k, n, dtype=torch.float32)
210+ 
211+ # Quantize to INT4 (quint4x2)
212+ scale_tmp = torch.ones(1, dtype=torch.float32).npu()
213+ x1 = torch_npu.npu_quantize(x1_fp.npu(), scale_tmp, None, torch.quint4x2, -1, False)
214+ x2 = torch_npu.npu_quantize(x2_fp.npu(), scale_tmp, None, torch.quint4x2, -1, False)
215+ 
216+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
217+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
218+ 
219+ # NPU custom op
220+ npu_result = torch_npu.npu_quant_matmul_gelu(
221+ x1, x2, x1_scale.npu(), x2_scale.npu(), approximate="gelu_tanh")
222+ 
223+ # For A4W4, we mainly test that the op runs without errors
224+ # Full accuracy comparison requires proper INT4 CPU implementation
225+ self.assertEqual(npu_result.shape, (m, n))
226+ self.assertTrue(torch.isfinite(npu_result).all())
227+ self.assertEqual(npu_result.dtype, torch.float16)
228+ 
229+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
230+ @SupportedDevices(['Ascend910B'])
231+ def test_npu_quant_matmul_gelu_a4w4_nd_gelu_erf_int4(self):
232+ """Test A4W4 with ND format, gelu_erf, using quint4x2 (direct INT4) type"""
233+ torch.manual_seed(0)
234+ m, k, n = 64, 128, 256
235+ 
236+ # Generate INT4 data using quint4x2 type
237+ x1_fp = torch.randn(m, k, dtype=torch.float32)
238+ x2_fp = torch.randn(k, n, dtype=torch.float32)
239+ 
240+ # Quantize to INT4 (quint4x2)
241+ scale_tmp = torch.ones(1, dtype=torch.float32).npu()
242+ x1 = torch_npu.npu_quantize(x1_fp.npu(), scale_tmp, None, torch.quint4x2, -1, False)
243+ x2 = torch_npu.npu_quantize(x2_fp.npu(), scale_tmp, None, torch.quint4x2, -1, False)
244+ 
245+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
246+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
247+ 
248+ # NPU custom op
249+ npu_result = torch_npu.npu_quant_matmul_gelu(
250+ x1, x2, x1_scale.npu(), x2_scale.npu(), approximate="gelu_erf")
251+ 
252+ self.assertEqual(npu_result.shape, (m, n))
253+ self.assertTrue(torch.isfinite(npu_result).all())
254+ self.assertEqual(npu_result.dtype, torch.float16)
255+ 
256+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
257+ @SupportedDevices(['Ascend910B'])
258+ def test_npu_quant_matmul_gelu_a4w4_nd_with_bias_int4(self):
259+ """Test A4W4 with ND format, gelu_tanh, bias, using quint4x2 (direct INT4) type"""
260+ torch.manual_seed(0)
261+ m, k, n = 64, 128, 256
262+ 
263+ # Generate INT4 data using quint4x2 type
264+ x1_fp = torch.randn(m, k, dtype=torch.float32)
265+ x2_fp = torch.randn(k, n, dtype=torch.float32)
266+ 
267+ # Quantize to INT4 (quint4x2)
268+ scale_tmp = torch.ones(1, dtype=torch.float32).npu()
269+ x1 = torch_npu.npu_quantize(x1_fp.npu(), scale_tmp, None, torch.quint4x2, -1, False)
270+ x2 = torch_npu.npu_quantize(x2_fp.npu(), scale_tmp, None, torch.quint4x2, -1, False)
271+ 
272+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
273+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
274+ bias = torch.randint(-5, 5, (n,), dtype=torch.int32)
275+ 
276+ # NPU custom op
277+ npu_result = torch_npu.npu_quant_matmul_gelu(
278+ x1, x2, x1_scale.npu(), x2_scale.npu(),
279+ bias=bias.npu(), approximate="gelu_tanh")
280+ 
281+ self.assertEqual(npu_result.shape, (m, n))
282+ self.assertTrue(torch.isfinite(npu_result).all())
283+ self.assertEqual(npu_result.dtype, torch.float16)
284+ 
285+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
286+ @SupportedDevices(['Ascend910B'])
287+ def test_npu_quant_matmul_gelu_a4w4_nd_gelu_tanh_int32(self):
288+ """Test A4W4 with ND format, gelu_tanh, using int32 (packed INT4) type"""
289+ torch.manual_seed(0)
290+ m, k, n = 128, 256, 512
291+ 
292+ # Generate INT4 data packed in INT32 format (8 int4 values per int32)
293+ # Shape for int32: (m, k//8) and (k, n//8)
294+ k_packed = k // 8
295+ n_packed = n // 8
296+
297+ x1 = torch.randint(-8, 8, (m, k_packed), dtype=torch.int32)
298+ x2 = torch.randint(-8, 8, (k, n_packed), dtype=torch.int32)
299+ 
300+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
301+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
302+ 
303+ # NPU custom op
304+ npu_result = torch_npu.npu_quant_matmul_gelu(
305+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(), approximate="gelu_tanh")
306+ 
307+ # Verify output shape: (m, n) where n is recovered from n_packed * 8
308+ self.assertEqual(npu_result.shape, (m, n))
309+ self.assertTrue(torch.isfinite(npu_result).all())
310+ self.assertEqual(npu_result.dtype, torch.float16)
311+ 
312+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
313+ @SupportedDevices(['Ascend910B'])
314+ def test_npu_quant_matmul_gelu_a4w4_nd_gelu_erf_int32(self):
315+ """Test A4W4 with ND format, gelu_erf, using int32 (packed INT4) type"""
316+ torch.manual_seed(0)
317+ m, k, n = 64, 128, 256
318+ 
319+ # Generate INT4 data packed in INT32 format
320+ k_packed = k // 8
321+ n_packed = n // 8
322+
323+ x1 = torch.randint(-8, 8, (m, k_packed), dtype=torch.int32)
324+ x2 = torch.randint(-8, 8, (k, n_packed), dtype=torch.int32)
325+ 
326+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
327+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
328+ 
329+ # NPU custom op
330+ npu_result = torch_npu.npu_quant_matmul_gelu(
331+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(), approximate="gelu_erf")
332+ 
333+ self.assertEqual(npu_result.shape, (m, n))
334+ self.assertTrue(torch.isfinite(npu_result).all())
335+ self.assertEqual(npu_result.dtype, torch.float16)
336+ 
337+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
338+ @SupportedDevices(['Ascend910B'])
339+ def test_npu_quant_matmul_gelu_a4w4_nd_with_bias_int32(self):
340+ """Test A4W4 with ND format, gelu_tanh, bias, using int32 (packed INT4) type"""
341+ torch.manual_seed(0)
342+ m, k, n = 64, 128, 256
343+ 
344+ # Generate INT4 data packed in INT32 format
345+ k_packed = k // 8
346+ n_packed = n // 8
347+
348+ x1 = torch.randint(-8, 8, (m, k_packed), dtype=torch.int32)
349+ x2 = torch.randint(-8, 8, (k, n_packed), dtype=torch.int32)
350+ 
351+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
352+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
353+ bias = torch.randint(-5, 5, (n,), dtype=torch.int32)
354+ 
355+ # NPU custom op
356+ npu_result = torch_npu.npu_quant_matmul_gelu(
357+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(),
358+ bias=bias.npu(), approximate="gelu_tanh")
359+ 
360+ self.assertEqual(npu_result.shape, (m, n))
361+ self.assertTrue(torch.isfinite(npu_result).all())
362+ self.assertEqual(npu_result.dtype, torch.float16)
363+ 
364+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
365+ @SupportedDevices(['Ascend910B'])
366+ def test_npu_quant_matmul_gelu_bf16_output(self):
367+ """Test with BF16 output (x2_scale is BF16)"""
368+ torch.manual_seed(0)
369+ m, k, n = 64, 128, 256
370+ 
371+ x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8)
372+ x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8)
373+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
374+ x2_scale = torch.randn(n, dtype=torch.bfloat16).abs() * 0.01 # BF16 scale
375+ 
376+ # NPU custom op
377+ npu_result = torch_npu.npu_quant_matmul_gelu(
378+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(), approximate="gelu_tanh")
379+ 
380+ # Check output dtype is BF16
381+ self.assertEqual(npu_result.dtype, torch.bfloat16)
382+ self.assertEqual(npu_result.shape, (m, n))
383+ self.assertTrue(torch.isfinite(npu_result.float()).all())
384+ 
385+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
386+ @SupportedDevices(['Ascend910B'])
387+ def test_npu_quant_matmul_gelu_batch(self):
388+ """Test with batch dimensions"""
389+ torch.manual_seed(0)
390+ batch, m, k, n = 4, 64, 128, 256
391+ 
392+ x1 = torch.randint(-5, 5, (batch, m, k), dtype=torch.int8)
393+ x2 = torch.randint(-5, 5, (batch, k, n), dtype=torch.int8)
394+ # x1_scale: per-token scale, shape should be (m,) for all batches or (batch*m,) for per-batch-per-token
395+ # For simplicity, use same scale for all batches: (m,)
396+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
397+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
398+ 
399+ # Process each batch separately for CPU reference
400+ cpu_results = []
401+ for i in range(batch):
402+ cpu_result = self.cpu_quant_matmul_gelu(
403+ x1[i], x2[i], x1_scale, x2_scale, approximate="gelu_tanh")
404+ cpu_results.append(cpu_result)
405+ cpu_result = torch.stack(cpu_results, dim=0)
406+ 
407+ # NPU custom op should handle batch automatically
408+ npu_result = torch_npu.npu_quant_matmul_gelu(
409+ x1.npu(), x2.npu(),
410+ x1_scale.npu(), # Per-token scale: (m,)
411+ x2_scale.npu(),
412+ approximate="gelu_tanh")
413+ 
414+ self.assertEqual(npu_result.shape, (batch, m, n))
415+ self.assertTrue(torch.isfinite(npu_result.float()).all())
416+ 
417+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
418+ @SupportedDevices(['Ascend910B'])
419+ def test_npu_quant_matmul_gelu_invalid_approximate(self):
420+ """Test with invalid approximate values"""
421+ m, k, n = 64, 128, 256
422+ 
423+ x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8)
424+ x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8)
425+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
426+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
427+ 
428+ # Test various invalid approximate values
429+ invalid_values = ["invalid_type", "gelu", "tanh", "erf", "gelu_relu", "", "none"]
430+ for invalid_val in invalid_values:
431+ with self.assertRaisesRegex(RuntimeError, "approximate must be 'gelu_tanh' or 'gelu_erf'"):
432+ torch_npu.npu_quant_matmul_gelu(
433+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu(), approximate=invalid_val)
434+ 
435+ @unittest.skip("Skipping due to outdated CANN version; please update CANN to the latest version and remove this skip")
436+ @SupportedDevices(['Ascend910B'])
437+ def test_npu_quant_matmul_gelu_default_approximate(self):
438+ """Test with default approximate (gelu_erf)"""
439+ torch.manual_seed(0)
440+ m, k, n = 64, 128, 256
441+ 
442+ x1 = torch.randint(-5, 5, (m, k), dtype=torch.int8)
443+ x2 = torch.randint(-5, 5, (k, n), dtype=torch.int8)
444+ x1_scale = torch.randn(m, dtype=torch.float32).abs() * 0.01
445+ x2_scale = torch.randn(n, dtype=torch.float32).abs() * 0.01
446+ 
447+ # CPU reference with default gelu_erf
448+ cpu_result = self.cpu_quant_matmul_gelu(x1, x2, x1_scale, x2_scale, approximate="gelu_erf")
449+ 
450+ # NPU custom op without specifying approximate (should use default "gelu_erf")
451+ npu_result = torch_npu.npu_quant_matmul_gelu(
452+ x1.npu(), x2.npu(), x1_scale.npu(), x2_scale.npu())
453+ 
454+ self.assertRtolEqual(cpu_result.numpy(), npu_result.cpu().float().numpy(), 0.01)
455+ 
456+ 
457+if __name__ == "__main__":
458+ run_tests()