Skip to content

[PTODSL][SIMTVF] 梳理并重构 Scalar 与 SIMT Ops,统一公共语义并明确专用边界 #583

Description

@Zhendong404

背景

当前 PTODSL/PTOAS 中同时存在三套容易混淆的概念:

  1. scalar.xxx:面向 DSL 用户的运行时标量语义;
  2. pto.xxx:既包含底层 PTO ISA,也包含 SIMT 执行模型操作;
  3. PTO_SimtOp / SimtOpInterface:同时被用于表示 SIMT 状态依赖、
    Vector section 归属和历史上的 SIMT 指令集合。

因此目前存在以下问题:

  • 相同数学语义在 scalar.xxxpto.xxx 中重复;
  • 标准 LLVM 数学操作被包装成 SIMT 专用 PTO op;
  • 仅类型或 packed ABI 特殊的操作被误认为执行模型专用;
  • 真正依赖 lane/thread 状态的操作与普通纯值运算使用相同 interface;
  • scalar.max/min 使用 arith.maximumf/minimumf,而底层实际希望采用更高效的
    LLVM maxnum/minnum
  • pto.load/store 继承 PTO_SimtOp,但 verifier 又允许其在非 SIMT 范围使用;
  • section pass 通过 SimtOpInterface 推断 Vector/SIMT 属性,导致 interface
    同时承担语义、执行域和 section 分类职责;
  • 文档按照命名空间分章,却没有明确普通语义、目标语义和执行域语义的区别。

相关任务:

本 issue 不以消灭所有 scalar.xxxpto.xxx 为目标,而是重新明确 Scalar、
SIMT、target-specific 和底层 PTO 接口的职责边界。

目标

  1. 相同语义只保留一条推荐路径,避免普通函数和 SIMT entry 各维护一套实现;
  2. 标准数学语义优先落到标准 LLVM operation/intrinsic;
  3. 只有真正依赖 SIMT 执行状态的操作才使用 SimtOpInterface
  4. 具有 rounding、saturation、cache policy、packed ABI 或 HiVM intrinsic 等
    特化语义的接口允许保留独立 pto.xxx
  5. target-specific 与 SIMT-only 分开建模,不能因为操作对应目标 intrinsic 就自动
    将其归为 SIMT;
  6. section pass、verifier、LLVM emitter、文档和测试使用同一套分类结果;
  7. 保持手写 VPTO 的兼容性,并为需要迁移的旧 surface API 提供清晰策略。

设计原则

1. 标准语义优先使用标准 LLVM 表达

如果一个操作具有明确、稳定的 LLVM 语义,并且不依赖 SIMT 执行状态,则不应仅因
历史原因被定义为 SIMT 专用操作。

典型操作包括:

add/sub/mul/div
comparison
bitwise
select
abs/sqrt/exp/log/pow
max/min

这些操作可以继续经过 arith/math lowering;如果标准 dialect 的语义会引入项目
不需要的额外处理,则允许直接选择更适合目标的 LLVM intrinsic。例如浮点
max/min 统一采用 llvm.maxnum/minnum,而不是
arith.maximumf/minimumf 的 NaN 传播语义。

2. 真正依赖 SIMT 状态的操作保持 SIMT 专用

只有依赖以下信息或行为的操作才应携带 SimtOpInterface

  • thread、lane、block、grid 状态;
  • 跨 lane 通信和 workitem collective;
  • SIMT 同步和内存可见性;
  • SIMT entry 间状态保存;
  • 仅在 SIMT 执行环境中有定义的访存或原子操作。

此类操作继续使用显式 pto.xxx 接口,并在普通函数中给出明确诊断。

3. 有特化语义的接口允许保留两套

两套接口本身不是问题。只有语义相同却实现分叉时,才需要统一。以下接口可以同时
存在:

scalar.cast       vs pto.convert
scalar.load/store vs pto.ldg/stg
普通 LLVM fma     vs HiVM/packed pto.fma
标准数学操作      vs 具有目标类型或 packed ABI 限制的 PTO 操作

前提是接口名称、支持类型、rounding、saturation、cache policy、packed ABI 和
执行域限制足够明确。

4. SimtOpInterface 只表达执行模型依赖

不能再使用 SimtOpInterface 表达:

  • 操作位于 SIMT 文档章节;
  • 操作最终生成 Vector 指令;
  • 操作支持 packed 类型;
  • 操作对应某个 target intrinsic。

执行域、target-specific、数据类型/ABI 和 section kind 是不同维度。

