module attributes {pto.target_arch = "a2a3"} {
func.func @x2_kernel(%arg0: !pto.ptr<f32>, %arg1: !pto.ptr<f32>) attributes {pto.kernel_kind = #pto.kernel_kind<vector>} {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c2 = arith.constant 2 : index
%c32 = arith.constant 32 : index
%c128 = arith.constant 128 : index
%cN = arith.constant 8 : index
%cWa = arith.constant 1152 : index
%cWo = arith.constant 1024 : index
%out_view = pto.make_tensor_view %arg0, shape = [%c32, %cWo], strides = [%cWo, %c1] {layout = #pto.layout<nd>} : !pto.tensor_view<?x?xf32>
%a_view = pto.make_tensor_view %arg1, shape = [%c32, %cWa], strides = [%cWa, %c1] {layout = #pto.layout<nd>} : !pto.tensor_view<?x?xf32>
%mb = pto.alloc_multi_tile valid_row = %c32 valid_col = %c128 : !pto.multi_tile_buf<!pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>, count=2>
// preload iter 0 -> slot0
%pre = pto.multi_tile_get %mb[%c0] : !pto.multi_tile_buf<!pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>, count=2> -> !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>
%av_pre = pto.partition_view %a_view, offsets = [%c0, %c0], sizes = [%c32, %c128] : !pto.tensor_view<?x?xf32> -> !pto.partition_tensor_view<32x128xf32>
pto.tload ins(%av_pre : !pto.partition_tensor_view<32x128xf32>) outs(%pre : !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>)
scf.for %i = %c0 to %cN step %c1 {
%next = arith.addi %i, %c1 : index
%cur_idx = arith.remui %i, %c2 : index
%next_idx = arith.remui %next, %c2 : index
// prefetch chunk i+1 into the other slot
%s_next = pto.multi_tile_get %mb[%next_idx] : !pto.multi_tile_buf<!pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>, count=2> -> !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>
%aoff = arith.muli %next, %c128 : index
%av = pto.partition_view %a_view, offsets = [%c0, %aoff], sizes = [%c32, %c128] : !pto.tensor_view<?x?xf32> -> !pto.partition_tensor_view<32x128xf32>
pto.tload ins(%av : !pto.partition_tensor_view<32x128xf32>) outs(%s_next : !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>)
// compute chunk i from the current slot, then store it
%s_cur = pto.multi_tile_get %mb[%cur_idx] : !pto.multi_tile_buf<!pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>, count=2> -> !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>
%y = pto.alloc_tile valid_row = %c32 valid_col = %c128 : !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>
pto.tadd ins(%s_cur, %s_cur : !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>, !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>) outs(%y : !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>)
%ooff = arith.muli %i, %c128 : index
%ov = pto.partition_view %out_view, offsets = [%c0, %ooff], sizes = [%c32, %c128] : !pto.tensor_view<?x?xf32> -> !pto.partition_tensor_view<32x128xf32>
pto.tstore ins(%y : !pto.tile_buf<loc=vec, dtype=f32, rows=32, cols=128, v_row=?, v_col=?, blayout=row_major, slayout=none_box, fractal=512, pad=0>) outs(%ov : !pto.partition_tensor_view<32x128xf32>)
}
return
}
}
Pass / pipeline name
pto-insert-sync(--enable-insert-sync): per-slot event priming/draining forpto.multi_tile_getback-edge dependenciesCommand line
ptoas 0.61 release binary (
ptoas-bin-aarch64.tar.gz).Before IR (input)
The "双 buffer prefetch(动态 slot)" example from
docs/designs/ptoas-multi-buffer-explicit-design.md§7.2. Only the elidedouts(...)ofpto.taddis filled in (a separate tile, asmulti_tile_same_region_two_gets_sync.ptodoes), and the GM argument uses the lit-testpartition_tensor_viewform:Device-runnable version: the same loop and op order, plus per-chunk GM offsets and a
tstoreso the output can be checked.ahas one extra chunk so the last prefetch stays in bounds. 8 chunks of 32x128 f32,out = 2 * a:x2_kernel.pto
Expected behavior / expected IR (after)
The kernel computes
out[:, 128*i : 128*(i+1)] = 2 * a[:, 128*i : 128*(i+1)]for every chunk and terminates for any trip count.For that, the V->MTE2 back-edge events need to be primed and drained per slot according to which slot the loop's producer and consumer actually touch. The WAR edge runs from
taddin iterationi(reads sloti % 2) totloadin iterationi + 1(writes slot(i + 2) % 2 == i % 2), so:i = 0) goes to slot 1, which nothing has read yet;i = 1) has to wait fortaddof iteration 0. The preload already wrote slot 0 without consuming a token, so priming slot 0 as well hands it a spare token;Actual IR or error
Emitted C++ for the §7.2 example: both slots' events are primed before the loop, the preload
TLOADconsumes no V->MTE2 token, and both are drained after the loop (v4 = 0/v3 = 512are the slot addresses andv6 = 0,v7 = 1,v8 = 2, so the V->MTE2 event id equals the slot index):V->MTE2 tokens over
nloop iterations:set_flagin loop (afterTADDof that slot)wait_flagin loop (beforeTLOADinto that slot)EVENT_ID0(slot 0)ieven: ceil(n/2)iodd: floor(n/2)EVENT_ID1(slot 1)iodd: floor(n/2)ieven: ceil(n/2)Slot 0 always holds one token more than the dependency allows, so every
TLOADinto slot 0 can start before theTADDstill reading that slot has finished. For oddn, slot 1's drain has no matchingset_flagand never returns.On an Ascend 910B1 (a2a3), with
arandom f32,outstarting at zero, and the result compared against2 * a:outwrong in chunks 0, 2, 4, 6 (9856 / 32768 elements, max abs diff 12.9)pto.multi_tile_get %mb[%i mod 2]per iteration (load and compute on the same slot)arith.remui,arith.remsior a loop-carriedk -> 1-karith.remsi,arith.andi %i, 1,(3*i) mod 2or a loop-carriedk -> 1-kSCHEDULER_TIMEOUT sub_class=S1:running-stalled(the AICore task never completes)The wrong chunks are exactly those computed from slot 0 inside the loop (chunk
iis read from sloti % 2).Git commit
5eea919 (tag
v0.61)Target Ascend arch (if relevant)
a3
PTOAS build level (if relevant)
level2
Additional context
set_flag/wait_flag, so it does not reproduce this.--pto-level=level3(pto.alloc_multi_tile addr = ...), checked on the prologue/epilogue variant. Slot indices written asarith.remsi,arith.andi,(3*i) mod 2or a loop-carried value get a single static MTE2->V event instead of per-slot ones, but their V->MTE2 accounting is identical.EVENT_ID1andEVENT_ID2(slots 0 and 1) are both primed, so iteration 1'swait_flag(PIPE_MTE2, PIPE_V, <slot 1>)can pass before iteration 0's prefetch of chunk 1 has landed. It did not corrupt data in these runs.multi_tile_affine_disjoint_slots.pto(no preload) FileChecks that both slots' events are primed and drained, which is the accounting above.Related: #1118
Related: #1106