feat(ptodsl): per-element pto.Vec values= constructor (issue #1242 Req 1) - #1272
Conversation
89a0aee to
40c1707
Compare
1ccc357 to
94deecc
Compare
| PTODSL uses builtin vector values in SIMT scalar code, including contiguous `scalar.load` / `scalar.store` paths and elementwise vector arithmetic. Create a builtin vector type descriptor or initialized vector value with `pto.Vec`: | ||
|
|
||
| #### `pto.Vec(dtype, size, *, init=None)` | ||
| #### `pto.Vec(dtype, size, *, init=None, values=None)` |
There was a problem hiding this comment.
我感觉这里直接复用init参数比较好,让init也接受tuple输入
| @@ -0,0 +1,75 @@ | |||
| #!/usr/bin/env python3 | |||
There was a problem hiding this comment.
这几个测试除了类型外区别不大,能否合成一个用例,用工厂函数之类的方法简化一下
There was a problem hiding this comment.
修改为工厂函数, 6 个仅类型不同的用例合并为单文件 test/dsl-st/vec_per_element_store_packs.py
Zhendong404
left a comment
There was a problem hiding this comment.
整体实现聚焦、测试覆盖扎实。以下 4 点意见已锚到对应代码行,详见行内评论。
|
|
||
|
|
||
| def _broadcast_vec_value(descriptor, init): | ||
| vector_type = _resolve_vector_type_with_signless_elements(descriptor) |
There was a problem hiding this comment.
_broadcast_vec_value 也改用了 _resolve_vector_type_with_signless_elements,这意味着 pto.Vec(pto.ui32, N, init=...) 产生的 VecValue.type 从 vector<Nxui32> 变成了 vector<Nxi32>。这确实修了原来 ui32 init 广播导致 LLVM verify 失败的问题,但属于超出本 PR 标题(values= 构造器)范围的行为变更:如果下游有代码依赖 vec.type == _resolve(descriptor) 或按 ui32 元素类型做算术分派,行为会静默改变。建议拆成独立 commit,或至少在 PR 描述里更醒目标注这一行为变化。
There was a problem hiding this comment.
PR 描述 Summary 醒目标注「Behavior change (beyond the title)」——pto.Vec(ui32, N, init=scalar) 类型由 vector 变为 vector(原路径 LLVM verify 失败,属顺带修复);两路径共用 signless 归一
| return VecValue(current) | ||
|
|
||
|
|
||
| def _resolve_vector_type_with_signless_elements(descriptor): |
There was a problem hiding this comment.
这里引入了一个类型系统的"双重人格":pto.Vec(pto.ui32, 2, values=...) 得到 vector<2xi32>(丢 sign 信息),而 scalar.load(ui32_ptr, ..., contiguous=2) 保留 vector<2xui32>。两者 store 位兼容没问题,但元素类型不相等,VecValue 逐元素算术无法混用。文档 §4.9 末尾如实说明了这个坑,但这会成为 API 层面的长期不一致,后续做向量算术/元素提取时会反复咬人。建议在 issue #1242 里开一个跟踪项,考虑统一两条路径的元素类型语义(例如 load 也 signless 化,或 Vec 保留 declared dtype 的元信息)。
There was a problem hiding this comment.
已在 PR Notes 挂 Follow-up(tracked on issue #1242)——pto.Vec(...) 产出 signless 整数向量,scalar.load(contiguous) 保留指针声明类型;store 两侧位兼容已放行,元素级 API(extract/insert/算术)引入前需统一语义;本 PR 不改 load 路径。
| return llvm.LoadOp(vector_type, ptr_value).res | ||
|
|
||
|
|
||
| def _vector_store_element_compatible(vector_element_type, elem_type) -> bool: |
There was a problem hiding this comment.
位兼容放宽只加在 store 一侧。当前没有反向需求所以可以接受,但注意这形成了一个不对称:store 接受任意同位宽整型元素,而 load 永远返回 declared 指针元素类型的向量。将来如果加 extract/insert 之类的元素级 API,signless 化的向量会丢掉原始 si/ui 语义,届时需要重新评估这里的兼容矩阵。建议在函数 docstring 里补一句"仅 store 路径放宽,load 保留 declared 类型",把这个设计取舍显式化。
There was a problem hiding this comment.
_vector_store_element_compatible docstring 明确「仅 store 路径放宽,load 保留 declared 元素类型;将来引入元素级 API 时需重评兼容矩阵」
| expect_parse_roundtrip_and_verify(per_element_vec_text, "per-element Vec values= specialization") | ||
| expect("vector<2xf32>" in per_element_vec_text, "pto.Vec(..., values=...) over f32 should produce vector<2xf32>") | ||
| expect( | ||
| per_element_vec_text.count("llvm.insertelement") == 2, |
There was a problem hiding this comment.
这类 count("llvm.insertelement") == 2 的 MLIR 文本字符串计数断言比较脆弱:canonicalize/常量折叠等无关 pass 的任何变化(比如合并 insertelement 链、常量折叠掉 undef 起始值)都会造成误报,下文 == 14 的 16-bit 断言尤其如此。作为白盒探针可以接受,但建议加注释说明这是快照式断言、预期随 pipeline 演进需要维护;或者更稳妥地改为断言 insertelement 的存在性 + 目标 SSA 值相异性,而非精确计数。
There was a problem hiding this comment.
在精确计数断言(== 2/4/14 等)处补充「快照式断言,随 canonicalize/常量折叠等 pipeline 演进需维护;行为级保证是 SSA 值相异性、无算术转换及 dsl-st golden」的注释,保留计数作白盒快照。
94deecc to
5ff1382
Compare
Factory-generated SIMT ST cases built by _make_pack_case from a shared spec table: f32x2, f16x2/x4, i16x2/x4, i8x2/x4, i32/ui32/si32 x2, i64x2. Each lane packs distinct runtime scalars with pto.Vec(dtype, N, init=(...)) and writes one vector store; goldens use distinct per-element values so a broadcast implementation cannot pass. Simulator instruction-layer evidence (single fused stores for 32-bit-element pairs, per-element stores for 8/16-bit integer packs) is recorded in the PR hw-native-sys#1272 description. Co-Authored-By: Claude <noreply@anthropic.com>
5ff1382 to
ff2d071
Compare
Extend pto.Vec(dtype, size, *, init=None) so SIMT callers can pack distinct runtime scalars into one builtin vector for a single contiguous llvm.store (issue hw-native-sys#1242 requirement 1). init= dispatches on the authored value: a scalar broadcasts to every element, a builtin vector passes through, and a sequence (tuple/list of scalars, len == size) packs one distinct element per entry in input order, coercing each element through the existing scalar coercion rules (undef + insertelement). Signed/unsigned integer dtypes (siN/uiN) build same-width signless iN vectors to satisfy the LLVM dialect constraint while keeping bit patterns; this also fixes the previously failing siN/uiN init=scalar broadcast path, and scalar.store(vector, ...) accepts same-width integer element types as bit-compatible. No backend / ODS / CLI changes. Adds f32/i32/ui32 positive probes plus negative probes to test_jit_compile.py, and documents init=sequence in the user guide (04 §4.9, 06 contiguous vector access).
Factory-generated SIMT ST cases built by _make_pack_case from a shared spec table: f32x2, f16x2/x4, i16x2/x4, i8x2/x4, i32/ui32/si32 x2, i64x2. Each lane packs distinct runtime scalars with pto.Vec(dtype, N, init=(...)) and writes one vector store; goldens use distinct per-element values so a broadcast implementation cannot pass. Simulator instruction-layer evidence (single fused stores for 32-bit-element pairs, per-element stores for 8/16-bit integer packs) is recorded in the PR hw-native-sys#1272 description.
ff2d071 to
6937cf4
Compare
A3 板测失败
失败用例
|
Summary
Implements Requirement 1 of issue #1242: a per-element builtin vector constructor so SIMT code can pack distinct runtime scalars into one vector and write it with a single contiguous vector store.
pto.Vecgainsinit=sequence: with a sequence initially. A sequence length must equalsize, preserves input element order, and coerces each element through the existing scalar coercion rules (undef + insertelement). Signed/unsigned integer dtypes (siN/uiN) build the same-width signless vector to satisfy the LLVM dialect constraint, keeping bit patterns. No backend / ODS / CLI changes.Changes
ptodsl/ptodsl/_builtin_vector.py:Vec(dtype, size, *, init=None)whereinitaccepts a scalar (broadcast), a tuple/list of scalars (per-element, length must equalsize), a builtin vector (passthrough), or None (descriptor); per-element path buildsundef + insertelementwith the existing coercion.ptodsl/ptodsl/scalar.py:scalar.store(vector, ...)element-type guard relaxed to same-width integer bit-compatibility (signless i32 vector vs ui32/i32 destination).ptodsl/tests/test_jit_compile.py: f32/i32/ui32init=sequenceprobes (two distinct runtime scalars; asserts exactly 2llvm.insertelementwith distinct SSA values, exactly 1llvm.store, nopto.store, no arithmetic conversions) + 4 negative probes (init+values conflict, length mismatch, non-sequence, str).ptodsl/docs/user_guide/04-type-system-and-buffer.md(§4.9) and06-scalar-and-pointer-ops.md(contiguous vector store): documentinit=sequence.test/dsl-st/vec_per_element_store_packs.py: factory-generated A5 SIMT ST cases (f32x2, f16x2/x4, i16x2/x4, i8x2/x4, i32/ui32/si32 x2, i64x2), all PASS on the Ascend950PR_9599 simulator.AscendC (make_*) vs pto.Vec cross-check
Added an evidence-backed comparison of
pto.Vec(..., init=sequence)against the AscendCmake_*family (simt_api/vector_functions.h,asc_fp16.h; CANN 9.0.0-beta.1 and9.1.0-beta.3 headers + bisheng toolchain):
API surface: every
make_*2/3/4(char/uchar/short/ushort/int/uint/long/ulong/float)maps 1:1 onto
pto.Vec(dtype, N, init=(...))(init= also accepts a scalar to broadcast or a builtin vector to pass through);pto.Vecis a strict superset(arbitrary
size,init=broadcast, descriptor form, coercion + length checks).Note: AscendC only ships
make_half2-- nomake_half3/4and nohalf4type inCANN 9.0 or 9.1.
Type layer: the real 9.1 bisheng device IR shows
make_short2/4returning<2 x i16>/<4 x i16>andmake_float2returning<2 x float>-- the sameLLVM vector forms PTODSL emits, i.e. both paths legalize through the same
llvm.storeshapes on the same A5 / bisheng backend.Instruction layer (144 simulator traces, A5 EU; all rows measured, dumps archived):
<2 x float><2 x i32><2 x i64><2 x half><4 x half><2 x i16><4 x i16><2 x i8><4 x i8>Single-store widths are backend-determined (hardware STG granularity 8/16/32/64/128-bit):
32-bit element pairs fuse into one 64-bit store, 64-bit pairs into one 128-bit store,
f16 pairs fuse into 32-bit, while 16/8-bit integer packs stay per-element.
Known asymmetry: 16/8-bit integer packs do not fuse into wider SIMT stores
(backend legalization, not frontend-controllable); AscendC EU-level traces were
not run (needs the official SIMT kernel host flow) -- LLVM-layer equivalence is
measured, EU behavior follows the same table as PTODSL's measured runs.
Notes
pto.Vec(...)builds signless integers whilescalar.load(..., contiguous=N)keeps the declared pointer element type (e.g.vector<2xui32>); stores accept both (bit-compatible), element-level APIs must revisit this if added.upstream/main5c5fe0dbd.