Profile vllm performance with nvidia-system and nvidia-compute

使用 nsys & ncu 来分析 vllm 性能

大模型这么火,但却发现网上很少有相关的性能分析的资料,绝大部分都是偏理论的性能分析

最近发现了 nvidia 有很好的性能分析工具,nsys 和 ncu,于是决定用来研究下 vllm 的 decode 性能,本文将是一些记录,方便大家更好的学习和使用

1. nsys & ncu 基础

1.1. 简介

在之前的CUDA版本中,所附带的性能分析工具主要是nvvp(NVIDIA Visual Profiler)和nvprof,前者是带有图形界面的分析工具,后者是命令行工具。在CUDA官方文档中有这样一段描述:

Note that Visual Profiler and nvprof will be deprecated in a future CUDA release.The NVIDIA Volta platform is the last architecture on which these tools are fully supported. It is recommended to use next-generation toolsNVIDIA Nsight Systemsfor GPU and CPU sampling and tracing andNVIDIA Nsight Computefor GPU kernel profiling.

所以nvvpnvprof现在已经废弃了,现在nvidia主要的性能分析工具就是nsys(Nsight System)和ncu(Nsight Compute)。

nsys是系统层面的分析工具,nsys 主要用来分析函数热点,产生类似于火焰图之类的数据。

ncu则是用于分析一个具体的核函数,更多的是看核函数执行过程中 GPU 硬件的性能,比如内存带宽和 SM 利用率等等。

两者均有图形界面版本和命令行版本。这2个工具一般都是组合起来一起使用的

1.2. 安装

需要用邮箱注册一下,然后下载:

  1. ncu:https://developer.nvidia.com/tools-overview/nsight-compute/get-started
  2. nsys:https://developer.nvidia.com/nsight-systems/get-started

需要下载 deb 包和 msi 安装包前者安装在容器镜像里面,用于采集 Profile 数据后者安装在 windows 笔记本上(如果你是 mac,就装 mac 版就行),用于分析 Profile 数据比如我在容器里安装的就是:

NsightSystems-linux-cli-public-2024.6.1.90-3490548.deb

nsight-compute-linux-2024.3.2.3-34861637.run

1.3. 基本用法

nsys:

nsys profile -o /tmp/profile.out -f true python3 vllm_offline.py

运行结束会产生一个 profile.out.nsys-rep 文件

用 windows 打开就得到


SplitWise:分离式架构的影响因子分析

在我们实际测试的过程中,我们发现分离式架构受模型和负载的影响其实是非常大的。

不同的模型,不同的负载,收益大小很不一样

  1. 模型:模型大小,开不开 gqa,等
  2. 负载:输入长度、输出长度、请求的 qps,等

那我们怎么知道什么场景下适合用分离式架构呢?

这篇文章我们从 decode 阶段的理论计算上来尝试推导一下,看模型和负载是如何影响分离式架构的收益的

1. Decode 计算和访存需求

参考:FASTDECODE: High-Throughput GPU-Efficient LLM Serving using Heterogeneous

decode 时间理论计算(假设默认开启 GQA = 8,量化 int8,xx):

batch 计算 batch 访存量
S-part 24bh² (12h² + 10bh) * sizeof(int8) = 12h² + 10bh
R-part 4bhs 2bhs * sizeof(int8) = 2bhs

注意:

  1. 12h² + 10bh,不管是大模型还是小模型,10bh耗时占比1/1000,所以10bh可以直接忽略
  2. R-part的计算耗时 4bhs/312,也是要远远小于其他3项,基本可忽略

所以整个 decode 阶段的耗时,可以按如下计算

TBT = \frac{24blh^2}{312} + \frac{12lh^2}{2} + \frac{2blhs}{2 * GQA} = b(\frac{24lh^2}{312} + \frac{2lhs}{2 * GQA}) + \frac{12lh^2}{2}

因此:TBT 是一个bs的线性函数

2. 分离式架构的理论收益公式

2.1 干扰程度

分离收益的前提,取决于干扰的程度,干扰越大,分离后的收益越大假设一个模型:

  1. tbt:单token输出时间
  2. ttft:首token计算时间
  3. s:输入长度
  4. n:输出长度
  5. λ:请求进来的速率

单个请求端到端的时间:e2e = tbt * n + ttft

受干扰的时间比例: β= \frac{e2e * λ * ttft}{e2e} = λ * ttft


ReFT: Reasoning with REinforced Fine-Tuning

最近 openai 发布圣诞系列的第一弹,就强调了强化微调,基于这个,可以让小模型结合行业数据,做到比大模型更强的推理效果

然后研究了下字节之前发过的类似的一篇论文:https://arxiv.org/pdf/2401.08967

 

