-
Notifications
You must be signed in to change notification settings - Fork 635
[Cleanup] Remove unnecessary macros in tilelang examples #1514
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
64dc38a
ea7da83
bf5e2d9
5bcb9cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,81 +50,6 @@ def flashattn( | |
| past_len = seq_kv - seq_q | ||
| assert past_len >= 0, "seq_kv must be greater than or equal to seq_q" | ||
|
|
||
| @T.macro | ||
| def MMA0( | ||
| K: T.Tensor(kv_shape, dtype), | ||
| Q_shared: T.SharedBuffer([block_M, dim], dtype), | ||
| K_shared: T.SharedBuffer([block_N, dim], dtype), | ||
| acc_s: T.FragmentBuffer([block_M, block_N], accum_dtype), | ||
| k: T.int32, | ||
| bx: T.int32, | ||
| by: T.int32, | ||
| bz: T.int32, | ||
| ): | ||
| T.copy(K[bz, by, k * block_N : (k + 1) * block_N, :], K_shared) | ||
| for i, j in T.Parallel(block_M, block_N): | ||
| q_idx = bx * block_M + i + past_len | ||
| k_idx = k * block_N + j | ||
| if window_size is not None: | ||
| acc_s[i, j] = T.if_then_else(q_idx >= k_idx and q_idx < k_idx + window_size, 0, -T.infinity(acc_s.dtype)) | ||
| else: | ||
| acc_s[i, j] = T.if_then_else(q_idx >= k_idx, 0, -T.infinity(acc_s.dtype)) | ||
| T.gemm(Q_shared, K_shared, acc_s, transpose_B=True, policy=T.GemmWarpPolicy.FullRow) | ||
|
|
||
| @T.macro | ||
| def MMA1( | ||
| V: T.Tensor(kv_shape, dtype), | ||
| V_shared: T.SharedBuffer([block_M, dim], dtype), | ||
| acc_s_cast: T.FragmentBuffer([block_M, block_N], dtype), | ||
| acc_o: T.FragmentBuffer([block_M, dim], accum_dtype), | ||
| k: T.int32, | ||
| by: T.int32, | ||
| bz: T.int32, | ||
| ): | ||
| T.copy(V[bz, by, k * block_N : (k + 1) * block_N, :], V_shared) | ||
| T.gemm(acc_s_cast, V_shared, acc_o, policy=T.GemmWarpPolicy.FullRow) | ||
|
|
||
| @T.macro | ||
| def Softmax( | ||
| acc_s: T.FragmentBuffer([block_M, block_N], accum_dtype), | ||
| acc_s_cast: T.FragmentBuffer([block_M, block_N], dtype), | ||
| scores_max: T.FragmentBuffer([block_M], accum_dtype), | ||
| scores_max_prev: T.FragmentBuffer([block_M], accum_dtype), | ||
| scores_scale: T.FragmentBuffer([block_M], accum_dtype), | ||
| scores_sum: T.FragmentBuffer([block_M], accum_dtype), | ||
| logsum: T.FragmentBuffer([block_M], accum_dtype), | ||
| ): | ||
| T.copy(scores_max, scores_max_prev) | ||
| T.fill(scores_max, -T.infinity(accum_dtype)) | ||
| T.reduce_max(acc_s, scores_max, dim=1, clear=False) | ||
| for i in T.Parallel(block_M): | ||
| scores_max[i] = T.max(scores_max[i], scores_max_prev[i]) | ||
| # To do causal softmax, we need to set the scores_max to 0 if it is -inf | ||
| # This process is called Check_inf in FlashAttention3 code, and it only need to be done | ||
| # NOTE(wt): check_inf is necessary for sliding window attention. | ||
| for i in T.Parallel(block_M): | ||
| if window_size is not None: | ||
| scores_max[i] = T.if_then_else(scores_max[i] == -T.infinity(accum_dtype), 0, scores_max[i]) | ||
| scores_scale[i] = T.exp2(scores_max_prev[i] * scale - scores_max[i] * scale) | ||
|
|
||
| for i, j in T.Parallel(block_M, block_N): | ||
| # Instead of computing exp(x - max), we compute exp2(x * log_2(e) - | ||
| # max * log_2(e)) This allows the compiler to use the ffma | ||
| # instruction instead of fadd and fmul separately. | ||
| acc_s[i, j] = T.exp2(acc_s[i, j] * scale - scores_max[i] * scale) | ||
| T.reduce_sum(acc_s, scores_sum, dim=1) | ||
| for i in T.Parallel(block_M): | ||
| logsum[i] = logsum[i] * scores_scale[i] + scores_sum[i] | ||
| T.copy(acc_s, acc_s_cast) | ||
|
|
||
| @T.macro | ||
| def Rescale( | ||
| acc_o: T.FragmentBuffer([block_M, dim], accum_dtype), | ||
| scores_scale: T.FragmentBuffer([block_M], accum_dtype), | ||
| ): | ||
| for i, j in T.Parallel(block_M, dim): | ||
| acc_o[i, j] *= scores_scale[i] | ||
|
|
||
| @T.prim_func | ||
| def main( | ||
| Q: T.Tensor(q_shape, dtype), | ||
|
|
@@ -169,10 +94,44 @@ def main( | |
| start = T.max(0, (bx * block_M + past_len - window_size) // block_N) if window_size is not None else 0 | ||
|
|
||
| for k in T.Pipelined(start, end, num_stages=num_stages): | ||
| MMA0(K, Q_shared, K_shared, acc_s, k, bx, by, bz) | ||
| Softmax(acc_s, acc_s_cast, scores_max, scores_max_prev, scores_scale, scores_sum, logsum) | ||
| Rescale(acc_o, scores_scale) | ||
| MMA1(V, V_shared, acc_s_cast, acc_o, k, by, bz) | ||
| T.copy(K[bz, by, k * block_N : (k + 1) * block_N, :], K_shared) | ||
| for i, j in T.Parallel(block_M, block_N): | ||
| q_idx = bx * block_M + i + past_len | ||
| k_idx = k * block_N + j | ||
| if window_size is not None: | ||
| acc_s[i, j] = T.if_then_else(q_idx >= k_idx and q_idx < k_idx + window_size, 0, -T.infinity(acc_s.dtype)) | ||
| else: | ||
| acc_s[i, j] = T.if_then_else(q_idx >= k_idx, 0, -T.infinity(acc_s.dtype)) | ||
| T.gemm(Q_shared, K_shared, acc_s, transpose_B=True, policy=T.GemmWarpPolicy.FullRow) | ||
|
|
||
| T.copy(scores_max, scores_max_prev) | ||
| T.fill(scores_max, -T.infinity(accum_dtype)) | ||
| T.reduce_max(acc_s, scores_max, dim=1, clear=False) | ||
| for i in T.Parallel(block_M): | ||
| scores_max[i] = T.max(scores_max[i], scores_max_prev[i]) | ||
| # To do causal softmax, we need to set the scores_max to 0 if it is -inf | ||
| # This process is called Check_inf in FlashAttention3 code, and it only need to be done | ||
| # NOTE(wt): check_inf is necessary for sliding window attention. | ||
| for i in T.Parallel(block_M): | ||
| if window_size is not None: | ||
| scores_max[i] = T.if_then_else(scores_max[i] == -T.infinity(accum_dtype), 0, scores_max[i]) | ||
| scores_scale[i] = T.exp2(scores_max_prev[i] * scale - scores_max[i] * scale) | ||
| for i, j in T.Parallel(block_M, block_N): | ||
| # Instead of computing exp(x - max), we compute exp2(x * log_2(e) - | ||
| # max * log_2(e)) This allows the compiler to use the ffma | ||
| # instruction instead of fadd and fmul separately. | ||
| acc_s[i, j] = T.exp2(acc_s[i, j] * scale - scores_max[i] * scale) | ||
| T.reduce_sum(acc_s, scores_sum, dim=1) | ||
| for i in T.Parallel(block_M): | ||
| logsum[i] = logsum[i] * scores_scale[i] + scores_sum[i] | ||
| T.copy(acc_s, acc_s_cast) | ||
|
|
||
| for i, j in T.Parallel(block_M, dim): | ||
| acc_o[i, j] *= scores_scale[i] | ||
|
|
||
| T.copy(V[bz, by, k * block_N : (k + 1) * block_N, :], V_shared) | ||
| T.gemm(acc_s_cast, V_shared, acc_o, policy=T.GemmWarpPolicy.FullRow) | ||
|
|
||
|
Comment on lines
+87
to
+124
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Same code duplication concern applies here. This file contains the same inlined attention logic as |
||
| for i in T.Parallel(block_M): | ||
| logsum[i] += T.exp2(sinks[i] * 1.44269504 - scores_max[i] * scale) # The only change for attention sink | ||
| for i, j in T.Parallel(block_M, dim): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
GQA variant duplicates logic with minor indexing differences.
This GQA implementation differs from the MHA versions only in the K/V indexing (
by // groupson lines 112, 147). The core attention computation is identical. This is a strong signal that a parameterized helper function could serve all variants (MHA, GQA, with/without pipelining) while eliminating duplication.A unified helper could accept an indexing callback or parameter to handle the MHA vs GQA difference, reducing the maintenance burden across all 27 affected files.