Component
Verifier / IR semantics (lib/PTO/IR)
Description
verifyCommGlobalLike() rejects any ? (dynamic) dimension on the dst/src partition views of pto.comm.tput and pto.comm.tget, so a variable-size transfer cannot be expressed — even though the rest of the pipeline already handles it end to end.
The verifier is the only blocker. I confirmed this by bypassing it (see "Proof the lowering already works" below): with the check out of the way, a fully ?-dimensioned pto.comm.tput lowers cleanly to a runtime-extent GlobalTensor and a valid pto::comm::TPUT(...) call, on both v0.48 and v0.54.
Why the rejection looks unintentional rather than a real constraint:
pto.partition_view with a ? result type + dynamic size SSA already lowers fine — it just isn't allowed to reach a comm op. The same partition view feeding pto.tload compiles without complaint (case C below).
- The live lowering path is dynamic-capable.
pto.partition_view → (PTOViewToMemref.cpp:1330 lowerPartitionViewOps) → memref.subview with dynamic sizes → SubviewToEmitCPattern (PTOToEmitC.cpp:3766), which passes adaptor.getSizes() straight into the pto::Shape constructor (PTOToEmitC.cpp:4172-4184). The static-only early-return in buildGlobalTensorFromMemref (PTOToEmitC.cpp:4406) sits on the memref-direct path, which comm ops built from partition views never take.
GlobalTensor can express runtime extents. pto::DYNAMIC = -1 and pto::Shape / pto::Stride have runtime constructors (pto-isa include/pto/common/pto_tile.hpp), which is exactly what SubviewToEmitCPattern emits.
- pto-isa reads the shape at runtime.
TPUT_IMPL (pto-isa include/pto/comm/a2a3/TPut.hpp) obtains extents via srcGlobalData.GetShape(GlobalTensorDim::DIM_n), with no shape-related static_assert; the 2-D slide auto-chunks a transfer larger than the staging tile.
Impact: push-based collectives in pypto must transfer the full compile-time capacity instead of the actual runtime count. In all_to_all_v / MoE dispatch, a peer that receives 1 of 32 possible rows still costs 32 rows of interconnect bandwidth. pypto codegen already emits the dynamic form (!pto.partition_tensor_view<?x64xf16> with a static chunked staging tile), so it fails at the ptoas verifier today — see pypto issue #2213.
Reproduction (minimal)
Case A — dynamic pto.comm.tput (fails). Identical to the static version except sizes uses a runtime %rows and the partition-view type is <?x4096xi8>:
module attributes {pto.target_arch = "a2a3"} {
func.func @tput_dynamic(%arg0: !pto.ptr<i8>, %arg1: !pto.ptr<i8>, %arg2: i32,
%__pypto_spmd_block_idx: i32, %__pypto_spmd_block_num: i32)
attributes {pto.kernel_kind = #pto.kernel_kind<vector>} {
%c0_i64 = arith.constant 0 : i64
%c0_index = arith.constant 0 : index
%c1_index = arith.constant 1 : index
%c32_index = arith.constant 32 : index
%c4096_index = arith.constant 4096 : index
%rows = arith.index_cast %arg2 : i32 to index
%dst_view = pto.make_tensor_view %arg0, shape = [%c32_index, %c4096_index], strides = [%c4096_index, %c1_index] {layout = #pto.layout<nd>}: !pto.tensor_view<?x?xi8>
%src_view = pto.make_tensor_view %arg1, shape = [%c32_index, %c4096_index], strides = [%c4096_index, %c1_index] {layout = #pto.layout<nd>}: !pto.tensor_view<?x?xi8>
%stage = pto.alloc_tile addr = %c0_i64 valid_row = %c1_index valid_col = %c4096_index : !pto.tile_buf<loc=vec, dtype=i8, rows=1, cols=4096, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>
%dst_pview = pto.partition_view %dst_view, offsets = [%c0_index, %c0_index], sizes = [%rows, %c4096_index] : !pto.tensor_view<?x?xi8> -> !pto.partition_tensor_view<?x4096xi8>
%src_pview = pto.partition_view %src_view, offsets = [%c0_index, %c0_index], sizes = [%rows, %c4096_index] : !pto.tensor_view<?x?xi8> -> !pto.partition_tensor_view<?x4096xi8>
pto.barrier <PIPE_ALL>
pto.comm.tput(%dst_pview, %src_pview, buf(%stage) : !pto.partition_tensor_view<?x4096xi8>, !pto.partition_tensor_view<?x4096xi8>, !pto.tile_buf<loc=vec, dtype=i8, rows=1, cols=4096, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>) {atomicType = #pto<atomic_type atomic_none>}
pto.barrier <PIPE_ALL>
return
}
}
ptoas dynamic.pto -o dynamic.cpp --pto-level=level3
# same file with sizes = [%c32_index, ...] and <32x4096xi8> succeeds
Case B — same file with pto.comm.tget: same error ('pto.comm.tget' op expects dst to have a positive static shape).
Case C — the same dynamic partition view feeding pto.tload (succeeds). Shows the ? partition view itself is fine; only the comm ops reject it:
%pv = pto.partition_view %view, offsets = [%c0_index, %c0_index], sizes = [%rows, %c32_index] : !pto.tensor_view<?x?xf32> -> !pto.partition_tensor_view<?x32xf32>
pto.tload ins(%pv : !pto.partition_tensor_view<?x32xf32>) outs(%tb : !pto.tile_buf<loc=vec, dtype=f32, rows=1, cols=32, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>)
emits the runtime extent and TLOAD without error:
pto::Shape<1, 1, 1, -1, 32> v10 = pto::Shape<1, 1, 1, -1, 32>(v8);
GlobalTensor<float, pto::Shape<1, 1, 1, -1, 32>, pto::Stride<-1, -1, -1, 32, 1>, pto::Layout::ND> v12 = ...;
TLOAD(v13, v12);
Proof the lowering already works. Taking Case A and only routing the staging tile through pto.bind_tile (so shouldBypassDecodedMemrefVerifier, PTO.cpp:514, skips the check) makes the same ?x4096 tput lower successfully on both v0.48 and v0.54:
%ubuf = memref.alloc() : memref<1x4096xi8, #pto.address_space<vec>>
%stage = pto.bind_tile %ubuf, %c1_index, %c4096_index
{config = #pto.tile_buf_config<blayout=0 : i32, slayout=0 : i32, s_fractal_size=512, pad=0 : i32>}
: memref<1x4096xi8, #pto.address_space<vec>> -> memref<1x4096xi8, #pto.address_space<vec>>
pto.comm.tput(%dst_pview, %src_pview, buf(%stage) : !pto.partition_tensor_view<?x4096xi8>, !pto.partition_tensor_view<?x4096xi8>, memref<1x4096xi8, #pto.address_space<vec>>) {atomicType = #pto<atomic_type atomic_none>}
$ ptoas dyn_bypass.pto -o dyn_bypass.cpp --pto-level=level2 # exit 0, no diagnostics
pto::Shape<1, 1, 1, -1, 4096> v11 = pto::Shape<1, 1, 1, -1, 4096>(v9); // v9 = runtime rows
pto::Stride<-1, -1, -1, 4096, 1> v12 = pto::Stride<-1, -1, -1, 4096, 1>(v10, v10, v10);
GlobalTensor<int8_t, pto::Shape<1, 1, 1, -1, 4096>, pto::Stride<-1, -1, -1, 4096, 1>, pto::Layout::ND> v13 = ...;
pto::comm::TPUT(v13, v17, v19);
--emit-pto-ir on that run confirms the intermediate form the EmitC patterns consume — a dynamic memref.subview, with the comm op already memref-typed at that point:
%subview = memref.subview %reinterpret_cast[0, 0] [%0, 4096] [1, 1] {layout = #pto.layout<nd>} : memref<?x?xi8, strided<[?, ?], offset: ?>, #pto.address_space<gm>> to memref<?x4096xi8, strided<[?, ?], offset: ?>, #pto.address_space<gm>>
pto.comm.tput(%subview, %subview_1, buf(%2) : memref<?x4096xi8, strided<[?, ?], offset: ?>, #pto.address_space<gm>>, ...)
Expected behavior
pto.comm.tput / pto.comm.tget should accept ? dimensions on their dst/src partition views and lower them to a runtime-extent GlobalTensor, exactly as Case C and the bypass run already do — the number of rows on the wire then equals the runtime payload instead of the compile-time capacity.
Suggested fix in verifyCommGlobalLike (lib/PTO/IR/PTO.cpp:4187-4191): keep rejecting non-positive static dims, allow kDynamic:
for (int64_t dim : shape) {
if (dim != ShapedType::kDynamic && dim <= 0)
return op->emitOpError() << "expects " << name
<< " to have a positive or dynamic shape";
}
Two related spots to consider in the same review:
TPutOp::verify() / TGetOp::verify() (PTO.cpp:16844 / :16862) compare getShapeVec(dst) != getShapeVec(src) under the message "same static shape". With ? allowed, two dynamic extents comparing equal as kDynamic is the right structural check, but the diagnostic wording should be updated.
verifyCommSignalLike and verifyCommStagingTileLike (PTO.cpp:4195 / :4206) reuse the same positive-static loop. Signals and VEC staging tiles genuinely want static shapes, so please keep those strict — only the GM dst/src views of tput/tget need relaxing.
Actual behavior / error logs
$ ptoas dynamic.pto -o dynamic.cpp --pto-level=level3
loc("dynamic.pto":17:5): error: 'pto.comm.tput' op expects dst to have a positive static shape
Error: Failed to parse MLIR.
$ ptoas dyn_tget.pto -o dyn_tget.cpp --pto-level=level3
loc("dyn_tget.pto":17:5): error: 'pto.comm.tget' op expects dst to have a positive static shape
Error: Failed to parse MLIR.
Reproduced identically on released binaries v0.48, v0.50 and v0.54, and with real pypto codegen output (pld.tensor.put(..., shape=[n, 64], chunk_rows=4, chunk_cols=32)):
loc("pypto_dyn_put.pto":22:3): error: 'pto.comm.tput' op expects dst to have a positive static shape
Error: Failed to parse MLIR.
Origin (v0.54, lib/PTO/IR/PTO.cpp:4177-4192):
static LogicalResult verifyCommGlobalLike(Operation *op, Value value,
StringRef name) {
...
for (int64_t dim : shape) {
if (dim == ShapedType::kDynamic || dim <= 0)
return op->emitOpError() << "expects " << name
<< " to have a positive static shape";
}
return success();
}
Git commit
195fdb8
Host platform
Linux (aarch64)
Target Ascend arch (if relevant)
a3
PTOAS build level (if relevant)
level3
Component
Verifier / IR semantics (lib/PTO/IR)
Description
verifyCommGlobalLike()rejects any?(dynamic) dimension on thedst/srcpartition views ofpto.comm.tputandpto.comm.tget, so a variable-size transfer cannot be expressed — even though the rest of the pipeline already handles it end to end.The verifier is the only blocker. I confirmed this by bypassing it (see "Proof the lowering already works" below): with the check out of the way, a fully
?-dimensionedpto.comm.tputlowers cleanly to a runtime-extentGlobalTensorand a validpto::comm::TPUT(...)call, on both v0.48 and v0.54.Why the rejection looks unintentional rather than a real constraint:
pto.partition_viewwith a?result type + dynamic size SSA already lowers fine — it just isn't allowed to reach a comm op. The same partition view feedingpto.tloadcompiles without complaint (case C below).pto.partition_view→ (PTOViewToMemref.cpp:1330lowerPartitionViewOps) →memref.subviewwith dynamic sizes →SubviewToEmitCPattern(PTOToEmitC.cpp:3766), which passesadaptor.getSizes()straight into thepto::Shapeconstructor (PTOToEmitC.cpp:4172-4184). The static-only early-return inbuildGlobalTensorFromMemref(PTOToEmitC.cpp:4406) sits on the memref-direct path, which comm ops built from partition views never take.GlobalTensorcan express runtime extents.pto::DYNAMIC = -1andpto::Shape/pto::Stridehave runtime constructors (pto-isainclude/pto/common/pto_tile.hpp), which is exactly whatSubviewToEmitCPatternemits.TPUT_IMPL(pto-isainclude/pto/comm/a2a3/TPut.hpp) obtains extents viasrcGlobalData.GetShape(GlobalTensorDim::DIM_n), with no shape-relatedstatic_assert; the 2-D slide auto-chunks a transfer larger than the staging tile.Impact: push-based collectives in pypto must transfer the full compile-time capacity instead of the actual runtime count. In
all_to_all_v/ MoE dispatch, a peer that receives 1 of 32 possible rows still costs 32 rows of interconnect bandwidth. pypto codegen already emits the dynamic form (!pto.partition_tensor_view<?x64xf16>with a static chunked staging tile), so it fails at the ptoas verifier today — see pypto issue #2213.Reproduction (minimal)
Case A — dynamic
pto.comm.tput(fails). Identical to the static version exceptsizesuses a runtime%rowsand the partition-view type is<?x4096xi8>:ptoas dynamic.pto -o dynamic.cpp --pto-level=level3 # same file with sizes = [%c32_index, ...] and <32x4096xi8> succeedsCase B — same file with
pto.comm.tget: same error ('pto.comm.tget' op expects dst to have a positive static shape).Case C — the same dynamic partition view feeding
pto.tload(succeeds). Shows the?partition view itself is fine; only the comm ops reject it:emits the runtime extent and
TLOADwithout error:Proof the lowering already works. Taking Case A and only routing the staging tile through
pto.bind_tile(soshouldBypassDecodedMemrefVerifier,PTO.cpp:514, skips the check) makes the same?x4096tput lower successfully on both v0.48 and v0.54:$ ptoas dyn_bypass.pto -o dyn_bypass.cpp --pto-level=level2 # exit 0, no diagnostics--emit-pto-iron that run confirms the intermediate form the EmitC patterns consume — a dynamicmemref.subview, with the comm op already memref-typed at that point:Expected behavior
pto.comm.tput/pto.comm.tgetshould accept?dimensions on theirdst/srcpartition views and lower them to a runtime-extentGlobalTensor, exactly as Case C and the bypass run already do — the number of rows on the wire then equals the runtime payload instead of the compile-time capacity.Suggested fix in
verifyCommGlobalLike(lib/PTO/IR/PTO.cpp:4187-4191): keep rejecting non-positive static dims, allowkDynamic:Two related spots to consider in the same review:
TPutOp::verify()/TGetOp::verify()(PTO.cpp:16844/:16862) comparegetShapeVec(dst) != getShapeVec(src)under the message "same static shape". With?allowed, two dynamic extents comparing equal askDynamicis the right structural check, but the diagnostic wording should be updated.verifyCommSignalLikeandverifyCommStagingTileLike(PTO.cpp:4195/:4206) reuse the same positive-static loop. Signals and VEC staging tiles genuinely want static shapes, so please keep those strict — only the GMdst/srcviews of tput/tget need relaxing.Actual behavior / error logs
Reproduced identically on released binaries v0.48, v0.50 and v0.54, and with real pypto codegen output (
pld.tensor.put(..., shape=[n, 64], chunk_rows=4, chunk_cols=32)):Origin (v0.54,
lib/PTO/IR/PTO.cpp:4177-4192):Git commit
195fdb8
Host platform
Linux (aarch64)
Target Ascend arch (if relevant)
a3
PTOAS build level (if relevant)
level3