Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,15 @@ if(BUILD_TESTING)
LABELS "PTO"
ENVIRONMENT "${_pto_python_test_env}"
)
add_test(
NAME pto_tload_cache_policy_bindings
COMMAND "${Python3_EXECUTABLE}"
"${CMAKE_CURRENT_SOURCE_DIR}/test/python/tload_cache_policy_bindings.py"
)
set_tests_properties(pto_tload_cache_policy_bindings PROPERTIES
LABELS "PTO"
ENVIRONMENT "${_pto_python_test_env}"
)
add_test(
NAME pto_install_nightly_wheel
COMMAND "${Python3_EXECUTABLE}"
Expand Down
26 changes: 26 additions & 0 deletions docs/PTO_IR_manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,21 @@ Padding mode for load operations.

---

#### LoadCachePolicy

Cache allocation policy for one `pto.tload` operation.

| Value | Int | Description |
|-------|-----|-------------|
| `default` | 0 | Preserve the target's ordinary load and cache behavior |
| `l2_bypass` | 1 | Request a target-defined GM load path that does not allocate in L2 |

The policy is attached to an individual load, so cached and L2-bypass operands
may be mixed in one kernel. The target library owns the architecture-specific
mechanism; PTOAS never emits an address-alias constant.

---

##### `pto.tload` - Load Partition View to Tile

**Summary:** Physical DMA transfer from a global partition view into a local tile buffer.
Expand All @@ -1193,6 +1208,7 @@ For each element (i, j) in the tile valid region:
| `right_padding_num` | `Index` (optional) | Right padding count |
| `init_out_buffer` | `BoolAttr` (default: false) | Initialize output buffer |
| `init_condition` | `AnyType` (optional) | Init condition |
| `cache_policy` | `LoadCachePolicyAttr` (optional) | Per-load cache allocation policy; absence means `default` |

**Results:** None. Writes into `dst` via DPS pattern.

Expand All @@ -1203,10 +1219,12 @@ For each element (i, j) in the tile valid region:
- The destination tile must use `loc=vec` or `loc=mat`.
- The destination tile element type and source partition element type must have the same bitwidth.
- Runtime: all source partition extents must be positive; the destination valid region must be non-negative.
- `cache_policy=l2_bypass` is supported and lowers to the corresponding typed PTO-ISA `TLOAD` policy.
- **Implementation checks (A5)**
- The source partition and destination tile element types must be one of `i8/i16/i32/i64/f16/bf16/f32/f8E4M3*/f8E5M2*/!pto.hif8/!pto.f4E1M2x2/!pto.f4E2M1x2`.
- The destination tile element size must be `1`, `2`, `4`, or `8` bytes, and must match the source partition element size.
- For `i64`, the destination tile `pad` must be `null` or `zero`.
- `cache_policy=l2_bypass` is rejected until the target defines equivalent behavior.

**Hardware Mapping:**

Expand All @@ -1219,6 +1237,14 @@ pto.tload ins(%pv : !pto.partition_tensor_view<16x16xf16>)
outs(%tb : !pto.tile_buf<loc=vec, dtype=f16, rows=16, cols=16, v_row=16, v_col=16, blayout=row_major, slayout=none_box, fractal=512, pad=0>)
```

**L2-bypass example (A2/A3):**

```mlir
pto.tload ins(%weight : !pto.partition_tensor_view<16x16xf16>)
outs(%weight_tile : !pto.tile_buf<loc=mat, dtype=f16, rows=16, cols=16, v_row=16, v_col=16, blayout=row_major, slayout=none_box, fractal=512, pad=0>)
{cache_policy = #pto.load_cache_policy<l2_bypass>}
```

---

##### `pto.tprefetch` - Prefetch Partition View into Tile
Expand Down
8 changes: 8 additions & 0 deletions docs/isa/tile-op/04-dma-data-movement.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This chapter documents the public tile DMA instructions `pto.tload` and `pto.tst
```mlir
pto.tload ins(%src : !pto.partition_tensor_view<...>)
outs(%dst : !pto.tile_buf<...>)
{cache_policy = #pto.load_cache_policy<l2_bypass>}
```
- **semantics:** Physical DMA transfer from a global partition view into a local tile buffer. For each element `(i, j)` in the destination valid region: `dst[i, j] = src[i, j]`.

Expand All @@ -22,13 +23,16 @@ pto.tload ins(%src : !pto.partition_tensor_view<...>)
|-----------|------|-------------|
| `src` | `PartitionTensorViewType` | Source partition view. |
| `dst` | `pto.tile_buf` | Destination tile buffer. |
| `cache_policy` | `LoadCachePolicyAttr` (optional) | `default` when absent; `l2_bypass` requests a non-allocating L2 path for this load. |

**Constraints:**

- Tile element type ∈ `{i8, i16, i32, i64, f16, bf16, f32}`.
- Destination tile must use `loc=vec`.
- Destination tile element type and source partition element type must have the same bitwidth.
- Runtime: source partition extents and destination valid region must be positive.
- `l2_bypass` is currently supported on A2/A3 and rejected on A5.
- PTO-ISA owns the target-specific bypass mechanism; PTOAS emits no address-alias constant.

**Pipeline:** `PIPE_MTE2`.

Expand All @@ -39,6 +43,10 @@ pto.tload ins(%pv : !pto.partition_tensor_view<16x16xf16>)
outs(%tb : !pto.tile_buf<vec, 16x16xf16>)
```

When `cache_policy` is absent or `default`, PTOAS emits the legacy
`TLOAD(dst, src)` call. `l2_bypass` emits
`TLOAD<pto::LoadCachePolicy::L2Bypass>(dst, src)`.

---

## `pto.tstore`
Expand Down
21 changes: 21 additions & 0 deletions include/PTO/IR/PTOAttrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ def PTO_AddressSpaceAttr : PTO_Attr<"AddressSpace", "address_space"> {
}];
}

//===----------------------------------------------------------------------===//
// Tile load cache policy
//===----------------------------------------------------------------------===//

def PTO_LoadCachePolicyEnum : PTO_I32Enum<
"LoadCachePolicy", "PTO tile load cache policy", [
I32EnumAttrCase<"Default", 0, "default">,
I32EnumAttrCase<"L2Bypass", 1, "l2_bypass">
]>;

def PTO_LoadCachePolicyAttr
: EnumAttr<PTO_Dialect, PTO_LoadCachePolicyEnum, "load_cache_policy"> {
let assemblyFormat = "`<` params `>`";
let summary = "cache allocation policy for a tile load";
let description = [{
Selects the cache allocation policy for one GM-to-tile transfer. The
default policy preserves the target's ordinary load behavior. L2Bypass
requests a target-defined load path that does not allocate in L2.
}];
}

//===----------------------------------------------------------------------===//
// Signedness
//===----------------------------------------------------------------------===//
Expand Down
19 changes: 12 additions & 7 deletions include/PTO/IR/PTOOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,8 @@ def TLoadOp : PTO_TOp<"tload", [
Optional<Index>:$left_padding_num,
Optional<AnyType>:$right_padding_num,
DefaultValuedOptionalAttr<BoolAttr, "false">:$init_out_buffer,
Optional<AnyType>:$init_condition
Optional<AnyType>:$init_condition,
OptionalAttr<PTO_LoadCachePolicyAttr>:$cache_policy
);

let results = (outs
Expand All @@ -617,37 +618,41 @@ def TLoadOp : PTO_TOp<"tload", [
build($_builder, $_state, res, src, dst,
/*pad_mode=*/nullptr, /*pad_value=*/nullptr,
/*left=*/nullptr, /*right=*/nullptr,
/*init_out=*/nullptr, /*init_cond=*/nullptr);
/*init_out=*/nullptr, /*init_cond=*/nullptr,
/*cache_policy=*/nullptr);
}]>,

// 2. With left_padding_num
OpBuilder<(ins "TypeRange":$res, "Value":$src, "Value":$dst,
"Value":$left_padding_num), [{
build($_builder, $_state, res, src, dst,
nullptr, nullptr, left_padding_num, nullptr, nullptr, nullptr);
nullptr, nullptr, left_padding_num, nullptr, nullptr, nullptr,
nullptr);
}]>,

// 3. With pad_mode, pad_value
OpBuilder<(ins "TypeRange":$res, "Value":$src, "Value":$dst,
"pto::PadModeAttr":$pad_mode, "Value":$pad_value), [{
build($_builder, $_state, res, src, dst,
pad_mode, pad_value, nullptr, nullptr, nullptr, nullptr);
pad_mode, pad_value, nullptr, nullptr, nullptr, nullptr, nullptr);
}]>,

// 4. ... + left
OpBuilder<(ins "TypeRange":$res, "Value":$src, "Value":$dst,
"pto::PadModeAttr":$pad_mode, "Value":$pad_value,
"Value":$left_padding_num), [{
build($_builder, $_state, res, src, dst,
pad_mode, pad_value, left_padding_num, nullptr, nullptr, nullptr);
pad_mode, pad_value, left_padding_num, nullptr, nullptr, nullptr,
nullptr);
}]>,

// 5. ... + left + right
OpBuilder<(ins "TypeRange":$res, "Value":$src, "Value":$dst,
"pto::PadModeAttr":$pad_mode, "Value":$pad_value,
"Value":$left_padding_num, "Value":$right_padding_num), [{
build($_builder, $_state, res, src, dst,
pad_mode, pad_value, left_padding_num, right_padding_num, nullptr, nullptr);
pad_mode, pad_value, left_padding_num, right_padding_num, nullptr,
nullptr, nullptr);
}]>,

// 6. ... + left + right + bool
Expand All @@ -657,7 +662,7 @@ def TLoadOp : PTO_TOp<"tload", [
"bool":$init_out_buffer), [{
build($_builder, $_state, res, src, dst,
pad_mode, pad_value, left_padding_num, right_padding_num,
init_out_buffer, nullptr);
init_out_buffer, nullptr, nullptr);
}]>
];

Expand Down
8 changes: 8 additions & 0 deletions include/pto-c/Dialect/PTO.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ MLIR_CAPI_EXPORTED MlirAttribute mlirPTOAddressSpaceAttrGet(MlirContext ctx, int
// Read back enum value (0..6)
MLIR_CAPI_EXPORTED int32_t mlirPTOAddressSpaceAttrGetValue(MlirAttribute attr);

// ---- #pto.load_cache_policy<...> ----
MLIR_CAPI_EXPORTED bool
mlirPTOAttrIsALoadCachePolicyAttr(MlirAttribute attr);
MLIR_CAPI_EXPORTED MlirAttribute
mlirPTOLoadCachePolicyAttrGet(MlirContext ctx, int32_t value);
MLIR_CAPI_EXPORTED int32_t
mlirPTOLoadCachePolicyAttrGetValue(MlirAttribute attr);

// ---- !pto.tensor_view<shape x elem> ----
MLIR_CAPI_EXPORTED bool mlirPTOTypeIsATensorViewType(MlirType type);
MLIR_CAPI_EXPORTED MlirType mlirPTOTensorViewTypeGet(MlirContext ctx, intptr_t rank,
Expand Down
8 changes: 8 additions & 0 deletions lib/Bindings/Python/PTOModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ void mlir::pto::python::populatePTODialectBindings(pybind11::module_ &m) {
.value("GM", mlir::pto::FenceScope::GM)
.value("All", mlir::pto::FenceScope::All)
.export_values();
py::enum_<mlir::pto::LoadCachePolicy>(m, "LoadCachePolicy")
.value("Default", mlir::pto::LoadCachePolicy::Default)
.value("L2Bypass", mlir::pto::LoadCachePolicy::L2Bypass);
py::enum_<mlir::pto::BLayout>(m, "BLayout")
.value("RowMajor", mlir::pto::BLayout::RowMajor)
.value("ColMajor", mlir::pto::BLayout::ColMajor);
Expand Down Expand Up @@ -551,6 +554,11 @@ void mlir::pto::python::populatePTODialectBindings(pybind11::module_ &m) {
return mlirPTOFenceScopeAttrGetValue(self);
});

bindPTOEnumAttr(m, "LoadCachePolicyAttr", "LoadCachePolicy",
mlirPTOAttrIsALoadCachePolicyAttr,
mlirPTOLoadCachePolicyAttrGet,
mlirPTOLoadCachePolicyAttrGetValue);

mlir_attribute_subclass(
m, "RoundModeAttr",
[](MlirAttribute a) { return mlirPTOAttrIsARoundModeAttr(a); })
Expand Down
20 changes: 20 additions & 0 deletions lib/CAPI/Dialect/PTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,26 @@ int32_t mlirPTOAddressSpaceAttrGetValue(MlirAttribute attr) {
return static_cast<int32_t>(a.getAddressSpace());
}

bool mlirPTOAttrIsALoadCachePolicyAttr(MlirAttribute attr) {
return mlir::isa<mlir::pto::LoadCachePolicyAttr>(unwrap(attr));
}

MlirAttribute mlirPTOLoadCachePolicyAttrGet(MlirContext ctx, int32_t value) {
const bool isValidPolicy =
value >= static_cast<int32_t>(mlir::pto::LoadCachePolicy::Default) &&
value <= static_cast<int32_t>(mlir::pto::LoadCachePolicy::L2Bypass);
if (!isValidPolicy) {
return MlirAttribute{nullptr};
}
auto policy = static_cast<mlir::pto::LoadCachePolicy>(value);
return wrap(mlir::pto::LoadCachePolicyAttr::get(unwrap(ctx), policy));
}

int32_t mlirPTOLoadCachePolicyAttrGetValue(MlirAttribute attr) {
auto policy = mlir::cast<mlir::pto::LoadCachePolicyAttr>(unwrap(attr));
return static_cast<int32_t>(policy.getValue());
}

//===----------------------------------------------------------------------===//
// Type queries / constructors for !pto.tensor_view<shape x elem>
//===----------------------------------------------------------------------===//
Expand Down
7 changes: 7 additions & 0 deletions lib/PTO/IR/PTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3863,6 +3863,13 @@ LogicalResult TLoadOp::verify() {
};

auto verifyA5 = [&]() -> LogicalResult {
if (auto policy = getCachePolicyAttr();
policy && policy.getValue() == pto::LoadCachePolicy::L2Bypass) {
return emitOpError(
"does not support cache_policy=l2_bypass on A5; the policy is "
"currently supported only on A2/A3");
}

auto common = verifyCommon(/*allowLowPrecision=*/true);
if (failed(common)) {
return failure();
Expand Down
4 changes: 3 additions & 1 deletion lib/PTO/Transforms/ConvertToPTOOp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ LogicalResult replaceMemCopyByPTOLoadOp(memref::CopyOp copyOp,
auto maybeLeftPadNum = getLeftPadNum(rewriter, maybeAlloc);

auto loadOp = rewriter.create<pto::TLoadOp>(copyOp->getLoc(), TypeRange(),
copyOp.getSource(), dst, nullptr, nullptr, nullptr, nullptr, false, nullptr);
copyOp.getSource(), dst, nullptr,
nullptr, nullptr, nullptr, false,
nullptr, nullptr);
if (maybeLeftPadNum.has_value()) {
loadOp.getLeftPaddingNumMutable().assign(maybeLeftPadNum.value());
}
Expand Down
9 changes: 8 additions & 1 deletion lib/PTO/Transforms/PTOToEmitC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4809,8 +4809,15 @@ struct PTOTLoadToTLOAD : public OpConversionPattern<pto::TLoadOp> {
Value src = peelGlobalTensorConversionBridge(adaptor.getSrc());
Value dst = adaptor.getDst();

ArrayAttr templateArgs = ArrayAttr{};
if (auto policy = op.getCachePolicyAttr();
policy && policy.getValue() == pto::LoadCachePolicy::L2Bypass) {
templateArgs = rewriter.getArrayAttr({emitc::OpaqueAttr::get(
rewriter.getContext(), "pto::LoadCachePolicy::L2Bypass")});
}

rewriter.create<emitc::CallOpaqueOp>(op.getLoc(), TypeRange{}, "TLOAD",
ArrayAttr{}, ArrayAttr{},
ArrayAttr{}, templateArgs,
ValueRange{dst, src});

if (op->getNumResults() == 1) {
Expand Down
10 changes: 10 additions & 0 deletions python/pto/dialects/pto.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def _export_optional_cext_symbol(name):
AddressSpaceAttr = _pto_mod.AddressSpaceAttr
FenceScope = _pto_mod.FenceScope
FenceScopeAttr = _pto_mod.FenceScopeAttr
LoadCachePolicy = _pto_mod.LoadCachePolicy
LoadCachePolicyAttr = _pto_mod.LoadCachePolicyAttr
TileBufConfigAttr = _pto_mod.TileBufConfigAttr
BLayout = _pto_mod.BLayout
BLayoutAttr = _pto_mod.BLayoutAttr
Expand Down Expand Up @@ -212,12 +214,18 @@ def address_space_attr_builder(value, context=None):
def fence_scope_attr_builder(value, context=None):
return FenceScopeAttr.get(value, context)

def load_cache_policy_attr_builder(value, context=None):
return LoadCachePolicyAttr.get(value, context)

_ods_ir.AttrBuilder.insert(
"PTO_AddressSpaceAttr", address_space_attr_builder, replace=True
)
_ods_ir.AttrBuilder.insert(
"PTO_FenceScopeAttr", fence_scope_attr_builder, replace=True
)
_ods_ir.AttrBuilder.insert(
"PTO_LoadCachePolicyAttr", load_cache_policy_attr_builder, replace=True
)


_install_enum_attr_builders()
Expand Down Expand Up @@ -248,6 +256,8 @@ def fence_scope_attr_builder(value, context=None):
"AddressSpaceAttr",
"FenceScope",
"FenceScopeAttr",
"LoadCachePolicy",
"LoadCachePolicyAttr",
"BLayout",
"BLayoutAttr",
"SLayout",
Expand Down
48 changes: 48 additions & 0 deletions test/lit/pto/tload_cache_policy.pto
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2026 Huawei Technologies Co., Ltd.
// This program is free software, you can redistribute it and/or modify it under the terms and conditions of
// CANN Open Software License Agreement Version 2.0 (the "License").
// Please refer to the License for details. You may not use this file except in compliance with the License.
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
// See LICENSE in the root of the software repository for the full text of the License.

// RUN: ptoas --pto-level=level3 --pto-arch=a3 --emit-pto-ir %s 2>&1 \
// RUN: | FileCheck %s --check-prefix=PTOIR
// RUN: ptoas --pto-level=level3 --pto-arch=a3 %s 2>&1 \
// RUN: | FileCheck %s --check-prefix=EMITC
// RUN: not ptoas --pto-level=level3 --pto-arch=a5 %s -o /dev/null 2>&1 \
// RUN: | FileCheck %s --check-prefix=A5-ERR

module {
func.func private @default_load(
%src: !pto.partition_tensor_view<16x16xf32>,
%dst: !pto.tile_buf<vec, 16x16xf32>) {
pto.tload ins(%src : !pto.partition_tensor_view<16x16xf32>)
outs(%dst : !pto.tile_buf<vec, 16x16xf32>)
return
}

func.func private @l2_bypass_load(
%src: !pto.partition_tensor_view<16x16xf32>,
%dst: !pto.tile_buf<vec, 16x16xf32>) {
pto.tload ins(%src : !pto.partition_tensor_view<16x16xf32>)
outs(%dst : !pto.tile_buf<vec, 16x16xf32>)
{cache_policy = #pto.load_cache_policy<l2_bypass>}
return
}
}

// PTOIR-LABEL: func.func private @default_load(
// PTOIR: pto.tload ins(
// PTOIR-NOT: cache_policy
// PTOIR-LABEL: func.func private @l2_bypass_load(
// PTOIR: pto.tload ins(
// PTOIR-SAME: cache_policy = #pto.load_cache_policy<l2_bypass>

// EMITC-LABEL: default_load(
// EMITC: TLOAD(
// EMITC-LABEL: l2_bypass_load(
// EMITC: TLOAD<pto::LoadCachePolicy::L2Bypass>(
// EMITC-NOT: 0x80000000000

// A5-ERR: 'pto.tload' op does not support cache_policy=l2_bypass on A5
Loading
Loading