news 2026/10/2 19:36:10

从零构建AI工程能力:推理服务、显存管理与性能优化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建AI工程能力:推理服务、显存管理与性能优化实战

1. 这个项目到底在解决什么问题

第一次看到 "ai-engineering-from-scratch" 这个标题,我脑子里蹦出来的第一个念头是:又是一个教人调包的教程?但仔细琢磨了一下 "from scratch" 这几个字,我意识到它想做的事情可能完全不一样。市面上讲 AI 工程的内容,绝大多数都是从pip install transformers开始,然后告诉你model.fit()就完事了。可真到了生产环境,你会发现真正难的地方根本不在于会不会调 API,而在于你能不能搞清楚一次推理请求从进入到返回,中间到底经历了什么。

这个项目的核心定位,我理解是从零开始构建 AI 工程能力,而不是从零开始训练一个大模型。这两件事有本质区别。前者关注的是:一个 AI 系统从数据准备、模型选型、推理服务搭建、性能优化到上线监控的完整链路,你需要掌握哪些底层能力。后者关注的是:如何用数学和代码实现一个 Transformer。对于绝大多数工程师来说,前者才是日常工作中真正需要的东西。

我之所以对这个方向特别有感触,是因为我自己就踩过这个坑。早些年做推荐系统的时候,我觉得自己模型调得还不错,AUC 也挺好看,结果一上线就崩了——延迟高得离谱,QPS 上不去,显存动不动就爆。后来才发现,问题根本不在模型本身,而在于我完全不懂推理引擎是怎么工作的,不知道 KV Cache 是怎么回事,不知道 batch size 和延迟之间的权衡曲线长什么样。这些东西,没有一个pip install能帮你解决。

所以这个项目适合谁呢?我认为有三类人特别值得关注:第一类是有一定编程基础但没怎么接触过 AI 系统部署的开发者,你想知道一个模型从 notebook 到线上服务中间要过几道坎;第二类是做过后端或数据工程,想转方向到 AI 基础设施的工程师,你有系统思维但缺 AI 领域的特定知识;第三类是在小团队里什么都得自己干的全栈工程师,老板说"把这个模型部署一下",你得知道从哪里下手。

这个项目要解决的核心问题是:填补"会调包"和"能扛住生产流量"之间的巨大鸿沟。它不会教你写 attention 的数学公式,但会告诉你为什么你的推理服务在并发上来之后会 OOM;它不会教你反向传播的推导,但会告诉你量化到 INT8 之后精度掉了两个点该怎么排查。

2. 从零构建 AI 工程能力的核心思路拆解

2.1 为什么"从零"不等于"重新造轮子"

很多人对 "from scratch" 有误解,觉得什么都得自己写。不是这样的。从零构建 AI 工程能力的核心思路是:你要理解每一层的原理,但不必自己实现每一层。这就像学操作系统,你不需要自己写一个 Linux 内核,但你需要知道进程调度、内存管理、文件系统大概是怎么回事,否则遇到性能问题你连从哪里开始查都不知道。

具体到 AI 工程这个领域,我认为需要从零理解的核心层次是这样的:最底层是硬件和驱动层,你得知道 GPU 的显存是怎么分配的,为什么有时候nvidia-smi显示显存没满但程序还是 OOM;往上是推理引擎层,你得知道 ONNX Runtime、TensorRT、vLLM 这些引擎各自适合什么场景,它们的调度策略有什么不同;再往上是服务层,你得知道怎么设计一个能扛住并发的推理 API,怎么做动态 batching,怎么做超时和降级;最上面才是应用层,也就是大多数人日常接触的那部分。

这个分层思路的好处是,它让你在学习的时候有一个清晰的路线图。你不会一上来就被各种框架和工具搞晕,而是知道每个工具在整体架构中处于什么位置,解决的是什么问题。

2.2 方案选型背后的取舍逻辑

在 AI 工程实践中,几乎每一个技术决策都是在多个维度之间做取舍。我拿几个最常见的场景来举例说明。

推理引擎的选择。你有三个主流选项:ONNX Runtime、TensorRT 和原生 PyTorch。ONNX Runtime 的优势是跨平台、部署简单、社区活跃,适合快速验证和中小规模部署;TensorRT 的优势是极致性能,在 NVIDIA 显卡上能比原生 PyTorch 快好几倍,但缺点是绑定硬件、调试困难、对模型结构有要求;原生 PyTorch 的优势是灵活、调试方便,适合研究和快速迭代,但性能最差。怎么选?我的经验是:如果你的 QPS 需求在 100 以下,ONNX Runtime 足够了;如果到了 1000 以上,而且用的是 NVIDIA 显卡,那就得上 TensorRT;如果还在实验阶段,模型结构天天变,那就老老实实用 PyTorch。

