ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零手搓AI工程:推理服务、上下文管理与成本控制实战

从零手搓AI工程:推理服务、上下文管理与成本控制实战 1. 从零手搓AI工程为什么我不建议你直接调包很多人一听到“AI工程”这四个字第一反应就是打开某个云平台调一个现成的大模型接口写几行胶水代码然后对外宣称自己做了个AI应用。我早期也这么干过结果在一次内部技术评审上被问得哑口无言——模型为什么在这个场景下响应变慢推理成本为什么比预估高了四倍上下文窗口塞满之后系统为什么直接崩了这些问题光靠调包是答不上来的。ai-engineering-from-scratch这个标题核心价值不在于“又教你做一个AI Demo”而在于把AI工程从黑盒拆成白盒。它适合三类人一是刚转行做AI应用、只会调API但不懂底层机制的开发者二是想系统补齐工程能力、从算法岗往全栈AI方向走的同学三是带团队做AI产品、需要判断技术方案可行性的技术负责人。这篇文章我会围绕“从零构建”这条主线把AI工程里最容易被忽略的环节——数据管道、推理服务、上下文管理、成本控制、可观测性——逐个拆开讲透并且给出可以直接抄作业的实操路径。先说一个反直觉的结论从零手搓一遍AI工程链路比直接调包更快让你具备独立交付能力。原因很简单调包时你遇到的所有问题都是“别人的问题”你只能等文档、等社区、等客服而从零构建时每一个报错都是你自己的问题你被迫理解token怎么切、显存怎么分、请求怎么排队。这种理解深度才是AI工程师真正的护城河。2. 拆解AI工程的最小可行骨架2.1 一个AI应用到底由哪几层组成很多人把AI工程等同于“模型推理”这是最大的认知偏差。一个能上线的AI应用至少包含五层接入层负责请求路由和鉴权编排层负责提示词组装和上下文管理推理层负责模型加载和批量计算数据层负责向量检索和缓存观测层负责日志、指标和成本追踪。这五层里模型推理只是其中一环而且往往不是最耗时的一环。我见过太多项目推理层写得漂漂亮亮结果接入层没有做请求限流一个爬虫脚本就能把整个服务打挂或者编排层没有做上下文截断用户聊到第二十轮时token直接爆掉。这些问题的根因都是把AI工程想得太窄了。2.2 从零构建时哪些轮子必须自己造不是所有东西都要手写。我的经验是与业务逻辑强相关的必须自己造与基础设施相关的可以复用。具体来说提示词模板管理、上下文裁剪策略、检索排序逻辑、成本核算规则这些必须自己实现因为它们直接决定产品体验而HTTP服务框架、向量数据库、日志采集组件这些可以用成熟方案没必要重复造轮子。判断标准很简单如果这个模块出问题你能不能在三分钟内定位到原因能就可以用现成的不能就自己写一遍把关键路径摸清楚。这个原则帮我省下了大量调试时间也让我在技术选型时更有底气。2.3 最小可行骨架的目录结构设计从零构建时目录结构决定了你后期维护的痛苦程度。我推荐按“关注点分离”来组织而不是按“文件类型”来组织。下面是我实际项目中反复打磨出来的结构ai-app/ ├── gateway/ # 接入层路由、鉴权、限流 ├── orchestrator/ # 编排层提示词、上下文、会话 ├── inference/ # 推理层模型加载、批处理、队列 ├── retrieval/ # 数据层向量化、检索、缓存 ├── observability/ # 观测层日志、指标、成本 ├── configs/ # 配置模型参数、阈值、开关 └── scripts/ # 运维脚本压测、回放、迁移这个结构的好处是每一层都可以独立测试和替换。比如你想把推理层从本地模型换成远程服务只需要改inference/目录其他层完全不受影响。这种可替换性在AI技术快速迭代的今天比任何性能优化都重要。3. 推理服务的从零搭建与性能调优3.1 模型加载显存分配的那些坑从零搭建推理服务第一个拦路虎就是显存分配。很多人以为模型文件多大就占多大显存实际上推理时的显存占用通常是模型权重的1.2到1.5倍因为还要算上激活值、KV Cache和临时缓冲区。以一个7B参数的模型为例FP16精度下权重约14GB但实际推理峰值可能到18GB以上。我踩过最深的坑是KV Cache没有预分配。早期我用动态分配结果长对话场景下显存碎片化严重跑了几十轮之后直接OOM。后来改成预分配固定大小的KV Cache池虽然浪费了一点显存但稳定性大幅提升。预分配大小的计算公式是batch_size × max_seq_len × num_layers × 2 × hidden_dim × dtype_size你可以根据实际业务的最大并发和最大对话长度来反推。注意预分配不是越大越好。分配过大反而会挤占激活值空间导致批处理能力下降。我的经验值是预留总显存的70%给权重和KV Cache剩下30%留给激活值和临时缓冲。3.2 批处理策略吞吐量和延迟的平衡术批处理是推理服务性能的核心杠杆。不做批处理GPU利用率可能只有20%做了批处理吞吐量能翻五到十倍。但批处理会引入排队延迟用户等太久体验就崩了。这里的关键是动态批处理窗口设置一个最大等待时间比如50毫秒窗口内到达的请求合并成一个批次窗口满了或者超时了就立即执行。具体实现上我推荐用“连续批处理”而不是“静态批处理”。静态批处理要等整个批次的所有请求都完成才能释放资源一个长请求会拖死整个批次连续批处理则允许完成的请求先返回新请求随时插入。这个差异在高并发场景下非常明显实测吞吐量能再提升30%以上。批处理策略吞吐量平均延迟适用场景无批处理低最低极低并发、调试静态批处理中中请求长度均匀连续批处理高中低生产环境首选动态窗口批处理最高可控高并发在线服务3.3 量化与蒸馏用精度换成本的边界在哪量化是降低推理成本最直接的手段。FP16转INT8显存占用直接减半推理速度还能提升20%到40%。但量化不是免费的午餐精度损失在有些任务上可以忽略在有些任务上却是灾难性的。我的经验是分类、抽取、摘要类任务对量化不敏感生成、推理、代码类任务对量化敏感。具体操作上我建议先做INT8量化用一批真实业务数据做A/B对比看关键指标下降是否超过3%。如果超过就退回FP16如果没超过就大胆上INT8。至于INT4目前只在极端成本敏感的场景下才考虑而且必须配合蒸馏或者LoRA微调来补偿精度。蒸馏则是另一条路用大模型生成高质量数据训练一个小模型来模仿。这条路成本更高但收益也更持久。我做过一个项目用70B模型蒸馏出一个7B模型在特定任务上达到了原模型92%的效果而推理成本只有原来的十分之一。这个投入产出比在业务量上来之后非常划算。4. 上下文工程被低估的AI应用核心4.1 上下文窗口不是越大越好很多人迷信“上下文窗口越大越好”恨不得把整个知识库都塞进去。实际测试下来上下文超过一定长度后模型对中间内容的注意力会显著下降这就是所谓的“迷失在中间”现象。我做过一组对比实验同样的问题把关键信息放在上下文开头、中间、结尾三个位置准确率分别是89%、62%、85%。中间位置的准确率断崖式下跌。所以上下文工程的核心不是“塞更多”而是“塞更准”。我的策略是关键信息放开头和结尾次要信息做摘要放中间无关信息直接丢弃。这个策略在多个项目上验证过准确率能稳定提升15个百分点以上。4.2 上下文裁剪的四种策略与选择依据上下文裁剪是每个AI应用都必须面对的问题。我总结下来有四种策略各有适用场景滑动窗口保留最近N轮对话简单粗暴适合闲聊类应用。缺点是会丢失早期关键信息。摘要压缩把早期对话压缩成摘要保留语义但丢失细节。适合客服、助手类应用。关键信息提取用规则或小模型提取实体、意图、约束条件只保留结构化信息。适合任务型对话。混合策略近期用滑动窗口中期用摘要远期用关键信息。适合长周期复杂任务。选择依据是业务对历史信息的依赖程度。如果用户经常引用十轮前说的话滑动窗口就不够用如果业务只关心当前意图关键信息提取就足够了。我通常会在项目初期用混合策略上线后根据日志分析逐步简化。4.3 提示词模板的版本管理与回滚机制提示词是AI应用的“源代码”但很多人把它硬编码在业务逻辑里改一次就要重新部署。这是大忌。我的做法是提示词模板独立管理带版本号和灰度开关。每次修改提示词先在小流量上验证指标达标再全量指标下降就一键回滚。具体实现上我用一个简单的YAML文件管理模板配合配置中心做热更新。模板里支持变量插值和条件分支比如template: | 你是一个{role}助手。 {#if context} 参考信息{context} {/if} 用户问题{question} 请用{style}的风格回答。 version: 2.3 rollout: 0.1这个机制帮我避免了好几次线上事故。有一次我改了一个提示词小流量测试时发现回答长度暴涨了三倍成本直接超标幸好有灰度机制及时回滚了。5. 数据管道与检索增强的工程细节5.1 文档切分粒度决定检索质量检索增强生成的效果七成取决于文档切分质量。切得太粗检索出来的内容包含大量无关信息浪费上下文窗口切得太细语义不完整模型看不懂。我的经验是按语义边界切分而不是按固定字数切分。具体操作上先用段落和标题做一级切分如果某段超过阈值比如500字再用句子边界做二级切分。切分时保留一定的重叠比如前后各50字避免关键信息被切断。这个重叠量不是拍脑袋定的而是根据业务文档的平均句长来调整句长越长重叠越多。提示切分后的每个片段都要带上元数据比如来源文档、章节标题、位置索引。这些元数据在检索时可以用于过滤和排序效果比纯向量相似度好得多。5.2 向量化模型的选择与本地部署向量化模型的选择很多人只看排行榜这是不够的。排行榜上的模型往往在通用语料上表现好但在你的垂直领域可能一塌糊涂。我的做法是用自己的业务数据做小规模评测准备100个查询和对应的正确文档测每个候选模型的召回率选召回率最高的那个。本地部署向量化模型时要注意批处理大小和序列长度的平衡。向量化模型通常比生成模型小得多但吞吐量要求更高。我一般设置批处理大小为32到64序列长度截断到模型最大长度的80%这样能在吞吐量和精度之间取得较好的平衡。5.3 检索结果的去重与重排序向量检索有个常见问题返回的结果高度相似前十条可能都在说同一件事。这会导致上下文窗口被浪费模型也得不到多角度信息。解决办法是先做去重再做重排序。去重可以用简单的余弦相似度阈值超过0.95的视为重复只保留一条。重排序则可以用一个小的交叉编码器模型对查询和候选文档做精细打分。这个步骤会增加几十毫秒延迟但检索质量提升非常明显。我实测下来加了重排序之后回答准确率能提升10到20个百分点。6. 成本控制与可观测性上线后的必修课6.1 Token成本核算每一分钱花在哪里AI应用的成本大头是token消耗。不做核算你根本不知道钱花在哪。我的做法是在编排层埋点记录每次请求的输入token、输出token、模型类型、耗时然后按天聚合生成成本报表。核算公式很简单成本 输入token数 × 输入单价 输出token数 × 输出单价。但关键在于区分不同业务场景的成本。比如问答场景输入长输出短生成场景输入短输出长两者的成本结构完全不同。分开核算之后你才能针对性地优化。我做过一个优化发现某个场景的输出token里有30%是重复的客套话。于是在提示词里加了“直接回答不要寒暄”的约束输出token直接降了25%成本同步下降。这种优化不做核算根本发现不了。6.2 缓存策略哪些请求可以复用缓存是降低成本和延迟的利器但不是所有请求都能缓存。我的分类是确定性请求可以缓存随机性请求不能缓存。比如“把这段文字翻译成英文”是确定性的同样的输入永远得到同样的输出可以缓存“给我写一首诗”是随机性的每次输出都不同缓存了反而奇怪。缓存实现上我用的是输入哈希作为key输出作为value设置合理的过期时间。过期时间根据业务变化频率来定知识库更新频繁的过期时间短一些静态知识的可以长一些。实测下来缓存命中率能到40%以上成本和延迟都大幅下降。6.3 日志、指标与告警的最小集合可观测性不是越多越好而是要抓住关键指标。我通常只监控五个核心指标请求量、错误率、P95延迟、token消耗量、缓存命中率。这五个指标能覆盖90%的线上问题。告警设置上我遵循“少而准”的原则。错误率超过5%告警P95延迟超过3秒告警token消耗量日环比超过50%告警。告警太多会导致麻木太少会漏掉问题。这个阈值是我踩了好几次坑之后调出来的比较适合中小规模应用。日志方面我建议记录完整的请求和响应但要做脱敏处理。这些日志在排查问题时非常有用比如用户反馈回答质量下降你可以回放当时的请求看看是检索出了问题还是模型出了问题。没有日志你只能靠猜。7. 从零构建的实操路线与避坑清单7.1 第一周跑通最小闭环第一周的目标不是做得多好而是跑通一个完整的闭环用户输入→编排→推理→输出。这个阶段不要追求性能不要追求精度只要流程能走通就行。我建议用一个最小的模型比如1B以下用最简单的提示词用内存做向量存储先把链路打通。这个阶段最容易犯的错是“过度设计”。有人一上来就搞微服务、搞分布式、搞Kubernetes结果一周过去了连个能跑的Demo都没有。我的建议是单进程、单文件、能跑就行。等闭环跑通了再逐步替换组件。7.2 第二到四周补齐工程能力闭环跑通之后开始补工程能力。优先级排序是先做错误处理再做性能优化最后做成本控制。错误处理包括超时重试、降级策略、输入校验性能优化包括批处理、缓存、异步成本控制包括token核算、模型选择、上下文裁剪。这个阶段我踩过最大的坑是忽略并发安全。早期我用全局变量存会话状态单用户测试没问题一上并发就串数据。后来改成每个请求独立上下文问题才解决。这个坑很隐蔽因为单用户测试根本发现不了。7.3 上线前的检查清单上线前我有一份固定的检查清单每次都要过一遍输入长度是否做了截断超长输入会不会导致OOM输出长度是否做了限制模型失控会不会产生天价账单是否有降级策略模型服务挂了应用能不能优雅降级是否有速率限制单个用户能不能刷爆服务日志是否脱敏有没有泄露用户隐私成本是否有上限日消耗超过阈值会不会自动熔断这份清单帮我避免了好几次线上事故。特别是输出长度限制有一次模型陷入循环输出了上万token幸好有上限不然那个月的账单会非常难看。7.4 那些只有踩过才知道的坑最后分享几个只有实际做过才会知道的坑。第一个是模型热加载更新模型时如果直接替换文件正在处理的请求会崩溃。正确做法是双缓冲新模型加载完成后再切换流量。第二个是向量维度不匹配换了向量化模型之后旧向量和新向量维度不同检索直接报错。必须做全量重新向量化。第三个是时区问题日志时间戳如果没统一时区排查跨天问题时会疯掉。这些坑文档里不会写社区里也很少提但每一个都能让你加班到凌晨。希望这份从零构建的路线能帮你少走一些弯路。AI工程这条路调包只能带你走前一百米真正决定你能走多远的是你对每一个环节的理解深度。
返回列表