
1. 从零搭建AI工程能力为什么我劝你别一上来就调包这两年AI应用开发的门槛肉眼可见地降低了随便拉个框架、调个API就能跑出一个能对话的Demo。但我带过不少新人也面试过不少号称“做过AI项目”的候选人发现一个很普遍的问题大家会用工具但不知道工具背后发生了什么。模型输出不稳定不知道从哪查推理速度慢不知道瓶颈在哪想换个模型发现代码跟某个厂商的SDK绑死了迁移成本高得离谱。ai-engineering-from-scratch这个方向说白了就是解决这个问题的。它不是让你去手写一个Transformer也不是让你从零训练一个大模型而是让你把AI工程链路里的每一个关键环节都亲手搭一遍理解数据怎么流转、模型怎么加载、推理怎么调度、服务怎么暴露、效果怎么评估。这些东西你亲手做过一次再用现成框架的时候心里就有底了。这篇文章适合谁看如果你已经会用Python调过几个大模型API但总觉得是在“黑盒”上做开发想搞清楚底层到底怎么回事那这篇内容就是给你写的。如果你是完全零基础也没关系我会尽量用生活化的类比把关键概念讲清楚但你需要有一点编程基础至少能看懂Python代码。我自己的背景是做了七八年后端和算法工程最近两年主要在做AI应用落地。踩过的坑不少有些是技术选型的问题有些是工程实现的问题还有些纯粹是认知不到位导致的。下面我把从零搭建AI工程能力的完整思路和实操细节拆开讲尽量做到你照着做就能复现。2. 整体设计思路为什么要“从零”而不是“从框架”2.1 先搞清楚“AI工程”到底包含什么很多人把AI工程等同于“调模型API”这其实只占了整个链路的一小部分。一个完整的AI工程链路至少包含以下几个环节数据层数据的采集、清洗、格式化、切分、向量化。这一步决定了模型能看到什么直接影响最终效果。模型层模型的选择、加载、推理、量化、缓存。这一步决定了响应速度和成本。服务层API设计、并发处理、流式输出、错误重试、限流降级。这一步决定了系统能不能扛住真实流量。评估层效果评估、回归测试、A/B对比、人工反馈闭环。这一步决定了你的系统能不能持续迭代。运维层日志、监控、告警、版本管理、灰度发布。这一步决定了出问题的时候你能不能快速定位。你如果只用某个框架的一站式解决方案这些环节都被封装起来了用起来很爽但一旦出问题你连从哪下手都不知道。从零搭建的意义在于你亲手把每个环节都实现一遍哪怕实现得很粗糙你对整个链路的理解也会完全不一样。2.2 技术选型为什么我选Python而不是其他语言AI工程领域Python是事实上的标准语言。原因很简单主流模型训练框架PyTorch、TensorFlow都是Python优先推理生态ONNX Runtime、TensorRT、vLLM也都有Python接口数据处理库NumPy、Pandas更是Python的天下。但这不意味着Python是完美的。Python的GIL限制了多线程并发在高并发场景下性能不如Go或Rust。所以我的做法是核心推理和服务逻辑用Python写性能敏感的部分用C或Rust扩展或者直接用现成的高性能推理引擎。这样既保证了开发效率又不至于在性能上吃大亏。具体到从零搭建的阶段我建议先用纯Python把整个链路跑通不要过早优化。等你把链路跑通了知道瓶颈在哪了再针对性地替换高性能组件。上来就追求极致性能很容易陷入“过度工程”的陷阱最后啥也没跑起来。2.3 项目结构设计怎么组织代码才不乱从零搭建AI工程代码组织很关键。我见过太多项目一开始就几个文件后来越加越多最后变成一坨。我的建议是从一开始就按职责分层ai-engineering-from-scratch/ ├── data/ # 数据处理相关 │ ├── loader.py # 数据加载 │ ├── cleaner.py # 数据清洗 │ └── vectorizer.py # 向量化 ├── model/ # 模型相关 │ ├── loader.py # 模型加载 │ ├── inference.py # 推理逻辑 │ └── cache.py # 推理缓存 ├── service/ # 服务相关 │ ├── api.py # API接口 │ ├── scheduler.py # 请求调度 │ └── stream.py # 流式输出 ├── eval/ # 评估相关 │ ├── metrics.py # 评估指标 │ └── regression.py # 回归测试 ├── config/ # 配置文件 │ └── settings.py # 全局配置 └── tests/ # 测试用例 ├── test_data.py ├── test_model.py └── test_service.py这个结构不是死的你可以根据自己的需求调整。但核心原则是每个模块只负责一件事模块之间通过明确的接口通信。这样你替换任何一个模块都不会影响其他模块。提示不要把所有代码都塞进一个文件里哪怕一开始只有几十行。养成好习惯后面会省很多事。3. 核心细节解析数据、模型、服务三个关键环节3.1 数据处理模型效果的上限由数据决定我经常跟团队里的人说一句话模型效果不好八成是数据的问题不是模型的问题。很多人一上来就换模型、调参数但真正该做的是回头看看数据。数据处理的第一步是清洗。原始数据里通常有大量噪声HTML标签、特殊字符、重复内容、乱码。这些东西不清理掉模型学到的就是垃圾。我一般会写一个清洗管道按顺序做以下几件事去重用哈希或者SimHash做近似去重避免重复内容影响模型。去噪用正则表达式去掉HTML标签、URL、特殊符号。归一化统一大小写、全半角、编码格式。分句分段把长文本切成合适的粒度方便后续处理。清洗完之后是格式化。不同的模型对输入格式要求不一样有的要求JSON有的要求纯文本有的要求特定的分隔符。我一般会定义一个统一的内部格式然后在输出的时候转换成目标格式。这样换模型的时候只需要改转换层不用动数据层。向量化是数据处理的最后一步。如果你用的是检索增强生成RAG方案就需要把文本转成向量存到向量数据库里。向量化的质量直接影响检索效果所以选一个合适的Embedding模型很重要。我的经验是不要盲目追求大模型先看看你的数据规模和检索需求。如果数据量不大用一个小一点的Embedding模型就够了速度快、成本低。# 一个简单的数据清洗示例 import re def clean_text(text: str) - str: # 去掉HTML标签 text re.sub(r[^], , text) # 去掉URL text re.sub(rhttp[s]?://\S, , text) # 去掉多余空白 text re.sub(r\s, , text) # 统一小写 text text.lower().strip() return text这段代码很简单但实际项目中你需要根据数据特点调整正则表达式。比如中文数据可能需要处理全角半角英文数据可能需要处理缩写和拼写错误。3.2 模型加载与推理别小看这一步的坑模型加载看起来很简单不就是model.load()吗但实际操作中坑不少。第一个坑是显存管理。如果你加载多个模型或者模型比较大很容易爆显存。我的做法是按需加载用完就释放。不要一次性把所有模型都加载到显存里除非你的显存足够大。另外可以用量化技术把模型压缩比如INT8量化可以把显存占用降到FP16的一半左右精度损失通常在可接受范围内。第二个坑是推理速度。同样的模型不同的推理引擎速度可能差好几倍。我实测下来vLLM在批量推理场景下比原生PyTorch快很多因为它做了PagedAttention和连续批处理。如果你的场景是单条推理差异可能没那么明显但如果是高并发场景推理引擎的选择就很关键了。第三个坑是模型版本管理。你可能会频繁换模型如果没有一个好的版本管理机制很容易搞混。我的做法是每个模型都打上版本号配置文件里指定版本日志里记录版本。这样出问题的时候可以快速回滚。# 模型加载的简单封装 class ModelLoader: def __init__(self, model_path: str, device: str cuda): self.model_path model_path self.device device self.model None def load(self): # 实际项目中这里可能是transformers或vLLM的加载逻辑 print(fLoading model from {self.model_path} on {self.device}) # self.model AutoModel.from_pretrained(self.model_path).to(self.device) return self def unload(self): if self.model is not None: del self.model self.model None # 清理显存 import torch torch.cuda.empty_cache()这个封装很粗糙但核心思想是加载和卸载要成对出现显存要及时清理。实际项目中你还需要考虑模型预热、并发加载等问题。3.3 服务层设计怎么让模型对外提供服务模型跑通了下一步是把它包装成一个服务。这里有几个关键点API设计我一般用FastAPI因为它异步支持好、性能不错、文档自动生成。接口设计上我建议至少提供两个接口一个同步接口用于简单场景一个流式接口用于对话场景。流式输出对用户体验提升很大尤其是生成长文本的时候。并发处理Python的GIL限制了多线程并发所以高并发场景下需要用异步或者多进程。FastAPI本身是异步的但如果你的推理逻辑是同步的会阻塞事件循环。解决办法是把推理逻辑放到线程池或者进程池里执行。错误重试模型推理可能会失败比如显存不足、输入超长、网络抖动。我一般会加一个重试机制但要注意重试次数不能太多否则会放大问题。另外重试的时候要考虑幂等性避免重复处理。限流降级真实流量往往不可预测所以需要限流保护。我一般用令牌桶算法做限流超过阈值的请求直接返回错误或者排队等待。降级策略也很重要比如模型服务不可用的时候可以返回缓存结果或者兜底回复。# FastAPI服务示例 from fastapi import FastAPI from fastapi.responses import StreamingResponse import asyncio app FastAPI() app.post(/generate) async def generate(prompt: str): # 模拟推理 result await asyncio.to_thread(lambda: fEcho: {prompt}) return {result: result} app.post(/stream) async def stream(prompt: str): async def event_generator(): for token in [Hello, , world, !]: yield fdata: {token}\n\n await asyncio.sleep(0.1) return StreamingResponse(event_generator(), media_typetext/event-stream)这个示例很简陋但展示了核心思路同步推理用线程池包装流式输出用异步生成器。实际项目中你还需要加认证、日志、监控等。4. 实操过程从零搭建一个完整的AI服务4.1 环境准备与依赖安装第一步是准备环境。我建议用conda或者venv创建独立的虚拟环境避免依赖冲突。Python版本建议3.10以上因为很多新库已经不支持3.8了。# 创建虚拟环境 python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 安装核心依赖 pip install fastapi uvicorn numpy pandas pip install torch transformers # 如果要用PyTorch pip install sentence-transformers # 如果要做向量化依赖安装这一步有个坑PyTorch的版本要和CUDA版本匹配。如果你有GPU一定要去PyTorch官网查一下对应的CUDA版本否则装完了用不了GPU。我见过太多人在这上面浪费时间。提示如果你不确定CUDA版本可以用nvidia-smi命令查看。如果没有GPU就装CPU版本的PyTorch虽然慢但至少能跑。4.2 数据管道的搭建与测试环境准备好之后先搭数据管道。我一般会先写一个简单的数据加载器从本地文件或者数据库读取数据然后跑一遍清洗和格式化最后输出成统一的格式。# 数据管道示例 class DataPipeline: def __init__(self, source: str): self.source source def load(self): # 从文件加载数据 with open(self.source, r, encodingutf-8) as f: raw_data f.readlines() return raw_data def clean(self, data): cleaned [] for line in data: line line.strip() if line and len(line) 10: # 过滤太短的 cleaned.append(line) return cleaned def format(self, data): # 格式化成模型输入需要的格式 return [{text: item} for item in data] def run(self): data self.load() data self.clean(data) data self.format(data) return data搭好之后一定要写测试。我一般会准备一个小数据集跑一遍完整流程检查输出是否符合预期。测试用例要覆盖正常情况和边界情况比如空数据、超长数据、特殊字符等。4.3 模型推理的封装与优化数据管道跑通之后开始封装模型推理。我一般会定义一个InferenceEngine类把模型加载、推理、卸载都封装进去。class InferenceEngine: def __init__(self, model_path: str): self.model_path model_path self.model None def load(self): # 实际项目中这里加载真实模型 print(fLoading model: {self.model_path}) self.model mock_model return self def predict(self, inputs): if self.model is None: raise RuntimeError(Model not loaded) # 实际项目中这里调用模型推理 return [fprediction_for_{item} for item in inputs] def unload(self): self.model None print(Model unloaded)封装好之后做一次性能测试。我一般会测两个指标单条推理延迟和批量推理吞吐。单条延迟影响用户体验批量吞吐影响成本。测试的时候要注意预热第一次推理通常比较慢因为要初始化各种资源。优化方面我一般会做以下几件事批处理把多个请求合并成一个批次推理提高GPU利用率。缓存对相同的输入缓存推理结果避免重复计算。量化用INT8或INT4量化减少显存占用和推理时间。异步用异步IO避免阻塞提高并发能力。4.4 服务接口的实现与联调模型推理封装好之后用FastAPI暴露接口。我一般会先实现一个最简单的同步接口跑通之后再加流式、限流、重试等功能。from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI() engine InferenceEngine(path/to/model).load() class GenerateRequest(BaseModel): prompt: str max_length: int 100 app.post(/generate) async def generate(req: GenerateRequest): try: result engine.predict([req.prompt]) return {result: result[0]} except Exception as e: raise HTTPException(status_code500, detailstr(e))联调的时候我一般用curl或者Postman发请求检查返回是否符合预期。然后写一个简单的压测脚本模拟并发请求看看服务能不能扛住。# 用curl测试 curl -X POST http://localhost:8000/generate \ -H Content-Type: application/json \ -d {prompt: Hello, max_length: 50}压测可以用ab或者wrk也可以写Python脚本用asyncio模拟并发。压测的时候要关注几个指标QPS、P99延迟、错误率。如果QPS上不去看看是不是GIL限制如果P99延迟高看看是不是有慢请求阻塞如果错误率高看看日志里有什么异常。4.5 评估与迭代怎么知道你的系统好不好服务跑起来之后下一步是评估。评估分两个层面离线评估和在线评估。离线评估就是准备一个测试集跑一遍模型计算准确率、召回率、F1等指标。我一般会写一个评估脚本自动跑测试集并输出报告。def evaluate(engine, test_data): correct 0 total len(test_data) for item in test_data: prediction engine.predict([item[input]])[0] if prediction item[expected]: correct 1 accuracy correct / total print(fAccuracy: {accuracy:.2%}) return accuracy在线评估就是收集真实用户的反馈比如点赞点踩、停留时长、转化率等。我一般会在服务里加一个反馈接口让用户可以对结果进行评价然后定期分析这些反馈数据。迭代的时候我一般遵循“小步快跑”的原则每次只改一个变量改完跑评估对比结果确认有效再继续。不要一次性改多个地方否则出了问题都不知道是哪个改动导致的。5. 常见问题与排查技巧实录5.1 模型加载失败从报错信息定位问题模型加载失败是最常见的问题之一。报错信息通常比较长但关键信息往往在最后几行。我一般会按以下顺序排查报错关键词可能原因解决方法CUDA out of memory显存不足减小batch size用量化清理显存File not found路径错误检查模型路径确认文件存在Version mismatch版本不兼容检查PyTorch和CUDA版本检查模型格式Permission denied权限问题检查文件权限用sudo或改权限我踩过最坑的一次是模型路径里有中文导致加载失败。后来统一改成英文路径就好了。所以路径尽量用英文不要有空格和特殊字符。5.2 推理速度慢从瓶颈定位到优化推理速度慢的原因可能有很多我一般按以下步骤排查确认是不是首次推理首次推理通常慢因为要初始化。跑几次之后如果还是慢那就是真慢。确认是不是GPU没跑起来用nvidia-smi看GPU利用率如果利用率很低可能是数据在CPU和GPU之间来回拷贝。确认是不是batch size太小batch size太小GPU利用率上不去。适当增大batch size可以提高吞吐。确认是不是模型太大模型太大推理自然慢。可以考虑量化或者换小模型。我实测下来量化是最有效的优化手段之一。INT8量化通常能把推理速度提升1.5到2倍精度损失在1%以内。如果对精度要求不高INT4量化还能更快。5.3 服务不稳定从日志和监控找线索服务不稳定表现为间歇性报错、延迟抖动、内存泄漏等。排查这类问题日志和监控是关键。我一般会在服务里加详细的日志记录每个请求的输入、输出、耗时、错误信息。然后用ELK或者Loki收集日志用Grafana做可视化。这样出问题的时候可以快速定位。内存泄漏是Python服务常见的问题。我一般用tracemalloc或者memory_profiler来排查。常见的内存泄漏原因包括全局变量不断增长、循环引用、缓存没有清理。解决办法是定期重启服务或者用弱引用避免循环引用。提示不要忽视日志的格式。结构化日志JSON格式比纯文本日志好分析得多。我一般用structlog或者loguru来输出结构化日志。5.4 效果不达预期从数据和评估找原因效果不达预期的时候很多人第一反应是换模型。但我的经验是先看数据再看评估最后才看模型。数据方面检查训练数据和推理数据的分布是否一致。如果训练数据是英文推理数据是中文效果肯定差。另外检查数据质量有没有噪声、重复、标注错误。评估方面检查评估指标是否合理。如果评估指标和业务目标不一致那评估结果就没有意义。比如业务关心的是用户满意度但你只看准确率那可能优化方向就错了。模型方面如果数据和评估都没问题那可能是模型本身不适合这个任务。这时候可以考虑换模型或者用微调来适配。5.5 常见问题速查表问题现象可能原因排查方法解决方案服务启动报错依赖缺失或版本冲突看报错信息检查依赖重装依赖固定版本推理结果为空输入格式错误打印输入检查格式调整输入格式响应时间过长模型太大或并发太高看监控测单条延迟量化模型加限流内存持续增长内存泄漏用memory_profiler排查修复泄漏定期重启结果不稳定随机种子未固定检查随机种子设置固定随机种子GPU利用率低数据加载瓶颈看GPU利用率和CPU利用率优化数据加载增大batch6. 一些个人体会和后续扩展方向从零搭建AI工程这件事我最大的体会是不要怕重复造轮子但也不要一直造轮子。从零实现一遍是为了理解原理理解之后就该用现成的工具提高效率。我见过一些人什么都自己写最后项目进度拖得很慢也见过一些人什么都是用现成的出了问题完全不知道从哪查。平衡点在于核心链路自己实现边缘功能用现成工具。后续扩展的话有几个方向可以考虑。一是多模型路由根据请求的特点自动选择最合适的模型兼顾效果和成本。二是反馈闭环把用户反馈自动收集起来定期微调模型。三是可观测性加更多的监控指标和告警规则让系统更稳定。最后分享一个小技巧每次改动都写一个简短的记录记录改了什么、为什么改、效果如何。这个习惯看起来不起眼但几个月后回头看能帮你省很多回忆的时间。我一般用Markdown文件记录放在项目根目录的CHANGELOG.md里简单但有效。