1. 背景

1.1. 传统的 CoT 训练方法

虚线之上是传统的 CoT 训练方法,就是使用数据集(x, e, y)不断的训练基础模型,让基础模型获得推理能力

比如 gsm8k 数据集:https://huggingface.co/datasets/openai/gsm8k/viewer/main/train?p=1&row=167

这个数据集里面每一行就是一条训练数据,包括3部分:

x就是问题

e就是解决这个问题的思路

y是答案

但是这种训练方法,模型推理的泛华能力是比较弱的,因为它只能学习到一种解题思路,就是数据集中的思路

1.2. ReFT:Reinforced Fine-Tuning

训练思路和 SFT 很不一样同一条数据集,SFT 会反复训练多次,让模型在数据集上误差最小。这样训练出来的模型,对于解决数据集中的问题肯定是没问题的,但是对于解决数据集的其他问题,就不一定是最佳的了。这个时候回答问题的质量取决于数据集的质量和规模ReFT 只需要1~2次预热,得到一个基础的模型,然后通过强化学习,让模型主动去探索不同的解题路径,这样得到的模型,泛化能力是最强的

如上图,ReFT 有2个核心阶段:

  1. warm-up:xx
  2. 强化学习阶段:specifically Proximal Policy Optimization (PPO)

特别注意:ReFT 并不依赖额外的训练数据集通过这个方法,论文使 CodeLLAMA 和 Galactica 模型在GSM8K、MathQA、SVAMP数据集上,泛化能力得到了显著提高


MemGPT: Towards LLMs as Operating Systems

论文:2个版本论文不太一样

  1. v1 https://arxiv.org/pdf/2310.08560v1
  2. v2 https://yiyibooks.cn/arxiv/2310.08560v2/index.html

代码:https://github.com/cpacker/MemGPT

核心亮点:

  1. 在有限的上下文窗口(8k)里,支持无线上下文的“错觉”,属于长文的一种解决方案
  2. 对智能体友好,能够提供超长记忆力,以及个性化的智能体体验

1. 产品场景

微软的类似产品能力:https://microsoft.github.io/autogen/docs/ecosystem/mem0

千帆 app-builder 类似的产品能力:

2. 论文背景

由于 Transformer 架构的自注意力机制,直接扩展 Transformer 的上下文长度会导致计算时间和内存成本成倍增加,这使得新的长上下文架构的设计成为紧迫的研究挑战(戴等人,2019; Kitaev 等人, 2020; Beltagy 等人, 2020)。虽然开发更长的模型是一个活跃的研究领域(Dong等人,2023),即使我们能够克服上下文缩放的计算挑战,最近的研究表明长上下文模型很难利用额外的上下文实际上(Liu等人,2023a)。因此,考虑到训练最先进的大语言模型所需的大量资源以及上下文扩展的收益递减,迫切需要替代技术来支持长上下文。
在本文中,我们研究如何在继续使用固定上下文模型的同时提供无限上下文的错觉。我们的方法借鉴了虚拟内存分页的理念,该理念通过在主内存和磁盘之间进行数据分页,使应用程序能够处理远远超出可用内存的数据集。
在MemGPT中,我们将上下文窗口视为受限内存资源,并为大语言模型设计了类似于传统操作系统中使用的内存层的内存层次结构(Patterson等人,1988)。传统操作系统中的应用程序与虚拟内存交互,通过将溢出数据分页到磁盘,并在应用程序访问时(通过页面错误)将数据检索回内存,提供了一种内存资源多于物理内存(即主内存)实际可用资源的假象。为了提供更长上下文长度(类似于虚拟内存)的类似错觉,我们允许大语言模型通过 “大语言模型操作系统”(我们称之为 MemGPT)来管理放置在自己上下文中的内容(类似于物理内存)。MemGPT 使大语言模型能够检索上下文中丢失的相关历史数据,并且还可以将不太相关的数据从上下文中驱逐到外部存储系统中。图3说明了MemGPT的组件。


Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving

一、摘要

MoonCake设计了KV Cache分离式架构,该架构下prefill和decode分离为2个集群。同时,存算分离KV Cache利用了GPU集群的CPU、MEM、SSD资源来构建。
1、MoonCake关键核心:以KV Cache为中心的调度,该调度核心价值:最大化吞吐、同时不打破TTFT和TBT。
2、MoonCake应对超高负载:设计了提前预估的拒绝策略
3、MoonCake效果:模拟场景中,MoonCake相比基线提升了5倍多的吞吐(满足延迟前提下)
4、MoonCake实际应用:MoonCake使得Kimi处理的请求量提升了75%

二、结论

