我最早接触可扩展性架构设计文档,是从一套需要支撑日活百万级请求的AI推理服务开始的。当时文档写得像系统部署说明,画了几张架构图、贴了一堆K8s配置,评审会上被问到“QPS翻三倍时瓶颈在哪”“GPU加一倍吞吐能跟着线性涨吗”“模型更新时扩展策略是否要改”,一个都答不上来。后来我才意识到,问题不在于模板缺了哪一章,而在于AI系统的可扩展性根本不是“多开几个实例”这么简单——数据并行、模型分片、动态批处理、显存与延迟的权衡,每一项都会改变扩容决策。这篇文章想聊的,就是如何从头写出一份真正能指导落地的AI系统可扩展性架构设计文档,以及一份可以直接套用的章节模板。
1. 为什么AI系统的可扩展性文档值得单独写一份
1.1 可扩展性在AI系统里的含义,和普通后端服务有本质差异
普通后端服务谈可扩展性,基本围绕无状态实例、负载均衡、缓存和数据库读写分离来展开。只要上游服务设计成无状态,加机器基本等于加吞吐。但AI系统不是这样,它至少有三条独立的扩展路径:数据管道要扩展,训练任务要扩展,推理服务也要扩展。每条路径的瓶颈不一样,数据管道可能卡在IO和特征处理,训练任务可能卡在梯度同步和显存,推理服务则卡在单卡吞吐和批处理延迟之间。
更关键的是,AI系统的请求负载不是均匀的。一个文本分类请求和一个多模态审核请求,计算量可能相差几十倍。输入长度变化、batch大小变化、模型是否开启vLLM或TensorRT优化,都会直接影响单实例吞吐。你没法用“每实例支持1000 QPS”这种固定指标来规划容量,必须在文档里把负载特征和扩展策略绑定起来,这条规则,普通后端文档很少需要面对。
1.2 没有文档,扩展性决策会在三个月后被悄悄推翻
我参与过不止一个模型服务项目,初期架构评审时定了“推理节点水平扩展,模型分片在必要时候再做”。三个月后新成员接手,为了修一个显存溢出问题,把请求全部路由到一个大batch推理实例上,单点故障风险瞬间回到原点。不是他不懂架构,而是当初的决策理由、触发条件、回滚方案全部只存在于几个人的聊天记录里。
所以可扩展性文档的真正作用,不是给老板看的汇报材料,而是把“扩展性设计”从口头承诺变成一个可审查、可追溯、可验证的工作产物。它要回答“为什么这么设计”“在什么条件下触发扩展”“扩展后如何验证生效”,将来任何人改动架构时,先看文档判断自己的修改是否违背了已确认的约束,这才是它最大的价值。
1.3 这份文档需要回答的五个核心问题
写文档容易陷入流水账,为了不让它变成“系统现状说明书”,我建议在动笔前先明确文档必须回答的五个问题,整份文档的结构就围绕这五个问题展开:
| 核心问题 | 对应文档章节 | 关键交付物 |
|---|---|---|
| 系统要支撑什么样的负载,目标是什么 | 背景与目标 | 明确QPS、并发数、延迟分位数、训练周期等数字 |
| 当前和预期的瓶颈在哪里 | 现状与瓶颈分析 | 瓶颈清单,区分计算、存储、网络、数据依赖 |
| 扩展靠什么手段实现 | 架构与扩展性方案 | 水平扩展、模型分片、动态批处理、缓存策略等设计 |
| 怎么确认扩展真的有效 | 验证与压测方案 | 压测方案、性能指标基线、扩容触发条件 |
| 扩展的代价和边界是什么 | 风险、取舍与实施路径 | 成本估算、故障域、已知权衡、实施里程碑 |
这五个问题并不是简单罗列,它们之间有一条逻辑链:先有目标,才能判断瓶颈;知道瓶颈,才能设计扩展手段;设计完必须有验证手段;验证后要能说清楚代价。缺少任何一环,评审时都会被问住。
2. 一份可以直接复用的扩展性架构文档模板:章节骨架
2.1 完整章节清单及每节要解决什么
下面这份模板是我在两个AI项目里反复改出来的,标题层面做了通用化处理,你可以根据项目类型增删。核心原则是:每一章都有明确的问题要回答,不是流水账。
- 背景与目标:说明项目要解决的业务问题,给出量化目标(QPS、时延、数据规模、训练周期)。
- 现状与瓶颈:描述当前系统的部署结构、资源规模,以及通过分析或压测发现的瓶颈点。
- 设计原则与取舍:列出扩展性设计遵循的原则,例如“无状态优先”“故障域隔离”“数据管道可重放”,并说明放弃过哪些方案。
- 系统架构总览:给出整体架构视图,标注数据流和控制流,明确每个组件在扩展链路中的角色。
- 扩展性方案设计:按训练侧、推理侧、数据管道分别说明扩展策略,包含触发条件与操作步骤。
- 容量规划与SLA:基于目标负载推算所需资源量,定义扩展后的服务等级目标。
- 验证与压测方案:设计压测场景、指标采集方式和通过标准。
- 风险、取舍与实施里程碑:列出已知风险、对应的缓解措施,以及分阶段实施计划。
- 附录:架构决策记录、术语表、关键配置基线。
这套章节不是拍脑袋定的。比如“设计原则与取舍”放在比较靠前的位置,是因为AI系统非常容易陷入“什么都要优化”的泥潭,如果不在前面锁定原则,后面方案越写越复杂,评审会变得没完没了;“实施里程碑”放在最后,则是为了让文档团队从设计平滑过渡到执行,避免设计完就没人管。
2.2 章节之间的逻辑链怎么串起来
写文档的人经常犯一个毛病:每个章节分开看都还行,合在一起却觉得前后矛盾。要避免这个问题,需要在写作时保持一条贯穿始终的主线。我的做法是,每一章开头都先写一段“本节要回答的问题”,章节正文只围绕这个问题展开,结尾用一句话总结“所以本节得出了什么结论”。当所有章节都遵循同一套叙事方式时,读者从头读到尾,会感觉像是在看一个完整的推演过程,而不是一堆孤立资料的拼凑。
另一个保持逻辑连贯的小技巧是回指。比如“扩展性方案设计”里头写了“推理节点采用水平扩展”,那么在“容量规划与SLA”里就一定要有对应的计算:“因此当QPS达到X时,需要Y个推理节点”。读者能清楚地看到目标是怎样一步步变成具体数字的,评审会上的提问也会少很多。
2.3 避免把设计文档写成系统现状说明书
新人写扩展性文档最常见的跑偏,是花大量篇幅描述现有的资源池、镜像仓库、CI/CD流程,而这些内容跟可扩展性没有直接关系。我理解为什么会这样写:因为这些都是平时最熟悉的东西,写起来不容易出错。但文档的价值密度会变得很低,评审人翻了三页还没看到任何关于扩展的决策。
一个有效的纠偏方法是“结论先行”。每个章节的标题下第一句话,直接给出结论或推荐方案,后面再展开论证。比如“现状与瓶颈”一节,第一句就写“当前系统的主要瓶颈是推理单实例吞吐受显存限制,在batch为32时已接近上限”,而不是从“本系统部署在K8s集群上,共使用多少台机器”开始铺垫。这样写,读文档的人省时间,写文档的人也不会跑偏。
3. 手把手示例:从零写一个多模态推理服务的扩展性文档
3.1 示例项目背景设定
为了让你能直接照着写,这里虚构一个比较典型的项目:某内容平台内部的多模态推理服务,主要功能是对用户上传的图文内容做分类、向量化和内容审核,上游调用方包括内容推荐、搜索、审核等多个业务线。当时项目启动时的目标是支撑500 QPS的日常负载,并具备扩展到2000 QPS峰值的能力,P95时延控制在100毫秒以内。模型侧使用一个多模态特征提取模型,输入是一张图和一段文本,输出是分类标签和一维向量。
这个设定覆盖了AI系统常见的三个扩展面:在线推理服务要扛住峰值流量,模型更新要尽量不中断服务,特征数据管道要跟上增量数据的处理速度。后面每个章节的演示都围绕这三个面展开,不会跑偏到纯业务后端去。
3.2 第一步:写清目标与度量,把“可扩展”变成数字
“可扩展”如果没有数字等于没有定义。这个示例的目标部分我建议写成这样:
- 日常负载:500 QPS,P95延迟小于100毫秒。
- 峰值负载:2000 QPS,P95延迟小于150毫秒,持续时长不低于30分钟。
- 资源效率:正常情况下GPU平均利用率不低于40%,峰值扩容时不低于60%。
- 扩展速度:从触发扩容到服务能力就绪,时间控制在10分钟以内。
- 成本上限:峰值扩容状态下单位请求的算力成本不得超过日常状态的1.5倍。
这些数字里,“扩展速度”经常被遗漏,但它在AI系统里特别关键。因为模型服务会涉及GPU资源调度,如果集群里没有空闲GPU,扩容可能不是加Pod而是先买机器或等资源释放,定义10分钟就倒逼架构设计时必须考虑资源预留和优先调度策略。同理,“峰值时延放宽到150毫秒”是很重要的取舍:纯水平扩展时网络和队列变长,时延会略升,如果不提前允许这个变化,压测永远过不了。
3.3 第二步:写架构拆解与扩展动作,明确每个组件扩什么
这个示例的架构我拆成了三个组件:请求网关、推理引擎、向量索引与缓存。文档里对每个组件都要写清楚它的状态特征是理解整个扩展性设计的关键:
- 请求网关:完全无状态,负责鉴权、路由、限流。要扩展时直接加实例,基本不需要动模型相关的配置。
- 推理引擎:无状态的模型计算单元,但需要读取模型权重和加载到GPU显存。每个实例持有的都是同一份模型副本,请求到达后独立计算,实例之间不通信。
- 向量索引与缓存:有状态,保存已经计算过的向量结果和倒排索引,支撑重复请求的召回。扩展方式不能只靠加节点,还要考虑索引分片和一致性更新。
文档里头要专门解释推理引擎的“无状态”设计。多模态请求不会像会话场景那样要求关联上下文,所以可以把用户请求直接路由到任意一个推理Pod,不需要session亲和。这让水平扩展变得极为简单,扩容时只需要加Pod,不需要重新分发状态。相反,向量索引和缓存是有状态组件,扩展时必须考虑分片策略,比如按业务线或向量ID范围做分片,再通过一致性哈希路由。
3.4 第三步:给出可复制的设计落地写法与配置基线
扩展性文档除了讲道理,还需要给出具体落地配置,让人知道“下一步到底怎么操作”。以下面这段YAML为例,展示推理引擎在K8s层面的扩展基线设计,文档里可以直接复用这类描述:
apiVersion: apps/v1 kind: Deployment metadata: name: multimodal-inference labels: app: multimodal-inference spec: replicas: 8 selector: matchLabels: app: multimodal-inference template: spec: containers: - name: inference image: inference-image:2.4.1 resources: limits: nvidia.com/gpu: 1 ports: - containerPort: 8080 topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: multimodal-inference-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: multimodal-inference minReplicas: 8 maxReplicas: 24 metrics: - type: Pods pods: metric: name: inference_queue_depth target: type: AverageValue averageValue: 5你不要把这段配置当成完整生产配置,它更重要的是说明写作方法。右侧的拓扑分布约束(topologySpreadConstraints)是为了避免多个推理Pod挤在同一台物理机上,导致GPU显存碎片和故障域集中;HPA用的是自定义指标inference_queue_depth而不是默认CPU,是因为GPU服务在batch推理时CPU往往不高,但请求队列深度已经说明处理能力跟不上了。这种微观决策写进文档里,就是它区别于普通配置文件的价值。
3.5 第三步续:训练与数据管道的扩展性如何写
很多写手会把推理服务的扩展文档写得非常详细,但AI系统还有训练和数据处理两个环节。示例项目的文档里,“扩展性方案设计”一章分成三节,分别对应推理、训练、数据管道。训练侧的写法不一定要写完整的分布式训练方案,但至少要回答“当训练数据翻倍时怎么办”。
比如这个示例项目当前用单机单卡训练,每周更新一次模型权重。如果数据翻倍或希望每天更新,就需要引入数据并行训练,把训练数据切分到多卡上,同步使用AllReduce做梯度聚合。文档里要写清楚选择框架的理由:团队对库的熟悉程度、调试成本、对模型结构的适配性。数据管道侧则强调可重放性,所有的特征计算结果都应该能追溯,进到消息队列里的每条原始记录要有唯一ID,这样下游扩展出问题时不至于需要重跑整条链路。
4. 量化指标与基准验证:让扩展性文档里的数字经得起评审
4.1 三个必须定义清楚的指标家族
可扩展性文档写得好不好,很大程度取决于指标是否定义得无歧义。我一般会要求文档里至少包含三个家族指标:
| 指标家族 | 代表指标 | 怎么测 | 可接受阈值(示例) |
|---|---|---|---|
| 吞吐与时延 | QPS、P50/P95/P99时延 | 压测工具逐步加压 | 峰值下P95≤150ms |
| 资源效率 | GPU利用率、显存水位、CPU/内存 | 监控系统与压测联动 | 正常负载利用率≥40% |
| 扩展效率 | 加速比、单位成本、扩容生效时间 | 固定负载下对比不同副本数 | 8实例到16实例吞吐提升≥1.6倍 |
“扩展效率”这一项最容易被忽略。比如你从8个推理实例扩到16个,理论上吞吐应该翻倍,但因为负载均衡不均、队列热点、模型推理batch处理不饱和,实际可能只提升1.3倍。如果不把扩展效率写进文档,团队就没有动力去优化调度策略,只会天真地以为加机器就能解决所有问题。
4.2 容量规划的反推计算:从QPS到GPU卡数
既然文档里要写容量规划,我就给出一个具体可复用的计算过程。假设上面的多模态推理服务需要支撑2000 QPS峰值,单张GPU在batch为32、输入约1024 token时,实测吞吐大约是300 QPS,P95延迟120毫秒。那么需要的卡数可以这样估算:
所需GPU卡数 = 峰值QPS ÷ (单卡吞吐 × 冗余系数)
代入数字后:2000 ÷ (300 × 0.85) ≈ 7.8,向上取整得到8张GPU卡。冗余系数0.85是给集群碎片化、压测波动和故障腾挪预留的量。有同学会问为什么不直接用1.0甚至1.2,原因是单卡吞吐在理想压测环境下测出来比较乐观,真实流量里长尾请求、多租户争抢、版本发布时的资源占用都会打折,0.85不算保守,是经验值。
文档里要把这个计算过程完整写出来,而不是只写结论“我们需要8卡”。评审人来历不一,有人会质疑单卡吞吐,有人会觉得冗余系数太低,如果你把公式和假设条件摆出来,讨论就能聚焦在参数合理性上,而不是推翻整个规划。
4.3 压测与验证流程:上线前必须做一轮“预演扩容”
扩展性文档里的数字不能只靠估算,必须有验证闭环。示例项目的压测方案分成三步:第一步是基线压测,在8卡部署下用目标负载跑30分钟,记录QPS、时延和GPU利用率;第二步是模拟峰值,把负载线性提升到2000 QPS,观察HPA触发扩容后服务的表现;第三步是持续验证,在峰值负载下运行60分钟,确认扩容后的服务没有出现时延毛刺或GPU内存溢出。
这一步的目的其实是验证两个关键假设:扩容真的提升了吞吐;扩容过程中系统没有做无用的调度。经常出现的情况是,HPA配置了但指标管道延迟太高,扩容指令到了5分钟后才开始执行,10分钟的目标直接超时。所以压测报告里要单列“扩容生效时间”,从指标超过阈值到新Pod Ready的时间,这个数字才是扩展速度的真实体现。
5. 文档撰写中最容易踩的坑,以及正确的写作习惯
5.1 坑一:只写最终架构,不写权衡过程
我见过太多扩展性文档,直接给出一个“最优方案”,但完全不提放弃过哪些替代方案。比如为什么不选模型并行而选数据并行?为什么不引入外部缓存?如果不写清楚这些取舍,后续有人发现方案在某些场景下不够好时,会直接推倒重来,因为他不知道当时的约束条件是什么。
正确做法是在“设计原则与取舍”章节里把候选方案和放弃原因写清楚。哪怕只是两三句话,比如“在推理侧没有选择模型并行,因为单卡可以容纳模型权重,模型并行会引入额外的跨卡通信,延时不达标”,这种记录能让后人少走很多弯路。
5.2 坑二:把“可扩展性”写成空话
“本系统支持水平扩展,可满足海量请求”这种话在文档里没有任何信息量。必须替换成可操作、可验证的描述:水平扩展的具体对象是什么,扩展触发条件是什么,扩展后的预期收益如何测量。我的一个执念是,每写完一个扩展性相关的句子,都要问自己“这句话能被测试证明吗”。如果证明不了,要么补指标,要么删掉,不要让它留在文档里制造幻觉。
5.3 坑三:忽略文档读者的多样性
一份扩展性文档的读者通常有三类人:做技术决策的架构评审者、负责实施和运维的工程师、以及后续维护的新成员。这三类人对同一段文字的关注点截然不同。评审者想快速看到决策和理由,实施者想找到具体的扩展操作步骤,后来者想知道当前的扩展边界在哪里。
应对办法是:在文档开头放一页精简的决策摘要,包含目标、关键扩展策略、已知取舍和风险;正文部分保留完整细节和操作步骤;附录放配置、术语表和架构决策记录。这样每一类读者都能快速找到自己的入口。
5.4 坑四:文档写完就锁死,跟实现彻底脱节
架构文档最大的敌人是时间。系统演进半年后,文档还停留在初始版本,不仅没用还会误导人。我建议把扩展性文档当成代码一样做版本管理,每一次架构决策变更都要同步更新文档。更轻量的做法是维护一张“变更记录表”,写在文档最前面,任何人的修改都要在表里追加一行:日期、修改人、变更内容、触发原因。这个习惯成本很低,却能让文档一直保持活跃。
5.5 坑五:只聊推理服务,不顾训练和数据的扩展
AI系统的一个特殊性是,训练消耗的资源往往是推理的好几倍,数据管道又把两者串在一起。很多项目只写了推理服务如何扩容,却没说训练数据增长时算力怎么规划,数据通道积压时怎么降级。结果业务量一旦上来,最先崩的往往是数据管道,而不是模型服务本身。这提醒我们,在写扩展性文档前先画一张完整的数据流图,再对照数据流推导瓶颈,而不是只盯着最容易被感知的在线推理部分。
最后再分享一个个人习惯:每次设计文档评审会结束,我会把评审人提的问题逐条记到文档末尾的“待确认清单”里,并在下一个版本中逐个闭环。“待确认清单”看起来不是模板的正式章节,但它承担着让文档可持续演进的作用,很多当初设计时没想到的扩展边界,都是通过评审意见补进去的。如果你现在正打算写AI系统的可扩展性架构设计文档,建议先跳过“写得多完整”这个执念,从回答五个问题开始,再用模板逐步填充细节,最后用压测数字验证。文档本身不会让系统变得可扩展,但它能把所有关于可扩展的讨论,都变成可以执行的工程决策。