操作分类与处理策略

A. 普通 Scalar 运算

包括 arithmetic、comparison、bitwise、select 和 scalar.cast

处理策略:

  • 保持 scalar API;
  • 继续使用标准 MLIR/LLVM 类型和操作;
  • 不增加无额外语义的同名 pto.xxx
  • signedness 必须在转成 signless LLVM carrier 前确定;
  • index 继续用于现有地址、循环和动态 shape 计算;
  • 普通 scalar 算术不携带 SimtOpInterface

B. max/min

推荐用户接口统一为:

scalar.max(lhs, rhs)
scalar.min(lhs, rhs)

类型和 lowering 规则:

类型 目标语义
float llvm.maxnum/minnum
signed integer llvm.smax/smin
unsigned integer llvm.umax/umin
index 保留现有 cmp + select

浮点路径不再使用 arith.maximumf/minimumf。项目底层以 LLVM intrinsic 为核心,
arith.maximumf/minimumf 的 NaN 传播和零号规则可能引入额外处理,不符合本任务
的性能目标。

现有 pto.fmax/fmin

  • 与新的 scalar.max/min 采用相同的 maxnum/minnum 语义;
  • 不再描述为独立的 SIMT 数学语义;
  • 作为手写 VPTO 和旧 PTODSL 代码的兼容接口保留;
  • 后续可以 canonicalize 到公共 pto.max/min,或明确标记为低层兼容接口;
  • 不应仅因名称或历史分组而参与 SIMT section 分类。

测试需要固定普通值、单侧 NaN、双侧 NaN、+0/-0+inf/-inf、signed、
unsigned、index、普通 AICore 和 SIMT entry 行为。

C. 标准浮点数学

包括:

abs
sqrt
exp
log
pow

普通标量推荐接口为 scalar.xxx。其中 scalar.pow 当前缺失,是否补充由实际需求
决定。普通 scalar 类型优先使用以下标准 LLVM 语义:

llvm.fabs
llvm.sqrt
llvm.exp
llvm.log
llvm.pow

现有 pto.absf/sqrt/exp/log/pow

  • scalar 类型上的语义若与 LLVM 完全相同,则不再作为推荐的 SIMT surface;
  • 手写 VPTO 继续支持,保证兼容;
  • packed 类型若只能通过 PTO op 表达,则保留相应低层接口;
  • 支持 packed 类型不能作为携带 SimtOpInterface 的充分理由;
  • packed 版本是否允许在普通函数使用,需要依据后端能力单独验证。

这里需要区分“语义公共”和“当前目标类型支持范围”,不能把所有低层类型限制都归为
SIMT 语义。

D. 舍入与 FMA

包括:

pto.ceil
pto.floor
pto.rint
pto.round
pto.fma

当前 lowering 使用:

llvm.hivm.ceil.*
llvm.hivm.floor.*
llvm.hivm.rint.*
llvm.hivm.round.*
llvm.hivm.ffma.*.rrr

因此暂时保留显式 pto.xxx,并逐项确认:

  • 与标准 LLVM ceil/floor/rint/round/fma 是否完全同义;
  • NaN、Inf、signed zero 和 rounding 行为;
  • fma 是否保证 fused semantics;
  • scalar 与 packed 类型的支持差异;
  • 普通函数是否支持对应 HiVM intrinsic;
  • 是否只在 SIMT entry 中合法。

如果语义完全相同且普通函数支持,可以后续增加统一 scalar.xxx;否则继续保持两套
接口,并在名称和文档中明确其目标特化语义。

E. 转换操作

普通转换:

scalar.cast(value, dtype)

用于标准类型转换,不携带显式硬件控制。

特化转换:

pto.convert(
    value,
    dtype,
    rounding=...,
    saturation=...,
    signedness=...,
)

pto.convert 涉及 rounding、saturation、signedness、低精度格式、packed carrier
和目标 ABI,因此继续保留独立接口,不与 scalar.cast 合并。需要补充 verifier,
明确哪些属性组合和类型对合法。

F. 普通访存与目标访存

普通访问的 surface 保持统一:

scalar.load(ptr, offset)
scalar.store(value, ptr, offset)

但 IR 应根据 pointer domain 分层,而不是全部直接生成 LLVM dialect。按照 #578 评论区
已经形成的讨论结论:

