已合并
GroupedDynamicBlockQuant op-plugin #3932
yin-peng创建于 2025年12月29日
GroupedDynamicBlockQuant op-plugin #3932
已合并
yin-peng创建于 2025年12月29日
5 个文件变更+163-1
Mop_plugin/config/op_plugin_functions.yaml+3-0
@@ -7156,6 +7156,9 @@ custom:
7156 op_api: all_version7156 op_api: all_version
7157 exposed: all_version7157 exposed: all_version
7158 7158 
7159+ - func: npu_grouped_dynamic_block_quant(Tensor x, Tensor group_list, *, float min_scale=0.0, str round_mode="rint", int dst_type=291, int row_block_size=1, int col_block_size=128, int group_list_type=0) -> (Tensor, Tensor)
7160+ op_api: [v2.1, newest]
7161+ 
7159 - func: npu_recurrent_gated_delta_rule(Tensor query, Tensor key, Tensor value, Tensor(a!) state, *, Tensor? beta=None, float? scale=None, Tensor? actual_seq_lengths=None, Tensor? ssm_state_indices=None, Tensor? num_accepted_tokens=None, Tensor? g=None, Tensor? gk=None) -> Tensor7162 - func: npu_recurrent_gated_delta_rule(Tensor query, Tensor key, Tensor value, Tensor(a!) state, *, Tensor? beta=None, float? scale=None, Tensor? actual_seq_lengths=None, Tensor? ssm_state_indices=None, Tensor? num_accepted_tokens=None, Tensor? g=None, Tensor? gk=None) -> Tensor
7160 op_api: all_version7163 op_api: all_version
7161 exposed: all_version7164 exposed: all_version
Aop_plugin/ops/opapi/GroupedDynamicBlockQuantNpuOpApi.cpp+79-0
@@ -0,0 +1,79 @@
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+// You may obtain a copy of the License at
7+//
8+// https://opensource.org/licenses/BSD-3-Clause
9+//
10+// Unless required by applicable law or agreed to in writing, software
11+// distributed under the License is distributed on an "AS IS" BASIS,
12+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+// See the License for the specific language governing permissions and
14+// limitations under the License.
15+ 
16+#include "op_plugin/OpApiInterface.h"
17+#include "op_plugin/utils/op_api_common.h"
18+ 
19+namespace op_api {
20+using npu_preparation = at_npu::native::OpPreparation;
21+namespace {
22+constexpr int64_t DIMENSION_2D = 2;
23+constexpr int64_t DIMENSION_3D = 3;
24+constexpr int64_t DIM_0 = 0;
25+constexpr int64_t DIM_1 = 1;
26+constexpr int64_t DIM_2 = 2;
27+constexpr int64_t ROW_BLOCK_SIZE_INVALID = 0;
28+}; // namespace
29+ 
30+std::tuple<at::Tensor, at::Tensor> npu_grouped_dynamic_block_quant(
31+ const at::Tensor& x,
32+ const at::Tensor& group_list,
33+ double min_scale,
34+ c10::string_view round_mode,
35+ int64_t dst_type,
36+ int64_t row_block_size,
37+ int64_t col_block_size,
38+ int64_t group_list_type)
AtlasAccountAtlasAccount
AtlasAccountAtlasAccount2025年12月29日

逻辑运算与副作用: 函数参数 'int64_t group_list_type' 在函数体内未被使用,可能是冗余参数或遗漏了实现。如果 group_list_type 应该用于确定 group_list 的处理方式(例如,是标量还是张量),那么当前代码忽略了它。

问题类型: 逻辑运算与副作用 文件路径: op_plugin/ops/opapi/GroupedDynamicBlockQuantNpuOpApi.cpp 行号: 33 问题代码:

int64_t group_list_type)

修改建议:

1. 检查 group_list_type 的预期用途,并在代码中使用它(例如,决定如何解析 group_list)。2. 如果确实不需要,考虑从函数签名中移除该参数,以避免混淆。

此评论由代码审查工具自动生成

likedislike
AtlasAccountAtlasAccount2025年12月29日

未使用的参数: 函数参数group_list_type在函数体内从未使用。这可能是设计错误,或者该参数应该用于某些逻辑判断(比如决定如何处理group_list参数)。

问题类型: 未使用的参数 文件路径: op_plugin/ops/opapi/GroupedDynamicBlockQuantNpuOpApi.cpp 行号: 33 问题代码:

int64_t group_list_type)

修改建议:

如果该参数确实不需要,应该从函数签名中移除。如果需要使用,应该在函数体内添加相应的逻辑处理。

此评论由代码审查工具自动生成

