Skip to content

[Bug] auto-sync should track aliasing through pto.subview #536

Description

@MirkoDeVita98

Component

PTO Dialect / ODS (include/PTO/IR)

Description

The auto-sync pass treats the result of pto.subset as an independent
buffer rather than an alias of its source tile. As a consequence, no
sync flag (set_flag / wait_flag pair) is emitted between a write
to a sub-view and a later read/write of either the parent tile or an
overlapping sub-view. Kernels that use pto.subset to express
sub-block parallelism (each AIV sub-block owning a row-half of a
shared tile) produce wrong data.

Reproduction (minimal)

module {
  func.func @cube_kernel(%arg0: !pto.ptr<f32>, %arg1: !pto.ptr<f16>, %arg2: !pto.ptr<f16>, %arg3: !pto.ptr<f16>) attributes {pto.kernel_kind = #pto.kernel_kind<cube>} {
    %c0 = arith.constant 0 : index
    %c1 = arith.constant 1 : index
    %c32 = arith.constant 32 : index
    %c32_0 = arith.constant 32 : index
    %c256 = arith.constant 256 : index
    %c8192 = arith.constant 8192 : index
    %c32_1 = arith.constant 32 : index
    %c64 = arith.constant 64 : index
    %0 = pto.get_block_num
    %1 = arith.index_cast %0 : i64 to index
    %2 = pto.get_block_idx
    %3 = arith.index_cast %2 : i64 to index
    %4 = arith.divsi %c64, %1 : index
    %5 = arith.remsi %c64, %1 : index
    %6 = arith.addi %4, %c1 : index
    %7 = arith.muli %3, %6 : index
    %8 = arith.addi %4, %c1 : index
    %9 = arith.muli %5, %8 : index
    %10 = arith.subi %3, %5 : index
    %11 = arith.muli %10, %4 : index
    %12 = arith.addi %9, %11 : index
    %13 = arith.cmpi slt, %3, %5 : index
    %14 = arith.select %13, %7, %12 : index
    %15 = arith.cmpi slt, %3, %5 : index
    %16 = arith.addi %4, %c1 : index
    %17 = arith.select %15, %16, %4 : index
    %18 = arith.addi %14, %17 : index
    %c65536 = arith.constant 65536 : index
    %19 = arith.muli %3, %c65536 : index
    %20 = pto.addptr %arg0, %19 : <f32> -> <f32>
    %21 = pto.import_reserved_buffer{name = "fa_c2v_fifo", peer_func = @vector_kernel} -> i32
    %22 = pto.reserve_buffer{name = "fa_v2c_fifo", size = 32768, location = <mat>, auto = true} -> i32
    pto.aic_initialize_pipe{dir_mask = 3, slot_size = 32768} (gm_slot_buffer = %20 : !pto.ptr<f32>, c2v_consumer_buf = %21 : i32, v2c_consumer_buf = %22 : i32)
    %23 = pto.alloc_tile : !pto.tile_buf<mat, 32x32xf16, blayout=col_major, slayout=row_major>
    %24 = pto.alloc_tile : !pto.tile_buf<left, 32x32xf16, slayout=row_major>
    %25 = pto.alloc_tile : !pto.tile_buf<mat, 32x256xf16, slayout=col_major>
    %26 = pto.alloc_tile : !pto.tile_buf<right, 32x256xf16, slayout=col_major>
    %27 = pto.alloc_tile : !pto.tile_buf<acc, 32x256xf32, blayout=col_major, slayout=row_major, fractal=1024>
    %28 = pto.alloc_tile : !pto.tile_buf<mat, 32x256xf16, blayout=col_major, slayout=row_major>
    %29 = pto.alloc_tile : !pto.tile_buf<left, 32x256xf16, slayout=row_major>
    %30 = pto.alloc_tile : !pto.tile_buf<mat, 256x32xf16, blayout=col_major, slayout=row_major>
    %31 = pto.alloc_tile : !pto.tile_buf<right, 256x32xf16, slayout=col_major>
    %32 = pto.alloc_tile : !pto.tile_buf<acc, 32x32xf32, blayout=col_major, slayout=row_major, fractal=1024>
    %c2048 = arith.constant 2048 : index
    %33 = pto.make_tensor_view %arg1, shape = [%c2048, %c32_0], strides = [%c32_0, %c1] : !pto.tensor_view<?x?xf16>
    %34 = pto.make_tensor_view %arg2, shape = [%c32_0, %c8192], strides = [%c1, %c32_0] : !pto.tensor_view<?x?xf16>
    %35 = pto.make_tensor_view %arg3, shape = [%c8192, %c32_0], strides = [%c32_0, %c1] : !pto.tensor_view<?x?xf16>
    scf.for %arg4 = %14 to %18 step %c1 {
      %36 = arith.muli %arg4, %c32 : index
      %37 = pto.partition_view %33, offsets = [%36, %c0], sizes = [%c32, %c32_0] : !pto.tensor_view<?x?xf16> -> !pto.partition_tensor_view<32x32xf16>
      pto.tload ins(%37 : !pto.partition_tensor_view<32x32xf16>) outs(%23 : !pto.tile_buf<mat, 32x32xf16, blayout=col_major, slayout=row_major>)
      pto.tmov ins(%23 : !pto.tile_buf<mat, 32x32xf16, blayout=col_major, slayout=row_major>) outs(%24 : !pto.tile_buf<left, 32x32xf16, slayout=row_major>)
      %38 = pto.partition_view %34, offsets = [%c0, %c0], sizes = [%c32_0, %c256] : !pto.tensor_view<?x?xf16> -> !pto.partition_tensor_view<32x256xf16>
      pto.tload ins(%38 : !pto.partition_tensor_view<32x256xf16>) outs(%25 : !pto.tile_buf<mat, 32x256xf16, slayout=col_major>)
      pto.tmov ins(%25 : !pto.tile_buf<mat, 32x256xf16, slayout=col_major>) outs(%26 : !pto.tile_buf<right, 32x256xf16, slayout=col_major>)
      pto.tmatmul ins(%24, %26 : !pto.tile_buf<left, 32x32xf16, slayout=row_major>, !pto.tile_buf<right, 32x256xf16, slayout=col_major>) outs(%27 : !pto.tile_buf<acc, 32x256xf32, blayout=col_major, slayout=row_major, fractal=1024>)
      pto.tpush_to_aiv(%27 : !pto.tile_buf<acc, 32x256xf32, blayout=col_major, slayout=row_major, fractal=1024>) {split = 0}
      %39 = pto.partition_view %35, offsets = [%c0, %c0], sizes = [%c256, %c32_0] : !pto.tensor_view<?x?xf16> -> !pto.partition_tensor_view<256x32xf16>
      pto.tload ins(%39 : !pto.partition_tensor_view<256x32xf16>) outs(%30 : !pto.tile_buf<mat, 256x32xf16, blayout=col_major, slayout=row_major>)
      scf.for %arg5 = %c1 to %c32_1 step %c1 {
        %41 = arith.muli %arg5, %c256 : index
        %42 = pto.partition_view %34, offsets = [%c0, %41], sizes = [%c32_0, %c256] : !pto.tensor_view<?x?xf16> -> !pto.partition_tensor_view<32x256xf16>
        pto.tload ins(%42 : !pto.partition_tensor_view<32x256xf16>) outs(%25 : !pto.tile_buf<mat, 32x256xf16, slayout=col_major>)
        %43 = pto.tpop_from_aiv {split = 0} -> !pto.tile_buf<mat, 32x256xf16, blayout=col_major, slayout=row_major>
        pto.tmov ins(%43 : !pto.tile_buf<mat, 32x256xf16, blayout=col_major, slayout=row_major>) outs(%29 : !pto.tile_buf<left, 32x256xf16, slayout=row_major>)
        pto.tfree_from_aiv{split = 0}
        pto.tmov ins(%30 : !pto.tile_buf<mat, 256x32xf16, blayout=col_major, slayout=row_major>) outs(%31 : !pto.tile_buf<right, 256x32xf16, slayout=col_major>)
        %44 = pto.partition_view %35, offsets = [%41, %c0], sizes = [%c256, %c32_0] : !pto.tensor_view<?x?xf16> -> !pto.partition_tensor_view<256x32xf16>
        pto.tload ins(%44 : !pto.partition_tensor_view<256x32xf16>) outs(%30 : !pto.tile_buf<mat, 256x32xf16, blayout=col_major, slayout=row_major>)
        pto.tmatmul ins(%29, %31 : !pto.tile_buf<left, 32x256xf16, slayout=row_major>, !pto.tile_buf<right, 256x32xf16, slayout=col_major>) outs(%32 : !pto.tile_buf<acc, 32x32xf32, blayout=col_major, slayout=row_major, fractal=1024>)
        pto.tpush_to_aiv(%32 : !pto.tile_buf<acc, 32x32xf32, blayout=col_major, slayout=row_major, fractal=1024>) {split = 0}
        pto.tmov ins(%25 : !pto.tile_buf<mat, 32x256xf16, slayout=col_major>) outs(%26 : !pto.tile_buf<right, 32x256xf16, slayout=col_major>)
        pto.tmatmul ins(%24, %26 : !pto.tile_buf<left, 32x32xf16, slayout=row_major>, !pto.tile_buf<right, 32x256xf16, slayout=col_major>) outs(%27 : !pto.tile_buf<acc, 32x256xf32, blayout=col_major, slayout=row_major, fractal=1024>)
        pto.tpush_to_aiv(%27 : !pto.tile_buf<acc, 32x256xf32, blayout=col_major, slayout=row_major, fractal=1024>) {split = 0}
      }
      %40 = pto.tpop_from_aiv {split = 0} -> !pto.tile_buf<mat, 32x256xf16, blayout=col_major, slayout=row_major>
      pto.tmov ins(%40 : !pto.tile_buf<mat, 32x256xf16, blayout=col_major, slayout=row_major>) outs(%29 : !pto.tile_buf<left, 32x256xf16, slayout=row_major>)
      pto.tfree_from_aiv{split = 0}
      pto.tmov ins(%30 : !pto.tile_buf<mat, 256x32xf16, blayout=col_major, slayout=row_major>) outs(%31 : !pto.tile_buf<right, 256x32xf16, slayout=col_major>)
      pto.tmatmul ins(%29, %31 : !pto.tile_buf<left, 32x256xf16, slayout=row_major>, !pto.tile_buf<right, 256x32xf16, slayout=col_major>) outs(%32 : !pto.tile_buf<acc, 32x32xf32, blayout=col_major, slayout=row_major, fractal=1024>)
      pto.tpush_to_aiv(%32 : !pto.tile_buf<acc, 32x32xf32, blayout=col_major, slayout=row_major, fractal=1024>) {split = 0}
    }
    return
  }
  func.func @vector_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
    %c32 = arith.constant 32 : index
    %c16 = arith.constant 16 : index
    %c32_0 = arith.constant 32 : index
    %c32_1 = arith.constant 32 : index
    %c64 = arith.constant 64 : index
    %0 = pto.get_block_num
    %1 = arith.index_cast %0 : i64 to index
    %2 = pto.get_block_idx
    %3 = arith.index_cast %2 : i64 to index
    %4 = arith.divsi %c64, %1 : index
    %5 = arith.remsi %c64, %1 : index
    %6 = arith.addi %4, %c1 : index
    %7 = arith.muli %3, %6 : index
    %8 = arith.addi %4, %c1 : index
    %9 = arith.muli %5, %8 : index
    %10 = arith.subi %3, %5 : index
    %11 = arith.muli %10, %4 : index
    %12 = arith.addi %9, %11 : index
    %13 = arith.cmpi slt, %3, %5 : index
    %14 = arith.select %13, %7, %12 : index
    %15 = arith.cmpi slt, %3, %5 : index
    %16 = arith.addi %4, %c1 : index
    %17 = arith.select %15, %16, %4 : index
    %18 = arith.addi %14, %17 : index
    %c65536 = arith.constant 65536 : index
    %19 = arith.muli %3, %c65536 : index
    %20 = pto.addptr %arg0, %19 : <f32> -> <f32>
    %21 = pto.reserve_buffer{name = "fa_c2v_fifo", size = 32768, location = <vec>, auto = true} -> i32
    %22 = pto.import_reserved_buffer{name = "fa_v2c_fifo", peer_func = @cube_kernel} -> i32
    pto.aiv_initialize_pipe{dir_mask = 3, slot_size = 32768} (gm_slot_buffer = %20 : !pto.ptr<f32>, c2v_consumer_buf = %21 : i32, v2c_consumer_buf = %22 : i32)
    %23 = pto.get_subblock_idx
    %24 = arith.index_cast %23 : i64 to index
    %25 = arith.muli %24, %c16 : index
    %26 = pto.alloc_tile : !pto.tile_buf<vec, 32x256xf32>
    %27 = pto.alloc_tile : !pto.tile_buf<vec, 32x256xf32>
    %28 = pto.alloc_tile : !pto.tile_buf<vec, 32x256xf16>
    %29 = pto.alloc_tile : !pto.tile_buf<vec, 32x32xf32>
    %30 = pto.subview %26[%25, %c0] sizes [16, 256] : !pto.tile_buf<vec, 32x256xf32> -> !pto.tile_buf<vec, 16x256xf32>
    %31 = pto.subview %27[%25, %c0] sizes [16, 256] : !pto.tile_buf<vec, 32x256xf32> -> !pto.tile_buf<vec, 16x256xf32>
    %32 = pto.subview %28[%25, %c0] sizes [16, 256] : !pto.tile_buf<vec, 32x256xf16> -> !pto.tile_buf<vec, 16x256xf16>
    %33 = pto.subview %29[%25, %c0] sizes [16, 32] : !pto.tile_buf<vec, 32x32xf32> -> !pto.tile_buf<vec, 16x32xf32>
    %34 = pto.alloc_tile : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>
    %35 = pto.alloc_tile : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>
    %36 = pto.alloc_tile : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>
    %37 = pto.alloc_tile : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>
    %38 = pto.alloc_tile : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>
    %cst = arith.constant 0.176776692 : f32
    %cst_2 = arith.constant 1.000000e+00 : f32
    %c2048 = arith.constant 2048 : index
    %39 = pto.make_tensor_view %arg1, shape = [%c2048, %c32_0], strides = [%c32_0, %c1] : !pto.tensor_view<?x?xf32>
    scf.for %arg2 = %14 to %18 step %c1 {
      %40 = arith.muli %arg2, %c32 : index
      scf.for %arg3 = %c0 to %c32_1 step %c1 {
        %43 = arith.cmpi eq, %arg3, %c0 : index
        %44 = pto.tpop_from_aic {split = 0} -> !pto.tile_buf<vec, 32x256xf32>
        %45 = pto.subview %44[%25, %c0] sizes [16, 256] : !pto.tile_buf<vec, 32x256xf32> -> !pto.tile_buf<vec, 16x256xf32>
        pto.tmuls ins(%45, %cst : !pto.tile_buf<vec, 16x256xf32>, f32) outs(%45 : !pto.tile_buf<vec, 16x256xf32>)
        pto.trowmax ins(%45, %30 : !pto.tile_buf<vec, 16x256xf32>, !pto.tile_buf<vec, 16x256xf32>) outs(%35 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>)
        %46 = pto.treshape %35 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major> -> !pto.tile_buf<vec, 1x16xf32>
        %47 = pto.treshape %34 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major> -> !pto.tile_buf<vec, 1x16xf32>
        %48 = pto.treshape %36 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major> -> !pto.tile_buf<vec, 1x16xf32>
        %49 = pto.treshape %37 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major> -> !pto.tile_buf<vec, 1x16xf32>
        %50 = pto.treshape %38 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major> -> !pto.tile_buf<vec, 1x16xf32>
        scf.if %43 {
          pto.trowexpandsub ins(%45, %35 : !pto.tile_buf<vec, 16x256xf32>, !pto.tile_buf<vec, 16x1xf32, blayout=col_major>) outs(%31 : !pto.tile_buf<vec, 16x256xf32>)
          pto.tmuls ins(%46, %cst_2 : !pto.tile_buf<vec, 1x16xf32>, f32) outs(%47 : !pto.tile_buf<vec, 1x16xf32>)
          pto.texp ins(%31 : !pto.tile_buf<vec, 16x256xf32>) outs(%31 : !pto.tile_buf<vec, 16x256xf32>)
          pto.trowsum ins(%31, %30 : !pto.tile_buf<vec, 16x256xf32>, !pto.tile_buf<vec, 16x256xf32>) outs(%37 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>)
        } else {
          pto.tmax ins(%46, %47 : !pto.tile_buf<vec, 1x16xf32>, !pto.tile_buf<vec, 1x16xf32>) outs(%46 : !pto.tile_buf<vec, 1x16xf32>)
          pto.tsub ins(%47, %46 : !pto.tile_buf<vec, 1x16xf32>, !pto.tile_buf<vec, 1x16xf32>) outs(%48 : !pto.tile_buf<vec, 1x16xf32>)
          pto.tmuls ins(%46, %cst_2 : !pto.tile_buf<vec, 1x16xf32>, f32) outs(%47 : !pto.tile_buf<vec, 1x16xf32>)
          pto.trowexpandsub ins(%45, %35 : !pto.tile_buf<vec, 16x256xf32>, !pto.tile_buf<vec, 16x1xf32, blayout=col_major>) outs(%31 : !pto.tile_buf<vec, 16x256xf32>)
          pto.texp ins(%48 : !pto.tile_buf<vec, 1x16xf32>) outs(%48 : !pto.tile_buf<vec, 1x16xf32>)
          pto.texp ins(%31 : !pto.tile_buf<vec, 16x256xf32>) outs(%31 : !pto.tile_buf<vec, 16x256xf32>)
          pto.tmul ins(%49, %48 : !pto.tile_buf<vec, 1x16xf32>, !pto.tile_buf<vec, 1x16xf32>) outs(%49 : !pto.tile_buf<vec, 1x16xf32>)
          pto.trowsum ins(%31, %30 : !pto.tile_buf<vec, 16x256xf32>, !pto.tile_buf<vec, 16x256xf32>) outs(%38 : !pto.tile_buf<vec, 16x1xf32, blayout=col_major>)
          pto.tadd ins(%49, %50 : !pto.tile_buf<vec, 1x16xf32>, !pto.tile_buf<vec, 1x16xf32>) outs(%49 : !pto.tile_buf<vec, 1x16xf32>)
        }
        pto.tcvt ins(%31 {rmode = #pto<round_mode CAST_RINT>} : !pto.tile_buf<vec, 16x256xf32>) outs(%32 : !pto.tile_buf<vec, 16x256xf16>)
        pto.barrier <PIPE_V>
        pto.tpush_to_aic(%28 : !pto.tile_buf<vec, 32x256xf16>) {split = 0}
        pto.tfree_from_aic{split = 0}
        %51 = pto.tpop_from_aic {split = 0} -> !pto.tile_buf<vec, 32x32xf32>
        %52 = pto.subview %51[%25, %c0] sizes [16, 32] : !pto.tile_buf<vec, 32x32xf32> -> !pto.tile_buf<vec, 16x32xf32>
        scf.if %43 {
          pto.tmov ins(%52 : !pto.tile_buf<vec, 16x32xf32>) outs(%33 : !pto.tile_buf<vec, 16x32xf32>)
        } else {
          pto.trowexpandmul ins(%33, %36 : !pto.tile_buf<vec, 16x32xf32>, !pto.tile_buf<vec, 16x1xf32, blayout=col_major>) outs(%33 : !pto.tile_buf<vec, 16x32xf32>)
          pto.tadd ins(%33, %52 : !pto.tile_buf<vec, 16x32xf32>, !pto.tile_buf<vec, 16x32xf32>) outs(%33 : !pto.tile_buf<vec, 16x32xf32>)
        }
        pto.tfree_from_aic{split = 0}
      }
      pto.trowexpanddiv ins(%33, %37 : !pto.tile_buf<vec, 16x32xf32>, !pto.tile_buf<vec, 16x1xf32, blayout=col_major>) outs(%33 : !pto.tile_buf<vec, 16x32xf32>)
      %41 = arith.addi %40, %25 : index
      %42 = pto.partition_view %39, offsets = [%41, %c0], sizes = [%c16, %c32_0] : !pto.tensor_view<?x?xf32> -> !pto.partition_tensor_view<16x32xf32>
      pto.tstore ins(%33 : !pto.tile_buf<vec, 16x32xf32>) outs(%42 : !pto.partition_tensor_view<16x32xf32>)
    }
    return
  }
  func.func @call_both(%arg0: memref<256xi64>, %arg1: !pto.ptr<f32>, %arg2: !pto.ptr<f16>, %arg3: !pto.ptr<f16>, %arg4: !pto.ptr<f16>, %arg5: !pto.ptr<f32>) attributes {pto.entry} {
    pto.set_ffts %arg0 : memref<256xi64>
    call @cube_kernel(%arg1, %arg2, %arg3, %arg4) : (!pto.ptr<f32>, !pto.ptr<f16>, !pto.ptr<f16>, !pto.ptr<f16>) -> ()
    call @vector_kernel(%arg1, %arg5) : (!pto.ptr<f32>, !pto.ptr<f32>) -> ()
    return
  }
}

Vector kernel pattern (simplified):

sb_idx     = pto.get_subblock_idx()
row_off_sb = sb_idx * S0_HALF                       # 0 or 16

# Shared full-size tile, allocated once per func
p_fp16 = pto.alloc_tile(p_fp16_ty)                  # [S0=32, S1_TILE]

# Each sub-block grabs its own non-overlapping row-half
p_fp16_half = pto.subset(
    p_fp16,
    offsets=[row_off_sb, 0],
    sizes=[S0_HALF, S1_TILE],
)

# ... softmax compute writing into `p_fp16_half` ...
tile.cvt(p_fp32_half, p_fp16_half)                  # WRITE via subset

# Later: collective op reads the parent tile
pto.tpush_to_aic(p_fp16, 0)                         # READ of parent

The RAW dependency from tile.cvt(... p_fp16_half) to
pto.tpush_to_aic(p_fp16, 0) is invisible to the auto-sync pass
because p_fp16_half and p_fp16 are distinct SSA values. No flag is
emitted; the push reads stale data.

Manual pto.pipe_barrier("V") calls inserted at every cross-sub-block
seam reduce but do not eliminate the mismatch (because they only order
within a sub-block, not across).

Diagnostic

To isolate the alias-tracking issue from the orthogonal "pipe ops
under sub-block parallelism" question (separate issue), I ran the
following control:

  • Force both sub-blocks to compute the upper 16-row half
    (row_off_sb = 0 for both).
  • Only sub-block 0 commits the GM store.
  • Lower 16 rows of every output block are intentionally untouched
    (≈ 50% of elements baseline-wrong).

Result: 47.8% mismatch — i.e. less than the 50% baseline, meaning
the upper 16 rows are computed correctly. So:

  • The half-view algorithm and the pto.subset op itself are correct.
  • When both sub-blocks are also active and writing to non-overlapping
    halves of the same parent (the realistic case), mismatch jumps to
    ~64% — the cross-sub-block writes are not getting the WAR/RAW
    protection auto-sync would normally insert for the parent.

Expected behavior

Two ops A, B accessing memory through any of:

  • the parent tile t,
  • subset(t, ...) or any chain of subsets rooted at t,

should be treated by the auto-sync dependency tracker as touching the
same memory. Standard 'set_flag / wait_flag' analysis on the parent tile. If the
subsets can be proven non-overlapping at compile time (constant
offsets + sizes), the pass may skip emitting a flag for that pair as
an optimisation.

Actual behavior / error logs

Result: 47.8% mismatch against torch npu reference — i.e. less than the 50% baseline, meaning the upper 16 rows are computed correctly. So:

  • The half-view algorithm and the pto.subset op itself are correct.
  • When both sub-blocks are also active and writing to non-overlapping
    halves of the same parent (the realistic case), mismatch jumps to
    ~64% — the cross-sub-block writes are not getting the WAR/RAW
    protection auto-sync would normally insert for the parent.

Git commit

07d7fc3

Host platform

Linux (aarch64)

Target Ascend arch (if relevant)

a3

PTOAS build level (if relevant)

None

Activity

  1. learning-chip commented on Apr 21, 2026

    @learning-chip
    Contributor

    p_fp16_half = pto.subset(
    p_fp16,
    offsets=[row_off_sb, 0],
    sizes=[S0_HALF, S1_TILE],
    )

    pto.subset will be deprecated. Try pto.SubviewOp instead here, it's supposed to work.

  2. TaoTao-real commented on Apr 21, 2026

    @TaoTao-real
    Contributor

    Thanks for reporting this.

    I re-validated on current main (commit 123b664) with the same dependency shape but using pto.subview (as suggested in-thread), and I can no longer reproduce the “missing auto-sync” behavior from the original pto.subset report.

    What I tested:

    • Static half-split subviews ([0:16, :] and [16:32, :]) writing subviews then reading parent tile.
    • Dynamic row-offset subview (row_off = subblock_idx * 16) writing subview then reading parent tile.

    In both cases, auto-sync now emits ordering ops around the hazard path, e.g.:

    • pipe_barrier(PIPE_V)
    • set_flag(PIPE_V, PIPE_MTE3, EVENT_ID0)
    • wait_flag(PIPE_V, PIPE_MTE3, EVENT_ID0)

    So for the subview-based path, this issue appears resolved on latest main.

    Notes:

    • pto.subset is currently not recognized in this path (parse error), consistent with deprecation/migration to pto.subview.
    • If needed, I can upstream the exact reproducer as a lit regression so this behavior stays guarded.
  3. MirkoDeVita98 commented on Apr 21, 2026

    @MirkoDeVita98
    Author

    Thanks for reporting this.

    I re-validated on current main (commit 123b664) with the same dependency shape but using pto.subview (as suggested in-thread), and I can no longer reproduce the “missing auto-sync” behavior from the original pto.subset report.

    What I tested:

    * Static half-split subviews (`[0:16, :]` and `[16:32, :]`) writing subviews then reading parent tile.
    
    * Dynamic row-offset subview (`row_off = subblock_idx * 16`) writing subview then reading parent tile.
    

    In both cases, auto-sync now emits ordering ops around the hazard path, e.g.:

    * `pipe_barrier(PIPE_V)`
    
    * `set_flag(PIPE_V, PIPE_MTE3, EVENT_ID0)`
    
    * `wait_flag(PIPE_V, PIPE_MTE3, EVENT_ID0)`
    

    So for the subview-based path, this issue appears resolved on latest main.

    Notes:

    * `pto.subset` is currently not recognized in this path (parse error), consistent with deprecation/migration to `pto.subview`.
    
    * If needed, I can upstream the exact reproducer as a lit regression so this behavior stays guarded.
    

    I forgot to add that I'm running the nightly build 0.28, your fixes are probably not included in that version right? I wrote also a minimal reproducer where the runtime result is: top half = expected, bottom half = uninitialized garbage.

    module {
      func.func @subview_shared_repro(%arg0: !pto.ptr<f32>) {
        %c0 = arith.constant 0 : index
        %c1 = arith.constant 1 : index
        %c32 = arith.constant 32 : index
        %c256 = arith.constant 256 : index
        %0 = pto.get_subblock_idx
        %1 = arith.index_cast %0 : i64 to index
        %c16 = arith.constant 16 : index
        %2 = arith.muli %1, %c16 : index
        pto.section.vector {
          %3 = pto.alloc_tile : !pto.tile_buf<vec, 32x256xf32>
          %4 = pto.subview %3[%2, %c0] sizes [16, 256] : !pto.tile_buf<vec, 32x256xf32> -> !pto.tile_buf<vec, 16x256xf32>
          %5 = arith.cmpi eq, %1, %c0 : index
          %cst = arith.constant 1.000000e+00 : f32
          %cst_0 = arith.constant 2.000000e+00 : f32
          %6 = arith.select %5, %cst, %cst_0 : f32
          %cst_1 = arith.constant 0.000000e+00 : f32
          pto.tmuls ins(%4, %cst_1 : !pto.tile_buf<vec, 16x256xf32>, f32) outs(%4 : !pto.tile_buf<vec, 16x256xf32>)
          pto.tadds ins(%4, %6 : !pto.tile_buf<vec, 16x256xf32>, f32) outs(%4 : !pto.tile_buf<vec, 16x256xf32>)
          pto.barrier <PIPE_V>
          %7 = arith.cmpi eq, %1, %c0 : index
          scf.if %7 {
            %8 = pto.make_tensor_view %arg0, shape = [%c32, %c256], strides = [%c256, %c1] : !pto.tensor_view<?x?xf32>
            %9 = pto.partition_view %8, offsets = [%c0, %c0], sizes = [%c32, %c256] : !pto.tensor_view<?x?xf32> -> !pto.partition_tensor_view<32x256xf32>
            pto.tstore ins(%3 : !pto.tile_buf<vec, 32x256xf32>) outs(%9 : !pto.partition_tensor_view<32x256xf32>)
          }
        }
        return
      }
    }

    Generated cpp using:

    ptoas --enable-insert-sync ./kernel.pto -o ./kernel.cpp

    and compiled using:

    bisheng \
        -I${PTO_LIB_PATH}/include \
        -fPIC -shared -D_FORTIFY_SOURCE=2 -O2 -std=c++17 \
        -Wno-macro-redefined -Wno-ignored-attributes -fstack-protector-strong \
        -xcce -Xhost-start -Xhost-end \
        -mllvm -cce-aicore-stack-size=0x8000 \
        -mllvm -cce-aicore-function-stack-size=0x8000 \
        -mllvm -cce-aicore-record-overflow=true \
        -mllvm -cce-aicore-addr-transform \
        -mllvm -cce-aicore-dcci-insert-for-scalar=false \
        --npu-arch=dav-2201 -DMEMORY_BASE \
        -std=gnu++17 \
        -DKERNEL_CPP="\"${SCRIPT_DIR}/kernel.cpp\"" \
        ./caller.cpp \
        -o ./lib.so
    #include "pto/pto-inst.hpp"
    using namespace pto;
    
    enum class PTOAutoSyncTailMode : int {
      kBarrierAll = 0,
      kSetWaitMte3ToSEvent0 = 1,
    };
    
    static AICORE inline void ptoas_auto_sync_tail(
        PTOAutoSyncTailMode mode = PTOAutoSyncTailMode::kBarrierAll) {
      switch (mode) {
      case PTOAutoSyncTailMode::kSetWaitMte3ToSEvent0:
        set_flag(PIPE_MTE3, PIPE_S, EVENT_ID0);
        wait_flag(PIPE_MTE3, PIPE_S, EVENT_ID0);
        break;
      case PTOAutoSyncTailMode::kBarrierAll:
      default:
        pipe_barrier(PIPE_ALL);
        break;
      }
    }
    
    __global__ AICORE void subview_shared_repro(__gm__ float* v1) {
      unsigned v2 = 1;
      unsigned v3 = 256;
      unsigned v4 = 0;
      const int32_t v5 = 256;
      const int32_t v6 = 1;
      const int32_t v7 = 0;
      const int32_t v8 = 16;
      const float v9 = 1.0f;
      const float v10 = 2.0f;
      const float v11 = 0.0f;
      const int64_t v12 = 0;
      using T = float;
      int64_t v13 = get_subblockid();
      int32_t v14 = (int32_t) ((int64_t) v13);
    
      #if defined(__DAV_VEC__)
      set_mask_norm();
      set_vector_mask(-1, -1);
      Tile<TileType::Vec, float, 32, 256, BLayout::RowMajor, 32, 256, SLayout::NoneBox, 512, PadValue::Null, CompactMode::Null> v15;
      TASSIGN(v15, v12);
      __ubuf__ float* v16 = v15.data();
      __ubuf__ float* v17 = v16 + (v4 + (unsigned) ((int32_t) (uint32_t) v14 * (uint32_t) v8) * v3 + v4 * v2);
      __ubuf__ float* v18 = (__ubuf__ float*) v17;
      Tile<TileType::Vec, float, 32, 256, BLayout::RowMajor, 16, 256, SLayout::NoneBox, 512, PadValue::Null, CompactMode::Null> v19;
      uint64_t v20 = reinterpret_cast<uint64_t>(v17);
      TASSIGN(v19, v20);
      bool v21 = v14 == v7;
      float v22 = v21 ? v9 : v10;
      TMULS(v19, v19, v11);
      pipe_barrier(PIPE_V);
      TADDS(v19, v19, v22);
      set_flag(PIPE_V, PIPE_MTE3, EVENT_ID0);
      pipe_barrier(PIPE_V);
      wait_flag(PIPE_V, PIPE_MTE3, EVENT_ID0);
      if (v21) {
        pto::Shape<1, 1, 1, 32, 256> v23 = pto::Shape<1, 1, 1, 32, 256>();
        pto::Stride<8192, 8192, 8192, 256, 1> v24 = pto::Stride<8192, 8192, 8192, 256, 1>();
        GlobalTensor<float, pto::Shape<1, 1, 1, 32, 256>, pto::Stride<8192, 8192, 8192, 256, 1>, pto::Layout::ND> v25 = GlobalTensor<float, pto::Shape<1, 1, 1, 32, 256>, pto::Stride<8192, 8192, 8192, 256, 1>, pto::Layout::ND>(v1 + (v4 + v4 * (unsigned) v5 + v4 * (unsigned) v6), v23, v24);
        TSTORE(v25, v15);
      }
      #endif // __DAV_VEC__
    
      ptoas_auto_sync_tail(PTOAutoSyncTailMode::kBarrierAll);
      return;
    }
    

    UPDATE: this works if I only let AIV write and store. The MLIR is generated by the following DSL:

    # Single-AIV control test for subview_shared_repro.
    #
    # Same parent allocation, same two subviews, same store — but only one AIV
    # does everything (the other does nothing). This isolates the per-AIV-
    # private-alloc_tile issue (Bug #2) from any subview mechanics:
    #
    #   * If THIS test passes → subview works fine inside one AIV; the only
    #     failure mode is cross-AIV sharing.
    #   * If THIS test also fails → subview itself is broken even within a
    #     single AIV.
    #
    # Test:
    #   On AIV0 only:
    #     - alloc parent [32, 256]
    #     - subview_top    = parent[ 0..16, :]   ← write 1.0
    #     - subview_bottom = parent[16..32, :]   ← write 2.0
    #     - store full parent to GM
    #   AIV1 does nothing.
    #
    # Expected: GM rows  0..16 = 1.0, rows 16..32 = 2.0.
    
    from ptodsl import pto, tile, to_ir_module
    from ptodsl import scalar as s
    
    const = s.const
    
    ROWS = 32
    HALF = ROWS // 2
    COLS = 256
    
    
    def meta_data():
        fp32 = pto.float32
        ptr_fp32 = pto.PtrType(fp32)
        o_tensor_ty = pto.TensorType(rank=2, dtype=fp32)
        o_sub_ty = pto.SubTensorType(shape=[ROWS, COLS], dtype=fp32)
        parent_ty = pto.TileBufType(
            shape=[ROWS, COLS], dtype=fp32, memory_space="VEC"
        )
        return locals()
    
    
    @to_ir_module(meta_data=meta_data)
    def subview_single_aiv(
        gm_o: "ptr_fp32",
    ) -> None:
        c0 = const(0)
        c1 = const(1)
        cROWS = const(ROWS)
        cCOLS = const(COLS)
        cHALF = const(HALF)
    
        sb_idx = s.index_cast(pto.get_subblock_idx())
        is_aiv0 = s.eq(sb_idx, c0)
    
        with pto.vector_section():
            # Both AIVs allocate (alloc_tile is per-AIV-private), but only AIV0
            # writes and stores. AIV1's parent stays unused.
            parent = pto.alloc_tile(parent_ty)
    
            with pto.if_context(is_aiv0):
                # Two subviews of the SAME AIV0-private parent.
                top = pto.subview(
                    parent, offsets=[c0, c0], sizes=[HALF, COLS]
                )
                bot = pto.subview(
                    parent, offsets=[cHALF, c0], sizes=[HALF, COLS]
                )
    
                # Fill top half with 1.0, bottom half with 2.0.
                tile.muls(top, const(0.0, s.float32), top)
                tile.adds(top, const(1.0, s.float32), top)
    
                tile.muls(bot, const(0.0, s.float32), bot)
                tile.adds(bot, const(2.0, s.float32), bot)
    
                pto.pipe_barrier("V")
    
                # Store full parent.
                tv_o = pto.as_tensor(
                    o_tensor_ty,
                    ptr=gm_o,
                    shape=[cROWS, cCOLS],
                    strides=[cCOLS, c1],
                )
                o_view = pto.slice_view(
                    o_sub_ty,
                    source=tv_o,
                    offsets=[c0, c0],
                    sizes=[cROWS, cCOLS],
                )
                pto.store(parent, o_view)
    
    
    if __name__ == "__main__":
        print(subview_single_aiv)
    module {
      func.func @subview_single_aiv(%arg0: !pto.ptr<f32>) {
        %c0 = arith.constant 0 : index
        %c1 = arith.constant 1 : index
        %c32 = arith.constant 32 : index
        %c256 = arith.constant 256 : index
        %c16 = arith.constant 16 : index
        %0 = pto.get_subblock_idx
        %1 = arith.index_cast %0 : i64 to index
        %2 = arith.cmpi eq, %1, %c0 : index
        pto.section.vector {
          %3 = pto.alloc_tile : !pto.tile_buf<vec, 32x256xf32>
          scf.if %2 {
            %4 = pto.subview %3[%c0, %c0] sizes [16, 256] : !pto.tile_buf<vec, 32x256xf32> -> !pto.tile_buf<vec, 16x256xf32>
            %5 = pto.subview %3[%c16, %c0] sizes [16, 256] : !pto.tile_buf<vec, 32x256xf32> -> !pto.tile_buf<vec, 16x256xf32>
            %cst = arith.constant 0.000000e+00 : f32
            pto.tmuls ins(%4, %cst : !pto.tile_buf<vec, 16x256xf32>, f32) outs(%4 : !pto.tile_buf<vec, 16x256xf32>)
            %cst_0 = arith.constant 1.000000e+00 : f32
            pto.tadds ins(%4, %cst_0 : !pto.tile_buf<vec, 16x256xf32>, f32) outs(%4 : !pto.tile_buf<vec, 16x256xf32>)
            %cst_1 = arith.constant 0.000000e+00 : f32
            pto.tmuls ins(%5, %cst_1 : !pto.tile_buf<vec, 16x256xf32>, f32) outs(%5 : !pto.tile_buf<vec, 16x256xf32>)
            %cst_2 = arith.constant 2.000000e+00 : f32
            pto.tadds ins(%5, %cst_2 : !pto.tile_buf<vec, 16x256xf32>, f32) outs(%5 : !pto.tile_buf<vec, 16x256xf32>)
            pto.barrier <PIPE_V>
            %6 = pto.make_tensor_view %arg0, shape = [%c32, %c256], strides = [%c256, %c1] : !pto.tensor_view<?x?xf32>
            %7 = pto.partition_view %6, offsets = [%c0, %c0], sizes = [%c32, %c256] : !pto.tensor_view<?x?xf32> -> !pto.partition_tensor_view<32x256xf32>
            pto.tstore ins(%3 : !pto.tile_buf<vec, 32x256xf32>) outs(%7 : !pto.partition_tensor_view<32x256xf32>)
          }
        }
        return
      }
    }
    
    

    with generated cpp:

    #include "pto/pto-inst.hpp"
    using namespace pto;
    
    enum class PTOAutoSyncTailMode : int {
      kBarrierAll = 0,
      kSetWaitMte3ToSEvent0 = 1,
    };
    
    static AICORE inline void ptoas_auto_sync_tail(
        PTOAutoSyncTailMode mode = PTOAutoSyncTailMode::kBarrierAll) {
      switch (mode) {
      case PTOAutoSyncTailMode::kSetWaitMte3ToSEvent0:
        set_flag(PIPE_MTE3, PIPE_S, EVENT_ID0);
        wait_flag(PIPE_MTE3, PIPE_S, EVENT_ID0);
        break;
      case PTOAutoSyncTailMode::kBarrierAll:
      default:
        pipe_barrier(PIPE_ALL);
        break;
      }
    }
    
    __global__ AICORE void subview_single_aiv(__gm__ float* v1) {
      unsigned v2 = 16;
      unsigned v3 = 1;
      unsigned v4 = 256;
      unsigned v5 = 0;
      const int32_t v6 = 256;
      const int32_t v7 = 1;
      const int32_t v8 = 0;
      const float v9 = 0.0f;
      const float v10 = 1.0f;
      const float v11 = 2.0f;
      const int64_t v12 = 0;
      using T = float;
      int64_t v13 = get_subblockid();
    
      #if defined(__DAV_VEC__)
      set_mask_norm();
      set_vector_mask(-1, -1);
      Tile<TileType::Vec, float, 32, 256, BLayout::RowMajor, 32, 256, SLayout::NoneBox, 512, PadValue::Null, CompactMode::Null> v14;
      TASSIGN(v14, v12);
      if ((int32_t) ((int64_t) v13) == v8) {
        __ubuf__ float* v15 = v14.data();
        __ubuf__ float* v16 = v15 + (v5 + v5 * v4 + v5 * v3);
        __ubuf__ float* v17 = (__ubuf__ float*) v16;
        Tile<TileType::Vec, float, 32, 256, BLayout::RowMajor, 16, 256, SLayout::NoneBox, 512, PadValue::Null, CompactMode::Null> v18;
        uint64_t v19 = reinterpret_cast<uint64_t>(v16);
        TASSIGN(v18, v19);
        __ubuf__ float* v20 = v14.data();
        __ubuf__ float* v21 = v20 + (v5 + v2 * v4 + v5 * v3);
        __ubuf__ float* v22 = (__ubuf__ float*) v21;
        Tile<TileType::Vec, float, 32, 256, BLayout::RowMajor, 16, 256, SLayout::NoneBox, 512, PadValue::Null, CompactMode::Null> v23;
        uint64_t v24 = reinterpret_cast<uint64_t>(v21);
        TASSIGN(v23, v24);
        TMULS(v18, v18, v9);
        pipe_barrier(PIPE_V);
        TADDS(v18, v18, v10);
        TMULS(v23, v23, v9);
        pipe_barrier(PIPE_V);
        TADDS(v23, v23, v11);
        set_flag(PIPE_V, PIPE_MTE3, EVENT_ID0);
        pipe_barrier(PIPE_V);
        pto::Shape<1, 1, 1, 32, 256> v25 = pto::Shape<1, 1, 1, 32, 256>();
        pto::Stride<8192, 8192, 8192, 256, 1> v26 = pto::Stride<8192, 8192, 8192, 256, 1>();
        GlobalTensor<float, pto::Shape<1, 1, 1, 32, 256>, pto::Stride<8192, 8192, 8192, 256, 1>, pto::Layout::ND> v27 = GlobalTensor<float, pto::Shape<1, 1, 1, 32, 256>, pto::Stride<8192, 8192, 8192, 256, 1>, pto::Layout::ND>(v1 + (v5 + v5 * (unsigned) v6 + v5 * (unsigned) v7), v25, v26);
        wait_flag(PIPE_V, PIPE_MTE3, EVENT_ID0);
        TSTORE(v27, v14);
      }
      #endif // __DAV_VEC__
    
      ptoas_auto_sync_tail(PTOAutoSyncTailMode::kBarrierAll);
      return;
    }
    
  4. changed the title [-][Bug] auto-sync should track aliasing through `pto.subset`[/-] [+][Bug] auto-sync should track aliasing through `pto.subview`[/+] on Apr 21, 2026
  5. zhangstevenunity commented on Apr 22, 2026

    @zhangstevenunity
    Collaborator

    In Split mode,you does not need subview to get the half part of parent tile. Please refer to https://github.com/hw-native-sys/PTOAS/blob/main/test/samples/TPushTPop/test4/kernel.pto

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions