It is time to realize it.
1. Motivation
As we move towards supporting the backward pass for Blackwell (SM120), we face a strict 99KB shared memory limit per thread block. This proposal strictly targets the optimization of the fused_bwd.py kernel for single-GPU execution.
For the target configuration of block_S = 32, DK = DV = 128, and fp16 precision, the current naive allocation in fused_bwd.py exceeds this limit by approximately 37KB. This issue outlines a two-step optimization strategy to fit the kernel within the hardware constraints while maintaining correctness.
Note: Context Parallelism (CP) is out of scope for this initial implementation and will be considered in subsequent phases.
2. Current Shared Memory Budget Analysis
Below is the breakdown of the current shared memory consumption for the target configuration: block_S = 32, DK = DV = 128, fp16.
| Buffer Group |
Components |
Calculation / Dimensions |
Current Size |
After Opt 1 |
After Opt 2 |
| tmp_shared_1_x |
1_1, 1_2, 1_3 |
$3 \times 32 \times 32 \times 2\text{B}$ |
6 KB |
6 KB |
6 KB |
| tmp_shared_2_x |
2_1, 2_2, 2_3 |
$3 \times 32 \times 128 \times 2\text{B}$ |
24 KB |
24 KB |
24 KB |
| tmp_shared_4_1 |
4_1 (Staging) |
$128 \times 128 \times 2\text{B}$ |
32 KB |
32 KB |
0 KB ✅ |
| I/O Buffers |
do, q, k, v |
$4 \times 32 \times 128 \times 2\text{B}$ |
32 KB |
24 KB ✅ |
24 KB |
| State Buffers |
h, a |
$32\text{KB} + 2\text{KB}$ |
34 KB |
34 KB |
34 KB |
| Scalars/Misc |
g, dg, db, etc. |
$6 \times 32 \times 4\text{B}$ |
0.75 KB |
0.75 KB |
0.75 KB |
| dqkv_shared |
Output staging |
- |
8 KB |
8 KB |
8 KB |
| Total Usage |
|
|
~136.75 KB |
~128.75 KB |
~96.75 KB |
| Hardware Limit |
SM120 (Blackwell) |
|
99 KB |
❌ Overflow |
✅ Fit
|
Deficit Analysis:
- Current Deficit: ~37.75 KB over the 99KB limit.
- After Opt 1 (Reuse
q_shared): Saves ~8 KB. Deficit reduces to ~29 KB.
- After Opt 2 (Merge Warps): Saves ~32 KB (
tmp_shared_4_1). Final usage fits within the limit with ~2.25 KB margin.
3. Proposed Optimization Plan
To bridge this gap, I propose the following two optimizations:
Opt 1: Reuse q_shared with tmp_shared_2_1 (Save ~8 KB)
- Mechanism: By moving
T.copy(q_shared, odot_fragment_2) (currently line 662) to occur before the write of dV' into tmp_shared_2_1, we can alias tmp_shared_2_1 to occupy the memory region previously reserved for q_shared.
- Validation: This pattern has been validated on H20 (SM90). It eliminates the standalone
q_shared allocation without breaking the producer-consumer pipeline.
- Result: Deficit reduces to ~29 KB.
- Reference: Briefly discussed in #PR 21.
Opt 2: Merge Consumer S & Consumer K Warps (Save ~32 KB)
- Mechanism:
- Currently,
tmp_shared_4_1 (32 KB) is used to stage dh_fragment for GEMM operations.
- By merging the
Consumer S (tx < 128) and Consumer K (tx < 256) warp groups, we can perform T.gemm directly between Shared Memory and Register Fragments (dh_fragment).
- TileLang supports
T.gemm(shared_memory, fragment) operations (verified on RTX 5090/SM120 simulators).
- Impact: This allows
dh_fragment to remain resident in registers throughout the iteration, eliminating the need for the large tmp_shared_4_1 buffer entirely.
- Trade-off: While this saves significant SMEM, it increases register pressure. However, since
tmp_shared_4_1 is only involved in two specific GEMM calls, keeping dh in registers is likely more efficient than spilling or using SMEM staging.
4. Discussion Points
Opt 1: Reuse q_shared with tmp_shared_2_1 (~8 KB savings)
Status: ✅ Validated on SM90 (H20/H800), but encountered precision issues in some test cases after dtype modification. Investigation ongoing.
Impact: Reduces deficit from ~37KB to ~29KB (still over limit).
Opt 2: Merge Consumer S & Consumer K Warps (~32 KB savings)
Status: ❌ Blocked due to fundamental conflicts:
Dual layout requirements: tmp_shared_4_1 needs both transpose-B and row-major layouts for different GEMMs, which register fragments cannot simultaneously satisfy.
Type conversion barrier: Cannot copy between fragments with different layouts in TileLang, forcing fallback to shared memory staging.
It is time to realize it.
1. Motivation
As we move towards supporting the backward pass for Blackwell (SM120), we face a strict 99KB shared memory limit per thread block. This proposal strictly targets the optimization of the
fused_bwd.pykernel for single-GPU execution.For the target configuration of
block_S = 32,DK = DV = 128, andfp16precision, the current naive allocation infused_bwd.pyexceeds this limit by approximately 37KB. This issue outlines a two-step optimization strategy to fit the kernel within the hardware constraints while maintaining correctness.2. Current Shared Memory Budget Analysis
Below is the breakdown of the current shared memory consumption for the target configuration:
block_S = 32,DK = DV = 128,fp16.Deficit Analysis:
q_shared): Saves ~8 KB. Deficit reduces to ~29 KB.tmp_shared_4_1). Final usage fits within the limit with ~2.25 KB margin.3. Proposed Optimization Plan
To bridge this gap, I propose the following two optimizations:
Opt 1: Reuse
q_sharedwithtmp_shared_2_1(Save ~8 KB)T.copy(q_shared, odot_fragment_2)(currently line 662) to occur before the write ofdV'intotmp_shared_2_1, we can aliastmp_shared_2_1to occupy the memory region previously reserved forq_shared.q_sharedallocation without breaking the producer-consumer pipeline.Opt 2: Merge Consumer S & Consumer K Warps (Save ~32 KB)
tmp_shared_4_1(32 KB) is used to stagedh_fragmentfor GEMM operations.Consumer S(tx < 128) andConsumer K(tx < 256) warp groups, we can performT.gemmdirectly between Shared Memory and Register Fragments (dh_fragment).T.gemm(shared_memory, fragment)operations (verified on RTX 5090/SM120 simulators).dh_fragmentto remain resident in registers throughout the iteration, eliminating the need for the largetmp_shared_4_1buffer entirely.tmp_shared_4_1is only involved in two specific GEMM calls, keepingdhin registers is likely more efficient than spilling or using SMEM staging.4. Discussion Points
Opt 1: Reuse q_shared with tmp_shared_2_1 (~8 KB savings)
Status: ✅ Validated on SM90 (H20/H800), but encountered precision issues in some test cases after dtype modification. Investigation ongoing.
Impact: Reduces deficit from ~37KB to ~29KB (still over limit).
Opt 2: Merge Consumer S & Consumer K Warps (~32 KB savings)
Status: ❌ Blocked due to fundamental conflicts:
Dual layout requirements: tmp_shared_4_1 needs both transpose-B and row-major layouts for different GEMMs, which register fragments cannot simultaneously satisfy.
Type conversion barrier: Cannot copy between fragments with different layouts in TileLang, forcing fallback to shared memory staging.