为了高效LLM推理,尤其是在长上下文和超载场景,我们设计了MoonCake。文章中讨论了该架构的必要性、挑战、针对最大化吞吐和保障延迟间做的设计折中。

三、介绍

1、研发分离式KV Cache背景和动机
  • prefill和decode具有不同计算特性,prefill偏计算,decode偏显存和带宽,拆分有利于提升资源利用率和吞吐
  • 最大化吞吐的核心办法:prefill阶段最大程度reuse KV Cache(本地Cache+远程Cache)、推理阶段在一个batch中最大化token数量(提升MFU,模型浮点计算)
  • 核心问题:最大化吞吐,通过远程Cache复用会增大TTFT,增大batch的size过大会导致TBT变大
  • 关键解法:设计一个分布式KV Cache,并以他为中心设计核心调度和优化
2、关键流程
  • 关键核心流程设计:对于一个请求,全局调度器Conductor根据最大化Cache和工作负载相结合原则,挑选prefill实例及decode实例
    • 1)传输尽可能多的可复用KV Cache到被选择的prefill实例
    • 2)按层分块并行完成prefill stage,并且prefill stage过程中按层分块流式传输KV Cache到对应decode实例
    • 3)decode实例load 被写入本地的KV Cache,然后继续流程完成推理
3、核心挑战
  • prefill阶段(最大化复用KV Cache)
    • 内存/SSD KV Cache load时延保证:分布式KV Cache核心存储在内存和SSD,如何控制加载时间,不影响TTFT
    • 短时间高并发大量KV Cache传输时延保证:存在KV Cache需要远程传输到本地,可能会出现网络拥塞,进一步加剧延迟
    • 调度优化和保障:Conductor需要预测未来KV Cache的使用情况,并提前执行swap或多副本策略
        • 最热的一些block Cache,需要复制到多个节点,避免网络拥塞
    • 内存限制:单机内存大部分被KV Cache Pool预留,留给prefill调度的内存有限,影响调度效果?
  • decode阶段(最大化增加batch size,增加token数量)
    • batch过大,TBT SLO可能被打破
    • prefill实例节点显存有限,影响size数量
  • 负载调度
    • 请求提前拒绝:prefill后,需要预测是否有decode槽位(空闲decode实例),如果没有则需要尽早拒绝,节省资源给其他请求
    • 粗暴拒绝会导致负载波动剧烈:需要预估生成的长度,然后做短期内的全局的负载预估,支撑更好的拒绝策略
    • 优先差异调度:区分不同请求优先级,实现更好的优先级调度
4、关键策略设计
  • CPP:Chunk Pipline Parallelism,将一个请求跨多节点并行处理,并且优化了网络占用,以应对长文本分布的动态变化,这样可以有效降低TTFT
  • 中心式KV Cache请求调度算法:基于启发式的自动化热点迁移方案,自动复制热的KV Cache块,无需依赖 精确预估未来KV Cache使用。能显著降低TTFT
  • 请求拒绝:基于实例系统负载,决定是接受请求还是拒绝请求

四、相关工作

五、核心设计

5.1 整体架构



Splitwise: Efficient Generative LLM Inference Using Phase Splitting

https://arxiv.org/pdf/2311.18677

https://yiyibooks.cn/arxiv/2311.18677v2/index.html

论文的核心思想:不管是 Ocra、还是 ExeGPT、还是 SplitWise,论文都提到一些关键的特点,整个推理可以分为2部分,提示阶段(首token计算阶段)和词生成,这2个阶段的负载类型是截然不同的,对算力的要求也不一样,因此,通过拆分这2阶段的计算,未来资源池里面,一部分机器用来跑首 token 计算,一部分机器用来跑词生成,通过这个方式,整个推理的吞吐提升了2倍左右

1. 论文的一些洞察

1.1. 【关键】提示阶段和词生成阶段的token分布

由于编码服务的常见情况只需要在用户键入时在程序中生成接下来的几个单词,因此输出词符的中位数为 13 个标记。另一方面,对话服务几乎呈双峰分布,生成的 Token 中位数为 129 个。洞察1:不同的推理服务可能具有截然不同的token数量分布(提示阶段、词生成阶段)

1.2. 【关键】批处理利用率

batch size = 30,但不代表每时每刻都有 30 个请求在处理,实际情况有很多管道气泡

如果一个请求,在做词生成,那标记为1个活跃的token。上面这个图就是 GPU 活跃 token 数量的一个累计分布函数从这个图可以看出:

  1. 对 chat 来说:GPU 大概有 60% 的时间,在处理 < 20 的 token。
  2. 对于 code 来说:这个情况更差,大概有 30% 的时间,在处理 1 个 token

也就是说,GPU 实际运行过程中,是有非常多管道气泡的。比如 batch size 是 30,但是还是有很多时间,只有1个token在处理

推测:

Q:为什么会出现这个问题?

A:由于请求和 seq 长度存在一定的分布,所以必然会存在很多时刻,很多流水线是处于 “短暂” 的空闲的,这种空闲的时间片,就是管道气泡

Q:能不能通过缩小 Batch size 来降低管道气泡?

A:不能,因为缩小 Batch size 会推高长尾延迟,整体吞吐下降


Infinite-LLM: Efficient LLM Service for Long Context with DistAttention and Distributed KVCache

Infinite-LLM: Efficient LLM Service for Long Context with DistAttention and Distributed KVCache

1. 问题

模型的架构:

  1. QKV Linear layer
  2. Multi-Head Self-Attention Mechanism, or the attention module
  3. FNN,Feed-Forward Neural Network

超长文推理的挑战,2k -> 256k ?

有2个:

  1. 不同的层对显存的需求差异很大(注意力层的显存和输入输出是成比例的,但是其他层不是),这就限制了并行的效率(类似木桶效应??)
  2. KV cache 的大小不可预估,显存容量不可规划

这2个问题,最终也极大的限制了超长文推理的效果


SpotServe – Serving Generative Large Language Models on Preemptible Instance

https://mp.weixin.qq.com/s/uK1owRF53FW4WI7nldYadg
https://arxiv.org/pdf/2311.15566.pdf
业界首个,在可抢占式实例上运行的分布式大语言模型(LLM)服务系统
目标:LLM 降本 -> 抢占式 GPU 实例 -> 怎么更好的用这些抢占实例
相比直接使用抢占式GPU实例,SpotServe 可以将推理引擎的长尾延迟降低 2.4x – 9.1x
LLM 特点:

  1. 高计算量
  2. 大内存占用

无论是哪一点,都意味着成本昂贵
把 LLM 运行在随时可抢占的 GPU 实例上
传统的方法,MArk、Cocktail,单实例多GPU卡,运行一个 small DNN 模型,通过请求重定向或者冗余计算来处理抢占,但是这种方式只是和数据并行这种小的DNN模型,不适合LLM
LLM 会同时使用数据并行、模型并行、流水线并行多种技术,单个实例抢占会影响整个多个实例的计算结果
所以需要有更有效的方法
亮点:第一个做推理容错的论文(其他的都是在做训练的容错)
SpotServe 的创新点:

  1. 动态配置并行度
  2. 实例迁移优化(复用模型参数、中间结果,减少迁移后的传输数据量):aws 抢占GPU实例,迁移后冷启动需要2m
  3. 高效利用宽限期(30s,尽量不中断推理)

1. 背景

1.1. Generative LLM Inference

当前类似的系统:

  1. FasterTransformer
  2. Orca
  3. FairSeq
  4. Megatron-LM

单次推理的总耗时,分析


texe(Sin) 是输入序列的解码时间
Sout 是生成的 token 数量,texe(1) 是生成每个 token 的时间
KV cache 技术可以将每个 token 的生成时间优化到接近常量 (i.e., 𝑡𝑒𝑥𝑒 (1) in E.q.(2) and Figure 1a).


Microsecond-scale Preemption for Concurrent GPU-accelerated DNN Inferences

这是到目前为止(2023/10)应该是唯一一篇在认真思考并且实现 GPU 抢占的论文了。
之前很多论文都研究过 GPU 怎么混部,怎么做算力和显存的隔离,但是目标都是提升利用率,但是很难保证长尾延迟。比较适合训练,但不适合推理。
但是这篇论文其实也是有很多限制的:
  1. 不支持 nvidia GPU,只支持 amd
  2. 模型需要经过 tvm 的编译,因为抢占的实现依赖了编译技术
这篇论文主要的工作就2个事情:
  1. GPU 抢占:real-time任务启动的时候,best-effort 任务怎么做到快速的退出。
  2. dynamic kernel padding:怎么在尽可能保证 rt 任务 latency 的情况下,混部 be 的任务
最终效果:
  1. 2% 的延迟损失,换取 7.7x 倍的性能提升

1. 系统架构

0
reef 在 GPU runtime 的基础上扩展了4个核心组件:
  1. Task Queues:一个rt队列和多个be队列,每个队列绑定一个 GPU Stream
  2. Scheduler:一个调度器,2种模式,rt模式,normal模式,如果当前没有rt任务,调度器会切换到normal模式,如果有rt任务,会切换到rt模式。这个地方应该是为了和下面的抢占进行协同
  3. Preemption:抢占模块,实现rt抢占be
  4. DKP:dynamic kernel padding
前面2个没啥东西,重点讲一下抢占是怎么实现的吧,这个比较有意思