← 返回系列总览
[从零训练一个500B大语言模型] 24/25:怎样让用户真正用上这个模型?

[从零训练一个500B大语言模型] 24/25:怎样让用户真正用上这个模型?

上一轮评估把C留作交付候选。我把它的权重、分词器和聊天模板放到一起,准备打开一个真正能用的聊天窗口。

训练时,5,120张B200围着同一件事忙:读材料,算梯度,更新参数。现在屏幕前换成了用户。有人只问一句话,有人贴进整份说明书,有人刚读完回答,又接着追问。机器要把这些长短不一的请求接住,再把答案及时送回来。

第一步先从一个人、一条请求开始。等这条路走通,再看多少人一起使用时,需要多少台机器。

一份模型,先放进两台服务器

500B权重按BF16保存,每个参数2字节,原始权重约1TB。手边一台服务器有八张B200,每张180GB,合计1.44TB。把权重均匀分开,每张卡已经占了约125GB。

还剩下一部分显存,但回答问题也要有工作空间。用户贴进来的长文、生成过程保留的记录、临时计算结果,都要住在这里。只把权重装进去,还没有回答同时来了几十个人时怎么办。

我先安排两台八卡服务器一起承载一个模型副本。第一台负责前一段模型,第二台接着算后一段;每台内部,八张卡共同分担每一层的计算。这就是机内TP8、跨机PP2。权重平均到16张卡上,每张约62.5GB,留下更多空间接待请求。

两台接力也有代价:中间结果要跨机器传,单条请求的等待可能增加。我把八卡方案留作紧凑对照,首轮先用16卡把加载、回答和容量跑清楚。

BF16:每个数占16位、也就是2字节的一种数值格式。这里用它保存计算权重。

服务副本:能够独立完成模型推理的一组资源,一个副本可以跨多张GPU或多台服务器。

TP8:张量并行度为8,把同一层的计算分给八张GPU。

PP2:流水线并行度为2,把模型的层分成前后两段,由两组GPU接力计算。

先把一条服务路径固定下来

服务器准备好,还需要软件负责加载权重、安排计算和接收请求。我在vLLM与SGLang之间先选了vLLM。它的并行文档给出了两台八卡机器使用TP8加PP2的路径,正好可以作为这次集成的起点。

这个选择让我先固定一套启动、监控和压测配置。模型格式是否正确、并行切分是否支持、显存在哪里占用,都沿着同一条路径核查。SGLang的服务参数也包含并行、缓存与服务设置,我把它留作下一轮同条件比较的对象。

眼前要完成的事这次怎样做留给下一轮的比较
两台服务器加载一个模型沿vLLM的TP8、PP2组合验收权重与输出同样资源上核对SGLang的架构支持与加载结果
找到稳定的服务速度固定一套请求、监控与压测记录同一请求集、质量门槛和延迟目标下比较吞吐
处理重复长文与多轮对话在基准上逐项启用缓存比较相同重复比例下的收益与迁移工作量

权重导出以后,我先送入与训练环境相同的输入,核对输出分布和任务成绩。转换正确,再看峰值显存和持续运行。版本、镜像、模板与启动参数一起保存,后面每次优化才有一个可靠的参照。

入口负责检查身份和请求额度,路由器把请求交给健康副本,vLLM生成回答,监控记录时间与错误。服务机器从训练任务的资源分区里分出来,用户等着回答时,它们就专心处理请求。

这条路接通后,我贴进一篇4,000个token的材料,请模型写一段总结。屏幕先空了一会儿,随后文字开始往外走。

路由器:在这里指把请求分配给可用模型副本的服务组件。

为什么先等一下,随后才一个字一个字出现

第一段等待里,模型在读我贴进去的材料。它要计算这些位置的表示,为开始回答做准备。许多输入位置可以放在一起计算,这个阶段叫prefill。

