├── .github └── workflows │ ├── check-web-deploy.yml │ ├── lint-md.yaml │ └── merge-to-docs-branch.yml ├── .gitignore ├── .lintmdrc ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── docs ├── 00_prev_concept │ ├── README.md │ └── images │ │ ├── A100.png │ │ ├── A100_SM.jpg │ │ ├── A100_params.png │ │ ├── GPU_mem_arch.jpg │ │ ├── SM_mem_detail.jpg │ │ ├── bank_conflict.jpg │ │ ├── bank_example_0.png │ │ ├── block_diff_sm.png │ │ ├── builtin_var.png │ │ ├── cgpu_diff.png │ │ ├── channel.png │ │ ├── cuda_core.png │ │ ├── cuda_mem.jpg │ │ ├── cuda_mem_func.png │ │ ├── dma_d2h.png │ │ ├── exec_model_SM.jpg │ │ ├── fess.png │ │ ├── fix_diverage.png │ │ ├── fsmss_overview.png │ │ ├── gpu_mem_feature.png │ │ ├── gpu_model.png │ │ ├── io_case.png │ │ ├── kernel_func.png │ │ ├── matmul.png │ │ ├── mem_bank.jpg │ │ ├── memory_arch.jpg │ │ ├── roof_line.png │ │ ├── roof_line_sq.png │ │ ├── scheduler.png │ │ ├── sm_detail.png │ │ ├── thread_grid.jpg │ │ ├── thread_mem.png │ │ ├── vmss.png │ │ ├── warp_diverage.png │ │ ├── warp_diverage2.png │ │ ├── warp_on_sm.png │ │ └── warp_view.jpg ├── 01_build_dev_env │ ├── README.md │ └── hello_world.cu ├── 02_first_kernel │ ├── README.md │ └── vector_add.cu ├── 03_nvprof_usage │ └── README.md ├── 04_first_refine_kernel │ ├── README.md │ ├── img │ │ ├── block_grid.png │ │ ├── parallel_block.png │ │ └── parallel_thread.png │ ├── vector_add_grid.cu │ └── vector_add_thread.cu ├── 05_intro_parallel │ └── README.md ├── 06_impl_matmul │ ├── README.md │ ├── images │ │ ├── 0b35adb64a964e56018dc9fb7277269a3efa72b1526058609e0860f33e00426b.png │ │ └── 6f55c7f9531e5efd955eab9a572ef5406733498bc0b50abed0e73985d88c840b.png │ └── matmul_raw.cu ├── 07_optimize_matmul │ ├── README.md │ ├── images │ │ ├── 029d6d7f597e03b81754dff8748ec5decdfaa49c55d2613be43772371659deed.png │ │ ├── 1a8df2b3afe650e592a3d3943d9d59088b5d1e531dcc9a01ec8987a6d3c739ba.png │ │ ├── 264915564b04781951d36d7d8527b418bbe0fea3a3969563a639f6575c1febd5.png │ │ ├── 3b9ca1d09a35e62b14f73b56e21b988d379bf0b38b8af6d4d9b17d9f46663c1c.png │ │ ├── 3bdc0715688817d9d6c4b6d3047861e76142c27a8b7cc59fca4020f951223baa.png │ │ └── b99194dc785674eb6347c91f3b30e150d29fc238e2c63332641d9c55a205fd8f.png │ ├── matmul_shared.cu │ └── matmul_tiled.cu ├── 08_impl_reduce │ ├── README.md │ ├── images │ │ ├── 1-CUDA层次结构.drawio │ │ ├── 1-CUDA层次结构.png │ │ ├── 2-计算原理图.drawio │ │ └── 2-计算原理图.png │ └── reduce_naive.cu ├── 09_optimize_reduce │ ├── 01_interleaved_addressing │ │ ├── README.md │ │ ├── images │ │ │ ├── 7257e14ad7e2c51a74b42f0f31a4bffa034546852182c7122fe45672cf4a576e.png │ │ │ ├── ae8f73237006381f3a0785df49aa6ccfa553513dbe1037c6a44a49e4d19b7b1d.png │ │ │ └── d6f37bda18827d5a253b26ac3ec49e2755e0249d7c52684e1713196706f0f625.png │ │ └── reduce_interleaved_addressing.cu │ ├── 02_bank_conflict │ │ ├── README.md │ │ ├── images │ │ │ ├── 0f65c7d9e911014e31ddd84c583dea859ba24ebd48715c2680eb604e7ebb9a2b.png │ │ │ ├── 7c9ce0996f0a32f29890e52e42291fdd2993502630aa5632c298598604144630.png │ │ │ ├── e69b477993846936b270e82a37615c00424010cd8003f429354aa27325c96f57.png │ │ │ └── ef322be7c3e5b6b9be69d2b90e88083f50569a58a97129f348e483b946ab4edf.png │ │ └── reduce_bank_conflict_free.cu │ ├── 03_idle_threads_free │ │ ├── README.md │ │ ├── images │ │ │ ├── 1-idle优化原理.drawio │ │ │ └── 1-idle优化原理.jpg │ │ └── reduce_idle_threads_free.cu │ ├── 04_unroll │ │ ├── README.md │ │ ├── reduce_unroll_all.cu │ │ └── reduce_unroll_last_warp.cu │ └── README.md ├── 10_what_my_id │ ├── README.md │ ├── my_id.cu │ └── my_id_dim2.cu ├── 11_gemm_optimize │ ├── 01_tiled2d │ │ ├── README.md │ │ ├── images │ │ │ ├── 2bd4653c7a81dc2a1a48f359a49bba0de7a7560fb8834470225c3fc55ec23221.png │ │ │ ├── 5a5157b46fb6c076cf1bf918b4ebe4c8bb7d70ecab97a2b77686c93fa4c89dde.png │ │ │ ├── 9047246849f79b5117961c15e1a3a340a44ab003566140ecc00600058c70a9e2.png │ │ │ └── f507ad687528e8bbb14a85c1fa3016cce50be55b5670ebc425c549cc5c5bd5a6.png │ │ └── sgemm_tiled2d.cu │ ├── 02_vectorize_smem_and_gmem_accesses │ │ ├── README.md │ │ ├── images │ │ │ ├── 05eee538f6394ffc2ffffc2947edc8c888175af7152a150d697bfefb47db7a98.jpg │ │ │ ├── load_to_smem.drawio │ │ │ └── load_to_smem.jpg │ │ └── sgemm_vectorize.cu │ ├── 03_warptiling │ │ ├── README.md │ │ ├── images │ │ │ ├── algorithm_loop.png │ │ │ ├── algorithm_pipeline.jpg │ │ │ └── sm.jpg │ │ └── sgemm_warp_tiling.cu │ ├── 04_double_buffer │ │ ├── README.md │ │ ├── images │ │ │ ├── double_buffer.drawio │ │ │ └── double_buffer.jpg │ │ └── sgemm_double_buffering.cu │ └── 05_bank_conflicts │ │ └── README.md ├── 12_convolution │ ├── 01_naive_conv │ │ ├── README.md │ │ ├── images │ │ │ ├── 80eae29b069a39fd446c2551ab8f8cea396190707e2104d6c868a35eb3735154.png │ │ │ └── 9ee5c9b3e312cca4ecd53ef2759dae7d3cba5a083f65c353a6c85935bd7ff256.png │ │ └── naive_conv.cu │ ├── 02_intro_conv_optimize │ │ ├── README.md │ │ └── images │ │ │ ├── im2col.drawio │ │ │ └── im2col.jpg │ ├── 03_im2col_conv │ │ ├── README.md │ │ ├── codes │ │ │ ├── Makefile │ │ │ ├── include │ │ │ │ ├── conv2d.h │ │ │ │ └── verfiy.h │ │ │ ├── job.sh │ │ │ └── src │ │ │ │ └── conv2d.cu │ │ └── images │ │ │ └── im2col.jpg │ ├── 04_implicit_gemm │ │ └── README.md │ └── 05_cutlass_conv │ │ └── README.md ├── 13_continuous_batch │ ├── README.md │ ├── images │ │ ├── 1debc7d6ea69981ae98d6182751466a1fe04b94227df015d3be0eee931905e50.png │ │ ├── 32fa966e74825b9b5a8cbbcd850017df74bd36e1a91dff6d27dff6c30f715074.png │ │ ├── 50b1c9d1d034c2965208d03231ec7a58ddf3facfb5c7a03a21d501809071495e.png │ │ ├── 5362c0f84bb0cfbf283aa965121077c5dda3517afad7b2bc01c7ba4c7dc683bf.png │ │ ├── 8ca864ed5e881bf264fb34c2f1f114ce66bd39f4fa85103ed43ca6c80687d188.png │ │ ├── c2f7c8c4c1afd0f4cb36b96498b5da9014dcc5af838402bdb95c230fa108a2e5.gif │ │ └── ccc8652e29ed6f5a96df40da1a28811158923081a804aa04613601e067ca9fe2.png │ └── paper │ │ └── osdi22-yu.pdf ├── 14_page_attention │ ├── README.md │ └── images │ │ ├── 126019db0548c4300504b8d3fd4042eb1387dd153236ed4ea0708aba9fb707af.png │ │ ├── 6dc1323d485f6649a19fe6cc7b712746de7a8c677ca09fc9b65d504ff5b18c00.png │ │ ├── 84157a2a4c8202fb88d7e2050d99e2e0c7d93f8d7b674bc760cb7b493af3c975.png │ │ └── fc42f820938dcdfed1836bef91dd3b4709bc531061a853181b94ef81d3e6f847.png ├── 15_vllm_page_attention │ ├── README.md │ └── images │ │ ├── 0e01659f51fd207c5aa621724b1d7c378151289e522c7f470318633a65afcfef.png │ │ ├── 14ff0f0fac048a2447fba166819025f25c4de8b01cf5f9ff98af52c7b8e8b057.png │ │ ├── 1dd8fe7fe7ac36f466ba879ae5fd53569dcd9586895fad6b194839d0c473522b.png │ │ ├── 5b998a6b401dcc4eb415aac1312429f9098d0c51b24babd4e58ad666a5062a45.png │ │ ├── 8ee63da47a04bb85bd8592a43216ecaa09cad05c017e10dbb9b2591d6331d536.png │ │ ├── 91c34c41368261301f36dccc18036bc59e10aa8140dccebf37bc8abe0d38e0e5.png │ │ └── 93f99de459d4457456aea89a2cf47851cb8697b0dc521c4b5cc1fefff5ad180b.png ├── 16_vllm_source_code │ ├── 01_vllm_arch.md │ ├── 02_preprocess_before_scheduler.md │ ├── 03_scheduler.md │ ├── 04_block_manager_part1.md │ ├── 05_block_manager_part2.md │ ├── drawio │ │ └── pic_draw.drawio │ └── images │ │ ├── 0318150945bc58f42bede6bb9325c06995933ba46d7d1545006ae75aadd7a996.png │ │ ├── 076ed826afbadd2bc485676224d5dc7c59871decc9c0b4d9e9aff84560894500.png │ │ ├── 1404d1a5b8f78da8ca1c9bc0abd322ccf6e46ab98468afd76b728747f528f055.png │ │ ├── 171265232857bb86e4f7b5ee3ab64f6c50a135922d773f80eb59a4375dd3152e.png │ │ ├── 21be763ab15d90c836f0312795c9c37aeb1ffa62a12e7babf3500f137ad1f6c0.png │ │ ├── 2ed7e89c1704106b1296eeaaf6ab68aca8cb9984b8aaa325337110b6f8e8aaa6.png │ │ ├── 420c9840542a4e04923ac94d217bcb369b05d504322065c08dedaa96c2a40d5b.png │ │ ├── 78f8a022abcbe9e15820c974cecf1e82599c121a8f3317c0e34d9b6ecff01be1.png │ │ ├── 87bb8d271124b0c533b60b8c3cf85480766e9697e024490e76f3b1ea9af06a6c.png │ │ ├── 91ade117f58928c01f48be56534b7807171d278e9524152a081472c5dc920d4f.png │ │ ├── 9c5cb31b4b105cc3464579c61568cfe2254914d65e87ee45f896d9de7d5a47ae.png │ │ ├── 9dc9cdf7e17cf610cb67772ff00014e715f61b76c8ac42942bfebdeea9658bc4.png │ │ ├── cd1eee13eda9b1f58ecf257a8bd840d6e9152f29fc5e98098eefc740716ca412.png │ │ └── ffb030f398e6377ae5306cfb0fd1bb170ea74c545e9a7076719d01f4f170128c.png ├── 17_flash_attn │ ├── 01_flash_attn_v1_part1.md │ ├── 02_flash_attn_v1_part2.md │ ├── drawio │ │ └── draw.drawio │ ├── flash_attn_v1.cu │ └── images │ │ ├── 5dfce77ba3c57779bce60c8ebc552aa40304c6c5f36bb2d207d6b102a4d8026e.png │ │ ├── 865f289b12429f1cf8de42cf2b6b019ecaef55ab09cb04c590ca66ac9b9f9ce7.png │ │ ├── 9626ab9b79ea64fb08e8f204c67d0e588f6ef384ab788cad38030846a21314c5.png │ │ ├── 9bcbef4156d32c8ff9836bafd4132191a1c62eca89b4316c8f78b7c98e22f84b.png │ │ └── c397679ab7ad09d15680161bc1e2344c62a97161daf94c08cd9a1766036d05f2.png ├── img │ ├── kernel-as-function.png │ ├── kernel-execution-on-gpu.png │ └── memory-hierarchy-in-gpus.png └── index.md ├── img ├── kernel-as-function.png ├── kernel-execution-on-gpu.png └── memory-hierarchy-in-gpus.png └── sidebars.js /.github/workflows/check-web-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Check the deployment of the web 2 | permissions: write-all 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - develop 8 | 9 | jobs: 10 | merge-to-docs: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Merge to Docs Branch 20 | run: | 21 | git config user.name "andsonder" 22 | git config user.email "changlu@keter.top" 23 | git config pull.rebase false 24 | git remote set-url origin "https://github-actions:${{ secrets.SONDER_TOKEN }}@github.com/PaddleJitLab/CUDATutorial.git" 25 | git remote -v 26 | git fetch --all 27 | git pull origin develop --allow-unrelated-histories --no-edit 28 | git checkout -b docs origin/docs 29 | git pull origin docs --allow-unrelated-histories --no-edit 30 | git pull origin develop --allow-unrelated-histories --no-edit 31 | git merge --no-ff ${{ github.event.pull_request.head.sha }} -m "Merge latest commit from PR" 32 | 33 | - name: Set up Node.js 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 21 37 | 38 | - name: Install dependencies 39 | run: npm install 40 | 41 | - name: Build 42 | run: npm run build 43 | -------------------------------------------------------------------------------- /.github/workflows/lint-md.yaml: -------------------------------------------------------------------------------- 1 | name: Lint Markdown Files 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - develop 7 | push: 8 | branches: 9 | - develop 10 | 11 | jobs: 12 | lint-md: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | # Step 1: Checkout the repository 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | # Step 2: Set up Node.js environment 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: '21' 25 | 26 | # Step 3: Install lint-md-cli 27 | - name: Install lint-md-cli 28 | run: npm install -g lint-md-cli 29 | 30 | # Step 4: Run lint-md using npx 31 | - name: Run lint-md check 32 | run: npx lint-md-cli . 33 | -------------------------------------------------------------------------------- /.github/workflows/merge-to-docs-branch.yml: -------------------------------------------------------------------------------- 1 | name: Merge to Docs Branch 2 | 3 | permissions: write-all 4 | 5 | on: 6 | push: 7 | branches: 8 | - develop 9 | 10 | jobs: 11 | merge-to-docs: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v2 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Merge to Docs Branch 21 | run: | 22 | git config user.name "andsonder" 23 | git config user.email "changlu@keter.top" 24 | git config pull.rebase false 25 | git remote set-url origin "https://github-actions:${{ secrets.SONDER_TOKEN }}@github.com/PaddleJitLab/CUDATutorial.git" 26 | git remote -v 27 | git fetch --all 28 | git pull origin develop --allow-unrelated-histories --no-edit 29 | git checkout -b docs origin/docs 30 | git pull origin docs --allow-unrelated-histories --no-edit 31 | git pull origin develop --allow-unrelated-histories --no-edit 32 | git push origin docs 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | .DS_Store 34 | -------------------------------------------------------------------------------- /.lintmdrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules":{ 3 | "no-long-code": [1, { 4 | "length": 1000, 5 | "exclude": ["dot"] 6 | }], 7 | "no-space-in-emphasis": [0, {}] 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: lint-md 5 | name: lint-md 6 | entry: lint-md -f 7 | language: node 8 | files: \.md$ 9 | types: [text] 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CUDATutorial 2 | ![](https://img.shields.io/badge/version-v0.1-brightgreen) ![](https://img.shields.io/badge/docs-latest-brightgreen) ![](https://img.shields.io/badge/PRs-welcome-orange) ![](https://img.shields.io/badge/pre--commit-Yes-brightgreen) 3 | 4 | 从零开始学习 CUDA 高性能编程,从入门到放弃,哦不!一起来边学习,边打笔记,日拱一卒! 5 | 6 | > [!NOTE] 7 | > 你可以访问 https://cuda.keter.top/ 来访问本仓库的网页版 8 | 9 |

10 | 11 |

