Posts 大模型推理技术栈
Post
Cancel

大模型推理技术栈

前言

影响LLM推理性能的因素有很多,包括但是不限于:模型配置、硬件类型、数据分布、优化算法、推理策略等。本位旨在综述各类技术点,后续会针对核心技术做详细展开。

LLMs计算建模

大模型计算建模

开源框架分析

开源的LLM推理框架有TensorRT-LLM、FasterTransformer、TGI、VLLM、SGLANG等,以VLLM为例简单介绍下推理框架: img

框架优化

Continuous Batching

Paper

Orca: A Distributed Serving System for Transformer-Based Generative Models LLM推理主要氛围Prefill/Decode阶段,其中Prefill阶段为Compute Bound,Decode阶段为Memory-IO Bound,意味着需要充分利用显存的数据带宽。但是由于显存的限制,如A100-40GB而言,以13B模型为例,权重为26GB,其消耗单个Token的显存约为1MB(激活值/Cache等),则当Seq长度为2048时,仅能支持7 batch的推理,此时算力利用率很低。为了提高算力利用率,一方面可以通过量化手段减小显存,也可以通过优化FlashAttention等算子实现减少Memory-IO,另一方面也可以从Schedule层面通过Continuous Batching手段来提高Batching效率。

Motivation

传统的 static batching 如下: img

显然,由于不同batch的out_token数目不一致,1,3,4batch存在empty slots,意味着一段时间内,只有batch 2单batch在计算。Continuous batching采用来iteration-level的调度策略: img

Key Points

由于Prefill/Decode两个阶段的 computational pattern 差别较大,主要体现在ltency和显存消耗上,所以实际prefill阶段往往没法直接和decode做batching,所以通过 waiting_served_ratio 来控制prefill阶段和decode阶段的调度策略。

Statistics

img

Prefix prompt cache

Paper

SGLang: Efficient Execution of Structured Language Model Prpgrams

Motivation

多轮对话中,由于Prompts存在大量相同前缀,因此 Prefix prompt cache 的复用就显得很重要:需要考虑:efficient prefix search,reuse,insertion,eviction已经对应的cache aware scheduing。

Key Points

SGLang采用了 prefix tree 的数据结构来进行Cache的数据存储: img

另外,Scheduling策略上,以匹配的prefix-length作为最高优先级。

VLLM结合PA和Prefix Cache,通过 hash(prefix tokens + block tokens) 来定义对应的KV Block,通过hash-mapping的方式来实现prefix-cache和physical block的mapping:

img

Statistics

multi-call 下的性能收益: img

MultiLora

Paper

MULTILORA: DEMOCRATIZING LORA FOR BETTER MULTI-TASK LEARNING

S-LoRA: SERVING THOUSANDS OF CONCURRENT LORA ADAPTERS

Motivation

PEFT降低了大模型fine-tuning的硬件算力需求,比较常见如:LoRA;对于多任务PEFT的工作,之前的工作无法像LoRA一样和BaseModel参数做无缝集成。 Multi-LoRA在推理部署的问题的困境在于:

  1. 单个LoRA可以通过merge权重的方式,实现无开销的LoRA推理;但是对于Multi-LoRA而言,这种方式的效率是偏低的。
  2. Multi-LoRA的Adapter的数目可能很多,全量加载可能会导致显存占用过高,需要实现Host/Device侧内存的Load/OffLoad
  3. 对于2.的问题需要解决内存碎片和动态Load/OffLoad的延迟开销问题。

Key Points

核心流程如下:多个lora独立,但是前向传播过程中共享所以训练数据的前向传播,但loss更新的时候会根据mask矩阵进行单个任务的LoRA权重更新。 img

推理部署而言,核心为了解决前文的Multi-LoRA的Adator的动态加载的问题,关键方案如下:

  1. Specified-CUDA-Kernels:定制了批处理GEMM内核来计算LoRA。 img

  2. Memory-Allocator Design:Host内存存储了全量的Adater权重,实际推理过程中需要实时fetch对应的Adater权重到显存 img

  3. Unified Paging:扩展了PagedAttention的内存管理方式,划分了LoRA权重专门的内存池以减少内存碎片,同时提前fetch权重来实现计算/权重加载并行 img

  4. 张量并行:LoRA侧的并行策略如下: img

Statistics

S-LoRA策略下,单个GPU/多个GPU下可以部署数千个LoRA适配器,以下为Llama-7B/13B/30B/70B的部分结果:

img

img

Speculative decoding

Paper

MEDUSA: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads A Hitchhiker’s Guide to Speculative Decoding

Motivation