等到第一个回答token出现,工作节奏就变了。模型把刚生成的token接到前面,预测下一个,再把新结果接回去。后一个要等前一个确定,这个逐步往下写的阶段叫decode。

因此,用户感受到的“快”其实有两件事:多久看见第一个输出,后面的内容又以什么速度出现。长文让开头的工作增多,长回答则让后面的生成持续更久。我把两段分别记下来,不再只看整条请求用了几秒。

第二个问题紧跟着来了。每生成一个新token,难道都要把刚才4,000个位置重新计算一遍?

Prefill:模型处理已有输入上下文、为开始生成做准备的阶段。

Decode:模型根据已有内容逐步生成新token的阶段。

前面的文字没变,那些记录可以留下来

第六篇讲过Attention的Q、K、V。生成继续往前走时,已有历史位置的K和V可以复用。引擎把它们留在显存里,下一步直接读取,再把新位置产生的K和V添进去。

Prefill生成首token并建立KV缓存,decode复用历史缓存逐步输出和追加新KV

沿图从左边看,prefill读完输入,留下历史K、V,并产生第一个回答token。右边的decode读入刚生成的token,复用历史记录,预测下一个,同时把新位置的K、V追加进去。两个MODEL是同一个模型在两个阶段的运行,缓存小块代表各位置的向量组,每组里有多个数值。

这就是KV cache。它省下重复计算,也占用了刚才预留的显存。

我先把这笔账拆到一层、一个位置。只看其中一组K和V,每组有128个数,每个数占2字节,这一小份记录就是2 × 128 × 2 = 512字节。模型有很多层,每层要留自己的记录;一层里又可能有多组K、V。历史越长,记录越多。

更关键的是,模型权重由整个副本共用,缓存却跟着请求走。两个人贴进不同材料,就各有各的历史。回答还没结束,缓存通常继续增长;请求完成,相应空间才可以释放,或按引擎规则保留可复用的前缀。

这解释了为什么一条请求能跑,不代表几十条也能一起跑。接下来要测的显存峰值,是目标输入长度、输出长度和并发数同时出现时的峰值。

KV cache:保存历史token在Attention中产生的K与V,让后续生成复用这些结果的缓存。

并发数:同一时间仍在处理中的请求数量。

把一层一位置的缓存,算到完整请求

每层保存的一组K、V称作一个KV头,维度表示其中每个向量包含多少个数。常规KV存储可按下面的关系计算张量容量:

KV字节数 = 2 × 层数 × KV头数 × 每头维度 × token数 × 每元素字节数

开头的2对应K和V。拿80层、8个KV头、每头128维、每元素2字节作一次换算,每个token就是327,680字节;8,192个token约占2.68GB,32条同样长度的请求约85.9GB。输出继续增长,容量也跟着增长。

部署账使用模型配置中的实际层数、KV头数和维度。这一式计算的是常规缓存张量;分配器、对齐和运行时另占空间。采用压缩缓存或其他Attention结构时,按相应实现重新计算。

第二个人来了,队列开始改变

一条请求跑通以后,我把测试改成多个人同时提问。有的答案只要几十个token,有的还在写长篇解释。如果一定等整批最长的那个结束,再接下一批,已经空下来的位置就会一直等。

连续批处理把这个空位用起来:完成的请求退出,新的请求加入,仍在生成的回答继续往前走。不同长度的请求因此能够共享计算批次。调度记录开始变得重要,我们既要让GPU持续工作,也要盯住每个人实际等了多久。

重复材料又提供了另一处机会。同一份说明书被反复提问,或者每次请求都带着相同的系统说明,匹配的前缀可以复用已有KV。vLLM的前缀缓存说明解释了这条路径。我把压测分成输入各不相同和共享前缀两组,分别看命中与未命中的速度,再按实际重复比例安排容量。

