1.3.llm_archs
训练框架
mfu(Model Flops Utilization)模型算力利用率是分布式训练效率的优化目标。
一个模型定义好之后,前向和反向的计算量就是固定(不考虑动态图的话)的,除以每个step的latency就是mfu。以nanoGPT中的代码为例:
一般分布式训练参数量越多->卡数越多->通信占比越高->MFU越低,所以要优化通信效率。
优化设置
batchsize:通常用比较大的batchsize,提高训练稳定性和吞吐量。GPT-3和PaLM在训练时动态增加batshzie,最终达到百万级别,batchsize从3.2w逐渐增加到320w个token。
优化器:
学习率:
预热(warm-up):在训练的初始0.1%到0.5%的steps中,用线性预热策略逐渐增加学习率到最大值(到之间,GPT-3是)
衰减(decay):后续steps中余弦衰减,逐渐降低到最大值的约10%,直到收敛
稳定训练:
权重衰减和梯度裁剪:权重衰减率设为0.1,梯度裁剪阈值设为1.0
梯度检查点:容易出现loss突增,PaLM和OPT从发生突增前的一个ckpt重新开始训练,并跳过可能有问题的数据
缩减emb梯度:GLM发现emb的异常梯度通常会导致loss突增,故缩减emb梯度以缓解
混合精度训练
FP16
推理(预测):所有参数都是fp16,相对fp32,存储变成一半,速度提升1倍。
训练:参数和梯度用fp32存储,但是在计算前会转成fp16,计算后再转回fp32。主要为了防止溢出,loss要乘一个scale,然后在fp16的梯度要除以scale。
fp16的参数:2bytes
fp16的梯度:2bytes
优化器状态(optimizer state):16bytes
fp32参数(4bytes)
fp32 variance【历史梯度平方和】(4bytes)
fp32 momentum【历史梯度滑动平均】(4bytes)
而在预测时只要存一个fp16的参数(2bytes)就行,所以预测的显存是训练的1/10,对应1.3B参数量的gpt2-xl,训练要占用,预测只要2.6GB
BF16
FP16可能导致计算精度的损失从而影响模型性能,BLOOM里用BF16(brain floating point)比FP16分配更多指数位和更少的有效位,在准确性方面更好
bf16的指数位和fp32一样多
可扩展的训练
需要提高训练吞吐量和加载更大模型到显存中
3D并行
如下三种并行(数据并行、流水线并行、张量并行)的组合
数据并行(Data Parallelism)
将模型参数和优化器状态复制到多个GPU上,每个GPU只处理分给它的数据,不同GPU算出的梯度进行聚合得到batch的梯度,再更新所有GPU上的模型。高度可扩展,增加GPU数就能提高训练吞吐。
torch的ddp
可以一起搞的技巧——梯度累积,当显存不够跑较大的batchsize时,训练效果可能会很差,可以先跑多个mini-batch的前向和反向,把梯度累积起来,再更新一次参数,在数学上等价于跑一个较大的batchsize。
也可以用torch的no_sync():
流水线并行(Pipeline Parallelism)
将LLM的不同层分配到多个GPU上,一般Transformer模型中会将连续的层加载到同一GPU上,以减少在GPU间传输已计算的隐层状态或梯度的成本。简单的实现会导致GPU利用率降低,因为每个GPU要等前一个完成计算,导致不必要的气泡开销,如下方法可以提高流水线效率:
1) GPipe
Gpipe主要思想:
图a:把模型不同layers顺序放在4张卡上,0->3卡流水线前向计算loss,3->0再反向计算gradients
图b:从时间顺序上看,每张卡有3/4时间是空闲的,GPU利用率非常低
图c:配合梯度累积,多个mini-batch可以同时跑在流水线里面,每张卡则有3/(3+4)的时间空闲(Bubble)
流水线并行的问题是中间有Bubble。当卡数,梯度累积次数,则
2) 重计算
因为要做pipeline+梯度累积,前向过程中的激活值要保存,以留给反向过程使用,保存很多份的激活值对显存造成了很大压力。recomputation(也叫checkpointing)用时间来换空间(反向的时候进行一次激活值的重计算),可以缓解显存问题。
深度更深的时候,一般效果更好
cpu offload:层数很深的时候,可能可以把一些计算挪到cpu上去,再搞回gpu
张量并行(Tensor Parallelism)
分解LLM的张量(参数矩阵),例如矩阵乘法,可以按列分成两个子矩阵和,从而改为,将和放到不同GPU上,然后就可能通过跨GPU通信将两个GPU的结果merge。
Megatron-LM:能扩展到更高维度的张量
Colossal-AI:
原始矩阵乘法是[m,k], [k, n] -> [m, n]
,有如下两种矩阵分解的等效:
列并行(column parallelism):第一个矩阵不变,第二个矩阵竖着劈成两半,即
[m,k], [k, n/2] -> [m, n/2]
concat([m, n/2], [m, n/2]) -> [m, n]
行并行(row parallelism):两个矩阵都横着劈成两半,即。从2推广到k,其实就是split-k算法,把两个矩阵都分成k个小块,两两相乘后,最后reduce_sum一下。因为每个线程计算的矩阵更小了,开销小,可以通过加大线程数来提升并行效率。
[m, k/2], [k/2, n] -> [m, n]
elemwise_add([m, n], [m, n]) -> [m, n]
行并行还可以扩展到推荐里,假设user有k/2维,item也是k/2维,concat在一起,然后过一个k*d的mlp,即[1,k] * [k, d] -->[1,d]
,那么可以按行并行的方法,拆成2个[1, k/2]
和[k/2,d]
相乘,再相加。这样item侧的[k/2,d]
可以把全库缓存过来,在线实时算user,排序时把对应item向量抽出来,和user加起来就行
megatron对transformer进行了如下优化:
MLP第一个nn按列分割,第二个nn按行分割,中间省了一次通信
Attention按照head来分割(类似列分割),后面接的nn按行分割,中间也省了一次通信
图里面的通信算子
是前向identity,反向all-reduce
是前向all-reduce,反向identity
综合来看,一层transformer layer如下
ZeRO
zero的思想:减少显存,搜广推大部分还是数据并行,所以模型参数是复制多份的,所以zero比较有用
fp16那一节中,optimizer state的显存占用,在前向和反向的时候都不用,只有最后optimizer step的时候才用。
===>zero的思想:把optimizer state分shard存在不同的卡上,只在最后gather时才用。
ZeRO(Zero Redundancy Optimizer)在DeepSpeed库中提出,解决数据并行中的内存冗余问题。数据并行其实并不需要每个GPU都存整个模型、梯度和优化器参数,ZeRO在每个GPU仅保存部分数据,当需要其余数据时从其他GPU检索。3种解决方案:
优化器状态分区:zero1,对显存最大开销的部分进行shard
梯度分区:zero2
参数分区:zero3
前两种方案不会增加通信开销,第三种方案增加约50%通信开销,但能节省和gpu数成比例的内存。
需要指定deepspeed的配置:
启动:
还有一些paper也能降低内存,如
序列并行
对比TP:
SP:
综合对比各种并行
几个缩写:params(p)/gradients(g)/optimizer states(os)/activation(a)
DP(数据并行)
p/g/os都复制在每张卡上,显存效率很低
计算和通信可以overlap,如果都在一个minipod内扩展性很好;梯度累积可以提高计算效率
batchsize不能太大,否则模型效果有损;batchsize/dp不能太小,不然打不满tensorcore
ZeRO(解决DP的显存冗余)
zero1/2/3把os/g/p分别shard到每张卡上,显存效率很高
需要做prefetch来减少通信对计算效率的影响
同DP
PP(流水线并行)
切分p,提高显存效率;a需要存多次,降低显存效率
通信次数最少,只发生在多层之间的切分点,但是有Bubble
每个Stage之间需要负载均衡,对模型结构和卡数有限制
TP(张量并行)
p/g/os/a被shard在每张卡上,显存效率也很高;有些层如layernorm是复制的,可以用sequence parallel优化
梯度不需要同步,提高计算效率;每层插入了4次通信,而且是跟计算有依赖的,会降低计算效率;每层的计算量进行了切分,也会降低计算效率
一般是单机内8卡使用nvlink时用TP
把神经网络看成是输入和权重的矩阵乘法,那么,DP和PP其实是对的拆分,而TP则是对的拆分
整体对比可以看
一般这么整合:
把机器分成N组,不同组之间用DP
一组机器有M台机器,不同机器之间用PP
一台机器有K张卡,不同卡之间用TP
编译优化
pytorch的TorchDynamo
最简单的用法torch.compile()
flash attention
FlashAttention其实是对的一种加速实现。
一般的实现:需要先 用矩阵存的结果,然后对按行做softmax得到新的,再用乘以得到最后结果。
FlashAttention通过一些特殊技巧,不需要算出这个临时变量,通过分块计算,让临时变量总是可以放在cache里,从而
减少Global Memory的大小
加速attenttion的计算,因为读cache比访问Global Memory快多了。
flexattention
训练稳定性
学习率+batchsize的一些经验:
另外,swiglu会让act_norm变大,得加一些方式让模型能训动,一种方法是把weight_decay调小:
act_norm会降低20-30%,因为正则项更重要了,会让权重更接近0
数据/训练策略
综述
高质量数据
制造人工合成数据,通过控制数据中知识的数量和类型,来严格调控数据中的知识比特数 (bits)。使用不同大小和架构的 LLM 在人工合成数据上进行训练,并给出数学定理,来精确计算训练好的模型从数据中学到了多少比特的知识。有如下几个发现:
如果训练时间充足,不论使用何种模型架构,模型的存储效率均可以达到2bit/param(即平均每个模型参数可以存储2比特的信息)。而且发现transformer中的知识并非主要存储在MLP层,因为即便移除所有MLP层,模型仍能达到 2bit/param 的存储效率。
如果训练时间不充足,GPT2模型能比LlaMA/Mistral存储超过30%的知识,主要是因为GatedMLP(MoE)会导致训练不稳定,因此对同样的知识,需要更长的训练时间。
压缩/量化的影响:将训练好的模型从float32/16压缩到int8,对知识的存储毫无影响。LLM可以达到“信息论极限”的1/4——因为int8只有8比特,但平均每个参数可以存储2比特的知识。
高质量数据的影响:如果我们的预训练数据中,有1/8来自高质量知识库(如百度百科),7/8来自低质量数据(如common crawl或论坛对话,甚至是完全随机的垃圾数据),会发现:
即使对高质量数据的训练时间保持一致,低质量数据的存在本身可能会让模型对高质量知识的存储量下降20倍,即便将高质量数据的训练时间延长 3倍,知识储量仍会降低3倍
解法:只需给所有的预训练数据加上自己的网站域名token即可,模型的知识存储量可以立即回升10倍,模型不需要任何先验知识来识别哪些网站上的知识是金子,而可以在预训练过程中,自动发现高质量知识的网站,并自动为这些高质量数据腾出存储空间。
DSIR
大致思路:
输入语料样本
学习语料的特征分布(target data是,raw data是)
利用语料样本衡量样本间的重要性权重
根据权重进行无放回的采样,兼顾多样性和分布一致性
DoReMi
使用初始的reference domain weights来训练一个小的reference model ,可以用简单的方式合并,例如用样本量加权
用reference mnodel来指导一个小的proxy model的训练,proxy model使用group DRO(group distributionally robust optimization)来得到domain weights,即让最大的loss gap最小化
domain weights:
proxy model参数:
domain语料:
使用tune完的domain weights来训练大模型
可以重复这个过程,用新的domain weights重新训练ref_model,迭代下去
dataset decomposition
LLM的训练语料大都是相同长度的sequence,一般是多篇文档concat到一起,然后再切分成等长的sequence,有可能一个sequence里有不同的毫不相关的文档,这样算attention就不太适合了。方法
将数据划分为多个桶,每个桶中的序列长度均为,保证每个序列仅来自同一个文档
训练时可以给定一个固定的batch_len,即直接从某个桶里采样,也可以用特定长度策略,从多个桶里采样,组合为特定长度
RHO-1
把语料中的无用token从loss里删了
训练ref_model,挑选少量高质量语料,建模语料的整体loss分布情况
拿ref_model在整个预训练语料上计算每个token的ppl
训练LLM,只关注得分比较高的tokens
infinit lr
linear warmup and cosine decay schedule:
linear warmup阶段:前步线性增加学习率,即直到时间步,学习率设置为
annealing阶段:对于接下来的个时间步,改为cosine annealing方式,即对于时间步:
infinit lr decay:
linear warmup阶段:同上
cool down阶段:学习率逐渐decay到一个常量
常数阶段:学习率保持为这个常数,该阶段结束时的ckpt可以用于新数据集的继续pretrain
annealing阶段:逐渐减小到最小值
JEST
现有的大规模预训练数据筛选方法速度慢、成本高,并且没有考虑到批次组成或训练过程中数据相关性的变化,这限制了多模态学习中的效率提升。因此,DeepMind团队研究了联合选择数据批次而非单个样本是否能够加速多模态学习。
挑选好的数据批次比单独挑选数据点更为有效
在线模型近似可用于更高效地过滤数据
可以引导小型高质量数据集以利用更大的非精选数据集
JEST能够在仅使用10%的FLOP预算的情况下超越之前的最先进水平。
硬件的可能影响
大模型训练(如InternLM-7B)实践中,曾经遇到过在A100集群上表现正常的代码和数据,迁移到A800集群却出现了模型准确度下降和梯度范数爆炸的问题。经过调查,我们发现这与A800和A100 GPU的NVLink带宽差异有关。通过在两个集群上使用nanoGPT模型进行的对照实验,我们确认了精度差异的原因在于NCCL的Ring all-reduce算法实现。进一步实验表明,设置环境变量NCCL_ALGO=Tree或使用gloo作为backend可以解决精度对齐问题。最终,我们提出了一个解决方案:在A800集群上设置NCCL_ALGO=Tree,强制使用Tree算法进行all-reduce操作,从而避免了Ring算法带来的精度问题,使得A800集群的模型能够正常收敛,并且与A100集群的训练精度对齐。
fastpersist
pathways
Google的大规模稀疏模型设计
megatron-lm
deepspeed
ray-llm
Google的几大LLM加速工具
maxdiffusion
JetStream
maxtext
Fire-Flyer AI-HPC
megatron-kwai
DiPaCo
推理框架
KVCache
一些结论:
浅层KV cache相比于深层KV cache,对模型的重要性更大。
量化
int4量化:GLM中用了
PPL.LLM
vLLM
(toread)
使用技巧:
cudagraph一般来讲会比eager快,所以不要加
--enforce-eager
--enable-prefix-caching
这个开起来--enable-chunked-prefill
这个也开起来,配合--max-num-batched-tokens
一起搞--max-model-len
限制prompt+response的总长度,利于加速,说实话这个是最有效的参数--max-num-seqs
可以设置一下,最好和实际的请求落到这个server实例的qps近似,需要实测投机采样说实话比较鸡肋,特别是对于qwen系列模型来讲,因为不同大小的qwen的词表是不一样大的,所以其实用不了,而如果用最简单的ngram,速度又非常慢
请求里的
presence_penalty
设置一下,不然qwen容易出现死循环输出从而打爆tokens
示例:
并行推理方法
medusa
用了tree-attention
CLLM
fasterTransformer/TensorRTLLM
remove padding的逻辑如下,把整个batch的数据变成一行数据,加上offset标注是哪一条样本的
直接有这么个脚本:
关于plugin:
一些参数设置:
--multiple_profiles
enables multiple TensorRT optimization profiles in the built engines, it will benefits the performance especially when GEMM plugin is disabled, because more optimization profiles help TensorRT have more chances to select better kernels. However, this feature will increase the engine build time.
trt-llm显存泄露解决
因为用到了kvcache,py的runner的memory管理有问题,改成cpp的runner就行了
huggingface/text-generation-inference
block transformer
MoonCake
MInference(microsoft)
Sarathi-Serve(microsoft)
SGLang(uc berkerley)
LazyLLM
各框架对比
DuoAttention
speculative-decoding
综述
在auto-regressive decoding的过程中先采用更小的draft model猜词,然后送给target model并行验证,以通过在访存限制的情况下充分利用算力,提升模型的推理效率;并且保证decoding的分布与target model一致;
几篇比较早期的
EAGLE系列
prompt-lookup decoding & REST
LayerSkip
DISTILLSPEC
Graph-Structured Speculative Decoding
算子加速
Triton
Mirage
deepseek开源list
FlashMLA
DeepEP
DeepGEMM
DualPipe
EPLB
profile-data
3fs
推理系统
优化目标:更大的吞吐,更低的延迟。 方案:大规模跨节点专家并行(Expert Parallelism, EP)
EP好处:
增加batchsize,提高gpu矩阵乘法效率,提升吞吐
专家分散在不同GPU,每个GPU只计算少量专家,访存更少,降低延迟
EP的复杂性:
引入了跨节点传输,需要设计合理计算流程使计算和传输可以同步进行
因为多节点,需要DP(数据并行),不同DP间要负载均衡
大规模跨节点专家并行
V3/R1的结构:从第4层开始是MOE,每层有1个共享专家和256个路由专家,每次激活8个路由专家,即8 in 256,稀疏度很高,因此需要用更大的overall batchsize,才能给每个专家提供足够的专家batchsize
Prefill:
路由专家EP32
MLA和共享专家DP32
一个部署单元是4节点(32卡),每张卡包括:
9个路由专家(共9*32=288个专家),其中:
1个冗余专家,共1*32=32个
8个路由专家,共8*32=256个
1个共享专家,共1*32=32个
Decode:
路由专家EP144
MLA和共享专家DP144,
一个部署单元是18节点(18*8=144卡),每张卡包括:
2个路由专家,共2*144=288
总共32个冗余专家
总共288-32=256个正常的路由专家
1个共享专家,共1*144=144个
计算通信重叠
多机多卡的EP会引入较大的通信开销,可以用双batch重叠来掩盖通信开销,提升整体吞吐:
prefill阶段:2个batch的计算和通信交错进行,一个batch计算时可以掩盖另一个batch的通信开销。即attn->dispatch->mlp->shared/combine这4步(这里是shared和combine并行)
decode阶段:不同阶段的执行时间有差别, attention部分拆成了两个stage(图中的attn-0(attn前的一些proj操作等)和attn-1(核心的attn计算等)),共5个stage的流水线(attn0->attn1->dispatch/shared->mlp->combine)来实现计算和通信的重叠(这里是shared和dispatch并行)。
尽可能地负载均衡
尽可能为每个GPU分配均衡的计算负载+通信负载
prefill load balancer:
问题:不同DP实例上的请求数、长度不同,导致core-attention的计算量、dispatch发送量不同
目标:
各GPU的计算量尽量相同==>core-attention计算负载均衡
输入的token数量尽量相同==>dispatch发送量负载均衡,避免部分GPU处理时间过长
decode load balancer:
问题:不同DP实例上的请求数量、长度不同,导致core-attention计算量(与 KVCache 占用量相关)、dispatch发送量不同
目标:
各GPU的KVCache占用量尽量相同==>core-attention计算负载均衡
请求数量尽量相同==>dispatch发送量负载均衡
expert-parallel load balancer:
问题:对于给定MoE模型,存在一些天然的高负载专家,导致不同GPU的专家计算负载不均衡
目标:每个GPU上的专家计算量均衡==>最小化所有GPU的dispatch接收量(即MOE的输入)的最大值
架构图
prefill和decode拆了2个service,并且每个前面有负载均衡,内部有EP的负载均衡
线上效果
H800,精度和训练一致:
矩阵计算和dispatch传输用fp8
core-attn计算和combine用bf16
白天用所有节点部署服务,晚上减少节点,峰值278节点(每个节点8卡H800,共278 * 8=2224卡,前面提到了prefill要32卡,decode要144卡,其实就是32x+14y=2224),平均占用226.75个节点,每张卡2美金1小时,那就是2 * 226.75 * 8 * 24 = 87072美金/天
输入总token数608B,有342B命中kvcache缓存,命中率56.3%
输出总token数168B,平均输出速度20~22tps(tokens per second),这里指的应该是单请求的生成速度,平均每输出一个token的KVCache长度是4989。
平均每台H800(8卡)的吞吐量:
prefill任务:输入吞吐约 73.7k tokens/s(含缓存命中)。
推测:每秒每8卡输入的token数是608000000000 / 226.75/86400=31k,好像对不太上这个73.7k,可能猜测:缓存那部分非常快,非缓存很慢,而这31k是假设全天流量不变的平均值
decode任务:输出吞吐约 14.8k tokens/s。
推测:每秒每8卡输出的token数是168000000000 / 226.75 /86400=8575,前面提到了每个请求每秒的输出token数是20~22,那就相当于每8卡机器每秒能处理有8575/21=408个请求,所以总的返回qps大概是408 * 226.75=9.25k
最后更新于
这有帮助吗?