批处理策略的选择。静态 batching 实现简单,但延迟不可控,因为你要等凑够一个 batch 才能处理;动态 batching 延迟可控,但实现复杂,需要处理超时和优先级。我实测下来的经验是:如果你的服务对延迟敏感(比如实时对话),用动态 batching,超时设 10-20ms;如果对吞吐量更敏感(比如离线批量处理),用静态 batching,batch size 设大一点。

量化方案的选择。FP32 精度最高但最慢,FP16 精度损失很小但速度提升明显,INT8 速度最快但精度损失需要评估。我的建议是:先上 FP16,这是性价比最高的选择,大多数场景下精度损失可以忽略不计;如果还不够快,再考虑 INT8,但一定要做充分的精度评估,特别是对于分类任务,INT8 量化后类别不平衡的问题可能会被放大。

2.3 为什么这个思路能避免"学了就忘"

我见过太多人学 AI 工程的方式是:看一篇博客,跟着敲一遍代码,跑通了,然后就忘了。为什么?因为没有建立知识之间的关联。你学 vLLM 的时候只学了 vLLM 的 API,不知道它底层的 PagedAttention 是怎么回事,不知道它和 Continuous Batching 的关系,那换个框架你又得从头学。

从零构建的思路之所以有效,是因为它强迫你建立因果链条。你知道因为 GPU 显存有限,所以需要量化;因为量化会损失精度,所以需要校准数据集;因为校准数据集的质量直接影响量化效果,所以需要精心准备。这一整条链路是逻辑自洽的,你理解了其中一环,就能推导出下一环。这样学到的知识是活的,不是死的。

3. 核心细节解析与实操要点

3.1 推理服务的性能瓶颈到底在哪里

很多人做 AI 工程,第一步就搞错了方向。他们花大量时间优化模型结构,却不知道瓶颈根本不在模型计算上。我做过一个实测:一个 BERT-base 的模型,在 V100 上做一次推理,纯计算时间大约是 5ms,但端到端的延迟可能是 50ms。那多出来的 45ms 花在哪里了?

我列了一个典型的推理请求时间分布表,你可以对照看看自己的服务是不是也这样:

阶段典型耗时优化空间
网络传输1-5ms压缩请求体、使用 HTTP/2
请求解析与预处理5-20ms使用更快的 tokenizer、批处理预处理
模型推理5-50ms量化、算子融合、使用推理引擎
后处理2-10ms简化后处理逻辑、异步处理
响应序列化与返回1-5ms使用更高效的序列化格式

从表里可以看出来,预处理和后处理加起来可能比模型推理本身还耗时。这就是为什么我经常说:先别急着换推理引擎,先把你的预处理逻辑优化一遍。我见过一个项目,光是 tokenizer 就花了 30ms,换成一个 fast tokenizer 之后直接降到 3ms,比换推理引擎的效果还明显。

3.2 显存管理:AI 工程中最容易翻车的地方

显存管理是 AI 工程中最容易出问题、也最难排查的环节。我总结了几个常见的显存陷阱和对应的解决方案。

陷阱一:碎片化导致的 OOM。你可能会遇到这种情况:nvidia-smi显示显存还有 2GB 空闲,但程序就是报 OOM。这通常是因为显存碎片化——空闲的显存不是连续的,无法分配一个大块。解决方案是设置PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,让 PyTorch 使用可扩展的显存段,减少碎片。

陷阱二:KV Cache 占用过大。在做大模型推理时,KV Cache 会随着序列长度线性增长。一个 7B 的模型,如果序列长度是 2048,batch size 是 8,KV Cache 可能就要占用好几 GB 的显存。解决方案是使用 PagedAttention(vLLM 的核心技术),它把 KV Cache 分成固定大小的块来管理,大大减少了浪费。

陷阱三:多进程显存不释放。如果你用 DataLoader 的num_workers > 0,每个 worker 进程都会占用一份显存。解决方案是设置pin_memory=False,或者在 worker 中只做 CPU 操作,把 GPU 操作放到主进程。

注意:显存问题一定要在开发阶段就暴露出来,不要等到上线才发现。我的做法是在本地用一个小显存的显卡(比如 8GB)做开发,这样任何显存问题都会立刻暴露。

3.3 动态 Batching 的实现细节与参数调优

