|
1 | 1 | # InjectFenceProxy Pass |
2 | 2 |
|
3 | | -`tl.InjectFenceProxy` is a TIR-level transform that keeps the GPU proxy state consistent on NVIDIA Hopper (SM90+) by inserting `fence.proxy.async` instructions when control flow switches from generic memory operations to asynchronous proxy operations. |
| 3 | +`tl.InjectFenceProxy` is a TIR-level transform that keeps the GPU proxy state consistent on NVIDIA Hopper (SM90+) by inserting `fence.proxy.async` instructions when execution switches from **generic proxy** memory operations to **async proxy** operations. |
4 | 4 |
|
5 | 5 | ## Why Fences Are Needed |
6 | 6 |
|
7 | | -Hopper separates memory instructions into generic and asynchronous proxy paths. When an asynchronous instruction (for example, `cp.async` or `tma.load`) issues after generic traffic (like `ldmatrix` or plain buffer stores), the hardware requires a `fence.proxy.async` to guarantee ordering. Missing fences can lead to race conditions or undefined behavior. |
| 7 | +Hopper separates memory instructions into generic and asynchronous proxy paths. When an asynchronous instruction (for example, `wgmma`, `tma.load`, or `cp.async.bulk`) issues after generic traffic (like `ldmatrix`, `cp.async`, or **shared-memory** buffer stores), the hardware requires a `fence.proxy.async` to guarantee ordering. Missing fences can lead to race conditions or undefined behavior. |
8 | 8 |
|
9 | 9 | ## What the Pass Does |
10 | 10 |
|
11 | | -- Walks every statement in the `PrimFunc`, tracking whether it behaves as a **generic**, **async**, or **neutral** proxy (neutral statements reset the state, such as an explicit fence). |
12 | | -- Automatically lowers `tma_store` intrinsics into the required `arrive`/`wait` handshake so that TMA stores participate correctly in synchronization. |
13 | | -- Injects an explicit `fence.proxy.async` whenever a generic statement is followed by an async statement without an intervening neutral barrier. |
| 11 | +- Walks statements in execution order while tracking a (may-)state of the last proxy kind (**generic**, **async**, or **none/reset**). Control-flow joins (e.g. `if`) merge states conservatively. |
| 12 | +- Normalizes `tma_store` by ensuring the required `tma_store_arrive` / `tma_store_wait` handshake exists immediately after the store. |
| 13 | +- Injects `fence.proxy.async` right before an async-proxy instruction whenever the preceding state can be generic. |
14 | 14 |
|
15 | | -The pass is conservative: unknown extern calls are treated as async so that the fence is inserted rather than accidentally omitted. |
| 15 | +By default, unknown/external calls do **not** affect proxy state. Opaque calls that may write into **shared memory** (e.g. via `tvm_access_ptr` / `address_of`) are treated as generic proxy traffic so a later async-proxy op will still be fenced. |
16 | 16 |
|
17 | 17 | ### Timeline View |
18 | 18 |
|
19 | 19 | ``` |
20 | | -generic initialize_wgmma_descriptor → generic shared-store → async wgmma |
21 | | - │ │ │ |
22 | | - └─ generic proxy ┴─ generic proxy ┴─ async proxy |
23 | | - │ fence inserted here ↑ |
24 | | - └──────────────────────────────┘ |
| 20 | +generic shared-store (or ldmatrix/stmatrix/cp.async) → async op (wgmma / tma / cp.async.bulk) |
| 21 | + │ │ |
| 22 | + └─ generic proxy └─ async proxy |
| 23 | + │ fence inserted here ↑ |
| 24 | + └──────────────────────────────┘ |
25 | 25 | ``` |
26 | 26 |
|
27 | | -The proxy tracker scans the sequence from left to right. The moment it detects a transition from generic to async (between the store and `cp.async` above), it synthesizes a `fence.proxy.async` to reset the hardware proxy state before the async path runs. |
| 27 | +The proxy tracker effectively scans the program in execution order. The moment it detects a possible transition from generic to async (between the store and the async op above), it synthesizes a `fence.proxy.async` to reset the hardware proxy state before the async path runs. |
28 | 28 |
|
29 | 29 | ## Coverage of Intrinsics |
30 | 30 |
|
31 | | -The tracker understands the TileLang intrinsics for TMA load/store, shared-memory MMA (`wgmma`), and TVM/PTX async copy intrinsics (`cp.async` variants). Generic operations currently include `ldmatrix`, `stmatrix`, and descriptor initialization. Other IR nodes (loops, blocks, attributes) receive a proxy kind derived from their bodies so that the analysis survives structured control flow. |
| 31 | +The tracker understands the TileLang intrinsics for TMA load/store, shared-memory MMA (`wgmma`), and TVM/PTX SM90 async copy intrinsics (`cp.async.bulk` family). Generic operations currently include `ldmatrix`, `stmatrix`, `cp.async`, and **shared-memory** `BufferStore` statements. Structured control flow (loops, blocks, branches) is handled by propagating and conservatively merging proxy state. |
32 | 32 |
|
33 | 33 | ## Usage |
34 | 34 |
|
@@ -110,4 +110,7 @@ The only change is the `fence_proxy_async` between the generic descriptor setup |
110 | 110 |
|
111 | 111 | ## Extending the Pass |
112 | 112 |
|
113 | | -If you introduce a new intrinsic that behaves like an async proxy, add it to `IsAsyncIntrinsic` in `src/transform/inject_fence_proxy.cc`. Likewise, extend `IsKnownGeneric` for additional generic operations. When adding new neutral barriers, make sure they set the proxy kind to `kNeutral` so the state resets correctly. |
| 113 | +If you introduce a new intrinsic that behaves like an async proxy, add it to `IsAsyncIntrinsic` in `src/transform/inject_fence_proxy.cc`. Likewise, extend `IsKnownGeneric` for additional generic operations. |
| 114 | + |
| 115 | +Most calls default to `"none"` (no proxy-state effect). `IsNonProxyIntrinsic` exists for well-known synchronization / scheduling helpers and to document intent, but it is not required for correctness if an op is neither generic nor async. |
| 116 | +For custom/opaque ops, you must lower them into known intrinsics (or manually insert `fence_proxy_async`) if they participate in proxy switching. |
0 commit comments