-
Notifications
You must be signed in to change notification settings - Fork 328
[Feature]: Add benchmark scripts for examples #1287
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
base: main
Are you sure you want to change the base?
Changes from all commits
f129b3b
de9c98a
12df0ec
725342d
ec019fc
29677d2
09912b5
17504a0
f2a4128
7fa4d18
6edc3a9
041aa95
4767697
31bfbe6
c16ebfe
2921c73
1c23908
730ae68
164672f
c7b3413
494ba47
326b8e5
7d75df2
58e7ffe
8df8ec5
b5cb2b0
487e652
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 |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ on: | |
| - created | ||
|
|
||
| permissions: | ||
| contents: read | ||
| contents: write | ||
|
|
||
| concurrency: | ||
| group: "${{ github.workflow }}-${{ github.ref }}" | ||
|
|
@@ -53,8 +53,12 @@ jobs: | |
| run: | | ||
| python -m venv tll | ||
| source tll/bin/activate | ||
| pip install -r requirements-test.txt | ||
| pip install . | ||
| export PIP_CONFIG_FILE=/dev/null | ||
| export PYTHONUSERBASE="" | ||
| pip config unset global.user | ||
| pip config unset user.user | ||
| pip install --no-user -r requirements-test.txt | ||
| pip install --no-user . | ||
|
|
||
| - name: Install original version | ||
| run: | | ||
|
|
@@ -64,25 +68,70 @@ jobs: | |
| git checkout main | ||
| python -m venv tl | ||
| source tl/bin/activate | ||
| pip install -r requirements-test.txt | ||
| pip install . | ||
| export PIP_CONFIG_FILE=/dev/null | ||
| export PYTHONUSERBASE="" | ||
| pip config unset global.user || true | ||
| pip config unset user.user || true | ||
| pip install --no-user -r requirements-test.txt | ||
| pip install --no-user . | ||
|
|
||
| - name: Run performance test | ||
| id: perfbench | ||
| run: | | ||
| source tl/bin/activate | ||
| python maint/scripts/ci_performance.py | ||
| - name: Read markdown table | ||
| id: read_md | ||
| run: | | ||
| echo "content<<EOF" >> $GITHUB_OUTPUT | ||
| cat bench.md >> $GITHUB_OUTPUT | ||
| echo "EOF" >> $GITHUB_OUTPUT | ||
| - name: Upload PNG to GitHub and get URL | ||
| id: upload_png | ||
| uses: actions/github-script@v8 | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| script: | | ||
| const fs = require('fs'); | ||
| const content = fs.readFileSync('bench.png').toString('base64'); | ||
| // Create blob in the repo | ||
| const blob = await github.rest.git.createBlob({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| content: content, | ||
| encoding: "base64", | ||
| }); | ||
| // Attach blob as a tree item | ||
| const tree = await github.rest.git.createTree({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| tree: [{ | ||
| path: `bench_${context.runId}.png`, | ||
| mode: '100644', | ||
| type: 'blob', | ||
| sha: blob.data.sha | ||
| }] | ||
| }); | ||
| // Raw file URL (works for embedding image) | ||
| const url = `https://raw.githubusercontent.com/${context.repo.owner}/${context.repo.repo}/${tree.data.sha}/bench_${context.runId}.png` | ||
| core.setOutput("url", url); | ||
|
Comment on lines
+89
to
+117
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. Fix the PNG upload and URL generation approach. The current implementation creates an orphaned blob and tree without a commit, then attempts to use the tree SHA in a raw GitHub URL. This will not work because:
Consider these alternatives: Option 1: Upload as workflow artifact and use GitHub API to get URL - name: Upload benchmark chart
uses: actions/upload-artifact@v4
with:
name: benchmark-chart
path: bench.pngThen link to the artifact in the comment (artifacts are accessible via GitHub UI but not embeddable). Option 2: Commit and push the image to a dedicated branch - name: Upload PNG to GitHub
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout --orphan benchmark-images
git rm -rf .
cp bench.png benchmark-${{ github.run_id }}.png
git add benchmark-${{ github.run_id }}.png
git commit -m "Add benchmark image for run ${{ github.run_id }}"
git push origin benchmark-images --forceThen use: Option 3: Use external image hosting service or GitHub issue attachments API 🤖 Prompt for AI Agents |
||
|
|
||
| - name: Post test results as PR comment | ||
| uses: actions/github-script@v8 | ||
| with: | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
| script: | | ||
| const md = `${{ steps.read_md.outputs.content }}`; | ||
| const img = `${{ steps.upload_png.outputs.url }}`; | ||
| github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: '📊 **Performance Test Results** (triggered by @' + context.payload.comment.user.login + '):\n\n' + | ||
| 'Run listed here: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\n' + | ||
| "${{ steps.perfbench.outputs.stdout }}" | ||
| body: | ||
| '📊 **Performance Test Results** (triggered by @' + | ||
| context.payload.comment.user.login + ')\n\n' + | ||
| 'Run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\n' + | ||
| md + | ||
| '\n\n📈 **Speedup Plot:**\n\n' + | ||
| `` | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import tilelang.tools.bench | ||
| import example_conv_analyze | ||
| import example_gemm_analyze | ||
|
|
||
|
|
||
| def bench_example_gemm_analyze(): | ||
| tilelang.tools.bench.process_func(example_gemm_analyze.main) | ||
|
|
||
|
|
||
| def bench_example_conv_analyze(): | ||
| tilelang.tools.bench.process_func(example_conv_analyze.main) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import tilelang.tools.bench | ||
| import example_gqa_sink_bwd_bhsd | ||
| import example_gqa_sink_fwd_bhsd_wgmma_pipelined | ||
| import example_mha_sink_bwd_bhsd | ||
| import example_mha_sink_fwd_bhsd | ||
| import example_mha_sink_fwd_bhsd_wgmma_pipelined | ||
|
|
||
|
|
||
| def bench_example_mha_sink_fwd_bhsd(): | ||
| tilelang.tools.bench.process_func(example_mha_sink_fwd_bhsd.main) | ||
|
|
||
|
|
||
| def bench_example_mha_sink_fwd_bhsd_sliding_window(): | ||
| tilelang.tools.bench.process_func(example_mha_sink_fwd_bhsd.main, window_size=128) | ||
|
|
||
|
|
||
| def bench_example_mha_sink_fwd_bhsd_wgmma_pipelined(): | ||
| tilelang.tools.bench.process_func(example_mha_sink_fwd_bhsd_wgmma_pipelined.main) | ||
|
|
||
|
|
||
| def bench_example_mha_sink_fwd_bhsd_wgmma_pipelined_sliding_window(): | ||
| tilelang.tools.bench.process_func( | ||
| example_mha_sink_fwd_bhsd_wgmma_pipelined.main, window_size=128) | ||
|
|
||
|
|
||
| def bench_example_gqa_sink_fwd_bhsd_wgmma_pipelined(): | ||
| tilelang.tools.bench.process_func(example_gqa_sink_fwd_bhsd_wgmma_pipelined.main) | ||
|
|
||
|
|
||
| def bench_example_gqa_sink_fwd_bhsd_wgmma_pipelined_sliding_window(): | ||
| tilelang.tools.bench.process_func( | ||
| example_gqa_sink_fwd_bhsd_wgmma_pipelined.main, window_size=128) | ||
|
|
||
|
|
||
| def bench_example_mha_sink_bwd_bhsd(): | ||
| tilelang.tools.bench.process_func(example_mha_sink_bwd_bhsd.main) | ||
|
|
||
|
|
||
| def bench_example_mha_sink_bwd_bhsd_sliding_window(): | ||
| tilelang.tools.bench.process_func(example_mha_sink_bwd_bhsd.main, window_size=128) | ||
|
|
||
|
|
||
| def bench_example_gqa_sink_bwd_bhsd(): | ||
| tilelang.tools.bench.process_func(example_gqa_sink_bwd_bhsd.main) | ||
|
|
||
|
|
||
| def bench_example_gqa_sink_bwd_bhsd_sliding_window(): | ||
| tilelang.tools.bench.process_func(example_gqa_sink_bwd_bhsd.main, window_size=128) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import tilelang.tools.bench | ||
| import block_sparse_attn_triton | ||
| import example_tilelang_block_sparse_attn | ||
| import example_tilelang_sparse_gqa_decode_varlen_indice | ||
| import example_tilelang_sparse_gqa_decode_varlen_mask | ||
| import example_triton_sparse_gqa_decode_varlen_indice | ||
| import example_triton_sparse_gqa_decode_varlen_mask | ||
|
|
||
|
|
||
| def bench_block_sparse_attn_triton(): | ||
| tilelang.tools.bench.process_func(block_sparse_attn_triton.main) | ||
|
|
||
|
|
||
| def bench_example_tilelang_block_sparse_attn(): | ||
| tilelang.tools.bench.process_func(example_tilelang_block_sparse_attn.main) | ||
|
|
||
|
|
||
| def bench_example_tilelang_sparse_gqa_decode_varlen_indice(): | ||
| tilelang.tools.bench.process_func( | ||
| example_tilelang_sparse_gqa_decode_varlen_indice.main, batch=1, max_cache_seqlen=2048) | ||
|
|
||
|
|
||
| def bench_example_tilelang_sparse_gqa_decode_varlen_mask(): | ||
| tilelang.tools.bench.process_func( | ||
| example_tilelang_sparse_gqa_decode_varlen_mask.main, batch=1, max_cache_seqlen=2048) | ||
|
|
||
|
|
||
| def bench_example_triton_sparse_gqa_decode_varlen_indice(): | ||
| tilelang.tools.bench.process_func( | ||
| example_triton_sparse_gqa_decode_varlen_indice.main, | ||
| batch=8, | ||
| heads=8, | ||
| heads_kv=4, | ||
| max_cache_seqlen=2048, | ||
| dim=128, | ||
| dim_v=128, | ||
| sparse_ratio=0.8, | ||
| block_size=32) | ||
|
|
||
|
|
||
| def bench_example_triton_sparse_gqa_decode_varlen_mask(): | ||
| tilelang.tools.bench.process_func( | ||
| example_triton_sparse_gqa_decode_varlen_mask.main, | ||
| batch=8, | ||
| heads=8, | ||
| heads_kv=4, | ||
| max_cache_seqlen=2048, | ||
| dim=128, | ||
| dim_v=128, | ||
| sparse_ratio=0.8, | ||
| block_size=32) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import tilelang.tools.bench | ||
| import example_blocksparse_gemm | ||
|
|
||
|
|
||
| def bench_example_blocksparse_gemm(): | ||
| tilelang.tools.bench.process_func(example_blocksparse_gemm.main) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,21 @@ | ||||||||||
| import tilelang.tools.bench | ||||||||||
| import example_group_per_split_token_cast_to_fp8 | ||||||||||
| import example_per_token_cast_to_fp8 | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def bench_example_group_per_split_token_cast_to_fp8(): | ||||||||||
| tilelang.tools.bench.process_func( | ||||||||||
| example_group_per_split_token_cast_to_fp8.main, | ||||||||||
| M=1024, | ||||||||||
| N=1024, | ||||||||||
| BG=2, | ||||||||||
| blk_m=4, | ||||||||||
| batch_sizes=[128, 896]) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def bench_example_per_token_cast_to_fp8(): | ||||||||||
| tilelang.tools.bench.process_func(example_per_token_cast_to_fp8.main, M=2048, N=512, blk_m=8) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| if globals().get("__name__") == "__main__": | ||||||||||
| tilelang.tools.bench.main() | ||||||||||
|
Comment on lines
+20
to
+21
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 Use idiomatic Python for main guard. The expression Apply this change: -if globals().get("__name__") == "__main__":
+if __name__ == "__main__":
tilelang.tools.bench.main()Note: This same pattern appears in all benchmark wrapper files in this PR and should be fixed consistently across:
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import tilelang.tools.bench | ||
| import example_convolution | ||
| import example_convolution_autotune | ||
|
|
||
|
|
||
| def bench_example_convolution(): | ||
| tilelang.tools.bench.process_func(example_convolution.main) | ||
|
|
||
|
|
||
| def bench_example_convolution_autotune(): | ||
| tilelang.tools.bench.process_func(example_convolution_autotune.main) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import tilelang.tools.bench | ||
| import example_deepgemm_fp8_2xAcc | ||
|
|
||
|
|
||
| def bench_example_deepgemm_fp8_2xAcc(): | ||
| tilelang.tools.bench.process_func(example_deepgemm_fp8_2xAcc.main) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import tilelang.tools.bench | ||
| import example_mla_decode | ||
|
|
||
|
|
||
| def bench_example_mla_decode(): | ||
| tilelang.tools.bench.process_func(example_mla_decode.main) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import tilelang.tools.bench | ||
| import example_tilelang_nsa_fwd | ||
| import example_tilelang_nsa_decode | ||
|
|
||
|
|
||
| def bench_example_tilelang_nsa_fwd(): | ||
| tilelang.tools.bench.process_func(example_tilelang_nsa_fwd.main) | ||
|
|
||
|
|
||
| def bench_example_tilelang_nsa_fwd_decode(): | ||
| tilelang.tools.bench.process_func(example_tilelang_nsa_decode.main) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import tilelang.tools.bench | ||
| import fp8_lighting_indexer | ||
| import sparse_mla_bwd | ||
| import sparse_mla_fwd | ||
| import sparse_mla_fwd_pipelined | ||
| import topk_selector | ||
|
|
||
|
|
||
| def bench_topk_selector(): | ||
| tilelang.tools.bench.process_func(topk_selector.test_topk_selector) | ||
|
|
||
|
|
||
| def bench_fp8_lighting_indexer(): | ||
| tilelang.tools.bench.process_func( | ||
| fp8_lighting_indexer.test_fp8_lighting_indexer, | ||
| S=512, | ||
| SKV=1024, | ||
| H=32, | ||
| HKV=1, | ||
| D=64, | ||
| kv_stride=1) | ||
|
|
||
|
|
||
| def bench_sparse_mla_fwd(): | ||
| tilelang.tools.bench.process_func( | ||
| sparse_mla_fwd.test_sparse_mla_fwd, | ||
| S=256, | ||
| SKV=1024, | ||
| H=64, | ||
| HKV=1, | ||
| DQK=576, | ||
| DV=512, | ||
| topk=256, | ||
| check_correctness=False) | ||
|
|
||
|
|
||
| def bench_sparse_mla_fwd_pipelined(): | ||
| tilelang.tools.bench.process_func( | ||
| sparse_mla_fwd_pipelined.test_sparse_mla_fwd_pipelined, | ||
| S=256, | ||
| SKV=512, | ||
| H=64, | ||
| HKV=1, | ||
| DQK=576, | ||
| DV=512, | ||
| topk=256, | ||
| check_correctness=False) | ||
|
|
||
|
|
||
| def bench_sparse_mla_bwd(): | ||
| tilelang.tools.bench.process_func( | ||
| sparse_mla_bwd.test_sparse_mla_bwd, | ||
| S=256, | ||
| SKV=512, | ||
| H=64, | ||
| HKV=1, | ||
| DQKV=576, | ||
| DV=512, | ||
| topk=256, | ||
| check_correctness=False) | ||
|
|
||
|
|
||
| if globals().get("__name__") == "__main__": | ||
| tilelang.tools.bench.main() |
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.
🧩 Analysis chain
Add
actions: writepermission for cache operations.The current permissions block only specifies
contents: write. When a permissions block is defined, any unlisted permission (includingactions) defaults tonone. This prevents setup-python's cache from being saved—it will only restore from cache but never write new entries.Apply this diff:
permissions: contents: write + actions: writeBased on learnings: In GitHub Actions workflows with a permissions block, setup-python's cache feature requires
actions: writeto save caches; without it, caches restore but never persist.I'll verify this review comment by checking the codebase and GitHub Actions documentation.
🏁 Script executed:
Length of output: 4705
🌐 Web query:
💡 Result:
Short answer: actions/cache (used by setup-python and many wrapper actions) needs the workflow's GITHUB_TOKEN to have actions: write when it needs to create/delete caches. Add permissions to the workflow or provide a PAT (for fork PRs where GITHUB_TOKEN is limited).
What to do
permissions:
actions: write
contents: read
permissions:
actions: write
contents: read
Notes
Sources
Add
actions: writepermission for cache operations.The workflow uses
actions/setup-python@v6withcache: pipenabled. When a permissions block is defined in GitHub Actions, the actions/cache (used by setup-python) requiresactions: writepermission to save caches. Without this permission, caches will restore but not persist across runs, degrading workflow performance.Apply this diff:
permissions: contents: write + actions: write🤖 Prompt for AI Agents