动态 Batching 是提升推理吞吐量最有效的手段之一,但实现起来有很多细节需要注意。我以自己实现过的一个动态 Batching 服务为例,讲讲关键点。

核心思路是:维护一个请求队列,当队列长度达到max_batch_size或者等待时间超过max_wait_time时,就取出当前队列中的所有请求组成一个 batch 进行推理。听起来简单,但实际实现时有几个坑。

第一个坑是超时时间的设置。设得太短,batch 凑不大,吞吐量上不去;设得太长,延迟增加,用户体验变差。我的经验值是:对于实时对话场景,max_wait_time设在 10-20ms;对于离线处理场景,可以设在 100ms 甚至更长。

第二个坑是batch 内请求长度不一致。如果 batch 里有一个特别长的序列,整个 batch 的推理时间都会被拉长。解决方案是使用长度感知的调度策略,把长度相近的请求放在同一个 batch 里。vLLM 的 Continuous Batching 就是做这个事情的。

第三个坑是错误处理。如果 batch 中某个请求处理失败了,怎么保证其他请求不受影响?我的做法是在推理前对每个请求做校验,把明显有问题的请求提前剔除;推理时如果发生异常,逐个重试。

4. 实操过程与核心环节实现

4.1 从零搭建一个可用的推理服务

我以搭建一个文本分类推理服务为例,完整走一遍从零开始的流程。这个例子虽然简单,但涵盖了 AI 工程的核心环节,你可以把其中的思路迁移到更复杂的场景。

第一步:环境准备与依赖管理。我强烈建议使用 Docker 来管理环境,因为 AI 工程的依赖关系非常复杂,CUDA 版本、PyTorch 版本、推理引擎版本之间都有兼容性要求。我的 Dockerfile 大致是这样的:

FROM nvidia/cuda:12.1-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y python3.10 python3-pip RUN pip install torch==2.1.0 --index-url https://download.pytorch.org/whl/cu121 RUN pip install onnxruntime-gpu==1.16.0 transformers==4.35.0 fastapi==0.104.0 uvicorn==0.24.0

这里有几个细节:使用runtime而不是devel镜像,因为部署时不需要编译工具,可以减小镜像体积;固定所有依赖的版本号,避免因为版本更新导致的行为变化;使用onnxruntime-gpu而不是onnxruntime,前者支持 GPU 加速。

第二步:模型导出与优化。把 PyTorch 模型导出为 ONNX 格式,然后做算子融合和量化:

import torch from transformers import AutoModelForSequenceClassification, AutoTokenizer model = AutoModelForSequenceClassification.from_pretrained("your-model") tokenizer = AutoTokenizer.from_pretrained("your-model") model.eval() dummy_input = tokenizer("example text", return_tensors="pt", padding="max_length", max_length=128) torch.onnx.export( model, (dummy_input["input_ids"], dummy_input["attention_mask"]), "model.onnx", input_names=["input_ids", "attention_mask"], output_names=["logits"], dynamic_axes={ "input_ids": {0: "batch_size", 1: "sequence_length"}, "attention_mask": {0: "batch_size", 1: "sequence_length"}, "logits": {0: "batch_size"} }, opset_version=14 )

导出时设置dynamic_axes很关键,这样模型才能支持动态的 batch size 和序列长度。opset_version建议用 14 或更高,因为低版本对某些算子的支持不好。

第三步:推理服务实现。用 FastAPI 搭建服务,核心是动态 Batching 的实现:

import asyncio import numpy as np import onnxruntime as ort from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() session = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"]) class Request(BaseModel): text: str request_queue = [] queue_lock = asyncio.Lock() async def batch_processor(): while True: await asyncio.sleep(0.01) # 10ms 等待窗口 async with queue_lock: if not request_queue: continue batch = request_queue[:32] # 最多 32 个 request_queue.clear() # 批量推理 inputs = tokenizer([r["text"] for r in batch], return_tensors="np", padding=True) outputs = session.run(None, dict(inputs)) for i, r in enumerate(batch): r["future"].set_result(outputs[0][i].tolist())

这个实现虽然简化了很多,但核心逻辑是完整的:维护一个请求队列,定期取出 batch 进行推理,然后通过 Future 把结果返回给对应的请求。

4.2 性能测试与调优的完整流程

服务搭起来之后,怎么知道它的性能到底行不行?我一般会做三个层次的测试。

第一层:单请求延迟测试。用curl或者 Python 脚本发单个请求,测量端到端延迟。这个数字告诉你服务在空载情况下的响应速度。我一般会测 100 次取 P50、P95 和 P99。