12 | 13 | 14 | ## 学习路线 15 | 16 | ### 新手村系列 🐸 17 | 18 | + [构建 CUDA 编程环境](./docs/01_build_dev_env/) 19 | + [手写第一个 Kernel](./docs/02_first_kernel/) 20 | + [nvprof 性能分析](./docs/03_nvprof_usage/) 21 | + [尝试第一次优化 Kernel](./docs/04_first_refine_kernel/) 22 | + [了解 CUDA 线程分布](./docs/10_what_my_id/) 23 | + [CUDA 编程模型](./docs/00_prev_concept/) 24 | 25 | ### 初阶系列 ⚔ 26 | 27 | + [初识多线程并行计算](./docs/05_intro_parallel/) 28 | + [手写实现矩阵乘 Matmul](./docs/06_impl_matmul/) 29 | + [矩阵乘 Matmul 性能优化实践](./docs/07_optimize_matmul/) 30 | 31 | ### 中阶系列 🚀 32 | 33 | + [手写实现 Reduce](./docs/08_impl_reduce/) 34 | + [Reduce 性能优化实践—交叉寻址](./docs/09_optimize_reduce/01_interleaved_addressing/README.md) 35 | + [Reduce 性能优化实践—解决 Bank Conflict](./docs/09_optimize_reduce/02_bank_conflict/README.md) 36 | + [Reduce 性能优化实践—解决空闲线程](./docs/09_optimize_reduce/03_idle_threads_free/README.md) 37 | + [Reduce 性能优化实践—展开最后一个 warp](./docs/09_optimize_reduce/04_unroll/README.md) 38 | + [GEMM 优化专题-二维 Thread Tile 并行优化](./docs/11_gemm_optimize/01_tiled2d/README.md) 39 | + [GEMM 优化专题-向量化访存](./docs/11_gemm_optimize/02_vectorize_smem_and_gmem_accesses/README.md) 40 | + [GEMM 优化专题-warp tiling](./docs/11_gemm_optimize/03_warptiling/README.md) 41 | + [GEMM 优化专题-双缓冲](./docs/11_gemm_optimize/04_double_buffer/README.md) 42 | + [GEMM 优化专题-解决 Bank Conflict](./docs/11_gemm_optimize/05_bank_conflicts/README.md) 43 | + [卷积算子优化专题-卷积算子简易实现](./docs/12_convolution/01_naive_conv/README.md) 44 | + [卷积算子优化专题-卷积算子优化思路介绍](./docs/12_convolution/02_intro_conv_optimize/README.md) 45 | + [卷积算子优化专题-im2col + gemm 实现卷积](./docs/12_convolution/03_im2col_conv/README.md) 46 | + [卷积算子优化专题-隐式 GEMM 实现卷积](./docs/12_convolution/04_implicit_gemm/README.md) 47 | + [卷积算子优化专题-CUTLASS 中的卷积优化策略](./docs/12_convolution/05_cutlass_conv/README.md) 48 | 49 | 50 | ### 高阶系列 ✈️ 51 | 52 | + 页锁定和主机内存 53 | + CUDA 流和多流使用 54 | + 使用多个 GPU 计算 55 | + ...(补充中) 56 | 57 | ### 大师系列 💡 58 | 59 | 我现在还不知道写啥,毕竟我现在还是菜鸡~~ 60 | 61 | ### LLM 推理技术 🤖 62 | 63 | + [Flash Attention v1 - 原理篇](./docs/17_flash_attn/01_flash_attn_v1_part1.md) 64 | + [Flash Attention v1 - 实现篇](./docs/17_flash_attn/02_flash_attn_v1_part2.md) 65 | + [连续批处理](./docs/13_continuous_batch/README.md) 66 | + [Page Attention - 原理篇](./docs/14_page_attention/README.md) 67 | + [Page Attention - 源码解析](./docs/15_vllm_page_attention/README.md) 68 | + [vLLM 源码解读系列 - vLLM 代码架构介绍](./docs/16_vllm_source_code/01_vllm_arch.md) 69 | + [vLLM 源码解读系列 - 调度前的预处理工作](./docs/16_vllm_source_code/02_preprocess_before_scheduler.md) 70 | + [vLLM 源码解读系列 - 调度器策略](./docs/16_vllm_source_code/03_scheduler.md) 71 | + [vLLM 源码解读系列 - vLLM BlockManager - NaiveBlockAllocator](./docs/16_vllm_source_code/04_block_manager_part1.md) 72 | + [vLLM 源码解读系列 - vLLM BlockManager - PrefixCachingBlockAllocator](./docs/16_vllm_source_code/05_block_manager_part2.md) 73 | 74 | [![Star History Chart](https://api.star-history.com/svg?repos=PaddleJitLab/CUDATutorial&type=Date)](https://star-history.com/#PaddleJitLab/CUDATutorial&Date) -------------------------------------------------------------------------------- /docs/00_prev_concept/images/A100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/A100.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/A100_SM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/A100_SM.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/A100_params.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/A100_params.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/GPU_mem_arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/GPU_mem_arch.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/SM_mem_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/SM_mem_detail.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/bank_conflict.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/bank_conflict.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/bank_example_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/bank_example_0.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/block_diff_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/block_diff_sm.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/builtin_var.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/builtin_var.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/cgpu_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/cgpu_diff.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/channel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/channel.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/cuda_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/cuda_core.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/cuda_mem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/cuda_mem.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/cuda_mem_func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/cuda_mem_func.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/dma_d2h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/dma_d2h.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/exec_model_SM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/exec_model_SM.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/fess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/fess.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/fix_diverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/fix_diverage.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/fsmss_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/fsmss_overview.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/gpu_mem_feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/gpu_mem_feature.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/gpu_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/gpu_model.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/io_case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/io_case.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/kernel_func.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/kernel_func.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/matmul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/matmul.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/mem_bank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/mem_bank.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/memory_arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/memory_arch.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/roof_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/roof_line.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/roof_line_sq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/roof_line_sq.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/scheduler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/scheduler.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/sm_detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/sm_detail.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/thread_grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/thread_grid.jpg -------------------------------------------------------------------------------- /docs/00_prev_concept/images/thread_mem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/thread_mem.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/vmss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/vmss.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/warp_diverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/warp_diverage.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/warp_diverage2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/warp_diverage2.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/warp_on_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/warp_on_sm.png -------------------------------------------------------------------------------- /docs/00_prev_concept/images/warp_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/00_prev_concept/images/warp_view.jpg -------------------------------------------------------------------------------- /docs/01_build_dev_env/README.md: -------------------------------------------------------------------------------- 1 | # 构建 CUDA 编程环境 2 | 3 | ## 1. 前言 4 | 5 | 进行 CUDA 开发时,首先需要一台带有 GPU 显卡的机器(废话~~),笔记本、台式机、服务器都可以。此仓库以 Linux 系统为基础环境,Windows 环境的配置下文会提供一些教程(我没有 windows,穷~~)。 6 | 7 | 8 | ## 2. Linux 环境搭建 9 | 10 | ### 2.1 查看 GPU 信息 11 | 12 | 在装有 GPU 显卡的 Linux 系统上,一般自带了 `nvidia-smi` 命令,可以查看显卡驱动版本号、型号等信息,如下是我开发机的输出信息: 13 | 14 | + CUDA 版本: 11.2 15 | + 驱动版本: 460.32.03 16 | + GPU 型号:Tesla 架构 V100 17 | + 显卡容量:16G * 8 卡 18 | 19 | 20 | ```bash 21 | +-----------------------------------------------------------------------------+ 22 | | NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 | 23 | |-------------------------------+----------------------+----------------------+ 24 | | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | 25 | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | 26 | | | | MIG M. | 27 | |===============================+======================+======================| 28 | | 0 Tesla V100-SXM2... On | 00000000:3F:00.0 Off | 0 | 29 | | N/A 34C P0 57W / 300W | 16128MiB / 16160MiB | 0% Default | 30 | | | | N/A | 31 | +-------------------------------+----------------------+----------------------+ 32 | | 1 Tesla V100-SXM2... On | 00000000:40:00.0 Off | 0 | 33 | | N/A 33C P0 53W / 300W | 764MiB / 16160MiB | 0% Default | 34 | | | | N/A | 35 | +-------------------------------+----------------------+----------------------+ 36 | | 2 Tesla V100-SXM2... On | 00000000:41:00.0 Off | 0 | 37 | | N/A 36C P0 54W / 300W | 9666MiB / 16160MiB | 0% Default | 38 | | | | N/A | 39 | +-------------------------------+----------------------+----------------------+ 40 | | 3 Tesla V100-SXM2... On | 00000000:42:00.0 Off | 0 | 41 | | N/A 37C P0 56W / 300W | 3280MiB / 16160MiB | 0% Default | 42 | | | | N/A | 43 | +-------------------------------+----------------------+----------------------+ 44 | | 4 Tesla V100-SXM2... On | 00000000:62:00.0 Off | 0 | 45 | | N/A 31C P0 40W / 300W | 3MiB / 16160MiB | 0% Default | 46 | | | | N/A | 47 | +-------------------------------+----------------------+----------------------+ 48 | | 5 Tesla V100-SXM2... On | 00000000:63:00.0 Off | 0 | 49 | | N/A 31C P0 39W / 300W | 3MiB / 16160MiB | 0% Default | 50 | | | | N/A | 51 | +-------------------------------+----------------------+----------------------+ 52 | | 6 Tesla V100-SXM2... On | 00000000:64:00.0 Off | 0 | 53 | | N/A 34C P0 41W / 300W | 3MiB / 16160MiB | 0% Default | 54 | | | | N/A | 55 | +-------------------------------+----------------------+----------------------+ 56 | | 7 Tesla V100-SXM2... On | 00000000:65:00.0 Off | 0 | 57 | | N/A 34C P0 41W / 300W | 3MiB / 16160MiB | 0% Default | 58 | | | | N/A | 59 | +-------------------------------+----------------------+----------------------+ 60 | ``` 61 | 62 | 如果你的机器上显卡驱动都没有安装,可以参考 Nvidia 官网根据你显卡的型号,下载和安装对应的驱动:https://www.nvidia.cn/geforce/drivers/ 63 | 64 | ### 2.2 安装 Toolkit 65 | 66 | CUDA Toolkit 是开发 CUDA 程序必备的工具。就像我们写 C++ 一样,你得装 GCC 吧,Toolkit 装完在命令行里输入 `nvcc -V` 就会输出版本信息,比如: 67 | 68 | ```bash 69 | nvcc: NVIDIA (R) Cuda compiler driver 70 | Copyright (c) 2005-2021 NVIDIA Corporation 71 | Built on Thu_Jan_28_19:32:09_PST_2021 72 | Cuda compilation tools, release 11.2, V11.2.142 73 | Build cuda_11.2.r11.2/compiler.29558016_0 74 | ``` 75 | 76 | 如果还不是很清楚 CUDA Toolkit 是什么,可以翻阅 [Nivida 官网的介绍](https://developer.nvidia.com/cuda-toolkit): 77 | ```plain 78 | The NVIDIA® CUDA® Toolkit provides a development environment for creating high performance GPU-accelerated applications. With the CUDA Toolkit, you can develop, optimize, and deploy your applications on GPU-accelerated embedded systems, desktop workstations, enterprise data centers, cloud-based platforms and HPC supercomputers. The toolkit includes GPU-accelerated libraries, debugging and optimization tools, a C/C++ compiler, and a runtime library to deploy your application. 79 | ``` 80 | 81 | 安装时,直接点击 [Nivida 官网](https://developer.nvidia.com/cuda-toolkit) 的 `Download Now` 下载安装即可。安装后可以借助 `nvcc -V` 来确认是否安装成功。 82 | 83 | 84 | ### 2.3 运行 Demo 样例 85 | 86 | 新建一个 `hello_world.cu` 文件(见此目录): 87 | ```cpp 88 | #include 89 | 90 | __global__ void cuda_say_hello(){ 91 | printf("Hello world, CUDA! %d\n", threadIdx.x); 92 | } 93 | 94 | int main(){ 95 | printf("Hello world, CPU\n"); 96 | cuda_say_hello<<<1,1>>>(); 97 | 98 | cudaError_t cudaerr = cudaDeviceSynchronize(); 99 | if (cudaerr != cudaSuccess) 100 | printf("kernel launch failed with error \"%s\".\n", 101 | cudaGetErrorString(cudaerr)); 102 | return 0; 103 | } 104 | ``` 105 | 106 | 首先使用如下命令编译 `nvcc hello_world.cu -o hello_world`, 然后执行 `./hello_world`, 会得到如下输出: 107 | 108 | ```bash 109 | Hello world, CPU 110 | Hello world, CUDA! 0 111 | ``` 112 | 113 | 恭喜你,已经完成的初步环境的搭建了。 114 | 115 | 116 | ### 2.3 Windows 环境配置 117 | 118 | Windows 环境配置同样需要安装 CUDA Toolkit,下载地址为:https://developer.nvidia.com/cuda-downloads。 119 | 120 | 在 Windows 进行安装时需要选自定义模式,采用精简模式安装后无法运行 nvcc 命令。 121 | 122 | 安装成功后可尝试 `nvcc --version` 检测下,编译时如果缺少 cl.exe,则需要安装 Microsoft Visual Studio(使用 C++的桌面开发版本即可)。安装完成后,将 cl.exe 所在路径添加到系统变量中,cl.exe 通常所在文件夹为`C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\{version}\bin\Hostx64\x64`,具体路径根据实际安装情况有所不同。 123 | 124 | 和 Linux 不同之处在于,安装 Toolkit 之后还需要配置下环境变量。默认系统会已经有 `CUDA_PATH` 和 `CUDA_PATH_V11.0`(11.0 应该是版本号),需要自己在额外添加如下环境变量: 125 | 126 | ```bash 127 | CUDA_BIN_PATH: %CUDA_PATH%\bin 128 | CUDA_LIB_PATH: %CUDA_PATH%\lib\x64 129 | CUDA_SDK_PATH: C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.6 #<---- 注意版本号可能不一样 130 | CUDA_SDK_BIN_PATH: %CUDA_SDK_PATH%\bin\win64 131 | CUDA_SDK_LIB_PATH: %CUDA_SDK_PATH%\common\lib\x64 132 | ``` 133 | 134 | 此外,还需要在系统变量 PATH 中添加如下变量: 135 | 136 | ```bash 137 | %CUDA_BIN_PATH% 138 | %CUDA_LIB_PATH% 139 | %CUDA_SDK_BIN_PATH% 140 | %CUDA_SDK_LIB_PATH% 141 | ``` 142 | 143 | 最终,可以运行安装目录下 Nvidia 提供的测试 `.exe` 执行文件:`deviceQuery.exe、bandwidthTest.exe`,如果运行没有问题,则表示环境配置成功了.(在安装路径 `extras/demo_suite`目录里) 144 | 145 | 146 | 147 | ## 附参考文档 148 | 149 | + [Say Hello to CUDA 文档](https://cuda-tutorial.readthedocs.io/en/latest/tutorials/tutorial01/) 150 | + [printf doesn't output anything in CUDA](https://stackoverflow.com/questions/13320321/printf-in-my-cuda-kernel-doesnt-result-produce-any-output) -------------------------------------------------------------------------------- /docs/01_build_dev_env/hello_world.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __global__ void cuda_say_hello(){ 4 | printf("Hello world, CUDA! %d\n", threadIdx.x); 5 | } 6 | 7 | int main(){ 8 | printf("Hello world, CPU\n"); 9 | cuda_say_hello<<<1,1>>>(); 10 | 11 | cudaError_t cudaerr = cudaDeviceSynchronize(); 12 | if (cudaerr != cudaSuccess) 13 | printf("kernel launch failed with error \"%s\".\n", 14 | cudaGetErrorString(cudaerr)); 15 | return 0; 16 | } -------------------------------------------------------------------------------- /docs/02_first_kernel/README.md: -------------------------------------------------------------------------------- 1 | # 手写第一个 Kernel 2 | 3 | ## 1. 需求 4 | 5 | 给定两个 1D 的张量 x 和 y,计算 `out = x + y` 的和,输出新的 1D 张量。 6 | 7 | ## 2. CPU 版本实现 8 | 9 | 从任务上来看,就是简单的 `out[i] = x[i] + y[i]` 外套一个 for 就完事了。 10 | 11 | 我们先简单粗暴地用 C++ 写一个 CPU 版本的: 12 | 13 | ```cpp 14 | #include 15 | 16 | void add_kernel(float *x, float *y, float *out, int n){ 17 | for (int i = 0; i < n; ++i){ 18 | out[i] = x[i] + y[i]; 19 | } 20 | } 21 | 22 | int main(){ 23 | int N = 10000000; 24 | size_t mem_size = sizeof(float) * N; 25 | 26 | float *x, *y, *out; 27 | x = static_cast(malloc(mem_size)); 28 | y = static_cast(malloc(mem_size)); 29 | out = static_cast(malloc(mem_size)); 30 | 31 | for(int i = 0; i < N; ++i){ 32 | x[i] = 1.0; 33 | y[i] = 2.0; 34 | } 35 | 36 | add_kernel(x, y, out, N); 37 | 38 | for(int i = 0; i < 10; ++i){ 39 | printf("out[%d] = %.3f\n", i, out[i]); 40 | } 41 | 42 | free(x); 43 | free(y); 44 | free(out); 45 | } 46 | ``` 47 | 48 | CPU 版本大家都比较熟悉,这里用了 malloc + free 在 heap 上申请主机 host 内存。 49 | 50 | ### 3. CUDA 版本实现 51 | 52 | 接下来让我们一起把它改为可以在 GPU 上跑起来的 CUDA Kernel。 53 | 54 | 首先在 CUDA 世界里,要对数据进行计算,那先得把数据放到 GPU 上,即显存,这叫「入乡随俗」。这个过程涉及了一个「内存搬运」操作,既然是搬运,就要有源数据(即 CPU 上内存),也要有目标数据(即 GPU 上显存),还要有人负责搬运(即设备间拷贝接口),分别对应于: 55 | 56 | + malloc 接口申请 CPU 内存 57 | + cudaMalloc 接口申请 GPU 显存 58 | + cudaMemcpy 接口负责设备间拷贝 59 | 60 | 如下是一个简单的「搬运」过程代码: 61 | 62 | ```cpp 63 | float *x, *cuda_x; 64 | 65 | // Allocate CPU memory 66 | x = static_cast(malloc(mem_size)); 67 | // Allocate CUDA memory 68 | cudaMalloc((void**)&cuda_x, mem_size); 69 | // Copy data from CPU to GPU 70 | cudaMemcpy(cuda_x, x, mem_size, cudaMemcpyHostToDevice); 71 | ``` 72 | 73 | 按照以上方法,我们可以将 CPU 版本代码中的 x,y 数据都先搬运到 GPU 上。 74 | 75 | 之后,我们需要对 `add_kernel` 做下修改,只需在函数定义处加一个 `__global__` 前置修饰符即可: 76 | 77 | ```cpp 78 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 79 | for (int i = 0; i < n; ++i) { 80 | out[i] = x[i] + y[i]; 81 | } 82 | } 83 | ``` 84 | 85 | 此外,在 Host 端启动一个 CUDA kernel 需要特殊的「形式」,即 `<<>>`,其中 M 表示一个 grid 有 M 个 thread blocks,T 表示一个 thread block 有 T 个并行 thread: 86 | ```cpp 87 | add_kernel<<<1, 1>>>(cuda_x, cuda_y, cuda_out, N); 88 | ``` 89 | 90 | 最后一步,如果我们想查看 GPU 上计算出的结果是不是正确的,想 printf 打印出来,我们可以选择用 `cudaMemcpy` 接口把结果从 GPU 上拷贝回 CPU,指定接口最后一个参数的拷贝方向为 `cudaMemcpyDeviceToHost` 即可。 91 | 92 | 手写第一个 CUDA kernel 就完成了,千万别忘了最后用 `cudaFree()` 释放掉显存。完整代码如下: 93 | 94 | ```cpp 95 | #include 96 | 97 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 98 | for (int i = 0; i < n; ++i) { 99 | out[i] = x[i] + y[i]; 100 | } 101 | } 102 | 103 | int main(){ 104 | int N = 10000000; 105 | size_t mem_size = sizeof(float) * N; 106 | 107 | float *x, *y, *out; 108 | float *cuda_x, *cuda_y, *cuda_out; 109 | 110 | // Allocate host CPU memory for x, y 111 | x = static_cast(malloc(mem_size)); 112 | y = static_cast(malloc(mem_size)); 113 | 114 | // Initialize x = 1, y = 2 115 | for(int i = 0; i < N; ++i){ 116 | x[i] = 1.0; 117 | y[i] = 2.0; 118 | } 119 | 120 | // Allocate Device CUDA memory for cuda_x and cuda_y, copy them. 121 | cudaMalloc((void**)&cuda_x, mem_size); 122 | cudaMemcpy(cuda_x, x, mem_size, cudaMemcpyHostToDevice); 123 | 124 | cudaMalloc((void**)&cuda_y, mem_size); 125 | cudaMemcpy(cuda_y, y, mem_size, cudaMemcpyHostToDevice); 126 | 127 | // Allocate cuda_out CUDA memory and launch add_kernel 128 | cudaMalloc((void**)&cuda_out, mem_size); 129 | add_kernel<<<1, 1>>>(cuda_x, cuda_y, cuda_out, N); 130 | 131 | // Copy result from GPU into CPU 132 | out = static_cast(malloc(mem_size)); 133 | cudaMemcpy(out, cuda_out, mem_size, cudaMemcpyDeviceToHost); 134 | 135 | // Sync CUDA stream to wait kernel completation 136 | cudaDeviceSynchronize(); 137 | 138 | // Print result and checkout out = 3. 139 | for(int i = 0; i < 10; ++i){ 140 | printf("out[%d] = %.3f\n", i, out[i]); 141 | } 142 | 143 | // Free CUDA Memory 144 | cudaFree(cuda_x); 145 | cudaFree(cuda_y); 146 | cudaFree(cuda_out); 147 | 148 | // Free Host CPU Memory 149 | free(x); 150 | free(y); 151 | free(out); 152 | 153 | return 0; 154 | } 155 | ``` 156 | 157 | ### 4. 编译执行 158 | 159 | 使用 `nvcc ./vector_add.cu -o add` 命令生成可执行文件,然后在终端输入 `./add` 执行 kernel,输出结果如下: 160 | 161 | ```bash 162 | out[0] = 3.000 163 | out[1] = 3.000 164 | out[2] = 3.000 165 | out[3] = 3.000 166 | out[4] = 3.000 167 | out[5] = 3.000 168 | out[6] = 3.000 169 | out[7] = 3.000 170 | out[8] = 3.000 171 | out[9] = 3.000 172 | ``` 173 | 174 | ## 附参考文档 175 | 176 | + [Exercise: Converting vector addition to CUDA](https://cuda-tutorial.readthedocs.io/en/latest/tutorials/tutorial01/#putting-things-in-actions) -------------------------------------------------------------------------------- /docs/02_first_kernel/vector_add.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 4 | for (int i = 0; i < n; ++i) { 5 | out[i] = x[i] + y[i]; 6 | } 7 | } 8 | 9 | int main(){ 10 | int N = 10000000; 11 | size_t mem_size = sizeof(float) * N; 12 | 13 | float *x, *y, *out; 14 | float *cuda_x, *cuda_y, *cuda_out; 15 | 16 | // Allocate host CPU memory for x, y 17 | x = static_cast(malloc(mem_size)); 18 | y = static_cast(malloc(mem_size)); 19 | 20 | // Initialize x = 1, y = 2 21 | for(int i = 0; i < N; ++i){ 22 | x[i] = 1.0; 23 | y[i] = 2.0; 24 | } 25 | 26 | // Allocate Device CUDA memory for cuda_x and cuda_y, copy them. 27 | cudaMalloc((void**)&cuda_x, mem_size); 28 | cudaMemcpy(cuda_x, x, mem_size, cudaMemcpyHostToDevice); 29 | 30 | cudaMalloc((void**)&cuda_y, mem_size); 31 | cudaMemcpy(cuda_y, y, mem_size, cudaMemcpyHostToDevice); 32 | 33 | // Allocate cuda_out CUDA memory and launch add_kernel 34 | cudaMalloc((void**)&cuda_out, mem_size); 35 | add_kernel<<<1, 1>>>(cuda_x, cuda_y, cuda_out, N); 36 | 37 | // Copy result from GPU into CPU 38 | out = static_cast(malloc(mem_size)); 39 | cudaMemcpy(out, cuda_out, mem_size, cudaMemcpyDeviceToHost); 40 | 41 | // Sync CUDA stream to wait kernel completation 42 | cudaDeviceSynchronize(); 43 | 44 | // Print result and checkout out = 3. 45 | for(int i = 0; i < 10; ++i){ 46 | printf("out[%d] = %.3f\n", i, out[i]); 47 | } 48 | 49 | // Free CUDA Memory 50 | cudaFree(cuda_x); 51 | cudaFree(cuda_y); 52 | cudaFree(cuda_out); 53 | 54 | // Free Host CPU Memory 55 | free(x); 56 | free(y); 57 | free(out); 58 | 59 | return 0; 60 | } -------------------------------------------------------------------------------- /docs/03_nvprof_usage/README.md: -------------------------------------------------------------------------------- 1 | # nvprof 性能分析 2 | 3 | ## 1. 简介 4 | 5 | 上一节我们手写了第一个 CUDA Kernel,算是牛刀小试。但我们其实并不知道我们写的 Kernel 执行的效率和性能如何,这就涉及了性能调优。 6 | 7 | 既然是「调优」,我们首先需要知道 CUDA Kernel 目前的耗时分布以及执行的性能瓶颈在哪里,因此就需要借助性能分析工具帮助我们获取到详细的执行信息,它就是 Nvidia 提供的一个命令行分析工具:`nvprof` 。 8 | 9 | ## 2. 用法 10 | 11 | 上一节我们使用 `nvcc ./vector_add.cu -o add` 命令生成了可执行文件,只需要在执行命令前面加上 `nvprof`,即执行 `nvprof ./add`,将会在终端中打印如下信息: 12 | 13 | ```bash 14 | ==33356== Profiling application: ./add 15 | ==33356== Profiling result: 16 | Type Time(%) Time Calls Avg Min Max Name 17 | GPU activities: 92.23% 570.25ms 1 570.25ms 570.25ms 570.25ms add_kernel(float*, float*, float*, int) 18 | 4.79% 29.586ms 1 29.586ms 29.586ms 29.586ms [CUDA memcpy DtoH] 19 | 2.99% 18.459ms 2 9.2297ms 9.2245ms 9.2349ms [CUDA memcpy HtoD] 20 | API calls: 56.06% 619.64ms 3 206.55ms 9.4402ms 600.73ms cudaMemcpy 21 | 43.58% 481.72ms 3 160.57ms 359.50us 481.00ms cudaMalloc 22 | 0.16% 1.7937ms 101 17.759us 239ns 933.68us cuDeviceGetAttribute 23 | 0.09% 1.0061ms 3 335.36us 278.68us 444.81us cudaFree 24 | 0.09% 956.79us 1 956.79us 956.79us 956.79us cuDeviceTotalMem 25 | 0.01% 132.25us 1 132.25us 132.25us 132.25us cuDeviceGetName 26 | 0.00% 50.300us 1 50.300us 50.300us 50.300us cudaLaunchKernel 27 | 0.00% 14.994us 1 14.994us 14.994us 14.994us cudaDeviceSynchronize 28 | 0.00% 10.974us 1 10.974us 10.974us 10.974us cuDeviceGetPCIBusId 29 | 0.00% 3.0460us 3 1.0150us 421ns 2.1590us cuDeviceGetCount 30 | 0.00% 1.7330us 2 866ns 328ns 1.4050us cuDeviceGet 31 | 0.00% 543ns 1 543ns 543ns 543ns cuDeviceGetUuid 32 | ``` 33 | 34 | `nvprof` 还有很多参数可以指定,这个我们稍后再学习。我们学习下如何看懂它给出的执行信息。 35 | 36 | ## 3. 分析 37 | 38 | 我们逐行分析上面的日志输出,其中第一行给出的是被分析的程序名 `./add`,即是我们前面 nvcc 编译生成的可执行文件: 39 | ```bash 40 | ==8936== Profiling application: ./add 41 | ``` 42 | 43 | 第二部分是执行可执行文件时,GPU 各个主要「行为」的耗时占比、具体时间、调用次数、平均/最小/最大耗时,接口行为名称: 44 | 45 | ```bash 46 | Type Time(%) Time Calls Avg Min Max Name 47 | GPU activities: 92.23% 570.25ms 1 570.25ms 570.25ms 570.25ms add_kernel(float*, float*, float*, int) 48 | 4.79% 29.586ms 1 29.586ms 29.586ms 29.586ms [CUDA memcpy DtoH] 49 | 2.99% 18.459ms 2 9.2297ms 9.2245ms 9.2349ms [CUDA memcpy HtoD] 50 | ``` 51 | 52 | 可以看出我们写的 CUDA 程序在 GPU 上主要包括 3 个关键活动: 53 | 54 | + `add_kernel`:即执行 kernel 的时间,占比 92%,耗时 570.25 ms 55 | + HtoD 的内存拷贝:即输入 `x` → `cuda_x`,`y` → `cuda_y` 的 2 次拷贝,占比 2.99%,耗时 18.459 ms 56 | + DtoH 的内存拷贝:即输出 `cuda_out` → `out` 的 1 次拷贝,占比 4.79%,耗时 29.586ms 57 | 58 | 59 | 第三个部分是 CUDA API 的具体调用开销,这个是从 API 层面来解读各个阶段的耗时: 60 | 61 | ```bash 62 | Type Time(%) Time Calls Avg Min Max Name 63 | API calls: 56.06% 619.64ms 3 206.55ms 9.4402ms 600.73ms cudaMemcpy 64 | 43.58% 481.72ms 3 160.57ms 359.50us 481.00ms cudaMalloc 65 | 0.16% 1.7937ms 101 17.759us 239ns 933.68us cuDeviceGetAttribute 66 | 0.09% 1.0061ms 3 335.36us 278.68us 444.81us cudaFree 67 | 0.09% 956.79us 1 956.79us 956.79us 956.79us cuDeviceTotalMem 68 | 0.01% 132.25us 1 132.25us 132.25us 132.25us cuDeviceGetName 69 | 0.00% 50.300us 1 50.300us 50.300us 50.300us cudaLaunchKernel 70 | 0.00% 14.994us 1 14.994us 14.994us 14.994us cudaDeviceSynchronize 71 | 0.00% 10.974us 1 10.974us 10.974us 10.974us cuDeviceGetPCIBusId 72 | 0.00% 3.0460us 3 1.0150us 421ns 2.1590us cuDeviceGetCount 73 | 0.00% 1.7330us 2 866ns 328ns 1.4050us cuDeviceGet 74 | 0.00% 543ns 1 543ns 543ns 543ns cuDeviceGetUuid 75 | ``` 76 | 77 | 其中最耗时的就是 3 次 `cudaMemcpy` 和`cudasMalloc` 的调用,99% 的时间都在干这两个事情,可以看出显存分配是一个比较「重」的操作,任何时候我们都应该尽量避免频繁的显存分配操作。在深度学习框架中,常会借助「内存池」技术一次申请较大的显存块,然后自己管理切分、分配和回收,这样就可以减少向系统 `cudaMalloc` 的次数,感兴趣的同学可以参考[Paddle 源码之内存管理技术](https://www.cnblogs.com/CocoML/p/14105729.html)。 78 | 79 | 剩下的 API 调用的开销基本差别不是特别大,大多数都是在 us 级别,我们一一介绍各个 API 的作用: 80 | 81 | + `cuDeviceGetAttribute` : 用于获取 CUDA 设备信息,比如 `cuDeviceGetAttribute(&val, CU_DEVICE_ATTRIBUTE_WARP_SIZE, device)`,参考[支持的 Attribute 列表](https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__DEVICE.html#group__CUDA__DEVICE_1g9c3e1414f0ad901d3278a4d6645fc266) 82 | + `cuDeviceTotalMem` : 返回指定显卡的容量,单位 bytes 83 | + `cudaFree` : 释放申请的显存数据区,归还给系统 84 | + `cuDeviceGetName` : 返回指定显卡的唯一标识字符串 85 | + `cudaMemcpy` : 用户设备之间的数据拷贝,可以是 HtoD、DtoH、DtoD 86 | + `cudaLaunchKernel` : 用于拉起一个函数到 GPU 上去执行,完整的函数签名是:`​cudaError_t cudaLaunchKernel ( const void* func, dim3 gridDim, dim3 blockDim, void** args, size_t sharedMem, cudaStream_t stream )`。这里需要对此函数的耗时有一个感知,即在几十 us 范围内 87 | + `cudaDeviceSynchronize` : 用于触发设备的强制同步,即会阻塞住直到设备上所有的计算都做完 88 | 89 | ## 4. 更多用法 90 | 91 | 在终端下使用 `nvprof --help` 可以看到更多的参数用法: 92 | 93 | ```bash 94 | -o, --export-profile : 可以选择导出到指定文件,后续可以被其他分析工具可视化 95 | 96 | --analysis-metrics : 搭配 --export-profile 使用,用于收集详细 profiling 信息 97 | 98 | --trace : Specify the option (or options seperated by commas) to be traced. 99 | 100 | --cpu-profiling : Turn on CPU profiling. Note: CPU profiling is not supported in multi-process mode. 101 | ``` 102 | 103 | 104 | ## 附参考资料 105 | 106 | + [Nvidia 官方 nvprof 使用文档](https://docs.nvidia.com/cuda/profiler-users-guide/index.html#nvprof) -------------------------------------------------------------------------------- /docs/04_first_refine_kernel/README.md: -------------------------------------------------------------------------------- 1 | # 尝试第一次优化 Kernel 2 | 3 | ## 1. 多线程计算 4 | 5 | 在[《手写第一个 Kernel》](https://cuda.keter.top/first_kernel/) 章节中,我们实现了两个 1D 张量的最朴素版本的 CUDA kernel: 6 | ```cpp 7 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 8 | for (int i = 0; i < n; ++i) { 9 | out[i] = x[i] + y[i]; 10 | } 11 | } 12 | 13 | int main(){ 14 | //... 15 | add_kernel<<<1, 1>>>(cuda_x, cuda_y, cuda_out, N); 16 | //... 17 | 18 | } 19 | ``` 20 | 21 | 从代码中可以看出,我们是通过`<<<1, 1>>>`方式拉起了 `add_kernel` ,意味着我们只是利用了 1 个线程,从起点 0 → n 依次遍历张量数据做加法操作,这个完全没有利用 GPU 并行计算的优势。 22 | 23 | 这里我们再复习下 `<<>>` 的含义:三尖括号告诉 CUDA 在使用多少个 thread 拉起 kernel。多个线程一组成为 `thread block`,多个`thread block`一组成为 `grid`。因为前面的 `M` 表示一个 `grid` 有 `M` 个 `thread block`, 一个 `thread block` 里有 `T` 个 `thread`。 24 | 25 | ![block_grid](./img/block_grid.png) 26 | 27 | 我们首先将上面的 kernel 升级为多线程版本,即类似 `add_kernel<<<1, 256>>>`。CUDA 提供了一些内建的变量来访问线程相关的信息,比如: 28 | 29 | + `threadIdx.x`: 指此线程在`thread block`中的下标位置 30 | + `blockDim.x`: 指一个`thread block`中的线程数 31 | 32 | 对于 `add_kernel<<<1, 256>>>` 而言,`threadIdx.x` 取值为 0~256 中的某个值,`blockDim.x` 的值为 256。 33 | 34 | 如果要将 `N = 10000000` 切分到 256 个线程里并行去计算,需要调整下 `add_kernel` 中的 for 语句的写法,实现同一份代码在被不同线程调用时,自动地各自计算各自的数据,首先改成如下的范式: 35 | ```cpp 36 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 37 | int index = 0; 38 | int stride = 1; 39 | 40 | for (int i = index; i < n; i += stride) { 41 | out[i] = x[i] + y[i]; 42 | } 43 | } 44 | ``` 45 | 46 | 每个线程从下标 index 开始遍历到 n,步长间隔为 stride,然后循环计算。为了让 256 个线程独立计算,我们只需要设置 stride = 256,然后每个线程计算的 index 是各自所在的 `threadIdx.x` 下标位置即可(取值范围为[0, 256)),如下图所示 47 | 48 | ![多线程](./img/parallel_thread.png) 49 | 50 | 则上述 `add_kernel` 的最终版本实现为: 51 | ```cpp 52 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 53 | int index = threadIdx.x; // 当前线程所在的下标位置 54 | int stride = blockDim.x; // 此样例中为 256,由<<<1, 256>>>自动传递过来 55 | 56 | for (int i = index; i < n; i += stride) { 57 | out[i] = x[i] + y[i]; 58 | } 59 | } 60 | ``` 61 | 62 | 完整的代码可以参考文件:[vector_add_thread.cu](./vector_add_thread.cu)。 63 | 64 | 编译命令:`nvcc ./vector_add_thread.cu -o add_thread`,执行和 Profile 命令: `nvprof ./add_thread`,结果如下: 65 | ```plain 66 | ==36546== Profiling application: ./add_p 67 | ==36546== Profiling result: 68 | Type Time(%) Time Calls Avg Min Max Name 69 | GPU activities: 46.76% 29.188ms 1 29.188ms 29.188ms 29.188ms [CUDA memcpy DtoH] 70 | 29.62% 18.485ms 2 9.2425ms 9.2208ms 9.2642ms [CUDA memcpy HtoD] 71 | 23.62% 14.745ms 1 14.745ms 14.745ms 14.745ms add_kernel(float*, float*, float*, int) 72 | ``` 73 | 74 | 相对于[《手写第一个 Kernel》](../02_first_kernel/) 章节中的性能提升: 75 | 76 | | | 耗时 |加速比| 77 | |:---:|:---:|:---:| 78 | |单线程| 570 ms | - | 79 | |多线程| 14.7 ms| 38.7x | 80 | 81 | 82 | ## 2. 多网格计算 83 | 84 | 上面我们只用到了 1 个 Thread block 就实现了 38 倍的加速比。接下来我们再看如何改为多个 Thread block 的版本。 85 | 86 | 一般而言,GPU 显卡上包含了很多流式处理器(即 Streaming Multiprocessors,简称 SMs),其中每个 SM 都包含了多个并行处理单元,均支持并发地执行多个 thread block。只有将 Kernel 放在多个 thread block 上去执行,才能最大限度地利用 GPU 并行加速的能力。 87 | 88 | 巧的是,CUDA 也提供了一些内建变量访问 block 相关的信息: 89 | 90 | + `blockIdx.x`: 指当前 thread block 在网格(grid)中的下标位置 91 | + `gridDim.x`: 指网格(grid)的大小(size) 92 | 93 | 94 | 同样的,我们么只需要修改 `add_kernel` 中的实现,确保每个 thread 都各自独立做计算即可,我们期望每个线程都只做 1 个浮点数的加法操作,也就意味着我们期望在 N = 10000000 个线程上并发地同时计算,切分示意图如下: 95 | 96 | ![多网格](./img/parallel_block.png) 97 | 98 | 其中,每个 thread block 包含的线程数依旧为 256,则需要 N/256 个 thread block,即 grid size = N/256: 99 | 100 | ```cpp 101 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 102 | // blockIdx.x 为当前 thread 所在的 block 在网格 grid 中下标索引,取值为[0, N/256) 103 | // blockDim.x = 256,为每个 block 中包含的线程数 104 | // threadIdx.x 为当前 thread 所在 block 的下标索引,取值为 [0, 256) 105 | int tid = blockIdx.x * blockDim.x + threadIdx.x; 106 | 107 | 108 | // 这里必须加判断,因为有的 thread 算出来的 tid 可能大于 n, 让它空跑就可以了 109 | if(tid < n) { 110 | out[tid] = x[tid] + y[tid]; 111 | } 112 | } 113 | ``` 114 | 115 | 完整的代码可以参考文件:[vector_add_grid.cu](./vector_add_grid.cu)。 116 | 117 | 编译命令:`nvcc ./vector_add_grid.cu -o add_grid`,执行和 Profile 命令: `nvprof ./add_grid`,结果如下: 118 | ```plain 119 | ==32660== Profiling application: ./add_g 120 | ==32660== Profiling result: 121 | Type Time(%) Time Calls Avg Min Max Name 122 | GPU activities: 63.20% 29.522ms 1 29.522ms 29.522ms 29.522ms [CUDA memcpy DtoH] 123 | 36.47% 17.036ms 2 8.5178ms 8.4890ms 8.5466ms [CUDA memcpy HtoD] 124 | 0.33% 152.61us 1 152.61us 152.61us 152.61us add_kernel(float*, float*, float*, int) 125 | ``` 126 | 127 | 相对于[《手写第一个 Kernel》](https://cuda.keter.top/first_kernel/) 章节中的性能提升: 128 | 129 | | | 耗时 |加速比| 130 | |:---:|:---:|:---:| 131 | |单线程| 570 ms | - | 132 | |多线程| 14.7 ms| 38.7x | 133 | |多 block| 0.153 ms| 3725x | 134 | 135 | 136 | ## 附参考文档 137 | 138 | + [CUDA in Actions](https://cuda-tutorial.readthedocs.io/en/latest/tutorials/tutorial02/#tutorial-02-cuda-in-actions) -------------------------------------------------------------------------------- /docs/04_first_refine_kernel/img/block_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/04_first_refine_kernel/img/block_grid.png -------------------------------------------------------------------------------- /docs/04_first_refine_kernel/img/parallel_block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/04_first_refine_kernel/img/parallel_block.png -------------------------------------------------------------------------------- /docs/04_first_refine_kernel/img/parallel_thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/04_first_refine_kernel/img/parallel_thread.png -------------------------------------------------------------------------------- /docs/04_first_refine_kernel/vector_add_grid.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 4 | int tid = blockIdx.x * blockDim.x + threadIdx.x; 5 | 6 | if(tid < n) { 7 | out[tid] = x[tid] + y[tid]; 8 | } 9 | } 10 | 11 | int main(){ 12 | int N = 10000000; 13 | size_t mem_size = sizeof(float) * N; 14 | 15 | float *x, *y, *out; 16 | float *cuda_x, *cuda_y, *cuda_out; 17 | 18 | // Allocate host CPU memory for x, y 19 | x = static_cast(malloc(mem_size)); 20 | y = static_cast(malloc(mem_size)); 21 | out = static_cast(malloc(mem_size)); 22 | 23 | // Initialize x = 1, y = 2 24 | for(int i = 0; i < N; ++i){ 25 | x[i] = 1.0; 26 | y[i] = 2.0; 27 | } 28 | 29 | // Allocate Device CUDA memory for cuda_x and cuda_y, copy them. 30 | cudaMalloc((void**)&cuda_x, mem_size); 31 | cudaMemcpy(cuda_x, x, mem_size, cudaMemcpyHostToDevice); 32 | 33 | cudaMalloc((void**)&cuda_y, mem_size); 34 | cudaMemcpy(cuda_y, y, mem_size, cudaMemcpyHostToDevice); 35 | 36 | // Allocate cuda_out CUDA memory and launch add_kernel 37 | cudaMalloc((void**)&cuda_out, mem_size); 38 | int block_size = 256; 39 | int grid_size = (N + block_size) / block_size; 40 | add_kernel<<>>(cuda_x, cuda_y, cuda_out, N); 41 | 42 | // Copy result from GPU into CPU 43 | cudaMemcpy(out, cuda_out, mem_size, cudaMemcpyDeviceToHost); 44 | 45 | // Sync CUDA stream to wait kernel completation 46 | cudaDeviceSynchronize(); 47 | 48 | // Print result and checkout out = 3. 49 | for(int i = 0; i < 10; ++i){ 50 | printf("out[%d] = %.3f\n", i, out[i]); 51 | } 52 | 53 | // Free CUDA Memory 54 | cudaFree(cuda_x); 55 | cudaFree(cuda_y); 56 | cudaFree(cuda_out); 57 | 58 | // Free Host CPU Memory 59 | free(x); 60 | free(y); 61 | free(out); 62 | 63 | return 0; 64 | } -------------------------------------------------------------------------------- /docs/04_first_refine_kernel/vector_add_thread.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 4 | int index = threadIdx.x; 5 | int stride = blockDim.x; 6 | 7 | for (int i = index; i < n; i += stride) { 8 | out[i] = x[i] + y[i]; 9 | } 10 | } 11 | 12 | int main(){ 13 | int N = 10000000; 14 | size_t mem_size = sizeof(float) * N; 15 | 16 | float *x, *y, *out; 17 | float *cuda_x, *cuda_y, *cuda_out; 18 | 19 | // Allocate host CPU memory for x, y 20 | x = static_cast(malloc(mem_size)); 21 | y = static_cast(malloc(mem_size)); 22 | 23 | // Initialize x = 1, y = 2 24 | for(int i = 0; i < N; ++i){ 25 | x[i] = 1.0; 26 | y[i] = 2.0; 27 | } 28 | 29 | // Allocate Device CUDA memory for cuda_x and cuda_y, copy them. 30 | cudaMalloc((void**)&cuda_x, mem_size); 31 | cudaMemcpy(cuda_x, x, mem_size, cudaMemcpyHostToDevice); 32 | 33 | cudaMalloc((void**)&cuda_y, mem_size); 34 | cudaMemcpy(cuda_y, y, mem_size, cudaMemcpyHostToDevice); 35 | 36 | // Allocate cuda_out CUDA memory and launch add_kernel 37 | cudaMalloc((void**)&cuda_out, mem_size); 38 | add_kernel<<<1, 256>>>(cuda_x, cuda_y, cuda_out, N); 39 | 40 | // Copy result from GPU into CPU 41 | out = static_cast(malloc(mem_size)); 42 | cudaMemcpy(out, cuda_out, mem_size, cudaMemcpyDeviceToHost); 43 | 44 | // Sync CUDA stream to wait kernel completation 45 | cudaDeviceSynchronize(); 46 | 47 | // Print result and checkout out = 3. 48 | for(int i = 0; i < 10; ++i){ 49 | printf("out[%d] = %.3f\n", i, out[i]); 50 | } 51 | 52 | // Free CUDA Memory 53 | cudaFree(cuda_x); 54 | cudaFree(cuda_y); 55 | cudaFree(cuda_out); 56 | 57 | // Free Host CPU Memory 58 | free(x); 59 | free(y); 60 | free(out); 61 | 62 | return 0; 63 | } -------------------------------------------------------------------------------- /docs/05_intro_parallel/README.md: -------------------------------------------------------------------------------- 1 | # 初识多线程并行计算 2 | 3 | ## 基础概念 4 | 5 | 这里我们系统性地学习下 CUDA 编程中的 Thread、Block、Grid 的概念。 6 | 7 | GPU 上一般包含很多流式处理器 SM,每个 SM 是 CUDA 架构中的基本计算单元,其可分为若干(如 2~3)个网格,每个网格内包含若干(如 65535)个线程块,每个线程块包含若干(如 512)个线程,概要地理解的话: 8 | 9 | + `Thread`: 一个 CUDA Kernel 可以被多个 threads 来执行 10 | + `Block`: 多个 threads 会组成一个 Block,而同一个 block 中的 threads 可以同步,也可以通过 shared memory 通信 11 | + `Grid`: 多个 blocks 可以组成一个 Grid 12 | 13 | 其中,一个 Grid 可以包含多个 Blocks。Blocks 的分布方式可以是一维的,二维,三维的;Block 包含多个 Threads,Threads 的分布方式也可以是一维,二维,三维的。 14 | 15 | ## 线程索引 16 | 17 | 在[尝试第一次优化 Kernel](https://cuda.keter.top/first_refine_kernel/)中的多 Block 优化的 `add_kernel` 函数实现中,我们计算了 `tid` 的唯一线程标识: 18 | ```cpp 19 | __global__ void add_kernel(float *x, float *y, float *out, int n){ 20 | // 唯一的线程下标 21 | int tid = blockIdx.x * blockDim.x + threadIdx.x; 22 | 23 | if(tid < n) { 24 | out[tid] = x[tid] + y[tid]; 25 | } 26 | } 27 | ``` 28 | 29 | 这里的 Block 和 Grid 里的单元都是按照一维的形式来组织的,所以在计算 `tid` 时,我们只要到的了 `.x` 后缀的内建变量。 30 | 31 | 对于二维、三维的 Block 和 Grid,每个线程的索引的计算公式如下。 32 | 33 | ### 一维 Grid 34 | Grid 为 一维,Block 为一维: 35 | ```cpp 36 | int threadId = blockIdx.x *blockDim.x + threadIdx.x; 37 | ``` 38 | 39 | Grid 为 一维,Block 为二维: 40 | ```cpp 41 | int threadId = blockIdx.x * blockDim.x * blockDim.y + 42 | threadIdx.y * blockDim.x + threadIdx.x; 43 | ``` 44 | 45 | Grid 为 一维,Block 为三维: 46 | ```cpp 47 | int threadId = blockIdx.x * blockDim.x * blockDim.y * blockDim.z + 48 | threadIdx.z * blockDim.y * blockDim.x + 49 | threadIdx.y * blockDim.x + threadIdx.x; 50 | ``` 51 | 52 | ### 二维 Grid 53 | Grid 为 二维,Block 为一维: 54 | ```cpp 55 | int blockId = blockIdx.y * gridDim.x + blockIdx.x; 56 | int threadId = blockId * blockDim.x + threadIdx.x; 57 | ``` 58 | 59 | Grid 为 二维,Block 为二维: 60 | ```cpp 61 | int blockId = blockIdx.x + blockIdx.y * gridDim.x; 62 | int threadId = blockId * (blockDim.x * blockDim.y) 63 | + (threadIdx.y * blockDim.x) + threadIdx.x; 64 | ``` 65 | 66 | Grid 为 二维,Block 为三维: 67 | ```cpp 68 | int blockId = blockIdx.x + blockIdx.y * gridDim.x; 69 | int threadId = blockId * (blockDim.x * blockDim.y * blockDim.z) 70 | + (threadIdx.z * (blockDim.x * blockDim.y)) 71 | + (threadIdx.y * blockDim.x) + threadIdx.x; 72 | ``` 73 | 74 | ### 三维 Grid 75 | Grid 为 三维,Block 为一维: 76 | ```cpp 77 | int blockId = blockIdx.x + blockIdx.y * gridDim.x 78 | + gridDim.x * gridDim.y * blockIdx.z; 79 | 80 | int threadId = blockId * blockDim.x + threadIdx.x; 81 | ``` 82 | 83 | Grid 为 三维,Block 为二维: 84 | ```cpp 85 | int blockId = blockIdx.x + blockIdx.y * gridDim.x 86 | + gridDim.x * gridDim.y * blockIdx.z; 87 | 88 | int threadId = blockId * (blockDim.x * blockDim.y) 89 | + (threadIdx.y * blockDim.x) + threadIdx.x; 90 | ``` 91 | 92 | Grid 为 三维,Block 为三维: 93 | ```cpp 94 | int blockId = blockIdx.x + blockIdx.y * gridDim.x 95 | + gridDim.x * gridDim.y * blockIdx.z; 96 | 97 | int threadId = blockId * (blockDim.x * blockDim.y * blockDim.z) 98 | + (threadIdx.z * (blockDim.x * blockDim.y)) 99 | + (threadIdx.y * blockDim.x) + threadIdx.x; 100 | ``` -------------------------------------------------------------------------------- /docs/06_impl_matmul/README.md: -------------------------------------------------------------------------------- 1 | # 手写实现矩阵乘 Matmul 2 | 3 | 目前,在训练和推理大型深度学习模型期间,矩阵乘法可能是最重要的算法之一。本文我们将手写实现矩阵乘法,以便更好地理解矩阵乘法的原理。 4 | 5 | ## CUDA 层次结构 6 | 7 | 在 CUDA 编程模型中,计算按照三级层次进行排序。每次调用 CUDA 内核都会创建一个新的网格,该网格由多个块组成。每个块由最多 1024 个单独的线程组成。这些常数可以在 CUDA 编程指南中查找。处于同一块内的线程可以访问相同的共享内存区域(SMEM)。 8 | 9 | 块中的线程数可以使用一个通常称为 `blockDim` 的变量进行配置,它是一个由三个整数组成的向量。该向量的条目指定了 `blockDim.x`、`blockDim.y` 和 `blockDim.z` 的大小,如下图所示: 10 | 11 | ![picture 0](images/0b35adb64a964e56018dc9fb7277269a3efa72b1526058609e0860f33e00426b.png) 12 | 13 | 同样,网格中的块数可以使用 `gridDim` 变量进行配置。当我们从主机启动一个新的内核时,它会创建一个包含按照指定方式排列的块和线程的单一网格。 14 | 15 | 对于我们的第一个内核,我们将使用 `grid`、`block` 和 `threa` 的层次结构,每个线程计算结果矩阵 C 中的一个元素。该线程将计算矩阵 A 相应行和矩阵 B 相应列的点积,并将结果写入矩阵 C。由于矩阵 C 的每个位置仅由一个线程写入,我们无需进行同步。我们将以以下方式启动内核: 16 | 17 | ```cpp 18 | #define CEIL_DIV(M, N) (((M) + (N)-1) / (N)) 19 | 20 | dim3 gridDim(CEIL_DIV(M, 32), CEIL_DIV(N, 32), 1); 21 | // 32 * 32 = 1024 thread per block 22 | dim3 blockDim(32, 32, 1); 23 | sgemm_naive<<>>(M, N, K, alpha, A, B, beta, C); 24 | ``` 25 | 26 | ## 内核实现 27 | 28 | CUDA 代码是从单线程的视角编写的。在内核代码中,我们使用 `blockIdx` 和 `threadIdx`。这些变量的值会根据访问它们的线程而异。在我们的例子中,`threadIdx.x` 和 `threadIdx.y` 将根据线程在网格中的位置从 0 到 31 变化。同样,`blockIdx.x` 和 `blockIdx.y` 也将根据线程块在网格中的位置从 0 到 `CEIL_DIV(N, 32)` 或 `CEIL_DIV(M, 32)` 变化。 29 | 30 | ```cpp 31 | __global__ void sgemm_naive_kernel(float *A, float *B, float *C, int M, int N, int K) 32 | { 33 | const uint x = blockIdx.x * blockDim.x + threadIdx.x; 34 | const uint y = blockIdx.y * blockDim.y + threadIdx.y; 35 | if (x < M && y < N) 36 | { 37 | float sum = 0.0f; 38 | for (int i = 0; i < K; i++) 39 | { 40 | sum += A[x * K + i] * B[i * N + y]; 41 | } 42 | C[x * N + y] = sum; 43 | } 44 | } 45 | ``` 46 | 47 | 下图可视化了我们的内核的执行方式: 48 | 49 | ![picture 1](images/6f55c7f9531e5efd955eab9a572ef5406733498bc0b50abed0e73985d88c840b.png) 50 | 51 | 一个好的编程习惯:在代码的最后一定一定记得释放堆内存,避免内存泄漏;并将指针置为空防止野指针的出现。不过这种事很容易忘记,有兴趣的宝贝可以学习下智能指针的用法,本文就不在展开介绍 C++ 的东西。 52 | 53 | ```cpp 54 | free(cpu_addr); // 释放 CPU 内存 55 | cpu_addr = nullptr; // 置空 56 | 57 | cudaFree(cuda_addr); // cudaFree API 释放 cuda 内存 58 | cuda_addr = nullptr; // 置空 59 | ``` 60 | 61 | 运行命令: 62 | 63 | ```plain 64 | nvcc -o matmul_raw matmul_raw.cu 65 | ./matmul_raw 66 | ``` 67 | 68 | 本文中我们使用的是最简单的矩阵乘法算法,下一篇文章我们将介绍更高效的矩阵乘法算法。 69 | 70 | ## References 71 | 72 | 1. https://siboehm.com/articles/22/CUDA-MMM 73 | 2. https://space.keter.top/docs/high_performance/GEMM%E4%BC%98%E5%8C%96%E4%B8%93%E9%A2%98/naive-gemm 74 | -------------------------------------------------------------------------------- /docs/06_impl_matmul/images/0b35adb64a964e56018dc9fb7277269a3efa72b1526058609e0860f33e00426b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/06_impl_matmul/images/0b35adb64a964e56018dc9fb7277269a3efa72b1526058609e0860f33e00426b.png -------------------------------------------------------------------------------- /docs/06_impl_matmul/images/6f55c7f9531e5efd955eab9a572ef5406733498bc0b50abed0e73985d88c840b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/06_impl_matmul/images/6f55c7f9531e5efd955eab9a572ef5406733498bc0b50abed0e73985d88c840b.png -------------------------------------------------------------------------------- /docs/06_impl_matmul/matmul_raw.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define CEIL_DIV(M, N) (((M) + (N) - 1) / (N)) 5 | 6 | void sgemm_naive_cpu(float *A, float *B, float *C, int M, int N, int K) 7 | { 8 | for (int x = 0; x < M; x++) 9 | { 10 | for (int y = 0; y < N; y++) 11 | { 12 | float sum = 0.0f; 13 | for (int i = 0; i < K; i++) 14 | { 15 | sum += A[x * K + i] * B[i * N + y]; 16 | } 17 | C[x * N + y] = sum; 18 | } 19 | } 20 | } 21 | 22 | __global__ void sgemm_naive_kernel(float *A, float *B, float *C, int M, int N, int K) 23 | { 24 | const uint x = blockIdx.x * blockDim.x + threadIdx.x; 25 | const uint y = blockIdx.y * blockDim.y + threadIdx.y; 26 | if (x < M && y < N) 27 | { 28 | float sum = 0.0f; 29 | for (int i = 0; i < K; i++) 30 | { 31 | sum += A[x * K + i] * B[i * N + y]; 32 | } 33 | C[x * N + y] = sum; 34 | } 35 | } 36 | 37 | void run_sgemm_naive(float *A, float *B, float *C, int m, int n, int k) 38 | { 39 | dim3 block_size(32, 32); 40 | dim3 grid_size(CEIL_DIV(m, 32), CEIL_DIV(n, 32)); 41 | sgemm_naive_kernel<<>>(A, B, C, m, n, k); 42 | } 43 | 44 | void randomize_matrix(float *mat, int N) 45 | { 46 | for (int i = 0; i < N; i++) 47 | { 48 | mat[i] = rand() % 100; 49 | } 50 | } 51 | 52 | int main() 53 | { 54 | int m = 256; 55 | int n = 256; 56 | int k = 256; 57 | 58 | // Allocate memory for matrices 59 | float *A, *B, *C, *C_ref; 60 | float *d_A, *d_B, *d_C; 61 | 62 | A = new float[m * k]; 63 | B = new float[k * n]; 64 | C = new float[m * n]; 65 | // save reference result 66 | C_ref = new float[m * n]; 67 | 68 | // Initialize matrices 69 | randomize_matrix(A, m * k); 70 | randomize_matrix(B, k * n); 71 | 72 | // Allocate device memory 73 | cudaMalloc((void **)&d_A, m * k * sizeof(float)); 74 | cudaMalloc((void **)&d_B, k * n * sizeof(float)); 75 | cudaMalloc((void **)&d_C, m * n * sizeof(float)); 76 | 77 | // Copy matrices to device 78 | cudaMemcpy(d_A, A, m * k * sizeof(float), cudaMemcpyHostToDevice); 79 | cudaMemcpy(d_B, B, k * n * sizeof(float), cudaMemcpyHostToDevice); 80 | cudaMemcpy(d_C, C, m * n * sizeof(float), cudaMemcpyHostToDevice); 81 | 82 | // Run naive sgemm 83 | run_sgemm_naive(d_A, d_B, d_C, m, n, k); 84 | 85 | // Copy result to host 86 | cudaMemcpy(C, d_C, m * n * sizeof(float), cudaMemcpyDeviceToHost); 87 | 88 | // Run reference sgemm 89 | sgemm_naive_cpu(A, B, C_ref, m, n, k); 90 | 91 | // Verify result 92 | for (int i = 0; i < m * n; i++) 93 | { 94 | if (C[i] != C_ref[i]) 95 | { 96 | printf("Error: mismatch at index %d, expected %f, got %f\n", i, C_ref[i], C[i]); 97 | return 1; 98 | } 99 | } 100 | 101 | free(A); 102 | free(B); 103 | free(C); 104 | free(C_ref); 105 | A = nullptr; 106 | B = nullptr; 107 | C = nullptr; 108 | C_ref = nullptr; 109 | 110 | cudaFree(d_A); 111 | cudaFree(d_B); 112 | cudaFree(d_C); 113 | d_A = nullptr; 114 | d_B = nullptr; 115 | d_C = nullptr; 116 | 117 | printf("Success!\n"); 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /docs/07_optimize_matmul/images/029d6d7f597e03b81754dff8748ec5decdfaa49c55d2613be43772371659deed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/07_optimize_matmul/images/029d6d7f597e03b81754dff8748ec5decdfaa49c55d2613be43772371659deed.png -------------------------------------------------------------------------------- /docs/07_optimize_matmul/images/1a8df2b3afe650e592a3d3943d9d59088b5d1e531dcc9a01ec8987a6d3c739ba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/07_optimize_matmul/images/1a8df2b3afe650e592a3d3943d9d59088b5d1e531dcc9a01ec8987a6d3c739ba.png -------------------------------------------------------------------------------- /docs/07_optimize_matmul/images/264915564b04781951d36d7d8527b418bbe0fea3a3969563a639f6575c1febd5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/07_optimize_matmul/images/264915564b04781951d36d7d8527b418bbe0fea3a3969563a639f6575c1febd5.png -------------------------------------------------------------------------------- /docs/07_optimize_matmul/images/3b9ca1d09a35e62b14f73b56e21b988d379bf0b38b8af6d4d9b17d9f46663c1c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/07_optimize_matmul/images/3b9ca1d09a35e62b14f73b56e21b988d379bf0b38b8af6d4d9b17d9f46663c1c.png -------------------------------------------------------------------------------- /docs/07_optimize_matmul/images/3bdc0715688817d9d6c4b6d3047861e76142c27a8b7cc59fca4020f951223baa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/07_optimize_matmul/images/3bdc0715688817d9d6c4b6d3047861e76142c27a8b7cc59fca4020f951223baa.png -------------------------------------------------------------------------------- /docs/07_optimize_matmul/images/b99194dc785674eb6347c91f3b30e150d29fc238e2c63332641d9c55a205fd8f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/07_optimize_matmul/images/b99194dc785674eb6347c91f3b30e150d29fc238e2c63332641d9c55a205fd8f.png -------------------------------------------------------------------------------- /docs/07_optimize_matmul/matmul_shared.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define CEIL_DIV(M, N) (((M) + (N) - 1) / (N)) 5 | 6 | void sgemm_naive_cpu(float *A, float *B, float *C, int M, int N, int K) 7 | { 8 | for (int x = 0; x < M; x++) 9 | { 10 | for (int y = 0; y < N; y++) 11 | { 12 | float sum = 0.0f; 13 | for (int i = 0; i < K; i++) 14 | { 15 | sum += A[x * K + i] * B[i * N + y]; 16 | } 17 | C[x * N + y] = sum; 18 | } 19 | } 20 | } 21 | 22 | template 23 | __global__ void sgemm_shared_mem_kernel(float *A, float *B, float *C, int M, int N, int K) 24 | { 25 | // the output block that we want to compute in this threadblock 26 | const uint c_row = blockIdx.x; 27 | const uint c_col = blockIdx.y; 28 | 29 | // allocate shared memory for the input and output submatrices 30 | __shared__ float A_shared[BLOCKSIZE * BLOCKSIZE]; 31 | __shared__ float B_shared[BLOCKSIZE * BLOCKSIZE]; 32 | 33 | // the inner row & col that we're accessing in this thread 34 | const uint thread_row = threadIdx.x / BLOCKSIZE; 35 | const uint thread_col = threadIdx.x % BLOCKSIZE; 36 | 37 | // advance pointers to the starting positions 38 | A += c_row * BLOCKSIZE * K; 39 | B += c_col * BLOCKSIZE; 40 | C += c_row * BLOCKSIZE * N + c_col * BLOCKSIZE; 41 | 42 | float tmp = 0.0f; 43 | for (int i = 0; i < K; i += BLOCKSIZE) 44 | { 45 | // load the next block of the input matrices into shared memory 46 | A_shared[thread_row * BLOCKSIZE + thread_col] = A[thread_row * K + thread_col]; 47 | B_shared[thread_row * BLOCKSIZE + thread_col] = B[thread_row * N + thread_col]; 48 | 49 | // wait for all threads to finish loading 50 | __syncthreads(); 51 | 52 | // compute the partial sum 53 | for (int j = 0; j < BLOCKSIZE; j++) 54 | { 55 | tmp += A_shared[thread_row * BLOCKSIZE + j] * B_shared[j * BLOCKSIZE + thread_col]; 56 | } 57 | 58 | // wait for all threads to finish computing 59 | __syncthreads(); 60 | 61 | // advance the pointers 62 | A += BLOCKSIZE; 63 | B += BLOCKSIZE * N; 64 | } 65 | 66 | C[thread_row * N + thread_col] = tmp; 67 | } 68 | 69 | void run_sgemm_shared_memory(float *A, float *B, float *C, int m, int n, int k) 70 | { 71 | const int BLOCKSIZE = 32; 72 | dim3 block_size(BLOCKSIZE * BLOCKSIZE); 73 | dim3 grid_size(CEIL_DIV(m, BLOCKSIZE), CEIL_DIV(n, BLOCKSIZE)); 74 | sgemm_shared_mem_kernel<<>>(A, B, C, m, n, k); 75 | } 76 | 77 | void randomize_matrix(float *mat, int N) 78 | { 79 | for (int i = 0; i < N; i++) 80 | { 81 | mat[i] = rand() % 100; 82 | } 83 | } 84 | 85 | int main() 86 | { 87 | int m = 256; 88 | int n = 256; 89 | int k = 256; 90 | 91 | // Allocate memory for matrices 92 | float *A, *B, *C, *C_ref; 93 | float *d_A, *d_B, *d_C; 94 | 95 | A = new float[m * k]; 96 | B = new float[k * n]; 97 | C = new float[m * n]; 98 | // save reference result 99 | C_ref = new float[m * n]; 100 | 101 | // Initialize matrices 102 | randomize_matrix(A, m * k); 103 | randomize_matrix(B, k * n); 104 | 105 | // Allocate device memory 106 | cudaMalloc((void **)&d_A, m * k * sizeof(float)); 107 | cudaMalloc((void **)&d_B, k * n * sizeof(float)); 108 | cudaMalloc((void **)&d_C, m * n * sizeof(float)); 109 | 110 | // Copy matrices to device 111 | cudaMemcpy(d_A, A, m * k * sizeof(float), cudaMemcpyHostToDevice); 112 | cudaMemcpy(d_B, B, k * n * sizeof(float), cudaMemcpyHostToDevice); 113 | cudaMemcpy(d_C, C, m * n * sizeof(float), cudaMemcpyHostToDevice); 114 | 115 | run_sgemm_shared_memory(d_A, d_B, d_C, m, n, k); 116 | 117 | // Copy result to host 118 | cudaMemcpy(C, d_C, m * n * sizeof(float), cudaMemcpyDeviceToHost); 119 | 120 | // Run reference sgemm 121 | sgemm_naive_cpu(A, B, C_ref, m, n, k); 122 | 123 | // Verify result 124 | for (int i = 0; i < m * n; i++) 125 | { 126 | if (C[i] != C_ref[i]) 127 | { 128 | printf("Error: mismatch at index %d, expected %f, got %f\n", i, C_ref[i], C[i]); 129 | return 1; 130 | } 131 | } 132 | 133 | free(A); 134 | free(B); 135 | free(C); 136 | free(C_ref); 137 | A = nullptr; 138 | B = nullptr; 139 | C = nullptr; 140 | C_ref = nullptr; 141 | 142 | cudaFree(d_A); 143 | cudaFree(d_B); 144 | cudaFree(d_C); 145 | d_A = nullptr; 146 | d_B = nullptr; 147 | d_C = nullptr; 148 | 149 | printf("Success!\n"); 150 | return 0; 151 | } -------------------------------------------------------------------------------- /docs/07_optimize_matmul/matmul_tiled.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define CEIL_DIV(M, N) (((M) + (N) - 1) / (N)) 5 | 6 | void sgemm_naive_cpu(float *A, float *B, float *C, int M, int N, int K) 7 | { 8 | for (int x = 0; x < M; x++) 9 | { 10 | for (int y = 0; y < N; y++) 11 | { 12 | float sum = 0.0f; 13 | for (int i = 0; i < K; i++) 14 | { 15 | sum += A[x * K + i] * B[i * N + y]; 16 | } 17 | C[x * N + y] = sum; 18 | } 19 | } 20 | } 21 | 22 | template 23 | __global__ void sgemm_blocktiling_1d_kernel(float *A, float *B, float *C, int M, int N, int K) 24 | { 25 | // the output block that we want to compute in this threadblock 26 | const uint c_row = blockIdx.y; 27 | const uint c_col = blockIdx.x; 28 | 29 | // allocate shared memory for the input and output submatrices 30 | __shared__ float A_shared[BM * BK]; 31 | __shared__ float B_shared[BK * BN]; 32 | 33 | // the inner row & col that we're accessing in this thread 34 | const uint thread_row = threadIdx.x / BN; 35 | const uint thread_col = threadIdx.x % BN; 36 | 37 | // advance pointers to the starting positions 38 | A += c_row * BM * K; 39 | B += c_col * BN; 40 | C += c_row * BM * N + c_col * BN; 41 | 42 | // use to avoid out-of-bounds accesses 43 | int global_m_pos = c_row * BM * K; 44 | int global_n_pos = c_col * BN; 45 | const uint m_size = M * K; 46 | const uint n_size = N * K; 47 | 48 | assert(BM * BK == blockDim.x); 49 | assert(BN * BK == blockDim.x); 50 | 51 | const uint A_inner_row = threadIdx.x / BK; // warp-level GMEM coalescing 52 | const uint A_inner_col = threadIdx.x % BK; 53 | const uint B_inner_row = threadIdx.x / BN; // warp-level GMEM coalescing 54 | const uint B_inner_col = threadIdx.x % BN; 55 | 56 | // allocate thread-local cache for results in registerfile 57 | float thread_results[TM] = {0.0}; 58 | 59 | // outer loop over block tiles 60 | for (uint bk_idx = 0; bk_idx < K; bk_idx += BK) 61 | { 62 | // load the next block of the input matrices into shared memory 63 | A_shared[A_inner_row * BK + A_inner_col] = (global_m_pos + A_inner_row * K + A_inner_col < m_size) ? A[A_inner_row * K + A_inner_col] : 0.0f; 64 | B_shared[B_inner_row * BN + B_inner_col] = (global_n_pos + B_inner_row * N + B_inner_col < n_size) ? B[B_inner_row * N + B_inner_col] : 0.0f; 65 | 66 | // wait for all threads to finish loading 67 | __syncthreads(); 68 | 69 | // advance the pointers 70 | A += BK; 71 | B += BK * N; 72 | global_m_pos += BK; 73 | global_n_pos += BK * N; 74 | 75 | // compute the partial sum 76 | for (uint dot_idx = 0; dot_idx < BK; dot_idx++) 77 | { 78 | // we make the dotproduct loop the outside loop, which facilitates 79 | // reuse of the Bs entry, which we can cache in a tmp var. 80 | float tmp_b = B_shared[dot_idx * BN + thread_col]; 81 | for (uint res_idx = 0; res_idx < TM; res_idx++) 82 | { 83 | thread_results[res_idx] += A_shared[(thread_row * TM + res_idx) * BK + dot_idx] * tmp_b; 84 | } 85 | } 86 | 87 | // wait for all threads to finish computing 88 | __syncthreads(); 89 | } 90 | 91 | for (uint res_idx = 0; res_idx < TM; res_idx++) 92 | { 93 | if (c_row * BM + thread_row * TM + res_idx < M && c_col * BN + thread_col < N) 94 | { 95 | C[(thread_row * TM + res_idx) * N + thread_col] = thread_results[res_idx]; 96 | } 97 | } 98 | } 99 | 100 | void run_sgemm_blocktiling_1d(float *A, float *B, float *C, int m, int n, int k) 101 | { 102 | const uint BM = 64; 103 | const uint BN = 64; 104 | const uint BK = 8; 105 | const uint TM = 8; 106 | dim3 grid_size(CEIL_DIV(n, BN), CEIL_DIV(m, BM)); 107 | dim3 block_size((BM * BN) / TM); 108 | sgemm_blocktiling_1d_kernel 109 | <<>>(A, B, C, m, n, k); 110 | } 111 | 112 | void randomize_matrix(float *mat, int N) 113 | { 114 | for (int i = 0; i < N; i++) 115 | { 116 | mat[i] = rand() % 100; 117 | } 118 | } 119 | 120 | int main() 121 | { 122 | int m = 256; 123 | int n = 256; 124 | int k = 256; 125 | 126 | // Allocate memory for matrices 127 | float *A, *B, *C, *C_ref; 128 | float *d_A, *d_B, *d_C; 129 | 130 | A = new float[m * k]; 131 | B = new float[k * n]; 132 | C = new float[m * n]; 133 | // save reference result 134 | C_ref = new float[m * n]; 135 | 136 | // Initialize matrices 137 | randomize_matrix(A, m * k); 138 | randomize_matrix(B, k * n); 139 | 140 | // Allocate device memory 141 | cudaMalloc((void **)&d_A, m * k * sizeof(float)); 142 | cudaMalloc((void **)&d_B, k * n * sizeof(float)); 143 | cudaMalloc((void **)&d_C, m * n * sizeof(float)); 144 | 145 | // Copy matrices to device 146 | cudaMemcpy(d_A, A, m * k * sizeof(float), cudaMemcpyHostToDevice); 147 | cudaMemcpy(d_B, B, k * n * sizeof(float), cudaMemcpyHostToDevice); 148 | cudaMemcpy(d_C, C, m * n * sizeof(float), cudaMemcpyHostToDevice); 149 | 150 | run_sgemm_blocktiling_1d(d_A, d_B, d_C, m, n, k); 151 | 152 | // Copy result to host 153 | cudaMemcpy(C, d_C, m * n * sizeof(float), cudaMemcpyDeviceToHost); 154 | 155 | // Run reference sgemm 156 | sgemm_naive_cpu(A, B, C_ref, m, n, k); 157 | 158 | // Verify result 159 | for (int i = 0; i < m * n; i++) 160 | { 161 | if (C[i] != C_ref[i]) 162 | { 163 | printf("Error: mismatch at index %d, expected %f, got %f\n", i, C_ref[i], C[i]); 164 | return 1; 165 | } 166 | } 167 | 168 | free(A); 169 | free(B); 170 | free(C); 171 | free(C_ref); 172 | A = nullptr; 173 | B = nullptr; 174 | C = nullptr; 175 | C_ref = nullptr; 176 | 177 | cudaFree(d_A); 178 | cudaFree(d_B); 179 | cudaFree(d_C); 180 | d_A = nullptr; 181 | d_B = nullptr; 182 | d_C = nullptr; 183 | 184 | printf("Success!\n"); 185 | return 0; 186 | } -------------------------------------------------------------------------------- /docs/08_impl_reduce/README.md: -------------------------------------------------------------------------------- 1 | # 手写实现 Reduce 2 | 3 | ## 1. 什么是 Reduce 4 | 5 | `Reduce` 是一个高阶函数,它接收一个函数作为参数,这个函数接收两个参数,然后返回一个值。`Reduce` 会从左到右依次对数组中的元素进行处理,最终得到一个值。以加法为例,就相当于给定一个数组,对数组求和。用 cpu 实现起来非常简单,一层循环解决问题。本文主要讨论如何用纯函数式的方式实现 `Reduce`。 6 | 7 | 8 | 9 | ## 2. 使用 GPU 实现 Reduce 10 | 11 | ### 2.1 CPU 实现 12 | 13 | 14 | ```cpp 15 | // reduce cpu version 16 | int reduce(int *arr, int len) { 17 | int sum = 0; 18 | for (int i = 0; i < len; i++) { 19 | sum += arr[i]; 20 | } 21 | return sum; 22 | } 23 | ``` 24 | 25 | 可以看到,cpu 实现非常简单,一层循环就可以解决问题。但是,这个实现并不是纯函数式的,因为它有副作用,即修改了 `sum` 的值。我们可以用 GPU 来实现一个纯函数式的 `Reduce`。 26 | 27 | ### 2.2 GPU 层次结构 28 | 29 | 首先我们先来回归一下 GPU 的层次结构。也就是代码中的 `block` 和 `grid`。`block` 是一个线程块,`grid` 是一个线程网格。`block` 中的线程可以通过 `threadIdx.x` 来获取自己的线程 id,`grid` 中的线程可以通过 `blockIdx.x` 来获取自己的线程 id。`block` 中的线程可以通过 `blockDim.x` 来获取 `block` 的大小,`grid` 中的线程可以通过 `gridDim.x` 来获取 `grid` 的大小。 30 | 31 | 对于 `Reduce` 来说,我们可以按照下面这个图设计: 32 | 33 | ![img1](./images/1-CUDA层次结构.png) 34 | 35 | 我们把数组分成了若干个 `block`,每个 `block` 中有若干个线程。每个线程负责处理一个元素。每个 `block` 的线程会把自己处理的元素的值累加到 `block` 的第一个线程中。最后,每个 `block` 的第一个线程会把自己 `block` 中的所有元素的值累加到 `grid` 的第一个线程中。最后,`grid` 的第一个线程就是我们要求的结果。一般我们会把 `block` 的大小设置为 `32`,但是图中为了方便演示,我们把 `block` 的大小设置为 `8`。 36 | 37 | GPU 的计算过程如下图所示: 38 | 39 | ![img2](./images/2-计算原理图.png) 40 | 41 | 以上图为例,我们来看一下 `Reduce` 的计算过程。首先,我们把数组分成了 `3` 个 `block`,每个 `block` 中有 `8` 个线程。在第一轮计算中,奇数线程会把自己的值累加到偶数线程中。在第二轮计算中,`block` 中的第 `0` 个线程会把 `4` 号线程的值累加到自己的值中。 每个 `block` 的值都计算完之后还需要对 `block` 的值进行累加,下面我们来看一下代码要如何实现。 42 | 43 | ### 2.3 GPU 实现 44 | 45 | 我们首先看 `Kernel` 的实现,代码如下: 46 | 47 | ```cpp 48 | template 49 | __global__ void reduce_naive_kernel(int *arr, int *out, int len) 50 | { 51 | __shared__ int sdata[BLOCKSIZE]; 52 | int tid = threadIdx.x; // 线程 id (block 内) 53 | int bid = blockIdx.x; // block id (grid 内) 54 | int bdim = blockDim.x; // block 大小 55 | int i = bid * bdim + tid; // 全局 id 56 | 57 | // 将数据拷贝到共享内存 58 | if (i < len) 59 | { 60 | sdata[tid] = arr[i]; 61 | } 62 | 63 | __syncthreads(); // 等待所有线程完成 64 | 65 | // 每个线程计算 log2(bdim)-1 个轮回 66 | // 比如 bdim = 8, 则每个线程计算 2 个轮回 67 | for (int s = 1; s < bdim; s *= 2) 68 | { 69 | if (tid % (2 * s) == 0 && i + s < len) 70 | { 71 | sdata[tid] += sdata[tid + s]; 72 | } 73 | // 等待所有线程完成 后再进行下一轮计算 74 | __syncthreads(); 75 | } 76 | 77 | // 每个 block 的第一个线程将结果写入到 out 中 78 | if (tid == 0) 79 | { 80 | out[bid] = sdata[0]; 81 | } 82 | } 83 | ``` 84 | 85 | 我们可以对照图来看一下代码。首先,我们把数组拷贝到共享内存中,然后等待所有线程完成。接着,每个线程计算 `log2(bdim)-1` 个轮回,每个轮回中,线程 `tid` 会把 `tid + s` 的值累加到 `tid` 中。最后,每个 `block` 的第一个线程会把自己的值写入到 `out` 中。 86 | 87 | 需要注意代码中的 `__syncthreads()`,这个函数会等待 `block` 中的所有线程完成,然后再进行下一步操作。这个函数是必须的,因为我们需要保证每个线程都能够读到自己需要的数据。如果没有这个函数,那么就会出现线程 `tid` 在读取 `tid + s` 的值的时候,线程 `tid + s` 还没有写入 `tid + s` 的值,这样就会出现错误的结果。 88 | 89 | 接下来我们看一下 `main` 函数的实现,代码如下: 90 | 91 | ```cpp 92 | const int len = 1000; 93 | 94 | int main() { 95 | int *arr = new int[len]; 96 | int *out = new int[len]; 97 | int *d_arr, *d_out; 98 | 99 | // 初始化数组 100 | for (int i = 0; i < len; i++) { 101 | arr[i] = i; 102 | } 103 | 104 | // 分配内存 105 | cudaMalloc((void **)&d_arr, sizeof(int) * len); 106 | cudaMalloc((void **)&d_out, sizeof(int) * len); 107 | 108 | // 拷贝数据到显存 109 | cudaMemcpy(d_arr, arr, sizeof(int) * len, cudaMemcpyHostToDevice); 110 | 111 | // 计算 block 和 grid 的大小 112 | int blocksize = 32; 113 | int gridsize = (len + blocksize - 1) / blocksize; 114 | 115 | // 调用 kernel 函数 116 | reduce_naive_kernel<<>>(d_arr, d_out, len); 117 | 118 | // 拷贝数据到内存 119 | cudaMemcpy(out, d_out, sizeof(int) * len, cudaMemcpyDeviceToHost); 120 | 121 | // 计算结果 122 | int sum = 0; 123 | // 注意是gridsize,不是blocksize 124 | // 因为每个block的第一个线程都会把自己的值写入到out中 125 | // gridsize是block的数量(结合图理解) 126 | for (int i = 0; i < gridsize; i++) { 127 | sum += out[i]; 128 | } 129 | printf("sum = %d\n", sum); 130 | 131 | // 核对结果 132 | int sum2 = 0; 133 | for (int i = 0; i < len; i++) { 134 | sum2 += arr[i]; 135 | } 136 | 137 | if (sum == sum2) { 138 | printf("success\n"); 139 | } else { 140 | printf("failed\n"); 141 | } 142 | 143 | // 释放内存 144 | cudaFree(d_arr); 145 | cudaFree(d_out); 146 | delete[] arr; 147 | delete[] out; 148 | return 0; 149 | } 150 | ``` 151 | 152 | 首先,我们需要把数组拷贝到显存中,然后计算 `block` 和 `grid` 的大小。接着,我们调用 `Kernel` 函数,最后把结果拷贝到内存中。最后,我们计算结果,核对结果,释放内存。 这里需要注意的是我们得到的结果是一个数组,我们需要对数组进行累加才能得到最终的结果。当然了我们也可以再调用一次 `Kernel` 函数,把数组中的值累加到 `grid` 的第一个线程中,这样就可以得到最终的结果了。 153 | 154 | 编译命令: 155 | 156 | ```bash 157 | nvcc -o reduce_naive reduce_naive.cu 158 | ./reduce_naive 159 | ``` 160 | 161 | ## 3. 总结 162 | 163 | 本文主要讨论了如何用 GPU 来实现 `Reduce`。我们首先回顾了 GPU 的层次结构,然后我们用 GPU 来实现了一个纯函数式的 `Reduce`。最后,我们对比了 cpu 和 GPU 的实现,发现 GPU 的实现更加简洁,而且可以充分利用 GPU 的并行计算能力。下一篇文章我们将讨论如何优化 `Reduce` 的实现。 164 | 165 | ## Reference 166 | 167 | 1. https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf 168 | 2. https://link.zhihu.com/?target=https%3A//github.com/guoruoqian/cudaReductionLearning 169 | 3. https://zhuanlan.zhihu.com/p/365581043 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /docs/08_impl_reduce/images/1-CUDA层次结构.drawio: -------------------------------------------------------------------------------- 1 | 7Vxdb6M4FP01kXYfOgo4EPI4bWd2dqWRVurDzjw6YIhVgrPGacj++rWxHT5CGhKlYCaklQLX3+f4Xh8bkQl4Wmd/ULhZfScBiif2NMgm4Hli25YFFvxLWPbKMnXn0hJRHChbYXjB/yGdUVm3OEBpJSMjJGZ4UzX6JEmQzyo2SCnZVbOFJK62uoEROjK8+DA+tv6DA7aSVs+eF/ZvCEcr3bLlqhGvoc6sRpKuYEB2JRP4MgFPlBAmr9bZE4oFehoXWe7ridRDxyhKWKsC4C+837AkfV1Se7ZjbkK9h5ms5Q3GWzVg1Vm21whQsk0CJCqZTsDjboUZetlAX6TuOOnctmLrmN9Z/DJ9RcxfqRtVN6IMZSc7bR2g4JMIkTVidM+zqAKeAk9PH0fd7wouFq6yrUo8zJQNKvqjQ80FQvxCgXQBYMA7j1jEIducHLyaqXCps08vBcWyW6Bid4nKohNQTvNxDFUJCqtTKJzzUKAk+CxCE79LSMKNjwFMV7mLWWfdCQVHAesMRIuzCDkNAGkbRTFk+K3aZhNqqoW/Cea9OczNOs4p2VIfqUzlaKXL6YwM0gixo4w5J4dRXE+TO9KUw51VabqWtay53AeTOB9JLKPv3YZEr1sSWyyo90RifXG/lsVDPR3RqAW7QRpAV+8ey6NuNYFlGTfHrWkzZvetCix7JOoX0AUWGGn8BZSB1eJ04q5oHKw2aLEp7kcbALt3bWDeTlTSNWqDGirm7TZ7ImrY2sC8/Wa/NA5UG7Q4cr4rGoeqDewWe+N+tMHM61sbaCgMmuSnHk3dtzawzdtw9kTUoLWBbd6Gs18ah6kNbPOewfZL42C1QYu9cT/awJ31rg3M245KukZtUEPFvA1nT0QNWxuYt+Hsl8ZhagPQ4jH1XdE4VG0AjD038KZ9awNg3rkBGM8NmlAx79ygJ6IGrQ2AeecG/dI4UG1g3rlBvzQOVRvMWvjjR7+k0eLNFdCtKGg6MHBj3uxjSHK2CnTcf7dEJzyk+UtZn3kGPv6sSOSGUL4HVNieyBr7POEFJin/+v5Szu5G4vsxJv7rVDfNhyJbl4lHNF3/9lGI4/iJxITmFYEAIi/0RSZGySsqpbi+h5Zh6/eVTk+4d920W7JnV+1WS1CiDLMfCnVx/bNkf85KCc97fZPwjv/Q2cTNz/JNUSi/06X8LX07RM+bhEzt/DI4tXAIGYzOZjRkLbXmVwZh261VNG0XlPkcgftSto3IkL6zaNTaUf0vZrOs8LYBv2nl7iu2WZ3HttARf42xLf+o0Zbs8tM65r270s2mpi11s6bnBn1NB7v76RCGtt+41AXu0nXc29A+b/EWase0X/ZIxI9hmmL/AyTE+4uWSYtJXZi3XUycOvldK/ymhyrSzdINTBo93Jd8Ce+m0fI33mH+z5uflq5+F5fS30U8COEax3tZhlcE15s8EfANv6hPBoBUBoB1epSjWlk9uFTSZG9FYkLoGsbV5J2aCSJ9JvubJ8aIMUQf+JB9nESN5bmDswcY4yiRyT6fiIhWkzGPO4mqvty1PJFRPr6QV6qr51JRZ9gRGlRbLxdfQv81yqPaQw17W/CnMLeFVtXXTomBAKebGCr0cRLjUsNhTCArd6geepci9P4ZZJ+yUviVU+NE+BWDPR93bxA4vUZ9VHl5vyEAgI+KmjqyjK40ulKjK+UqJv9lFtM8yVo4FVc6/GpMb77UdJ4/nrFcfMZySfy058ekd6s7ncvOjS/WncgKHDRvgnXhzgEs9PxgdCeoe2D7BztnKvpg3emc3lmOi+W4WIJHtqIIBoMQngffuf1qyW+LHzaTvlf8Phz48j8= -------------------------------------------------------------------------------- /docs/08_impl_reduce/images/1-CUDA层次结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/08_impl_reduce/images/1-CUDA层次结构.png -------------------------------------------------------------------------------- /docs/08_impl_reduce/images/2-计算原理图.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/08_impl_reduce/images/2-计算原理图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/08_impl_reduce/images/2-计算原理图.png -------------------------------------------------------------------------------- /docs/08_impl_reduce/reduce_naive.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const int len = 10000; 6 | 7 | template 8 | __global__ void reduce_naive_kernel(int *arr, int *out, int len) 9 | { 10 | __shared__ int sdata[BLOCKSIZE]; 11 | int tid = threadIdx.x; // 线程 id (block 内) 12 | int bid = blockIdx.x; // block id (grid 内) 13 | int bdim = blockDim.x; // block 大小 14 | int i = bid * bdim + tid; // 全局 id 15 | 16 | // 将数据拷贝到共享内存 17 | if (i < len) 18 | { 19 | sdata[tid] = arr[i]; 20 | } 21 | 22 | __syncthreads(); // 等待所有线程完成 23 | 24 | // 每个线程计算 bdim^0.5 个轮回 25 | // 比如 bdim = 8, 则每个线程计算 2 个轮回 26 | for (int s = 1; s < bdim; s *= 2) 27 | { 28 | if (tid % (2 * s) == 0 && i + s < len) 29 | { 30 | sdata[tid] += sdata[tid + s]; 31 | } 32 | // 等待所有线程完成 后再进行下一轮计算 33 | __syncthreads(); 34 | } 35 | 36 | // 每个 block 的第一个线程将结果写入到 out 中 37 | if (tid == 0) 38 | { 39 | out[bid] = sdata[0]; 40 | } 41 | } 42 | 43 | int main() 44 | { 45 | int *arr = new int[len]; 46 | int *out = new int[len]; 47 | int *d_arr, *d_out; 48 | 49 | // 初始化数组 50 | for (int i = 0; i < len; i++) 51 | { 52 | arr[i] = i; 53 | } 54 | 55 | // 分配内存 56 | cudaMalloc((void **)&d_arr, sizeof(int) * len); 57 | cudaMalloc((void **)&d_out, sizeof(int) * len); 58 | 59 | // 拷贝数据到显存 60 | cudaMemcpy(d_arr, arr, sizeof(int) * len, cudaMemcpyHostToDevice); 61 | 62 | // 计算 block 和 grid 的大小 63 | const int blocksize = 32; 64 | const int gridsize = (len + blocksize - 1) / blocksize; 65 | 66 | // 调用 kernel 函数 67 | reduce_naive_kernel<<>>(d_arr, d_out, len); 68 | 69 | // 拷贝数据到内存 70 | cudaMemcpy(out, d_out, sizeof(int) * len, cudaMemcpyDeviceToHost); 71 | 72 | // 计算结果 73 | int sum = 0; 74 | for (int i = 0; i < gridsize; i++) 75 | { 76 | sum += out[i]; 77 | } 78 | printf("sum = %d\n", sum); 79 | 80 | // 核对结果 81 | int sum2 = 0; 82 | for (int i = 0; i < len; i++) 83 | { 84 | sum2 += arr[i]; 85 | } 86 | 87 | if (sum == sum2) 88 | { 89 | printf("success\n"); 90 | } 91 | else 92 | { 93 | printf("failed\n"); 94 | } 95 | 96 | // 释放内存 97 | cudaFree(d_arr); 98 | cudaFree(d_out); 99 | delete[] arr; 100 | delete[] out; 101 | return 0; 102 | } -------------------------------------------------------------------------------- /docs/09_optimize_reduce/01_interleaved_addressing/README.md: -------------------------------------------------------------------------------- 1 | # 交错寻址 2 | 3 | 上一篇文章中,我们手写了一个简单的 Reduce 算法,但是性能并不是很好,这一篇文章我们就来逐步优化这个算法。 4 | 5 | ## 1. 优化点分析 6 | 7 | 我们首先来看一下上一篇文章中的 Reduce 算法的性能瓶颈在哪里。上一篇文章中我们的实现代码有下面这个部分: 8 | 9 | ```cpp 10 | for (int s = 1; s < bdim; s *= 2) 11 | { 12 | if (tid % (2 * s) == 0 && i + s < len) 13 | { 14 | sdata[tid] += sdata[tid + s]; 15 | } 16 | // 等待所有线程完成 后再进行下一轮计算 17 | __syncthreads(); 18 | } 19 | ``` 20 | 21 | 这一部分的代码有俩个问题,一个 warp divergent,另一个是取模这个操作很昂贵。 22 | 23 | ### 1.1. Warp Divergent 24 | 25 | 我们先来看一下 warp divergent 这个问题。warp 是 SM 的基本执行单元。逻辑上我们认为 GPU 有多个计算网格,一个计算网格里面所有的线程就叫做一个线程块。在内核启动之后,线程块里的线程会被分配到某个 SM 上,然后在被分为多个 warp。下图可以很好的说明这个问题。 26 | 27 | ![picture 0](images/d6f37bda18827d5a253b26ac3ec49e2755e0249d7c52684e1713196706f0f625.png) 28 | 29 | 我们知道,一个 warp 有 32 个线程。一个 warp 中的线程执行着相同的指令。也就是说同一时间 warp 里面的线程要么执行 if 分支要么执行 else 分支。 30 | 31 | 不同的线程执行的数据是不一样的,同一时间 warp 里面的线程执行的指令又要完全一样。这就导致在部分线程满足 if 分支的条件并执行 if 分支的时候,其他的线程就会被阻塞,这样就导致了 Warp Divergent (线程束分化)的问题。 32 | 33 | ### 1.2 取模操作 34 | 35 | GPU 设计的初衷是为了高度并行的图形处理和通用计算。取模操作涉及到除法,而除法是一个相对于其他算术运算而言较为复杂的操作,难以并行化。在 GPU 中,一些线程可能需要等待其他线程完成除法操作,导致并行性下降。取模操作通常伴随着对内存的访问,而内存访问模式对于性能具有重要影响。如果取模操作导致了不规则的内存访问模式,这可能使得缓存的效率下降,从而影响整体性能。 36 | 37 | ## 2. 使用交错寻址进行优化 38 | 39 | 之前的代码里面我们是基于线程的 id 来进行寻址的,偶数线程的行为和奇数线程的行为是不一样的。这样就导致了 warp divergent 的问题。同一个 warp 里面有一半的线程都没用上。想要解决这个问题,我们可以使用交错寻址的方式,代码如下: 40 | 41 | ```cpp 42 | // 不使用交错寻址 43 | for (int s = 1; s < bdim; s *= 2) 44 | { 45 | if (tid % (2 * s) == 0 && i + s < len) 46 | { 47 | sdata[tid] += sdata[tid + s]; 48 | } 49 | // 等待所有线程完成 后再进行下一轮计算 50 | __syncthreads(); 51 | } 52 | 53 | // 使用交错寻址 54 | for (int s = 1; s < bdim; s *= 2) 55 | { 56 | int index = 2 * s * tid; 57 | if ((index + s < bdim) && (bdim * bid + s < len)) 58 | { 59 | sdata[index] += sdata[index + s]; 60 | } 61 | } 62 | ``` 63 | 64 | 让我们对比一下这俩种方式的寻址方式,在不使用交错寻址的情况下,计算原理图如下: 65 | 66 | ![picture 1](images/7257e14ad7e2c51a74b42f0f31a4bffa034546852182c7122fe45672cf4a576e.png) 67 | 68 | 可以看到只有偶数线程在参与计算,奇数线程都是空闲的。而使用交错寻址的方式,计算原理图如下: 69 | 70 | ![picture 2](images/ae8f73237006381f3a0785df49aa6ccfa553513dbe1037c6a44a49e4d19b7b1d.png) 71 | 72 | 在 `BLOCKSIZE` 为 256 的情况下,一个 block 中分配 256 个线程,32 个线程为一组,绑定在一个 SIMD 单元。所以 256 个线程可以简单地理解为分配了 8 组 SIMD 单元。(但实际的硬件资源分配不是这样,因为一个 SM 的计算资源有限,不可能真的给每一个 block 都分配这么多的 SIMD 单元。)在第 1 次迭代中,warp 的 index 为 0-3 的进入分支,而 4-7 的 warp 的 index 大于等于 blockDim.x。由于每个 warp 都只进入一个分支,因此不存在 warp divergence 的情况。 73 | 74 | 在第 2 次迭代中,warp 的 index 为 0 和 1 的进入计算分支。在第 3 次迭代中,只有 warp 的 index 为 0 的 warp 进入计算分支。而在第 4 次迭代中,仅有 warp 的 index 为 0 的 warp 的前 16 个线程进入分支。这时开始出现 warp divergence。通过这种方式,成功地消除了前 3 次迭代中的 warp divergence。 75 | 76 | 这样的写法也消除了取模操作,因为我们不再需要取模操作来判断线程的奇偶性。 77 | 78 | 编译运行代码: 79 | 80 | ```bash 81 | nvcc -o reduce_interleaved_addressing reduce_interleaved_addressing.cu 82 | ./reduce_interleaved_addressing 83 | ``` 84 | 85 | 同样的配置下和上一篇文章中的代码进行对比结果如下: 86 | 87 | | 优化手段 | 运行时间(us) | 带宽 | 加速比 | 88 | | --- | --- | --- | --- | 89 | | Baseline | 3118.4 | 42.503GB/s | ~ | 90 | | 交错寻址 | 1904.4 | 73.522GB/s | 1.64 | 91 | 92 | ## 总结 93 | 94 | 本文我们介绍了 Reduce 算法的性能优化实践。我们首先分析了 Reduce 算法的性能瓶颈,然后针对性的进行了优化。最后我们介绍了交错寻址的方式,成功地消除了前 3 次迭代中的 warp divergence。但是这样的写法也引入了新的问题,Bank Conflict。这个问题我们会在下一篇文章中进行介绍。 95 | 96 | 97 | ## Reference 98 | 99 | 1. https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf 100 | 2. https://github.com/guoruoqian/cudaReductionLearning 101 | 3. https://zhuanlan.zhihu.com/p/365581043 102 | 4. http://www.giantpandacv.com/project/OneFlow/%E3%80%90BBuf%E7%9A%84CUDA%E7%AC%94%E8%AE%B0%E3%80%91%E4%B8%89%EF%BC%8Creduce%E4%BC%98%E5%8C%96%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/01_interleaved_addressing/images/7257e14ad7e2c51a74b42f0f31a4bffa034546852182c7122fe45672cf4a576e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/01_interleaved_addressing/images/7257e14ad7e2c51a74b42f0f31a4bffa034546852182c7122fe45672cf4a576e.png -------------------------------------------------------------------------------- /docs/09_optimize_reduce/01_interleaved_addressing/images/ae8f73237006381f3a0785df49aa6ccfa553513dbe1037c6a44a49e4d19b7b1d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/01_interleaved_addressing/images/ae8f73237006381f3a0785df49aa6ccfa553513dbe1037c6a44a49e4d19b7b1d.png -------------------------------------------------------------------------------- /docs/09_optimize_reduce/01_interleaved_addressing/images/d6f37bda18827d5a253b26ac3ec49e2755e0249d7c52684e1713196706f0f625.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/01_interleaved_addressing/images/d6f37bda18827d5a253b26ac3ec49e2755e0249d7c52684e1713196706f0f625.png -------------------------------------------------------------------------------- /docs/09_optimize_reduce/01_interleaved_addressing/reduce_interleaved_addressing.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const int len = 32 * 1024 * 1024; 6 | 7 | template 8 | __global__ void reduce_naive_kernel(int *arr, int *out, int len) 9 | { 10 | __shared__ int sdata[BLOCKSIZE]; 11 | int tid = threadIdx.x; // 线程 id (block 内) 12 | int bid = blockIdx.x; // block id (grid 内) 13 | int bdim = blockDim.x; // block 大小 14 | int i = bid * bdim + tid; // 全局 id 15 | 16 | // 将数据拷贝到共享内存 17 | if (i < len) 18 | { 19 | sdata[tid] = arr[i]; 20 | } 21 | 22 | __syncthreads(); // 等待所有线程完成 23 | 24 | // 使用交错寻址 25 | for (int s = 1; s < bdim; s *= 2) 26 | { 27 | int index = 2 * s * tid; 28 | if ((index + s < bdim) && (bdim * bid + s + index < len)) 29 | { 30 | sdata[index] += sdata[index + s]; 31 | } 32 | __syncthreads(); 33 | } 34 | 35 | // 每个 block 的第一个线程将结果写入到 out 中 36 | if (tid == 0) 37 | { 38 | out[bid] = sdata[0]; 39 | } 40 | } 41 | 42 | int main() 43 | { 44 | int *arr = new int[len]; 45 | int *out = new int[len]; 46 | int *d_arr, *d_out; 47 | 48 | // 初始化数组 49 | for (int i = 0; i < len; i++) 50 | { 51 | arr[i] = 1; 52 | } 53 | 54 | // 分配内存 55 | cudaMalloc((void **)&d_arr, sizeof(int) * len); 56 | cudaMalloc((void **)&d_out, sizeof(int) * len); 57 | 58 | // 拷贝数据到显存 59 | cudaMemcpy(d_arr, arr, sizeof(int) * len, cudaMemcpyHostToDevice); 60 | 61 | // 计算 block 和 grid 的大小 62 | const int blocksize = 256; 63 | const int gridsize = (len + blocksize - 1) / blocksize; 64 | 65 | // 调用 kernel 函数 66 | reduce_naive_kernel<<>>(d_arr, d_out, len); 67 | 68 | // 拷贝数据到内存 69 | cudaMemcpy(out, d_out, sizeof(int) * len, cudaMemcpyDeviceToHost); 70 | 71 | // 计算结果 72 | long long sum = 0; 73 | for (int i = 0; i < gridsize; i++) 74 | { 75 | sum += out[i]; 76 | } 77 | printf("sum = %d\n", sum); 78 | 79 | // 核对结果 80 | long long sum2 = 0; 81 | for (int i = 0; i < len; i++) 82 | { 83 | sum2 += arr[i]; 84 | } 85 | 86 | if (sum == sum2) 87 | { 88 | printf("success\n"); 89 | } 90 | else 91 | { 92 | printf("failed, the result is %d\n", sum2); 93 | } 94 | 95 | // 释放内存 96 | cudaFree(d_arr); 97 | cudaFree(d_out); 98 | delete[] arr; 99 | delete[] out; 100 | return 0; 101 | } 102 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/02_bank_conflict/README.md: -------------------------------------------------------------------------------- 1 | # 解决 bank conflict 2 | 3 | 上一篇文章中我们通过交叉寻址的方式解决了 warp divergence 的问题。具体的做法就是让连续的线程尽可能保持一样的行为,这样就不会出现分支判断的情况,从而避免了 warp divergence。但是这种做法也会带来一个新的问题,那就是 bank conflict。本文将会介绍 bank conflict 的原因以及解决方案。 4 | 5 | ## 1. Bank Conflict 6 | 7 | > 为了获得高的内存带宽,共享内存在物理上被分为 32 个同样宽度的、能被同时访问的内存 bank。共享内存中每连续的 128 字节的内容分摊到 32 个 bank 的同一层中。bank 是共享内存的最小单元。 8 | 9 | 同一个 Block 的线程会共享一块共享内存,Bank conflict 是指一个 warp 内的多个线程同时访问同一个 bank 的不同地址,那么它们的访问就会被串行化,从而降低性能。在最坏的情况下,即一个 warp 中的所有线程访问了相同 bank 的 32 个不同地址的话,那么这 32 个访问操作将会全部被序列化,大大降低了内存带宽。在一个 warp 内对同一个 bank 中的 n 个地址同时访问将导致 n 次内存事务,称为发生了 n 路 bank conflict。需要注意的是,不同 warp 中的线程之间不存在 bank conflict。为了避免或减少访存冲突,可以使用一些技巧,如改变数据布局、使用 padding、使用 shuffle 指令等。 10 | 11 | 接下来让我们来分析上一个 Kernel 中的 bank conflict。上一个 Kernel 中,我们使用了交叉寻址的方式,使得连续的线程访问的地址不再连续。这样做的目的是为了避免 warp divergence。但是这样做也会导致 bank conflict。 12 | 13 | 我们以 0 号 warp 为例。在第一次迭代中,0 号线程需要加载 shared memory 的 0 号和 1 号地址,然后写回 0 号地址。同时,0 号 warp 的 16 号线程需要加载 shared memory 的 32 和 33 号地址,并写回 32 号地址。因此,在一个 warp 内同时访问了一个 bank 的不同内存地址,导致发生了 2 路的 Bank Conflict,如下图所示: 14 | 15 | ![picture 1](images/ef322be7c3e5b6b9be69d2b90e88083f50569a58a97129f348e483b946ab4edf.png) 16 | 17 | 类似地,在第二次迭代过程中,0 号 warp 的 0 号线程会加载 0 号和 2 号地址并写回 0 号地址。然后,0 号 warp 的 8 号线程需要加载 shared memory 的 32 号和 34 号地址(4*8=32,32+2=34),并写回 32 号线程。此时,16 号线程会加载 64 号和 66 号地址,24 号线程会加载 96 号和 98 号地址。由于 0 号、32 号、64 号、96 号地址都在一个 bank 中,产生了 4 路的 Bank Conflict。这样以此类推,下一次迭代会产生 8 路的 Bank Conflict,使得整个 Kernel 一直受到 Bank Conflict 的影响。 18 | 19 | 我们可以使用 nvprof 来查看 bank conflict 的情况。 20 | 21 | ```bash 22 | nvprof --events shared_st_bank_conflict ./reduce_interleaved_addressing 23 | ``` 24 | 25 | | Invocations | Event Name | Min | Max | Avg | Total | 26 | | ----------- | ----------------------- | ------- | ------- | ------- | ------- | 27 | | 1 | shared_st_bank_conflict | 4587520 | 4587520 | 4587520 | 4587520 | 28 | 29 | 30 | 如果你的设备不支持 nvprof,你可以使用 nsight-compute 的命令行工具 ncu 来查看 bank conflict 的情况。 31 | 32 | ```bash 33 | sudo ncu --metrics l1tex__data_bank_conflicts_pipe_lsu_mem_shared_op_st.sum ./reduce_interleaved_addressing 34 | ``` 35 | 36 | 37 | ## 2. 解决方案 38 | 39 | 根据上面的分析我们了解到,只要让一个 warp 内的线程不是同一个 bank 的,就可以避免 bank conflict。下面我们来想一想如何让一个 warp 内的不同线程访问不同的 bank。 40 | 41 | 一个 warp 有 32 个线程,bank 也有 32 个。当 `BLOCKSIZE` 是 256 的时候,布局图如下所示: 42 | 43 | ![picture 3](images/7c9ce0996f0a32f29890e52e42291fdd2993502630aa5632c298598604144630.png) 44 | 45 | 每个 Block 里面有一半的线程是需要加载数据的,这些加载数据的线程就可能会发生 bank conflict。我们可以让这些线程访问不同的 bank。前面我们已经说过了为了防止线程束分化,所以这一半加载数据的线程就是 0-127 号线程。这 128 个线程可以分成 4 个 warp,每个 warp 有 32 个线程。我们可以让这 4 个 warp 分别访问 4 个不同的 bank。每个 warp 访问一个 bank。仔细看看上面的布局图,我们一共有 8 行,正好可以分成 4 个 warp,每个 warp 有 2 行。结合交叉寻址的方式,我们可以让每个 warp 访问一个 bank。这样就可以避免 bank conflict 了。我用不同颜色的线表示了不同的 warp,如下图所示: 46 | 47 | ![picture 4](images/e69b477993846936b270e82a37615c00424010cd8003f429354aa27325c96f57.png) 48 | 49 | 0 号 warp 的 0 号线程访问 0 号 bank 的 0 号地址和 128 号地址,1 号 warp 的 0 号线程访问 1 号 bank 的 32 号地址和 128 号地址,2 号 warp 的 0 号线程访问 2 号 bank 的 64 号地址和 160 号地址,以此类推。 50 | 51 | 整体过程如下图所示: 52 | 53 | ![picture 6](images/0f65c7d9e911014e31ddd84c583dea859ba24ebd48715c2680eb604e7ebb9a2b.png) 54 | 55 | > [!NOTE] 56 | > 图里面的 block size 是 16 而不是 256,这是为了方便说明。实际上,我们的 block size 是 256。 57 | 58 | 了解了意图之后,我们就可以开始编写代码了。代码的改动也不对,主要是把循环迭代的顺序修改一下,使得每个 warp 访问一个 bank。具体的代码如下所示: 59 | 60 | ```cpp 61 | // 修改之前 62 | for (int s = 1; s < bdim; s *= 2) 63 | { 64 | int index = 2 * s * tid; 65 | if ((index + s < bdim) && (bdim * bid + s < len)) 66 | { 67 | sdata[index] += sdata[index + s]; 68 | } 69 | } 70 | 71 | // 修改之后 72 | for(int s=blockDim.x/2; s>0; s >>= 1) { 73 | if (tid < s){ 74 | sdata[tid] += sdata[tid + s]; 75 | } 76 | __syncthreads(); 77 | } 78 | ``` 79 | 80 | 修改后我们可以再次运行 nvprof 来查看 bank conflict 的情况,输出如下所示: 81 | 82 | | Invocations | Event Name | Min | Max | Avg | Total | 83 | | ----------- | ----------------------- | --- | --- | --- | ----- | 84 | | 1 | shared_st_bank_conflict | 0 | 0 | 0 | 0 | 85 | 86 | 性能和带宽的测试情况如下: 87 | 88 | | 优化手段 | 运行时间(us) | 带宽 | 加速比 | 89 | | ------------------ | ------------ | ---------- | ------ | 90 | | Baseline | 3118.4 | 42.503GB/s | ~ | 91 | | 交错寻址 | 1904.4 | 73.522GB/s | 1.64 | 92 | | 解决 bank conflict | 1475.2 | 97.536GB/s | 2.29 | 93 | 94 | 可以看到,解决 bank conflict 之后,性能和带宽都有了很大的提升。 95 | 96 | 本文代码可以按如下方式编译运行: 97 | 98 | ```bash 99 | nvcc -o reduce_bank_conflict_free reduce_bank_conflict_free.cu 100 | ``` 101 | 102 | ## Reference 103 | 104 | - [https://developer.nvidia.com/blog/using-shared-memory-cuda-cc/](https://developer.nvidia.com/blog/using-shared-memory-cuda-cc/) 105 | - [http://giantpandacv.com/project/OneFlow/%E3%80%90BBuf%E7%9A%84CUDA%E7%AC%94%E8%AE%B0%E3%80%91%E4%B8%89%EF%BC%8Creduce%E4%BC%98%E5%8C%96%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/#2-bank-conflict](http://giantpandacv.com/project/OneFlow/%E3%80%90BBuf%E7%9A%84CUDA%E7%AC%94%E8%AE%B0%E3%80%91%E4%B8%89%EF%BC%8Creduce%E4%BC%98%E5%8C%96%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/#2-bank-conflict) 106 | - [https://github.com/BBuf/how-to-optim-algorithm-in-cuda](https://github.com/BBuf/how-to-optim-algorithm-in-cuda) 107 | 108 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/02_bank_conflict/images/0f65c7d9e911014e31ddd84c583dea859ba24ebd48715c2680eb604e7ebb9a2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/02_bank_conflict/images/0f65c7d9e911014e31ddd84c583dea859ba24ebd48715c2680eb604e7ebb9a2b.png -------------------------------------------------------------------------------- /docs/09_optimize_reduce/02_bank_conflict/images/7c9ce0996f0a32f29890e52e42291fdd2993502630aa5632c298598604144630.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/02_bank_conflict/images/7c9ce0996f0a32f29890e52e42291fdd2993502630aa5632c298598604144630.png -------------------------------------------------------------------------------- /docs/09_optimize_reduce/02_bank_conflict/images/e69b477993846936b270e82a37615c00424010cd8003f429354aa27325c96f57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/02_bank_conflict/images/e69b477993846936b270e82a37615c00424010cd8003f429354aa27325c96f57.png -------------------------------------------------------------------------------- /docs/09_optimize_reduce/02_bank_conflict/images/ef322be7c3e5b6b9be69d2b90e88083f50569a58a97129f348e483b946ab4edf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/02_bank_conflict/images/ef322be7c3e5b6b9be69d2b90e88083f50569a58a97129f348e483b946ab4edf.png -------------------------------------------------------------------------------- /docs/09_optimize_reduce/02_bank_conflict/reduce_bank_conflict_free.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const int len = 32 * 1024 * 1024; 6 | 7 | template 8 | __global__ void reduce_bank_conflict_free_kernel(int *arr, int *out, int len) 9 | { 10 | __shared__ int sdata[BLOCKSIZE]; 11 | int tid = threadIdx.x; // 线程 id (block 内) 12 | int bid = blockIdx.x; // block id (grid 内) 13 | int bdim = blockDim.x; // block 大小 14 | int i = bid * bdim + tid; // 全局 id 15 | 16 | // 将数据拷贝到共享内存 17 | if (i < len) 18 | { 19 | sdata[tid] = arr[i]; 20 | } 21 | 22 | __syncthreads(); // 等待所有线程完成 23 | 24 | for (int s = blockDim.x / 2; s > 0; s >>= 1) 25 | { 26 | if (tid < s) 27 | { 28 | sdata[tid] += sdata[tid + s]; 29 | } 30 | __syncthreads(); 31 | } 32 | 33 | // 每个 block 的第一个线程将结果写入到 out 中 34 | if (tid == 0) 35 | { 36 | out[bid] = sdata[0]; 37 | } 38 | } 39 | 40 | int main() 41 | { 42 | int *arr = new int[len]; 43 | int *out = new int[len]; 44 | int *d_arr, *d_out; 45 | 46 | // 初始化数组 47 | for (int i = 0; i < len; i++) 48 | { 49 | arr[i] = 1; 50 | } 51 | 52 | // 分配内存 53 | cudaMalloc((void **)&d_arr, sizeof(int) * len); 54 | cudaMalloc((void **)&d_out, sizeof(int) * len); 55 | 56 | // 拷贝数据到显存 57 | cudaMemcpy(d_arr, arr, sizeof(int) * len, cudaMemcpyHostToDevice); 58 | 59 | // 计算 block 和 grid 的大小 60 | const int blocksize = 256; 61 | const int gridsize = (len + blocksize - 1) / blocksize; 62 | 63 | // 调用 kernel 函数 64 | reduce_bank_conflict_free_kernel<<>>(d_arr, d_out, len); 65 | 66 | // 拷贝数据到内存 67 | cudaMemcpy(out, d_out, sizeof(int) * len, cudaMemcpyDeviceToHost); 68 | 69 | // 计算结果 70 | long long sum = 0; 71 | for (int i = 0; i < gridsize; i++) 72 | { 73 | sum += out[i]; 74 | } 75 | printf("sum = %d\n", sum); 76 | 77 | // 核对结果 78 | long long sum2 = 0; 79 | for (int i = 0; i < len; i++) 80 | { 81 | sum2 += arr[i]; 82 | } 83 | 84 | if (sum == sum2) 85 | { 86 | printf("success\n"); 87 | } 88 | else 89 | { 90 | printf("failed, the result is %d\n", sum2); 91 | } 92 | 93 | // 释放内存 94 | cudaFree(d_arr); 95 | cudaFree(d_out); 96 | delete[] arr; 97 | delete[] out; 98 | return 0; 99 | } -------------------------------------------------------------------------------- /docs/09_optimize_reduce/03_idle_threads_free/README.md: -------------------------------------------------------------------------------- 1 | # IDLE 线程 2 | 3 | 4 | ## 1. 问题分析 5 | 6 | 在上一个 Kernel 有大量的空闲线程。在 BLOCKSIZE 为 256 的配置下,第一轮迭代只有 128 个线程是在工作的,第二轮迭代只有 64 个线程是在工作的,第三轮迭代只有 32 个线程是在工作的。 7 | 8 | 这些空闲线程会浪费大量的计算资源,因为 GPU 会为这些线程分配资源,但是这些线程并没有工作要做。所以我们就可以让这些闲置的线程也做一些工作,这样就可以提高 GPU 的利用率。 9 | 10 | ## 2. 优化方案 11 | 12 | 13 | 为了让空闲的线程也做一些工作,我们可以让一个线程干俩个线程的活。 14 | 原本我们在把数据加载到共享内存的时候,我们只加载一个数据,我们现在可以在加载的时候顺便做一次加法计算。 15 | 也就是说,现在我们的一个 BLOCK 干了俩个 BLOCK 的活,所以我们的 BLOCK 数量就可以减半了(原来一个 BLOCK 是负责 256 个元素现在是负责 512 个了)。 16 | 17 | 下图可以帮助我们更好的理解这个优化方案: 18 | 19 | ![images/1-idle优化原理](images/1-idle优化原理.jpg) 20 | 21 | 我们需要修改的代码如下: 22 | 23 | ```cpp 24 | // 修改前 25 | __shared__ int sdata[BLOCKSIZE]; 26 | int tid = threadIdx.x; // 线程 id (block 内) 27 | int bid = blockIdx.x; // block id (grid 内) 28 | int bdim = blockDim.x; // block 大小 29 | int i = bid * bdim + tid; // 全局 id 30 | 31 | // 将数据拷贝到共享内存 32 | if (i < len) 33 | { 34 | sdata[tid] = arr[i]; 35 | } 36 | 37 | // 修改后 38 | __shared__ int sdata[BLOCKSIZE]; 39 | int tid = threadIdx.x; // 线程 id (block 内) 40 | int bid = blockIdx.x; // block id (grid 内) 41 | int bdim = blockDim.x; // block 大小 42 | // 注意这里是 bdim * 2 因为我们要让一个线程干俩个线程的活 43 | int i = bid * bdim * 2 + tid; // 全局 id 44 | 45 | // 将数据拷贝到共享内存 46 | if (i < len) 47 | { 48 | sdata[tid] = arr[i] + arr[i + bdim]; 49 | } 50 | ``` 51 | 52 | 完整代码在 reduce_idle_threads_free.cu 中,编译代码如下: 53 | 54 | ```bash 55 | nvcc reduce_idle_threads_free.cu -o reduce_idle_threads_free 56 | ``` 57 | 58 | 下面让我们对比一下优化后的 Kernel 和前几个 Kernel 的性能: 59 | 60 | | 优化手段 | 运行时间(us) | 带宽 | 加速比 | 61 | | --- | --- | --- | --- | 62 | | Baseline | 3118.4 | 42.503GB/s | ~ | 63 | | 交错寻址 | 1904.4 | 73.522GB/s | 1.64 | 64 | | 解决 bank conflict | 1475.2 | 97.536GB/s | 2.29 | 65 | | 去除 idle 线程 | 758.38 | 189.78GB/s | 4.11 | 66 | 67 | ## 3. 总结 68 | 69 | 通过本文的实践,我们学习到了如何通过减少空闲线程来提高 GPU 的利用率。 70 | 当程序中有大量的空闲线程时,我们可以让空闲的线程也做一些工作,也可以减少线程的数量。 71 | 下一篇文章中我们会通过展开 warp 来进一步提高 Kernel 的性能。 72 | 73 | ## Reference 74 | 75 | 1. https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf 76 | 2. http://www.giantpandacv.com/project/OneFlow/%E3%80%90BBuf%E7%9A%84CUDA%E7%AC%94%E8%AE%B0%E3%80%91%E4%B8%89%EF%BC%8Creduce%E4%BC%98%E5%8C%96%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ 77 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/03_idle_threads_free/images/1-idle优化原理.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/09_optimize_reduce/03_idle_threads_free/images/1-idle优化原理.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/09_optimize_reduce/03_idle_threads_free/images/1-idle优化原理.jpg -------------------------------------------------------------------------------- /docs/09_optimize_reduce/03_idle_threads_free/reduce_idle_threads_free.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const int len = 32 * 1024 * 1024; 6 | 7 | template 8 | __global__ void reduce_idle_thread_free(int *arr, int *out, int len) 9 | { 10 | __shared__ int sdata[BLOCKSIZE]; 11 | int tid = threadIdx.x; // 线程 id (block 内) 12 | int bid = blockIdx.x; // block id (grid 内) 13 | int bdim = blockDim.x; // block 大小 14 | int i = bid * bdim * 2 + tid; // 全局 id 15 | 16 | // 将数据拷贝到共享内存 17 | if (i < len) 18 | { 19 | sdata[tid] = arr[i] + arr[i + bdim]; 20 | } 21 | 22 | __syncthreads(); // 等待所有线程完成 23 | 24 | for (int s = blockDim.x / 2; s > 0; s >>= 1) 25 | { 26 | if (tid < s) 27 | { 28 | sdata[tid] += sdata[tid + s]; 29 | } 30 | __syncthreads(); 31 | } 32 | 33 | // 每个 block 的第一个线程将结果写入到 out 中 34 | if (tid == 0) 35 | { 36 | out[bid] = sdata[0]; 37 | } 38 | } 39 | 40 | int main() 41 | { 42 | int *arr = new int[len]; 43 | int *out = new int[len]; 44 | int *d_arr, *d_out; 45 | 46 | // 初始化数组 47 | for (int i = 0; i < len; i++) 48 | { 49 | arr[i] = 1; 50 | } 51 | 52 | // 分配内存 53 | cudaMalloc((void **)&d_arr, sizeof(int) * len); 54 | cudaMalloc((void **)&d_out, sizeof(int) * len); 55 | 56 | // 拷贝数据到显存 57 | cudaMemcpy(d_arr, arr, sizeof(int) * len, cudaMemcpyHostToDevice); 58 | 59 | // 计算 block 和 grid 的大小 60 | const int blocksize = 256; 61 | const int gridsize = (len + blocksize - 1) / (blocksize * 2); 62 | 63 | // 调用 kernel 函数 64 | reduce_idle_thread_free<<>>(d_arr, d_out, len); 65 | 66 | // 拷贝数据到内存 67 | cudaMemcpy(out, d_out, sizeof(int) * len, cudaMemcpyDeviceToHost); 68 | 69 | // 计算结果 70 | long long sum = 0; 71 | for (int i = 0; i < gridsize; i++) 72 | { 73 | sum += out[i]; 74 | } 75 | printf("sum = %d\n", sum); 76 | 77 | // 核对结果 78 | long long sum2 = 0; 79 | for (int i = 0; i < len; i++) 80 | { 81 | sum2 += arr[i]; 82 | } 83 | 84 | if (sum == sum2) 85 | { 86 | printf("success\n"); 87 | } 88 | else 89 | { 90 | printf("failed, the result is %d\n", sum2); 91 | } 92 | 93 | // 释放内存 94 | cudaFree(d_arr); 95 | cudaFree(d_out); 96 | delete[] arr; 97 | delete[] out; 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/04_unroll/README.md: -------------------------------------------------------------------------------- 1 | # 展开 Warp 2 | 3 | 现在我们已经使用了 3 种方法对 Reduce Kernel 进行优化 (交错寻址、去除 Bank Conflilt、减少空闲线程)。 4 | 当下实现的 Kernel 距离理论带宽还有一定距离,我们可以继续优化。Reduce 并不是一个算术密集型的 Kernel。 5 | 对于这种 Kernel,一个可能的瓶颈就是地址算术指令和循环的开销。 6 | 7 | :::note 8 | 9 | 什么是算术密集型? 10 | 11 | 算术密集型任务强调的是涉及大量的算术运算,其中包括加法、减法、乘法、除法等基本的数学运算。这类任务通常不涉及复杂的控制流程或数据访问模式,而是侧重于数值计算。图像处理、信号处理和许多科学计算问题都可能属于算术密集型任务。 12 | 13 | ::: 14 | 15 | ## 1. 问题分析 16 | 17 | 18 | 在上一个 Kernel 中有如下循环: 19 | 20 | ```cpp 21 | for (int s = blockDim.x / 2; s > 0; s >>= 1) 22 | { 23 | if (tid < s) 24 | { 25 | sdata[tid] += sdata[tid + s]; 26 | } 27 | __syncthreads(); 28 | } 29 | ``` 30 | 31 | 每一次循环都会进行一个 BLOCK 中线程的同步。但是实际上当 `s <= 32` 的时候,由于 `tid <= s` 所以我们只用到了一个 Warp 的线程。由于 cuda 是单指令多线程的设计,所以同一个 Warp 中的线程都是并行执行的。所以最后一个 Warp 在同一个 simd 单元上的这些线程本来就是同步的,所以这个 `__syncthreads()` 同步就是没有必要的了。 32 | 33 | 34 | ## 2. 优化方案 35 | 36 | ### 2.1. 展开最后一个 Warp 37 | 38 | 根据前面的分析,我们可以对最后一个 Warp 进行展开,这样就可以减少同步的次数。 39 | 40 | ```cpp 41 | __device__ void warp_reduce(volatile int *sdata, int tid) 42 | { 43 | sdata[tid] += sdata[tid + 32]; 44 | sdata[tid] += sdata[tid + 16]; 45 | sdata[tid] += sdata[tid + 8]; 46 | sdata[tid] += sdata[tid + 4]; 47 | sdata[tid] += sdata[tid + 2]; 48 | sdata[tid] += sdata[tid + 1]; 49 | } 50 | ``` 51 | 52 | 注意这里的 `sdata` 是 `volatile` 的,这样可以防止编译器对这些变量进行优化。 53 | 54 | :::tip 55 | 56 | 被 `volatile` 修饰的变量,每次访问都会从内存中读取,而不是从寄存器中读取。这样可以防止编译器对这些变量进行优化。 57 | 如果不加 `volatile` 修饰符,编译器会认为这些变量的值不会变化,所以会将这些变量的值缓存在寄存器中。 58 | 这样可能导致读到的值不是最新的值。 59 | 60 | ::: 61 | 62 | 下面我们就可以对上面的循环进行修改了: 63 | 64 | ```cpp 65 | for (int s = blockDim.x / 2; s > 32; s >>= 1) 66 | { 67 | if (tid < s) 68 | { 69 | sdata[tid] += sdata[tid + s]; 70 | } 71 | __syncthreads(); 72 | } 73 | 74 | if (tid < 32) 75 | { 76 | warp_reduce(sdata, tid); 77 | } 78 | ``` 79 | 80 | 编译运行命令如下: 81 | 82 | ```bash 83 | nvcc -o reduce_unroll_last_warp reduce_unroll_last_warp.cu 84 | ``` 85 | 86 | 对上面的 Kernel 进行性能分析结果如下: 87 | 88 | 89 | | 优化手段 | 运行时间(us) | 带宽 | 加速比 | 90 | | ------------------ | ------------ | ---------- | ------ | 91 | | Baseline | 3118.4 | 42.503GB/s | ~ | 92 | | 交错寻址 | 1904.4 | 73.522GB/s | 1.64 | 93 | | 解决 bank conflict | 1475.2 | 97.536GB/s | 2.29 | 94 | | 去除 idle 线程 | 758.38 | 189.78GB/s | 4.11 | 95 | | 展开最后一个 Warp | 484.01 | 287.25GB/s | 6.44 | 96 | 97 | ### 2.2. 完全展开 98 | 99 | 如果你想追求极致的性能优化,我们可以对 for 循环进行完全展开,这样就可以减少循环的开销。 100 | 同时我们可以写一个更加通用的 `warp_reduce` 函数以适用于不同的 BLOCKSIZE。 101 | 102 | ```cpp 103 | template 104 | __device__ void warp_reduce(volatile int *sdata, int tid) 105 | { 106 | if (BLOCKSIZE >= 64) 107 | { 108 | sdata[tid] += sdata[tid + 32]; 109 | } 110 | if (BLOCKSIZE >= 32) 111 | { 112 | sdata[tid] += sdata[tid + 16]; 113 | } 114 | if (BLOCKSIZE >= 16) 115 | { 116 | sdata[tid] += sdata[tid + 8]; 117 | } 118 | if (BLOCKSIZE >= 8) 119 | { 120 | sdata[tid] += sdata[tid + 4]; 121 | } 122 | if (BLOCKSIZE >= 4) 123 | { 124 | sdata[tid] += sdata[tid + 2]; 125 | } 126 | if (BLOCKSIZE >= 2) 127 | { 128 | sdata[tid] += sdata[tid + 1]; 129 | } 130 | } 131 | ``` 132 | 133 | 下面我们就可以对上面的循环进行修改了: 134 | 135 | ```cpp 136 | if (BLOCKSIZE >= 512) 137 | { 138 | if (tid < 256) 139 | { 140 | sdata[tid] += sdata[tid + 256]; 141 | } 142 | __syncthreads(); 143 | } 144 | if (BLOCKSIZE >= 256) 145 | { 146 | if (tid < 128) 147 | { 148 | sdata[tid] += sdata[tid + 128]; 149 | } 150 | __syncthreads(); 151 | } 152 | if (BLOCKSIZE >= 128) 153 | { 154 | if (tid < 64) 155 | { 156 | sdata[tid] += sdata[tid + 64]; 157 | } 158 | __syncthreads(); 159 | } 160 | 161 | if (tid < 32) 162 | { 163 | warp_reduce(sdata, tid); 164 | } 165 | ``` 166 | 167 | 编译运行命令如下: 168 | 169 | ```bash 170 | nvcc -o reduce_unroll_all reduce_unroll_all.cu 171 | ``` 172 | 173 | 对上面的 Kernel 进行性能分析结果如下: 174 | 175 | | 优化手段 | 运行时间(us) | 带宽(GB/s) | 加速比 | 176 | | ------------------ | ------------ | ---------- | ------ | 177 | | Baseline | 3118.4 | 42.503 | ~ | 178 | | 交错寻址 | 1904.4 | 73.522 | 1.64 | 179 | | 解决 bank conflict | 1475.2 | 97.536 | 2.29 | 180 | | 去除 idle 线程 | 758.38 | 189.78 | 4.11 | 181 | | 展开最后一个 Warp | 484.01 | 287.25 | 6.44 | 182 | | 完全展开 | 477.23 | 291.77 | 6.53 | 183 | 184 | 185 | ## 3. 总结 186 | 187 | 在这一节中,我们对 Reduce Kernel 进行了展开 Warp 的优化。 188 | 以后我们再写 Kernel 的时候,如果发现有循环的话,可以考虑对循环进行展开,这样可以减少循环的开销。 189 | 同时我们也可以考虑有没有不必要的同步,这样可以减少同步的次数,从而提高性能。 190 | 191 | ## Reference 192 | 193 | 1. https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf 194 | 2. http://www.giantpandacv.com/project/OneFlow/%E3%80%90BBuf%E7%9A%84CUDA%E7%AC%94%E8%AE%B0%E3%80%91%E4%B8%89%EF%BC%8Creduce%E4%BC%98%E5%8C%96%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ 195 | 196 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/04_unroll/reduce_unroll_all.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const int len = 32 * 1024 * 1024; 6 | 7 | template 8 | __device__ void warp_reduce(volatile int *sdata, int tid) 9 | { 10 | if (BLOCKSIZE >= 64) 11 | { 12 | sdata[tid] += sdata[tid + 32]; 13 | } 14 | if (BLOCKSIZE >= 32) 15 | { 16 | sdata[tid] += sdata[tid + 16]; 17 | } 18 | if (BLOCKSIZE >= 16) 19 | { 20 | sdata[tid] += sdata[tid + 8]; 21 | } 22 | if (BLOCKSIZE >= 8) 23 | { 24 | sdata[tid] += sdata[tid + 4]; 25 | } 26 | if (BLOCKSIZE >= 4) 27 | { 28 | sdata[tid] += sdata[tid + 2]; 29 | } 30 | if (BLOCKSIZE >= 2) 31 | { 32 | sdata[tid] += sdata[tid + 1]; 33 | } 34 | } 35 | 36 | template 37 | __global__ void reduce_unroll_all(int *arr, int *out, int len) 38 | { 39 | __shared__ int sdata[BLOCKSIZE]; 40 | int tid = threadIdx.x; // 线程 id (block 内) 41 | int bid = blockIdx.x; // block id (grid 内) 42 | int bdim = blockDim.x; // block 大小 43 | int i = bid * bdim * 2 + tid; // 全局 id 44 | 45 | // 将数据拷贝到共享内存 46 | if (i < len) 47 | { 48 | sdata[tid] = arr[i] + arr[i + bdim]; 49 | } 50 | 51 | __syncthreads(); // 等待所有线程完成 52 | 53 | if (BLOCKSIZE >= 512) 54 | { 55 | if (tid < 256) 56 | { 57 | sdata[tid] += sdata[tid + 256]; 58 | } 59 | __syncthreads(); 60 | } 61 | if (BLOCKSIZE >= 256) 62 | { 63 | if (tid < 128) 64 | { 65 | sdata[tid] += sdata[tid + 128]; 66 | } 67 | __syncthreads(); 68 | } 69 | if (BLOCKSIZE >= 128) 70 | { 71 | if (tid < 64) 72 | { 73 | sdata[tid] += sdata[tid + 64]; 74 | } 75 | __syncthreads(); 76 | } 77 | 78 | if (tid < 32) 79 | { 80 | warp_reduce(sdata, tid); 81 | } 82 | 83 | // 每个 block 的第一个线程将结果写入到 out 中 84 | if (tid == 0) 85 | { 86 | out[bid] = sdata[0]; 87 | } 88 | } 89 | 90 | int main() 91 | { 92 | int *arr = new int[len]; 93 | int *out = new int[len]; 94 | int *d_arr, *d_out; 95 | 96 | // 初始化数组 97 | for (int i = 0; i < len; i++) 98 | { 99 | arr[i] = 1; 100 | } 101 | 102 | // 分配内存 103 | cudaMalloc((void **)&d_arr, sizeof(int) * len); 104 | cudaMalloc((void **)&d_out, sizeof(int) * len); 105 | 106 | // 拷贝数据到显存 107 | cudaMemcpy(d_arr, arr, sizeof(int) * len, cudaMemcpyHostToDevice); 108 | 109 | // 计算 block 和 grid 的大小 110 | const int blocksize = 256; 111 | const int gridsize = (len + blocksize - 1) / (blocksize * 2); 112 | 113 | // 调用 kernel 函数 114 | reduce_unroll_all<<>>(d_arr, d_out, len); 115 | 116 | // 拷贝数据到内存 117 | cudaMemcpy(out, d_out, sizeof(int) * len, cudaMemcpyDeviceToHost); 118 | 119 | // 计算结果 120 | long long sum = 0; 121 | for (int i = 0; i < gridsize; i++) 122 | { 123 | sum += out[i]; 124 | } 125 | printf("sum = %d\n", sum); 126 | 127 | // 核对结果 128 | long long sum2 = 0; 129 | for (int i = 0; i < len; i++) 130 | { 131 | sum2 += arr[i]; 132 | } 133 | 134 | if (sum == sum2) 135 | { 136 | printf("success\n"); 137 | } 138 | else 139 | { 140 | printf("failed, the result is %d\n", sum2); 141 | } 142 | 143 | // 释放内存 144 | cudaFree(d_arr); 145 | cudaFree(d_out); 146 | delete[] arr; 147 | delete[] out; 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/04_unroll/reduce_unroll_last_warp.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | const int len = 32 * 1024 * 1024; 6 | 7 | __device__ void warp_reduce(volatile int *sdata, int tid) 8 | { 9 | sdata[tid] += sdata[tid + 32]; 10 | sdata[tid] += sdata[tid + 16]; 11 | sdata[tid] += sdata[tid + 8]; 12 | sdata[tid] += sdata[tid + 4]; 13 | sdata[tid] += sdata[tid + 2]; 14 | sdata[tid] += sdata[tid + 1]; 15 | } 16 | 17 | template 18 | __global__ void reduce_unroll_last_warp(int *arr, int *out, int len) 19 | { 20 | __shared__ int sdata[BLOCKSIZE]; 21 | int tid = threadIdx.x; // 线程 id (block 内) 22 | int bid = blockIdx.x; // block id (grid 内) 23 | int bdim = blockDim.x; // block 大小 24 | int i = bid * bdim * 2 + tid; // 全局 id 25 | 26 | // 将数据拷贝到共享内存 27 | if (i < len) 28 | { 29 | sdata[tid] = arr[i] + arr[i + bdim]; 30 | } 31 | 32 | __syncthreads(); // 等待所有线程完成 33 | 34 | for (int s = blockDim.x / 2; s > 32; s >>= 1) 35 | { 36 | if (tid < s) 37 | { 38 | sdata[tid] += sdata[tid + s]; 39 | } 40 | __syncthreads(); 41 | } 42 | 43 | if (tid < 32) 44 | { 45 | warp_reduce(sdata, tid); 46 | } 47 | 48 | // 每个 block 的第一个线程将结果写入到 out 中 49 | if (tid == 0) 50 | { 51 | out[bid] = sdata[0]; 52 | } 53 | } 54 | 55 | int main() 56 | { 57 | int *arr = new int[len]; 58 | int *out = new int[len]; 59 | int *d_arr, *d_out; 60 | 61 | // 初始化数组 62 | for (int i = 0; i < len; i++) 63 | { 64 | arr[i] = 1; 65 | } 66 | 67 | // 分配内存 68 | cudaMalloc((void **)&d_arr, sizeof(int) * len); 69 | cudaMalloc((void **)&d_out, sizeof(int) * len); 70 | 71 | // 拷贝数据到显存 72 | cudaMemcpy(d_arr, arr, sizeof(int) * len, cudaMemcpyHostToDevice); 73 | 74 | // 计算 block 和 grid 的大小 75 | const int blocksize = 256; 76 | const int gridsize = (len + blocksize * 2 - 1) / (blocksize * 2); 77 | 78 | // 调用 kernel 函数 79 | reduce_unroll_last_warp<<>>(d_arr, d_out, len); 80 | 81 | // 拷贝数据到内存 82 | cudaMemcpy(out, d_out, sizeof(int) * len, cudaMemcpyDeviceToHost); 83 | 84 | // 计算结果 85 | long long sum = 0; 86 | for (int i = 0; i < gridsize; i++) 87 | { 88 | sum += out[i]; 89 | } 90 | printf("sum = %d\n", sum); 91 | 92 | // 核对结果 93 | long long sum2 = 0; 94 | for (int i = 0; i < len; i++) 95 | { 96 | sum2 += arr[i]; 97 | } 98 | 99 | if (sum == sum2) 100 | { 101 | printf("success\n"); 102 | } 103 | else 104 | { 105 | printf("failed, the result is %d\n", sum2); 106 | } 107 | 108 | // 释放内存 109 | cudaFree(d_arr); 110 | cudaFree(d_out); 111 | delete[] arr; 112 | delete[] out; 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /docs/09_optimize_reduce/README.md: -------------------------------------------------------------------------------- 1 | # Reduce 性能优化实践 2 | 3 | 上一篇文章中,我们手写了一个简单的 Reduce 算法,但是性能并不是很好,这一章中我们将会逐步优化这个算法。 4 | 5 | + 交叉寻址 6 | + 解决 Bank Conflict 7 | + 解决空闲线程 8 | + 展开最后一个 warp -------------------------------------------------------------------------------- /docs/10_what_my_id/my_id.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | __global__ void what_is_my_id(unsigned int * block, 5 | unsigned int * thread, 6 | unsigned int * warp, 7 | unsigned int * calc_thread ){ 8 | const unsigned int thread_idx = blockIdx.x * blockDim.x + threadIdx.x; 9 | block[thread_idx] = blockIdx.x; 10 | thread[thread_idx] = threadIdx.x; 11 | 12 | warp[thread_idx] = threadIdx.x / warpsize; 13 | calc_thread[thread_idx] = thread_idx; 14 | } 15 | 16 | #define ARRAY_SIZE 128 17 | #define ARRAY_SIZE_IN_BYTES (sizeof(unsigned int) * ARRAY_SIZE) 18 | 19 | unsigned int cpu_block[ARRAY_SIZE]; 20 | unsigned int cpu_thread[ARRAY_SIZE]; 21 | unsigned int cpu_warp[ARRAY_SIZE]; 22 | unsigned int cpu_calc_thread[ARRAY_SIZE]; 23 | 24 | int main(){ 25 | const unsigned int num_blocks = 2; 26 | const unsigned int num_threads = 64; 27 | const unsigned int warp_size = 32; 28 | 29 | unsigned int * gpu_block; 30 | unsigned int * gpu_thread; 31 | unsigned int * gpu_warp; 32 | unsigned int * gpu_calc_thread; 33 | 34 | unsigned int i; 35 | 36 | cudaMalloc((void**)&gpu_block, ARRAY_SIZE_IN_BYTES); 37 | cudaMalloc((void**)&gpu_thread, ARRAY_SIZE_IN_BYTES); 38 | cudaMalloc((void**)&gpu_warp, ARRAY_SIZE_IN_BYTES); 39 | cudaMalloc((void**)&gpu_calc_thread, ARRAY_SIZE_IN_BYTES); 40 | 41 | what_is_my_id<<>>(gpu_block, gpu_thread, gpu_warp, gpu_calc_thread); 42 | 43 | cudaMemcpy(cpu_block, gpu_block, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 44 | cudaMemcpy(cpu_thread, gpu_thread, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 45 | cudaMemcpy(cpu_warp, gpu_warp, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 46 | cudaMemcpy(cpu_calc_thread, gpu_calc_thread, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 47 | 48 | cudaFree(gpu_block); 49 | cudaFree(gpu_thread); 50 | cudaFree(gpu_warp); 51 | cudaFree(gpu_calc_thread); 52 | 53 | for(i=0; i< ARRAY_SIZE; i++){ 54 | printf("cac_thread %3u - block %2u - warp %3u - thread %2u\n", cpu_calc_thread[i], cpu_block[i], cpu_warp[i], cpu_thread[i]); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /docs/10_what_my_id/my_id_dim2.cu: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | __global__ void what_is_my_id_2d(unsigned int * griddim_x, unsigned int * griddim_y, 4 | unsigned int * blockdim_x, unsigned int * blockdim_y, 5 | unsigned int * calc_thread, 6 | unsigned int * blockidx_x, unsigned int * blockidx_y, 7 | unsigned int * threadidx_x, unsigned int * threadidx_y, 8 | unsigned int * thread_x, unsigned int * thread_y 9 | ){ 10 | 11 | const unsigned int idx = blockIdx.x * blockDim.x + threadIdx.x; 12 | const unsigned int idy = blockIdx.y * blockDim.y + threadIdx.y; 13 | const unsigned int thread_idx = ((gridDim.x * blockDim.x) * idy) + idx ; 14 | 15 | griddim_x[thread_idx] = gridDim.x; 16 | griddim_y[thread_idx] = gridDim.y; 17 | blockdim_x[thread_idx] = blockDim.x; 18 | blockdim_y[thread_idx] = blockDim.y; 19 | calc_thread[thread_idx] = thread_idx; 20 | 21 | blockidx_x[thread_idx] = blockIdx.x; 22 | blockidx_y[thread_idx] = blockIdx.y; 23 | threadidx_x[thread_idx] = threadIdx.x; 24 | threadidx_y[thread_idx] = threadIdx.y; 25 | thread_x[thread_idx] = idx; 26 | thread_y[thread_idx] = idy; 27 | 28 | 29 | } 30 | 31 | #define ARRAY_SIZE_X 32 32 | #define ARRAY_SIZE_Y 16 33 | 34 | #define ARRAY_SIZE_IN_BYTES (sizeof(unsigned int) * ARRAY_SIZE_X * ARRAY_SIZE_Y) 35 | 36 | unsigned int cpu_griddim_x[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 37 | unsigned int cpu_griddim_y[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 38 | unsigned int cpu_blockdim_x[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 39 | unsigned int cpu_blockdim_y[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 40 | unsigned int cpu_calc_thread[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 41 | unsigned int cpu_blockidx_x[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 42 | unsigned int cpu_blockidx_y[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 43 | unsigned int cpu_threadidx_x[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 44 | unsigned int cpu_threadidx_y[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 45 | unsigned int cpu_thread_x[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 46 | unsigned int cpu_thread_y[ARRAY_SIZE_Y][ARRAY_SIZE_X]; 47 | 48 | 49 | int main(){ 50 | const dim3 num_blocks(1, 4); 51 | const dim3 num_threads(32, 4); 52 | 53 | unsigned int * gpu_griddim_x; 54 | unsigned int * gpu_griddim_y; 55 | unsigned int * gpu_blockdim_x; 56 | unsigned int * gpu_blockdim_y; 57 | unsigned int * gpu_calc_thread; 58 | unsigned int * gpu_blockidx_x; 59 | unsigned int * gpu_blockidx_y; 60 | unsigned int * gpu_threadidx_x; 61 | unsigned int * gpu_threadidx_y; 62 | unsigned int * gpu_thread_x; 63 | unsigned int * gpu_thread_y; 64 | 65 | 66 | cudaMalloc((void**)&gpu_griddim_x, ARRAY_SIZE_IN_BYTES); 67 | cudaMalloc((void**)&gpu_griddim_y, ARRAY_SIZE_IN_BYTES); 68 | cudaMalloc((void**)&gpu_blockdim_x, ARRAY_SIZE_IN_BYTES); 69 | cudaMalloc((void**)&gpu_blockdim_y, ARRAY_SIZE_IN_BYTES); 70 | cudaMalloc((void**)&gpu_calc_thread, ARRAY_SIZE_IN_BYTES); 71 | cudaMalloc((void**)&gpu_blockidx_x, ARRAY_SIZE_IN_BYTES); 72 | cudaMalloc((void**)&gpu_blockidx_y, ARRAY_SIZE_IN_BYTES); 73 | cudaMalloc((void**)&gpu_threadidx_x, ARRAY_SIZE_IN_BYTES); 74 | cudaMalloc((void**)&gpu_threadidx_y, ARRAY_SIZE_IN_BYTES); 75 | cudaMalloc((void**)&gpu_thread_x, ARRAY_SIZE_IN_BYTES); 76 | cudaMalloc((void**)&gpu_thread_y, ARRAY_SIZE_IN_BYTES); 77 | 78 | 79 | what_is_my_id_2d<<>>(gpu_griddim_x, gpu_griddim_y, gpu_blockdim_x, gpu_blockdim_y, gpu_calc_thread, gpu_blockidx_x, gpu_blockidx_y, gpu_threadidx_x, gpu_threadidx_y, gpu_thread_x, gpu_thread_y); 80 | 81 | cudaMemcpy(cpu_griddim_x, gpu_griddim_x, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 82 | cudaMemcpy(cpu_griddim_y, gpu_griddim_y, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 83 | cudaMemcpy(cpu_blockdim_x, gpu_blockdim_x, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 84 | cudaMemcpy(cpu_blockdim_y, gpu_blockdim_y,ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 85 | cudaMemcpy(cpu_calc_thread, gpu_calc_thread,ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 86 | cudaMemcpy(cpu_blockidx_x, gpu_blockidx_x, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 87 | cudaMemcpy(cpu_blockidx_y, gpu_blockidx_y, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 88 | cudaMemcpy(cpu_threadidx_x, gpu_threadidx_x, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 89 | cudaMemcpy(cpu_threadidx_y, gpu_threadidx_y, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 90 | cudaMemcpy(cpu_thread_x, gpu_thread_x, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 91 | cudaMemcpy(cpu_thread_y, gpu_thread_y, ARRAY_SIZE_IN_BYTES, cudaMemcpyDeviceToHost); 92 | 93 | cudaFree(gpu_griddim_x); 94 | cudaFree(gpu_griddim_y); 95 | cudaFree(gpu_blockdim_x); 96 | cudaFree(gpu_blockdim_y); 97 | cudaFree(gpu_calc_thread); 98 | cudaFree(gpu_blockidx_x); 99 | cudaFree(gpu_blockidx_y); 100 | cudaFree(gpu_threadidx_x); 101 | cudaFree(gpu_threadidx_y); 102 | cudaFree(gpu_thread_x); 103 | cudaFree(gpu_thread_y); 104 | 105 | 106 | for(int y=0; y < ARRAY_SIZE_Y; y++){ 107 | for(int x=0; x < ARRAY_SIZE_X; x++){ 108 | printf("graddim_x %2u - graddim_y %2u - blockdim_x %2u - blockdim_y %2u -cac_thread %3u - blockidx_x %2u - blockidx_y %2u- threadidx_x %2u - threadidx_y %2u - thread_x %2u - thread_y %2u \n", 109 | cpu_griddim_x[y][x], cpu_griddim_y[y][x], cpu_blockdim_x[y][x], cpu_blockdim_y[y][x], cpu_calc_thread[y][x], cpu_blockidx_x[y][x], cpu_blockidx_y[y][x], cpu_threadidx_x[y][x], cpu_threadidx_y[y][x], cpu_thread_x[y][x], cpu_thread_y[y][x]); 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /docs/11_gemm_optimize/01_tiled2d/README.md: -------------------------------------------------------------------------------- 1 | # 二维 Thread Tile 并行优化 2 | 3 | 在初级系列中我们已经实现了一个简单的矩阵乘法的 kernel,并使用共享内存和一维线程块来优化了矩阵乘法的性能。在 GEMM 优化专栏里面,我们将会继续优化矩阵乘法的性能,这一节我们将会使用二维线程块来优化矩阵乘法的性能。 4 | 5 | ## 1. 一维 Thread Tile 6 | 7 | 在介绍二维 Thread Tile 之前,我们先来回顾一下一维 Thread Tile 的优化方法。在初级系列中,我们使用了一维线程块来优化矩阵乘法的性能,我们将矩阵乘法的计算任务分配给了一维线程块,每个线程块负责计算一个小的矩阵块。这样做的好处是可以充分利用共享内存,减少全局内存的访问次数,从而提高矩阵乘法的性能。 8 | 9 | 还记得一维 Thread Tile 中的例子吗?如果输入的 A 和 B 都是 7x7 的矩阵: 10 | 11 | 1. 如果我们一次读取 1 行 A 和 1 列 B,当每一个线程只计算一个结果的时候,我们需要从 A 中读取 7 个数据,从 B 中读取 7 个数据,从 C 中读取 1 个数据,然后写 1 次 C。这样的话,每个线程需要读取 15 个数据,写 1 次数据。计算 16 个结果需要 16 个线程,共 16x16 = 256 次 IO。 12 | 2. 如果我们一次读取 4 行 A 和 1 列 B,那么每一个线程计算 4 个结果,此时需要从 A 中读取 4x7 个数据,从 B 中读取 7 个数据,从 C 中读取 4 个数据,然后写 4 次 C。计算 16 个结果需要 4 个线程,共 4x43 = 172 次 IO。 13 | 3. 如果我们一次读取 4 行 A 和 4 列 B,那么每一个线程计算 16 个结果,此时需要从 A 中读取 4x7 个数据,从 B 中读取 4x7 个数据,从 C 中读取 16 个数据,然后写 16 次 C。计算 16 个结果一共需要 1 个线程,共 1x88 = 88 次 IO。 14 | 15 | 上述的 `2` 就是一维 Thread Tile 优化,上述的 `3` 就是 二维 Thread Tile 优化,计算结果不变的同时,减少 IO 次数,提升算法的执行时间。所以想要继续优化这个 Kernel 的性能,我们可以使用二维线程块来计算二维的矩阵块。 16 | 17 | ## 2. 二维 Thread Tile 18 | 19 | ### 2.1 优化思路 20 | 21 | 本文的主要优化思路就是让每个线程计算一个 8\*8 的网格。下面我们来看一下这个 Kernel 的主题思路图: 22 | 23 | ![picture 1](images/9047246849f79b5117961c15e1a3a340a44ab003566140ecc00600058c70a9e2.png) 24 | 25 | 首先在内核的第一阶段, 所有线程协同工作, 从全局内存中加载矩阵 A 和矩阵 B 到共享内存中。 26 | 27 | 当 SMEM 缓存填充完毕后,每个线程负责将其相关的 SMEM 条目相乘,并将结果累加到本地寄存器中。可以看到, 每个线程计算的是一个 TM \* TN 的矩阵块。如果图中的 TN 是 1, 那么就是一维 Thread Tile。 28 | 29 | ### 2.2 代码实现 30 | 31 | 接下来让我们动手实现这个内核, 我们按照上面的原理图来写代码。 32 | 33 | :::tip 34 | 35 | 写 Kernel 代码的时候, 适当画图是非常有帮助的。这样可以帮助我们更好的理解 Kernel 的执行流程。 36 | 37 | ::: 38 | 39 | 首先我们需要定义一些常量方便后续使用: 40 | 41 | ```cpp 42 | // Block 索引 43 | const uint c_row = blockIdx.y; 44 | const uint c_col = blockIdx.x; 45 | 46 | // Thread 索引 47 | const uint thread_col = threadIdx.x % (BN / TN); 48 | const uint thread_row = threadIdx.x / (BN / TN); 49 | 50 | // 二维 tile (block tile) 的大小 51 | const uint total_results_block_tile = BM * BN; 52 | // 一个 block tile 需要的线程数量 53 | const uint number_threads_block_tile = total_results_block_tile / (TM * TN); 54 | 55 | assert(number_threads_block_tile == blockDim.x); 56 | 57 | // 计算该 Thread 负责加载的共享内存索引 58 | const uint inner_row_A = threadIdx.x / BK; 59 | const uint inner_col_A = threadIdx.x % BK; 60 | // 计算每个线程块一次加载的行数 61 | const uint stride_A = number_threads_block_tile / BK; 62 | 63 | const uint inner_row_B = threadIdx.x / BN; 64 | const uint inner_col_B = threadIdx.x % BN; 65 | const uint stride_B = number_threads_block_tile / BN; 66 | ``` 67 | 68 | 其中 `c_row` 和 `c_col` 表示的是当前计算的结果矩阵的行和列 (对应图上的 cRow 和 cCol)。 69 | `thread_col` 和 `thread_row` 对应的是当前线程在 `BM * BN` 的网格中的列和行 (结合图中的标注理解)。 70 | `inner_row_A` 和 `inner_col_A` 表示的是当前线程在共享内存中加载矩阵 A 的索引。 71 | `stride_A` 表示的是每个线程块一次加载的行数。 单纯这样解释可能有点抽象,不用急结合后面代码的实现就会明白。 72 | 73 | 接下来我们需要定义一些共享内存,线程块的结果和寄存器变量: 74 | 75 | ```cpp 76 | __shared__ float smem_A[BM * BK]; 77 | __shared__ float smem_B[BN * BK]; 78 | 79 | float thread_results[TM * TN] = {0.0}; 80 | float reg_m[TM] = {0.0}; 81 | float reg_n[TN] = {0.0}; 82 | 83 | A += c_row * BM * K; 84 | B += c_col * BN; 85 | C += c_row * BM * N + c_col * BN; 86 | 87 | // 外层循环 88 | for (uint bkIdx = 0; bkIdx < K; bkIdx += BK) 89 | { 90 | ... // 每个线程的具体逻辑 91 | } 92 | ``` 93 | 94 | 然后我们需要在内核的外层循环中,将矩阵 A 和矩阵 B 的数据加载到共享内存中 95 | 96 | ```cpp 97 | // Load matrix A and B into shared memory 98 | for (uint load_offset = 0; load_offset < BM; load_offset += stride_A) 99 | { 100 | smem_A[(inner_row_A + load_offset) * BK + inner_col_A] = A[(inner_row_A + load_offset) * K + inner_col_A]; 101 | } 102 | for (uint load_offset = 0; load_offset < BK; load_offset += stride_B) 103 | { 104 | smem_B[(inner_row_B + load_offset) * BN + inner_col_B] = B[(inner_row_B + load_offset) * N + inner_col_B]; 105 | } 106 | 107 | __syncthreads(); 108 | 109 | // 移动到下一个 block tile 110 | A += BK; 111 | B += BK * N; 112 | ``` 113 | 114 | 下图可以更好的帮助我们理解上面的代码: 115 | 116 | ![picture 2](images/f507ad687528e8bbb14a85c1fa3016cce50be55b5670ebc425c549cc5c5bd5a6.png) 117 | 118 | 图中画出了矩阵 A 加载共享内存的过程。在每一步中,每个线程负责加载一个元素到共享内存中。这个元素的索引是 `inner_row_A` 和 `inner_col_A` 。for 循环中的 `load_offset` 递增的步长是 `stride_A` 。在图中就是向下移动了 `stride_A` 个元素。 119 | 120 | 下一步我们需要计算矩阵乘法的结果, 我们需要计算 BM * BN 个结果, 并将这一步的结果累加到 `thread_results` 中。 121 | 122 | ```cpp 123 | // 计算矩阵乘法的结果 124 | for (uint dot_idx = 0; dot_idx < BK; ++dot_idx) 125 | { 126 | // 加载矩阵 A 和矩阵 B 到寄存器中 127 | for (uint i = 0;i < TM;i ++) 128 | { 129 | reg_m[i] = smem_A[(thread_row * TM + i) * BK + dot_idx]; 130 | } 131 | for (uint i = 0;i < TN;i ++) 132 | { 133 | reg_n[i] = smem_B[dot_idx * BN + thread_col * TN + i]; 134 | } 135 | for (uint reg_idx_m = 0; reg_idx_m < TM; ++reg_idx_m) 136 | { 137 | for (uint reg_idx_n = 0; reg_idx_n < TN; ++reg_idx_n) 138 | { 139 | thread_results[reg_idx_m * TN + reg_idx_n] += 140 | reg_m[reg_idx_m] * reg_n[reg_idx_n]; 141 | } 142 | } 143 | } 144 | 145 | __syncthreads(); 146 | ``` 147 | 148 | 在内部循环中,我们通过将 dot_idx 作为外层循环,明确加载我们在两个内部循环中所需的值到寄存器中,从而减少 SMEM 访问的次数。下面是对 dot_idx 循环随时间的可视化,展示了在每一步中哪些 SMEM 条目被加载到线程本地寄存器中: 149 | 150 | ![picture 3](images/2bd4653c7a81dc2a1a48f359a49bba0de7a7560fb8834470225c3fc55ec23221.png) 151 | 152 | :::note 153 | 154 | 由于绘图需要,我不得不缩小一些维度,使其更容易绘制。在内核中:BK=TM=TN=8 155 | 156 | ::: 157 | 158 | 这里最容易搞错的就是 `reg_m` 和 `reg_n` 的索引计算。索引计算的时候用到了 `thread_row` 和 `thread_col` 。它们是用于定位到当前线程在哪一个 8 * 8 的网格中。 159 | 160 | 最后我们需要将结果写回到全局内存中: 161 | 162 | ```cpp 163 | for (uint reg_idx_m = 0; reg_idx_m < TM; ++reg_idx_m) 164 | { 165 | for (uint reg_idx_n = 0; reg_idx_n < TN; ++reg_idx_n) 166 | { 167 | C[(thread_row * TM + reg_idx_m) * N + thread_col * TN + reg_idx_n] = 168 | thread_results[reg_idx_m * TN + reg_idx_n]; 169 | } 170 | } 171 | ``` 172 | 173 | 编译运行命令如下: 174 | 175 | ```bash 176 | nvcc -o sgemm_tiled2d sgemm_tiled2d.cu 177 | ./sgemm_tiled2d 256 256 256 178 | ``` 179 | 180 | ## 3. 性能测试 181 | 182 | 我们将上该内核的性能和之前的内核进行比较,我们分别计算 256x256、512x512、1024x1024、2048x2048 (Matrix 1、Matrix 2、Matrix 3、Matrix 4、Matrix 5)的矩阵乘法的性能 (us)。在 1080Ti 上运行,结果如下: 183 | 184 | 185 | | Algorithm | Matrix 1 | Matrix 2 | Matrix 3 | Matrix 4 | 186 | | ---------------- | -------- | -------- | -------- | -------- | 187 | | Naive | 95.5152 | 724.396 | 28424 | 228681 | 188 | | 共享内存缓存块 | 40.5293 | 198.374 | 8245.68 | 59048.4 | 189 | | 一维 Thread Tile | 35.215 | 174.731 | 894.779 | 5880.03 | 190 | | 二维 Thread Tile | 34.708 | 92.946 | 557.829 | 3509.920 | 191 | 192 | ## 4. 总结 193 | 194 | 本文我们介绍了二维 Thread Tile 并行优化方法。我们将矩阵乘法的计算任务分配给了二维线程块,每个线程块负责计算一个小的矩阵块。这样做的好处是可以充分利用共享内存,减少全局内存的访问次数,从而提高矩阵乘法的性能。 195 | 196 | ## Reference 197 | 198 | 1. https://siboehm.com/articles/22/CUDA-MMM 199 | 2. https://space.keter.top/docs/high_performance/GEMM%E4%BC%98%E5%8C%96%E4%B8%93%E9%A2%98/%E4%BA%8C%E7%BB%B4Thread%20Tile%E5%B9%B6%E8%A1%8C%E4%BC%98%E5%8C%96 200 | 3. https://github.com/siboehm/SGEMM_CUDA 201 | -------------------------------------------------------------------------------- /docs/11_gemm_optimize/01_tiled2d/images/2bd4653c7a81dc2a1a48f359a49bba0de7a7560fb8834470225c3fc55ec23221.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/01_tiled2d/images/2bd4653c7a81dc2a1a48f359a49bba0de7a7560fb8834470225c3fc55ec23221.png -------------------------------------------------------------------------------- /docs/11_gemm_optimize/01_tiled2d/images/5a5157b46fb6c076cf1bf918b4ebe4c8bb7d70ecab97a2b77686c93fa4c89dde.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/01_tiled2d/images/5a5157b46fb6c076cf1bf918b4ebe4c8bb7d70ecab97a2b77686c93fa4c89dde.png -------------------------------------------------------------------------------- /docs/11_gemm_optimize/01_tiled2d/images/9047246849f79b5117961c15e1a3a340a44ab003566140ecc00600058c70a9e2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/01_tiled2d/images/9047246849f79b5117961c15e1a3a340a44ab003566140ecc00600058c70a9e2.png -------------------------------------------------------------------------------- /docs/11_gemm_optimize/01_tiled2d/images/f507ad687528e8bbb14a85c1fa3016cce50be55b5670ebc425c549cc5c5bd5a6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/01_tiled2d/images/f507ad687528e8bbb14a85c1fa3016cce50be55b5670ebc425c549cc5c5bd5a6.png -------------------------------------------------------------------------------- /docs/11_gemm_optimize/02_vectorize_smem_and_gmem_accesses/images/05eee538f6394ffc2ffffc2947edc8c888175af7152a150d697bfefb47db7a98.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/02_vectorize_smem_and_gmem_accesses/images/05eee538f6394ffc2ffffc2947edc8c888175af7152a150d697bfefb47db7a98.jpg -------------------------------------------------------------------------------- /docs/11_gemm_optimize/02_vectorize_smem_and_gmem_accesses/images/load_to_smem.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/11_gemm_optimize/02_vectorize_smem_and_gmem_accesses/images/load_to_smem.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/02_vectorize_smem_and_gmem_accesses/images/load_to_smem.jpg -------------------------------------------------------------------------------- /docs/11_gemm_optimize/03_warptiling/README.md: -------------------------------------------------------------------------------- 1 | # Warp Tiling 2 | 3 | ## 1. 优化思路 4 | 5 | 上一个 Kernel 中我们使用向量化访存的方式来提高访存效率, 它的循环结构如下图所示: 6 | 7 | ![picture 0](images/algorithm_loop.png) 8 | 9 | 第一个循环 (blocktiling loop) 是对 K 的循环, 这个循环中会将数据从全局内存移动到共享内存中, 第二个循环 (threadtiling loop) 是线程级别的循环, 这个循环中会将数据从共享内存移动到寄存器中, 第三和第四个循环是对寄存器中的数据进行计算。 10 | 11 | 本文中我们将会在第一个循环, 第二个循环之前加入一个 warp tiling 的循环, 以此来提高访存效率。Warp 是 GPU 硬件中的一个概念, 一个 warp 由 32 个线程组成, 这 32 个线程会被分配到一个 SM 中的一个 warp scheduler 中, warp scheduler 会负责调度这 32 个线程的执行。一个 SM 上可能会有多个 warp scheduler, 下图是一个 SM 中 warp scheduler 的示意图: 12 | 13 | ![picture 1](images/sm.jpg) 14 | 15 | 在 warp 级别上进行并行计算可以充分利用 GPU 的并行处理能力。通过同时执行多个 warp 中的线程,可以实现更高的计算吞吐量,从而加快整体计算速度。 16 | 17 | 在使用 warp tiling 之后, 我们的循环结构如下所示: 18 | 19 | 1. Block Tile: 不同的块可以在不同的 SM 上并行执行。 20 | 2. Warp Tile: 不同的 warps 可以在不同的 warp 调度器上并行执行,并且同时在同一个 warp 调度器上执行 21 | 3. Thread Tile: 指令可以在同一个 CUDA 核心上并行执行(即指令级并行性,又称 ILP) 22 | 23 | :::note 24 | 25 | ILP 是指在一个线程中的指令可以并行执行, warp tiling 会增加 warp 级别的并行性, 从而提高计算效率。 26 | 27 | ::: 28 | 29 | 下图给出了算法的整体流程图: 30 | 31 | ![picture 2](images/algorithm_pipeline.jpg) 32 | 33 | ## 2. 代码实现 34 | 35 | 在了解了代码的整体结构之后, 我们来看一下 warp tiling 的代码实现。首先我们还是先来看看添加了 warp tiling 之后各个相对坐标要如何计算。相比上一个 Kernel 我们多了一层 warp tiling 的循环, warp id 和 warp 内 thread 的 id 计算方式是新加入的: 36 | 37 | ```cpp 38 | const uint warp_idx = threadIdx.x / WARPSIZE; 39 | const uint warp_col = warp_idx % (BN / WN); 40 | const uint warp_row = warp_idx / (BN / WN); 41 | 42 | // warp tile 的大小 43 | // WM 是每个 Warp 处理数据的行数,WN 是每个 Warp 处理数据的列数 44 | // 数据行数 / 迭代次数 = 每次迭代处理的行数 45 | constexpr uint WSUBM = WM / WMITER; 46 | constexpr uint WSUBN = WN / WNITER; 47 | 48 | // warp 内的线程索引 49 | const uint thread_idx_in_warp = threadIdx.x % WARPSIZE; // [0, 31] 50 | const uint thread_col_in_warp = thread_idx_in_warp % (WSUBN / TN); 51 | const uint thread_row_in_warp = thread_idx_in_warp / (WSUBN / TN); 52 | ``` 53 | 54 | `warp_idx` 很好理解, 它是当前 warp 在 block 中的索引, `warp_col` 和 `warp_row` 是当前 warp 在 block 中的坐标。`BN / WN` 是 block 中 warp 的列数, `warp_col` 是当前 warp 在 block 中的列索引, `warp_row` 是当前 warp 在 block 中的行索引。`WSUBM` 和 `WSUBN` 是 warp tile 的大小。`WM` 和 `WN` 是 block tile 的大小, `WMITER` 和 `WNITER` 是 warp tile 的迭代次数。 55 | 56 | `thread_idx_in_warp` 是当前线程在 warp 中的索引, `thread_col_in_warp` 和 `thread_row_in_warp` 是当前线程在 warp 中的坐标。`WSUBN / TN` 是 warp 中线程的列数, `thread_col_in_warp` 是当前线程在 warp 中的列索引, `thread_row_in_warp` 是当前线程在 warp 中的行索引。 57 | 58 | 结合上面的算法流程图会更容易理解这些相对坐标的计算方式。 59 | 60 | 在计算了相对坐标之后, 我们就可以开始实现 warp tiling 的循环了,首先是将数据从全局内存移动到共享内存中,这一步和上一个 Kernel 中的实现是一样的: 61 | 62 | ```cpp 63 | // 从全局内存加载 A 到共享内存 64 | for (uint offset = 0; offset < BM; offset += stride_a) 65 | { 66 | const float4 tmp = FETCH_FLOAT4(A[OFFSET(offset + inner_row_a, inner_col_a, K)]); 67 | smem_a[OFFSET(inner_col_a, offset + inner_row_a, BM)] = tmp.x; 68 | smem_a[OFFSET(inner_col_a + 1, offset + inner_row_a, BM)] = tmp.y; 69 | smem_a[OFFSET(inner_col_a + 2, offset + inner_row_a, BM)] = tmp.z; 70 | smem_a[OFFSET(inner_col_a + 3, offset + inner_row_a, BM)] = tmp.w; 71 | } 72 | 73 | // 从全局内存加载 B 到共享内存 74 | for (uint offset = 0; offset < BK; offset += stride_b) 75 | { 76 | FETCH_FLOAT4(smem_b[OFFSET(inner_row_b + offset, inner_col_b, BN)]) = 77 | FETCH_FLOAT4(B[OFFSET(inner_row_b + offset, inner_col_b, N)]); 78 | } 79 | ``` 80 | 81 | 在下一步 warp tiling 的循环中, 我们需要将数据从共享内存移动到寄存器中, 由于我们添加了 warp tiling 的循环, 所以将数据读取到寄存器中以及对数据进行计算的时候都多了 warp 层的循环: 82 | 83 | ```cpp 84 | // 计算每个线程的部分结果 85 | for (uint warp_sub_row_idx = 0; warp_sub_row_idx < WMITER; ++warp_sub_row_idx) 86 | { 87 | for (uint warp_sub_col_idx = 0; warp_sub_col_idx < WNITER; ++warp_sub_col_idx) 88 | { 89 | for (int m = 0; m < TM; m++) 90 | { 91 | for (int n = 0; n < TN; n++) 92 | { 93 | // 计算矩阵乘法结果并累加到 thread_results 数组中 94 | thread_results[(warp_sub_row_idx * TM + m) * (WNITER * TN) + (warp_sub_col_idx * TN) + n] += reg_a[warp_sub_row_idx * TM + m] * reg_b[warp_sub_col_idx * TN + n]; 95 | } 96 | } 97 | } 98 | } 99 | ``` 100 | 101 | 虽然看上去多了很多代码但是实际上 warp tiling 的实现并不复杂, 只是在原来的代码基础上多了一层 warp 的循环。在 warp tiling 的循环中我们将数据从共享内存移动到寄存器中, 并且对数据进行计算。 102 | 103 | 同理在最后的计算结果写回到全局内存的时候也多了 warp 层的循环: 104 | 105 | ```cpp 106 | // 将线程的结果写入全局内存 107 | for (uint warp_sub_row_idx = 0; warp_sub_row_idx < WMITER; ++warp_sub_row_idx) 108 | { 109 | for (uint warp_sub_col_idx = 0; warp_sub_col_idx < WNITER; ++warp_sub_col_idx) 110 | { 111 | // 计算 C 的内存索引并将结果写入 C 112 | float *C_interim = C + (warp_sub_row_idx * WSUBM) * N + warp_sub_col_idx * WSUBN; 113 | for (int m = 0; m < TM; m++) 114 | { 115 | for (int n = 0; n < TN; n += 4) 116 | { 117 | FETCH_FLOAT4(C_interim[OFFSET(m + thread_row_in_warp * TM, n + thread_col_in_warp * TN, N)]) = 118 | FETCH_FLOAT4(thread_results[(warp_sub_row_idx * TM + m) * (WNITER * TN) + (warp_sub_col_idx * TN) + n]); 119 | } 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | 编译命令如下: 126 | 127 | ```bash 128 | nvcc -o sgemm_warp_tiling sgemm_warp_tiling.cu 129 | ./sgemm_warp_tiling 256 256 256 130 | ``` 131 | 132 | ## 3. 性能对比 133 | 134 | 我们将上该内核的性能和之前的内核进行比较,我们分别计算 256x256、512x512、1024x1024、2048x2048 (Matrix 1、Matrix 2、Matrix 3、Matrix 4、Matrix 5)的矩阵乘法的性能 (ns)。在 1080Ti 上运行,结果如下: 135 | 136 | 137 | | Algorithm | Matrix 1 | Matrix 2 | Matrix 3 | Matrix 4 | 138 | | --------- | -------- | -------- | -------- | -------- | 139 | | Naive | 95.5152 | 724.396 | 28424 | 228681 | 140 | | 共享内存缓存块 | 40.5293 | 198.374 | 8245.68 | 59048.4 | 141 | | 一维 Thread Tile | 35.215 | 174.731 | 894.779 | 5880.03 | 142 | | 二维 Thread Tile | 34.708 | 92.946 | 557.829 | 3509.920 | 143 | | 向量化访存 | 36.567 | 90.745 | 427.701 | 2901.475 | 144 | | Warp Tiling | 25.071 | 65.810 | 361.433 | 2651.449 | 145 | 146 | ## 4. 总结 147 | 148 | 越是优化到后面,代码中 for 循环的层级就越多,这与 CUTLASS 库的实现理念非常接近。究其原因,还是因为 CUDA 在设计上存在 Refer 性结构。 149 | 150 | ## Reference 151 | 152 | 1. https://siboehm.com/articles/22/CUDA-MMM 153 | 2. https://github.com/siboehm/SGEMM_CUDA 154 | 3. https://github.com/wangzyon/NVIDIA_SGEMM_PRACTICE -------------------------------------------------------------------------------- /docs/11_gemm_optimize/03_warptiling/images/algorithm_loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/03_warptiling/images/algorithm_loop.png -------------------------------------------------------------------------------- /docs/11_gemm_optimize/03_warptiling/images/algorithm_pipeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/03_warptiling/images/algorithm_pipeline.jpg -------------------------------------------------------------------------------- /docs/11_gemm_optimize/03_warptiling/images/sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/03_warptiling/images/sm.jpg -------------------------------------------------------------------------------- /docs/11_gemm_optimize/04_double_buffer/README.md: -------------------------------------------------------------------------------- 1 | # 双缓冲 2 | 3 | 我们前面实现的 Kernel 都是单缓存的。单缓存是指申请单块共享内存,缓存全局数据,申请单块寄存器内存,缓存共享数据,单块缓存不能实现读取和存储并行进行,因为数据之间存在依赖。例如单缓存场景,计算依赖共享内存数据,为保证计算前全局内存完全存入共享内存,需要进行一次同步;同样因为计算依赖共享内存数据,所以在存新一轮全局内存到共享内存前也需要进行一次同步,保证上一轮计算完成。 4 | 5 | 那么有没有办法实现读取和存储并行进行呢?答案是有的,那就是双缓存,本文将介绍双缓存的实现方法。 6 | 7 | ## 1. 优化思路 8 | 9 | 在之前的 Kernel 中, 我们在一次循环中使用了两次 `__syncthreads()` 以防止不同线程之间的数据不一致。第一个 `__syncthreads()` 是用于保证写后读的顺序,这个是无法避免的。它是为了防止部分线程还未读取 As 或者 Bs 中的内容,保证读后写(Write-After-Read)的顺序性。它本质上是因为我们在不同迭代中使用了同一块空间来保存我们所需的数据,这两次迭代中的数据之间并不存在真正的依赖关系。如果我们将其写入到其他地址上,那么就不需要使用同步了 [4]。 10 | 11 | 双缓存的优化思路是:申请双倍存储空间,将读和写分开,计算数据读取一块存储空间同时,可以同时向另一块内存写入下一轮依赖的数据,因此,只需要保证计算前待读取共享内存完成写入,即一次同步即可。 12 | 13 | 下图是双缓存的示意图: 14 | 15 | ![picture1](./images/double_buffer.jpg) 16 | 17 | 上面的是单缓存,下面的是双缓存。可以看到,双缓存的读和写是分开的,因此可以实现读取和存储并行进行。 18 | 19 | ## 2. 代码实现 20 | 21 | 双缓存的实现方法很简单,只需要在共享内存中申请两块空间,然后在计算前后交换读写指针即可。 22 | 23 | 首先我们先看一下单缓存的实现的简略代码: 24 | 25 | ```cpp 26 | // 声明需要用的变量 27 | ... 28 | 29 | // 外层循环遍历矩阵块 30 | for (uint bk_idx = 0; bk_idx < K; bk_idx += BK) 31 | { 32 | // 读取数据到共享内存 33 | ... 34 | __syncthreads(); 35 | 36 | // 计算 37 | ... 38 | __syncthreads(); 39 | 40 | A += BK; 41 | B += BK * N; 42 | } 43 | ``` 44 | 45 | 可以看到,我们在读取数据到共享内存后,进行了一次同步,然后进行计算,计算完成后,再进行一次同步。下面是双缓存的实现的简略代码: 46 | 47 | ```cpp 48 | // 声明需要用的变量 49 | ... 50 | // 分配共享内存 51 | __shared__ float smem_a[2][BM * BK]; 52 | __shared__ float smem_b[2][BK * BN]; 53 | 54 | float reg_a[2][WMITER * TM] = {0.0}; // 缓存 smem_a 55 | float reg_b[2][WNITER * TN] = {0.0}; // 缓存 smem_b 56 | 57 | ... 58 | 59 | int write_index = 0; 60 | 61 | // 外层循环遍历矩阵块 62 | for (uint bk_idx = 0; bk_idx < K; bk_idx += BK) 63 | { 64 | // 读取数据到共享内存 smem_a[write_index] 和 smem_b[write_index] 65 | ... 66 | __syncthreads(); 67 | 68 | // 计算 69 | // 加载数据到寄存器 reg_a[write_index] 和 reg_b[write_index] 70 | ... 71 | 72 | A += BK; 73 | B += BK * N; 74 | 75 | // 原来的同步就不要了 76 | ... 77 | 78 | write_index = 1 - write_index; // 切换读写指针 79 | } 80 | ``` 81 | 82 | 可以看到,我们在读取数据到共享内存后,不再进行一次同步,而是直接进行计算。这是因为我们在读取数据到共享内存后,可以直接进行计算,同时将下一轮的数据写入到另一块共享内存中。这样就实现了读取和存储并行进行。 83 | 84 | 完整的代码在 `sgemm_double_buffering.cu` 中,编译运行方法如下: 85 | 86 | ```bash 87 | nvcc sgemm_double_buffering.cu -o sgemm_double_buffering 88 | ./sgemm_double_buffering 256 256 256 89 | ``` 90 | 91 | ## 3. 性能测试 92 | 93 | 我们将上该内核的性能和之前的内核进行比较,我们分别计算 256x256、512x512、1024x1024、2048x2048 (Matrix 1、Matrix 2、Matrix 3、Matrix 4、Matrix 5)的矩阵乘法的性能 (us)。在 1080Ti 上运行,结果如下: 94 | 95 | 96 | | Algorithm | Matrix 1 | Matrix 2 | Matrix 3 | Matrix 4 | 97 | | --------- | -------- | -------- | -------- | -------- | 98 | | Naive | 95.5152 | 724.396 | 28424 | 228681 | 99 | | 共享内存缓存块 | 40.5293 | 198.374 | 8245.68 | 59048.4 | 100 | | 一维 Thread Tile | 35.215 | 174.731 | 894.779 | 5880.03 | 101 | | 二维 Thread Tile | 34.708 | 92.946 | 557.829 | 3509.920 | 102 | | 向量化访存 | 36.567 | 90.745 | 427.701 | 2901.475 | 103 | | Warp Tiling | 25.071 | 65.810 | 361.433 | 2651.449 | 104 | | 双缓冲 | 22.383 | 62.539 | 332.631 | 2533.086 | 105 | 106 | ## 4. 总结 107 | 108 | 双缓存的策略是一种用空间换时间的策略,通过增加共享内存的大小,来减少同步的次数,从而提高计算效率。 109 | 110 | 111 | # References 112 | 113 | 1. https://siboehm.com/articles/22/CUDA-MMM 114 | 2. https://github.com/siboehm/SGEMM_CUDA 115 | 3. https://github.com/wangzyon/NVIDIA_SGEMM_PRACTICE 116 | 4. https://linn-ylz.com/Computer-Science/CUDA/CUDA-SGEMM-optimization-notes/ -------------------------------------------------------------------------------- /docs/11_gemm_optimize/04_double_buffer/images/double_buffer.drawio: -------------------------------------------------------------------------------- 1 | 7Zxdk5o6HMY/jZfdAcKLXrZ2t6czZ2c69eL0XHUiRGGKxIa4aj99g7xJgAqOCQyNN5p/IJD88iThAZmB5e70icC9/4o9FM4MzTvNwMeZYeiaPmdfSeScRhzTSQNbEnjZRmVgFfxC+Z5Z9BB4KK5sSDEOabCvBl0cRcillRgkBB+rm21wWD3qHm5RLbByYViP/hd41E+jlqZpZcY/KNj6lM/ZwXzrLBD70MPHqxB4noElwZimv3anJQqT1ssbJt3vpSW3ODOCItplh+/k5TuyfsLz2qTP1mfj29z8/C4r5Q2Gh6zGM8MOWXkfNpgVy1oMummG/fOQnOmHJd4FLstYwShmX6+rMov92ibf/2LosaxPIV7D5AzyItck32JFMUEs5yva6nkuO/P0mOkmWaPRc46C4EPkoaQyGss++gFFq316dkfW+VjMp7uQpXT2M/6BqOtniU0QhkscYnIpCKDLJ9mIEvwDXeUAGyyAVxz8DRGKTq3NrRcQWfdHeIcoObNNTtUefM6TWfpYdiPdyWL+VQ+ysxjMeu62KLlky35keHugNgSizjjCXYIhWsf7dJMW6iuftaInnbvrImuzEc593gG8JhM8EAi+QNkRPesohnzwHlrP16LBG6MDb4oA//L6Xv6IrXsWcpoALmwHQPsxAEEHgEAmP0vx68PP7DDlSuVni+D39PQ0VX7myPg5auKUMXGaztgmzrkaePsAtDoAlCrcheLXh5+tjYxfPpGrmbMjQDA2gMpXEjVb2naH2VKqsaQrZ0mKszTvoHK56yRdpLX0d8ucu5YtzKXhVC7ETVIqv+UfN5KXq3IhPpS6Dr5lII+AvBAHa7oXUryD3ERQ7jpciIU1XYC8hTw8QCFW1HSvhHkPeXiAQrwoNXneMpGHnzzz537U2Hufizy4dA0hJtZ0AfI28vAAhRhT0508eRt5eIDKX5JlIw9vMBnKYBrERh7BSkkZTFLWyLw9AczBySuDqd9VTgeCcmdoZTD1u8jRxwZQGUz9AJpjAyjSYPq718j8DRlz8Ect8p6l1siC3USrA3m5f+IR+USVWiOX4/v4yAtxsaa7xLI7EJQ6QwMhLtaEAXaYduUCFOJMTXeN7OhjA6gMJimTp2OObvJUBlMvgvMOBOVKVxlM/QA6YwOoDKZeABfa2AA2GUxcG6PIe5+8bYel3BDGMcNXadZ0B+TVXrZzs1Wuam011DqPERRCGrxVi29qiuwIX3Bw6XttD10DrjVjfCAuyvYqG/RmQYU9lBdEIdkiWivoQqao9v2w8uG7QW1e8FaBlkuKdVX6DobBNpoBNipqLkOCSF1yeTnxHkZ8rE3JbqbkOFXyLq4XG58jl/oEQS9uVXQRbjo2C1+q1iL/pHq3df8A4er8vygXdeEu/tCFH//6jybLaGLCrd3MNu4Ubu3PVfx4Klq47S6PEq5Y4TqcYaQ3PBAhV7hNhtHEhLt4lHD5gqQLt90cUsIVK9z56ITb5DNNTLj8CtfiW/PepXJxJ12WcNutISVcuUtlc3DhNrlMExMuf2fTunfGrd0ilT3jtjtKSrhihcv/y2p44T7AnGJ1J+dv14n/rxMfk7prReqcpbrK3dBSOfyhEumT72NZid89LvAFyR4X8vZQ48LgK3GB4wJLlu9MT7tO+ep58Pwb -------------------------------------------------------------------------------- /docs/11_gemm_optimize/04_double_buffer/images/double_buffer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/11_gemm_optimize/04_double_buffer/images/double_buffer.jpg -------------------------------------------------------------------------------- /docs/11_gemm_optimize/05_bank_conflicts/README.md: -------------------------------------------------------------------------------- 1 | # 减少 Bank Conflict -------------------------------------------------------------------------------- /docs/12_convolution/01_naive_conv/README.md: -------------------------------------------------------------------------------- 1 | # 卷积算子简易实现 2 | 3 | ## 1. 什么是卷积运算 4 | 5 | 卷积运算是一种特殊的线性运算,它是一种对两个实变函数的一种数学运算,表征函数之间的一种重要的运算方式。卷积运算的定义如下: 6 | 7 | $$ 8 | (f*g)(t) = \int_{-\infty}^{\infty} f(\tau)g(t-\tau)d\tau 9 | $$ 10 | 11 | 其中, $f(t)$ 和 $g(t)$ 是两个实变函数,$*$表示卷积运算。卷积运算的定义是在实数域上的,但是在深度学习中,我们通常使用离散卷积运算。离散卷积运算的定义如下: 12 | 13 | $$ 14 | (f*g)(t) = \sum_{\tau=-\infty}^{\infty} f(\tau)g(t-\tau) 15 | $$ 16 | 17 | 其中,$f(t)$ 和 $g(t)$ 是两个离散函数,$*$表示离散卷积运算。 18 | 19 | 在图象处理中,卷积运算是一种对图像进行滤波的操作,它可以提取图像的特征。卷积运算的过程如下: 20 | 21 | 1. 将卷积核与图像进行逐元素相乘; 22 | 2. 将相乘的结果求和。 23 | 24 | 其中,卷积核是一个小的矩阵,它可以提取图像的特征。卷积核的大小通常是$3\times 3$或$5\times 5$。 25 | 下图是一个卷积运算的示意图: 26 | 27 | ![picture 0](images/9ee5c9b3e312cca4ecd53ef2759dae7d3cba5a083f65c353a6c85935bd7ff256.png) 28 | 29 | :::note 30 | 31 | 卷积的运算过程就是相应区域的数字相乘后相加 32 | 33 | ::: 34 | 35 | 有图像处理相关经验的同学应该知道,一个图像是有三个通道的。那么理论上卷积运算也应该是对三个通道都要进行卷积运算才行。所以我们就会有三个卷积核分别进行计算。 36 | 37 | ![picture 1](images/80eae29b069a39fd446c2551ab8f8cea396190707e2104d6c868a35eb3735154.png) 38 | 39 | 卷积是一个特征提取的过程,不同的卷积核可以提取图像中不同的特征。 40 | 41 | 本文将介绍如何使用 CUDA 实现一个卷积算子。 42 | 43 | ## 2. 卷积算子的实现 44 | 45 | CUDA 代码是从并发的角度来实现卷积算子的。具体来说,我们将使用一个线程来计算一个输出元素。这样,我们可以使用一个线程块来计算一个输出通道的所有输出元素。 46 | 47 | 首先我们先明确一下我们的任务,我们的任务是给定一个矩阵和一个卷积核,计算卷积运算的结果。矩阵的纬度是 $n \times c \times h \times w$,卷积核的纬度是 $k \times c \times r \times s$。其中, $n$ 是 batch size, $c$ 是通道数, $h$ 是数据高, $w$ 是数据宽, $k$ 是卷积核数量, $r$ 是卷积核高, $s$ 是卷积核宽。一些常用的符号定义如下: 48 | 49 | ```cpp 50 | int n; // batch szie 51 | int c; // 通道数 52 | int h; // 数据高 53 | int w; // 数据宽 54 | int k; // 卷积核数量 55 | int r; // 卷积核高 56 | int s; // 卷积核宽 57 | int u; // 卷积在高方向上的步长 58 | int v; // 卷积在宽方向上的步长 59 | int p; // 卷积在高方向上的补边 60 | int q; // 卷积在宽方向上的补边 61 | 62 | int out_h; // 卷积在高方向上的输出大小 63 | int out_w; // 卷积在宽方向上的输出大小 64 | ``` 65 | 66 | 卷积运算的结果的纬度是 $n \times k \times out_h \times out_w$。 $out_h$ 和 $out_w$ 是卷积运算的结果的高和宽它们的计算代码如下: 67 | 68 | ```cpp 69 | out_h = (h + 2 * p - r) / u + 1; 70 | out_w = (w + 2 * q - s) / v + 1; 71 | ``` 72 | 73 | 首先我们先想一下如何组织线程来进行计算,我们可以用一个线程来计算一个输出元素。这样,我们可以使用一个线程块来计算一个输出通道的所有输出元素。输入的矩阵是一个 4 维矩阵,我们可以用二维的 grid 和二维的 block: 74 | 75 | ```cpp 76 | // 定义线程块的大小 77 | const int blockDim_x = 16; 78 | const int blockDim_y = 16; 79 | 80 | // 计算网格中线程块的数量 81 | const int gridDim_x = (out_h * out_w + blockDim_x - 1) / blockDim_x; 82 | const int gridDim_y = (k + blockDim_y - 1) / blockDim_y; 83 | ``` 84 | 85 | gridDim_x 和 gridDim_y,分别表示在 x 和 y 方向上需要的线程块数量。 86 | 87 | 对于输出特征图的大小,我们需要考虑每个输出像素需要进行卷积操作,因此使用 out_h * out_w 计算总的输出像素数量。将总的输出像素数量除以每个线程块中的线程数量,即 blockDim_x 和 blockDim_y,可以得到在 x 和 y 方向上需要的线程块数量。由于 GPU 线程块的数量必须是整数,所以我们使用 `(total_pixels + blockDim - 1) / blockDim` 来确保线程块数量足够覆盖所有像素,并且没有多余的线程块。 88 | 89 | 接下来我们来看一下卷积运算的实现代码, 首先我们先需要获取线程的索引: 90 | 91 | ```cpp 92 | // 根据线程在二维网格中的位置计算出线程在一维线程空间中的全局唯一索引 93 | int x = blockIdx.x * blockDim.x + threadIdx.x; 94 | int y = blockIdx.y * blockDim.y + threadIdx.y; 95 | // 表示当前处理的 batch 中的索引 96 | int z = blockIdx.z; 97 | ``` 98 | 99 | 检查当前线程是否超出了工作范围。如果超出了输出特征图的范围或者卷积核的数量,或者超出了 batch 的数量: 100 | 101 | ```cpp 102 | if (x >= out_h * out_w || y >= k || z >= n) 103 | { 104 | return; 105 | } 106 | ``` 107 | 108 | 下面我们需要明确几个坐标,首先是这个线程计算的是输出特征图的哪一个像素,然后是这个像素对应的输入特征图的哪一个像素, 109 | 110 | ```cpp 111 | // 计算当前线程处理的输出特征图中的索引 112 | int pos_out_h = x / out_w; 113 | int pos_out_w = x % out_w; 114 | 115 | // 计算输入数据的坐标 116 | int pos_ori_h = pos_out_h * u - p; 117 | int pos_ori_w = pos_out_w * v - q; 118 | 119 | // 计算输入数据的偏移量,即输入数据中当前像素的起始位置 120 | int in_offset = z * c * h * w + pos_ori_h * w + pos_ori_w; 121 | // 计算卷积核的偏移量,即卷积核中当前通道的起始位置 122 | int weight_offset = y * c * r * s; 123 | // 计算输入数据中通道之间的偏移量 (输入数据的大小是 n * c * h * w) 124 | int in_channel_offset = h * w; 125 | // 计算卷积核中通道之间的偏移量 (卷积核的大小是 k * c * r * s) 126 | int weight_channel_offset = r * s; 127 | ``` 128 | 129 | 在理清楚了坐标之后,我们就可以开始计算卷积运算的结果了,计算过程很简单就是把卷积核和输入特征图对应位置的元素相乘然后求和: 130 | 131 | ```cpp 132 | float sum = 0.0f; 133 | 134 | for (int i = 0; i < r; i++) 135 | { 136 | for (int j = 0; j < s; j++) 137 | { 138 | int pos_real_h = pos_ori_h + i; 139 | int pos_real_w = pos_ori_w + j; 140 | 141 | // 只处理有效的数据点 142 | if (pos_real_h >= 0 && pos_real_w >= 0 && pos_real_w < w && pos_real_h < h) 143 | { 144 | int in_offset_tmp = in_offset; 145 | int wei_offset_tmp = weight_offset; 146 | for (int channel = 0; channel < c; channel++) 147 | { 148 | // 计算卷积和 149 | sum += in[in_offset_tmp + i * w + j] * weight[wei_offset_tmp + i * s + j]; 150 | in_offset_tmp += in_channel_offset; 151 | wei_offset_tmp += weight_channel_offset; 152 | } 153 | } 154 | } 155 | } 156 | ``` 157 | 158 | 最后我们将计算结果写入到输出特征图中: 159 | 160 | ```cpp 161 | // 将计算结果写入到输出特征图中 162 | int out_offset = z * k * out_h * out_w + y * out_h * out_w + x; 163 | out[out_offset] = sum; 164 | ``` 165 | 166 | 编译运行命令如下: 167 | 168 | ```bash 169 | nvcc -o naive_conv naive_conv.cu 170 | ./naive_conv 171 | ``` 172 | 173 | ## 3. 总结 174 | 175 | 本文介绗了卷积运算的定义和卷积运算的实现。卷积运算是一种特殊的线性运算,它是一种对两个实变函数的一种数学运算,表征函数之间的一种重要的运算方式。卷积运算的定义是在实数域上的,但是在深度学习中,我们通常使用离散卷积运算。离散卷积运算的定义是在离散函数上的。卷积运算是一种对图像进行滤波的操作,它可以提取图像的特征。卷积运算的过程是将卷积核与图像进行逐元素相乘,然后将相乘的结果求和。 176 | 177 | CUDA 代码是从并发的角度来实现卷积算子的。具体来说,我们将使用一个线程来计算一个输出元素。这样,我们可以使用一个线程块来计算一个输出通道的所有输出元素。 178 | 179 | ## References 180 | 181 | 1. https://space.keter.top/docs/deep_learning/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C 182 | 2. http://cs231n.stanford.edu/ 183 | -------------------------------------------------------------------------------- /docs/12_convolution/01_naive_conv/images/80eae29b069a39fd446c2551ab8f8cea396190707e2104d6c868a35eb3735154.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/12_convolution/01_naive_conv/images/80eae29b069a39fd446c2551ab8f8cea396190707e2104d6c868a35eb3735154.png -------------------------------------------------------------------------------- /docs/12_convolution/01_naive_conv/images/9ee5c9b3e312cca4ecd53ef2759dae7d3cba5a083f65c353a6c85935bd7ff256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/12_convolution/01_naive_conv/images/9ee5c9b3e312cca4ecd53ef2759dae7d3cba5a083f65c353a6c85935bd7ff256.png -------------------------------------------------------------------------------- /docs/12_convolution/02_intro_conv_optimize/README.md: -------------------------------------------------------------------------------- 1 | # 卷积算子优化思路介绍 2 | 3 | 上一篇文章中,我们介绍了卷积算子的简易实现,它是直接模拟卷积操作的过程,这种实现方式的缺点是计算量大,效率低。在本文中,我们将介绍卷积算子的优化思路。 4 | 5 | 卷积算子的主要优化思路就是将卷积运算转换为矩阵乘法运算。进而卷积算子优化问题就转化为了矩阵乘法优化问题卷积算子的主要优化思路就是将卷积运算转换为矩阵乘法运算。进而卷积算子优化问题就转化为了矩阵乘法优化问题。。这篇文章中我们主要介绍一下如何将卷积运算转换为矩阵乘法运算。 6 | 7 | ## 1. 卷积算法映射为矩阵乘法 8 | 9 | 首先我们先来回顾一下卷积算法的定义,假设输入的特征图为 $X$,卷积核为 $K$,输出特征图为 $Y$,$X$ 的大小为 $N \times C_{in} \times H_{in} \times W_{in}$,$K$ 的大小为 $M \times C_{in} \times K_h \times K_w$,$Y$ 的大小为 $N \times M \times H_{out} \times W_{out}$。那么卷积算法的定义如下: 10 | 11 | $$ 12 | Y[n,oc,oh,ow] = \sum_{ic}\sum_{fh}\sum_{fw}X[n,ic,ih,iw] \times K[oc,ic,fh,fw] 13 | $$ 14 | 15 | 其中,$n$ 表示 batch 的索引,$m$ 表示输出特征图的索引,$i$ 和 $j$ 分别表示输出特征图的高和宽的索引。其中 ih, iw 等坐标计算如下: 16 | 17 | ```plain 18 | ih = oh * stride_h + fh - padding_h 19 | iw = ow * stride_w + fw - padding_w 20 | ``` 21 | 22 | 其中,$stride_h$ 和 $stride_w$ 分别表示卷积核的高和宽的步长,$padding_h$ 和 $padding_w$ 分别表示卷积核的高和宽的填充。想要把卷积算法映射为矩阵乘法算法,我们需要使用 `im2col` 算法将输入特征图转换为矩阵,然后使用 `gemm` 算法进行矩阵乘法运算。 23 | 24 | ## 2. im2col 算法 25 | 26 | im2col 就是 image to column 的缩写,它是一种将输入特征图转换为矩阵的算法。im2col 算法的主要思想是将卷积核在输入特征图上滑动,每次滑动的步长为卷积核的步长,然后将卷积核覆盖的区域拉成一个列向量,最后将所有的列向量拼接在一起,就得到了一个矩阵。 27 | 28 | 下面我们通过一个简单的例子来说明 im2col 算法的原理。假设输入特征图的大小为 $4 \times 4$,卷积核的大小为 $3 \times 3$,步长为 1,填充为 0。那么 im2col 算法的过程如下图所示: 29 | 30 | ![im2col](./images/im2col.jpg) 31 | 32 | 从上图中可以看出,im2col 算法的过程就是将卷积核在输入特征图上滑动,每次滑动的步长为卷积核的步长,然后将卷积核覆盖的区域拉成一个列向量,最后将所有的列向量拼接在一起,就得到了一个矩阵。 33 | 34 | 如果我们把这个矩阵记为 $X_{col}$,那么卷积算法就可以表示为: 35 | 36 | $$ 37 | Y = K \times X_{col} 38 | $$ 39 | 40 | 其中,$K$ 表示卷积核,$X_{col}$ 表示输入特征图转换得到的矩阵。 41 | 42 | ## 3. 隐式 gemm 算法 43 | 44 | im2col 算法会把输入特征图转换为一个矩阵,然后保存在内存中。其实我们可以直接在计算的时候不用保存这个矩阵,而是直接计算坐标的偏移量,然后直接从输入特征图中读取数据。这种算法就是隐式 gemm 算法。 45 | 46 | 根据上面的讨论,我们可以把卷积的运算过程,写成一个隐式矩阵乘法 (Implicit GEMM) 的形式: 47 | 48 | ```plain 49 | GEMM_M = OC 50 | GEMM_N = N * OH * OW 51 | GEMM_K = IC * FH * FW 52 | 53 | For i=0 to GEMM_M 54 | oc = i 55 | For j=0 to GEMM_N 56 | accumulator = 0 57 | n = j / (OH * OW) 58 | j_res = j % (OH * OW) 59 | oh = j_res / OW 60 | ow = j_res % OW 61 | For k=0 to GEMM_K 62 | ic = k / (FH * FW) 63 | k_res = k % (FH * FW) 64 | fh = k_res / FW 65 | fw = k_res % FW 66 | ih = oh * stride_h - pad_h + fh 67 | iw = ow * stride_w - pad_w + fw 68 | accumulator = accumulator + x(n, ic, ih, iw) * w(oc, ic, fh, fw) 69 | y(n, oc, oh, ow) = accumulator 70 | ``` 71 | 72 | 上面的代码中,`GEMM_M` 表示输出特征图的通道数,`GEMM_N` 表示输出特征图的大小,`GEMM_K` 表示输入特征图的大小。在这个算法中,我们直接计算坐标的偏移量,然后直接从输入特征图中读取数据,然后进行计算。 73 | 74 | ## 4. 总结 75 | 76 | 总的来说,卷积算子的优化思路就是将卷积运算转换为矩阵乘法运算。这样做的好处是可以利用矩阵乘法的高效实现来提高卷积算子的计算效率。在下一篇文章中,我们将动手实现基于 im2col 算法的卷积算子优化版本。 77 | 78 | ## References 79 | 80 | 1. https://zhuanlan.zhihu.com/p/372973726 81 | 2. https://blog.csdn.net/m0_45388819/article/details/120757424 82 | 3. https://blog.csdn.net/dwyane12138/article/details/78449898 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/12_convolution/02_intro_conv_optimize/images/im2col.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/12_convolution/02_intro_conv_optimize/images/im2col.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/12_convolution/02_intro_conv_optimize/images/im2col.jpg -------------------------------------------------------------------------------- /docs/12_convolution/03_im2col_conv/codes/Makefile: -------------------------------------------------------------------------------- 1 | CC=nvcc 2 | 3 | CXXFLAGS += -DNDEBUG -DUSE_DEFAULT_STDLIB -g 4 | 5 | INCLUDES += -I./include 6 | 7 | LDFLAGS = -gencode arch=compute_75,code=sm_75 -gencode arch=compute_50,code=sm_50 -gencode arch=compute_52,code=sm_52 -gencode arch=compute_60,code=sm_60 -gencode arch=compute_61,code=sm_61, -gencode arch=compute_70,code=sm_70 8 | 9 | # 获取当前目录下的cu文件集,放在变量CUR_SOURCE中 10 | CUR_SOURCE=${wildcard ./src/*.cu} 11 | 12 | # 将对应的cu文件名转为o文件后放在下面的CUR_OBJS变量中 13 | CUR_OBJS=${patsubst %.cu, %.o, $(CUR_SOURCE)} 14 | 15 | EXECUTABLE=conv2ddemo 16 | 17 | all: $(EXECUTABLE) 18 | 19 | $(EXECUTABLE): $(CUR_OBJS) 20 | $(CC) $(CUR_OBJS) $(LDFLAGS) -o $(EXECUTABLE) 21 | 22 | %.o: %.cu 23 | $(CC) -c $< $(CXXFLAGS) $(INCLUDES) -o $@ -Xptxas -v -lineinfo --std=c++11 ${LDFLAGS} 24 | 25 | clean: 26 | rm -f $(EXECUTABLE) 27 | rm -f ./src/*.o -------------------------------------------------------------------------------- /docs/12_convolution/03_im2col_conv/codes/include/conv2d.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONV2D_FWD_HEADER__ 2 | #define __CONV2D_FWD_HEADER__ 3 | 4 | #define __in__ 5 | #define __out__ 6 | #define __in_out__ 7 | 8 | typedef struct 9 | { 10 | float *in; // 输入数据地址 11 | float *weight; // 权值数据地址 12 | float *out; // 输出数据地址 13 | unsigned int n; // batch szie default value 1 14 | unsigned int c; // channel number default value 32 15 | unsigned int h; // 数据高 default value 32 16 | unsigned int w; // 数据宽 default value 32 17 | unsigned int k; // 卷积核数量 default value 32 18 | unsigned int r; // 卷积核高 default value 1 19 | unsigned int s; // 卷积核宽 default value 1 20 | unsigned int u; // 卷积在高方向上的步长 default value 1 21 | unsigned int v; // 卷积在宽方向上的步长 default value 1 22 | unsigned int p; // 卷积在高方向上的补边 default value 0 23 | unsigned int q; // 卷积在宽方向上的补边 default value 0 24 | } problem_t; 25 | 26 | typedef struct 27 | { 28 | unsigned int blockx; // blockx number 29 | unsigned int blocky; // blocky number 30 | unsigned int blockz; // blockz number 31 | unsigned int threadx; // threadx number per block 32 | unsigned int thready; // thready number per block 33 | unsigned int threadz; // threadz number per block 34 | unsigned int dynmicLdsSize; // 动态分配的lds大小,如果不使用动态分配的lds,则该值为0; 35 | void *kernelPtr; // kernel ptr 36 | } kernelInfo_t; 37 | 38 | int getParamsize(__in__ problem_t *problem, __out__ int *paramSize); 39 | int getkernelInfo(__in__ problem_t *problem, __out__ kernelInfo_t *kernelInfo, __in_out__ void *param); 40 | 41 | #endif -------------------------------------------------------------------------------- /docs/12_convolution/03_im2col_conv/codes/include/verfiy.h: -------------------------------------------------------------------------------- 1 | #ifndef __VERFIY_HEADER__ 2 | #define __VERFIY_HEADER__ 3 | 4 | float getPrecision(float tmp) 5 | { 6 | int tmpInt = (int)tmp; 7 | float eNum = 1.0e-6; 8 | if (abs(tmpInt) > 0) 9 | { 10 | while (tmpInt != 0) 11 | { 12 | tmpInt = (int)(tmpInt / 10); 13 | eNum *= 10; 14 | } 15 | } 16 | else 17 | { 18 | 19 | if (tmp == 0) 20 | return eNum; 21 | 22 | eNum = 1.0e-5; 23 | 24 | while (tmpInt == 0) 25 | { 26 | tmp *= 10; 27 | tmpInt = (int)(tmp); 28 | eNum /= 10; 29 | } 30 | } 31 | return eNum; 32 | } 33 | 34 | void conv2dcpu(float *pin, float *pwei, float *pout, int n, int c, int h, int w, int k, int r, int s, int u, int v, int p, int q) 35 | { 36 | int oh = (h + 2 * p - r) / u + 1; 37 | int ow = (w + 2 * q - s) / v + 1; 38 | 39 | for (int nNum = 0; nNum < n; nNum++) 40 | { 41 | for (int kNum = 0; kNum < k; kNum++) 42 | { 43 | for (int i = 0; i < oh; i++) 44 | { 45 | for (int j = 0; j < ow; j++) 46 | { 47 | double sum = 0.0; 48 | int posh = i * u - p; 49 | int posw = j * v - q; 50 | 51 | for (int cNum = 0; cNum < c; cNum++) 52 | { 53 | for (int khNum = 0; khNum < r; khNum++) 54 | { 55 | for (int kwNum = 0; kwNum < s; kwNum++) 56 | { 57 | int posh_ori = posh + khNum; 58 | int posw_ori = posw + kwNum; 59 | if (posw_ori >= 0 && posh_ori >= 0 && posw_ori < w && posh_ori < h) 60 | { 61 | sum += (double)(pin[nNum * c * h * w + cNum * (w * h) + posh_ori * w + posw_ori] * pwei[kNum * r * s * c + cNum * r * s + khNum * s + kwNum]); 62 | } 63 | } 64 | } 65 | } 66 | 67 | pout[nNum * k * oh * ow + kNum * oh * ow + i * ow + j] = (float)sum; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | #endif -------------------------------------------------------------------------------- /docs/12_convolution/03_im2col_conv/codes/job.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | make clean 3 | make 4 | 5 | ./conv2ddemo 128 3 225 225 32 3 3 2 2 0 0 6 | ./conv2ddemo 49 128 35 35 384 3 3 2 2 0 0 7 | ./conv2ddemo 16 128 105 105 256 3 3 2 2 0 0 8 | ./conv2ddemo 128 3 230 230 64 7 7 2 2 0 0 9 | ./conv2ddemo 2 3 838 1350 64 7 7 2 2 0 0 10 | ./conv2ddemo 256 256 28 28 256 2 2 2 2 0 0 11 | ./conv2ddemo 128 3 225 225 32 3 3 1 1 0 0 12 | -------------------------------------------------------------------------------- /docs/12_convolution/03_im2col_conv/images/im2col.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/12_convolution/03_im2col_conv/images/im2col.jpg -------------------------------------------------------------------------------- /docs/12_convolution/04_implicit_gemm/README.md: -------------------------------------------------------------------------------- 1 | # 隐式 GEMM 实现卷积 -------------------------------------------------------------------------------- /docs/12_convolution/05_cutlass_conv/README.md: -------------------------------------------------------------------------------- 1 | # CUTLASS 中的卷积优化策略 -------------------------------------------------------------------------------- /docs/13_continuous_batch/README.md: -------------------------------------------------------------------------------- 1 | # 连续批处理 2 | 3 | ## 1. LLM 推理 4 | 5 | LLM 推理是一个迭代过程,在每个新前馈循环后获得一个额外的完成标记。例如,如果您提示一个句子”What is the capital of California:“,它需要进行十次前馈循环才能得到完整的回答[“S”,“a”,“c”,“r”,“a”,“m”,“e”,“n”,“t”,“o”]。大模型的推理可以大致上分为,Prefill(预填充)和 Generation(生成)两个过程。 6 | 7 | Prefill 阶段主要负责处理用户输入的提示(prompt)。在这一阶段,模型需要对整个提示中的所有 Token 进行 Attention(注意力机制)计算,以理解上下文和语义关系。 8 | 9 | Generation 阶段则负责根据 Prefill 阶段的上下文,逐步生成新的 Token,直至生成结束标志(如 END)。在这一阶段,每生成一个新的 Token,模型仅需对最新生成的 Token 进行 Attention 计算,而无需重新处理整个提示内容。 10 | 11 | ![picture 2](images/5362c0f84bb0cfbf283aa965121077c5dda3517afad7b2bc01c7ba4c7dc683bf.png) 12 | 13 | 14 | 上图显示了一个支持最大序列长度为 8 个标记(T1,T2,……,T8)的假设模型。从 Prompt(黄色)开始,迭代过程逐个生成一个标记(蓝色)。一旦模型生成了一个结束序列标记(红色),生成就结束了。 15 | 16 | Continuous Batching 是另一种内存优化技术,它不需要对模型权重进行修改。之所以需要对内存进行优化是因为 LLM 推理具有以下特点: 17 | 18 | 1. LLM 推理的瓶颈是内存 IO 限制,而不是计算限制。换句话说,目前加载 1MB 的数据到 GPU 所需的时间比 1MB 的数据在 GPU 上计算所需的时间长。这意味着 LLM 推理的吞吐量很大程度上取决于能将多少批数据装入到高速 GPU 内存中; 19 | 2. GPU 内存的消耗量随着基本模型大小和标记长度的增加而增加。如果我们将序列长度限制为 512,那么在一个批处理中,我们最多只能处理 28 个序列;一个序列长度为 2048 则批处理大小最多只能为 7 个序列; 20 | 21 | ## 2. 静态批处理 22 | 23 | 在部署大规模语言模型(如 GPT 系列)时,**Batching(批处理)** 技术是提升推理效率和资源利用率的关键手段。然而,传统的静态 Batch 策略存在一些局限性: 24 | 25 | 1. 固定 Batch 大小:静态 Batch 大小在不同负载下可能不够灵活,导致资源利用不均。 26 | 2. 高延迟:在低负载时,等待达到 Batch 大小的请求可能增加单个请求的延迟。比如图里面一个 Batch 里面黄色的数据很快就处理完了,但是它要等待红色数据处理完了之后才能返回结果。 27 | 3. 内存浪费:不同请求的输入长度差异较大时,静态 Batch 可能导致大量填充(padding)操作,浪费内存和计算资源。 28 | 29 | ![picture 3](images/ccc8652e29ed6f5a96df40da1a28811158923081a804aa04613601e067ca9fe2.png) 30 | 31 | 与传统的深度学习模型不同,由于大型语言模型(LLM)推理的迭代特性,批处理操作会更加复杂。这主要是因为在一个批次中,某些请求可能会比其他请求提前“完成”,但释放其资源并将新请求添加到批次中比较麻烦,因为新请求可能处于不同的完成阶段。这导致 GPU 的利用率下降 [^1],尤其是在批次中的序列生成长度不一致时。例如,右图中序列 1、3 和 4 的结束符之后出现的白色空白。 32 | 33 | :::tip 34 | 35 | 静态批处理中,GPU 的利用率有多低? 36 | 37 | 这取决于批次中序列的生成长度。例如,如果用 LLM 来做分类任务,只生成一个 token。在这种情况下,每个输出序列的大小都是相同的(1 个 token)。如果输入序列的长度也一致(比如 512 个 token),那么静态批处理可以达到最佳的 GPU 利用率。但对于依赖 LLM 的聊天机器人服务来说,输入序列和输出序列的长度并不是固定的。目前,一些专有模型的最大上下文长度已经超过了 8000 个 token。使用静态批处理时,生成输出的长度差异可能会导致 GPU 的严重低效利用。 38 | 39 | ::: 40 | 41 | 下图展示了使用静态批处理的 LLM 推理系统的整体流程。 42 | 43 | ![picture 5](images/1debc7d6ea69981ae98d6182751466a1fe04b94227df015d3be0eee931905e50.png) 44 | 45 | 系统的核心部分是调度器 (Scheduler) 调度的主要职责有 4 点: 46 | 47 | 1. 从队列中取出请求并生成一个批次 48 | 2. 安排 Execution Engine(例如 FasterTransformer)处理这个批次 49 | 3. Execution Engine 通过多次运行模型来处理这个批次的请求 50 | 4. 把生成的文本返回给服务系统 51 | 52 | 图中,系统安排引擎同时处理两个请求(x1: “I think”,x2: “I love”),引擎分别为 x1 生成了 “this is great”,为 x2 生成了 “you”。 53 | 54 | 这样的处理逻辑就会出现上面说到的问题,即 GPU 利用率低下。因为 x1 的生成速度比 x2 慢,x2 生成完之后,x1 还没有生成完,这样就会导致 GPU 有空闲时间。 55 | 56 | 57 | ## 3. Continuous Batching(连续批处理) 58 | 59 | ### 3.1 Orca 60 | 61 | OSDI 2022 上发表的 Orca [^2] 是第一篇解决这个问题的论文。它采用了迭代级调度,其中批大小根据每次迭代确定。一旦批中的一个序列完成生成,就可以在其位置插入一个新的序列,从而实现比静态批处理更高的 GPU 利用率。 62 | 63 | 下面的动图可以很好的说明 Orca 的工作原理: 64 | 65 | ![picture 4](images/c2f7c8c4c1afd0f4cb36b96498b5da9014dcc5af838402bdb95c230fa108a2e5.gif) 66 | 67 | 想要实现上面的调度效果有两个关键问题: 68 | 69 | **难点 1: 如何处理提前完成和新加入的请求问题** 70 | 71 | 现有系统的一个主要问题是,Server System 和 Execution Engine 只有在以下两种情况下交互: 72 | 73 | 1. Server System 在 Engine 空闲时调度下一批请求 74 | 2. Engine 处理完当前批次的请求 75 | 76 | 换句话说,系统按请求的批次调度执行,Engine 会保持一个固定的请求批次,直到所有请求都完成。这在处理生成模型时容易产生问题,因为每个请求所需的迭代次数不同,可能有的请求比其他请求更早完成,这也就是我们前面反复提到的问题。 77 | 78 | 为了解决上述问题,Orca 提出**按迭代的粒度进行调度**。简单来说,调度器重复以下步骤: 79 | 80 | 1. 选择下一批要运行的请求 81 | 2. 调用 Engine 为选中的请求执行一次迭代 82 | 3. 接收该迭代的执行结果 83 | 84 | 由于调度器在每次迭代后都会收到返回结果,它可以检测到请求是否完成,并立即将生成的词汇返回给客户端。对于新到达的请求,它可以在当前迭代结束后被调度,极大减少了排队延迟。通过迭代级调度,调度器完全掌控每次迭代中处理多少个请求和选择哪些请求。 85 | 86 | 下图展示了 ORCA 系统的架构及其基于迭代级调度的工作流程。ORCA 提供了一个入口(例如 HTTPS 或 gRPC),用于接收推理请求并发送响应。这个入口会将新到的请求放入请求池,池负责管理系统中所有请求的生命周期。调度器会监控请求池,并负责从中选择一组请求,安排执行引擎对这些请求进行一次迭代,接收引擎返回的执行结果(即生成的输出),并将每个输出结果追加到相应的请求中。引擎负责执行实际的张量运算。 87 | 88 | ![picture 6](images/8ca864ed5e881bf264fb34c2f1f114ce66bd39f4fa85103ed43ca6c80687d188.png) 89 | 90 | 调度器首先与请求池交互,决定接下来要运行哪些请求,然后调用引擎处理四个已选请求(x1, x2, x3, x4)。对于首次调度的请求,调度器会提供输入给引擎。在图中的例子中,x3 和 x4 尚未运行任何迭代,因此调度器将 x31 和 x32 交给 x3,将 x41、x42 和 x43 交给 x4。引擎运行这些请求的一次迭代,并返回生成的输出(x15, x23, x33, x44),每个请求都会得到一个输出结果。一旦某个请求处理完成,请求池会移除该请求并通知入口发送响应。这样 ORCA 的调度器可以在每次迭代中动态调整处理的请求 91 | 92 | **难点 2: 任意请求的批处理** 93 | 94 | 为了提高效率,执行引擎应该能够批量处理任何选定的请求。如果没有批处理功能,就必须逐个处理每个选定的请求,无法充分利用 GPU 的强大并行计算能力。 95 | 96 | 然而,即使是两个请求(xi, xj),在下一次迭代中它们的执行也未必能合并为批量处理。这种情况有三种: 97 | 98 | 1. 两个请求都处于初始阶段,但输入的 token 数量不同(如上图中的 x3 和 x4) 99 | 2. 两个请求都处于 Decode 阶段,但每个请求正在处理不同索引的 token(x1 和 x2) 100 | 3. 两个请求处于不同阶段:Prefill 阶段或 Decode 阶段(x1 和 x3) 101 | 102 | 要进行批处理,多个请求的执行必须由相同的操作组成,且每个操作的输入张量形状必须一致。对于第一种情况,由于输入 token 数量不同,请求的输入张量的“长度”维度不相等,无法批处理。第二种情况中,Attention 的键和值张量的形状不同,因为每个请求处理的 token 索引不同。第三种情况中,不同阶段的迭代无法批处理,因为它们的输入 token 数量不同;初始阶段的迭代同时处理所有输入 token,而增量阶段的每次迭代只处理一个 token(假设使用 fairseq-style 的增量解码)。 103 | 104 | 只有当两个请求处于相同阶段且输入 token 数量相同时,批处理才适用。在实际工作负载中,这一限制大大降低了批处理的可能性,因为调度器需要等待两个能够同时批处理的请求出现。输入张量 x3 和 x4 可以组成一个形状为[$\sum$L,H] = [5,H]的二维张量,不需要显式的批处理维度。这个张量可以用于所有非 Attention 操作,包括 Linear、LayerNorm、Add 和 GeLU 操作,因为这些操作不需要区分不同请求的张量元素。另一方面,Attention 操作需要区分请求(即需要批处理维度),以便仅计算同一请求的词汇之间的 Attention。 105 | 106 | Ocra 中引入了**选择性批处理机制**技术;它在 Attention 操作中拆分批次,单独处理每个请求,而对其他操作进行基于词汇(而非请求)的批处理,不需要区分请求。 107 | 108 | 下图展示了 选择性批处理机制如何处理一批请求(x1, x2, x3, x4)。 109 | 110 | ![picture 7](images/50b1c9d1d034c2965208d03231ec7a58ddf3facfb5c7a03a21d501809071495e.png) 111 | 112 | 这批请求共有 7 个输入词汇要处理,所以输入张量的形状为 [7,H],然后应用非 Attention 操作。在 Attention 操作之前,插入一个 Split 操作,将张量按请求拆分,并分别对每个请求运行 Attention 操作。Attention 操作的输出通过 Merge 操作重新合并为形状为 [7,H] 的张量,恢复批处理功能,以继续后续操作。 113 | 114 | 为了让 Decode 阶段的请求可以使用前几次迭代中处理的 Attention 键和值,ORCA 维护了一个 Attention 键/值管理器(KV Cache)。该管理器为每个请求分别保存这些键和值,直到调度器明确要求移除某个请求的键和值(例如该请求处理完成时)。Decode 阶段的 Attention 操作(如 x1 和 x2)使用管理器中保存的先前词汇的键和值(如 x1 的 x11, x12, x13;x2 的 x21),并结合当前词汇的查询、键和值(通过 Split 操作产生),以计算当前词汇与之前词汇之间的 Attention。 115 | 116 | 这样,ORCA 通过迭代级调度和选择性批处理机制,实现了高效的 LLM 推理。 117 | 118 | OCRA 还没考虑 KVCache 内存管理优化,它每个序列预先分配 max token 数的作为 KVCache 显存空间。OCRA 的实验都是按照 max token 来生成。后续的工作也对这点进行了优化,下面我们来看看 vLLM 和 LightLLM 的连续批处理算法。 119 | 120 | 121 | ### 3.2 vLLM 中的连续批处理 122 | 123 | vLLM[^3] 在 Iteration-level Batching 时候 prefill 和 decoding 是分开的,一个 Batching step 要么处理 decoding 要么处理 prefill。这样实现比 OCRA 更简单了,prefill 直接调用 xformers 处理计算密集的 prefill attention 计算;decoding 手写 CUDA PageAttention 处理访存密集的 Attention 计算 124 | 125 | :::note 126 | 127 | Page Attention 是一种显存优化技术,我们会在下篇文章中介绍。 128 | 129 | ::: 130 | 131 | 132 | vLLM 和 ORCA 的不同之处在于,vLLM 将 prefill 和 decoding 两个阶段在迭代级别的批处理(Iteration-level Batching)中分离。在每一个批处理步骤中,vLLM 只处理 prefill 或 decoding,而不是像 ORCA 那样在同一个步骤中处理两个阶段。这使得实现更加简单,尤其是在处理复杂的大模型时。 133 | 134 | 不过因为 Prefill 过程会抢占 decoding 的 step 前进,如果输入 prompt sequence length 过长,所有 decoding 过程都需要等待,造成大家更长的延迟,因此留下了一些优化空间。 135 | 136 | ### 3.3 LightLLM 中的连续批处理 137 | 138 | LightLLM 通过将长的 prompt request 分解成更小的块,在多个 forward step 中进行调度,从而让每个 forward 的计算量保持均衡。只有当最后一个块的 forward 计算完成后,整个 prompt request 的生成才结束。而短的 prompt request 则可以用精确的 step 填充计算空隙,以确保所有请求的平均延迟更为稳定。这里我们暂时先只介绍一下 LightLLM 中连续批处理的核心思想,后面有机会我们再结合源码来深入了解。 139 | 140 | ## 4. 总结 141 | 142 | 连续批处理是一种内存优化技术,它不需要对模型权重进行修改。在大型语言模型(LLM)推理中,连续批处理可以提高 GPU 利用率,减少内存浪费,提高推理效率。Orca 是第一篇解决这个问题的论文,它采用了迭代级调度,其中批大小根据每次迭代确定。vLLM 和 LightLLM 也提出了连续批处理的方法,它们在迭代级别的批处理中分离了 prefill 和 decoding 阶段,以简化实现。 143 | 144 | 145 | 146 | [^1]: https://www.anyscale.com/blog/continuous-batching-llm-inference 147 | [^2]: Orca: A Distributed Serving System for Transformer-Based Generative Models 148 | [^3]: vLLM: https://github.com/vllm-project/vllm -------------------------------------------------------------------------------- /docs/13_continuous_batch/images/1debc7d6ea69981ae98d6182751466a1fe04b94227df015d3be0eee931905e50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/images/1debc7d6ea69981ae98d6182751466a1fe04b94227df015d3be0eee931905e50.png -------------------------------------------------------------------------------- /docs/13_continuous_batch/images/32fa966e74825b9b5a8cbbcd850017df74bd36e1a91dff6d27dff6c30f715074.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/images/32fa966e74825b9b5a8cbbcd850017df74bd36e1a91dff6d27dff6c30f715074.png -------------------------------------------------------------------------------- /docs/13_continuous_batch/images/50b1c9d1d034c2965208d03231ec7a58ddf3facfb5c7a03a21d501809071495e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/images/50b1c9d1d034c2965208d03231ec7a58ddf3facfb5c7a03a21d501809071495e.png -------------------------------------------------------------------------------- /docs/13_continuous_batch/images/5362c0f84bb0cfbf283aa965121077c5dda3517afad7b2bc01c7ba4c7dc683bf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/images/5362c0f84bb0cfbf283aa965121077c5dda3517afad7b2bc01c7ba4c7dc683bf.png -------------------------------------------------------------------------------- /docs/13_continuous_batch/images/8ca864ed5e881bf264fb34c2f1f114ce66bd39f4fa85103ed43ca6c80687d188.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/images/8ca864ed5e881bf264fb34c2f1f114ce66bd39f4fa85103ed43ca6c80687d188.png -------------------------------------------------------------------------------- /docs/13_continuous_batch/images/c2f7c8c4c1afd0f4cb36b96498b5da9014dcc5af838402bdb95c230fa108a2e5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/images/c2f7c8c4c1afd0f4cb36b96498b5da9014dcc5af838402bdb95c230fa108a2e5.gif -------------------------------------------------------------------------------- /docs/13_continuous_batch/images/ccc8652e29ed6f5a96df40da1a28811158923081a804aa04613601e067ca9fe2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/images/ccc8652e29ed6f5a96df40da1a28811158923081a804aa04613601e067ca9fe2.png -------------------------------------------------------------------------------- /docs/13_continuous_batch/paper/osdi22-yu.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/13_continuous_batch/paper/osdi22-yu.pdf -------------------------------------------------------------------------------- /docs/14_page_attention/README.md: -------------------------------------------------------------------------------- 1 | # Page Attention - 原理篇 2 | 3 | ## 1. KV-Cache 4 | 5 | 在介绍 Page Attention 之前我们先来介绍一下 LLM 中的 KV-Cache,KV-Cache 是 LLM 中的一个重要组件,它的作用是缓存 Key 和 Value,以减少计算量。 6 | 7 | Attention 机制的计算公式如下: 8 | 9 | $$ 10 | \begin{aligned} 11 | & q_i=W_q x_i, k_i=W_k x_i, v_i=W_v x_i \\ 12 | & a_{i j}=\frac{\exp \left(q_i^{\top} k_j / \sqrt{d}\right)}{\sum_{t=1}^i \exp \left(q_i^{\top} k_t / \sqrt{d}\right)}, o_i=\sum_{j=1}^i a_{i j} v_j 13 | \end{aligned} 14 | $$ 15 | 16 | 其中 $q_i, k_i, v_i$ 分别是 Query, Key, Value,$W_q, W_k, W_v$ 是权重矩阵,$x_i$ 是输入向量,$d$ 是维度。$a_{i j}$ 是第 $i$ 个 Query 和第 $j$ 个 Key 的 Attention 分数,$o_i$ 是第 $i$ 个 Query 的输出。 17 | 18 | 在 Decode 阶段,每次的输出都需要计算一次 Attention,这样会导致计算量过大,KV-Cache 的作用就是缓存 Key 和 Value,以减少计算量。 19 | 20 | :::tip 21 | 22 | KV-Cache 是一种用空间换时间的策略,通过缓存 Key 和 Value,可以减少计算量。 23 | 24 | ::: 25 | 26 | 但是对于 LLM 推理的 Decode 阶段,这个阶段我们事先不知道什么时候生成会停止,也不知道总共会生成多少个 Token,但 KVCache 需要保证在显存中的连续性。因此,我们需要在显存里预留出一片足够大的连续空间。实际场景中训练框架一般会提前申请一份远大于我们实际请求需要的连续显存空间。 27 | 28 | 下图展示了两个请求:请求 A 的最大可能序列长度为 2048, 请求 B 的最大为 512。现有系统中的这种预分配内存块的方式存在三种主要的内存浪费来源: 29 | 30 | 1. 为未来的 token 保留的插槽 31 | 2. 为可能的最大序列长度超额分配而导致的内部碎片 32 | 3. 以及来自内存分配器(如 buddy 分配器)的外部碎片。 33 | 34 | 在处理请求时,外部内存碎片(即由于分配方式产生的小空隙)是不会被用到的,这一点在处理请求之前就已经知道。而内部内存碎片(因为为最大序列长度预留了过多空间)也是浪费的,但只有在请求结束后,我们才会发现这部分空间其实没被用上。也就是说,这两部分内存都白白占用了空间。 35 | 36 | 虽然有些预留的内存最终会被使用,但如果预留的时间太长,特别是当预留的空间很大时,这些内存本可以用于其他请求,结果却被占用了,导致系统效率降低。这就是为什么预留过多内存会造成性能瓶颈。 37 | 38 | ![picture 4](images/6dc1323d485f6649a19fe6cc7b712746de7a8c677ca09fc9b65d504ff5b18c00.png) 39 | 40 | 41 | Page Attention 的目标就是解决这个问题,它可以动态地分配显存,以减少内存浪费。 42 | 43 | ## 2. Page Attention 44 | 45 | 46 | 与传统的注意力算法不同,PagedAttention[^1] 允许将连续的键(key)和值(value)向量存储在不连续的内存空间中。具体来说,PagedAttention 将每个序列的 KV 缓存分成若干 KV 块。每个块包含一定数量的 token 的键和值向量,我们称其为 KV 块大小$(B)$。定义键块为 $K_j=\left(k_{(j-1) B+1}, \ldots, k_{j B}\right)$,值块为 $V_j=\left(v_{(j-1) B+1}, \ldots, v_{j B}\right)$。注意力计算公式可以转化为以下按块计算的形式: 47 | 48 | $$ 49 | A_{i j}=\frac{\exp \left(q_i^{\top} K_j / \sqrt{d}\right)}{\sum_{t=1}^{[i / B\rceil} \exp \left(q_i^{\top} K_t 1 / \sqrt{d}\right)}, o_i=\sum_{j=1}^{\lceil i / B\rceil} V_j A_{i j}^{\top}, 50 | $$ 51 | 52 | 53 | 其中 $A_{i j}=\left(a_{i,(j-1) B+1}, \ldots, a_{i, j B}\right)$ 是在第 $j$ 个 KV 块上的注意力得分的行向量。 54 | 55 | 在注意力计算过程中,PagedAttention 会分别识别和提取不同的 KV 块。 56 | 57 | 下图展示了 PagedAttention 如何存储:键和值向量被分布在三个块中,且这些块在物理内存中是不连续的。在每次计算中,内核将查询 token(例如 “forth”)的查询向量 $q_i$ 与一个块中的键向量 $K_j$(例如块 0 中的 “Four score and seven” 键向量)相乘,计算注意力得分 $A_{i j}$,然后再将 $A_{i j}$ 与值向量 $V_j$ 相乘,得到最终的注意力输出 $o_i$。 58 | 59 | ![picture 5](images/126019db0548c4300504b8d3fd4042eb1387dd153236ed4ea0708aba9fb707af.png) 60 | 61 | 62 | 接下来我们通过一个示例来演示 vLLM 如何在解码单个输入序列时执行 PagedAttention 并管理内存。 63 | 64 | ![picture 6](images/84157a2a4c8202fb88d7e2050d99e2e0c7d93f8d7b674bc760cb7b493af3c975.png) 65 | 66 | vLLM 类似于操作系统中的虚拟内存机制,不需要一开始就为可能生成的最大序列长度预留大量内存。相反,它只为 prefill 阶段生成的 KV 缓存预留必要的 KV 块。比如,当输入提示包含 7 个 token 时,vLLM 会将前两个逻辑 KV 块映射到两个物理 KV 块,并将它们作为一个序列输入到 LLM 中。在计算过程中,vLLM 使用 PagedAttention 内核访问之前的 KV 缓存,并将新生成的 KV 缓存保存到物理 KV 块中。通过在一个 KV 块中存储多个 token,vLLM 能够并行处理更多位置的数据,从而提高硬件利用率并减少延迟。不过,块大小越大,可能会增加内存碎片的问题。 67 | 68 | 随着生成的 token 数量增加,vLLM 会动态为新生成的 KV 缓存分配新的物理块。每个块按顺序填充,只有当前面的块被填满时,才会分配新的物理块。这种方法将每个请求的内存浪费限制在一个块的范围内,因此内存可以更高效地利用,能够容纳更多的请求进入内存,从而提高批处理的吞吐量。当一个请求完成时,它所使用的 KV 块会被释放,供其他请求使用。 69 | 70 | 在具体 Decode 过程中,vLLM 在 prefill 阶段使用传统的自注意力算法生成提示的 KV 缓存,并将前几个 token 的数据存储在逻辑块中,剩余的空位预留给自回归生成阶段。接着,在自回归解码步骤中,vLLM 利用 PagedAttention 算法处理新生成的 token。如果逻辑块已经满了,vLLM 会为新生成的 KV 缓存分配新的物理块,并将映射关系记录在块表中。如此循环下去,vLLM 在每次解码迭代时,会选择合适的请求进行批处理,并动态分配内存空间,从而提高解码效率并最大限度利用内存。 71 | 72 | ![picture 7](images/fc42f820938dcdfed1836bef91dd3b4709bc531061a853181b94ef81d3e6f847.png) 73 | 74 | 在 LLM 的计算过程中,vLLM 会将多个请求和生成阶段的最新 token 合并为一个序列输入 LLM。vLLM 使用 PagedAttention 访问之前存储的 KV 缓存(这些缓存以逻辑 KV 块的形式存储),并将新生成的 KV 缓存保存到物理 KV 块中。将多个 token 存储在一个 KV 块中(块大小 > 1)可以让 PagedAttention 并行处理更多的位置,从而提高硬件利用率并减少延迟。然而,块大小过大也会增加内存碎片问题。 75 | 76 | vLLM 会根据生成的 token 数量动态地将新的物理块分配给逻辑块。所有块都是从左到右依次填充,只有当之前的块全部填满时才会分配新的物理块。通过这种方式,vLLM 将每个请求的内存浪费限制在一个块的范围内,从而更加高效地利用内存。 77 | 78 | 在上图中展示了 vLLM 如何为两个序列管理内存。这两个序列的逻辑块被映射到不同的物理块中,这些物理块由 GPU 工作节点的块引擎预留。相邻的逻辑块不需要在物理 GPU 内存中是连续的,这样可以更有效地利用物理块的空间,让多个序列同时使用。 79 | 80 | ## 3. 总结 81 | 82 | Page Attention 是一种新的注意力机制,它可以动态地分配显存,以减少内存浪费。Page Attention 允许将连续的键(key)和值(value)向量存储在不连续的内存空间中,从而提高硬件利用率并减少延迟。vLLM 通过 PagedAttention 算法处理新生成的 token,动态分配内存空间,提高解码效率并最大限度利用内存。后续我们会再结合源码来看看 Page Attention 的具体实现。 83 | 84 | 85 | [^1]: vLLM: https://github.com/vllm-project/vllm -------------------------------------------------------------------------------- /docs/14_page_attention/images/126019db0548c4300504b8d3fd4042eb1387dd153236ed4ea0708aba9fb707af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/14_page_attention/images/126019db0548c4300504b8d3fd4042eb1387dd153236ed4ea0708aba9fb707af.png -------------------------------------------------------------------------------- /docs/14_page_attention/images/6dc1323d485f6649a19fe6cc7b712746de7a8c677ca09fc9b65d504ff5b18c00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/14_page_attention/images/6dc1323d485f6649a19fe6cc7b712746de7a8c677ca09fc9b65d504ff5b18c00.png -------------------------------------------------------------------------------- /docs/14_page_attention/images/84157a2a4c8202fb88d7e2050d99e2e0c7d93f8d7b674bc760cb7b493af3c975.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/14_page_attention/images/84157a2a4c8202fb88d7e2050d99e2e0c7d93f8d7b674bc760cb7b493af3c975.png -------------------------------------------------------------------------------- /docs/14_page_attention/images/fc42f820938dcdfed1836bef91dd3b4709bc531061a853181b94ef81d3e6f847.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/14_page_attention/images/fc42f820938dcdfed1836bef91dd3b4709bc531061a853181b94ef81d3e6f847.png -------------------------------------------------------------------------------- /docs/15_vllm_page_attention/images/0e01659f51fd207c5aa621724b1d7c378151289e522c7f470318633a65afcfef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/15_vllm_page_attention/images/0e01659f51fd207c5aa621724b1d7c378151289e522c7f470318633a65afcfef.png -------------------------------------------------------------------------------- /docs/15_vllm_page_attention/images/14ff0f0fac048a2447fba166819025f25c4de8b01cf5f9ff98af52c7b8e8b057.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/15_vllm_page_attention/images/14ff0f0fac048a2447fba166819025f25c4de8b01cf5f9ff98af52c7b8e8b057.png -------------------------------------------------------------------------------- /docs/15_vllm_page_attention/images/1dd8fe7fe7ac36f466ba879ae5fd53569dcd9586895fad6b194839d0c473522b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/15_vllm_page_attention/images/1dd8fe7fe7ac36f466ba879ae5fd53569dcd9586895fad6b194839d0c473522b.png -------------------------------------------------------------------------------- /docs/15_vllm_page_attention/images/5b998a6b401dcc4eb415aac1312429f9098d0c51b24babd4e58ad666a5062a45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/15_vllm_page_attention/images/5b998a6b401dcc4eb415aac1312429f9098d0c51b24babd4e58ad666a5062a45.png -------------------------------------------------------------------------------- /docs/15_vllm_page_attention/images/8ee63da47a04bb85bd8592a43216ecaa09cad05c017e10dbb9b2591d6331d536.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/15_vllm_page_attention/images/8ee63da47a04bb85bd8592a43216ecaa09cad05c017e10dbb9b2591d6331d536.png -------------------------------------------------------------------------------- /docs/15_vllm_page_attention/images/91c34c41368261301f36dccc18036bc59e10aa8140dccebf37bc8abe0d38e0e5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/15_vllm_page_attention/images/91c34c41368261301f36dccc18036bc59e10aa8140dccebf37bc8abe0d38e0e5.png -------------------------------------------------------------------------------- /docs/15_vllm_page_attention/images/93f99de459d4457456aea89a2cf47851cb8697b0dc521c4b5cc1fefff5ad180b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/15_vllm_page_attention/images/93f99de459d4457456aea89a2cf47851cb8697b0dc521c4b5cc1fefff5ad180b.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/01_vllm_arch.md: -------------------------------------------------------------------------------- 1 | # vLLM 代码结构介绍 2 | 3 | 在这个系列中,我们将深入解读 vLLM 的源码实现,包括 vLLM 的代码结构、调度策略、PageAttention 的实现等内容。在开始详细介绍之前,本篇将对 vLLM 的代码结构进行一个简要概述,后续我们会逐步深入。 4 | 5 | 在阅读本系列源码解读文章之前,你需要了解连续批处理和 PageAttention 的基础概念。本系列会对这些概念进行简单介绍,但不会深入讲解。 6 | 7 | :::note 8 | 9 | 本系列的代码基于 vLLM 的 0.6.3 版本介绍 10 | 11 | ::: 12 | 13 | 14 | ## 1. vLLM 整体代码架构 15 | 16 | ![picture 0](images/78f8a022abcbe9e15820c974cecf1e82599c121a8f3317c0e34d9b6ecff01be1.png) 17 | 18 | vLLM 的代码架构以 LLMEngine 作为核心组件展开,是整个推理过程的起点。在架构图中,LLMEngine 是处理请求的主要入口,它和 Centralized Controller(中央控制器)在同一个进程中运行,且都位于 CPU 上。LLMEngine 的任务是接收用户请求,并与中央控制器配合,确保请求能够被分配到合适的计算资源中处理。 19 | 20 | ## 1.1 Centralized Controller 21 | 22 | Centralized Controller 实际上是实现了调度功能的模块,叫做 Scheduler(调度器)。它决定每个推理阶段的任务调度,主要负责选择哪些数据需要送到模型去推理,并且管理 KV Cache 的分配。然而,调度器并不直接处理这些物理缓存块的实际存储与管理,只是分配了缓存块的 ID,而数据的实际存储则由分布式 Worker 处理,这些 Worker 分布在 GPU 上。 23 | 24 | 在调度器之下,架构图中展示了 BlockSpaceManager 和 BlockAllocator,它们的任务是管理计算所需的内存块。BlockSpaceManager 是一种抽象管理器,而 BlockAllocator 则是真正参与内存分配的类,它有 CPU 和 GPU 两种类型,用于管理不同设备上的内存块。BlockAllocator 的作用不仅限于 GPU 上的内存管理,还包括当 GPU 显存不足时,将某些缓存块卸载到 CPU 并进行管理,以便稍后再重新加载到 GPU 中。这种内存管理机制确保系统可以处理更大规模的模型而不受显存限制。 25 | 26 | ## 1.2 Distributed Workers 27 | 28 | 右侧的 Distributed Workers 是整个推理任务的执行者,图中绿色部分展示了这些 Worker 的结构。你可以将它们理解为多个 GPU 进程,每个 GPU 都有一个对应的 Worker,它们负责模型的实际加载和推理。架构中称它们为 Worker,但更准确的说法可能是 Executor,因为它不仅执行推理,还对这些 Worker 实例进行整体管理和协调。 29 | 30 | 每个 Worker 中都有两个关键模块:CacheEngine 和 Worker.model(model_runner)。CacheEngine 负责管理实际的缓存数据,确保所有的计算任务都有合适的资源可用。而 Worker.model 则负责模型的加载和执行,利用 PagedAttention 这样的组件来高效处理注意力机制,从而保证推理任务的性能。 31 | 32 | ## 2. vLLM 处理请求的流程 33 | 34 | 在了解了 vLLM 的核心模块之后,我们来看一看当一个请求过来的时候,各个模块是如何协作的。 35 | 36 | ### 2.1 初始化并加载模型权重 37 | 38 | ![picture 1](images/9c5cb31b4b105cc3464579c61568cfe2254914d65e87ee45f896d9de7d5a47ae.png) 39 | 40 | 在具体执行之前,vLLM 需要初始化并加载模型权重。vLLM 支持从 HF Hub 加载模型,也支持从本地加载模型。在加载模型的过程中,vLLM 会将模型权重加载到 GPU 中,以便后续的推理任务可以直接在 GPU 上执行。 41 | 42 | ### 2.1.1 估计 KV Cache 的物理块数量 43 | 44 | 45 | 在模型部署的初始化阶段,vLLM 会通过一个模拟实验步骤来决定 GPU 和 CPU 上可以分配的 KV cache 物理块数量,确保后续推理时的内存分配不会导致显存溢出。这个步骤在 vLLM 中被称为 determine_num_available_blocks。 46 | 47 | 首先,在启动 LLMEngine 时,系统会进行一个 “假数据模拟” 来测量模型的内存使用情况。它通过构造假数据并执行一次模拟前向推理,来观察 GPU 上模型运行时的峰值内存需求。在这次前向推理中,系统不使用 KV cache,而是单纯地模拟模型推理所需的基本内存。这种方式可以帮助确定整个推理过程会占用多少显存,从而为后续的内存分配提供依据。 48 | 49 | 在完成内存需求的测量后,vLLM 会使用测得的内存数据来计算可分配给 KV cache 的显存总量。具体来说,分配给 KV cache 的显存等于 GPU 总显存减去在不使用 KV cache 时推理所占用的显存(包括模型本身和推理过程中的中间数据)。这样可以确保显存分配合理,不会因为内存不足而导致 OOM(Out Of Memory)错误。 50 | 51 | 接下来,通过计算显存中可以分配的物理块数量,vLLM 会确定 GPU 上可以使用的 KV cache 数量。物理块的大小由用户定义,包括多个参数,例如 block_size、num_heads、head_size、num_layers 以及数据类型的大小(如 fp16 对应的字节数是 2)。计算公式会依据这些参数来估算单个物理块的大小,然后根据剩余显存估算出可以分配的物理块总数。 52 | 53 | 总之,determine_num_available_blocks 的主要作用是通过模拟一次推理,来确定 GPU 和 CPU 上的内存可以容纳多少个 KV cache 物理块,从而在推理正式开始之前完成显存的预分配。这种方法确保了系统可以在推理阶段高效地使用内存资源,最大程度地避免因为显存不足而导致的推理失败问题。 54 | 55 | 56 | ![picture 2](images/9dc9cdf7e17cf610cb67772ff00014e715f61b76c8ac42942bfebdeea9658bc4.png) 57 | 58 | ### 2.1.2 预分配 KV Cache 59 | 60 | 在确定好 KV cache 块的大小之后,vLLM 会进行显存的预分配,以确保后续推理过程中有足够的内存来存储 KV cache。这一过程的核心是创建空的张量(empty tensor),并将它们直接分配到 GPU 上,从而锁定一定的显存空间专门用于 KV cache。这种显存预分配的方式能够避免推理过程中频繁的动态内存分配,提升系统的稳定性和推理效率。 61 | 62 | 预分配的显存专门用于 KV cache,因此在 vLLM 初始化后,你可能会注意到显存的占用比单纯加载模型时要多一些。这是因为这些额外的显存已经被预先分配给了 KV cache,确保它们在推理时不会受到其他任务的影响。通过这种显存的预先规划和锁定,系统在处理推理请求时能够更高效地管理内存资源,避免了推理阶段因显存分配不足而出现的瓶颈。 63 | 64 | ![picture 3](images/91ade117f58928c01f48be56534b7807171d278e9524152a081472c5dc920d4f.png) 65 | 66 | 在后续的文章中,我们会详细介绍 vLLM 的内存管理机制,包括 BlockAllocator 的实现细节以及 KV cache 的分配策略。 67 | 68 | ## 2.2 处理 Request 69 | 70 | 在完成了模型的加载和 KV cache 的预分配之后,vLLM 就可以开始处理用户的请求了。 71 | 72 | ### 2.1 请求到达 LLMEngine 73 | 74 | 当用户发送请求时,例如输入了一个文本提示 "The future of Artificial Intelligence",LLMEngine 首先会对请求进行处理。第一步是将输入文本进行 Tokenization(即将文本转换为一系列标识符,称为 tokens),这些 tokens 可以被模型识别和处理。之后,LLMEngine 将这些 tokens 加入到 Scheduler 的等待队列中,准备进一步处理。 75 | 76 | ![picture 4](images/cd1eee13eda9b1f58ecf257a8bd840d6e9152f29fc5e98098eefc740716ca412.png) 77 | 78 | 79 | ### 2.2 调度器的任务 80 | 81 | 在请求进入调度器后,Scheduler 会根据当前的资源情况(如可用的 KV 缓存块)来决定如何执行任务。调度器维护了三个请求队列: 82 | 83 | - Waiting Queue:等待执行的请求。 84 | - Running Queue:当前正在处理的请求。 85 | - Swapped Queue:由于显存不足而被暂时置换出去的请求。 86 | 87 | 调度器会判断是否有足够的内存块可以分配给新的 tokens。如果有足够的可用 KV 块,则请求从等待队列移动到正在运行的队列(waiting → running);如果内存不足,调度器会将一些运行中的请求交换到 CPU 内存(running → swapped),以腾出空间让新请求进入运行队列。 88 | 89 | ![picture 5](images/420c9840542a4e04923ac94d217bcb369b05d504322065c08dedaa96c2a40d5b.png) 90 | 91 | 92 | ### 2.3 Worker 执行推理 93 | 94 | 当请求进入运行队列后,Scheduler 会将任务分发给多个 Worker。每个 Worker 在 GPU 上运行,负责实际的推理计算。在这一过程中,CacheEngine 会按照调度器的指示管理缓存块,包括在 GPU 和 CPU 之间交换内存块,确保内存资源得到高效利用。此外,CacheEngine 还会对共享缓存块执行写时复制(copy-on-write),以确保数据的一致性和高效性。 95 | 96 | ![picture 6](images/076ed826afbadd2bc485676224d5dc7c59871decc9c0b4d9e9aff84560894500.png) 97 | 98 | 99 | ### 2.4 模型的推理过程 100 | 101 | 每个 Worker 中的 Worker.model 模块负责加载并执行模型推理。在这个过程中,它会依赖 PagedAttention 来实现高效的注意力计算。PagedAttention 是优化的注意力机制实现,适用于大规模的 Transformer 模型,并使用诸如 xformers 或 FlashAttention 等技术来加速推理。 102 | 103 | 此外,模型的其他部分(例如线性层、量化层等)也进行了优化,以便在分布式执行和张量并行的情况下达到最高性能。在推理阶段,Sampler 会负责选择下一个生成的 token,使用贪心算法、随机采样或者 Beam Search 等策略。 104 | 105 | ![picture 7](images/2ed7e89c1704106b1296eeaaf6ab68aca8cb9984b8aaa325337110b6f8e8aaa6.png) 106 | 107 | 108 | ### 2.5 请求的完成与结果返回 109 | 110 | 推理完成后,结果会被发送回 LLMEngine。LLMEngine 会对生成的 tokens 进行 detokenization,将它们转换回可读的文本,并最终将生成的结果流式地返回给用户。这一流程使得生成的结果可以尽快交付给用户,而无需等待整个请求的完全完成。 111 | 112 | 整个请求的处理流程由 LLMEngine 进行协调调度,通过 Scheduler 管理内存和资源的有效利用,Worker 在 GPU 上执行具体的推理计算,最终将结果流式地返回给用户。 113 | 114 | ![picture 8](images/ffb030f398e6377ae5306cfb0fd1bb170ea74c545e9a7076719d01f4f170128c.png) 115 | 116 | 在接下来的文章中,我们将深入介绍 vLLM 的内存管理机制、调度策略以及其他实现细节 117 | 118 | ## 参考文献 119 | 120 | 1. https://zhuanlan.zhihu.com/p/691045737 121 | 2. https://arxiv.org/pdf/2309.06180 122 | 3. https://docs.google.com/presentation/d/1RgUD8aCfcHocghoP3zmXzck9vX3RCI9yfUAB2Bbcl4Y/edit#slide=id.g2716b686c46_0_3 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /docs/16_vllm_source_code/drawio/pic_draw.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/0318150945bc58f42bede6bb9325c06995933ba46d7d1545006ae75aadd7a996.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/0318150945bc58f42bede6bb9325c06995933ba46d7d1545006ae75aadd7a996.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/076ed826afbadd2bc485676224d5dc7c59871decc9c0b4d9e9aff84560894500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/076ed826afbadd2bc485676224d5dc7c59871decc9c0b4d9e9aff84560894500.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/1404d1a5b8f78da8ca1c9bc0abd322ccf6e46ab98468afd76b728747f528f055.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/1404d1a5b8f78da8ca1c9bc0abd322ccf6e46ab98468afd76b728747f528f055.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/171265232857bb86e4f7b5ee3ab64f6c50a135922d773f80eb59a4375dd3152e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/171265232857bb86e4f7b5ee3ab64f6c50a135922d773f80eb59a4375dd3152e.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/21be763ab15d90c836f0312795c9c37aeb1ffa62a12e7babf3500f137ad1f6c0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/21be763ab15d90c836f0312795c9c37aeb1ffa62a12e7babf3500f137ad1f6c0.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/2ed7e89c1704106b1296eeaaf6ab68aca8cb9984b8aaa325337110b6f8e8aaa6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/2ed7e89c1704106b1296eeaaf6ab68aca8cb9984b8aaa325337110b6f8e8aaa6.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/420c9840542a4e04923ac94d217bcb369b05d504322065c08dedaa96c2a40d5b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/420c9840542a4e04923ac94d217bcb369b05d504322065c08dedaa96c2a40d5b.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/78f8a022abcbe9e15820c974cecf1e82599c121a8f3317c0e34d9b6ecff01be1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/78f8a022abcbe9e15820c974cecf1e82599c121a8f3317c0e34d9b6ecff01be1.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/87bb8d271124b0c533b60b8c3cf85480766e9697e024490e76f3b1ea9af06a6c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/87bb8d271124b0c533b60b8c3cf85480766e9697e024490e76f3b1ea9af06a6c.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/91ade117f58928c01f48be56534b7807171d278e9524152a081472c5dc920d4f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/91ade117f58928c01f48be56534b7807171d278e9524152a081472c5dc920d4f.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/9c5cb31b4b105cc3464579c61568cfe2254914d65e87ee45f896d9de7d5a47ae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/9c5cb31b4b105cc3464579c61568cfe2254914d65e87ee45f896d9de7d5a47ae.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/9dc9cdf7e17cf610cb67772ff00014e715f61b76c8ac42942bfebdeea9658bc4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/9dc9cdf7e17cf610cb67772ff00014e715f61b76c8ac42942bfebdeea9658bc4.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/cd1eee13eda9b1f58ecf257a8bd840d6e9152f29fc5e98098eefc740716ca412.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/cd1eee13eda9b1f58ecf257a8bd840d6e9152f29fc5e98098eefc740716ca412.png -------------------------------------------------------------------------------- /docs/16_vllm_source_code/images/ffb030f398e6377ae5306cfb0fd1bb170ea74c545e9a7076719d01f4f170128c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/16_vllm_source_code/images/ffb030f398e6377ae5306cfb0fd1bb170ea74c545e9a7076719d01f4f170128c.png -------------------------------------------------------------------------------- /docs/17_flash_attn/drawio/draw.drawio: -------------------------------------------------------------------------------- 1 | 7V1tc5s4Hv80nmlvph4hISReNkl7d7Pb3e5lb2/vVYbYOKF1TI6QJtlPf+JB2EjCgI0ENqSd1gGD8f/54ae/Zujy4fXvkfd4/yVc+usZBMvXGbqaQWhBCtl/yZG37IjrouzAXRQss0Nge+A6+MvPr+RHn4Ol/5Qfyw7FYbiOg8fywUW42fiLuHTMi6Lwpfy2Vbhelg48ene+dOB64a3lo/8JlvF9dhQDALYn/uEHd/exeObB4+/O7/F07y3Dl53vjD7N0GUUhnH26uH10l8n1CsT5nPF2eLJIn8TN7lg+fWflxfP1ub7n38+B1/Jtz/+WtEPlGS3+eGtn/OvnD9t/MZpEIXPm6Wf3MWaoYuX+yD2rx+9RXL2hXGdHbuPH9b56eJbguSX7368uOdn4ij87l+G6zBK74su0x92xosWOecZUS7yB/Kj2H+t/KpWQUAmen744MfRG3sLvwDBOc4uyuXOwjTny8uWjZZFCH/ffZmJuQTl0nNXfMKWvuxFTuI25Ib9kdv6nPzphsCuA8rkJURBXq7au7SFVBttLWO0XYWbeIey2U9yPFiv+fFNuPF1swFBgQ2OJbOhoPguGwredM4GC8hk95fMqOa/hlF8H96FG2/9aXv0YvEc/Sj4suWSRPnt1T+H4WN+8Jsfx2+5BfGe47DMRkbL6O3P/GbpL/9NfmGKn/969bp78uqNf67IuYv0T8G55Evt5xujQfgcLfw91CJu7tS86M6P95K1QhQif+3FwY/yo3TOVf6cJeVy1nFCp0dvU2K387/nxKelWvJh5T0Ea0bSj7NE4hzv4TElIEJ2wvXwIVgk13qbp8RlPknvSK5K/01v9pTyOLmVhR9ft5/EXt0l/8/nc/5U7FtmD5ad0WUJtq7LNq7sKpurVHZ9Nteh5xk+CJQGqEXwYOsitn2AXS2b0j6tp00p/vRZ5GqHppSnB3WWFPVpR1UxSmawEgNXbUZ3LB9RWb4ZtJO/+DJJO25XM3Lx24xc5Ue3JjH7kC5M4m6ow7i79Hy6Wqj47iyozx5on2p3obK4rLJQFe4jo7bROiQO6jb0qVbCDvUOnoLeqfIuPXr30wx/+n1UuicFJgpvqU5CLG26Zx3iLDUmIdWsqWJmhxqKGmoo6VNDkTENvU61E6S3ApLLVKpy9iC3kXDTESq3yrEaTjpUJUs9kvK1SlJkifBfH9mb34ly9n7MsuPYguxsM6Z66cG6XAM4XnxcZfGhKUsZQeMy37x1cLdhrxeMsD5j0UVC9mDhrT/mJx6C5TKLD332GN5tequEZY9hsIlTEuGLGb5K7sV8T/aoXbHRFdJhGyhMgIqH2ry7OV/xx6giObGsDwmVOF2RRWlTV3w8r6mS1+jqXBWWxdXl7pcNiMRHJRu1aayjW2NXkbdIHPOO7n5MdDf5Wzq6zo+mDn2xDGPBtY9L4y1LUnnZQ5tWeVVRuabXsMgIlwhKdHf7DiRMhezjwc6r97NSM2FvZ+LeX//wE41u3pFI5bN0LnvK5OQmjB68dfn0S07M5LydPWd6cs0SST/6wL7qItjcKa9PDNKH3AYlp3MzVDodMJnc5LcHO4+Wnowjb/O0Yjflt9/4xRtewmhZ/vTdy2+9xfe7VOI/CDSHNi1oDW13+xrvUH4ZPD2uvZzqwWYd7Hzwah168e4D1bvntRCgPz0/3ASCQldE5jfBe0nN93aMci9Qo9+d6KRdNt9QEXAVQdiuTiJd9pse33s4toJSKs5I1tJNf2aH9ytmHRZEcy7U11tstRw0Lrikl36MIu9t5w15hLG989fkwI54ieU6Lkqfm15AHSxIVPYMW/kqvswx5XNwgMyZLOHpwAM0lh14rOwcV07vIIkeRh9rD8indaTWhe0XgVXQkW2/YWCVVUjvQDSxsPFWycbXWHgz+tu0G4adXvVX1Q/To0YdKIXUYAJu7zA3lw5UJ4aIcoN2Q61we21B8cecQG77QG46FFrl5Qx3laA5JHwHFJQA2BAr6jbKOMHVRUGkwoiKNnKz/Jgsy2C/Ldbe01OwEIjWBsJeb5tk+r3sruKQqcOPtUvBpJQJQUfwWESgemYz88v2JF+Y1t0pM6rSndqmhdiu+CCtWR4vIg7Pjc6BVQ4v5xjTGmea/vbVjwJGlqQtYM7D4qYeNivvmq45ICCYK47XqxJ76QKHEP3SiKsDgK7T2nc/3Xx7rx8q1ji9Xa1WcKFMb5fOrcOylW4cP2kQybtGA3ncQYOxMc8Zw5P/f79ZvE/BIWPjP7aGx//q1uT+oP8A4NkN++cb7z+26T30VtLahv5WR4srJBOgiv1VIqAv9MfViDItIsDUf+RCINmB/oWAx9mDi0TFKHQIBR3cdNkiBWo50NzisgTkoYXqelzSFUaaXFiVMmtCsmXOR4ajqi3R2YYgUuUEAQXITVU50VY44U9kSgoS/zNoOfCdCjkg7i0AZY9ENImFzQPD/sSierEg+wCPnVnce9FTbn8575/j1QeqEoXWCKocPpVjp2qAU8Vdqord/HkqC937gFPVqKk6yNQ+vFQ1WGo/UmoPTKoJRqoASBXoqEpolBIXlTLz8KZAMxxTFMbMQYcJltUF3aiYBQRYqoUU2Z+tUDFtuCang4LPuBcDWEi1IMgktthRJfBt2w6VnYaDVqv31XaQgN/bvKpt30G+lTTYoKLx0FVs7NTGxitvUVbNy9z7XGfe58v1rmK2XygAFoVAbA+iRfojq3z4nGFw10mGd6barxisBWxZ/y0VOFWbASCw3gCcwlAtCfs1hKFa1Vj8QSphsNmMUQmxIpAyq4S0i+b/eXjhIiOU9LitExbvBAE264NdczP19JpWaaTTAGyr28Ey1pEnH1QFBDVp9txDW0cTZLAd68VZAkOADLqHDkVsX7dlr+NgvfTzoRRTI5lJhOhm++8hFiFXPxIx9ZWp6CCGIBPVZfzJRZgZnSo5jwGMTi3qa52Hf9kVZxkAbvFinJOuYm202dEWoDoEXAY/lGw8oD3X0EvsL83sTjVQOph3v8zg5VJeGF8WogozURxOv/ZR3SdJCruQHWFEKMaKDFAlOmLO3aHoVFfXSjQchmwsmWz8cp6yIS5Iauoe9InGnjFlAxSNX8YjGiqzYVo2qiPKSTZMyoYYVw5BNuCpycaZhhsioGwA4caeuYmTbBgNRcVW7xCEw9xWHsdsn/TJuQJXnzpig9hwp1DBBqCqF2kDfVrA3HBTRQkxqKoqm8ABS8ym8BY5zkyuLWKfLu2OfLhYUVAJgVkRsMyNIhiKKkqrwlQ7mam4YGvbbofUQl+6XRU2PtWTmN676hFzC8B/uwlOZ72FMWdrdIEFz8ym1VfGpEBaZtO/FLjVGXPXUvBr73a+n6GGEvqqf64Tk2MfplEfLSI6jVvTwgYg1VPcrlLEAzMFk6eBEQKV+1VyiLeGbTqq69TTlNjjYimJ47bVdP6bPkQDUpUu9JC9CwMFJSJCu+H2RBptFGqwnG04RCSQinLYFFetr36CGtRPRjOIkEpCDoWNO5qPIqy/V+thhF2vWLCwgteDmOOyM7rloHHVxRYHTWWuwRwXC+XqW7//PK6QY0P7JiNz1bihxetmYgpEGrlDwxG7cqbs+KbzmREBbA1RBPhQzqEYdDOG2YaNDXO/G9rzB52KqMb0VA66HdQs6NYXc9sG9+Q8gRlW7Va/dDTDSpILhyMFe5SL6o7aNMRqGmJ1ckOsCEFlHcOKCMnoDCvLngYgFev/RO4UZdG2xQ7pTo7brNTRWSHDrt3nflijV8Yx/0hstLiKsrvRwSt5YWaUlRFTncxG3RbTabHhgHvQeZeh3Qvw8PKuPdtXmFxdPm113fVW1+1UGUKVKv8cesuEDmESE/zr45e2yygOCro7UDSHz0UvypBUkcg6eE6hItDWVuo6ZKuQaWP6UWirFCnlq5fazmJppmICah+kPx1FuNSZY4AwdG3LIo5NiZAGUXsOQHqKAEy5Au7GQY5CJ/X5vwMGcE06OU6dZL7w8mJxgkqJKGZKt1VKwTtiiIelk9W9+kknJ50s6eTF4kQdJeZqVmghcueKqoBK8/RFqOcJcBb6WiiVVJHQRAFupryI2j2plZv2mCl24OSPigVO+iPLfbGsswtmCEUQ5DRdy6EReazYKucvXdzogIY2EmiIgOyzTQOPHWhMoDsgoeuUS7KIKMIe08Bjl0oUOzPsqWBYPrMfZ1td7Qb6xDlbD30i/UKfnPGuITMDdLJpAztpuO+i3G5oQqTqEQBHjPyGIABkWEbeDB7VabxQIJuh0J9RNrdQYPh9UTNaKsViFDVcjKgvFKvfPWzCo5YFQceeqoJcuLT3fjlRpe0THnXCo16cJh7VAnzBbaFkfHxYb4BU5YZ8A0YujmPTOCyA2BDEiiqqJQuKvliNmBsY0AtgTMpfba4a/aUvyhFnZ0Rz4g4vGCUTcGhqiJ49cAiWMAoCcAhR6g4KpEAm4NCkk+cOHLLdfcAhZFvOsHRyAg5NOnnuwCFqCVroWlbvwCE+L3AoTY1t53q227fetrEH2bkmOWfrmyS032lKxFx1fDhzrk3NZhTqcY4jl1kM55/KQdeiuo9jhXixWn4LXztwhbh0JxcYXiHOF0WdaykH8c2shqRK1bXtKUSdQtRyiBqdaIiKkLMvbSS2PccDShtddwpejw9eeW++Pnh1Sa/BKz37rpHQwWBur2GC6CJtRK+e4zX5vcnvyX7vROulQsSZurpGqufY2lSvuns4GMjSVjUr1XCfDlYrYJ327VO9ar3br3R7NK6JuumAIjWuq3SvX0KwcVxgiQkLLIu4EdmCo7PA3HWLs1iBFuTj3c1ElrS6STip3mhVb+9inGEroA0TBYRFaieUsGzXYgqYZ30E8NO7CqhalahRARvsSjySymW6vpy42x+BdbSYUtd6Y4/9d8bUEu+su7LpTgXrnR1chJA0mdJPD+O0zT9/3820M7dV/g6Oyd8lTK7xfY2lEjaVbSrki1VKEFBHl011WyFAe6S/Brg8InROxPIKUA3ugFSR5GlkygFzHKf6yijrK9enF3Zi0e84liK3IyYjS7dV8/QoK1iFQ5Cso2QKWa7MuKDNFckLhZTdVI1WD/bGBL6PvcQE0etITLGvXLezgqMjMAVDRc5lmCmtdqDUFx80g+xoCBKwheQgQRG3WY5qQ11tjIF8f9Wpz3lMn9PNrV5tnzN3SL31Od0G25iecp/TcWxZzRQIHxfNoULPXF0NFxbWTrH4FIs3isXZ6zhYL3328mvj+Q/DjtUJQIJOqmJ1ZDJYh3zqz6SRk0aeL/rAIUByiAMAIEAwsMUhvcaPEDTFyUHgHBk/ppdW752urvETKvRvnDxSqmoKyBfkBr9ybzQkVhLKF7AX2UN32jdgJnFyApMTaOQE/NfHd9czfJEsEgR/S75d+qTwIhVazAQEfEgeL3y5eTDkKLoM0RxX6Nsrq0aGQ7TauZgVM3lYsrmSefp7QuMhzuLRyFZqiWzlc6132Mptb6nkRHVx1ZraUpPNbWZz/7j5dnqWlHCURjGmBANFvG3YlnIdH0rAfZKF3qKfWB+oQ/fIQP1II3vmhV5pJJfDp3D2to4T8iEMfTe5+gDBJKAvqc5AsQoEYxttcFkN0J6m28ESZyi8Ramt0sEZV3RHqoGBihmN+njC1fKUFCWdeNBRj0ocF6DC79nUbCMYmENQDgO70ogNhvXCHH5omCgJConIEwWeyDBEgjNhylenfLUmX/31FBfQURvNbbdyqYZjyfAJw7krbIAuH8nSDWn8swOE9fmNV+jwUT+Vd9K8aANCVSFwnGx1iIxbIhY4jLPEpjV30s7ZBnDPkXCWWqCAmhUZGMSHrqsrBoLtuZd27jaoLY2Fu1gANu3UetuyVrqXa0nLL7WztlUJ6/QmKUrbB/U//q2Im06jQqWFCaosj4edhnDwEz53SvIaJnmnl+JZAJC5O+gcD6lyvHqsR46V3N2nqfONoLBqKvG/H5denMhRHLJ/fh0brsQVgxUVottVSBBsL0Hs1yhMiL8NdZjy3H8Jl37yjv8D -------------------------------------------------------------------------------- /docs/17_flash_attn/images/5dfce77ba3c57779bce60c8ebc552aa40304c6c5f36bb2d207d6b102a4d8026e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/17_flash_attn/images/5dfce77ba3c57779bce60c8ebc552aa40304c6c5f36bb2d207d6b102a4d8026e.png -------------------------------------------------------------------------------- /docs/17_flash_attn/images/865f289b12429f1cf8de42cf2b6b019ecaef55ab09cb04c590ca66ac9b9f9ce7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/17_flash_attn/images/865f289b12429f1cf8de42cf2b6b019ecaef55ab09cb04c590ca66ac9b9f9ce7.png -------------------------------------------------------------------------------- /docs/17_flash_attn/images/9626ab9b79ea64fb08e8f204c67d0e588f6ef384ab788cad38030846a21314c5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/17_flash_attn/images/9626ab9b79ea64fb08e8f204c67d0e588f6ef384ab788cad38030846a21314c5.png -------------------------------------------------------------------------------- /docs/17_flash_attn/images/9bcbef4156d32c8ff9836bafd4132191a1c62eca89b4316c8f78b7c98e22f84b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/17_flash_attn/images/9bcbef4156d32c8ff9836bafd4132191a1c62eca89b4316c8f78b7c98e22f84b.png -------------------------------------------------------------------------------- /docs/17_flash_attn/images/c397679ab7ad09d15680161bc1e2344c62a97161daf94c08cd9a1766036d05f2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/17_flash_attn/images/c397679ab7ad09d15680161bc1e2344c62a97161daf94c08cd9a1766036d05f2.png -------------------------------------------------------------------------------- /docs/img/kernel-as-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/img/kernel-as-function.png -------------------------------------------------------------------------------- /docs/img/kernel-execution-on-gpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/img/kernel-execution-on-gpu.png -------------------------------------------------------------------------------- /docs/img/memory-hierarchy-in-gpus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/docs/img/memory-hierarchy-in-gpus.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # CUDATutorial 2 | 3 | 从零开始学习 CUDA 高性能编程,从入门到放弃,哦不!一起来边学习,边打笔记,日拱一卒! 4 | 5 | ![Overview](./img/kernel-execution-on-gpu.png) 6 | 7 | ## 学习路线 8 | 9 | ### 新手村系列 🐸 10 | 11 | + [构建 CUDA 编程环境](/build_dev_env) 12 | + [手写第一个 Kernel](/first_kernel) 13 | + [nvprof 性能分析](/nvprof_usage) 14 | + [尝试第一次优化 Kernel](/first_refine_kernel) 15 | + [打印线程号相关信息](/what_my_id) 16 | + [CUDA 编程模型](/prev_concept) 17 | 18 | ### 初阶系列 ⚔ 19 | 20 | + [初识多线程并行计算](/intro_parallel) 21 | + [手写实现矩阵乘 Matmul](/impl_matmul) 22 | + [矩阵乘 Matmul 性能优化实践](/optimize_matmul) 23 | 24 | ### 中阶系列 🚀 25 | 26 | + [手写实现 Reduce](/impl_reduce) 27 | + [Reduce 性能优化实践](/optimize_reduce) 28 | + [Reduce 性能优化实践—交叉寻址](/optimize_reduce/interleaved_addressing) 29 | + [Reduce 性能优化实践—解决 Bank Conflict](/optimize_reduce/bank_conflict) 30 | + [Reduce 性能优化实践—解决空闲线程](/optimize_reduce/idle_threads_free) 31 | + [Reduce 性能优化实践—展开最后一个 warp](/optimize_reduce/unroll) 32 | + [GEMM 优化专题-二维 Thread Tile 并行优化](/gemm_optimize/tiled2d) 33 | + [GEMM 优化专题-向量化访存](/gemm_optimize/vectorize_smem_and_gmem_accesses) 34 | + [GEMM 优化专题-warp tiling](/gemm_optimize/warptiling) 35 | + [GEMM 优化专题-双缓冲](/gemm_optimize/double_buffer) 36 | + [GEMM 优化专题-解决 Bank Conflict](/gemm_optimize/bank_conflicts) 37 | + [卷积算子优化专题-卷积算子简易实现](/convolution/naive_conv) 38 | + [卷积算子优化专题-卷积算子优化思路介绍](/convolution/intro_conv_optimize) 39 | + [卷积算子优化专题-im2col + gemm 实现卷积](/convolution/im2col_conv) 40 | + [卷积算子优化专题-隐式 GEMM 实现卷积](/convolution/implicit_gemm) 41 | + [卷积算子优化专题-CUTLASS 中的卷积优化策略](/convolution/cutlass_conv) 42 | 43 | 44 | 45 | ### 高阶系列 ✈️ 46 | 47 | + 页锁定和主机内存 48 | + CUDA 流和多流使用 49 | + 使用多个 GPU 计算 50 | + ...(补充中) 51 | 52 | ### 大师系列 💡 53 | 54 | 我现在还不知道写啥,毕竟我现在还是菜鸡~~ 55 | 56 | ### LLM 推理技术 🤖 57 | 58 | + [FlashAttention v1 - 原理篇](/flash_attn/flash_attn_v1_part1) 59 | + [FlashAttention v1 - 实现篇](/flash_attn/flash_attn_v1_part2) 60 | + [连续批处理](/continuous_batch) 61 | + [Page Attention - 原理篇](/page_attention) 62 | + [Page Attention - 源码解析](/vllm_page_attention) 63 | + [vLLM 源码解读系列 - vLLM 代码架构介绍](/vllm_source_code/vllm_arch) 64 | + [vLLM 源码解读系列 - 调度前的预处理工作](/vllm_source_code/preprocess_before_scheduler) 65 | + [vLLM 源码解读系列 - 调度器策略](/vllm_source_code/scheduler) 66 | + [vLLM 源码解读系列 - vLLM BlockManager - NaiveBlockAllocator](/vllm_source_code/block_manager_part1) 67 | + [vLLM 源码解读系列 - vLLM BlockManager - PrefixCachingBlockAllocator](/vllm_source_code/block_manager_part2) 68 | -------------------------------------------------------------------------------- /img/kernel-as-function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/img/kernel-as-function.png -------------------------------------------------------------------------------- /img/kernel-execution-on-gpu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/img/kernel-execution-on-gpu.png -------------------------------------------------------------------------------- /img/memory-hierarchy-in-gpus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaddleJitLab/CUDATutorial/919a26d985641c8ca4b4cf7d9140da3fbb6a17ec/img/memory-hierarchy-in-gpus.png --------------------------------------------------------------------------------