likedislike
39+{
40+ at::Tensor y;
41+ at::Tensor scale;
42+ auto y_shape = op_infer::array_to_small_vector(x.sizes());
43+ auto scale_shape = op_infer::array_to_small_vector(x.sizes());
AtlasAccount
AtlasAccountAtlasAccount2025年12月29日

代码结构与可维护性: 第38行 'auto scale_shape = op_infer::array_to_small_vector(x.sizes());' 中,scale_shape 初始化为 x 的形状,但后续根据维度修改。对于3D情况,第43行错误地重新赋值了整个向量,导致逻辑混乱。代码结构不清晰,容易出错。

问题类型: 代码结构与可维护性 文件路径: op_plugin/ops/opapi/GroupedDynamicBlockQuantNpuOpApi.cpp 行号: 38 问题代码:

auto scale_shape = op_infer::array_to_small_vector(x.sizes());

修改建议:

1. 重构 scale_shape 的计算逻辑,确保正确性。2. 考虑将2D和3D情况下的 scale 形状计算提取为单独的函数,以提高可读性。3. 添加注释说明 scale 形状的计算公式。

此评论由代码审查工具自动生成

likedislike
44+ TORCH_CHECK(row_block_size != ROW_BLOCK_SIZE_INVALID, "[npu_grouped_dynamic_block_quant]: row_block_size cannot be zero." + OPS_ERROR(ErrCode::PARAM));
45+ int64_t group_list_dim = group_list.dim();
46+ TORCH_CHECK(group_list_dim == 1, "group_list must be 1D tensor, got dim = ", group_list.dim());
47+ int64_t group_list_shape = group_list.sizes()[0];
48+ ASCEND_LOGI("[npu_grouped_dynamic_block_quant]: group_list shape is %ld.", group_list_shape);
49+ if (group_list_type == 0) {
50+ if (scale_shape.size() == DIMENSION_2D) {
51+ scale_shape[DIM_0] = scale_shape[DIM_0] / row_block_size + group_list_shape;
52+ scale_shape[DIM_1] = op_infer::CeilDiv(scale_shape[DIM_1], col_block_size);
53+ } else if (scale_shape.size() == DIMENSION_3D) {
54+ scale_shape[DIM_1] = scale_shape[DIM_1] / row_block_size + group_list_shape;
55+ scale_shape[DIM_2] = op_infer::CeilDiv(scale_shape[DIM_2], col_block_size);
56+ } else {
57+ TORCH_CHECK(false, "x must be 2 or 3 dimensional.", OPS_ERROR(ErrCode::NOT_SUPPORT));
58+ }
59+ } else {
60+ ASCEND_LOGI("[npu_grouped_dynamic_block_quant]: group_list_type only supports value 0.");
61+ }
62+
63+ ASCEND_LOGI("[npu_grouped_dynamic_block_quant]: Getting aclTensor y dtype by Parameter(dst_type): %ld", dst_type);
64+ aclDataType y_acltype = c10_npu::GetAclDataType(dst_type);
65+ at::ScalarType dtype = npu_preparation::convert_to_scalar_type(y_acltype);
66+ 
67+ y = npu_preparation::apply_tensor_without_format(y_shape, c10::dtype(dtype));
68+ scale = npu_preparation::apply_tensor_without_format(scale_shape, c10::dtype(c10::ScalarType::Float));
69+ 
70+ char *round_mode_ptr = const_cast<char *>(round_mode.data());
71+ ASCEND_LOGI("[npu_grouped_dynamic_block_quant]: Setting aclTensor y dtype to: %s",
72+ at_npu::native::AclDataTypeToString(y_acltype).c_str());
73+ TensorWrapper y_wrapper = {y, y_acltype};
74+ EXEC_NPU_CMD(aclnnGroupedDynamicBlockQuant, x, group_list, min_scale, round_mode_ptr, y_acltype,
75+ row_block_size, col_block_size, group_list_type, y_wrapper, scale);
76+ 
77+ return std::tie(y, scale);
78+}
79+}
Mop_plugin/python/meta/_meta_registrations.py+22-1
@@ -4890,4 +4890,25 @@ has_side_effect(torch.ops.npu.save_npugraph_tensor.default)
4890 4890 
4891@impl(m, "save_npugraph_tensor")4891@impl(m, "save_npugraph_tensor")
4892def save_npugraph_tensor_meta(self, *, save_path=None):4892def save_npugraph_tensor_meta(self, *, save_path=None):
4893- return4893+ return
4894+ 
4895+ 
4896+@impl(m, "npu_grouped_dynamic_block_quant")
Z

meta缺少测试用例,在test_faketensor.py中添加