长输入仍然可能挤占时间,让已经开始输出的短回答停下来。分块prefill把长输入的处理拆开,与decode工作一起调度。我逐步调整批处理的token上限,观察首token等待和后续生成间隔,找到两者都能接受的位置。

第一版保留同一个副本完成prefill和decode。分开部署两阶段,可以分别扩容,但也要传输KV、增加调度与网络工作。我先把当前队列测清楚,记录到底是哪一段吃紧,再决定是否值得拆开。

连续批处理:在生成过程中动态加入和移除请求,使不同长度的请求共享计算批次。

前缀缓存:复用完全匹配的输入前缀所产生的缓存,减少重复处理。权限不同的请求仍需正确隔离。

想再省显存,就得回到那张试卷

缓存和调度调过以后,权重仍然占着约1TB空间。若把每个权重使用的位数减少,8位的原始权重约500GB,4位约250GB。省下的空间可能接待更多请求,也可能减少数据搬运。

但更少的位数会改变数值精度。我把当前BF16版本留下,作为质量与速度的基准,随后才按vLLM的量化支持列表核对固定版本与B200能够使用的8位方案。格式里的缩放因子等元数据、缓存和临时空间也一起计入运行显存。

每个候选先跑第23篇的独立验证题,尤其看长文、接近正确边界的题目和工具格式,再用相同并发重跑压测。这样,速度收益与质量变化出现在同一份记录里。需要选校准材料的方案,也覆盖中英文、代码、长文和困难题。

首版继续以BF16交付。8位方案要同时守住质量、拿到持续吞吐收益,才进入部署候选;4位留到下一轮单独比较。变更了数值格式,最终放行也要连同服务配置重新检查。

量化:用更少的数值位数表示权重或其他张量,以降低存储和计算相关开销。

一组机器测完了,整个服务该留多少卡

经过这轮压测,16卡副本在目标等待时间内,持续处理每秒8个请求。这一行记录旁边还存着输入输出长度分布,因为一个输出100个token的问题,和一个输出2,000个token的问题,占用的资源差很多。

产品预期的忙时流量是每秒20个请求。我没有把每个副本排到压测极限,而是把正常负载定在测得能力的70%,给波动留出空间。一个副本按每秒5.6个请求安排,工作副本数量就是:

向上取整[20 ÷ (8 × 70%)] = 4个

四个工作副本占64张卡。再留一个能够接替故障的副本,一共五组、80张卡,也就是十台八卡服务器。

用户请求经过入口和路由器分给四个工作副本,另留一个接替副本;每个副本由两台八卡服务器组成,共80张GPU

现在沿总图走一遍。用户请求通过入口检查,路由器把它交给一个可用副本;副本中的两台服务器接力完成回答,再把输出送回用户。四组分别接待不同请求,标作备用的第五组准备在故障时接替。放大一组,还是开头那两台机器:每台八张卡,一台负责模型前段,一台负责后段。

主训练结束后,这十台服务器从原来的活跃资源中划出来,其余机器继续承担研发与评估。训练用的20台备用机仍保留维修替换职责。下一轮若要同时用足5,120张卡训练,服务池就需要另行安排,或者把训练规模与排期重新对齐。

最后还要把真实请求轨迹回放到这五组机器上,检查高峰和故障切换。每秒8个请求只对那组压测输入成立;长文比例、回答长度或用户行为改变,容量表就跟着重算。

我再次把那份说明书贴进聊天窗口。屏幕上的动作仍然很简单:等待片刻,文字开始出现。背后却已经有了完整的路径,知道谁负责接入、哪两台机器处理这一条请求,以及忙起来时怎样把请求分给下一组。

模型终于从训练目录走到了用户面前。接下来要把这80张卡的运行费用,连同前面投入的机器、人和时间放到同一张账里,看看完成一次回答、完成一轮改进,究竟花了多少钱。

新文章,直接发到你的邮箱。

AI、工程与实验,每月 1–2 封。

无垃圾邮件,随时取消。