从零搭建AI工程:推理服务、数据管线与提示词管理实战
1. 从零搭建AI工程能力为什么“会调包”远远不够很多人对AI工程的理解停留在“会调API、会跑通一个demo”的层面。拿一个预训练模型套上几行推理代码输出看起来像模像样就觉得AI工程不过如此。但真正进入生产环境之后问题会一个接一个冒出来模型推理延迟忽高忽低、批量请求下显存直接爆掉、输出结果不稳定、版本更新后旧接口全部失效、日志里全是看不懂的报错。这些问题的根源往往不是模型本身不够强而是工程能力没有跟上。ai-engineering-from-scratch这个标题核心讲的不是“从零训练一个大模型”而是从工程视角出发把AI能力真正落地到可运行、可维护、可扩展的系统里。它涉及的核心领域包括模型推理服务的搭建、数据处理管线的设计、向量检索与缓存策略、提示词工程的结构化管理、以及监控与迭代机制。关键词可以拆解为AI工程化、推理服务、数据管线、向量数据库、提示词管理、性能优化、从零搭建。这篇文章适合几类人看一是刚入门的AI应用开发者能跑通demo但不知道怎么上生产二是有后端经验但没接触过AI系统的工程师想了解AI工程和传统Web服务的差异三是技术负责人需要评估从零搭建一套AI工程体系需要哪些模块和投入。我会按照实际搭建的顺序把每个环节的关键决策、踩过的坑、以及可以直接复用的配置和代码都讲清楚。需要提前说明的是这篇文章不会涉及任何模型训练层面的数学推导也不会推荐具体的商业API。所有内容围绕“工程实现”展开重点在于系统设计、工具选型、性能调优和运维经验。读完之后你应该能独立规划出一套可运行的AI工程架构并且知道每个环节最容易出问题的地方在哪里。2. 推理服务层从单次调用到高并发稳定输出2.1 为什么不能直接用Flask写一个接口就上线我见过太多项目初期为了快速验证直接用Flask写一个/predict接口里面加载模型收到请求就推理返回结果。本地测试没问题一上线就崩。原因很简单Flask默认是同步阻塞的一个请求在推理时其他请求全部排队。如果模型推理需要2秒10个并发请求就要等20秒用户体验直接归零。更严重的是每次请求都重新加载模型或者重复初始化计算图显存和内存的消耗会成倍增长。我实测过一个7B参数的模型如果每次请求都重新加载单次显存占用会多出2GB左右并发稍微高一点就直接OOM。所以推理服务层的第一个核心原则就是模型只加载一次常驻内存所有请求共享同一份权重。正确的做法是使用专门的推理服务框架。目前主流的选择有几类一是基于Python的异步框架比如FastAPI配合Uvicorn利用异步IO处理并发请求模型在启动时加载一次二是使用专门的推理服务器比如Triton Inference Server或者TorchServe它们内置了动态批处理、模型版本管理、多实例并发等能力三是使用ONNX Runtime或者TensorRT做推理加速把模型转换成中间表示提升单次推理速度。对于大多数从零搭建的团队我建议从FastAPI ONNX Runtime开始。FastAPI负责HTTP接口和异步调度ONNX Runtime负责高效推理。这个组合的优点是轻量、可控、调试方便而且ONNX格式跨平台后续迁移到其他推理引擎也容易。2.2 动态批处理把零散请求攒在一起算单次推理的GPU利用率其实很低。假设你有一个文本分类模型单条推理耗时50毫秒其中大部分时间花在数据搬运和kernel启动上真正计算的时间可能只有10毫秒。如果每次只处理一条请求GPU大部分时间在空转。动态批处理Dynamic Batching的思路是在很短的时间窗口内比如10毫秒把多个请求攒成一个批次一次性送入GPU计算。这样单次推理的耗时可能只增加到80毫秒但一次处理了8条请求平均每条只要10毫秒吞吐量提升非常明显。实现动态批处理有两种方式。一种是使用Triton Inference Server它内置了动态批处理功能只需要在配置文件中设置max_batch_size和batch_timeout_microseconds即可。另一种是自己用Python实现一个简单的批处理调度器核心逻辑是维护一个请求队列当队列长度达到阈值或者等待时间超过阈值时触发一次批量推理。我实测过Triton的动态批处理在并发量50左右的时候吞吐量比单条推理提升了6到8倍。但要注意批处理会增加单次请求的延迟因为要等待攒批。所以需要根据业务场景权衡如果是离线任务可以设置较大的批处理窗口如果是在线交互窗口要小一些比如5到10毫秒。2.3 显存管理与模型量化让大模型跑在小显卡上显存是推理服务最稀缺的资源。一个7B参数的模型FP16精度下大约需要14GB显存加上KV Cache和中间激活值实际占用可能超过20GB。如果只有一张24GB的显卡基本只能跑一个实例并发能力很有限。模型量化是解决显存问题的关键手段。常见的量化方案有INT8量化把FP16的权重压缩到8位整数显存占用减半推理速度提升30%到50%精度损失通常在1%以内INT4量化显存占用降到四分之一但精度损失会明显一些适合对精度要求不高的场景。量化的工具有很多比如ONNX Runtime的量化工具、TensorRT的PTQ训练后量化、以及Hugging Face的bitsandbytes库。我一般推荐先用ONNX Runtime的INT8量化因为流程简单只需要准备一批校准数据跑一遍量化脚本就能得到量化后的模型。校准数据的质量很关键最好从真实业务数据中采样几百条覆盖各种输入长度和类型。除了量化还可以使用PagedAttention和Continuous Batching技术来优化KV Cache的管理。vLLM这个推理框架就是专门为LLM推理设计的它通过PagedAttention把KV Cache分页管理显存利用率比传统方案高很多。我实测过同样的模型和硬件vLLM的吞吐量比Hugging Face的默认推理 pipeline 高出3到5倍。2.4 健康检查与优雅降级推理服务上线之后必须要有健康检查机制。最基本的健康检查是/health接口返回服务是否存活。但仅仅这样不够还需要检查模型是否正常加载、显存是否充足、推理队列是否积压。我建议在健康检查里加入几个关键指标当前队列长度、最近一分钟的平均推理延迟、GPU显存使用率、以及最近一次推理是否成功。如果队列长度超过阈值或者显存使用率超过90%就返回不健康状态让负载均衡器把流量切走。优雅降级也很重要。当请求量超过服务能力时不能直接拒绝所有请求而是要有策略地降级。比如对于非核心请求可以返回缓存结果或者简化结果对于核心请求可以降低批处理窗口优先保证延迟如果实在扛不住可以返回一个友好的错误提示而不是让请求超时。3. 数据管线从原始文本到模型可用的输入3.1 数据清洗不是可选项而是必选项很多人拿到数据之后直接丢给模型结果发现输出质量很差然后怪模型不行。实际上大部分问题出在数据上。原始数据里可能有HTML标签、特殊字符、乱码、重复内容、超长文本、以及格式不一致的问题。如果不做清洗模型要么报错要么输出乱七八糟的结果。数据清洗的第一步是统一编码。所有文本统一转成UTF-8去掉BOM头替换掉不可见字符。第二步是去除噪声比如HTML标签、URL、邮箱、电话号码等可以用正则表达式批量处理。第三步是处理超长文本根据模型的最大输入长度进行截断或者分段。截断的策略有几种从头截断、从尾截断、或者取中间部分。我一般推荐按句子边界截断避免把一句话切成两半。第四步是去重。重复数据会导致模型在训练或微调时过拟合在推理时也会浪费计算资源。去重可以用精确匹配也可以用相似度匹配。精确匹配用哈希就行相似度匹配可以用MinHash或者SimHash。我实测过在一个百万级的数据集里精确去重能去掉15%到20%的重复内容相似去重还能再去掉5%左右。3.2 分块策略文本切分的艺术对于检索增强生成RAG类的应用文本分块是数据管线的核心环节。分块的大小和方式直接影响检索效果。分块太大检索到的内容包含太多无关信息模型容易被干扰分块太小可能丢失上下文导致检索不到关键信息。常见的分块策略有固定长度分块比如每512个token一块简单但容易切断句子按段落分块保留自然段落结构但段落长度不均匀按语义分块用句子嵌入模型计算相邻句子的相似度在相似度下降的地方切分效果最好但计算成本高。我一般推荐混合策略先按段落分块如果段落超过最大长度再按句子边界切分。分块之间保留一定的重叠overlap比如重叠50到100个token避免关键信息刚好落在边界上被切断。重叠比例一般设置在10%到20%之间。分块之后每个块需要生成一个嵌入向量embedding存入向量数据库。嵌入模型的选择很关键不同的模型在不同语言和领域上的表现差异很大。对于中文场景我建议用专门针对中文优化的嵌入模型比如BGE或者M3E系列。嵌入维度一般在768到1024之间维度越高表达能力越强但存储和检索成本也越高。3.3 向量数据库选型不要一上来就上集群向量数据库是RAG系统的核心组件。市面上选择很多FAISS、Chroma、Milvus、Qdrant、Weaviate、Pinecone等。我的建议是从零搭建时先用FAISS或者Chroma不要一上来就上分布式集群。FAISS是Facebook开源的向量检索库轻量、速度快、支持GPU加速适合单机场景。Chroma更上层一些提供了简单的API和持久化能力适合快速原型开发。当数据量超过百万级或者需要多租户、高可用时再考虑Milvus或者Qdrant。选型时要关注几个指标检索延迟、召回率、索引构建时间、内存占用、以及是否支持增量更新。我实测过FAISS的IVF索引在百万级向量上单次检索延迟可以控制在10毫秒以内召回率在95%以上。但如果数据量到千万级就需要用HNSW或者DiskANN等更高级的索引结构。还有一个容易忽略的点向量数据库的更新策略。如果数据频繁更新每次都要重建索引成本很高。所以选型时要确认是否支持增量插入和删除。FAISS本身不支持增量删除需要定期重建索引Milvus和Qdrant支持实时增删改更适合动态数据场景。3.4 缓存层避免重复计算在AI工程里很多计算是重复的。比如同一个用户反复问相同的问题或者多个用户问相似的问题。如果每次都重新推理浪费资源也增加延迟。所以缓存层是必不可少的。缓存可以分几级第一级是精确缓存用请求的哈希值作为key直接返回之前的结果。第二级是语义缓存用请求的嵌入向量在缓存库里做相似度检索如果相似度超过阈值就返回缓存结果。第三级是嵌入缓存把文本到嵌入向量的映射缓存起来避免重复调用嵌入模型。精确缓存用Redis就行设置合理的过期时间。语义缓存可以用向量数据库实现把历史请求的嵌入向量存进去新请求来了先检索。我实测过在一个客服问答场景里语义缓存能命中30%到40%的请求平均延迟从800毫秒降到200毫秒以下。4. 提示词工程把“咒语”变成可维护的代码4.1 提示词不是写在代码里的字符串很多项目的提示词是硬编码在Python文件里的改一个词就要重新部署。更糟糕的是同一个提示词在多个地方复制粘贴改了一处忘了另一处导致行为不一致。提示词应该是可配置、可版本管理、可测试的。我的做法是把提示词抽成独立的模板文件用YAML或者JSON格式管理。每个模板有唯一的ID、版本号、以及变量占位符。代码里通过ID引用模板运行时填充变量。这样修改提示词不需要改代码只需要更新模板文件重启服务或者热加载即可。更进一步可以给提示词加上元数据适用场景、预期输入输出示例、以及评估指标。这样当提示词效果下降时可以快速定位是哪个模板出了问题。4.2 结构化输出让模型返回可解析的结果让模型返回自由文本然后写正则表达式去解析是非常脆弱的做法。模型稍微换个说法解析就失败了。更好的方式是让模型返回结构化数据比如JSON。实现结构化输出有几种方式。一是直接在提示词里要求模型返回JSON格式并给出示例。二是使用函数调用Function Calling或者工具调用Tool Use能力让模型按照预定义的schema返回。三是使用约束解码Constrained Decoding在推理时限制输出只能符合特定语法。我一般推荐第二种方式因为大多数现代模型都支持函数调用而且返回结果稳定。如果模型不支持可以用第一种方式但要在提示词里强调“只返回JSON不要有其他内容”并且在解析时做好异常处理。4.3 提示词版本管理与A/B测试提示词修改之后效果是变好还是变坏不能靠感觉判断。需要有一套评估机制。最基本的做法是准备一个测试集包含输入和期望输出每次修改提示词后跑一遍测试集计算准确率、召回率、或者人工评分。更进一步可以做A/B测试。把流量分成两组一组用旧提示词一组用新提示词对比关键指标用户满意度、任务完成率、平均对话轮数等。我建议在提示词模板里加入版本号日志里记录每次请求使用的版本这样后续分析时能追溯到具体版本。版本管理可以用Git把提示词文件纳入代码仓库。每次修改提交一个commit写清楚修改原因和预期效果。回滚的时候直接checkout到之前的版本即可。4.4 少样本示例的选择与维护少样本提示Few-shot Prompting是提升模型效果的有效手段但示例的选择很讲究。示例太少模型学不到模式示例太多占用上下文窗口增加成本。而且示例的质量直接影响输出质量如果示例里有错误或者不一致模型会跟着学坏。我一般建议放3到5个示例覆盖不同的输入类型和边界情况。示例要精心挑选最好从真实数据里找确保分布和实际请求一致。示例的顺序也有影响把最典型的示例放在前面模型更容易学到核心模式。维护示例库时要定期审查。如果发现某个示例经常导致模型输出错误就把它替换掉。可以给每个示例打标签记录它的来源、适用场景、以及效果评分。5. 监控与迭代上线只是开始5.1 必须监控的四个核心指标AI服务上线之后如果没有监控就等于在黑暗中开车。我建议至少监控四个指标延迟、吞吐量、错误率、以及输出质量。延迟分几个维度P50、P95、P99。平均延迟好看不代表体验好如果P99延迟很高说明有少量请求特别慢可能是长文本或者复杂请求导致的。吞吐量用QPS或者TPS衡量反映系统的处理能力。错误率包括HTTP错误、推理超时、以及解析失败。输出质量比较难量化可以用人工抽检或者自动评估指标比如BLEU、ROUGE、或者基于嵌入的相似度。监控工具可以用Prometheus Grafana这是最成熟的组合。在代码里埋点暴露metrics接口Prometheus定期抓取Grafana展示。告警规则可以设置P99延迟超过阈值、错误率超过1%、或者队列长度持续增长。5.2 日志设计为排查问题留足线索日志是排查问题的第一手资料。AI服务的日志要记录请求ID、时间戳、输入摘要、输出摘要、使用的模型版本、提示词版本、推理耗时、以及任何异常信息。输入输出摘要不要记录完整内容避免日志过大和隐私问题。可以记录前100个字符或者哈希值。请求ID要贯穿整个调用链从入口到推理到缓存到数据库方便追踪。日志级别要合理使用DEBUG用于开发调试INFO用于正常请求WARN用于可恢复的异常ERROR用于需要人工介入的问题。生产环境一般开INFO级别排查问题时临时调到DEBUG。5.3 反馈闭环让用户帮你发现问题用户反馈是最直接的质量信号。可以在产品里加入反馈按钮让用户对输出结果点赞或点踩。点踩的时候可以选原因不准确、不相关、有害、或者格式错误。这些反馈数据收集起来定期分析找出高频问题。除了显式反馈还可以收集隐式反馈。比如用户是否复制了输出内容、是否继续追问、是否在短时间内重新提问。这些行为都能反映用户对输出的满意度。反馈数据要闭环到迭代流程里。每周或每两周做一次复盘看哪些问题最多然后针对性地优化提示词、补充数据、或者调整模型参数。5.4 模型更新与回滚策略模型更新是双刃剑。新模型可能效果更好但也可能引入新的问题。所以更新必须有策略先小流量灰度观察关键指标确认没问题再全量。灰度发布的流程是部署新模型实例把1%的流量切过去对比新旧模型在延迟、错误率、以及输出质量上的差异。如果新模型表现更好逐步增加流量比例直到全量。如果发现问题立即切回旧模型。回滚要快。所以旧模型实例不要马上销毁保留至少一个版本随时可以切回来。模型文件、配置文件、提示词模板都要版本化确保回滚时环境一致。6. 从零搭建的实操路线与避坑清单6.1 第一周跑通最小可用系统第一周的目标不是完美而是跑通。具体步骤第一步选一个轻量模型比如ONNX格式的小型文本分类或嵌入模型。第二步用FastAPI写一个推理接口模型在启动时加载。第三步用Docker把服务打包写一个Dockerfile和docker-compose.yml。第四步本地测试用curl或者Postman发请求确认能正常返回。这一步最容易踩的坑是环境依赖。Python版本、CUDA版本、ONNX Runtime版本、以及模型文件格式任何一个不匹配都会报错。我的经验是用Docker固定环境基础镜像选nvidia/cuda的官方镜像Python依赖用requirements.txt锁定版本。模型文件放在镜像里或者挂载卷确保路径正确。6.2 第二周加入数据管线和缓存第二周的目标是让系统能处理真实数据。第一步写数据清洗脚本处理编码、噪声、超长文本。第二步实现文本分块和嵌入生成把结果存入向量数据库。第三步加入Redis缓存缓存精确匹配的请求结果。第四步写一个简单的检索接口输入问题返回相关文档块。这一步的坑在于嵌入模型的选择和分块参数。嵌入模型要选和业务语言匹配的中文场景不要用纯英文模型。分块大小要实测先用512 token看看检索效果再调整。缓存过期时间不要设太长避免返回过时结果。6.3 第三周提示词管理和结构化输出第三周的目标是让输出可控。第一步把提示词抽成YAML模板代码里通过ID引用。第二步实现结构化输出用函数调用或者JSON模式。第三步准备测试集写评估脚本每次修改提示词后跑一遍。第四步加入日志和基础监控。这一步的坑在于结构化输出的稳定性。有些模型在长文本或者复杂schema下会返回不合法的JSON。解决方案是在提示词里给出明确的schema示例加上“只返回JSON”的强调以及在代码里做重试和降级处理。6.4 第四周监控、告警和迭代机制第四周的目标是让系统可运维。第一步接入Prometheus暴露关键metrics。第二步配置Grafana面板和告警规则。第三步设计反馈收集机制加入点赞点踩按钮。第四步制定迭代流程每周复盘反馈数据优化提示词或数据。这一步的坑在于告警疲劳。如果告警规则设得太敏感每天收到几十条告警很快就没人看了。所以告警要分级P0是服务不可用立即处理P1是性能下降当天处理P2是偶发错误每周汇总处理。6.5 那些文档里不会写的经验第一个经验不要过早优化。很多团队一开始就追求高并发、低延迟、完美架构结果花了大量时间在基础设施上核心功能反而没做好。正确的顺序是先跑通再优化最后规模化。第二个经验模型不是越大约好。大模型效果好但成本高、延迟高、部署复杂。很多场景下一个小模型加上好的提示词和数据管线效果不比大模型差。我实测过一个1.3B的模型在特定领域经过提示词优化后效果接近7B模型但推理速度快了5倍。第三个经验数据质量比模型选择更重要。同样的模型用清洗过的数据比用原始数据效果提升非常明显。我做过对比实验在文本分类任务上数据清洗后准确率提升了8个百分点而换一个更大的模型只提升了3个百分点。第四个经验监控要覆盖全链路。不要只监控推理服务还要监控数据管线、向量数据库、缓存、以及外部依赖。任何一个环节出问题都会影响最终体验。我遇到过向量数据库连接池耗尽导致整个服务不可用的情况如果当时有监控可以提前发现并扩容。第五个经验文档和注释要同步更新。AI工程迭代快今天写的代码下周可能就改了。如果文档不同步新人接手或者自己回头排查问题时会浪费大量时间。我的做法是每次修改代码必须同步更新对应的文档和注释把修改原因和影响范围写清楚。7. 关于成本控制的一些实际做法AI工程的成本主要来自三块GPU算力、存储、以及网络带宽。GPU算力是大头尤其是推理服务。控制成本的核心思路是提高利用率。第一个做法是混部。把推理服务和离线任务部署在同一批GPU上离线任务在推理低峰期跑推理高峰期暂停。这样可以平滑GPU利用率避免高峰期不够用、低峰期闲置。第二个做法是自动扩缩容。根据队列长度或者QPS自动调整实例数量。低峰期缩到最少实例高峰期自动扩容。Kubernetes的HPA可以基于自定义metrics做扩缩容配合Prometheus的指标。第三个做法是选择合适的精度。FP16比FP32快一倍INT8比FP16快30%到50%。在精度损失可接受的前提下尽量用量化后的模型。第四个做法是缓存一切可以缓存的东西。嵌入向量、检索结果、推理输出能缓存就缓存。缓存命中率每提升10%GPU成本就能降低5%到8%。存储成本相对好控制向量数据库的存储可以用压缩和分层策略。热数据放内存或SSD冷数据放对象存储。网络带宽成本主要出现在跨区域调用尽量把服务和数据放在同一区域。8. 团队协作与工程规范从零搭建AI工程不是一个人的事。即使是小团队也需要明确分工和规范。我的建议是至少划分三个角色数据工程师负责数据管线和向量数据库算法工程师负责模型选型和提示词优化后端工程师负责推理服务和监控。协作的关键是接口定义。数据管线的输出格式、推理服务的输入输出schema、提示词模板的变量定义这些都要提前约定好写成文档。任何一方修改接口必须通知其他方并且更新文档。代码规范方面Python代码用black格式化类型注解用mypy检查单元测试用pytest。模型文件和配置文件用DVC或者Git LFS管理避免直接提交大文件到Git仓库。版本管理要严格。模型版本、提示词版本、数据版本、代码版本都要有明确的版本号和变更记录。每次上线新版本记录清楚改了什么、为什么改、预期效果是什么。这样出问题时能快速定位和回滚。最后分享一个我踩过的坑有一次更新嵌入模型忘了同步更新向量数据库里的向量导致检索结果完全错乱。原因是新旧模型的嵌入空间不兼容用新模型编码的查询向量去检索旧模型生成的文档向量相似度计算完全失效。从那以后我养成了一个习惯任何模型更新必须同步重建向量索引并且在更新前做好备份更新后做检索质量验证。这个教训花了我两天时间才排查出来希望你能避开。