1
- # CUDA高频面试题汇总/C++笔记/CUDA笔记
1
+ # CUDA高频面试题汇总/C++笔记/CUDA笔记 📔📕📗
2
+
3
+ <div align =' center ' >
4
+ <a href =" https://star-history.com/#DefTruth/Awesome-LLM-Inference&Date " >
5
+ <picture align =' center ' >
6
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=DefTruth/cuda-learn-note&type=Date&theme=dark" />
7
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=DefTruth/cuda-learn-note&type=Date" />
8
+ <img width=450 height=300 alt="Star History Chart" src="https://api.star-history.com/svg?repos=DefTruth/cuda-learn-note&type=Date" />
9
+ </picture >
10
+ </a >
11
+ </div >
2
12
3
13
CUDA 笔记 / 高频面试题汇总 / C++笔记,个人笔记,更新随缘: sgemm、sgemv、warp reduce、block reduce、dot、elementwise、softmax、layernorm、rmsnorm、histogram、relu、sigmoid ...
4
14
5
15
## 0x00 前言
6
- 前段时间参加了一些` 大模型 ` 面试,大部分都要手撕CUDA,因此也整体复习了一遍CUDA优化相关的内容,整理了一些高频题的基本写法,保存在这里也便于日后自己复习。当然,有些代码不一定是最优化解,比如GEMM,想要在面试短短的30分钟内写一个好的` GEMM ` Kernel,是有些难度的。印象比较深刻的是,其中有一场面试2个多小时,一个小时问项目,剩下一个小时在写GEMM,说实话,如果不是事先有准备过一些,直接上手写优化版还是会有点慌。[ 代码文件] ( ./cuda-check/check.cu )
7
- TIPS: 文章整理为方便自己复习,不喜欢的请自动跳过哈。
16
+ 前段时间参加了一些` 大模型 ` 面试,大部分都要手撕CUDA,因此也整体复习了一遍CUDA优化相关的内容,整理了一些高频题的基本写法,保存在这里也便于日后自己复习。当然,有些代码不一定是最优化解,比如GEMM,想要在面试短短的30分钟内写一个好的` GEMM ` Kernel,是有些难度的。印象比较深刻的是,其中有一场面试2个多小时,一个小时问项目,剩下一个小时在写GEMM,虽然写的kernel很一般,但是印象还挺深刻的。[ 代码文件] ( ./cuda-check/check.cu )
17
+ TIPS: 仓库整理的代码为方便自己复习回顾,不喜欢的请自动跳过哈。
18
+
8
19
## 0x01 高频面试题汇总简介
20
+ <div id =" kernellist " ></div >
21
+
9
22
相关kernel如下:
10
- - [x] sgemm naive, sgemm + block-tile + k-tile + vec4
11
- - [x] sgemv k32/k128/k16 kernel
12
- - [x] warp/block reduce sum/max, block all reduce + vec4
13
- - [x] dot product, dot product + vec4
14
- - [x] elementwise, elementwise + vec4
15
- - [x] histogram, histogram + vec4
16
- - [x] softmax, softmax + vec4 (grid level memory fence)
17
- - [x] safe softmax, safe softmax + vec4
18
- - [x] sigmoid, sigmoid + vec4
19
- - [x] relu, relu + vec4
20
- - [x] layer_norm, layer_norm + vec4
21
- - [x] rms_norm, rms_norm + vec4
23
+ - [x] [ sgemm naive, sgemm + block-tile + k-tile + vec4] ( #sgemm )
24
+ - [x] [ sgemv k32/k128/k16 kernel] ( #sgemv )
25
+ - [x] [ warp/block reduce sum/max] ( #warpreduce )
26
+ - [x] [ block all reduce + vec4] ( #blockallreduce )
27
+ - [x] [ dot product, dot product + vec4] ( #dot )
28
+ - [x] [ elementwise, elementwise + vec4] ( #elementwise )
29
+ - [x] [ histogram, histogram + vec4] ( #histogram )
30
+ - [x] [ softmax, softmax + vec4 (grid level memory fence)] ( #softmax )
31
+ - [x] [ safe softmax, safe softmax + vec4] ( #safesoftmax )
32
+ - [x] [ sigmoid, sigmoid + vec4] ( #sigmoid )
33
+ - [x] [ relu, relu + vec4] ( #relu )
34
+ - [x] [ layer_norm, layer_norm + vec4] ( #layernorm )
35
+ - [x] [ rms_norm, rms_norm + vec4] ( #rmsnorm )
36
+ - [x] [ nms] ( #NMS )
22
37
- [ ] sgemm + double buffer
23
38
- [ ] sgemm + fp16
39
+ - [ ] ...
40
+
24
41
42
+ 题内话,大模型相关的岗位,手撕CUDA的概率非常大,leetcode反而写的少,就前段时间个人的经验,基本是4:1的比例,还是建议好好复习下CUDA。当然,这些只是最简单的kernel实现,比如flash_attn,FMHA这些优化手段,就不在这里写了,面试中基本都会问到。后边有空再补档一些文章吧。
25
43
26
- 题内话,大模型相关的岗位,手撕CUDA的概率非常大,leetcode反而写的少,就前段时间个人的经验,基本是4:1的比例,还是建议好好复习下CUDA。当然,这些只是最简单的kernel实现,比如flash_attn,FMHA, FMHCA这些优化手段,就不在这篇文章里写了,面试中基本都会问到。后边有空再补档一些文章吧。
27
- ## 0x02 sgemm naive, sgemm + block-tile + k-tile + vec4
44
+ ## 0x02 sgemm naive, sgemm + block-tile + k-tile + vec4 ( [ ©️back👆🏻 ] ( #kernellist ) )
45
+ < div id = " sgemm " ></ div >
28
46
29
47
``` c++
30
48
#include < stdio.h>
@@ -171,7 +189,12 @@ __global__ void sgemm_thread_tile_vec4(
171
189
}
172
190
}
173
191
```
174
- ## 0x03 warp/block reduce sum/max
192
+ 这里gemm的实现比较简单,只使用了CUDA Cores,并且只实现Block Tile + K Tile以及Block Tile + K Tile+Thread Tile+向量化的版本。主要在于如何加载gmem中的数据到smem,也就是把全局内存中的数据索引mapping到共享内存中的。核心思维:把一个block中的线程id按照线性来理解,然后把这个线性的id和全局内存索引以及共享内存索引进行匹配。比如Block Tile + K Tile的实现,block内一共32x32个Threads,需要加载到smem的数据也是32x32,那么,最简单的做法,只需要每个线程加载一个互不重复数据即可。NOTE,本文的gemm kernel修改自:[紫气东来:CUDA(三):通用矩阵乘法:从入门到熟练](https://zhuanlan.zhihu.com/p/657632577)
193
+
194
+
195
+ ## 0x03 warp/block reduce sum/max ([©️back👆🏻](#kernellist))
196
+ <div id="warpreduce"></div>
197
+
175
198
```C++
176
199
// Warp Reduce Sum
177
200
template<const int kWarpSize = WARP_SIZE>
@@ -227,8 +250,11 @@ __device__ __forceinline__ float block_reduce_max(float val) {
227
250
return val;
228
251
}
229
252
```
253
+ warp reduce几乎已经成为大部分reduce kernel的标准写法了,比如vLLM中,就是这种经典的写法。所以,先搞懂warp reduce(也就是搞懂各种warp functions的用法),再去写其他kernel,思路就会容易很多。需要注意的是,warp函数处理的是寄存器上的数据,也就是说,此时,没必要先加载数据到smem,再进行reduce,直接加载到寄存器即可(以前犯过这个小错误...)。Warp Functions建议参考:[ jhang:CUDA编程入门之Warp-Level Primitives] ( https://zhuanlan.zhihu.com/p/572820783 )
254
+
255
+ ## 0x04 block all reduce + vec4 ([ ©️back👆🏻] ( #kernellist ) )
256
+ <div id =" blockallreduce " ></div >
230
257
231
- ## 0x04 block all reduce + vec4
232
258
``` c++
233
259
// Block All Reduce Sum
234
260
// grid(N/128), block(128)
@@ -280,8 +306,11 @@ __global__ void block_all_reduce_sum_vec4(float* a, float* y, int N) {
280
306
if (tid == 0) atomicAdd(y, sum);
281
307
}
282
308
```
309
+ block all reduce是在warp reduce的基础上进行的,reduce_smem这部分的共享内存申请无法避免,这是用来同步每个warp之间得到局部结果。注意,最后,还需要atomicAdd做一个block级别的原子操作,以得到全局的和。float4向量化优化访存,可以减缓WarpScheduler发送指令的压力。
310
+
311
+ ## 0x05 sgemv k32/k128/k16 kernel ([©️back👆🏻](#kernellist))
312
+ <div id="sgemv"></div>
283
313
284
- ## 0x05 sgemv k32/k128/k16 kernel
285
314
```C++
286
315
// SGEMV: Warp SGEMV K32
287
316
// 假设K为32的倍数,每个warp负责一行
@@ -359,8 +388,11 @@ __global__ void sgemv_k16(float* A, float* x, float* y, int M, int K) {
359
388
}
360
389
}
361
390
```
391
+ 估计有些大佬倒立都能写sgemv的各种优化版了,核心思路其实也是基于warp reduce,考虑K的不同情况进行优化。本文的sgemv kernel修改自:[ 有了琦琦的棍子:深入浅出GPU优化系列:gemv优化] ( https://zhuanlan.zhihu.com/p/494144694 )
392
+
393
+ ## 0x06 dot product, dot product + vec4 ([ ©️back👆🏻] ( #kernellist ) )
394
+ <div id =" dot " ></div >
362
395
363
- ## 0x06 dot product, dot product + vec4
364
396
``` c++
365
397
// Dot Product
366
398
// grid(N/128), block(128)
@@ -414,8 +446,11 @@ __global__ void dot_vec4(float* a, float* b, float* y, int N) {
414
446
if (tid == 0) atomicAdd(y, prod);
415
447
}
416
448
```
449
+ dot product kernel的核心就是block reduce,不多说了。
450
+
451
+ ## 0x07 elementwise, elementwise + vec4 ([©️back👆🏻](#kernellist))
452
+ <div id="elementwise"></div>
417
453
418
- ## 0x07 elementwise, elementwise + vec4
419
454
```c++
420
455
// ElementWise Add
421
456
// grid(N/128), block(128)
@@ -442,8 +477,11 @@ __global__ void elementwise_add_vec4(float* a, float* b, float* c, int N) {
442
477
}
443
478
}
444
479
```
480
+ elementwise可以考虑加点向量化进行访存优化。
481
+
482
+ ## 0x08 histogram, histogram + vec4
483
+ <div id =" histogram " ></div >
445
484
446
- ## 0x08 histogram, histogram + vec4
447
485
``` c++
448
486
// Histogram
449
487
// grid(N/128), block(128)
@@ -467,8 +505,11 @@ __global__ void histogram_vec4(int* a, int* y, int N) {
467
505
}
468
506
}
469
507
```
508
+ 统计频数直方图,很简单,两行代码搞定。
509
+
510
+ ## 0x09 softmax, softmax + vec4 (grid level memory fence) ([©️back👆🏻](#kernellist))
511
+ <div id="softmax"></div>
470
512
471
- ## 0x09 softmax, softmax + vec4 (grid level memory fence)
472
513
```c++
473
514
// Softmax x: N, y: N
474
515
// grid(N/128), block(K=128)
@@ -540,8 +581,11 @@ __global__ void softmax_v2_vec4(float* x, float* y, float* total, int N) {
540
581
}
541
582
}
542
583
```
584
+ softmax稍微要注意的就是内存同步的问题,这里,你需要做一个网格级别的同步,而不能仅仅是block级别,否则拿不到全局的exp sum作为分母项。因此使用 __ threadfence 这个网格及内存同步操作。不过效率我还没测过,实在要高效的话,可能得整成FA2那样的 1-pass + online softmax的实现。不过,如果是面试的话,就不要太为难自己了...,但是FA1/FA2的论文很经典,强烈建议多读几遍。
585
+
586
+ ## 0x0a safe softmax, safe softmax + vec4 ([ ©️back👆🏻] ( #kernellist ) )
587
+ <div id =" safesoftmax " ></div >
543
588
544
- ## 0x0a safe softmax, safe softmax + vec4
545
589
``` c++
546
590
// Safe Softmax x: N, y: N
547
591
// grid(N/128), block(K=128)
@@ -561,8 +605,11 @@ __global__ void softmax_safe(float* x, float* y, float* total, int N) {
561
605
if (idx < N) y[ idx] = exp_val / (* total);
562
606
}
563
607
```
608
+ 对比softmax减去一个max值防止数值溢出,比如float16。
609
+
610
+ ## 0x0b sigmoid, sigmoid + vec4 ([©️back👆🏻](#kernellist))
611
+ <div id="sigmoid"></div>
564
612
565
- ## 0x0b sigmoid, sigmoid + vec4
566
613
```c++
567
614
// Sigmoid x: N, y: N y=1/(1+exp(-x))
568
615
// grid(N/128), block(K=128)
@@ -587,7 +634,9 @@ __global__ void sigmoid_vec4(float* x, float* y, int N) {
587
634
}
588
635
```
589
636
590
- ## 0x0c relu, relu + vec4
637
+ ## 0x0c relu, relu + vec4 ([ ©️back👆🏻] ( #kernellist ) )
638
+ <div id =" relu " ></div >
639
+
591
640
``` c++
592
641
// Relu x: N, y: N y=max(0,x)
593
642
// grid(N/128), block(K=128)
@@ -612,7 +661,9 @@ __global__ void relu_vec4(float* x, float* y, int N) {
612
661
}
613
662
```
614
663
615
- ## 0x0d layer_norm, layer_norm + vec4
664
+ ## 0x0d layer_norm, layer_norm + vec4 ([©️back👆🏻](#kernellist))
665
+ <div id="layernorm"></div>
666
+
616
667
```c++
617
668
// Layer Norm: x: NxK(K=128<1024), y': NxK, y'=x-mean(x)/std(x) each row
618
669
// mean(x) = sum(x)/K, 1/std(x) = rsqrtf( sum( (x-mean(x))^2 )/K ) each row
@@ -679,8 +730,11 @@ __global__ void layer_norm_vec4(float* x, float* y, float g, float b, int N, int
679
730
if (idx < N * K) FLOAT4(y[idx]) = reg_y;
680
731
}
681
732
```
733
+ layer norm实现的核心同样也是block reduce和warp reduce,然后再整点向量化...
734
+
735
+ ## 0x0e rms_norm, rms_norm + vec4 ([ ©️back👆🏻] ( #kernellist ) )
736
+ <div id =" rmsnorm " ></div >
682
737
683
- ## 0x0e rms_norm, rms_norm + vec4
684
738
``` c++
685
739
// RMS Norm: x: NxK(K=128<1024), y': NxK, y'=x/rms(x) each row
686
740
// 1/rms(x) = rsqrtf( sum(x^2)/K ) each row
@@ -730,8 +784,11 @@ __global__ void rms_norm_vec4(float* x, float* y, float g, int N, int K) {
730
784
if (idx < N * K) FLOAT4(y[ idx] ) = reg_y;
731
785
}
732
786
```
787
+ rms norm实现的核心同样也是block reduce和warp reduce...,然后再加点float4向量化什么的。
788
+
789
+ ## 0x0d NMS ([©️back👆🏻](#kernellist))
790
+ <div id="NMS"></div>
733
791
734
- ## 0x0d NMS(CV相关的经常会要手撕NMS,也记录下)
735
792
```c++
736
793
struct Box {
737
794
float x1, y1, x2, y2, score;
@@ -764,6 +821,10 @@ void hard_nms(std::vector<Box> &input, std::vector<Box> &output, float iou_thres
764
821
}
765
822
}
766
823
```
824
+ CV相关的经常会要手撕NMS,也记录下。
825
+
826
+ ## 0x0f 总结 ([ ©️back👆🏻] ( #kernellist ) )
827
+ 可以发现,大部分kernel的基本写法都是依赖warp reduce和block reduce的,基本上只要熟练应用warp functions各种场景的写法,应该问题不大;softmax需要考虑网格级同步的问题,或者online softmax以及FlashAttention;sgemm的优化是个很大的课题,不是案例中写的这么简单,但是入门的话,基本就是tiling的思想以及如何做索引之间的mapping;sgemv的优化则主要考虑K不同的值(因为M为1了),比如K=16,64,128等情况下,如何按照warp来处理;relu、sigmoid等都是elementwise的操作,很好实现,可以再考虑加点向量化优化访存;layer norm和rms norm在数学上其实也是挺清晰简单的,落实到cuda kernel时,只要按照逐个token来处理,headdim没有超过1024的情况下(一个block最多可以放1024个threads),可以放到一个block处理,这样并行化就很好写。当然,核心还是warp reduce和block reduce;NMS是乱入的,没有CUDA版本,别问了...
767
828
768
829
## ©️License
769
830
GNU General Public License v3.0
0 commit comments