在工作中如果我们写了一个 cuda kernel,需要去计算他的理论利用率和理论带宽,这样我们才能知道这个 kernel 还有多少的优化空间。
搞清楚两个定义,tflops 和 memorybandwidth 分别是什么? memory bandwidth 是单位时间 HBM 到寄存器的访问量。
在 llm 中,最重要的一个 kernel 就是 attention,下面针对 causal LM,计算 self attention 在前向过程中的 tflops 和 bandwidth。
首先回顾一下 attention 的计算流程,假设 query,key 和 value 分别为 QKV,那么整体 attention 的计算过程如下:
- 首先计算 attention score \(A= \frac{Q K^T}{\sqrt{d_k}}\),其中 \(\sqrt{d_k}\) 一般是 head_size;
- 接着通过 softmax 归一化将 attention scores 转换成概率
p = softmax(masked_tri(A))
,注意对于 causal LM 的推理流程,我们取下三角; - 使用计算之后的 p 对 V 进行加权获得输出,
O = p * V
。
其中有两个矩阵操作...
一般来说,transformer 都是使用的 MHA(Multi-Head Attention),这里可以对单个 head 进行分析,multi-head 只需要乘上 head_size
即可。
在 LLM 进行推理的过程中有两个不同的 stage,分别是 prefill 和 decode。在 prefill 阶段会一次推理处理所有的 token,而在 decode 阶段则是每次处理一个 token,下面分别分析这两个阶段的 tflops
和 bandwidth
。
在开始分析之前,先约定一下超参数
Symbol | Definition |
---|---|
Scalars | |
\[b\] | Batch size |
\[s\] | Sequence length |
Model Hyper-parameter | |
\[n\] | The number of attention heads |
\[d\] | Hidden state size of one head |
\[h\] | Hidden state size (\(h = n \cdot d\)) |
如何计算 tflops
对于 single-head,假设 QKV 的 size 是 [M, head_size]
,通过 profiling 获得了 attention kernel 的计算耗时是 ms
,计算 tflops
可以使用下面的公式
perf = lambda ms: (
2 # two ops
* 2 # q@k + p@v
/ 2 # causal
* batch_size
* num_heads
* head_size
* seq_len
* seq_len
* 1e-12
/ (ms * 1e-3)
)
下面来解释下为什么?
对于 \(QK^T\) 的计算,他的 flops 是 \(O(bs^2nd)\),其中 b 是 b
如何计算 bandwidth
对于 pre-fill 阶段,计算 bandwidth
可以通过下面的公式
elem_size = q.element_size()
perf = lambda ms: (
elem_size
* batch_size
* (num_q_heads + 2 * num_k_heads)
* head_size
* seq_len
* 1e-9
/ (ms * 1e-3)
)
下面来解释一下为什么?
elem_size
是每一个 data element 的大小,单位是bytes
,比如fp16
就是 2,fp8
或者int8
就是 1;