算子提交行为与禁止规则

本文档说明 CANN Bench 对提交算子的基本要求,以及哪些实现方式会被视为无效或作弊。这里的代码片段只展示行为类型,不对应任何具体提交。

解包后的源码目录、构建产物和接口格式见评测输入与接口规范。本页只定义代码执行行为边界,规则编号与格式规范保持独立;通过 cannbench.com 网站提交时还需满足网站自身的 ZIP 上传约束。

1. 算子编写原则

CANN Bench 评测的是提交者实现 Ascend C / NPU kernel 的能力。候选算子应满足以下原则:

  • 真实实现目标算子语义:计算主体应由提交工程中的 Ascend C kernel、自定义 ACLNN 算子或 direct launch kernel 完成。
  • Python/C++ 包装层只做调度和参数整理:包装层可以读取 shape、dtype、attrs,做 tiling 参数准备、输出张量分配、调用自定义 kernel;不应把核心计算交给 PyTorch、torch_npu 或 CANN 内置同名算子。
  • 输入/输出张量处理也属于算子语义:如果某个输入布局变换、输出重排、dtype 转换或中间 tensor 处理会改变 tensor 数值、布局或物理存储,它应由提交 kernel 完成;不能先用 PyTorch / torch_npu 处理好再交给 kernel。
  • 实现应泛化到所有合法输入:不能只针对公开 case、固定 shape、固定 data pointer 或固定输入值返回预先准备的结果。
  • 输出必须是真实计算得到的 Tensor:返回值需要是普通 torch.Tensor,不能用 FakeTensor、懒求值包装器或伪造对象绕过精度检查。
  • 性能数据必须来自提交算子实际执行的 NPU kernel:功能正确但没有实际执行提交 kernel 的实现,不符合评测目标。

允许使用提交 kernel 内的 Ascend C 原生 API 和 intrinsic,例如 AscendC::AddAscendC::MulAscendC::ExpAscendC::Mmad 等。这些 API 在编译期成为提交 kernel 的一部分,不等同于在包装层直接调用现成的 CANN / torch_npu 算子。

2. 禁止行为分类

2.1 [SUB-BEH-001] 调用 PyTorch / torch_npu 内置计算 API 代算

候选算子的执行路径中,不应直接调用 PyTorch / torch_npu 内置计算 API 来完成目标算子。即使只把部分计算交给现成 API,也属于无效实现。包装层可以做参数整理,但不能把核心计算交给 matmul、conv、softmax、attention、activation、normalization 等现成实现。

反例:

import torch
import torch.nn.functional as F

def softmax(x, dim=-1):
    return F.softmax(x, dim=dim)
def grouped_matmul(x, weight):
    return x @ weight

同一类绕过写法也不允许,例如改用 torch.ops.aten.matmulx.matmul(y)torch.mmtorch.nn.functional.conv2d 等。

2.2 [SUB-BEH-002] 使用 PyTorch / torch_npu 处理输入输出 tensor

输入预处理、输出后处理和中间 tensor 变换也是目标算子实现的一部分。不能用 PyTorch / torch_npu 的 tensor API 先完成 transpose、permute、contiguous、reshape-copy、cast、slice、gather、scatter 等实质性数据搬运或布局变换,再把处理后的 tensor 交给提交 kernel。

注:此类 I/O 搬运 / 布局变换(transpose/permute/gather/scatter 等)属人工审查判定为无效的范畴——为避免误伤 Gather / Transpose 等以其为核心语义的算子,框架不对这些算子自动拦截;而 matmul / conv / softmax 等计算类算子的绕过,则由 TorchOpGuard / DeviceResidencyGuard 自动拦截(默认 block)。

反例:

def transpose_then_kernel(x):
    # 反例:先用现成 NPU tensor API 完成布局变换,再让 kernel 只处理剩余部分。
    x_t = x.transpose(0, 1).contiguous()
    return launch_custom_kernel(x_t)
def permute_output(x):
    y = launch_custom_kernel(x)
    # 反例:输出重排由 PyTorch / torch_npu 完成,而不是提交 kernel 完成。
    return y.permute(0, 2, 1).contiguous()

如果算法需要类似 transpose / permute / cast / slice 的数据变换,应在提交 kernel 内用 Ascend C API、数据搬运和索引逻辑实现,而不是在包装层调用现成 tensor 算子。