第二层:并发压力测试。用wrk或者locust模拟并发请求,逐步增加并发数,观察延迟和吞吐量的变化。你会看到一个典型的曲线:并发数增加,吞吐量先上升后趋于平缓,延迟则是指数上升。那个拐点就是你的服务的最佳工作点。

第三层:长时间稳定性测试。用固定的并发数跑几个小时,观察显存占用、延迟、错误率的变化。这一步主要是发现内存泄漏和显存碎片问题。

我整理了一个性能测试的检查清单,你可以直接拿去用:

测试项工具关注指标合格标准
单请求延迟curl/requestsP50/P95/P99P99 < 100ms
并发吞吐wrk/locustQPS根据业务需求
显存占用nvidia-smi峰值显存< 显卡显存的 80%
长时间稳定性自定义脚本延迟漂移4小时内漂移 < 10%
错误率服务日志5xx 比例< 0.1%

4.3 监控与告警的搭建

服务上线之后,没有监控就等于裸奔。我一般会监控这几个核心指标:请求量(QPS)、延迟分布(P50/P95/P99)、错误率、显存占用、GPU 利用率。这些指标可以用 Prometheus + Grafana 来采集和展示。

具体实现上,我会在推理服务中埋点,记录每个请求的处理时间、batch size、是否出错等信息,然后通过 Prometheus 的 Python client 暴露出来。Grafana 面板上,我会设置几个关键的告警规则:P99 延迟超过 200ms 持续 5 分钟、错误率超过 1% 持续 1 分钟、显存占用超过 90% 持续 1 分钟。

提示:告警阈值不要设得太敏感,否则你会被频繁的告警搞得麻木。我的经验是,先跑一周,观察指标的波动范围,然后取正常波动范围的上限作为告警阈值。

5. 常见问题与排查技巧实录

5.1 推理结果不一致的排查思路

这个问题我遇到过好几次,表现是:同一个输入,有时候返回结果 A,有时候返回结果 B。排查思路一般是这样的。

首先检查是否有随机性。模型本身可能有 dropout 层没有关掉,或者用了随机采样策略。解决方案是在推理前调用model.eval(),并且设置torch.no_grad()。

其次检查数值精度问题。FP16 和 FP32 的结果可能有细微差异,如果模型对数值敏感,这个差异可能被放大。解决方案是统一使用 FP32 做推理,或者做精度对齐测试。

最后检查并发问题。如果多个请求共享了同一个 session 或者 buffer,可能会出现数据竞争。解决方案是确保每个请求有独立的输入输出 buffer,或者使用线程安全的推理引擎。

5.2 显存泄漏的定位方法

显存泄漏是最难排查的问题之一,因为它的表现是渐进的,可能跑几个小时才出问题。我的排查方法是这样的。

第一步,用torch.cuda.memory_summary()打印显存分配详情,看看是哪个部分在增长。如果是 PyTorch 的缓存内存在增长,那可能是没有及时释放中间变量;如果是 CUDA 上下文内存在增长,那可能是创建了太多的 session 或者 stream。

第二步,用tracemalloc或者objgraph追踪 Python 对象的增长,看看是不是有对象没有被回收。常见的原因是循环引用,比如在闭包中引用了外部对象。

第三步,如果以上都排查不出来,那就用二分法:注释掉一半的代码,跑一段时间看显存是否还在增长,逐步缩小范围。

5.3 常见问题速查表

我把 AI 工程实践中常见的问题和解决方案整理成了一个速查表,方便你遇到问题时快速定位:

问题现象可能原因排查方法解决方案
服务启动就 OOM模型太大/显存碎片检查模型大小和显存占用量化模型/设置 expandable_segments
延迟忽高忽低动态 batching 参数不合理观察 batch size 分布调整 max_wait_time 和 max_batch_size
吞吐量上不去预处理是瓶颈profile 各阶段耗时优化 tokenizer/使用异步预处理
精度下降明显量化过度对比量化前后输出使用 FP16 或做量化感知训练
服务跑一段时间就崩显存泄漏监控显存变化趋势定位泄漏点/定期重启服务
GPU 利用率低数据加载是瓶颈检查 DataLoader 配置增加 num_workers/使用预取

5.4 几个我踩过的坑和对应的经验

坑一:盲目追求低延迟。我曾经为了把延迟从 50ms 降到 30ms,花了两周时间做各种优化,结果上线后发现用户根本感知不到这个差异。后来我学乖了,先搞清楚业务对延迟的真实要求,再决定投入多少精力优化。

