AI系统架构分层指南:Workflow与Inference的边界与协作

发布时间:2026/9/9 22:26:51
AI系统架构分层指南:Workflow与Inference的边界与协作 我这两年跟不少团队聊过AI系统架构发现一个很有意思的现象刚把模型训练跑通的人几乎都会觉得分层是多余的。逻辑很简单——一个脚本里把数据处理、模型调用、结果拼装全写完调通就能上线为什么要拆成好几个模块、好几层服务但等到模型真的上了生产环境流量一上来需求一多问题就接踵而至一个节点挂了整条链路瘫痪、想替换其中的某个模型结果牵一发动全身、出了故障根本不知道是数据问题还是推理问题。这时候再回头看为什么必须分层答案已经不是理论问题而是血泪教训了。这个标题里的 Workflow 和 Inference恰好是AI系统里最容易被混为一谈、却必须分开对待的两层。Workflow 管的是流程和状态Inference 管的是模型计算本身。很多系统设计的乱象都是从把这两件事揉在一起开始的。这篇文章我想从原理、实践、坑位三个角度把分层的逻辑和这两层的边界讲清楚给正在搭AI系统、尤其是准备把原型改造成生产级系统的同学一个可以直接参考的思路。1. 为什么AI系统必须分层从耦合到失控的必然1.1 不分层会怎样一个真实到不能再真实的例子先说一个我见过很多次的场景。某个团队做内容审核系统最初的MVP版脚本长这样def audit_text(text): # 1. 文本清洗 clean_text remove_noise(text) # 2. 调用模型推理 result model.predict(clean_text) # 3. 后处理拼接 if result[label] reject: return {action: block, reason: result[detail]} return {action: pass, reason: None}单看这段代码简洁、清晰、能跑。问题出在一个月后产品说要加一个人工抽检环节又过了两周另一个模型上线要求先过A模型再过B模型再后来要支持批量异步处理老板要求所有决策都要留痕可追溯。这个函数开始膨胀从几十行变成几百行从单进程变成多线程从直接调用变成加缓存加队列加回调。每一步改动都像在雷区里走——因为你根本不知道加一个环节应该放在哪一层所有逻辑挤在一起互相依赖改了数据处理就影响模型调用改了模型调用就影响返回结构。这就是不分层的本质问题责任边界模糊。当数据处理、业务规则、模型推理、结果输出全部出现在同一个模块里系统的可维护性和可扩展性会随着需求增长呈指数级恶化而不是线性恶化。1.2 分层的本质把做什么和怎么做分开分层不是一种格式化要求而是一种控制复杂度的策略。每一层只回答一个问题接入层请求从哪来、参数怎么解析、鉴权怎么做。Workflow层这个任务要经过哪些步骤、步骤之间怎么流转、失败怎么处理。Inference层模型怎么加载、推理怎么执行、性能怎么优化。这样拆开之后每一层的修改都是局部的。想换模型只动Inference层。想调整处理流程只动Workflow层。想改API参数格式只动接入层。改动的波及范围被限制住了测试范围也自然缩小。有人会反问小系统也值得这样拆吗我的判断是如果这个系统活不过三个月怎么简单怎么来如果它要活过一年以上分层是必须的。因为一年内需求一定会变变的频率远超你的想象。做一个方便修改的架构比做一个当下最优的架构更重要而分层恰恰是方便修改的基础。注意分层不是越细越好。我看到有人把AI系统拆成十几个微服务每个服务就干一件极小的事结果光是服务间通信和部署就耗掉了大半精力。分层的核心是按变化频率和职责边界划分而不是按功能切片。1.3 两个关键概念的定位Workflow 和 Inference 各管什么在AI系统的分层语境里Workflow 和 Inference 是两个容易混淆的层级。Workflow 解决的是流程编排问题一个任务从进入到完成需要经历哪些步骤步骤是串行还是并行哪些步骤需要人工确认哪些步骤可以自动跳过失败之后是重试还是降级这些问题的答案构成了一个业务逻辑的拓扑结构。Workflow 不关心单个模型的计算细节它只关心流程什么时候走完、走到哪一步了、状态是什么。Inference 解决的是模型计算问题给一段输入怎么通过模型得到输出。它关注的是模型加载方式、显存管理、批量推理效率、推理延迟、并发处理能力等。Inference 层可以是一个独立的推理服务也可以是多个模型服务集群。两者的关系是Workflow 编排任务流Inference 执行任务流中的计算节点。把这两个概念分开是AI系统架构的第一步。如果硬把它们混在一个模块里就会出现我前面提到的那个脚本膨胀的局面——业务逻辑和计算逻辑互相纠缠谁也无法独立演进。2. Workflow层AI系统的流程编排中枢2.1 Workflow到底在管哪些事Workflow层在AI系统里承担的角色可以类比成一个项目里的项目经理。它不亲自写代码但他知道整个项目要分几个阶段、每个阶段的负责人是谁、什么时候该做什么、出了问题找谁。落到具体技术上Workflow层要管的几件事任务建模把一个业务请求拆解成有向无环图DAG节点是具体操作步骤边是步骤之间的依赖关系。比如用户上传一张图片图可能是图片解码 → 基础质量检测 → 主模型识别 → 后处理规则校验 → 输出结果。每一个节点都是可独立执行的单元。状态管理任务执行到哪一步了每个中间态的结果是什么任务处于等待中、运行中、成功还是失败。状态必须持久化否则进程一重启所有任务状态就丢了这在生产环境是不可接受的。调度执行节点如何被触发。串行执行是最简单的模式但如果做得好节点之间可以并行。比如一个任务同时调用两个模型它们互不依赖Workflow层应该能并行调度这两个Inference请求而不是一个一个排队。失败处理与重试某个节点失败了怎么办是直接重试还是跳过还是把整个任务标记为失败并通知人工介入这是Workflow层最重要的职责之一也是跟Inference层最大的区别Inference层一般只管算成功或算失败而Workflow层要管失败之后的路怎么走。2.2 流程描述与代码实现用状态机思维而不是回调地狱很多人第一次写Workflow时容易掉进回调地狱。用简单的Python代码示意一下错误的做法def process(request): result1 step1(request) if result1.success: result2 step2(result1.data) if result2.success: result3 step3(result2.data) return result3 else: return fallback_2(result2) else: return fallback_1(result1)这段代码的问题在于业务流程被硬编码在函数调用栈里。每增加一个步骤就要增加一层嵌套每增加一个失败分支就要增加一个if。两周之后这段逻辑会复杂到没人敢改。更好的思路是用数据驱动的方式描述流程。把流程定义成一张表或一个字典workflow { steps: [ {name: image_decode, type: processor}, {name: quality_check, type: validator}, {name: main_inference, type: inference, model: detect_v3}, {name: rule_filter, type: rule, threshold: 0.5}, {name: output_format, type: serializer} ], edges: [ {from: image_decode, to: quality_check}, {from: quality_check, to: main_inference}, {from: main_inference, to: rule_filter}, {from: rule_filter, to: output_format} ], retry_policy: {max_attempts: 3, on_failure: skip} }然后由一个通用的Workflow引擎去解释执行这张表。这样做的好处是流程的变化不再需要修改代码只需要改配置。产品的同学甚至可以在可视化编辑器里拖拽流程而不用麻烦开发。这里我补充一个小建议如果团队规模不大、没有专门的基础设施团队不建议一开始就上专门的Workflow引擎比如Airflow、Temporal等先写一个轻量级的流程解析器用几十行代码维护一个有序步骤列表再逐步增加重试、超时、状态持久化能力。等复杂度真的上来了再迁移到成熟的框架也不迟。过早引入重框架学习成本和运维成本会淹没业务开发时间。2.3 轻量Workflow引擎的参考实现我实际用过的一个轻量方案是这样的class WorkflowEngine: def __init__(self, definition, context): self.steps definition[steps] self.context context self.history [] def run(self): for step in self.steps: executor get_executor(step[type]) try: result executor.execute(self.context, step) self.context[f{step[name]}_result] result self.history.append({step: step[name], status: ok}) except Exception as e: # 根据retry policy处理 if self._should_retry(step): self._retry(step) else: self.history.append({step: step[name], status: failed, error: str(e)}) # 决定是跳过还是中止整个流程 return self.context这个引擎很简单但已经具备了流程执行、结果记录、失败感知的能力。关键在于业务的增删只体现在definition里而引擎本身几乎不用改。2.4 Workflow层的扩展条件分支与并行当流程变复杂单线串行就不够了。常见场景是先做一次初筛初筛通过走A路径不通过走B路径。还有场景是需要同时调用两个独立的模型服务最后汇总结果。条件分支的实现可以在step定义里加一个condition字段{name: route, type: router, condition: lambda ctx: ctx[quality_check_result][score] 0.8, next: high_quality_path}并行执行的实现更复杂一点需要引入线程或异步任务。我的建议是除非性能瓶颈真的在串行环节否则第一版不要做并行。并行带来的状态同步和错误恢复复杂度在早期阶段完全是负担。先把串行流程跑稳再针对热点路径优化。3. Inference层决定AI系统性能的最后一公里3.1 推理不只是调用一下模型很多从训练转向推理的人有一个误区模型在训练时能跑推理时直接加载进来用不就行了事实远没那么简单。推理是AI系统里最贴近硬件、最敏感于延迟的一层。一个典型的Inference服务要做的事包括模型加载与生命周期管理模型文件从哪读、加载到GPU显存还是CPU内存、热更新时怎么平滑切换版本。请求预处理文本要tokenize图片要resize到特定尺寸数值要做归一化。这些预处理虽然看起来不重要但占了推理延迟的很大一部分。批处理优化单条请求推理效率往往很低把多条请求拼成一个batch并行算吞吐量能提升数倍。但batch要等到多大才触发等待会不会增加延迟这是经典的吞吐和延迟权衡。后处理模型输出的裸张量要转换为业务可读的结构。3.2 性能优化的核心维度延迟、吞吐、资源占用Inference层最核心的三个指标是延迟、吞吐和资源占用。它们之间互相制约实际取舍取决于业务场景。如果做的是实时风险拦截拦截请求必须毫秒级返回那就不能为了吞吐等太久的batch。如果做的是离线批量处理比如批量给历史数据打标签那延迟不敏感可以把batch开大把吞吐拉满。资源占用方面最重要的是显存管理。一个3B参数的模型FP16精度下大约占用6GB显存如果并发多个副本显存翻倍。我见过很多团队在推理层疯狂横向扩展——本质是因为他们没有优化推理路径只能靠堆机器硬扛。实际上常见的优化手段至少能把单机吞吐翻一番。几个性价比很高的推理优化手段优化手段原理适用场景收益动态批处理把并发请求攒成batch一起推理在线服务有并发但能容忍几十毫秒等待吞吐提升2-5倍量化INT8/FP16降低模型精度换取速度对精度损失不敏感的场景内存减半、速度提升2-3倍模型缓存高频输入直接走缓存重复查询多、结果可复用延迟降到微秒级自研算子融合把多个算子合并减少IO模型结构固定、需要极致性能提速30%-60%3.3 推理服务的部署形态独立服务还是嵌入进程Inference层应该独立部署还是跟业务代码合在一起我的答案是只要资源允许一定独立部署。独立推理服务例如用Triton、TorchServe或者简单的FastAPI加模型加载有几个明显优势独立扩缩容。业务流量大了扩Web服务推理流量大了扩推理服务互不干扰。独立版本管理。模型升级不需要重新发布整个应用。独立故障隔离。推理服务崩溃了Workflow层可以感知并做降级处理而不是整个系统一起挂。不过独立部署也带来一个新问题——网络开销。原本同一个进程里函数调用只要微秒级拆成两个服务后变成一次HTTP调用可能要几毫秒到几十毫秒。这就要在架构清晰和网络开销之间找一个平衡。我的经验是同机房内网调用延迟通常在1-5ms以内对大多数AI场景完全可接受。如果真有极低延迟要求比如实时推荐要求P99小于10ms可以用gRPC 长连接 连接池来压缩开销但不要因此否定独立部署的价值。3.4 Inference层的关键接口设计Inference服务对外暴露的接口应当尽量通用化不要绑定具体业务。一个比较推荐的接口设计service Inference { rpc Predict(PredictRequest) returns (PredictResponse); } message PredictRequest { string model_name 1; string model_version 2; bytes payload 3; // 原始输入数据 mapstring, string params 4; // 推理参数 } message PredictResponse { bool success 1; bytes result 2; int64 latency_ms 3; string error_message 4; }为什么payload用bytes而不是结构化字段因为不同模型的输入差异巨大——有的是文本有的是图片有的是多模态拼接。把接口定义成通用字节流Inference服务内部再做具体的反序列化这样上层Workflow调用时就不用关心每个模型的具体输入格式。这也是分层思想在接口层面的一个体现。4. Workflow vs Inference这不是二选一而是两件不同的事4.1 关注点差异业务正确性 vs 计算效率Workflow层的核心关注点是业务正确性流程是否按照预期走到终点、中间状态是否一致、失败路径是否符合预期。你不太需要关心某个具体模型是怎么算的你关心的是整个任务的状态机是否正确。Inference层的核心关注点是计算效率如何压GPU利用率、如何降低P99延迟、如何让显存不爆炸。你不太需要关心上层业务为什么发起这个请求、结果用在哪里你只需要把模型推理做好。这两者的思维方式是完全不同的。Workflow层通常是CPU密集、IO密集、业务逻辑密集Inference层是计算密集、显存敏感、模型生命周期敏感。放在同一个系统里、同一个进程里很容易互相拖累——业务日志刷屏把推理日志淹没业务频繁变动的代码导致推理服务频繁重启推理的GPU显存波动又影响业务进程稳定性。分开是自然而然的。4.2 失败语义差异可重试 vs 不可盲目重试这是一个很重要但常被忽略的差异。Workflow层处理失败时可以重试如果第2步失败了可以重新执行第2步甚至整个任务重新跑一遍。因为Workflow操作的往往是业务逻辑重试的代价是可控的。Inference层则不同。推理失败通常意味着模型输入非法、显存不足、模型文件损坏、推理服务过载。这些情况下盲目重试没有意义——重试还是会失败甚至加重过载。正确的做法是Inference服务对失败进行快速归类返回明确错误码是输入不合法还是服务过载还是模型不可用Workflow层再根据错误码决定是跳过、重试、还是走降级策略。我见过一个事故某团队在Inference层做了无脑重试推理服务已经过载了重试请求还在不断涌入最后服务彻底雪崩。加了熔断和退避之后才恢复。注意重试不是万能的尤其在推理服务这种对资源敏感的节点上。重试要有最大次数、退避策略和熔断机制。4.3 性能指标差异耗时构成不同Workflow层的耗时可大可小取决于流程中有多少步骤、步骤之间是否串行、是否有等待人工处理的环节。有些任务可能几秒完成有些要几分钟、几小时比如大量数据的批量处理。Workflow的性能指标是吞吐率和流程准确率。Inference层的耗时通常是毫秒到秒级单次推理的延迟是核心指标。它的性能指标是P50、P95、P99延迟以及吞吐量每秒请求数、GPU利用率。这两者在监控上也要分开。你不能用Workflow的流程耗时去衡量推理快不快也不能用推理的单次延迟去推断流程整体需要多久。分开监控、分开告警、分开优化是分层之后的自然要求。4.4 演进方式差异各自独立变化分层的最大收益其实是演进自由。Workflow层会随业务需求频繁变化——新增步骤、调整顺序、修改失败策略这些变化可能一周发生好几次。而Inference层相对稳定——模型版本可能一两个月迭代一版模型结构可能更久才变一次。如果这两层耦合在一起业务的频繁变化会逼着推理代码跟着改模型服务的稳定性就得不到保障。分开之后Workflow可以快速迭代Inference可以稳定运行彼此通过明确的接口协议通信互不阻塞。这就是我一开始说的方便修改的架构的价值所在。5. 一个真实的系统演进从单脚本到三层架构5.1 原始状态的能跑就行我帮一个团队做过类似的改造。他们的应用场景是智能客服工单分类用户提交工单系统自动判断类别并路由给对应的处理组。最初的实现是这样的单函数def classify_ticket(ticket_text): # 清理 cleaned clean(ticket_text) # 推理 label, score model_v1.predict(cleaned) # 业务规则 if 退款 in ticket_text: label after_sales if score 0.5: label manual_review return label这个函数跑原型没问题但生产环境的需求是工单量大了以后需要批量异步处理不能每次同步等待模型返回。新模型上线要灰度先让10%的流量走新模型看效果再全量。分类结果需要审计日志整个决策路径要可追溯。不同的工单类型要路由到不同的Workflow有的走自动分类有的先走模型再走人工复核。这些需求在原函数里硬塞代码结构会越来越臃肿。于是我们做了重构。5.2 重构后的分层架构重构后系统被清晰地分成了三层[API Gateway] → [Workflow Service] → [Inference Service] → [Model v1 / Model v2] (Python State Machine) (FastAPI Model Server)接入层API Gateway接收工单请求做鉴权、参数校验、限流然后把标准化后的任务丢进消息队列。Workflow层核心业务逻辑从队列取出任务构造任务的状态机。初始状态是pending依次经过text_cleaning→inference→rule_apply→route→完成。每一步的状态和结果都写入数据库。失败重试也在这层做——如果推理调用超时会重新发起调用最多重试3次仍失败则置为manual_review状态。# workflow 定义 ticket_flow { name: ticket_classify_flow, steps: [ {name: validate, type: validator, required: [ticket_id, text]}, {name: clean, type: processor, handler: text_cleaner}, {name: infer, type: inference_call, model: classify_v2, timeout_ms: 2000}, {name: apply_rules, type: rule_engine, rules: [refund_check, score_threshold]}, {name: route, type: router, targets: [auto_classify, manual_review, after_sales]} ] }Inference层模型服务加载实际分类模型提供/predict接口。接口内部做tokenize、推理、后处理。不同模型版本通过模型名称区分新版本上线先在服务里部署只让一部分流量通过Workflow层路由过来实现灰度。5.3 重构之后的效果这个改造花了两周时间。带来的直接变化新增模型只需要在Inference层加一个部署然后在Workflow层配一个节点不用改任何流程代码。模型灰度上线变成了配置项不需要走代码发版流程。出问题排查方便了——Workflow日志和Inference日志分开直接看是哪个环节挂了。人工复核只需要把任务状态改成manual_review其他层不用感知。这个案例说明一个道理不是系统大了才需要分层而是想让它变大而不崩塌就要先分层。分层是给系统留出的扩展空间。5.4 如果一开始就用成熟框架会不会更好这个团队的同事当时问过我们为什么不直接上Airflow或者Kubeflow我的回答是不是所有系统都需要那么重的框架。Airflow更适合周期性批处理任务调度而这里需要的是在线请求处理加简单的状态流转。用一个轻量的状态机库就够了。选型的逻辑应该是根据任务特征选工具。在线长任务、需要状态持久化和重试的可以用Temporal这类工作流引擎周期性的ETL和批处理用Airflow只是一个简单的在线请求处理自己写个几十行的状态机完全够用。工具是为场景服务的不要反过来让场景迁就工具。6. 常见问题与排查技巧实录6.1 分层引入的过度设计分层的反面是过度分层。我见过一个团队把一个简单的文本分类系统拆成了API层、应用层、调度层、模型代理层、模型服务层、缓存层、消息队列层一共七层。结果每处理一个请求要经过四五次网络跳转延迟从10ms变成了60ms部署一个功能要改四个仓库。分层的粒度把握有一个判断标准每一层是否有独立的演进需求。如果某一层永远不变没必要单独拆出去。如果某一层的变化频率和相邻层完全一致合并也许更好。分层是手段不是目的。分层带来代码结构清晰但代价是网络开销和管理复杂度。在性能和可维护性之间找一个自己能接受的平衡点这本身就体现架构水平。6.2 状态不一致问题Workflow层和Inference层各自维护状态Workflow层记录任务状态Inference层记录模型调用状态。两者都可能出问题最大的坑是状态不一致Workflow认为任务成功了但Inference的实际结果没有正确返回或者Inference执行成功了但Workflow因为网络超时错误地把它标记为失败。解决这个问题的思路是以Workflow层的持久化状态为最终依据Inference层的成功不是任务的最终成功写库成功才是。也就是说Inference返回结果后Workflow层要把结果持久化到自己的存储中再更新任务状态。如果持久化失败整个任务仍然按失败处理可以重新发起执行。这本质上是分布式系统里经典的两阶段提交思想的简化版虽然不完美但在大多数场景下可以有效避免状态错乱。6.3 性能瓶颈定位经验系统上线之后性能问题是最常见的。我常用的排查路径是先看Workflow层的耗时分布找到耗时最高的步骤再看这个步骤是调用了推理服务还是做了大量CPU计算如果是前者用链路追踪看Inference层的推理时间。一个实例某次排查发现工单分类任务P95延迟从200ms涨到了800ms外部看是变慢了很多内部看Workflow层的日志发现infer步骤耗时700ms。再往下查Inference服务的P95推理时间其实只有120ms但网络传输和排队花了500多ms——原因是推理服务只有一个副本请求排队严重。解决办法是扩容推理服务到三个副本并把Workflow层的并发调用做成连接复用P95立刻降回300ms以内。这个案例的教训是别只看一层的数据瓶颈可能在链路任何位置。分层之后每层的日志和监控都要有才能快速定位问题在哪一层。6.4 多版本模型的灰度与回滚模型上线最怕出问题。分层架构下灰度其实是很好做的Inference服务里同时部署v1和v2两个版本Workflow层在调用时读配置中心决定当前流量走v1还是v2。想灰度10%就在配置中心把v2的权重设为10%。如果v2效果不好回滚只需要把配置改回去不用重新发版不用重启服务。这种能力在单脚本架构里几乎不可能实现——你只能整体回滚到上一版代码那会影响所有其他逻辑。我的一个建议是从第一天起就为每个推理请求记录model_version字段作为审计日志的一部分。这样出了问题你可以回溯任何一个结果是由哪个版本的模型产生的这对原因定位和争议处理很重要。这个字段成本极低价值极高。6.5 重试风暴与熔断最后分享一个我踩过的坑。某次大促流量突然暴涨推理服务开始响应变慢有个别请求超时。Workflow层发现超时后自动重试结果重试的请求把推理服务打得更慢形成恶性循环。当时最要命的是重试逻辑和正常逻辑完全一样重试请求同样需要排队进一步加剧了过载。后来我们加了三个机制最大重试次数限制默认2次最多5次。指数退避第一次重试等200ms第二次等400ms第三次等800ms。熔断器如果推理服务的错误率在10秒内超过50%直接打开熔断不再发起新的推理调用而是让Workflow层进入降级路径比如先转人工。这套机制上线之后即使推理服务彻底不可用整个系统的入口也不会被打爆。分层架构给了我们一个很好的熔断位置在Workflow层熔断而不是在业务入口熔断。这也是分层的价值之一——你有了一个可以集中管控故障策略的位置。回到为什么必须分层这个话题。我的体会是分层不是某种教科书的教条而是在AI系统从小变大、从原型到生产的过程中最靠谱的一条路。它的核心价值不是让代码好看而是让每一次业务变化都能被限制在局部让每一次故障都能被快速定位让模型和业务可以各自独立演进。如果你正在搭一个AI系统我建议你先别急着写代码先用一张纸把Workflow流程和Inference服务边界画出来。画清楚的那一刻很多架构问题其实已经解决了一半。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询