LLM的decoding阶段通常会受限于显存带宽(memory-bandwidth-bound),因为decoding阶段单次仅生成单个token,导致计算效率偏低。 提高decoding阶段的计算效率,其中一种方式便是:提高单次inference的计算强度/减少deocding的迭代次数。其设计思路是:采用一个轻量的draft model来预测多个token,然后通过original model来refine生成的token,从而减少总的计算量,同时又不损失精度。

Key Points

MEDUSA策略是:添加了多个head在最后一层的hidden states后,在实际的推理的过程中,连续的heads能生成连续多个token,最后再通过原始模型对多个Candidates进行确认。 img

对于多个tokens的计算,采用了Tree-Attention的方式来进行Attention的计算。 img

实际部署过程中的一些细节设计:

  1. HEAD的效率问题:更多的HEAD,能够增加单次推理的token数目,如果完全被original model接受的话,效率更高;但是更多的HAD,对训练的难度更高,且如果draft model的预测精度不高的话,会造成一定的计算资源/显存损失,实验上看3-4 heads收益比较大,部分常见6-8heads也有比较大的收益。
  2. KV-Cache的设计:original model对多个token的验证,Lookahead scheduling设计能够减少对proposal token的KV Cache的重新计算/显存拷贝 img

Statistics

Llama2 13B的测试结果如下,在保持TTFT/ITL一致的情况下,能提升约一倍的吞吐。 img

Pipeline/Tensor parallism

训练场景,并行策略有DP/TP/PP/EP等策略,也有不同并行策略的组合,以及Zero等OffLoad的策略。对于推理而言,最常用的是TP/PP策略。 TP最常用的策略主要在MLP/Self-Attention(num_head切分)进行切分,整体为:col-spllit->row-split->all-reduce。从参数量上看,单卡的权重为1/TP,计算量上为1/TP,激活值为1/TP,但是由于输入为完整的seq_len,所以KV_Cache为完整的,通信量上看是 2*b*s*h + 2*b*s*h ,通信量比较大,所以一般需要较大的通信带宽。 img

img

PP的切分策略比较简单,通常按照Transformer-Block进行切分(num_layers/pp_world_size),不同Block之间通过send/reiv通信,且由于推理过程中仅涉及forward阶段,所以PP的Bubble现象也不显著:需要保证不同卡间TransforBlock计算的均衡。从参数量上看,单卡的权重为1/PP,计算量为1/PP,激活值为1/PP,KV_Cache为1/PP;通信量上是 b*s*h

img

TODO PD Seperate

TODO ChunkPrefill

模型优化

Graph Compile

图编译优化主要是围绕AI编译器来实现的,整体框架可参考:The Deep Learning Compiler: A Comprehensive Survey: img

对于LLM来说,本身模型结构的变化不大,意味这图层面的优化策略是比较通用的,如:Quant算子的融合/PointWise算子融合/自定义融合算子等,所以常用的策略是复用PyTorch等框架的Compiler,自定义LLM的Compiler,如[RFC] A Graph Optimization System in vLLM using torch.compile中介绍的:基于TorchDynamo的FX Graph添加Used-defined Compiler来实现LLM的自定义Compiler优化。其优势是能够不开发专用Compiler的前提下,完成high-level的IR优化,同时复用TorchDynoma Low-IR侧的优化能力,但是缺点是其输入的图属于”raw FX Graph”,对于例如code elimination/topo sort,实现上会有些困难。 img

量化

llm int8

weight混合精度量化,channel-wise,outlier采用FP16.

GPTQ

W4A16,W8A16属于PTQ(训练后量化),一般需要校准数据集:通过分解海森矩阵来迭代量化,对权重矩阵进行block切分,同样为混合精度量化。

AWQ

W4A16,W8A16,激活感知的量化,为完全的int4/int8权重量化;核心思路:对关键权重先乘一个方法系数再量化进行一个保护。首先,识别关键权重的方法是分析Activation分布。显著权重乘一个放大系数scale后,量化误差会较之前变小,并且对于显著权重也给予不同的保护粒度。论文通过最小化layer量化前后的差值来在搜索空间寻找最优的 scaling。

SmoothQuant

W8A8,支持weight/activation联合量化,其动机是发现activations需要channel-wise才能保持精度,但是channel-wise的activation量化效率很低,需要采用:把Activation量化的难度平滑转移到weight的量化上。SmoothQuant 提出了一种数学上等价的逐通道缩放变换(per-channel scaling transformation),可显著平滑通道间的幅度,从而使模型易于量化。 img

TODO 稀疏

算子优化

硬件简介

