Merged
【feat】: 支持子字节dtype(DT_INT4)的ND/FRACTAL_NZ格式转换与常量折叠防护 #5199
kantao1Add on Sep 28
【feat】: 支持子字节dtype(DT_INT4)的ND/FRACTAL_NZ格式转换与常量折叠防护 #5199
Merged
kantao1Add on Sep 28
Total: 18 files changed+1794-51
@@ -12,8 +12,10 @@
12 12 
13#include <securec.h>13#include <securec.h>
14#include <memory>14#include <memory>
15+#include <vector>
15 16 
16#include "graph_metadef/common/ge_common/util.h"17#include "graph_metadef/common/ge_common/util.h"
18+#include "graph_metadef/graph/debug/ge_util.h"
17#include "formats/utils/formats_definitions.h"19#include "formats/utils/formats_definitions.h"
18#include "formats/utils/formats_trans_utils.h"20#include "formats/utils/formats_trans_utils.h"
19#include "framework/common/debug/ge_log.h"21#include "framework/common/debug/ge_log.h"
@@ -88,6 +88,12 @@ bool IsTransposeArgValid(const uint8_t *const src, const std::vector<int64_t> &s
88 TypeUtils::DataTypeToSerialString(src_data_type).c_str());88 TypeUtils::DataTypeToSerialString(src_data_type).c_str());
89 return false;89 return false;
90 }90 }
91+ // 子字节类型(如 DT_INT4)GetSizeByDataType 返回位宽编码值,逐元素字节拷贝不适用
92+ if (GetSizeByDataType(src_data_type) > kDataTypeSizeBitOffset) {
93+ GELOGE(ACL_ERROR_GE_DATATYPE_INVALID, "[Trans][Param]Failed, the sub-byte data type %s is not supported",
94+ TypeUtils::DataTypeToSerialString(src_data_type).c_str());
95+ return false;
96+ }
91 return IsShapeArgValid(src_shape, perm_arg);97 return IsShapeArgValid(src_shape, perm_arg);
92}98}
93 99 
@@ -27,7 +27,72 @@
27 27 
28namespace ge {28namespace ge {
29namespace formats {29namespace formats {
30+namespace {
31+// Element-level format transfer indexes data by per-element byte size.
32+// Sub-byte types (e.g. DT_INT4) encode bit-width in GetSizeByDataType (kDataTypeSizeBitOffset + bits),
33+// which is not a real byte size, so they cannot be processed by existing transfers,
34+// except the fractal NZ family transfers which handle packed data by bit-width.
35+bool IsSubByteDataType(const DataType data_type) {
36+ return GetSizeByDataType(data_type) > kDataTypeSizeBitOffset;
37+}
38+ 
39+bool IsNdFamilyFormat(const Format format) {
40+ const auto primary = static_cast<Format>(GetPrimaryFormat(static_cast<int32_t>(format)));
41+ return (primary == FORMAT_ND) || (primary == FORMAT_NCHW) || (primary == FORMAT_NHWC);
42+}
43+ 
44+bool IsFractalNzFamilyFormat(const Format format) {
45+ const auto primary = static_cast<Format>(GetPrimaryFormat(static_cast<int32_t>(format)));
46+ return (primary == FORMAT_FRACTAL_NZ) || (primary == FORMAT_FRACTAL_NZ_C0_2) || (primary == FORMAT_FRACTAL_NZ_C0_4) ||
47+ (primary == FORMAT_FRACTAL_NZ_C0_8) || (primary == FORMAT_FRACTAL_NZ_C0_16) ||
48+ (primary == FORMAT_FRACTAL_NZ_C0_32);
49+}
50+ 
51+// 判断格式是否显式携带 C0:primary 枚举 C0 变体(如 FRACTAL_NZ_C0_16/C0_32)或组合位域
52+bool HasExplicitC0(const Format format) {
53+ if (GetC0Value(static_cast<int32_t>(format)) > 0) {
54+ return true;
55+ }
56+ const auto primary = static_cast<Format>(GetPrimaryFormat(static_cast<int32_t>(format)));
57+ return (primary == FORMAT_FRACTAL_NZ_C0_2) || (primary == FORMAT_FRACTAL_NZ_C0_4) ||
58+ (primary == FORMAT_FRACTAL_NZ_C0_8) || (primary == FORMAT_FRACTAL_NZ_C0_16) ||
59+ (primary == FORMAT_FRACTAL_NZ_C0_32);
60+}
61+ 
62+bool IsSubByteSupportedTransfer(const TransArgs &args) {
63+ const bool nd_to_nz = IsNdFamilyFormat(args.src_format) && IsFractalNzFamilyFormat(args.dst_format);
64+ const bool nz_to_nd = IsFractalNzFamilyFormat(args.src_format) && IsNdFamilyFormat(args.dst_format);
65+ if (!nd_to_nz && !nz_to_nd) {
66+ return false;
67+ }
68+ // 子字节场景要求 NZ 侧显式携带 C0,裸 FORMAT_FRACTAL_NZ 无法确定 C0,直接拒绝
69+ if (!HasExplicitC0(args.src_format) && !HasExplicitC0(args.dst_format)) {
70+ return false;
71+ }
72+ // 显式 C0 的 NZ 格式仅注册了与 FORMAT_ND 的互转,门禁与注册表口径保持一致,
73+ // 避免落入 BuildFormatTransfer 后报误导性 FORMAT_INVALID
74+ return (args.src_format == FORMAT_ND) || (args.dst_format == FORMAT_ND);
75+}
76+} // namespace
77+ 
30Status TransDataFormat(const TransArgs &args, TransResult &result) {78Status TransDataFormat(const TransArgs &args, TransResult &result) {
79+ if (IsSubByteDataType(args.src_data_type)) {
80+ // 子字节类型仅放行 ND/NCHW/NHWC 与 FRACTAL_NZ 族互转(packed 位级转换),其余拒绝
81+ GELOGI("TransDataFormat sub-byte dtype %s, src format %s -> dst format %s, supported transfer %d",
82+ TypeUtils::DataTypeToSerialString(args.src_data_type).c_str(),
83+ TypeUtils::FormatToSerialString(args.src_format).c_str(),
84+ TypeUtils::FormatToSerialString(args.dst_format).c_str(), IsSubByteSupportedTransfer(args) ? 1 : 0);
85+ if (!IsSubByteSupportedTransfer(args)) {
86+ const std::string error = "Failed to trans data format, sub-byte data type " +
87+ FmtToStr(TypeUtils::DataTypeToSerialString(args.src_data_type)) + " transfer from " +
88+ TypeUtils::FormatToSerialString(args.src_format) + " to " +
89+ TypeUtils::FormatToSerialString(args.dst_format) +
90+ " is not supported, only nd <-> fractal nz with explicit c0 is supported";
91+ GE_ERRORLOG_AND_ERRORMSG(ACL_ERROR_GE_DATATYPE_INVALID, error.c_str());
92+ return ACL_ERROR_GE_DATATYPE_INVALID;
93+ }
94+ }
95+ 
31 const auto transfer = BuildFormatTransfer(args);96 const auto transfer = BuildFormatTransfer(args);
32 if (transfer == nullptr) {97 if (transfer == nullptr) {
33 const std::string error = "Failed to trans data from format " +98 const std::string error = "Failed to trans data from format " +
@@ -160,7 +160,8 @@ int GetDataTypeBits(const ge::DataType data_type) {
160 if (data_type_size <= 0) {160 if (data_type_size <= 0) {
161 return data_type_size;161 return data_type_size;
162 }162 }
163- if (data_type > ge::kDataTypeSizeBitOffset) {163+ // 子字节类型(如 DT_INT4)GetSizeByDataType 返回位宽编码值(kDataTypeSizeBitOffset + bits)
164+ if (data_type_size > ge::kDataTypeSizeBitOffset) {
164 return data_type_size - ge::kDataTypeSizeBitOffset;165 return data_type_size - ge::kDataTypeSizeBitOffset;
165 }166 }
166 return data_type_size * ge::kBitNumOfOneByte;167 return data_type_size * ge::kBitNumOfOneByte;
@@ -12,6 +12,7 @@
12 12 
13#include <memory>13#include <memory>
14#include <new>14#include <new>
15+#include <set>
15#include <utility>16#include <utility>
16#include <vector>17#include <vector>
17#include "graph_metadef/common/ge_common/util.h"18#include "graph_metadef/common/ge_common/util.h"
@@ -28,8 +29,10 @@
28#include "graph/ir_definitions_recover.h"29#include "graph/ir_definitions_recover.h"
29#include "graph/op_desc.h"30#include "graph/op_desc.h"
30#include "graph/operator_factory.h"31#include "graph/operator_factory.h"
32+#include "graph/types.h"
31#include "graph/utils/node_utils.h"33#include "graph/utils/node_utils.h"
32#include "graph/utils/constant_utils.h"34#include "graph/utils/constant_utils.h"
35+#include "graph/utils/type_utils.h"
33#include "host_cpu_engine/host_cpu_engine.h"36#include "host_cpu_engine/host_cpu_engine.h"
34#include "api/gelib/gelib.h"37#include "api/gelib/gelib.h"
35#include "register/op_kernel_registry.h"38#include "register/op_kernel_registry.h"
@@ -40,6 +43,41 @@ namespace {
40const int64_t kStartCallNum = 1;43const int64_t kStartCallNum = 1;
41const int64_t kShapeCalNum = 8;44const int64_t kShapeCalNum = 8;
42const char *const kKernelLibName = "aicpu_ascend_kernel";45const char *const kKernelLibName = "aicpu_ascend_kernel";
46+ 
47+// 子字节类型(如 DT_INT4)GetSizeByDataType 返回位宽编码值(kDataTypeSizeBitOffset + bits),并非真实字节数
48+bool IsSubByteDtype(const DataType dtype) {
49+ return GetSizeByDataType(dtype) > kDataTypeSizeBitOffset;
50+}
51+ 
52+// 子字节 dtype HostCpuOp 折叠白名单:已在 Compute 中实现 packed 位级处理的算子
53+// (如 QuantBiasOptimization 的 INT4 packed→int8 解包)可安全折叠,放行输入检查
54+const std::set<std::string> kSubByteFoldWhiteList = {"QuantBiasOptimization"};
55+ 
56+// 子字节类型(如 DT_INT4)的数据为 packed 存储(2元素/字节),需按位读写处理;
57+// HostCpuOp/自定义算子实现可能缺少位级处理导致越界写,需拦截并回落 GE 内置 kernel
58+// (TransdataKernel 等已适配子字节位级转换)。
59+// 覆盖 top/bottom 优先级自定义算子与 legacy HostCpuOp 全部路径;
60+// 白名单内的算子已在 Compute 中实现子字节 packed 位级处理(如 QuantBiasOptimization),放行输入检查
61+bool IsSubByteFoldingBlocked(const std::string &op_type, const NodePtr &node,
62+ const std::vector<ConstGeTensorPtr> &inputs) {
63+ if (kSubByteFoldWhiteList.count(op_type) == 0U) {
64+ for (const auto &input : inputs) {
65+ if ((input != nullptr) && IsSubByteDtype(input->GetTensorDesc().GetDataType())) {
66+ GELOGI("Sub-byte dtype %s folding skips HostCpuOp for op %s, fallback to built-in kernel.",
67+ TypeUtils::DataTypeToSerialString(input->GetTensorDesc().GetDataType()).c_str(), op_type.c_str());
68+ return true;
69+ }
70+ }
71+ }
72+ for (const auto &output_desc : node->GetOpDesc()->GetAllOutputsDesc()) {
73+ if (IsSubByteDtype(output_desc.GetDataType())) {
74+ GELOGI("Sub-byte dtype %s folding skips HostCpuOp for op %s, fallback to built-in kernel.",
75+ TypeUtils::DataTypeToSerialString(output_desc.GetDataType()).c_str(), op_type.c_str());
76+ return true;
77+ }
78+ }
79+ return false;
80+}
43const char *const kOpsFlagClose = "0";81const char *const kOpsFlagClose = "0";
44const char *const kPassName = "ConstantFoldingPass";82const char *const kPassName = "ConstantFoldingPass";
45 83 
@@ -332,6 +370,9 @@ Status ConstantFoldingPass::ComputeWithHostCpuCustomOp(const NodePtr &node, cons
332 std::vector<GeTensorPtr> &outputs) {370 std::vector<GeTensorPtr> &outputs) {
333 const std::string op_type = NodeUtils::GetNodeType(node);371 const std::string op_type = NodeUtils::GetNodeType(node);
334 const AscendString op_type_str(op_type.c_str());372 const AscendString op_type_str(op_type.c_str());
373+ if (IsSubByteFoldingBlocked(op_type, node, inputs)) {
374+ return UNSUPPORTED;
375+ }
335 if (!CustomOpFactory::IsExistOp(op_type_str, OpBackend::kHostCPU)) {376 if (!CustomOpFactory::IsExistOp(op_type_str, OpBackend::kHostCPU)) {
336 GELOGD("Op of type %s is not supported by host cpu custom op.", op_type.c_str());377 GELOGD("Op of type %s is not supported by host cpu custom op.", op_type.c_str());
337 return UNSUPPORTED;378 return UNSUPPORTED;
@@ -385,6 +426,9 @@ Status ConstantFoldingPass::RunOpKernel(const NodePtr &node, const std::vector<C
385 std::vector<GeTensorPtr> &outputs) {426 std::vector<GeTensorPtr> &outputs) {
386 const std::string op_type = NodeUtils::GetNodeType(node);427 const std::string op_type = NodeUtils::GetNodeType(node);
387 const AscendString op_type_str(op_type.c_str());428 const AscendString op_type_str(op_type.c_str());
429+ if (IsSubByteFoldingBlocked(op_type, node, inputs)) {
430+ return UNSUPPORTED;
431+ }
388 if (CustomOpFactory::IsExistOp(op_type_str, OpBackend::kHostCPU, OpRegistrationPriority::kBottom,432 if (CustomOpFactory::IsExistOp(op_type_str, OpBackend::kHostCPU, OpRegistrationPriority::kBottom,
389 OpEngine::kHostCpu)) {433 OpEngine::kHostCpu)) {
390 GELOGD("Bottom priority host cpu op is registered. op type = %s, engine = %s.", op_type.c_str(), "HOST_CPU");434 GELOGD("Bottom priority host cpu op is registered. op type = %s, engine = %s.", op_type.c_str(), "HOST_CPU");
@@ -15,6 +15,7 @@
15#include <climits>15#include <climits>
16 16 
17#include "framework/common/framework_types_internal.h"17#include "framework/common/framework_types_internal.h"
18+#include "graph/types.h"
18#include "graph/utils/op_desc_utils.h"19#include "graph/utils/op_desc_utils.h"
19#include "graph/utils/tensor_utils.h"20#include "graph/utils/tensor_utils.h"
20#include "graph/utils/type_utils.h"21#include "graph/utils/type_utils.h"
@@ -177,12 +178,6 @@ bool KernelUtils::CheckSizeForTransOp(const ge::ConstGeTensorPtr &const_weight_p
177 return false;178 return false;
178 }179 }
179 180 
180- uint32_t length = 1U;
181- if (!TypeUtils::GetDataTypeLength(data_type, length)) {
182- GELOGE(PARAM_INVALID, "Input datatype %d is not supported.", data_type);
183- return false;
184- }
185- 
186 GELOGI("Const real value Size:%zu, op_desc Shape Size:%ld, data_type:%s.", data_size, cal_size,181 GELOGI("Const real value Size:%zu, op_desc Shape Size:%ld, data_type:%s.", data_size, cal_size,
187 TypeUtils::DataTypeToSerialString(data_type).c_str());182 TypeUtils::DataTypeToSerialString(data_type).c_str());
188 if (shape_size != 0) {183 if (shape_size != 0) {
@@ -199,7 +194,9 @@ bool KernelUtils::CheckSizeForTransOp(const ge::ConstGeTensorPtr &const_weight_p
199 }194 }
200 } else {195 } else {
201 // Scalar tensor, has only one element in tensor196 // Scalar tensor, has only one element in tensor
202- if ((length != 0U) && ((data_size / static_cast<size_t>(length)) != 1U)) {197+ // 子字节类型(如 DT_INT4)按位宽 CeilDiv 得到单元素 packed 字节数
198+ const int64_t expect_scalar_size = ge::GetSizeInBytes(1, data_type);
199+ if ((expect_scalar_size <= 0) || (data_size != static_cast<size_t>(expect_scalar_size))) {
203 GELOGW("Const input data size is not equal with tensor desc shape");200 GELOGW("Const input data size is not equal with tensor desc shape");
204 return false;201 return false;
205 }202 }
@@ -19,6 +19,7 @@
19#include "framework/common/op/ge_op_utils.h"19#include "framework/common/op/ge_op_utils.h"
20#include "framework/common/debug/ge_log.h"20#include "framework/common/debug/ge_log.h"
21#include "graph/debug/ge_attr_define.h"21#include "graph/debug/ge_attr_define.h"
22+#include "graph/types.h"
22#include "host_kernels/kernel_utils.h"23#include "host_kernels/kernel_utils.h"
23#include "graph/utils/type_utils.h"24#include "graph/utils/type_utils.h"
24#include "host_kernels/kernel_factory.h"25#include "host_kernels/kernel_factory.h"
@@ -78,6 +79,12 @@ Status PackKernel::ValidateKernelParams(const ge::OpDescPtr &op_desc_ptr,
78 return PARAM_INVALID;79 return PARAM_INVALID;
79 }80 }
80 data_type_ = op_desc_ptr->GetInputDesc(0).GetDataType();81 data_type_ = op_desc_ptr->GetInputDesc(0).GetDataType();
82+ // 子字节类型(如 DT_INT4)的 Pack 需要位级打包,逐元素字节拷贝逻辑不适用,安全跳过折叠
83+ if (GetSizeByDataType(data_type_) > kDataTypeSizeBitOffset) {
84+ GELOGI("Pack kernel skips sub-byte data type %s, ignore folding.",
85+ TypeUtils::DataTypeToSerialString(data_type_).c_str());
86+ return NOT_CHANGED;
87+ }
81 GeShape shape = op_desc_ptr->GetInputDesc(0).GetShape();88 GeShape shape = op_desc_ptr->GetInputDesc(0).GetShape();
82 if (axis_ < 0 || axis_ > (static_cast<int64_t>(shape.GetDimNum()) + 1)) {89 if (axis_ < 0 || axis_ > (static_cast<int64_t>(shape.GetDimNum()) + 1)) {
83 GELOGW("Axis is %ld ,which is out of range [0,R+1].", axis_);90 GELOGW("Axis is %ld ,which is out of range [0,R+1].", axis_);
@@ -70,9 +70,11 @@ Tensor *EagerOpExecutionContext::MallocOutputTensor(size_t index, const StorageS
70 GE_ASSERT_NOTNULL(output_tensor);70 GE_ASSERT_NOTNULL(output_tensor);
71 71 
72 SetTensorDesc(shape, format, dtype, output_tensor);72 SetTensorDesc(shape, format, dtype, output_tensor);
73- const size_t tensor_size = shape.GetStorageShape().GetShapeSize() * GetSizeByDataType(dtype);73+ // 子字节类型(如 DT_INT4)按位宽 CeilDiv 换算 packed 字节数
74- size_t aligned_tensor_size = tensor_size;74+ const int64_t byte_size = ge::GetSizeInBytes(shape.GetStorageShape().GetShapeSize(), dtype);
75- GE_ASSERT_TRUE(!ge::RoundUpOverflow(tensor_size, kMemAlignment, aligned_tensor_size));75+ GE_ASSERT_TRUE(byte_size >= 0, "Get tensor size in bytes failed, dtype: %d", static_cast<int>(dtype));
76+ size_t aligned_tensor_size = 0U;
77+ GE_ASSERT_TRUE(!ge::RoundUpOverflow(static_cast<size_t>(byte_size), kMemAlignment, aligned_tensor_size));
76 const TensorData &tensor_data = output_tensor->GetTensorData();78 const TensorData &tensor_data = output_tensor->GetTensorData();
77 // 静态场景下内存地址已赋值 不需要去Malloc79 // 静态场景下内存地址已赋值 不需要去Malloc
78 // 注意:静态图逻辑地址可能是合法的0(偏移),不能用 GetAddr() != nullptr 判断80 // 注意:静态图逻辑地址可能是合法的0(偏移),不能用 GetAddr() != nullptr 判断
@@ -1274,7 +1274,9 @@ bool TransferShapeUtils::GetNdToNzAlignedShape(const AlignShapeInfo &align_shape
1274 (void)aligned_shape.SetDim(i, 1);1274 (void)aligned_shape.SetDim(i, 1);
1275 }1275 }
1276 int64_t m0 = GetM0ByDtype(align_shape_info.data_type);1276 int64_t m0 = GetM0ByDtype(align_shape_info.data_type);
1277- int64_t c0 = GetC0ByDtype(align_shape_info.data_type);1277+ // 统一 C0 解析口径:primary 枚举 C0 变体(如 FRACTAL_NZ_C0_16/C0_32)、组合位域、dtype 回退,
1278+ // 与格式转换侧(GetEffectiveC0)保持一致
1279+ const int64_t c0 = GetC0Value(align_shape_info.data_type, align_shape_info.dst_format);
1278 (void)aligned_shape.SetDim(target_index, m0);1280 (void)aligned_shape.SetDim(target_index, m0);
1279 (void)aligned_shape.SetDim(target_index + 1, c0);1281 (void)aligned_shape.SetDim(target_index + 1, c0);
1280 return true;1282 return true;
@@ -46,7 +46,13 @@ struct WeightInfo {
46 }46 }
47 47 
48 if ((shape_size > 0) && (datatype < data_type_size.size())) {48 if ((shape_size > 0) && (datatype < data_type_size.size())) {
49- total_data_size = (static_cast<size_t>(shape_size)) * data_type_size[datatype];49+ // 子字节类型(如 DT_INT4)按位宽 CeilDiv 换算 packed 字节数,否则高估导致越界写
50+ if (ge::GetSizeByDataType(datatype) > ge::kDataTypeSizeBitOffset) {
51+ const int64_t byte_size = ge::GetSizeInBytes(shape_size, datatype);
52+ total_data_size = (byte_size > 0) ? static_cast<size_t>(byte_size) : 0U;
53+ } else {
54+ total_data_size = (static_cast<size_t>(shape_size)) * data_type_size[datatype];
55+ }
50 } else {56 } else {
51 total_data_size = 0;57 total_data_size = 0;
52 }58 }
@@ -353,6 +353,7 @@ target_link_libraries(ge_common_atc
353add_executable(graph_engine_compile_test353add_executable(graph_engine_compile_test
354 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_main.cc"354 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_main.cc"
355 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_file_constant.cc"355 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_file_constant.cc"
356+ "${AIR_CODE_DIR}/tests/ge/st/testcase/test_sub_byte_format.cc"
356 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_data_slice.cc"357 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_data_slice.cc"
357 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_swap_pass.cc"358 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_swap_pass.cc"
358 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_mem_layout_conflict.cc"359 "${AIR_CODE_DIR}/tests/ge/st/testcase/test_mem_layout_conflict.cc"
@@ -14,6 +14,7 @@
14#include <graph/operator_reg.h>14#include <graph/operator_reg.h>
15#include <ge_running_env/fake_op.h>15#include <ge_running_env/fake_op.h>
16#include <ge_running_env/ge_running_env_faker.h>16#include <ge_running_env/ge_running_env_faker.h>
17+#include <ge_running_env/tensor_utils.h>
17 18 
18#include "ge/ge_api.h"19#include "ge/ge_api.h"
19#include "graph/debug/ge_attr_define.h"20#include "graph/debug/ge_attr_define.h"
@@ -154,7 +155,8 @@ class ConstantFoldingTest : public testing::Test {
154 .Install(FakeOp(SQUEEZEV3).InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction))155 .Install(FakeOp(SQUEEZEV3).InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction))
155 .Install(FakeOp(ClipByValue).InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction))156 .Install(FakeOp(ClipByValue).InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction))
156 .Install(FakeOp(CAST).InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction))157 .Install(FakeOp(CAST).InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction))
157- .Install(FakeOp("FakeWhere").InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction));158+ .Install(FakeOp("FakeWhere").InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction))
159+ .Install(FakeOp(TRANSDATA).InfoStoreAndBuilder("DNN_VM_GE_LOCAL_OP_STORE").InferShape(StubInferFunction));
158 }160 }
159 void TearDown() {161 void TearDown() {
160 std::cout << "End constant folding st" << std::endl;162 std::cout << "End constant folding st" << std::endl;
@@ -811,3 +813,26 @@ TEST_F(ConstantFoldingTest, HostCpuCustomOpConstantFoldingCopiesTensor) {
811 };813 };
812 CustomOpFactory::RemoveCustomOps({op_type});814 CustomOpFactory::RemoveCustomOps({op_type});
813}815}
816+ 
817+// INT4 constant weight folded via TransData (ND -> FRACTAL_NZ_C0_16),
818+// covering the sub-byte (packed) format transfer chain in ST.
819+TEST_F(ConstantFoldingTest, test_int4_transdata_folding) {
820+ Graph graph;
821+ {
822+ auto weight = GenerateTensor(DT_INT4, {2, 32});
823+ auto transdata = OP_CFG(TRANSDATA).TensorDesc(FORMAT_FRACTAL_NZ_C0_16, DT_INT4, {1, 2, 1, 16, 16});
824+ auto netoutput = OP_CFG(NETOUTPUT).TensorDesc(FORMAT_FRACTAL_NZ_C0_16, DT_INT4, {1, 2, 1, 16, 16});
825+ auto constant = OP_CFG(CONSTANT).Weight(weight).TensorDesc(FORMAT_ND, DT_INT4, {2, 32});
826+ DEF_GRAPH(g1) {
827+ CHAIN(NODE("int4_const", constant)->EDGE(0, 0)->NODE("int4_transdata", transdata));
828+ CHAIN(NODE("int4_transdata", transdata)->EDGE(0, 0)->NODE("netoutput", netoutput));
829+ };
830+ graph = ToGeGraph(g1);
831+ }
832+ map<AscendString, AscendString> options;
833+ Session session(options);
834+ session.AddGraph(1, graph, options);
835+ std::vector<InputTensorInfo> inputs;
836+ auto ret = session.BuildGraph(1, inputs);
837+ EXPECT_EQ(ret, SUCCESS);
838+}
@@ -0,0 +1,561 @@
1+/**
2+ * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3+ * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4+ * CANN Open Software License Agreement Version 2.0 (the "License").
5+ * Please refer to the License for details. You may not use this file except in compliance with the License.
6+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7+ * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
8+ * See LICENSE in the root of the software repository for the full text of the License.
9+ */
10+ 
11+#include <gtest/gtest.h>
12+#include <map>
13+#include <vector>
14+ 
15+#include "formats/format_transfers/format_transfer_fractal_nz.h"
16+#include "formats/format_transfers/format_transfer_transpose.h"
17+#include "formats/formats.h"
18+#include "transfer_shape_utils.h"
19+#include "framework/common/ge_inner_error_codes.h"
20+#include "graph/types.h"
21+#include "graph/utils/type_utils.h"
22+#include "host_kernels/kernel.h"
23+#include "host_kernels/kernel_factory.h"
24+#include "host_kernels/kernel_utils.h"
25+#include "register/graph_optimizer/fusion_common/fusion_turbo.h"
26+#include "register/op_kernel_registry.h"
27+ 
28+using namespace std;
29+using namespace ge;
30+ 
31+namespace {
32+uint8_t MakeInt4Byte(const uint8_t lo, const uint8_t hi) {
33+ return static_cast<uint8_t>((lo & 0x0FU) | static_cast<uint8_t>((hi & 0x0FU) << 4));
34+}
35+ 
36+std::vector<uint8_t> MakeInt4PackedData(const size_t elem_count) {
37+ std::vector<uint8_t> data((elem_count + 1U) / 2U, 0);
38+ for (size_t i = 0U; i < elem_count; ++i) {
39+ const auto value = static_cast<uint8_t>(i % 16U);
40+ if ((i % 2U) == 0U) {
41+ data[i / 2U] = MakeInt4Byte(value, data[i / 2U] >> 4);
42+ } else {
43+ data[i / 2U] = MakeInt4Byte(data[i / 2U] & 0x0FU, value);
44+ }
45+ }
46+ return data;
47+}
48+ 
49+uint8_t GetInt4Elem(const uint8_t *const data, const size_t elem_idx) {
50+ const uint8_t byte = data[elem_idx / 2U];
51+ return ((elem_idx % 2U) == 0U) ? static_cast<uint8_t>(byte & 0x0FU) : static_cast<uint8_t>(byte >> 4);
52+}
53+} // namespace
54+ 
55+namespace ge {
56+namespace formats {
57+class StestSubByteFormat : public testing::Test {
58+ protected:
59+ void SetUp() {}
60+ void TearDown() {}
61+};
62+ 
63+// ND -> NZ (sub-byte): W aligned to C0, layout check + padding
64+TEST_F(StestSubByteFormat, nd_to_nz_c0_16_success) {
65+ const std::vector<uint8_t> src = MakeInt4PackedData(2U * 32U);
66+ const Format src_format = static_cast<Format>(GetFormatFromSubAndC0(FORMAT_ND, FORMAT_RESERVED, 5));
67+ const Format dst_format = static_cast<Format>(GetFormatFromSubAndC0(FORMAT_FRACTAL_NZ, FORMAT_RESERVED, 5));
68+ TransArgs args{src.data(), src_format, dst_format, FORMAT_ND, FORMAT_FRACTAL_NZ, FORMAT_RESERVED,
69+ FORMAT_RESERVED, 16, 16, {2, 32}, {1, 2, 1, 16, 16}, DT_INT4};
70+ TransResult result;
71+ FormatTransferFractalNz transfer;
72+ ASSERT_EQ(transfer.TransFormat(args, result), SUCCESS);
73+ EXPECT_EQ(result.length, (1U * 2U * 1U * 16U * 16U) / 2U);
74+ const auto elem_at = [&result](const size_t r, const size_t w) {
75+ const size_t idx = (w / 16U) * 256U + r * 16U + (w % 16U);
76+ return GetInt4Elem(result.data.get(), idx);
77+ };
78+ EXPECT_EQ(elem_at(0, 0), 0U);
79+ EXPECT_EQ(elem_at(0, 1), 1U);
80+ EXPECT_EQ(elem_at(1, 17), (1U * 32U + 17U) % 16U);
81+ EXPECT_EQ(elem_at(3, 20), 0U);
82+}
83+ 
84+// ND -> NZ -> ND round trip keeps data
85+TEST_F(StestSubByteFormat, nd_to_nz_round_trip) {
86+ const std::vector<uint8_t> src = MakeInt4PackedData(2U * 32U);
87+ const Format nd_format = static_cast<Format>(GetFormatFromSubAndC0(FORMAT_ND, FORMAT_RESERVED, 5));
88+ const Format nz_format = static_cast<Format>(GetFormatFromSubAndC0(FORMAT_FRACTAL_NZ, FORMAT_RESERVED, 5));
89+ TransArgs to_nz{src.data(), nd_format, nz_format, FORMAT_ND, FORMAT_FRACTAL_NZ, FORMAT_RESERVED,
90+ FORMAT_RESERVED, 16, 16, {2, 32}, {1, 2, 1, 16, 16}, DT_INT4};
91+ TransResult nz_result;
92+ FormatTransferFractalNz transfer;
93+ ASSERT_EQ(transfer.TransFormat(to_nz, nz_result), SUCCESS);
94+ TransArgs to_nd{nz_result.data.get(), nz_format, nd_format, FORMAT_FRACTAL_NZ, FORMAT_ND, FORMAT_RESERVED,
95+ FORMAT_RESERVED, 16, 16, {1, 2, 1, 16, 16}, {2, 32}, DT_INT4};
96+ TransResult nd_result;
97+ FormatTransferFractalNzND reverse_transfer;
98+ ASSERT_EQ(reverse_transfer.TransFormat(to_nd, nd_result), SUCCESS);
99+ ASSERT_EQ(nd_result.length, src.size());
100+ for (size_t i = 0U; i < src.size(); ++i) {
@@ -13,6 +13,7 @@
13 13 
14#include "formats/format_transfers/format_transfer_fractal_nz.h"14#include "formats/format_transfers/format_transfer_fractal_nz.h"
15#include "formats/formats.h"15#include "formats/formats.h"
16+#include "graph/types.h"
16#include "time.h"17#include "time.h"
17#include "framework/common/ge_inner_error_codes.h"18#include "framework/common/ge_inner_error_codes.h"
18 19 
@@ -5002,5 +5002,13 @@ TEST_F(UtestFormatTranspose, null_src_transpose) {
5002 TransResult result;5002 TransResult result;
5003 EXPECT_EQ(Transpose(nullptr, {2, 2}, DT_FLOAT16, {1, 0}, result), ACL_ERROR_GE_PARAM_INVALID);5003 EXPECT_EQ(Transpose(nullptr, {2, 2}, DT_FLOAT16, {1, 0}, result), ACL_ERROR_GE_PARAM_INVALID);
5004}5004}
5005+// sub-byte dtype is rejected by transpose arg validation
5006+TEST_F(UtestFormatTranspose, sub_byte_rejected) {
5007+ uint8_t data[4] = {1, 2, 3, 4};
5008+ TransResult result;
5009+ EXPECT_EQ(TransposeWithShapeCheck(data, std::vector<int64_t>({2, 2}), std::vector<int64_t>({2, 2}), DT_INT4,
5010+ std::vector<int64_t>({1, 0}), result),
5011+ ACL_ERROR_GE_PARAM_INVALID);
5012+}
5005} // namespace formats5013} // namespace formats
5006} // namespace ge5014} // namespace ge
@@ -340,6 +340,31 @@ NodePtr BuildHostCpuFoldNode(const std::string &op_type) {
340 return output;340 return output;
341}341}
342 342 
343+GeTensorPtr MakeHostCpuInputTensor(DataType data_type) {
344+ auto input_tensor = MakeShared<GeTensor>();
345+ if (input_tensor == nullptr) {
346+ return nullptr;
347+ }
348+ input_tensor->MutableTensorDesc().SetShape(GeShape({8}));
349+ input_tensor->MutableTensorDesc().SetOriginShape(GeShape({8}));
350+ input_tensor->MutableTensorDesc().SetFormat(FORMAT_NCHW);
351+ input_tensor->MutableTensorDesc().SetOriginFormat(FORMAT_NCHW);
352+ input_tensor->MutableTensorDesc().SetDataType(data_type);
353+ input_tensor->MutableTensorDesc().SetOriginDataType(data_type);
354+ if (input_tensor->SetData(std::vector<uint8_t>{1, 2, 3, 4}) != SUCCESS) {
355+ return nullptr;
356+ }
357+ return input_tensor;
358+}
359+ 
360+NodePtr BuildHostCpuFoldNode(const std::string &op_type, DataType output_dtype) {
361+ auto builder = ut::GraphBuilder("test");
362+ auto input = builder.AddNode("input", CONSTANT, 0, 1, FORMAT_NCHW, DT_UINT8, {3});
363+ auto output = builder.AddNode("output", op_type, 1, 1, FORMAT_NCHW, output_dtype, {3});
364+ builder.AddDataEdge(input, 0, output, 0);
365+ return output;
366+}
367+ 
343/**368/**
344 * netoutput1369 * netoutput1
345 * |370 * |
@@ -1536,4 +1561,65 @@ TEST_F(UtestGraphPassesConstantFoldingPass, test_compute_with_host_cpu_custom_op
1536 EXPECT_FALSE(node->GetOpDesc()->GetIrInputs().empty());1561 EXPECT_FALSE(node->GetOpDesc()->GetIrInputs().empty());
1537 EXPECT_FALSE(node->GetOpDesc()->GetIrOutputs().empty());1562 EXPECT_FALSE(node->GetOpDesc()->GetIrOutputs().empty());
1538}1563}
1564+ 
1565+// ----------------------- sub-byte folding gate tests -----------------------
1566+ 
1567+TEST_F(UtestGraphPassesConstantFoldingPass, sub_byte_input_blocked_for_non_whitelist_op) {
1568+ TestLegacyHostCpuOp::compute_count_ = 0;
1569+ const std::string op_type = "SubByteFoldBlocked";
1570+ auto node = BuildHostCpuFoldNode(op_type);
1571+ ASSERT_NE(node, nullptr);
1572+ auto input_tensor = MakeHostCpuInputTensor(DT_INT4);
1573+ ASSERT_NE(input_tensor, nullptr);
1574+ std::vector<ConstGeTensorPtr> inputs{input_tensor};
1575+ std::vector<GeTensorPtr> outputs;
1576+ 
1577+ OpKernelRegistry::GetInstance().RegisterHostCpuOp(op_type, []() -> HostCpuOp * { return new TestLegacyHostCpuOp(); });
1578+ EXPECT_EQ(ConstantFoldingPass::RunOpKernel(node, inputs, outputs), UNSUPPORTED);
1579+ EXPECT_EQ(TestLegacyHostCpuOp::compute_count_, 0);
1580+}
1581+ 
1582+TEST_F(UtestGraphPassesConstantFoldingPass, sub_byte_input_whitelist_op_proceeds) {
1583+ TestLegacyHostCpuOp::compute_count_ = 0;
1584+ const std::string op_type = "QuantBiasOptimization";
1585+ auto node = BuildHostCpuFoldNode(op_type);
1586+ ASSERT_NE(node, nullptr);
1587+ auto input_tensor = MakeHostCpuInputTensor(DT_INT4);
1588+ ASSERT_NE(input_tensor, nullptr);
1589+ std::vector<ConstGeTensorPtr> inputs{input_tensor};
1590+ std::vector<GeTensorPtr> outputs;
1591+ 
1592+ OpKernelRegistry::GetInstance().RegisterHostCpuOp(op_type, []() -> HostCpuOp * { return new TestLegacyHostCpuOp(); });
1593+ EXPECT_EQ(ConstantFoldingPass::RunOpKernel(node, inputs, outputs), SUCCESS);
1594+ EXPECT_EQ(TestLegacyHostCpuOp::compute_count_, 1);
1595+}
1596+ 
1597+TEST_F(UtestGraphPassesConstantFoldingPass, sub_byte_output_blocked_even_for_whitelist_op) {
1598+ TestLegacyHostCpuOp::compute_count_ = 0;
1599+ const std::string op_type = "QuantBiasOptimization";
1600+ auto node = BuildHostCpuFoldNode(op_type, DT_INT4);
1601+ ASSERT_NE(node, nullptr);
1602+ auto input_tensor = MakeHostCpuInputTensor();
1603+ ASSERT_NE(input_tensor, nullptr);
1604+ std::vector<ConstGeTensorPtr> inputs{input_tensor};
1605+ std::vector<GeTensorPtr> outputs;
1606+ 
1607+ OpKernelRegistry::GetInstance().RegisterHostCpuOp(op_type, []() -> HostCpuOp * { return new TestLegacyHostCpuOp(); });
1608+ EXPECT_EQ(ConstantFoldingPass::RunOpKernel(node, inputs, outputs), UNSUPPORTED);
1609+ EXPECT_EQ(TestLegacyHostCpuOp::compute_count_, 0);
1610+}
1611+ 
1612+TEST_F(UtestGraphPassesConstantFoldingPass, sub_byte_input_blocked_for_custom_op_path) {
1613+ TestHostCustomFoldOp::execute_count_ = 0;
1614+ auto node = BuildHostCpuFoldNode("IrDefinedHostFold");
1615+ ASSERT_NE(node, nullptr);
1616+ auto input_tensor = MakeHostCpuInputTensor(DT_INT4);
1617+ ASSERT_NE(input_tensor, nullptr);
1618+ std::vector<ConstGeTensorPtr> inputs{input_tensor};
1619+ std::vector<GeTensorPtr> outputs;
1620+ 
1621+ ConstantFoldingPass pass;
1622+ EXPECT_EQ(pass.ComputeWithHostCpuCustomOp(node, inputs, outputs), UNSUPPORTED);
1623+ EXPECT_EQ(TestHostCustomFoldOp::execute_count_, 0);
1624+}
1539} // namespace ge1625} // namespace ge
@@ -108,3 +108,45 @@ TEST_F(UtestGraphPassesFoldingKernelkernelUtils, CheckSizeForTransOpSizeMismatch
108 bool ret = KernelUtils::CheckSizeForTransOp(tensor_0, op_desc_ptr);108 bool ret = KernelUtils::CheckSizeForTransOp(tensor_0, op_desc_ptr);
109 EXPECT_EQ(false, ret);109 EXPECT_EQ(false, ret);
110}110}
111+ 
112+TEST_F(UtestGraphPassesFoldingKernelkernelUtils, CheckSizeForTransOpScalarFp16) {
113+ vector<uint8_t> data_vec_0 = {0x11, 0x22};
114+ GeTensorDesc tensor_desc_0(GeShape(), FORMAT_NCHW, DT_FLOAT16);
115+ ConstGeTensorPtr tensor_0 =
116+ std::make_shared<GeTensor>(tensor_desc_0, (uint8_t *)data_vec_0.data(), data_vec_0.size());
117+ 
118+ OpDescPtr op_desc_ptr = std::make_shared<OpDesc>("TransOp", "TransOp");
119+ GeTensorDesc input_desc(GeShape(), FORMAT_NCHW, DT_FLOAT16);
120+ op_desc_ptr->AddInputDesc(input_desc);
121+ 
122+ bool ret = KernelUtils::CheckSizeForTransOp(tensor_0, op_desc_ptr);
123+ EXPECT_EQ(true, ret);
124+}
125+ 
126+TEST_F(UtestGraphPassesFoldingKernelkernelUtils, CheckSizeForTransOpScalarSubByte) {
127+ vector<uint8_t> data_vec_0 = {0x5A};
128+ GeTensorDesc tensor_desc_0(GeShape(), FORMAT_NCHW, DT_INT4);
129+ ConstGeTensorPtr tensor_0 =
130+ std::make_shared<GeTensor>(tensor_desc_0, (uint8_t *)data_vec_0.data(), data_vec_0.size());
131+ 
132+ OpDescPtr op_desc_ptr = std::make_shared<OpDesc>("TransOp", "TransOp");
133+ GeTensorDesc input_desc(GeShape(), FORMAT_NCHW, DT_INT4);
134+ op_desc_ptr->AddInputDesc(input_desc);
135+ 
136+ bool ret = KernelUtils::CheckSizeForTransOp(tensor_0, op_desc_ptr);
137+ EXPECT_EQ(true, ret);
138+}
139+ 
140+TEST_F(UtestGraphPassesFoldingKernelkernelUtils, CheckSizeForTransOpScalarSizeMismatch) {
141+ vector<uint8_t> data_vec_0 = {0x11, 0x22, 0x33};
142+ GeTensorDesc tensor_desc_0(GeShape(), FORMAT_NCHW, DT_FLOAT16);
143+ ConstGeTensorPtr tensor_0 =
144+ std::make_shared<GeTensor>(tensor_desc_0, (uint8_t *)data_vec_0.data(), data_vec_0.size());
145+ 
146+ OpDescPtr op_desc_ptr = std::make_shared<OpDesc>("TransOp", "TransOp");
147+ GeTensorDesc input_desc(GeShape(), FORMAT_NCHW, DT_FLOAT16);
148+ op_desc_ptr->AddInputDesc(input_desc);
149+ 
150+ bool ret = KernelUtils::CheckSizeForTransOp(tensor_0, op_desc_ptr);
151+ EXPECT_EQ(false, ret);
152+}
@@ -536,3 +536,41 @@ TEST_F(UtestGraphPassesFoldingKernelPackKernel, ZeroInShapeSuccess) {
536 Status status = kernel->Compute(op_desc_ptr, input, outputs);536 Status status = kernel->Compute(op_desc_ptr, input, outputs);
537 EXPECT_EQ(SUCCESS, status);537 EXPECT_EQ(SUCCESS, status);
538}538}
539+ 
540+// sub-byte dtype pack is skipped (fold ignored) to avoid byte-wise copy on packed data
541+TEST_F(UtestGraphPassesFoldingKernelPackKernel, SubByteSkipped) {
542+ OpDescPtr op_desc_ptr = std::make_shared<OpDesc>("pack", "Pack");
543+ vector<bool> is_input_const_vec = {true, true, true, true};
544+ op_desc_ptr->SetIsInputConst(is_input_const_vec);
545+ AttrUtils::SetInt(op_desc_ptr, PACK_ATTR_NAME_NUM, (int64_t)4);
546+ AttrUtils::SetInt(op_desc_ptr, ATTR_NAME_T, (int64_t)DT_INT4);
547+ AttrUtils::SetInt(op_desc_ptr, ATTR_NAME_AXIS, (int64_t)0);
548+ op_desc_ptr->AddInputDesc(0, GeTensorDesc(GeShape({1}), FORMAT_NCHW, DT_INT4));
549+ 
550+ vector<int64_t> dims_vec_0;
551+ vector<uint8_t> data_vec_0 = {0};
552+ GeTensorDesc tensor_desc_0(GeShape(dims_vec_0), FORMAT_NCHW, DT_INT4);
553+ ConstGeTensorPtr tensor_0 = std::make_shared<GeTensor>(tensor_desc_0, (uint8_t *)data_vec_0.data(), 1U);
554+ 
555+ vector<int64_t> dims_vec_1;
556+ vector<uint8_t> data_vec_1 = {0};
557+ GeTensorDesc tensor_desc_1(GeShape(dims_vec_1), FORMAT_NCHW, DT_INT4);
558+ ConstGeTensorPtr tensor_1 = std::make_shared<GeTensor>(tensor_desc_1, (uint8_t *)data_vec_1.data(), 1U);
559+ 
560+ vector<int64_t> dims_vec_2;
561+ vector<uint8_t> data_vec_2 = {0};
562+ GeTensorDesc tensor_desc_2(GeShape(dims_vec_2), FORMAT_NCHW, DT_INT4);
563+ ConstGeTensorPtr tensor_2 = std::make_shared<GeTensor>(tensor_desc_2, (uint8_t *)data_vec_2.data(), 1U);
564+ 
565+ vector<int64_t> dims_vec_3;
566+ vector<uint8_t> data_vec_3 = {0};
567+ GeTensorDesc tensor_desc_3(GeShape(dims_vec_3), FORMAT_NCHW, DT_INT4);
568+ ConstGeTensorPtr tensor_3 = std::make_shared<GeTensor>(tensor_desc_3, (uint8_t *)data_vec_3.data(), 1U);
569+ 
570+ vector<ConstGeTensorPtr> input = {tensor_0, tensor_1, tensor_2, tensor_3};
571+ vector<GeTensorPtr> outputs;
572+ 
573+ shared_ptr<Kernel> kernel = KernelFactory::Instance().Create(PACK);
574+ auto status = kernel->Compute(op_desc_ptr, input, outputs);
575+ EXPECT_EQ(NOT_CHANGED, status);
576+}