likedislike
yin-peng
1月28日 评论:
4897+def npu_dynamic_block_quant_meta(x, group_list, *, min_scale=0.0, round_mode="rint", dst_type=torch.float8_e5m2, row_block_size=1, col_block_size=128, group_list_type=0):
4898+ dtype = TORCH_DTYPE_ENUM_VALUE_TO_SCALAR_TYPE_MAP.get(dst_type, torch.float8_e5m2)
4899+ y = torch.empty(x.shape, dtype=dtype, device=x.device)
4900+ scale_shape = list(x.shape)
4901+ 
4902+ if len(scale_shape) == 2:
4903+ scale_shape[0] = scale_shape[0] / row_block_size + group_list.shape[0]
4904+ scale_shape[1] = math.ceil(scale_shape[1] / col_block_size)
4905+ elif len(scale_shape) == 3:
4906+ scale_shape[1] = scale_shape[1] / row_block_size + group_list.shape[0]
4907+ scale_shape[2] = math.ceil(scale_shape[2] / col_block_size)
4908+ else:
4909+ raise RuntimeError(f"Expected x to have 2 or 3 dimensions, but got {x.dim()}.")
4910+ 
4911+ scale_shape = torch.Size(scale_shape)
4912+ 
4913+ scale = torch.empty(scale_shape, dtype=torch.float32, device=x.device)
4914+ return y, scale
Mtest/core_tests/torch_npu_OpApi_schema_all.json+3-0
@@ -4690,6 +4690,9 @@
4690 "func: npu_dynamic_block_quant(Tensor x, *, float min_scale=0.0, str round_mode=\"rint\", int dst_type=1, int row_block_size=1, int col_block_size=128) -> (Tensor y, Tensor scale)": {4690 "func: npu_dynamic_block_quant(Tensor x, *, float min_scale=0.0, str round_mode=\"rint\", int dst_type=1, int row_block_size=1, int col_block_size=128) -> (Tensor y, Tensor scale)": {
4691 "version": ["all_version"]4691 "version": ["all_version"]
4692 },4692 },
4693+ "func: npu_grouped_dynamic_block_quant(Tensor x, Tensor group_list, *, float min_scale=0.0, str round_mode=\"rint\", int dst_type=291, int row_block_size=1, int col_block_size=128, int group_list_type=0) -> (Tensor, Tensor)": {
4694+ "version": ["all_version"]
4695+ },
4693 "func: fused_linear_online_max_sum(Tensor input, Tensor weight, Tensor target, int vocab_start_index, int vocab_end_index, bool return_logits=False) -> (Tensor, Tensor, Tensor, Tensor, Tensor, Tensor)": {4696 "func: fused_linear_online_max_sum(Tensor input, Tensor weight, Tensor target, int vocab_start_index, int vocab_end_index, bool return_logits=False) -> (Tensor, Tensor, Tensor, Tensor, Tensor, Tensor)": {
4694 "version": ["all_version"]4697 "version": ["all_version"]
4695 },4698 },
Atest/test_custom_ops/test_npu_grouped_dynamic_block_quant.py+56-0
@@ -0,0 +1,56 @@
1+import math
2+import unittest
3+import copy
4+import struct
5+from struct import pack, unpack
6+import numpy as np
7+import torch
8+import torch_npu
9+from torch_npu.testing.testcase import TestCase, run_tests
10+from torch_npu.testing.common_utils import SupportedDevices
11+from torch.testing import assert_close
12+ 
13+class TestGroupedDynamicBlockQuant(TestCase):
14+ def custom_op_exec(self, input_tensor, group_list_tensor, min_scale=0.0, round_mode="rint", dst_type=291, row_block_size=1, col_block_size=128, group_list_type=0):
15+ return torch_npu.npu_grouped_dynamic_block_quant(input_tensor,
16+ group_list_tensor,
17+ min_scale=min_scale,
18+ round_mode=round_mode,
19+ dst_type=dst_type,
20+ row_block_size=row_block_size,
21+ col_block_size=col_block_size,
22+ group_list_type=group_list_type)
23+ 
24+ def supported_op_exec(self, input_tensor):
25+ if torch.all(torch.eq(input_tensor, 0.0)) and input_tensor.shape == torch.Size([1, 2]):
26+ device = input_tensor.device
27+ y = torch.tensor([[0, 0]], dtype=torch.float8_e5m2, device=device)
28+ scale = torch.tensor([[0.0], [0.0]], dtype=torch.float, device=device)
29+
30+ return y, scale
31+ 
32+ def generate_input(self, input, group_list, input_dtype="float16"):
33+ input_data_type = torch.float16 if input_dtype == "float16" else torch.bfloat16
34+ input_value = 0.0
35+ input_tensor = torch.full(input, input_value, dtype=input_data_type)
36+ group_list_data_type = torch.int32
37+ group_list_value = 1
38+ group_list_tensor = torch.full(group_list, group_list_value, dtype=group_list_data_type)
39+ 
40+ return input_tensor, group_list_tensor
41+
42+ @SupportedDevices(['Ascend910_95'])
43+ def test_npu_grouped_dynamic_block_quant(self, device="npu"):
44+ input_tensor, group_list_tensor = self.generate_input(input=[1, 2], group_list=[1], input_dtype="float16")
45+ input_tensor = input_tensor.to(device)
46+ group_list_tensor = group_list_tensor.to(device)
47+ supported_output = self.supported_op_exec(input_tensor.clone())
48+ custom_output = self.custom_op_exec(input_tensor.clone(), group_list_tensor.clone(), 0.0, "rint", 291, 1, 128, 0)
49+ y = custom_output[0].view([1, 2]).view(torch.uint8)
50+ scale = custom_output[1].view([2, 1])
51+ 
52+ assert torch.all(y == supported_output[0].view(torch.uint8))
53+ assert_close(supported_output[1], scale, atol=0.01, rtol=0.001)
54+
55+if __name__ == "__main__":
56+ run_tests()