当前深度学习应用硬件主要是GPU和各类XPU(AISC),GPU架构设计上的最大的特点是:

  • 内存架构:GPU的片上Cache相对较小,且通常采用HBM等高带宽的存储,会设计多级存储方案
  • 计算单元:GPU有强大的计算单元,且超配大量的多线程,专注大规模并行的计算任务。计算单元以GPU SM为单位,GPU单个时钟周期能支持多个Warp,A100为例,每个SM包含64个Warp,包含2048个线程。

    img

    GPU独特的硬件设计对应其SIMT编程特性,而传统的CPU或者XPU都是SIMD编程模型,其主要区别在于:

    • SIMD采用一个线程处理一个指令,指令是向量化处理的,意味可以支持单指令多地址,且维度是固定的,如32bit的4维向量vec4,需要4的整数倍数据。其优势是,对于大尺寸连续数据的处理上,SIMD性能更有优势,但是对于小数据尤其是没法保持为整数倍的数据,会有一定程度计算浪费。

    • SIMT采用的思路是单核多线程,即单个计算单元(SM)是支持多线程的,即多线程同指令,通过多线程的方式来降低IO的时延。SIMT对开发人员的要求是更低的,因为SIMT始终是统一指令的,只要处理好不同线程之间的数据排布即可。

GEMM

GEMM优化的核心在于:

  1. 提高Cache命中率,设计更好的数据排布(Tiling)
  2. 提高并行度,充分利用指令向量化和多核并行

先看一个native的实现:

1
2
3
4
5
6
7
8
9
10
void naive_row_major_sgemm(const float* A, const float* B, float* C, const int M,
                           const int N, const int K) {
    for (int m = 0; m < M; ++m) {
        for (int n = 0; n < N; ++n) {
            for (int k = 0; k < K; ++k) {
                C[m * N + n] += A[m * K + k] * B[k * N + n];
            }
        }
    }
}

其浮点运算量为2xMxNxK,缓存读次数为2xMxNxK,写次数为MxN;但是这种写法会造成大量的Cache miss: img

简单的循环重排就可以得到加速:

1
2
3
4
5
6
7
8
9
10
void optimize_row_major_sgemm(const float* A, const float* B, float* C, const int M,
                              const int N, const int K) {
    for (int m = 0; m < M; ++m) {
        for (int k = 0; k < K; ++k) {
            for (int n = 0; n < N; ++n) {
                C[m * N + n] += A[m * K + k] * B[k * N + n];
            }
        }
    }
}

进一步提高Cache命中率,则需要做矩阵A/B的分块计算,以CUDA而言:需要设计从Global Memory->Shared Memory->Register的分块逻辑。 img

FlashAttention

FlashAttentionV2算是FlashAttention的集大成者,V3更多的是基于GPU硬件定制优化。本身FlashAttentionV1缓解了IO显存的时延问题,FlashAttentionV2在其基础上进一步做了优化:

  1. 减少了non-matmul FLOPS数量
  2. 基于不同thread block实现序列并行化,优化了warp的工作分配。

前置补充FlashAttentionV1的核心点:主要通过tiling设计/Online-Softmax来优化中间结果的缓存处处,可以将内存开销降低到线性级别,提高了2-4倍加速。

v2 python实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import torch

torch.manual_seed(456)

N, d = 16, 8
Q_mat = torch.rand((N, d))
K_mat = torch.rand((N, d))
V_mat = torch.rand((N, d))

expected_softmax = torch.softmax(Q_mat @ K_mat.T, dim=1)
expected_attention = expected_softmax @ V_mat

# 分块(tiling)尺寸,以SRAM的大小计算得到
Br = 4
Bc = d

O = torch.zeros((N, d))

# 算法流程第3步,执行外循环
for block_start_Br in range(0, N, Br):
    block_end_Br = block_start_Br + Br
    # 算法流程第4步,从HBM中load Qi 的一个block到SRAM
    Qi = Q_mat[block_start_Br:block_end_Br, :]
    # 算法流程第5步,初始化每个block的值
    Oi = torch.zeros((Br, d))  # shape Br x d
    li = torch.zeros((Br, 1))  # shape Br x 1
    mi = torch.full((Br, 1), -torch.inf)  # shape Br x 1

    # 算法流程第6步,执行内循环
    for block_start_Bc in range(0, N, Bc):
        block_end_Bc = block_start_Bc + Bc

        # 算法流程第7步,load Kj, Vj到SRAM
        Kj = K_mat[block_start_Bc:block_end_Bc, :]
        Vj = V_mat[block_start_Bc:block_end_Bc, :]

        # 算法流程第8步
        Sij = Qi @ Kj.T
        # 算法流程第9步
        mi_new = torch.max(torch.column_stack([mi, torch.max(Sij, dim=1).values[:, None]]), dim=1).values[:, None]
        Pij_hat = torch.exp(Sij - mi_new)
        li = torch.exp(mi - mi_new) * li + torch.sum(Pij_hat, dim=1)[:, None]
        # 算法流程第10步
        Oi = Oi * torch.exp(mi - mi_new) + Pij_hat @ Vj

        mi = mi_new

    # 第12步
    Oi = Oi / li

    # 第14步
    O[block_start_Br:block_end_Br, :] = Oi