访问对象 PTODSL 生成的 IR 原因
GM/UB,单元素 pto.load/store 保留 !pto.ptr<T, space> 和 PTO 地址域
GM/UB,contiguous=N vector-typed pto.load/store 保留 PTO pointer domain,并统一连续向量访问
local fragment/栈 llvm.load/store 对象由 llvm.alloca 创建,类型为 !llvm.ptr
最终 LLVM IR LLVM load/store 由 PTOAS LLVM emitter 统一 lowering

因此 #578 的正确方向不是让所有 scalar.load/store 都直接生成 LLVM dialect,而是:

  • GM/UB 访问统一停留在 PTO pointer domain;
  • 扩展或复用 pto.load/store 支持连续向量类型;
  • local fragment/栈访问保持 LLVM pointer domain;
  • 不为使用 llvm.load/store 而提前将 !pto.ptr 转成 !llvm.ptr
  • 最终在 PTOAS LLVM emitter 中完成地址空间和 LLVM load/store lowering。

带 cache policy 的目标访问继续使用:

pto.ldg(...)
pto.stg(...)

并保留 L1/L2 cache policy、GM 地址空间限制、HiVM intrinsic 和实际需要的 SIMT
执行域约束。

当前 pto.load/store 虽继承 PTO_SimtOp,verifier 却允许普通函数使用,这仍然
是需要修正的分类矛盾:

  • pto.load/store 表达 PTO pointer domain 上的普通访存,不应仅因处理 scalar
    value 而被当作 SIMT-only;
  • pto.load/store 是否参与 section 分类应由地址域、所在函数和实际执行要求决定;
  • pto.ldg/stg 表达 cache/目标特化访存,并依据实际执行要求决定是否 SIMT-only;
  • 手写 VPTO 的现有 pto.load/store 继续兼容。

G. Atomic

包括:

atomic_exch
atomic_add/sub
atomic_min/max
atomic_and/or/xor
atomic_cas

Atomic 具有内存副作用、地址空间限制、cache policy、signedness、packed 类型限制和
目标原子语义,因此继续保留 pto.xxx,不属于普通 Scalar Math 的统一范围。

需要逐项确认当前 atomic 是否都必须位于 SIMT entry,不能仅凭历史 trait 推断。

H. SIMT Query

包括 thread、block、grid、lane、lanemask、clock 和 active mask 等查询操作。

这些操作直接读取执行状态,必须保留:

pto.xxx
PTO_SimtOp
SimtOpInterface
普通函数中拒绝

I. Collective 与跨 Lane 操作

包括:

vote_*
shuffle_*
redux_*
simt_allreduce_*

它们依赖 lane 拓扑和 workitem 集合,继续保持 SIMT-only。

软件 allreduce 内部的 combine 操作遵循公共 scalar 语义:

sum -> 普通 add
max -> LLVM maxnum
min -> LLVM minnum

Collective 是 SIMT 专用的,但 combine 的纯值语义应复用公共 Scalar 规则。

J. 同步和状态操作

包括:

syncthreads
threadfence
threadfence_block
keep
resume
simt_launch

这些操作不是纯值运算,应继续使用 PTO_SimtOpSimtOpInterface、对应的
MemoryEffects 和 verifier。普通函数误用必须给出稳定诊断。

K. Target-specific Scalar Ops

包括:

prmt
mulhi
mul_i32toi64

它们虽然处理 scalar value,但直接对应 HiVM intrinsic,因此继续使用显式
pto.xxx,不提供通用 scalar.xxx 别名。

是否携带 SimtOpInterface 应由“是否只能在 SIMT 执行域运行”决定,而不是由“是否
target-specific”决定。若普通 AICore 也能执行,应通过 target capability/verifier
表达限制。

Trait 与 Pass 调整

至少需要明确以下三个维度:

机制 表达内容
SimtOpInterface 操作依赖 SIMT 执行模型
MicroOpInterface PTO 底层微操作
target capability/verifier 操作依赖特定 target intrinsic

不建议仅为分类增加大量新 interface。只有多个 pass 确实需要统一识别时,再增加类似
TargetSpecificOpInterface 的机制。

section pass 应调整为:

  1. SIMT query、collective、sync、state 可以证明函数属于 SIMT/Vector;
  2. 普通 scalar math 不单独决定 section kind;
  3. target-specific scalar op 不自动决定 section kind;
  4. 优先使用 enclosing function、显式 section 和数据流确定 section;
  5. pto.load/store 不能因为历史上的 SimtOpInterface 被误判。

分阶段实施

阶段 0:形成分类基线

建立完整清单,记录:

surface API
VPTO op
trait/interface
支持类型
允许执行域
LLVM intrinsic
语义差异
副作用
packed ABI
现有测试
迁移结论

每个操作必须得到以下结论之一:

保持公共 scalar
保持 SIMT-only
保持 target-specific
保留双接口
兼容后废弃
需要进一步验证

阶段 1:完成 max/min 闭环

  • 浮点统一为 LLVM maxnum/minnum
  • 整数统一为 smax/sminumax/umin
  • index 保留 cmp + select
  • scalar.max/min 可同时用于普通函数和 SIMT entry;
  • 更新 allreduce combine;
  • pto.fmax/fmin 转为兼容接口;
  • 删除 arith.maximumf/minimumf 性能路径。

阶段 2:整理公共浮点数学

逐项处理 abs/sqrt/exp/log/pow,确认 scalar 和 packed 类型的后端支持范围,移除
不必要的 SIMT 分类,同时保持手写 VPTO 兼容。

阶段 3:审计特化运算

逐项验证:

ceil/floor/rint/round/fma
convert
prmt/mulhi/mul_i32toi64

明确它们属于标准语义、目标特化语义、SIMT-only,还是 target-specific 但执行域
无关。

阶段 4:整理访存和 Atomic

  • [PTODSL][SIMTVF] 统一 scalar.load/store 的访存 lowering 为 LLVM load/store #578 评论区结论,让 GM/UB 的单元素及连续向量访问统一使用
    pto.load/store,local fragment/栈访问使用 llvm.load/store
  • 保持 !pto.ptr 到 PTOAS LLVM emitter 阶段,再转换为对应 LLVM 地址空间;
  • 修正 pto.load/store 的 trait 与 verifier 不一致;
  • 保持 pto.ldg/stg cache 语义;
  • 审计每种 atomic 的执行域约束;
  • 增加 PTO/LLVM pointer domain、地址空间、副作用和非法作用域测试。

阶段 5:修正 section 架构

  • SimtOpInterface 只保留在真正 SIMT-dependent op 上;
  • section pass 不通过普通 Scalar Math 推断 SIMT;
  • 增加普通函数、SIMT entry、tile helper 的架构回归测试。

阶段 6:文档和兼容清理

文档按语义层次组织:

Scalar operations
SIMT execution operations
Target-specific scalar intrinsics
Memory and atomic operations

不再把所有处理 scalar value 的操作都放进 SIMT Scalar Math。旧接口需要明确标记为
继续推荐、低层兼容、deprecated 或仅供手写 VPTO 使用。

测试要求

每类迁移需要覆盖:

层次 覆盖内容
PTODSL 推荐 surface 生成预期 IR,普通函数和 SIMT entry 行为一致
VPTO ODS、verifier、类型、属性和 assembly round-trip
Pass section normalization/materialization 不误分类
LLVM 两套 emitter 的 intrinsic 和类型映射一致
Runtime 普通值、边界值、NaN/Inf、signed zero、rounding、saturation
Packed 每种明确支持的 packed carrier/ABI
Negative 不支持类型、执行域、属性组合和 target capability 的诊断

验收标准

  • scalar.max/min 最终使用 LLVM maxnum/minnum,不再产生额外 NaN 传播逻辑;
  • 普通 scalar API 不需要根据是否处于 SIMT entry 进行分支;
  • 只有真正依赖 SIMT 状态的操作携带 SimtOpInterface
  • packed 类型或 target intrinsic 不再自动等同于 SIMT-only;
  • scalar.castpto.convert 的语义边界明确;
  • scalar.load/storepto.ldg/stg 的语义边界明确;
  • query、collective、sync 和 state 操作在普通函数中稳定报错;
  • 两套 LLVM emitter 的公共 intrinsic 选择一致;
  • section pass 不再把普通纯值运算误判为 SIMT section;
  • 手写 VPTO 在兼容策略范围内继续可用;
  • 文档和测试能够明确回答应该使用 scalar.xxx 还是 pto.xxx

非目标

  • 不要求一次性删除所有旧接口或手写 VPTO op;
  • 不把 SIMT query、collective、sync、state 强行推广到普通函数;
  • 不因为形式统一而抹平 rounding、saturation、cache policy 或 packed ABI 差异;
  • 不在缺少后端验证时批量扩大支持类型;
  • 不要求普通函数与 SIMT entry 生成完全相同的机器指令,只要求公共语义一致且目标代码
    合法;
  • 不把所有 target-specific op 都定义成 SIMT-only。

/cc @mouliangyu

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions