我见过不少团队,算法那边把模型训练好了,评测指标也很漂亮,结果交到工程手里一上线就卡死、超时、显存炸掉,最后大家互相甩锅。这类问题十有八九不是模型不行,而是AI系统架构没搭对。很多人对AI系统架构的理解还停留在“把模型封装成一个HTTP接口丢到服务器上”,但真实生产环境里,这套东西要面对的远不止一个接口:并发流量、多租户隔离、模型副本调度、上下文管理、向量检索、成本控制、可观测性,哪个环节没有设计好,整个系统都会变得又慢又贵又难维护。这篇文章我结合自己做过的一些实际项目,把一套可落地的AI系统架构从设计思路到核心组件、再到参数计算和问题排查,完整拆开讲清楚,适合正在做AI应用工程化、或者准备从单体脚本升级到服务化架构的同学参考。
1. AI系统架构到底在解决什么问题
1.1 它不是简单的读写数据库,而是“模型+系统”的组合工程
传统后端架构的核心是数据一致性和可用性,你写一个订单系统,需要考虑的是怎么保证库存不超卖、数据库挂了怎么切换。AI系统架构多了一个特别麻烦的东西:模型推理。模型推理不是一个纯计算过程,它会占用大量显存、有较大的延迟波动、还依赖上下文状态。更麻烦的是,一个AI系统通常不是一个模型单独跑,数据要经过预处理、召回、重排、生成、后处理等多个环节,这些环节还要跟现有业务系统打通。
我个人的理解是,AI系统架构更像是在“模型的不确定性”和“系统的确定性”之间做翻译。模型输出的结果不可能每次都一样,响应时间也不可能都是恒定值,但对外提供的接口必须稳定、可控。这个矛盾就是架构设计要解决的第一问题。
1.2 单体脚本为什么撑不起一个AI应用
一开始很多团队的做法是这样:写一个Python脚本,加载一个开源模型,用Flask包一个接口,数据库直连,接口内部既做向量检索又做Prompt拼装还要同步调用模型。这个方案做个Demo完全没问题,但只要你面临以下任何一个场景,它就顶不住了:
- 同时有多个业务方要调用同一个模型,各自需要不同的Prompt逻辑和权限控制;
- 模型推理耗时长,并发一上来,进程内排队直接导致接口超时;
- 需要升级模型版本,却不能停服,只能在服务运行状态下动态切换;
- 单一模型解决不了所有问题,需要根据请求内容路由到不同模型。
单体脚本把这些逻辑全部耦合在一起,任何一个环节变动都要重新发版,可维护性极差。所以稍微正规一点的项目,都要把架构拆成多层服务。
1.3 架构分层背后的核心原则
做AI系统架构有一个容易走偏的点:一上来就追求大而全的微服务、Kubernetes、复杂网关,结果团队精力全部消耗在基础设施上,业务模型反而没人管。我的经验是:架构设计应该从“最复杂的链路”倒推,而不是从“最流行的组件”正推。也就是说,先画出一次完整请求从进入到返回所经过的所有节点,找到最消耗资源或者最不稳定的环节,然后围绕这个环节做独立拆分和扩展。
另外,成本也是一个很现实的问题。GPU资源很贵,AI系统的架构又不能像普通业务一样无限横向扩容,因为显存是硬约束,买再多的机器也得看模型能不能拆分到多卡上。所以AI系统架构设计从一开始就不是纯技术问题,而是技术、成本和业务三者之间找平衡。
2. 核心组件拆解与选型思路
AI系统架构跟传统后端最大的不同,是它多了一个专门跑模型的推理层。下面我把一套典型架构按五个层次拆开,每一层做什么、该怎么选型,都会讲到。
2.1 接入层与API网关:先把入口管起来
接入层是外部请求进入系统的第一道门。在AI系统里,接入层跟普通网关不太一样,它除了要处理鉴权、限流、日志这些常规功能之外,还需要额外处理两个问题:一个是模型接口的语义统一,另一个是按模型维度的配额管理。
什么叫语义统一?不同模型(自己训练的、开源部署的、第三方云服务的)输入输出格式各不相同。有的接受Messages数组,有的自带System字段,有的只接受单条文本。如果业务方直接对接每一种模型,那模型一换,所有调用方的代码都得改。接入层要做的事情是把这些差包装起来,对外只暴露一个统一的“问答接口”或“向量接口”,内部再转换成每个模型自己的格式。
配额管理则是因为AI模型的调用成本高,且需要跟账号体系打通,所以接入层要对每个调用方设置独立的QPS上限、每分钟Token上限、单次请求超时时间。比如某个内部工具是低频使用,那就给它限制在10 QPS;某个核心流程用量大,可以放开到200 QPS,但每用户单日Token总量要限制。这些策略在接入层做最合适,因为后面各层不需要关心“调用方是谁”。
2.2 服务编排层:把模型变成业务流程
服务编排层是AI系统架构里最灵活的层,也是我见过最多烂代码的地方。通常一个真实的AI功能不是简单地“输入一句话-模型返回一句话”,它前面可能要检索企业知识库,中间要判断需不需要调用外部工具,最后还要做输出格式校验。这些步骤之间是串行还是并行、哪个环节失败可以降级、哪个环节不能容忍失败,都属于服务编排层的职责。
这里的关键设计原则是:编排层不应该写死任何模型细节。也就是说,编排层只关心流程逻辑,比如“先检索,再生成”,至于生成用的是什么模型、模型参数是多少,应该通过配置中心下发。这样做有一个很直接的好处:模型升级或切换时,只需要改配置,不需要改代码。
实际执行中,我会把每个环节抽象成独立函数或独立微服务,环节之间通过明确的输入输出结构连接。举例来说,一个知识库问答的编排流程大概是:
- 接收用户问题;
- 调用向量检索服务召回相关文档片段;
- 对召回结果做重排,保留最相关的Top 3;
- 把用户问题、召回文档片段、历史对话组合成Prompt;
- 调用模型服务生成回答;
- 对生成结果做关键词过滤和格式修正;
- 返回结果并记录trace。
这个流程看起来不复杂,但每个步骤都可能出问题:向量库挂了怎么办?模型超时了怎么办?召回结果为空是直接告诉用户“没找到”,还是走一个兜底模型?这些都是编排层在写代码前就要定好的决策。我的建议是,在编排层把所有外部依赖都设计成可降级的接口,宁可回答质量差一点,也比整个请求失败要好。
2.3 模型推理层:性能瓶颈的集中地
模型推理层是整个AI系统架构最核心的部分。这层不做别的,就是高效地运行模型,对外提供推理能力。为什么单把它拎出来,而不直接在代码里加载模型调用?因为模型推理有自己独立的性能模型:推理要占用显存、推理过程有长尾延迟、并发吞吐受限于batch大小。如果不把它单独拆出来,这些性能问题会直接绑架整个业务系统。
模型推理层的第一个技术重点,是推理引擎的选择。开源生态里可选的方案很多,不同引擎对同一模型的支持度、显存占用、吞吐优化、动态批处理支持都不一样。我的经验是,先用最通用的引擎把服务跑起来,再根据真实压测数据评估是否值得换高吞吐引擎。很多团队一上来就追求高性能引擎,结果模型格式不兼容,折腾半天。其实对于大多数业务系统,默认引擎加合理的并发控制,已经能覆盖80%的场景。
第二个重点是模型是否需要长期驻留显存。大多数自建AI系统,模型都是常驻显存的,这样可以避免反复加载。但多模型场景下,如果同时常驻多个大模型,显存会非常紧张。这时候有两种选择:一种是对流量不高的模型做“按需加载+存活时间管理”,也就是一段时间没请求就释放显存,等有请求再重新加载;另一种是把不同模型打散到不同的服务实例上,每个实例只跑一个模型,通过路由把请求分发到对应实例。具体选哪一种,要看模型切换频率和单模型流量。
2.4 数据与向量检索:AI系统里的记忆系统
AI系统不能只依赖模型自身的参数知识,尤其是企业内部知识库、私域数据这类场景,必须把“事实”存到系统外部,在需要时检索出来交给模型。这一层很容易被忽略,但它往往决定了系统回答的准确度。
核心组件是向量数据库和文档处理流水线。文档入库的过程一般是:解析原始文档(PDF、Word、Markdown),做文本清洗,按一定规则切片,为每个切片生成向量,然后把向量和原始文本一起存入向量库。看起来简单,但切片大小的选择直接影响召回效果。切得太碎,语义不完整;切得太大,向量之间区分度降低,检索出来一堆无关内容还浪费模型上下文。
我的做法是,先按标题和段落结构做结构化切片,每个切片控制在500-800字左右,再设置一定的重叠长度,避免关键信息恰好被切在边缘。另外,切片入库时最好保留标题、章节路径等元数据,这样在前端展示引用来源时,可以直接告诉用户“这段话来自哪个文档的哪个小节”,对提升信任感帮助很大。
至于向量库选型,市面上方案很多。如果是数据量不大、几万条级别,直接用轻量的向量索引就够了;如果是百万级向量、且对延迟要求很高,再上专业向量数据库或者分布式方案。不要过度设计,这是数据层最容易犯的错误。
2.5 基础设施与可观测性:没有监控的架构等于盲飞
AI系统架构较传统系统更难排查问题,因为链路长、每个环节都有延迟、模型输出还是非确定性的。没有一套完善的可观测性体系,出了问题根本没法定位。
基础设施层我重点关注三个维度:
- 资源监控:GPU显存使用率、GPU算力利用率、CPU内存、磁盘IO,这些指标决定你还能不能抗住下一波流量;
- 链路追踪:一次请求在接入层、编排层、模型层、向量库分别耗时多久,哪个环节成了瓶颈;
- 日志结构化:把每次请求的输入、检索到的文档ID、拼接后的Prompt、模型输出、Token消耗量全部结构化记录,方便复盘“为什么这次的回答是错的”。
这里要特别强调Token消耗量的重要性。很多AI系统的成本跟Token使用量直接挂钩,按请求记录Token,不仅方便算账,也能帮助发现异常调用。比如某个业务方请求量没变,但Token消耗翻了三倍,很可能是因为他的Prompt模板里被塞进了大段冗余文本。
3. 一次完整落地案例:某知识库问答系统的架构复盘
前面把分层讲了一遍,但光说不练还是不够。下面我拿一个实际做过的模拟项目“企业知识库智能问答系统”来复盘,这个项目当时是给公司内部客服和销售团队用的,需要从几百份产品文档和售后手册中找答案。
3.1 项目需求与架构分层
这个系统的核心流程很明确:用户提问,系统从知识库中检索相关文档,再交由模型组织答案。但实际做的时候,需求方又提了一些硬性要求:回答必须标注引用来源;不能出现文档里没有的信息;客服端并发很高,高峰期不能把后台查询压垮。
基于这些约束,我最终给的架构是这样分层的:
| 层次 | 组件 | 核心职责 |
|---|---|---|
| 接入层 | API网关+统一鉴权 | 校验调用方身份、按应用限流、统一请求/响应格式 |
| 编排层 | 问答服务 | 管理检索、重排、Prompt拼装、调用模型的整个流程 |
| 模型层 | 模型服务集群 | 运行开源问答模型,提供文本生成能力 |
| 数据层 | 文档切片服务+向量库 | 负责文档清洗、切片、向量化、相似度检索 |
| 可观测层 | 监控+日志+链路追踪 | 记录全链路耗时、Token消耗、异常报警 |
这样拆分之后,每个模块都能独立迭代。比如文档切片逻辑要调整,只需要改数据层;模型要升级,只需要在模型层灰度替换,编排层的代码完全不用动。
3.2 服务编排里的核心流程与容错决策
这个系统的编排逻辑,我写过一个简化版的伪代码(不是真实项目代码,只是示意):
def answer_question(user_question, history): # 1. 生成提问向量并召回,设置一个较短的超时时间 try: candidates = vector_search(question=user_question, top_k=10) except VectorDBTimeout: candidates = [] # 2. 对召回结果做重排,把最相关的文档排到前面 top_docs = rerank(candidates, user_question)[:3] # 3. 组装 Prompt,注意控制上下文长度 context = concat_documents(top_docs) prompt = build_prompt(user_question, context, history) # 4. 调用模型生成,这里必须设置最大等待时间 response = model_generate(prompt, max_tokens=512) # 5. 对回答里的引用标记做后处理,保证展示格式正确 return format_quote(response, top_docs)这段逻辑在实际运行中踩过好几个坑,我在第5节会展开讲。单从架构角度说,最关键的决策是:向量库超时不能拖累整个问答主流程。实际方案里我设计了一个降级策略——当向量库不可用或召回结果为空时,系统会自动切换成“仅靠模型既有知识回答,并明确提示用户答案可能不准确”。这样虽然回答质量会下降,但接口不会直接失败。
3.3 模型服务的配置与关键参数
模型服务那一层,我用的配置大概是这样的思路(以常见推理服务框架为例,参数名可能因技术栈不同而有差异):
modelserver: model: 本地路径或模型标识 device: cuda gpu_memory_utilization: 0.85 # 最多可用显存比例,留一部分给上下文缓存 max_total_tokens: 4096 # 单请求最大总Token数 max_batch_size: 8 # 动态批处理最大合并请求数 wait_time: 0.1 # 动态批处理等待窗口,单位秒这里解释几个参数为什么这样定。gpu_memory_utilization不要配成1.0,因为在推理过程中,多请求共享的KV Cache也要占显存,一旦显存耗尽服务会直接OOM,留15%左右作为余量更安全。max_total_tokens决定了单次请求能处理的文本总长度,如果知识库文档拼得太多,很容易超限,所以编排层在组装Prompt时就要估算Token数量,超了就截断。max_batch_size和wait_time是一组配合参数,控制的是“多个并发请求是否合并成一次推理”——合并得越多,GPU利用率越高,但每个请求都要多等一个等待窗口,延迟会变长,需要根据实际流量调。
3.4 上线前后的调优过程
刚上线时我们遇到的第一个现象是:只有几十个人在用,但模型服务偶尔会出现几秒钟的卡顿。查下来发现根因是并发请求一到,动态批处理的等待窗口设置得太长,导致每个请求都在排队,整体延迟反而上升。后来把等待窗口从0.5秒调到0.1秒,延迟立刻降下来了。
第二个现象是:显存占用率不高,但GPU算力利用率很低。这说明模型推理没有把算力吃满。排查后确认是单次推理的batch太小。调大max_batch_size后,吞吐量提升明显。这里有一个经验:小模型低并发场景下,latency优先;大模型高并发场景下,throughput优先。两者不可兼得,架构上要分清主次。
4. 关键参数计算与容量规划
AI系统架构不能只停留在“能跑”,还要能回答“需要几台服务器”这个问题。下面分享我常用的估算方法,不算严谨的学术推导,但对工程规划够用。
4.1 并发与QPS的推导
容量规划要从业务预估开始。比如某个知识库系统,预估每天有6000次问答请求,集中在8小时工作时间内,那每秒平均请求量大约只有6000除以28800秒,约0.2 QPS。看起来很低对不对?但真实业务从来不是均匀分布的,高峰期10分钟内可能占到全天40%的请求量,那我们取10分钟6000乘以0.4等于2400次,除以600秒,峰值就是4 QPS。
4 QPS看起来不高,但AI系统问题不是QPS而是“并发下的排队延迟”。如果每个请求的端到端耗时是4秒,那么4 QPS意味着系统需要同时处理16个请求。假设模型服务单实例并发能力只有4路,那就至少需要4个模型服务实例。这就是最基本的容量推导逻辑。
4.2 显存与模型副本的估算
模型推理占用的显存主要由三部分组成:模型权重、运行时状态、KV Cache。以常见7B参数规模为例,半精度加载权重大约需要14GB显存,如果再算上推理时的中间激活和上下文缓存,单实例推荐至少预留20-24GB显存。如果是13B模型,半精度权重约26GB,单实例就需要约32-40GB显存。用一张常见24GB显存的GPU来看,跑7B模型可以勉强吃下,但并发一高就发愁,所以实际生产里经常要用多卡或多机。
怎么估算副本数?我一般用这个流程:先压测单实例的极限并发,比如测出来并发8路时延迟仍可接受,那单实例给到的业务并发就是8;然后用系统峰值并发除以8,向上取整得到最小副本数。这里要额外留20%-30%的缓冲区,防止突发流量和单机故障、版本发布时的滚动重启。
4.3 向量检索的召回与延迟平衡
向量检索的参数主要是top_k和距离阈值。top_k越大,召回越全,但可能把不相关内容也带进来,同时增加重排和模型上下文的负担。我们的实践是以top_k=10起步,经重排后只保留Top 3。距离阈值则根据不同向量模型的分数分布来定,需要拿一批真实问题做评测,画出分数分布图后找一个能让“召回率”和“精确率”平衡的切分点。
延迟方面,向量库单次查询耗时一般能做到几十毫秒到一两百毫秒,如果超过这个范围,要先检查是不是真实查询的数据量太大或者索引类型不合适。这里插一句经验:向量检索的索引类型直接决定延迟和召回率,训练向量模型的人通常不会关心这个,但系统架构师必须懂。
4.4 成本估算:不只看服务器价格
用多少服务器、多少GPU,只是最直接的成本。AI系统还有一笔隐性成本:Token费用。如果模型服务是自建的,Token费用可以通过“每Token算力成本”来估算,计算公式可以简化为:小时成本除以每小时处理的Token总数。如果调用的是外部模型API,那就更直接了,每天的Token消耗乘以单价就是成本。
运营过程中我发现很多团队对Token成本没有概念,一个简单的问题是:你系统平均每个回答消耗多少Token?如果这个数字比模型输出的内容长度大了好几倍,说明Prompt模板里塞了太多不必要的上下文。把Prompt里的固定引导词精简、限制历史对话轮数、压缩检索出来的文档片段,可以显著降低Token成本,而且回答速度也会更快。
5. 常见问题与故障排查实录
最后分享一些真实运行时最常遇到的问题,每条都来自我自己的“血泪史”。
5.1 GPU利用率很高,但接口响应还是很慢
这是最典型的假象。首先确认慢在哪个环节,用链路追踪看一眼,如果是模型服务耗时长,再检查单次推理的输入长度是不是超长。输入越长,模型生成第一个Token的前置计算就越慢。解决方案是控制每次喂给模型的上下文长度,不要把整个知识库文档都塞进Prompt。另外,如果模型配置了“生成前需要思考”之类的内部推理步骤,那即使输出很短,延迟也会很高,这时候要评估这个特性对业务是否有必要。
5.2 首次请求特别慢,后续请求正常
基本可以断定是模型冷加载导致。服务刚启动或者扩缩容后,模型权重要从磁盘加载到显存,这个过程可能要几十秒,如果系统没有做预热,第一个请求就会撞在加载上。解决办法是在服务注册到负载均衡之前,先发一个健康检查请求把模型激活,或者在启动脚本里显式跑一次推理完成预热。
5.3 向量召回的结果明显不符合问题意图
先别急着换向量库,按这几个方向查:第一,查询文本有没有做和入库时一致的预处理,比如入库时做了“去掉换行符”“统一小写”,查询时也要做;第二,切片有没有保留足够的上下文语义,一个3000字的文档强行切成几十个字的小碎片,语义早散了;第三,查询时有没有做意图改写,用户口语化提问跟文档书面语差别很大,考虑加一层“把问题改写为更规范的表达”的环节。
5.4 模型服务偶发超时,重试后雪崩
这是AI系统最常见的故障模式。上游发现超时后自动重试,重试请求又占满了模型服务的排队队列,导致更严重的超时,形成雪崩。我的建议是:上游不要盲目重试,第一次超时就先降级或直接返回;如果一定要重试,重试请求也必须走独立的低优先级队列。另外,在网关层针对模型相关的接口做熔断,连续失败达到阈值(比如10秒内失败5次)就快速失败,不再向后转发。
5.5 长跑后系统变慢,重启恢复
典型原因是上下文缓存与碎片积累。很多推理服务会缓存多轮对话的状态,时间长了内存和显存碎片会越来越多,导致性能下降。可以在监控里加一项“累计处理Token数”,当超过阈值时自动触发实例重启或流量摘除。
为了方便快速定位问题,我把上面的排查路径整理成一张速查表:
| 现象 | 优先排查方向 | 常用手段 |
|---|---|---|
| GPU占用高但响应慢 | 上下文过长 / 批处理排队 | 减短Prompt、调批处理等待窗口 |
| 首次请求慢 | 模型冷加载 | 启动预热请求 |
| 召回结果不准 | 预处理不一致 / 切片不合理 | 统一文本处理流程、调整切片策略 |
| 超时后雪崩 | 盲目重试 / 缺乏熔断 | 设置熔断、重试走独立队列 |
| 长时间运行变慢 | 显存碎片 / 缓存膨胀 | 按累计Token数触发自动重启 |
| 多副本流量不均 | 负载均衡策略 / 实例规格差异 | 改成按最小并发数分发 |
5.6 一个容易忽视的根因:版本不一致
还有一个坑想单独提一下:线上跑着的向量化模型、文本切片逻辑、Prompt模板,是不是和离线评测时用的完全一致?我遇到过不止一次,评测效果很好,一上线就崩,最后发现是线上代码里把文本预处理函数改了一行,导致向量结果全变了。解决这个问题要靠基础设施层做版本管理,切片代码、模型权重、Prompt模板都要打版本号,并且跟随请求日志一起记录下来。
6. 个人经验与扩展建议
写了这么多,最后分享一个我个人在架构设计中的小习惯:先画一次请求的“调用链时序”,再画系统拓扑图。调用链时序能帮你看到数据如何流动、在哪里等待、哪里可能超时失败,拓扑图反而容易让人只关注组件之间怎么连,忽略了延迟和失败传播。我每次做架构评审,都会让团队对着调用链逐个环节问三个问题:这一环能降级吗?这一环超时了会怎样?这一环的成本占比是多少?能把这三个问题回答清楚,架构基本就稳了。
如果你准备从零开始搭一套AI系统架构,我的建议是不要一开始就用几十个微服务,而是先用一个包含接入、编排、模型、数据四层的单机版本,把整个链路跑通、监控做好,再根据瓶颈逐步拆分。模型推理层往往是最先需要拆出来的,其次是向量检索,最后才是编排层的子服务化。架构演进必须跟着真实流量走,提前设计得再花哨,不如上线后用数据说话。这套思路我在好几个项目里验证过,踩过的坑都在前面写出来了,照着做至少能少走一大半弯路。