assert torch.allclose(O, expected_attention)

TODO PageAttention

场景优化

MOEs(Mixed Expert Models)

MoEs最早由Switch Transformer提出,核心结构由稀疏MoE层和门控网络或者路由组成:

  • 稀疏 MoE 层: 这些层代替了传统 Transformer 模型中的前馈网络 (FFN) 层。MoE 层包含若干“专家”(例如 8 个),每个专家本身是一个独立的神经网络。在实际应用中,这些专家通常是前馈网络 (FFN),但它们也可以是更复杂的网络结构,甚至可以是 MoE 层本身,从而形成层级式的 MoE 结构。
  • 门控网络或路由: 这个部分用于决定哪些令牌 (token) 被发送到哪个专家。例如,在下图中,“More”这个令牌可能被发送到第二个专家,而“Parameters”这个令牌被发送到第一个专家。有时,一个令牌甚至可以被发送到多个专家。令牌的路由方式是 MoE 使用中的一个关键点,因为路由器由学习的参数组成,并且与网络的其他部分一同进行预训练。

img

MoEs加速的问题在于:其本身的分支结构会导致计算效率低下,且由于Experts数目的增多会导致需要多卡/多机之间的分布式部署,从而引入额外的通信开销。对于MoEs模型,通常需要采用多种并行计算策略:DP/PP(MP)/TP/EP,且多种策略往往需要组合使用。 img

DeepSpeed的并行策略组合如下:下图是以单个 MoE 层为例解释了如何使用混合并行策略,假设总共有16 个 GPU,专家数量为 8 个,可以看到并行模式是针对专家和非专家模块分别设计的,具体如下:

  • 对于非 expert 参数模块:
    • 使用4 路数据并行,也就是说该部分参数复制了 4 遍
    • 每一路数据并行的内部采用 4 路的tensor 并行
  • 对于 expert 参数模块:
    • 使用 8 路专家并行
    • 每个专家通过 2 路的 tensor 并行进行参数拆分

img

同时,TP/EP间也可以做通信优化:核心思路是TP的输入/输出数据是一致的,不需要做all-to-all通信,能够节省EP的通信开销:

img

长文本

长文本对LLM带来的挑战主要在于显存上的,核心原因是:原始Attention的计算过程中,中间变量的大小是O(n^2)增长的,所以核心思路是优化Attention的中间计算结果。其中,最为典型的思路便是:SP(序列并行,Sequence Parallism),而序列并行最典型的思路便是:ULYSESSRing Attention。 Ulysess的核心思路是:Q/K/V矩阵按照N维度做切分,然后workers之间通过All2All转换为d维度切分(前置知识:All2All等价于分布式Transpose),然后可以按照Softmax(QK^T)计算,然后再做一次All2All通信转置回来,单次通信量为O(N*d),与卡数目无关,总通信量为3xO(N*d)限制条件是d/P可整除,对GQA/MQA不友好。 img

RingAttention可以认为是分布式的FlashAttention,其核心修改点是:内层循环K、V的时候需要通过P2P通信来得到不同Device间的K、V中间结果,RingAttention的单次通信量是O(Nxd)*P,和卡量相关,总通信量为N/P/c*(P-1)*O(N*d),其中P为卡量,c为单次计算的block大小,但是计算和通信理论上可以流水,且当计算时间大于通信时间的时候,能够overlap。Ring-Attention的优点在于泛化性好,即插即用,P2P对通信要求低,但是通信量更大,且存在负载均衡的问题,具体表现为越到后面的seq实际的计算量越大。 img

TODO 多模态

参考资料

揭秘 LLM 推理:全面解析 LLM 推理性能的关键因素

投机采样

Continuos——Batching

Qlora/GPTQ量化概述

LLM推理部署 - 量化(llm.int8,AWQ,GPTQ,SMOOTHQUANT)

谈谈对OpenAI Triton的一些理解

CUTLASS:Fast Linear Algebra in CUDA C++

一文搞懂TorchDynamo原理

混合专家模型(MoE)详解

LLM学习笔记-Deepspeed-MoE论文

大模型训练之序列并行双雄:DeepSpeed Ulysses & Ring-Attention

更适合flash attention体质的上下文训练方案

GPU工作原理解析

从现代GPU编程角度看SIMD与SIMT

x64 CPU GEMM优化

CUTLASS: Efficient GEMM in CUDA

深入浅出GPU优化系列:GEMM优化(一)

FlashAttention2详解(性能比FlashAttention提升20%)

This post is licensed under CC BY 4.0 by the author.