CANN opbase op_common math 模块详解:CeilDiv、FloorDiv 与对齐工具函数实战指南
2026/9/18 15:10:23 网站建设 项目流程

CANN opbase op_common math 模块详解:CeilDiv、FloorDiv 与对齐工具函数实战指南

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

本文围绕 op_common math 接口文档 展开,系统讲解Ops::Base命名空间下FloorDivCeilDivFloorAlignCeilAlignIsFloatEqual五个数学工具函数的功能、约束与边界行为,并结合 主机侧实现头文件、算子内核侧实现头文件 与 单元测试 剖析其底层细节。读完本文,你可以在算子的 Tiling/Infershape 代码中正确选用这些工具函数处理除法取整、内存对齐和浮点相等判断,并理解零值防护与溢出防护等边界设计。

模块概览与源码位置

math 模块是 op_common 提供给算子开发者的一组小型头文件级数学工具集,其接口索引见 math.md,涵盖以下五个函数:

  • FloorDiv:向下取整的除法
  • FloorAlign:以 align 为单元向下对齐
  • CeilDiv:向上取整的除法
  • CeilAlign:以 align 为单元向上对齐
  • IsFloatEqual:判断两个 float/double 数值是否相等

这组函数的主机侧(Host)实现集中在 math_util.h,全部以 C++ 模板形式内联定义在Ops::Base命名空间中,通过std::enable_if做类型约束,因此算子开发者只需包含头文件即可在 Tiling 与 Infershape 代码中直接使用,无需链接额外的库。

FloorDiv:向下取整的除法

接口定义

功能说明:向下取整的除法。函数原型(摘自 FloorDiv 文档):

template <typename T> auto FloorDiv(T x, T y) -> typename std::enable_if<std::is_integral<T>::value, T>::type
参数名输入/输出说明
x输入被除数
y输入除数

返回值:T,返回向下取整的除法结果;当除数为 0 时,返回被除数(零除数防护)。

约束说明:除数和被除数请使用相同类型整型(不包括 bool 类型)。

源码实现

math_util.h 中的实现非常精炼:

template <typename T> auto FloorDiv(T x, T y) -> typename std::enable_if<std::is_integral<T>::value, T>::type { return y == 0 ? x : x / y; }

要点有二:

  1. 零除数防护y == 0时直接返回x,避免触发除零异常/未定义行为。这在 Tiling 代码中处理动态 shape(shape 维度可能临时为 0 或非法值)时非常重要,让调用方无需每次都手动判空除数。
  2. 整型语义:底层直接复用 C++ 整型除法。从源码结构看,对于负数操作数,其实际行为是 C++ 标准的"向零截断"而非严格的数学向下取整;在常见的 shape/大小计算场景中操作数均为非负值,二者一致。

调用示例

关键代码示例如下,仅供参考,不支持直接拷贝运行:

FloorDiv<int32_t>(10, 3) // 返回 3

单元测试 test_util.cpp 中的验证覆盖了常规值与极大值边界:

EXPECT_EQ(FloorDiv<int32_t>(10, 3), 3); EXPECT_EQ(FloorDiv<int32_t>(87, 20), 4); EXPECT_EQ(FloorDiv<int64_t>(999, 100), 9); EXPECT_EQ(FloorDiv<int32_t>(std::numeric_limits<int32_t>::max(), 8), 268435455);

CeilDiv:向上取整的除法

接口定义

功能说明:向上取整的除法。由于符号类型存在不同处理需求,文档(见 CeilDiv)给出了两个重载原型:

template <typename T> auto CeilDiv(T x, T y) -> typename std::enable_if<std::is_signed<T>::value, T>::type template <typename T> auto CeilDiv(T x, T y) -> typename std::enable_if<std::is_unsigned<T>::value, T>::type
参数名输入/输出说明
x输入被除数
y输入除数

返回值:T,返回向上取整的除法结果;当除数为 0 时,返回被除数

约束说明:除数和被除数是相同类型整型(不包括 bool 类型)。

调用示例:

CeilDiv<int32_t>(5000, 4096) // 返回 2

源码实现:符号位异或技巧

math_util.h 的两个重载实现如下:

// 有符号版本 template <typename T> auto CeilDiv(T x, T y) -> typename std::enable_if<std::is_signed<T>::value, T>::type { if (y != 0 && x != 0) { const T quotient = x / y; return (x % y != 0 && ((x ^ y) >= 0)) ? (quotient + 1) : quotient; } return x; } // 无符号版本 template <typename T> auto CeilDiv(T x, T y) -> typename std::enable_if<std::is_unsigned<T>::value, T>::type { if (y != 0 && x != 0) { const T quotient = x / y; return (x % y != 0) ? (quotient + 1) : quotient; } return x; }

这段实现值得细读,它正确处理了 C++ 整型除法"向零截断"与"向上取整"之间的语义差:

  • 无符号版本:被除数、除数非负,C++ 商就是向下取整值;只要余数非零,商加 1 即为向上取整结果。
  • 有符号版本(x ^ y) >= 0是一个符号位判断技巧——xy同号(同号时异或符号位为 0,结果非负)才需要加 1。当xy异号时,C++ 的向零截断商本身恰好等于数学意义下的向上取整值(例如CeilDiv(10, -3)中 C++ 商为 -3,而真正的 ceil(-10/3) = -3),因此不加 1 反而是正确的。

单元测试 test_util.cpp 验证了典型取整行为:

EXPECT_EQ(CeilDiv<int32_t>(5000, 4096), 2); EXPECT_EQ(CeilDiv<int32_t>(130, 32), 5); EXPECT_EQ(CeilDiv<int32_t>(999, 100), 10);

FloorAlign 与 CeilAlign:以 align 为单元的对齐

FloorAlign:向下对齐

功能说明:以align为单元,向下对齐。原型(摘自 FloorAlign 文档):

template <typename T> auto FloorAlign(T x, T align) -> typename std::enable_if<std::is_integral<T>::value, T>::type
参数名输入/输出说明
x输入待向下对齐的数值
align输入对齐的单元

返回值:T,返回以align为单元向下对齐的结果;当 align 为 0 时,返回 0

约束说明:待向下对齐的数值和对齐的单元是相同类型整型(不包括 bool 类型)。

调用示例:

FloorAlign<int32_t>(12345, 4096) // 返回 12288

源码实现 即align == 0 ? 0 : x / align * align:先整除得到对齐单元个数,再乘回对齐单元,天然保证结果落在align的整数倍上且不超过x

CeilAlign:向上对齐

功能说明:以align为单元,向上对齐。原型(摘自 CeilAlign 文档):

template <typename T> auto CeilAlign(T x, T align) -> typename std::enable_if<std::is_integral<T>::value, T>::type
参数名输入/输出说明
x输入待向上对齐的数值
align输入对齐的单元

返回值:T,返回以align为单元向上对齐的结果;当 align 为 0 时,返回 0

约束说明:待向上对齐的数值和对齐的单元是相同类型整型(不包括 bool 类型)。

调用示例:

CeilAlign<int32_t>(1000, 64) // 返回 1024

源码实现 是五个函数中防御最充分的一个:

template <typename T> auto CeilAlign(T x, T align) -> typename std::enable_if<std::is_integral<T>::value, T>::type { if (align == 0) { return 0; } T div = CeilDiv(x, align); if (div > std::numeric_limits<T>::max() / align) { return std::numeric_limits<T>::max(); } return div * align; }

这里有两层保护值得注意:

  1. 复用 CeilDiv:向上对齐 = 向上取整的商 × 对齐单元,逻辑上零除数防护也随之继承。
  2. 乘法溢出防护:在div * align之前先检查div > max / align,一旦乘积会溢出,则钳制返回std::numeric_limits<T>::max(),而不是产生回绕后的错误值。单元测试 test_util.cpp 专门验证了这一钳制行为:
EXPECT_EQ(CeilAlign<int32_t>(1000, 64), 1024); EXPECT_EQ(CeilAlign<int32_t>(12345, 4096), 16384); EXPECT_EQ(CeilAlign<int32_t>(std::numeric_limits<int32_t>::max(), 8), std::numeric_limits<int32_t>::max());

IsFloatEqual:浮点相等判断

接口定义

功能说明:判断两个 float 类型或 double 类型的数值是否相等。原型(摘自 IsFloatEqual 文档):

template <typename T> auto IsFloatEqual(T a, T b) -> typename std::enable_if<std::is_floating_point<T>::value, bool>::type
参数名输入/输出说明
a输入待比较参数,数据类型支持 float、double
b输入待比较参数,数据类型支持 float、double

返回值:bool类型。true表示两个 float 或 double 类型数值相等;false表示不相等。

约束说明:文档标注为"无",但原型通过std::is_floating_point<T>限定了只支持浮点类型。

源码实现:NaN、Inf 与 epsilon 三层判断

直接写a == b对浮点数并不可靠,源码实现 分三层处理:

template <typename T> auto IsFloatEqual(T a, T b) -> typename std::enable_if<std::is_floating_point<T>::value, bool>::type { if (std::isnan(a) || std::isnan(b)) { return false; // NaN 与任何值(包括自身)都不相等 } if (std::isinf(a) || std::isinf(b)) { return std::signbit(a) == std::signbit(b); // 同号无穷大视为相等 } return fabs(a - b) <= std::numeric_limits<T>::epsilon(); // 正常值按类型 epsilon 容差比较 }
  • NaN 优先:IEEE 754 规定 NaN 不等于任何值,任何含 NaN 的比较直接返回false
  • 无穷大按符号比较+inf+inf相等、-inf+inf不等;
  • 有限值按epsilon容差:以类型自身的最小精度单位std::numeric_limits<T>::epsilon()作为差值上界,避免浮点表示误差导致本应相等的值被判为不等。

调用示例(Infershape 场景)

文档给出的示例展示了该函数在算子 Infershape 代码中的典型用法——读取delta属性并拒绝取值为 0 的配置(摘自 IsFloatEqual 文档):

float delta; if (op.GetAttr("delta", delta) == ge::GRAPH_FAILED) { std::string err_msg = GetInputInvalidErrMsg("delta"); VECTOR_INFER_SHAPE_INNER_ERR_REPORT(TbeGetName(op), err_msg); return GRAPH_FAILED; } if (IsFloatEqual(delta, 0.0f)) { string excepted_value = ConcatString("not equal to 0"); std::string err_msg = GetAttrValueErrMsg("delta", ConcatString("delta"), excepted_value); VECTOR_INFER_SHAPE_INNER_ERR_REPORT(TbeGetName(op), err_msg); return GRAPH_FAILED; }

这段代码体现了"属性值等于某个字面量"的校验模式:由于delta从属性解析而来,直接用delta == 0.0f比较存在浮点误差风险,IsFloatEqual(delta, 0.0f)是更稳妥的写法。

主机侧与算子内核侧实现对比

值得注意的是,opbase 为同一组工具函数提供了两份实现:主机侧 op_host/util/math_util.h 与算子内核侧 op_kernel/math_util.h。内核侧版本通过__aicore__ inline修饰,供 AI Core 上的 Tiling/Kernel 代码使用,并针对小位宽类型做了位宽扩展防溢出处理(摘自 op_kernel/math_util.h):

template <typename T> __aicore__ inline T CeilDiv(T a, T b) { using type = typename std::conditional<sizeof(T) == sizeof(uint8_t) || sizeof(T) == sizeof(uint16_t), uint32_t, uint64_t>::type; type res = (static_cast<type>(a) + static_cast<type>(b) - 1) / static_cast<type>(b); return static_cast<T>(res); } template <typename T> __aicore__ inline T CeilAlign(T a, T b) { using type = typename std::conditional<sizeof(T) == sizeof(uint8_t) || sizeof(T) == sizeof(uint16_t), uint32_t, uint64_t>::type; type res = (static_cast<type>(a) + static_cast<type>(b) - 1) / static_cast<type>(b) * static_cast<type>(b); return static_cast<T>(res); }

从源码结构看,内核侧CeilDiv/CeilAlign采用经典的(a + b - 1) / b公式,并先扩展到uint32_t/uint64_t再运算:当Tuint8_t/uint16_t时,a + b - 1的中间结果可能超出原类型表示范围,扩展位宽正是为了防止小类型下的中间溢出。开发者在两侧代码中调用时应留意:内核侧实现针对非负整型场景设计,负数语义与主机侧有符号版本不完全等同,选型时请按目标运行环境包含对应头文件。

单元测试与仓库内的实际应用

单元测试覆盖

单元测试 tests/op_common/ut/test_util.cpp 对四个整型函数逐一构造了断言,覆盖三类典型输入:

  • 常规值:如CeilDiv<int32_t>(5000, 4096) == 2FloorAlign<int32_t>(12345, 4096) == 12288
  • 非整除边界:如FloorAlign<int32_t>(1023, 64) == 960CeilAlign<int32_t>(257, 32) == 288
  • 类型极限值:如CeilDiv<int64_t>(std::numeric_limits<int64_t>::max(), 8)CeilAlign<uint64_t>(std::numeric_limits<uint64_t>::max(), 8),验证大值路径与溢出钳制逻辑。

实际应用:Reduce Tiling 调度

在仓库的公共 Tiling 实现中,这些函数是 shape/内存规模换算的高频工具。以 reduce_tiling.cpp 为例:

// 计算输入张量按元素大小折算的单元数 return CeilDiv(opDag_.maxInputBytes, static_cast<uint64_t>(ge::GetSizeByDataType(opInput_.inputDtype))); ... // CacheLine 调度中的对齐换算 cacheLineStep = CeilDiv(cacheSize, cacheLineShape); cacheLineStep = cacheLineStep > sliceShape_[i] ? FloorAlign(cacheLineStep, sliceShape_[i]) : cacheLineStep;

以及 UB(Uniform Block)空间计算中的CeilAlign(shape[i], ubBlockSize)(reduce_tiling.cpp 第 582、667-688 行)。可见CeilDiv+FloorAlign/CeilAlign的组合正是"按块切分、块间对齐"这一 Tiling 核心模式的数学基础:先向上取整保证覆盖全部数据,再向下对齐使块边界落在合法单元上。

小结:选型建议与边界速查

结合本文对文档与源码的梳理,给算子开发者一份速查表:

函数用途零值/边界行为关键防护
FloorDiv向下取整除法y 为 0 时返回 x零除数防护
CeilDiv向上取整除法y 或 x 为 0 时返回 x符号异或保证混合符号取整正确
FloorAlign向下对齐align 为 0 时返回 0零对齐防护
CeilAlign向上对齐align 为 0 时返回 0零对齐防护 + 乘法溢出钳制到类型最大值
IsFloatEqual浮点相等判断NaN 恒不等;Inf 按符号epsilon 容差比较

这些函数都以模板内联形式提供、无额外依赖,配合 math 接口索引文档 与各子页面查阅细节,即可覆盖算子开发中绝大多数整型取整、对齐与浮点比较需求。

【免费下载链接】opbase本项目是CANN算子库的基础框架库,为算子提供公共依赖文件和基础调度能力。项目地址: https://gitcode.com/cann/opbase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询