Skip to content

Version-Aware Fusion Plan#912

Draft
Melikano wants to merge 24 commits into
hw-native-sys:mainfrom
afshinarefi:fusion-plan-version-selection
Draft

Version-Aware Fusion Plan#912
Melikano wants to merge 24 commits into
hw-native-sys:mainfrom
afshinarefi:fusion-plan-version-selection

Conversation

@Melikano

@Melikano Melikano commented Jul 7, 2026

Copy link
Copy Markdown

Description

This draft PR starts the version-aware fusion planning work for Tile Fusion.

The main motivation is that a single TileOp / FusionComputeNode may have
multiple legal implementation versions. For example, when tile regions are
contiguous, a tile op may be implementable with a simpler 1D loop nest;
otherwise it may need a different loop structure. The fusion planner therefore
should not treat each compute node as a single fixed unit. Instead, it should
eventually reason about:

FusionComputeNode
  |-- version 0: default / 2D
  |-- version 1: contiguous / 1D
  `-- version 2: tail / post-update / other variant

The long-term goal is to enumerate possible fusion groups per seed across these
implementation versions, score them with a cost model, and select the best
version-aware group.

This PR is currently a foundation/draft PR. It does not yet replace the
existing PTOFusionPlan behavior and does not yet wire in the full
version-aware search algorithm.

Goal

  • Share template-candidate metadata parsing between:
    • InsertTemplateAttributes
    • PTOVersionAwareFusionPlan
  • Represent fusion members as (compute node, implementation version).
  • Prepare internal state and comparison helpers for exact per-seed
    version-aware search.

Current Algorithm Direction

The intended version-aware planning algorithm is:

flowchart TD
    A[Block compute nodes] --> B[Pick next unassigned seed]
    B --> C[Read legal versions for seed]
    C --> D[Create one GroupState per seed version]

    D --> E[Explore append candidates]
    E --> F[For each candidate node]
    F --> G[Read legal versions for candidate]
    G --> H[Try appending each version]

    H --> I{Append legal by DAG / domain / boundary rules?}
    I -- no --> E
    I -- yes --> J[Create new GroupState]

    J --> K[Compute base fusion cost]
    K --> L[Add version trait cost]
    L --> M[Record valid group states]

    M --> N{More expansions?}
    N -- yes --> E
    N -- no --> O[Select best group by deterministic comparator]

    O --> P[Assign group_id / order]
    P --> B
Loading

Each GroupState represents one possible version-aware group:

GroupState
  members:
    - (node A, version A1)
    - (node B, version B0)
    - (node C, version C2)

  nodeIds:
    {A, B, C}

  cost:
    dependencyBenefit
    loopMergeBenefit
    versionCompatibilityBenefit
    liveTilePenalty
    vfParameterPenalty
    versionPenalty

The key difference from the current node-only planner is that the search
frontier branches by implementation version:

Seed node A

Current planner:
  A  ---> grow one group

Version-aware planner:
  (A, v0) ---> grow group candidates
  (A, v1) ---> grow group candidates
  (A, v2) ---> grow group candidates

Cost Model Direction

The planner will compare candidate groups using both existing fusion cost and
version-specific cost.

Base fusion terms:

+ dependencyBenefit
+ loopMergeBenefit
- liveTilePenalty
- vfParameterPenalty

Version-aware terms added by this PR:

+ versionCompatibilityBenefit
- versionPenalty

Current version-trait scoring helper models:

all members use 1D versions        => benefit
mixed loop depths                  => penalty
tail versions                      => penalty
post-update versions               => penalty

The deterministic tie-break order is:

  1. Higher total cost.
  2. Larger group size.
  3. Earlier first block order.
  4. Lexicographically smaller compute node ids.
  5. Lexicographically smaller version ids.

Current State of This PR

Implemented so far:

  • Added shared template-candidate metadata utility:
    • include/PTO/Transforms/TemplateAttributes.h
    • lib/PTO/Transforms/TemplateAttributes.cpp
  • Moved shared candidate schema into that utility:
    • candidates
    • id
    • name
    • loop_depth
    • postupdate
    • tail
  • Updated InsertTemplateAttributes.cpp to build candidate attrs through the
    shared helper.
  • Updated PTOVersionAwareFusionPlan.cpp to parse candidate attrs through the
    shared helper.
  • Added internal version-aware planning data structures:
    • TileOpImplVersion
    • PlannedFusionMember
    • PlannedFusionGroup
    • GroupState
  • Added preparatory helpers:
    • version-trait cost computation
    • deterministic candidate-group comparison
    • seed-state creation with one GroupState per legal implementation version

Not implemented yet:

  • The exact per-seed enumeration loop is not wired into planBlock.
  • The current version-aware pass still relies on the existing greedy group
    formation behavior.
  • Version-aware cost is not yet used to choose final groups.
  • No selected implementation-version metadata is emitted to IR yet.
  • PTOFusionPlan.cpp remains unchanged intentionally.

ManiSadati and others added 23 commits July 3, 2026 17:44
@Melikano Melikano changed the title Version-Aware Version-Aware Fusion Plan Jul 7, 2026
@reedhecre

reedhecre commented Jul 7, 2026

Copy link
Copy Markdown

Codex Review

该评论由 review 机器人自动更新。

  • PR: Version-Aware Fusion Plan #912 Version-Aware Fusion Plan
  • Author: Melikano
  • Base/Head: main / fusion-plan-version-selection
  • Head SHA: d25fef585aca
  • Trigger: PR 有新提交
  • Generated At: 2026-07-08T18:10:44Z
  • Previous Head SHA: 358fe2c4bf51
  • Status: failed at codex-review (exit=1)

Summary

Review failed at stage codex-review: exit=1

Findings

未生成结构化 findings,因为 review 过程提前失败。

Log Tail

 ptodsl/ptodsl/tilelib/templates/a5/tsqrt.py        |  23 +
 ptodsl/ptodsl/tilelib/templates/a5/tstore.py       | 259 ++++++
 ptodsl/ptodsl/tilelib/templates/a5/tsub.py         |  38 +
 ptodsl/ptodsl/tilelib/templates/a5/tsubs.py        |  27 +
 ptodsl/ptodsl/tilelib/templates/a5/txor.py         |  25 +
 ptodsl/ptodsl/tilelib/templates/a5/txors.py        |  26 +
 ptodsl/tests/fixtures/tadd_a5_8x64_f32.golden.mlir |  38 +
 ptodsl/tests/test_tilelib_catalog.py               | 804 +++++++++++++++++++
 ptodsl/tests/test_tilelib_constraints.py           |  81 ++
 ptodsl/tests/test_tilelib_daemon.py                | 263 ++++++
 ptodsl/tests/test_tilelib_elementwise.py           |  49 ++
 ptodsl/tests/test_tilelib_render.py                |  71 ++
 ptodsl/tests/test_tilelib_select.py                | 235 ++++++
 scripts/ptoas_env.sh                               |   3 +
 test/lit/vpto/expand_tile_op_ptodsl_tadd.pto       |  82 ++
 test/lit/vpto/expand_tile_op_ptodsl_tsub.pto       |  47 ++
 tools/ptoas/CMakeLists.txt                         |   9 +-
 tools/ptoas/TilelangDaemon.cpp                     |  49 +-
 tools/ptoas/TilelangDaemon.h                       |  13 +-
 tools/ptoas/ptoas.cpp                              | 156 +++-
 158 files changed, 13159 insertions(+), 108 deletions(-)
===== END STAGE clone rc=0 @ 2026-07-09 02:10:35 =====

===== STAGE codex-review @ 2026-07-09 02:10:35 =====
set -euo pipefail
cd '/tmp/ptoas-pr-review-monitor/runs/20260709_021027_pr912/repo'
'codex' exec -C '/tmp/ptoas-pr-review-monitor/runs/20260709_021027_pr912/repo' -s read-only -c 'model_provider="codereview"' -c 'model="gpt-5.4"' -c 'model_reasoning_effort="xhigh"' --output-schema '/tmp/ptoas-pr-review-monitor/runs/20260709_021027_pr912/review_schema.json' -o '/tmp/ptoas-pr-review-monitor/runs/20260709_021027_pr912/codex_last_message.json' --color never - < '/tmp/ptoas-pr-review-monitor/runs/20260709_021027_pr912/review_prompt.txt'
[monitor] stage timeout: 1800s
OpenAI Codex v0.115.0 (research preview)
--------
workdir: /tmp/ptoas-pr-review-monitor/runs/20260709_021027_pr912/repo
model: gpt-5.4
provider: codereview
approval: never
sandbox: read-only
reasoning effort: xhigh
reasoning summaries: none
session id: 019f42ec-b2e7-7d51-bb2d-5127d99b7bd2
--------
user
你现在在审查 GitHub PR。

仓库:hw-native-sys/PTOAS
PR:#912 Version-Aware Fusion Plan
作者:Melikano
base branch:origin/main
head branch:HEAD(当前已 checkout 到 PR head)

要求:
1. 只审查这个 PR 相对 origin/main 的改动,必要时可以看上下文文件。
2. 重点找真实的 correctness / regression / contract mismatch / CI / runtime / compatibility 问题。
3. 不要提纯风格建议,不要提低价值猜测。
4. 严格按优先级输出:
   - P1:高概率会导致错误结果、编译/运行失败、严重回归、发布阻断
   - P2:重要缺陷、行为回归、遗漏校验/测试、较大兼容性问题
   - P3:次要但明确可改的问题
5. 如果没有问题,summary 直接写:未检查到 PR #912 存在问题,并返回 findings=[]。
6. 如果有问题,summary 简洁概括,findings 里每条都要给出:
   - severity
   - title
   - body(说明为什么是问题,尽量具体)
   - file(尽量给相对路径)
   - line(能确定就填整数,否则 null)

建议先查看:
- git status --short
- git diff --stat origin/main...HEAD
- git diff --unified=80 origin/main...HEAD

最终输出必须严格匹配 JSON schema。

mcp startup: no servers
Reconnecting... 1/5 (unexpected status 403 Forbidden: {"code":"INSUFFICIENT_BALANCE","message":"Insufficient account balance"}, url: https://codex.0u0o.com/responses, cf-ray: a1810bf29f0c37c1-SJC, request id: a0d29000-776a-486f-ac3a-814eb0751650)
Reconnecting... 2/5 (unexpected status 403 Forbidden: {"code":"INSUFFICIENT_BALANCE","message":"Insufficient account balance"}, url: https://codex.0u0o.com/responses, cf-ray: a1810bf52ada2eaf-LAX, request id: c5e74bb4-2f36-481c-a29c-8c5a2db6c11a)
Reconnecting... 3/5 (unexpected status 403 Forbidden: {"code":"INSUFFICIENT_BALANCE","message":"Insufficient account balance"}, url: https://codex.0u0o.com/responses, cf-ray: a1810bf97872f97b-SJC, request id: 9e0a8694-2202-4496-85b6-7987f6dc3266)
Reconnecting... 4/5 (unexpected status 403 Forbidden: {"code":"INSUFFICIENT_BALANCE","message":"Insufficient account balance"}, url: https://codex.0u0o.com/responses, cf-ray: a1810c02490d314f-LAX, request id: 2d82ff29-7896-48c9-8b32-655df30d5275)
Reconnecting... 5/5 (unexpected status 403 Forbidden: {"code":"INSUFFICIENT_BALANCE","message":"Insufficient account balance"}, url: https://codex.0u0o.com/responses, cf-ray: a1810c0df8f33385-LAX, request id: 556784fc-6bbd-4fb6-a072-c29fca4b4d3e)
ERROR: unexpected status 403 Forbidden: {"code":"INSUFFICIENT_BALANCE","message":"Insufficient account balance"}, url: https://codex.0u0o.com/responses, cf-ray: a1810c24185f2ef6-LAX, request id: 8a200b59-2f4b-44a8-a026-4777d5fbb6c1
Warning: no last agent message; wrote empty content to /tmp/ptoas-pr-review-monitor/runs/20260709_021027_pr912/codex_last_message.json
===== END STAGE codex-review rc=1 @ 2026-07-09 02:10:44 =====

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a version-aware tile fusion planning pass and a new PTODSL TileLib backend for template expansion. The changes include adding a new VersionAwareFusionPlan pass, a shared metadata contract for template candidates, and a daemon-based RPC mechanism for template rendering. My review identified several security and reliability issues, including insecure socket creation, potential DoS vulnerabilities in the daemon, missing module imports, and missing template files. I have provided actionable feedback to address these concerns.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +320 to +321
super().__init__(socket_path, _Handler)
os.chmod(socket_path, 0o600)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-high high

Creating a Unix domain socket in a world-writable directory like /tmp can lead to a race condition where other local users can connect to the socket before os.chmod restricts its permissions. To prevent this, temporarily set the process umask to 0077 before binding the socket, or create the socket in a secure, user-private directory.

Suggested change
super().__init__(socket_path, _Handler)
os.chmod(socket_path, 0o600)
import os
old_umask = os.umask(0o077)
try:
super().__init__(socket_path, _Handler)
finally:
os.umask(old_umask)
os.chmod(socket_path, 0o600)

@@ -18,25 +18,74 @@
from pathlib import Path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The module sys is used in _pythonpath_already_provides_mlir_and_pto (via sys.path), but it is not imported in this file. This will result in a NameError at runtime. Please import sys at the top of the file.

Suggested change
from pathlib import Path
import sys
from pathlib import Path

Comment on lines +83 to +86
("a5", "pto.tpartadd"): ".a5.tpartadd",
("a5", "pto.tpartmax"): ".a5.tpartmax",
("a5", "pto.tpartmin"): ".a5.tpartmin",
("a5", "pto.tpartmul"): ".a5.tpartmul",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The template modules tpartadd, tpartmax, tpartmin, and tpartmul are registered in _TEMPLATE_MODULES, but their corresponding files (tpartadd.py, tpartmax.py, tpartmin.py, tpartmul.py) are missing from this pull request. Attempting to load or expand these operations will result in a ModuleNotFoundError at runtime.

("a5", "pto.trowexpandsub"): ".a5.trowexpandsub",
("a5", "pto.trowmax"): ".a5.trowmax",
("a5", "pto.trowmin"): ".a5.trowmin",
("a5", "pto.trowprod"): ".a5.trowprod",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The template module trowprod is registered in _TEMPLATE_MODULES as ".a5.trowprod", but the file ptodsl/ptodsl/tilelib/templates/a5/trowprod.py is missing from this pull request. Attempting to load or expand pto.trowprod will result in a ModuleNotFoundError at runtime.

Comment on lines +419 to +421
class _Handler(socketserver.BaseRequestHandler):
def handle(self):
try:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Since TileLibDaemonServer is a sequential (single-threaded, blocking) server, a client that connects and sends a large length prefix but no payload can block the daemon indefinitely, causing a Denial of Service (DoS). Setting a socket timeout on the connection socket (e.g., in setup or at the start of handle) prevents such hangs.

Suggested change
class _Handler(socketserver.BaseRequestHandler):
def handle(self):
try:
class _Handler(socketserver.BaseRequestHandler):
def setup(self):
self.request.settimeout(10.0)
def handle(self):
try:

chunks = []
remaining = length
while remaining:
chunk = sock.recv(remaining)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Calling sock.recv(remaining) with a very large remaining value (up to 64MB) can cause Python to allocate a large buffer in memory before reading. To prevent excessive memory allocation and potential Out-Of-Memory (OOM) issues, read the socket in smaller, capped chunks (e.g., at most 64KB per recv call).

Suggested change
chunk = sock.recv(remaining)
chunk = sock.recv(min(remaining, 65536))

Comment on lines +124 to +128
def __post_init__(self):
if len(self.shape) != 2:
raise ValueError("TileSpec currently only supports rank-2 tile shapes")
if any(not isinstance(dim, int) or dim <= 0 for dim in self.shape):
raise ValueError("TileSpec.shape must contain positive integers")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The valid_shape parameter in TileSpec is not validated in __post_init__. If provided, it should be checked to ensure it has the same rank as shape, contains positive integers, and does not exceed the physical shape dimensions.

    def __post_init__(self):
        if len(self.shape) != 2:
            raise ValueError("TileSpec currently only supports rank-2 tile shapes")
        if any(not isinstance(dim, int) or dim <= 0 for dim in self.shape):
            raise ValueError("TileSpec.shape must contain positive integers")
        if self.valid_shape is not None:
            if len(self.valid_shape) != 2:
                raise ValueError("TileSpec.valid_shape must be rank-2")
            if any(not isinstance(dim, int) or dim <= 0 for dim in self.valid_shape):
                raise ValueError("TileSpec.valid_shape must contain positive integers")
            if any(v > s for v, s in zip(self.valid_shape, self.shape)):
                raise ValueError("TileSpec.valid_shape dimensions cannot exceed physical shape")

Comment on lines +28 to +29
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.connect(self.socket_path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The client socket is created without a timeout. If the daemon hangs or becomes unresponsive, the client (and consequently ptoas) will block indefinitely. Setting a reasonable timeout (e.g., 30 seconds) on the client socket prevents infinite hangs.

Suggested change
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.connect(self.socket_path)
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.settimeout(30.0)
sock.connect(self.socket_path)

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