适用于所有接入 TTK 的算子。文中标「源码」的是在 TTK 仓核实过的实现事实。
只有三方比对才有「真值」一说。 两方泛化是 NPU 与 CPU 两个平级实现互比,谁都不是真值。
「源码」ttk/core_modules/npu/op/profiling.py:
ttk/core_modules/npu/op/profiling.py
standards = _resolve_tolerance(...) # 逐输出解析,用的是**未提升前**的 dtype need_3party = any(s.token == "cross_check" for s in standards) if need_3party: context.golden_mode_override = "Promote"
cross_check
binary_equal
close
mix_tolerance
「源码」三方腿门控只看 need_3party,没有任何 dtype 过滤 (_run_xpu_and_workspace(context, need_3party) → _xpu_mode(switches, need_3party))。 need_3party 是用例级的:多输出算子只要有一个输出声明 cross_check,整个用例就走三方。
need_3party
_run_xpu_and_workspace(context, need_3party)
_xpu_mode(switches, need_3party)
该张量是浮点还是整型 ? │ ┌───────────────┴───────────────┐ 浮点 整型 │ │ 判据含 cross_check ? 整型**没有三方一说** │ (TTK 不提升整型,也不跑 GPU 腿) ┌──────┴──────┐ │ 是 否 │ │ │ │ ┌────▼─────┐ ┌────▼──────────────────┐ ┌───▼──────────────────┐ │ 零 cast │ │ 按 NPU 的加宽行为 │ │ 按 NPU 的加宽行为 │ │ 入口出口 │ │ 决定是否 cast 放宽 │ │ 决定是否 cast 放宽 │ │ 都不做 │ │ │ │ │ │ TTK 的 │ │ NPU 不宽 → 也不宽 │ │ NPU 不宽 → 也不宽 │ │ Promote │ │ NPU 宽到哪 → 就宽到哪 │ │ NPU 宽到哪 → 就宽到哪 │ │ 负责 │ │ 出口跟随其窄回行为 │ │ 出口跟随其窄回行为 │ └──────────┘ └───────────────────────┘ └──────────────────────┘
一句话:只有「浮点 + 三方」这一格是零 cast,其余全部「跟 NPU」。
「源码」DTYPE_PROMOTE_MAP(ttk/utilities/dtypes.py)只含浮点与复数:
DTYPE_PROMOTE_MAP
ttk/utilities/dtypes.py
fp16 → fp32 bf16 → fp32 fp32 → fp64 complex32 → complex64 complex64 → complex128 fp8(各变体) → fp32
无任何整型条目 —— 这就是「整型没有三方」的根源:既不提升,也组不成双标杆。
「源码」Promote 时 TTK 同时抬高 input_arrays、input_dtypes 和 output_dtypes, golden 算完在 finally 里全部还原(output_generation.py: __promote_dtype)。 注意:golden 收到的 output_dtypes 是提升后的值,据它取 dtype 不等于取到算子声明的 dtype。
input_arrays
input_dtypes
output_dtypes
finally
output_generation.py: __promote_dtype
算子声明 dtype │ 判据解析 (未提升前的 dtype) │ need_3party = 判据含 cross_check ? │ ┌──────────────────────┴──────────────────────┐ 是 否 │ │ ═══ 三方比对 ═══ ═══ 两方泛化 ═══ TTK 开 Promote TTK 不提升 执行 GPU 腿 不执行 GPU 腿 有"真值" 无"真值", 平级互比 │ │ ┌───────┼────────┬──────────┐ ┌──────────┼──────────┐ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ NPU CPU GPU 判据 NPU CPU 判据 │ │ │ cross_check │ │ binary_equal │ │ │ 比值判定 │ │ /close 等 │ │ │ │ │ 逐位或容差 │ │ │ │ │ │ │ 按 NPU │ **按 NPU 的加宽行为** │ │ 加宽行为 │ 决定是否 cast 放宽 │ │ 同步 cast │ (NPU 不宽则不宽, │ │ 出口对应窄回 │ NPU 宽到哪就宽到哪, │ │ │ 出口跟随其窄回行为) │ │ │ │ ┌──┴── 该张量是浮点还是整型 ? ──┐ │ │ │ │ │ │ 浮点 整型 │ │ │ │ │ │ 零 cast **按 NPU 的加宽行为** │ TTK 抬一档 决定是否 cast 放宽 │ (fp16/bf16→fp32 (TTK 不提升整型; │ fp32→fp64) 出口跟随 NPU 窄回) │ 出口也不 cast │ │ └─ 用输入类型算, 内部是否加宽由实现决定 ────┘ 输出不 cast ← 两支里 NPU 行为相同, 它就是基准
判断该算子是否含 tf 通路,看 golden 的注册:
__golden__ = { "kernel": {"<op_name>": "..."}, "e2e": {"tf.compat.v1.<op>": "..."}, # ← 有这一项即含 tf 通路 }
_TOL_KERNEL / _TOL_E2E 必须覆盖算子 def 注册的全部 dtype。 缺声明的 dtype 会落到 TTK 的默认判据(mix_tolerance)上,而不是算子自己声明的标准 —— 属规范缺口。
_TOL_KERNEL
_TOL_E2E
def
对照方法:拿 op_host/<op>_def.cpp 里的 DT_* 集合与 _TOL_KERNEL 的键逐一比对。
op_host/<op>_def.cpp
DT_*
astype(np.float32)
|三方−golden|
var.dtype
AccT
INT_MIN / -1
--compare close
need_3party=False
__call_numpy_api
__call_custom_golden_func
x / -1 ≡ -x
-1
neg
truncatediv(INT_MIN, -1)
truncatediv(x, 0)
InternalError
_resolve_tolerance
framework_of(func)
__module__
LoadWiden
NarrowStore
TTK 三腿比对与 golden 编写规范
适用于所有接入 TTK 的算子。文中标「源码」的是在 TTK 仓核实过的实现事实。
一、三条腿的角色
只有三方比对才有「真值」一说。 两方泛化是 NPU 与 CPU 两个平级实现互比,谁都不是真值。
二、走几方:由判据决定
「源码」
ttk/core_modules/npu/op/profiling.py:standards = _resolve_tolerance(...) # 逐输出解析,用的是**未提升前**的 dtype need_3party = any(s.token == "cross_check" for s in standards) if need_3party: context.golden_mode_override = "Promote"cross_checkbinary_equal/close/mix_tolerance…)「源码」三方腿门控只看
need_3party,没有任何 dtype 过滤(
_run_xpu_and_workspace(context, need_3party)→_xpu_mode(switches, need_3party))。need_3party是用例级的:多输出算子只要有一个输出声明cross_check,整个用例就走三方。三、CPU(golden)要不要 cast —— 核心规则
一句话:只有「浮点 + 三方」这一格是零 cast,其余全部「跟 NPU」。
「源码」
DTYPE_PROMOTE_MAP(ttk/utilities/dtypes.py)只含浮点与复数:无任何整型条目 —— 这就是「整型没有三方」的根源:既不提升,也组不成双标杆。
「源码」Promote 时 TTK 同时抬高
input_arrays、input_dtypes和output_dtypes,golden 算完在
finally里全部还原(output_generation.py: __promote_dtype)。注意:golden 收到的
output_dtypes是提升后的值,据它取 dtype 不等于取到算子声明的 dtype。四、完整逻辑图
五、四格对照表
cross_check(比值)close等(容差)(多输出混合 dtype 时才出现)
binary_equal(逐位)六、各腿用什么框架实现
不含 tf 通路 → 推荐 torch
判断该算子是否含 tf 通路,看 golden 的注册:
__golden__ = { "kernel": {"<op_name>": "..."}, "e2e": {"tf.compat.v1.<op>": "..."}, # ← 有这一项即含 tf 通路 }七、判据声明
_TOL_KERNEL/_TOL_E2E必须覆盖算子def注册的全部 dtype。缺声明的 dtype 会落到 TTK 的默认判据(
mix_tolerance)上,而不是算子自己声明的标准 —— 属规范缺口。对照方法:拿
op_host/<op>_def.cpp里的DT_*集合与_TOL_KERNEL的键逐一比对。八、常见误区(都是实际踩过的)
astype(np.float32)把结果统一成算子 dtype|三方−golden|趋零 → 三比值分母被夹 → RMSE 比值爆表假红output_dtypes已被抬高,按声明 dtype 取会砍回去;按下发的var.dtype取才是对的AccT宽度上回绕,golden 留在 int64 会规避掉(如INT_MIN / -1),两边结果不一致--compare close做「不跑三方的泛化」close覆盖声明判据 →need_3party=False→ 连带关掉 Promote,双标杆塌成单标杆,浮点必然假红__call_numpy_api)会桥接。自定义 golden 走__call_custom_golden_func,不桥接。而 torch/tf 本就原生支持 bf16,按推荐框架写就没这问题INT_MIN / -1触发 SIGFPE 硬件陷阱(进程直接崩)。回绕语义下x / -1 ≡ -x,把除数为-1的位置改走neg即可规避truncatediv(INT_MIN, -1)正常回绕不崩;但truncatediv(x, 0)会抛InternalError,除零仍须自行守护附录:查证方法
ttk/utilities/dtypes.py的DTYPE_PROMOTE_MAPcross_check(_resolve_tolerance的结果)framework_of(func)读函数真实__module__:numpy/torch/tf → 对应路径;自定义函数 →__call_custom_golden_funcAccT)与LoadWiden/NarrowStore一类的搬运封装op_host/<op>_def.cpp的DT_*