在工作中如果我们写了一个 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;