上面第二个反例如果连 .contiguous() 都省掉、直接 return y.permute(0, 2, 1), 则由框架自动拦截: 精度比对前会校验候选输出 is_contiguous(), 非连续输出直接判失败(记为结构性失败, 不是精度不达标)。极端情形是完全不算、直接返回输入的某个视图 -- shape 与数值都能对上, 但 stride 会暴露它。

def fake_by_view(x):
    # 反例: 转置视图数值可能与 golden 完全一致, 但没有任何计算发生;
    # is_contiguous() 校验会直接判失败。
    return x.t()

.contiguous() 把数据真复制一遍不在这条自动校验的范围内, 仍属人工审查判定的无效实现。

2.3 [SUB-BEH-003] 路由到 CANN 内置同名算子

提交工程不应只是把任务转发给评测环境中已有的 CANN 内置同名算子。无论目标环境是否提供同名内置实现,候选提交都应包含自己的 kernel 实现。

反例:

// 反例:包装层只调用环境已有的同名内置算子。
CHECK_ACL(aclnnSoftmaxGetWorkspaceSize(x, dim, y, &workspaceSize, &executor));
CHECK_ACL(aclnnSoftmax(workspace, workspaceSize, executor, stream));
// 反例:注册/转发到现成同名 AiCore 实现,而不是提交自己的 kernel。
ADD_TO_LAUNCHER_LIST_AICORE(Softmax);

正例是提交自己的 kernel,并在 kernel 内使用 Ascend C 原语完成计算:

// 正例:在提交的 kernel 内使用 Ascend C API 组成目标计算。
AscendC::Exp(dstLocal, srcLocal, elementCount);

2.4 [SUB-BEH-004] CPU fallback 或没有执行提交 NPU kernel

候选算子不能把输入搬回 CPU 计算,再把结果搬回 NPU。即使结果数值正确,只要核心计算不是由提交的 NPU kernel 完成,也属于无效实现。

反例:

import torch

def exp(x):
    y_cpu = torch.exp(x.cpu())
    return y_cpu.to(x.device)
def add(x, y):
    # 反例:结果来自 host 侧计算或预处理,而不是提交的 NPU kernel。
    return (x.cpu() + y.cpu()).npu()

2.5 [SUB-BEH-005] 缓存输出、固定输出或按输入地址命中

实现不能缓存某次执行结果,然后在后续 case 或 repeat 中直接返回。实现也不应依赖固定输入地址、固定 shape、固定 dtype、固定 attrs 或公开 case 特征来返回预设结果。

强制方式:性能测量阶段框架会轮换输入地址(每个 repeat 步喂入的张量为独立 clone,data_ptr() 不同),按输入地址命中的缓存会 cache miss。由此返回的过期/预设结果会在性能阶段的精度复检中暴露、判为精度失败。(该轮换仅作用于性能阶段;精度阶段不轮换。)

反例:

_cache = {}

def sigmoid(x):
    key = x.data_ptr()
    if key in _cache:
        return _cache[key]
    out = launch_or_compute_once(x)
    _cache[key] = out
    return out
_last_output = None

def relu(x):
    global _last_output
    if _last_output is not None:
        return _last_output
    _last_output = launch_or_compute_once(x)
    return _last_output

同样不允许根据公开 case 的固定 shape、dtype 或属性直接返回常量结果。

2.6 [SUB-BEH-006] 篡改 profiler 或 timing API

提交代码不应 monkey-patch、替换或删除评测环境的计时、同步、性能采集或运行时 API。任何让评测结果不再反映真实 kernel 执行时间的环境篡改都属于作弊行为。

反例:

import torch
import torch_npu

torch.npu.synchronize = lambda *args, **kwargs: None
torch_npu.profiler.profile = fake_profile

这类行为会让评测结果不可信,应视为无效提交。

2.7 [SUB-BEH-007] 返回 FakeTensor、懒求值包装器或伪 Tensor 对象

候选算子必须返回真实 torch.Tensor。用对象包装真实计算、延迟到比较阶段再求值,或返回 Tensor 子类伪装结果,都会破坏评测边界。

反例:

class LazyTensor:
    def __init__(self, x):
        self.x = x

    def __torch_function__(self, func, types, args=(), kwargs=None):
        return func(real_compute(self.x), *(args or ()), **(kwargs or {}))

def mish(x):
    return LazyTensor(x)

3. 判定原则

本规范关注的是行为边界,不依赖具体文件名、提交 ID 或评测实现细节。若某种写法的效果是绕过“提交者实现真实 NPU kernel”这一评测目标,即使没有出现在上面的示例中,也可能被判为无效提交。