Skip to content

Latest commit

 

History

History
191 lines (122 loc) · 10.8 KB

File metadata and controls

191 lines (122 loc) · 10.8 KB

对 Decoding (chat) models 推理性能调优

安利姐妹篇 对 prefill only models 推理性能调优

引言

  1. 首字延迟 & 生成延迟
  • Decoding (chat) models 推理过程可以分成,预填充 (Prefill) 和解码 (Decoding) 两个阶段
  • 预填充 (Prefill) 阶段对应于输出第一个词的的,需要将提示词每个词的中间结果都写入 kv cache 里,才能推理得到第一个词。 这个阶段是对 kv cache 进行预填充。
  • 首字延迟(First Token Latency、Time To First Token (TTFT))也就是系统生成第一个字符所需的响应时间
  • 解码 (Decoding) 阶段对应于输出之后的 token 的输出。 kv cache 已经填入之前 token。 推理过程就是从下往上过一遍模型,最后从 lm_head 输出 logits 采样。
  • 生成延迟(Decoding Latency、Time Per Output Token (TPOT))之后的字符响应时间。
  1. 推理速度极限
  • 现代 CPU/GPU 的 ALU 操作(乘法、加法)内存 IO 速度要快得多,transformer 这种只需要对每个元素执行两次操作的场景,必定受到访存带宽的限制。
  • 以 NVIDIA GeForce RTX 4090 为例,1008 GB/s 显存带宽和 83 TFLOPS,Flop:byte = 82:1,算力非常充足。
  • 对于解码 (Decoding) 阶段,每输出一个词需要读一遍模型,读取模型的极限就是速度的极限
  • 对于预填充 (Prefill) 阶段,读一遍模型计算提示词中的多个词,无论从 SIMD (Single Instruction Multiple Data) 的角度还是从带宽瓶颈 vs 算力瓶颈的角度都很划算
  • 更多推理速度极限细节情参考 LLM inference speed of light
  1. 显存管理
  1. 大语言模型多用户场景下性能优化技术

如何评估推理性能

wde 使用 Chunked Fill,所以不区分预填充 (Prefill) 和解码 (Decoding)阶段,也就是一个step既有Prefill阶段的任务,也有Decoding阶段的任务。

一个step的执行时间,基本上只与 max_num_batched_tokens 或者叫 chunked_prefill_size 有关, 具体细节参考 DeepSpeed-FastGen

我一般会通过依次采样多个不同的 max_num_batched_tokens,得到相应的吞吐和响应,绘制吞吐延迟曲线评估推理性能

横坐标为单位qps,纵坐标为延迟单位毫秒。延迟低吞吐高表明模型推理性能好,所以吞吐延迟曲线评估右下更好

吞吐量(Throughput)定义和计算比较明确,单位时间里完成的请求

延迟 (Latency)计算口径比较多,下面每张图每个表的口径都可能不太一样,要注意区分

本文以 Qwen2.5-7B-Instruct fp8模型在单张 4090 推理性能举例

离线批量推理优化性能

为了更好的对系统性能观察和调优,记录了以下Metrics。一个请求会输出多个token,也就是一个请求会解码多次,请求的一次解码生命周期如下:

时间戳

  • arrival_ts:进入系统时间戳
  • scheduled_ts:调度器调度时间戳
  • first_scheduled_ts:请求第一次被调度的时间戳
  • inference_begin_ts:执行器执行开始的时间戳
  • inference_end_ts:执行器执行完成的时间戳
  • finish_ts:完成的时间戳

通过时间戳可以计算以下时间

  • waiting_time = arrival_ts - first_scheduled_ts: 请求在调度前队列里等待的时间
  • scheduling2inference = inference_begin_ts - scheduled_ts:从调度到执行的时间
  • inference_time = inference_end_ts - inference_begin_ts:执行器推理的时间
  • latency = finish_ts - scheduled_ts:从调度到完成的时间

离线批量推理情景下 waiting_time 跟总请求量有关,一般不关注,所以有两个延迟口径需要关注:

  • inference_time = inference_end_ts - inference_begin_ts:执行器推理的时间
  • latency = finish_ts - scheduled_ts:从调度到完成的时间

同步调度分析

python -m benchmarks.chat.profiler.profiling_decoding

同步调度代码

使用 chrome://tracing/ 查看 sync-1-1.json ~ sync-1-64.json

图1 对于轻负载,两个线程 cuda kernels launch 肯定比一个效率高,应该能有不错的加速效果

图2 对于重负载,不是每次都会添加新request,而两次解码需要的CPU处理时间非常短,异步调度估计提高有限

图3 放大对于7B-fp8的模型,一个step时间大概14ms,其中cpu部分比如调度预处理后处理才0.3ms

图4 1.5B的模型,一个step时间大概6ms,0.3ms也是不值一提

所以简单的实现异步调度几乎不会提高吞吐, 尤其是考虑到GIL的影响,甚至会导致性能下降

异步调度

异步调度优化QPS的原理请移步 对 prefill only models 推理性能调优

直接对着图说吧

7B-bf16

图5 延迟口径是inference_time

图6 延迟口径是latency

图7 与其他系统 QPS对比

  • max_num_batched_tokens = 32 时,async-2 对比 sync QPS 高 8%
  • max_num_batched_tokens = 768 时 对比 sync QPS 低 2%
  • max_num_batched_tokens = 1024 时出现抢占
  • max_num_batched_tokens = 1536 时 oom
  • 7B-bf16 压力测试对于 24G 的 4090 来说还是太大了
7B-fp8

图8 延迟口径是inference_time

  • simple_async 和 async-1,几乎没有性能提升,甚至有性能下降
  • max_num_batched_tokens = 32 时,async-2 对比 sync QPS高22%
  • max_num_batched_tokens = 1536 时, async-2 对比 sync QPS几乎相同,这时GPU饱和
  • 所以提高GPU利用率最好的办法是加大 batchsize

图9 延迟口径是latency

  • async-2 延迟是 sync 的两倍
  • async-3 对比 async-2 几乎没有QPS提升,但延迟是sync的两倍

图10 与其他系统 QPS对比

7B-fp8 QPS 几乎是 bf16 的两倍,所有测评显示fp8推理对性能没有损失,fp8是最佳的推理选择

3B-bf16

图11 延迟口径是inference_time

图12 延迟口径是latency

图13 与其他系统 QPS对比

  • max_num_batched_tokens = 32 时,async-2 对比 sync QPS 高 23%
  • max_num_batched_tokens = 768 时,async-2 对比 sync QPS 高 5%
  • max_num_batched_tokens = 1024 时,async-2 与 sync 相同
  • max_num_batched_tokens = 1536 时,async-2 对比 sync QPS 低 2%

总结

异步调度对小模型低负载效果比较好,大模型高负载几乎没有效果

未完待续