坑二:忽略冷启动问题。推理服务第一次处理请求时,需要加载模型、初始化 CUDA 上下文,这个过程可能需要几十秒。如果服务是弹性伸缩的,新实例启动后立刻接收流量,就会导致大量超时。解决方案是配置就绪探针,确保服务完全初始化后才接收流量。

坑三:没有做降级方案。有一次推理服务因为显存问题挂了,整个系统都不可用。后来我加了一个降级方案:如果推理服务不可用,就返回一个默认结果或者走规则引擎。虽然效果差一些,但至少系统不会完全挂掉。

坑四:低估了数据预处理的重要性。我见过一个项目,模型推理只花了 10ms,但预处理花了 100ms,因为他们在预处理中做了大量的字符串操作和正则匹配。后来把预处理逻辑用 C++ 重写,延迟直接降了一个数量级。

这些经验告诉我,AI 工程的核心不是把模型调得多好,而是把整个系统的每一个环节都做到位。模型只是其中一环,而且往往不是最耗时的那一环。

6. 从零构建的知识体系该如何持续演进

AI 工程这个领域变化非常快,新的推理引擎、新的优化技术、新的硬件层出不穷。但底层的东西变化很慢:显存管理的基本原理、批处理的调度逻辑、性能优化的方法论,这些是相对稳定的。我的建议是,把 70% 的精力花在理解底层原理上,30% 的精力用来跟进新工具和新框架。这样无论技术怎么变,你都能快速适应。

具体到学习路径上,我建议按照"硬件层→推理引擎层→服务层→应用层"的顺序来构建知识体系。每学一层,都要动手写代码验证,不要只看文档。比如学推理引擎的时候,自己动手把一个模型导出为 ONNX 和 TensorRT,对比两者的性能和精度差异;学服务层的时候,自己实现一个简单的动态 Batching 服务,感受一下参数调优的过程。

我在实际工作中发现,那些真正能把 AI 系统做稳的人,往往不是最懂模型的人,而是最懂系统的人。他们知道瓶颈可能出现在哪里,知道怎么用最小的代价解决最大的问题。这种能力,只能通过一个个项目实战积累出来,没有捷径。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 19:35:58

残差扩散模型赋能MIMO CSI可变率JSCC,性能提升数量级

残差扩散模型赋能MIMO CSI可变率联合信源信道编码&#xff0c;性能实现数量级提升【附python代码】做无线通信系统的人&#xff0c;这几年应该都明显感觉到一个趋势&#xff1a;物理层和AI的边界正在快速融合&#xff0c;尤其是CSI反馈这个方向&#xff0c;简直是被深度学习“卷…

作者头像 李华
网站建设 2026/10/2 19:31:43

GitHub日榜高效阅读指南:从热榜中挖掘高价值技术项目

1. 日榜项目的真实价值&#xff1a;为什么值得每天花十分钟扫一遍很多人对 GitHub 热榜有个误解&#xff0c;觉得那不过是"看个热闹"——今天这个项目涨了几千星&#xff0c;明天那个项目被刷屏&#xff0c;跟自己手头的活儿没什么关系。我刚开始也是这么想的&#x…

作者头像 李华
网站建设 2026/10/2 19:28:34

Vue3企业级项目实战:从脚手架配置到性能优化全攻略

1. 项目初始化与工程化选型1.1 从零搭建Vue3项目&#xff1a;脚手架的选择与配置我今年接手了三个Vue3企业级项目&#xff0c;其中两个是从零起步&#xff0c;一个是从Vue2老项目迁移过来的。第一个踩的坑就是项目脚手架选择。现在官方主推的是npm create vuelatest&#xff0c…

作者头像 李华
网站建设 2026/10/2 19:28:32

Win10/Win7下Protel 99 SE添加库文件完整指南与避坑

前两天帮一位做电源模块的老哥收拾一台工控机&#xff0c;Win10 21H2 的系统&#xff0c;机子里装着一套用了十几年的 Protel 99 SE。问题很典型&#xff1a;打开原理图&#xff0c;左边 Browse Sch 面板的库列表一片空白&#xff0c;点 Add/Remove 按钮跟点了块木头一样没反应…

作者头像 李华
网站建设 2026/10/2 19:27:48

SOP+AI视频分析:破解装配质量过程监控难题

1. 先聊聊装配质量这个老难题干过产线的人都有感触&#xff0c;装配环节的质量管控&#xff0c;多少年都是靠“人盯人”。SOP写了一大本&#xff0c;培训也做了&#xff0c;但实际执行起来&#xff0c;漏装、错装、扭矩不到位的现象总在重复出现。这几年我陆续参与过几条装配线…

作者头像 李华