Skip to content

feat(ptodsl): per-element pto.Vec values= constructor (issue #1242 Req 1) - #1272

Merged
Zhendong404 merged 2 commits into
hw-native-sys:mainfrom
jimmychou0:issue-fix-1242
Aug 19, 2026
Merged

feat(ptodsl): per-element pto.Vec values= constructor (issue #1242 Req 1)#1272
Zhendong404 merged 2 commits into
hw-native-sys:mainfrom
jimmychou0:issue-fix-1242

Conversation

@jimmychou0

@jimmychou0 jimmychou0 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

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.

pair = pto.Vec(pto.f32, 2, init=(value0, value1))
scalar.store(pair, dst, index)  # one aligned <2 x f32> llvm.store

pto.Vec gains init=sequence: with a sequence initially. A sequence length must equal size, 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) where init accepts a scalar (broadcast), a tuple/list of scalars (per-element, length must equal size), a builtin vector (passthrough), or None (descriptor); per-element path builds undef + insertelement with 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/ui32 init=sequence probes (two distinct runtime scalars; asserts exactly 2 llvm.insertelement with distinct SSA values, exactly 1 llvm.store, no pto.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) and 06-scalar-and-pointer-ops.md (contiguous vector store): document init=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 AscendC
make_* family (simt_api/vector_functions.h, asc_fp16.h; CANN 9.0.0-beta.1 and
9.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.Vec is a strict superset
    (arbitrary size, init= broadcast, descriptor form, coercion + length checks).
    Note: AscendC only ships make_half2 -- no make_half3/4 and no half4 type in
    CANN 9.0 or 9.1.

  • Type layer: the real 9.1 bisheng device IR shows make_short2/4 returning
    <2 x i16> / <4 x i16> and make_float2 returning <2 x float> -- the same
    LLVM vector forms PTODSL emits, i.e. both paths legalize through the same
    llvm.store shapes on the same A5 / bisheng backend.

  • Instruction layer (144 simulator traces, A5 EU; all rows measured, dumps archived):

    pack frontend LLVM store EU instructions (measured) STG detail
    f32 x2 (8B) <2 x float> 1 x 64-bit btype:3, Rs+Rs1
    i32/ui32/si32 x2 (8B) <2 x i32> 1 x 64-bit each btype:3, Rs+Rs1
    i64 x2 (16B) <2 x i64> 1 x 128-bit btype:4, Rs..Rs3
    f16 x2 (4B) <2 x half> 1 x 32-bit btype:2
    f16 x4 (8B) <4 x half> 2 x 32-bit btype:2, #ofst 0/1
    i16 x2 (4B) <2 x i16> 2 x 16-bit btype:1, per-element
    i16 x4 (8B) <4 x i16> 4 x 16-bit btype:1, per-element
    i8 x2 (2B) <2 x i8> 2 x 8-bit btype:0, per-element
    i8 x4 (4B) <4 x i8> 4 x 8-bit btype:0, per-element

    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

  • Follow-up (tracked on issue [Feature][PTODSL][SIMTVF] 增加 per-element vector constructor 与 loop-unroll hint #1242): unify the element-type semantics between the two vector origins — pto.Vec(...) builds signless integers while scalar.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.
  • Branch based on upstream/main 5c5fe0dbd.
  • Local-only untracked files (AGENTS.md, build-local-vpto/, spec docs) are not part of the PR.

@jimmychou0
jimmychou0 force-pushed the issue-fix-1242 branch 2 times, most recently from 89a0aee to 40c1707 Compare August 17, 2026 07:02
@jimmychou0
jimmychou0 marked this pull request as ready for review August 18, 2026 01:39
@jimmychou0
jimmychou0 force-pushed the issue-fix-1242 branch 2 times, most recently from 1ccc357 to 94deecc Compare August 18, 2026 08:44
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)`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我感觉这里直接复用init参数比较好,让init也接受tuple输入

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改为复用init参数

Comment thread test/dsl-st/vec_per_element_store.py Outdated
@@ -0,0 +1,75 @@
#!/usr/bin/env python3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这几个测试除了类型外区别不大,能否合成一个用例,用工厂函数之类的方法简化一下

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修改为工厂函数, 6 个仅类型不同的用例合并为单文件 test/dsl-st/vec_per_element_store_packs.py

@Zhendong404 Zhendong404 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

整体实现聚焦、测试覆盖扎实。以下 4 点意见已锚到对应代码行,详见行内评论。



def _broadcast_vec_value(descriptor, init):
vector_type = _resolve_vector_type_with_signless_elements(descriptor)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_broadcast_vec_value 也改用了 _resolve_vector_type_with_signless_elements,这意味着 pto.Vec(pto.ui32, N, init=...) 产生的 VecValue.typevector<Nxui32> 变成了 vector<Nxi32>。这确实修了原来 ui32 init 广播导致 LLVM verify 失败的问题,但属于超出本 PR 标题(values= 构造器)范围的行为变更:如果下游有代码依赖 vec.type == _resolve(descriptor) 或按 ui32 元素类型做算术分派,行为会静默改变。建议拆成独立 commit,或至少在 PR 描述里更醒目标注这一行为变化。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里引入了一个类型系统的"双重人格":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 的元信息)。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在 PR Notes 挂 Follow-up(tracked on issue #1242)——pto.Vec(...) 产出 signless 整数向量,scalar.load(contiguous) 保留指针声明类型;store 两侧位兼容已放行,元素级 API(extract/insert/算术)引入前需统一语义;本 PR 不改 load 路径。

Comment thread ptodsl/ptodsl/scalar.py
return llvm.LoadOp(vector_type, ptr_value).res


def _vector_store_element_compatible(vector_element_type, elem_type) -> bool:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

位兼容放宽只加在 store 一侧。当前没有反向需求所以可以接受,但注意这形成了一个不对称:store 接受任意同位宽整型元素,而 load 永远返回 declared 指针元素类型的向量。将来如果加 extract/insert 之类的元素级 API,signless 化的向量会丢掉原始 si/ui 语义,届时需要重新评估这里的兼容矩阵。建议在函数 docstring 里补一句"仅 store 路径放宽,load 保留 declared 类型",把这个设计取舍显式化。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_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,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这类 count("llvm.insertelement") == 2 的 MLIR 文本字符串计数断言比较脆弱:canonicalize/常量折叠等无关 pass 的任何变化(比如合并 insertelement 链、常量折叠掉 undef 起始值)都会造成误报,下文 == 14 的 16-bit 断言尤其如此。作为白盒探针可以接受,但建议加注释说明这是快照式断言、预期随 pipeline 演进需要维护;或者更稳妥地改为断言 insertelement 的存在性 + 目标 SSA 值相异性,而非精确计数。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在精确计数断言(== 2/4/14 等)处补充「快照式断言,随 canonicalize/常量折叠等 pipeline 演进需维护;行为级保证是 SSA 值相异性、无算术转换及 dsl-st golden」的注释,保留计数作白盒快照。

@jimmychou0
jimmychou0 requested a review from Zhendong404 August 19, 2026 00:30
jimmychou0 added a commit to jimmychou0/PTOAS that referenced this pull request Aug 19, 2026
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>
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.
@Zhendong404
Zhendong404 merged commit 7077e10 into hw-native-sys:main Aug 19, 2026
11 checks passed
@reedhecre

Copy link
Copy Markdown

A3 板测失败

  • 触发方式:merged
  • 源码提交:7077e1040a85
  • 结果汇总:OK 313 / FAIL 2 / SKIP 30
  • 日志:/home/zhongxuan/ptoas-board-monitor/runtime/logs/20260819_010416_merged_pr1272.log
  • 结果 TSV:/home/zhongxuan/ptoas-board-monitor/runtime/logs/20260819_010416_merged_pr1272.tsv
  • 失败阶段:board-validation / exit=1

失败用例

  • Ci/ci (run, exit=2)
  • Qwen3_14BPrefillA3/out_proj_aiv (run, exit=2)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants