AI AI infra · 推理侧
第 1 章
AI infra · 推理侧

第 12 章 综合案例:一个文档问答服务的全部账

前面十一章各讲一段。这一章把它们串起来,走一个完整的判断:一个企业文档问答服务,从需求到选型,从上线到出问题。

案例是虚构的,但每个数字都能用前面的公式算出来。你手上的场景不会完全一样,方法是一样的。

需求

一家公司要做内部知识库问答。员工上传问题,系统检索相关文档片段,拼成提示词,让模型回答。

几个关键参数。输入平均 8K token,因为要带上前几段检索结果和对话历史。输出平均 512 token。峰值每秒 10 条请求。体验要求:首字 3 秒以内,出字不低于每秒 30 个 token,P99 不能超过 P50 的三倍。

这几个数字决定了后面所有选择。输入长、输出中等,属于预填充偏重的负载;但输出也不算短,解码的成本不能忽略。

第一步:选模型

候选两个:8B 的 GQA 模型,70B 的 GQA 模型。

8B 的权重 15 GiB,每 token 缓存 128 KiB,8K 请求 1 GiB。

70B 的权重 130 GiB,每 token 缓存 320 KiB,8K 请求 2.5 GiB。

先看装不装得下。8B 一张 80GB 卡绰绰有余;70B 至少要两张,实际要四张才留得出缓存空间。

再看质量。文档问答对事实准确性要求高,70B 通常更好。但这是可以测的:拿一百条真实问题,两个模型各答一遍,人工打分。如果 8B 的准确率差得不多,就没必要为它付四倍的硬件。

假设测下来 8B 够用。这个假设很关键,因为后面所有账都建立在它上面。

第二步:算单卡能接多少

8B,FP16,A100 80GB。

权重 15 GiB,剩 65 GiB。每 token 缓存 128 KiB,一条 8K 请求 1 GiB。按余量算,能并 32 条左右。

再看速度。每步读 15 GiB 权重,按 2039 GB/s 是 7.6 毫秒。批大小 32 时,计算量是 32 乘以 2 乘以 80 亿,约 512 GFLOP,按 312 TFLOPS 是 1.6 毫秒,仍低于读取时间。所以还是带宽受限,每步 7.6 毫秒。

一步产出 32 个 token,一秒约 4200 个。单条请求的出字速度是每秒 131 个,远高于要求的 30 个。

首字延迟:8K 预填充约 166 TFLOP(第 9 章算过),按 312 TFLOPS 是 0.53 秒,实际打对折约 1 秒。加上排队,2 秒以内,满足 3 秒的要求。

结论:一张卡能并 32 条,速度够。

第三步:算要几张卡

用利特尔法则。到达率每秒 10 条;单条停留时间等于首字 1 秒加生成 512 除以 131,约 3.9 秒,合计约 5 秒。

在系统里的平均请求数是 10 乘以 5,等于 50 条。

一张卡只能并 32 条,所以要两张。两张卡的缓存容量是 64 条,留了余量,能扛住短时突发。

这个结果和直觉不同。很多人会按"每秒 10 条、每条 4 秒"直接算 40 条,然后买两张卡;也有人按单卡吞吐算,觉得一张卡的 4200 token 每秒远超需求的 5120,买一张就够。两种算法都漏了同一样东西:在系统里的请求数不等于并发处理数,它由停留时间决定。

第四步:三个方案

方案硬件首字出字每小时成本判断
单卡 L401 张 48GB约 4 秒每秒 21 个低首字和出字都不达标
双卡 A1002 张 80GB约 1 秒每秒 131 个中满足要求,有余量
八卡 H100 加拆解8 张 80GB约 0.6 秒每秒 200 个高明显过剩

单卡 L40 为什么不行:48 GB 显存装 15 GiB 权重够,但带宽只有 300 GB/s,每步读 15 GiB 要 50 毫秒,出字速度掉到每秒 20 个,低于要求的 30。首字也慢,因为算力只有 A100 的一半左右。

八卡方案为什么过剩:它能把首字压到 0.6 秒,但需求只要 3 秒;它能把出字提到 200,但需求只要 30。多出来的能力没人买单。

选中间那个方案。这就是选型的常态:不是选最强的,是选刚好覆盖需求、还留一点余量的。

第五步:成本

两张 A100,按每张每小时两美元举例,每小时四美元。

产出:每秒 5120 个输出 token,一小时 1840 万个。每百万输出 token 的成本约两毛二。

输入也要算。每秒 10 条乘 8K,是每秒 8.2 万 token,一小时 2.95 亿。成本四美元除以 295,每百万输入 token 约一分四。

总成本主要花在输出上,因为输出的单位成本高得多。这也解释了为什么按 token 计费的服务,输出价格通常是输入的几倍。

如果利用率只有一半,两个数字都翻倍。如果上了前缀缓存——文档问答的前缀复用率很高,同一份文档会被反复问到——预填充成本能降一半以上,首字延迟也跟着降。

第六步:上线之后

压测通过不等于线上稳定,因为线上流量会抖。

第一个月遇到三次问题,每次症状不同。

第一次,首字延迟从 1 秒涨到 4 秒,出字正常。查下来是缓存命中率掉了:文档库更新,检索出来的片段变了,前缀缓存全部失效,每条请求都在重新预填充。处理办法是给缓存加版本号,文档更新时渐进失效,而不是一次性全清。

第二次,出字速度从 131 掉到 60,首字正常。查下来是并发涨到了 60 条,超过单卡的 32 条,两张卡都在满负荷,批大小被迫拉大,每步时间上升。这不是故障,是容量不足。加了第三张卡,同时把入口限流从 80 调到 60。

第三次,P99 突然变成 P50 的五倍,平均值几乎没变。查下来是重试风暴:网关超时设得太短,慢请求被重试,重试又加重了队列。把超时从 2 秒调到 8 秒,重试次数从 3 降到 1,问题消失。

三次问题的共同点:症状都在延迟上,病因分别在第 6 章、第 5 章、第 0 章讲过的层。判断的第一步不是改参数,是先看是首字慢还是出字慢,再看队列长度,最后才往下钻。

如果预算只有一半

同样的需求,预算砍半,方案会变成什么样。

两张 A100 换成一张,成本减半,但并发从 64 掉到 32,而需求是 50。直接砍会排队。可行的做法有三条。

第一,量化到 INT8 或用 FP8。权重从 15 GiB 降到 8 GiB 左右,省出的显存能多并七八条,每步读取也减半,单卡吞吐接近翻倍。代价是质量要重新测,好在文档问答对精度不那么敏感。

第二,砍输出长度。512 改成 256,单条停留时间从 5 秒降到 2.9 秒,利特尔法则算出的并发需求从 50 降到 29,一张卡刚好装下。这条最有效,因为输出是成本的大头。

第三,限流加排队。峰值 10 条每秒改成 7 条,超出的排队。用户体验下降,但系统不会崩。

三条叠起来,一张卡能撑住大部分场景。这也说明成本优化的顺序:先砍需求(输出长度、上下文长度),再改精度,最后才加硬件。反过来做,钱花了,问题还在。

第七步:什么时候该换方案

三种信号。

质量不够。如果 8B 答不准,要上 70B。四张卡起步,成本翻倍,但这是质量问题,优化解决不了。

流量涨十倍。两张卡撑不住每秒 100 条,要么加到二十张,要么上拆解:预填充池和解码池分开扩,因为文档问答的预填充占比很高,两个池子的比例和聊天服务完全不同。

上下文变长。如果输入从 8K 涨到 32K,缓存从 1 GiB 变成 4 GiB,单卡并发从 32 掉到 8,卡数要翻四倍。这时候先看前缀缓存能不能救——长文档的前缀复用率往往更高,命中一次省下的也多。

这一章的方法

回头看,整个判断只用了四个工具。

第一,权重和缓存的字节数,决定装不装得下。第二,算力带宽比,决定瓶颈在哪一段。第三,利特尔法则,决定要几张卡。第四,分位数,决定体验好不好。

四个工具都不复杂,难的是顺序:先确认质量,再确认装得下,再算速度,再算容量,最后算钱。顺序反了,就会在错误的层上优化。

这本书到这里结束。十二个章节讲的是一个判断链:一个请求怎么走,每一步受什么限制,限制来自哪一层,改哪一层最划算。具体的技术会变,这条链不会。