1. 从零搭建AI工程能力:为什么我劝你别一上来就啃论文
"ai-engineering-from-scratch"这个标题,第一次看到的时候我以为是又一个教你怎么调API的速成教程。点进去翻了翻,发现方向完全不是那么回事——它讲的是从最底层开始,把AI工程化落地所需要的整套能力一点点搭起来。这个思路我太认同了。
过去两年我带过几个团队做AI相关的项目,见过太多人卡在同一个地方:模型能跑通,demo能演示,但一上生产就各种问题。延迟高、成本失控、效果不稳定、数据管道三天两头断。根子在哪?不是模型不够好,是工程能力没跟上。很多人学AI的路径是"看论文→跑开源代码→调参→部署",每一步都在跳,跳到最后发现自己只会复制粘贴,遇到新问题就懵了。
"ai-engineering-from-scratch"这个项目标题背后的核心价值,就是帮你把这条断裂的路径补全。它适合谁?我认为有三类人特别需要:第一类是有一定编程基础但没系统做过AI项目的开发者,第二类是从数据/后端转AI工程方向的工程师,第三类是带团队的技术负责人,需要理解AI工程的全貌才能做技术决策。这篇文章我会把这个项目涉及的核心思路、技术选型、实操步骤和我自己踩过的坑,尽可能完整地拆开讲一遍。
2. 整体设计思路:为什么"从零"不等于"从底层造轮子"
2.1 先搞清楚"AI工程"到底包含什么
很多人把AI工程等同于"训练模型",这是最大的认知偏差。训练模型只是其中一环,而且对大多数应用场景来说,这一环甚至不是最耗时间的。一个完整的AI工程体系,我把它拆成五层:
- 数据层:数据采集、清洗、标注、版本管理、特征存储
- 训练层:实验管理、分布式训练、超参搜索、模型评估
- 推理层:模型服务、批处理、流式推理、GPU调度
- 应用层:Prompt管理、RAG管道、Agent编排、输出校验
- 运维层:监控告警、成本追踪、A/B测试、灰度发布
"从零搭建"的意思是,这五层你都要理解,但不意味着每一层你都要自己写。比如特征存储可以用Feast,实验管理可以用MLflow,推理服务可以用Triton或vLLM。关键是你要知道每一层解决什么问题、什么时候该引入什么工具、工具之间的边界在哪里。
我见过一个团队,上来就自己写了一套模型服务框架,花了三个月,最后发现vLLM一行配置就能达到更好的吞吐。这就是典型的"从零"理解错了——从零是让你从零理解问题,不是从零造所有轮子。
2.2 技术选型的核心原则:按阶段匹配复杂度
从零搭建AI工程能力,最容易犯的错是过度设计。我的建议是按阶段来:
| 阶段 | 数据量级 | 推荐方案 | 核心目标 |
|---|---|---|---|
| 验证期 | <10万条 | 单机+脚本 | 快速验证可行性 |
| 成长期 | 10万-1000万 | 轻量管道+托管服务 | 稳定迭代 |
| 规模化 | >1000万 | 分布式+自建核心组件 | 成本与效率平衡 |
验证期就别想着上Kubernetes,一个Jupyter Notebook加几个Python脚本就够了。成长期再引入Airflow或Prefect做编排,用托管的向量数据库省运维精力。到了规模化阶段,才需要考虑自建推理集群、做GPU利用率优化这些事。
这个原则背后的逻辑很简单:工程复杂度必须匹配业务复杂度。过早引入复杂架构,只会让你在还没验证需求的时候就陷入运维泥潭。
2.3 为什么我建议从"推理侧"入手而不是"训练侧"
这是我自己踩坑之后的经验。大多数人学AI工程,第一反应是从训练入手——搞数据集、调模型、跑实验。但训练侧的门槛其实很高:需要GPU资源、需要理解模型架构、需要调参经验,而且反馈周期长,一个实验跑几个小时甚至几天。
推理侧不一样。你拿一个开源模型,部署起来,马上就能看到效果。在这个过程中你会遇到真实的问题:显存不够怎么办、并发上不去怎么办、输出格式不稳定怎么办。这些问题逼着你去理解tokenization、KV Cache、批处理策略这些核心概念。等你把推理侧摸透了,再回头看训练侧,很多概念是相通的。
"ai-engineering-from-scratch"这个思路里,我理解的核心路径就是:先让模型跑起来→再让模型跑得稳→最后让模型跑得便宜。这个顺序不能反。
3. 核心细节解析:从零搭建必须吃透的四个技术点
3.1 数据处理管道:别小看清洗和分块
数据是AI工程的地基,但大多数人在这上面花的时间太少。我见过一个RAG项目,效果怎么调都不行,最后发现是文档分块策略有问题——把表格和正文混在一起切了,检索出来的内容全是碎片。
从零搭建数据管道,你需要关注这几个环节:
采集与去重:网页数据、PDF、数据库导出,格式五花八门。去重不是简单的字符串匹配,要用MinHash或SimHash做近似去重,否则你的训练数据里会有大量重复内容,导致模型过拟合。
清洗与标准化:HTML标签、乱码、特殊字符、多余空白,这些都要处理。我一般会写一个清洗管道,用正则加规则引擎,把常见问题一次性解决。注意保留原始数据的备份,清洗规则改了可以重新跑。
分块策略:这是RAG场景下最关键的环节。我的经验是:技术文档按标题层级切,每块500-1000 token;对话记录按轮次切;代码按函数或类切。切完之后要加overlap,一般10%-20%,防止边界信息丢失。
版本管理:数据也要做版本控制。DVC是个不错的选择,它把大文件存在对象存储里,Git里只存元数据。每次数据更新打一个tag,模型效果出问题可以回溯到具体的数据版本。
注意:数据清洗规则一定要写成可配置的,不要硬编码在脚本里。我吃过亏,规则改了要改代码重新部署,效率极低。
3.2 模型推理服务:从单机到高并发的演进路径
推理服务是从零搭建AI工程能力最核心的实操环节。我按演进路径来讲:
第一步:单机直接加载。用Transformers库的pipeline,几行代码就能跑。这个阶段重点是理解模型的输入输出格式、tokenizer的行为、生成参数(temperature、top_p、max_tokens)的影响。
第二步:引入推理框架。vLLM是目前最主流的选择,核心优势是PagedAttention和连续批处理。实测下来,同样的硬件,vLLM的吞吐能比原生Transformers高5-10倍。部署命令很简单:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192这里有几个参数需要根据实际情况调:gpu-memory-utilization控制显存占用比例,0.9是个比较安全的默认值;max-model-len决定最大上下文长度,设太大浪费显存,设太小长文本会截断。
第三步:加一层网关。当你有多个模型、多个版本需要管理时,需要一个统一的入口。这层网关负责路由、限流、鉴权、日志。可以用FastAPI自己写,也可以用现成的方案。核心是要把模型版本和API路径解耦,方便做灰度发布。
第四步:GPU调度与弹性伸缩。到了这个阶段,你需要监控GPU利用率、请求队列长度,根据负载动态调整实例数。Kubernetes加KEDA可以做基于指标的自动伸缩,但配置起来比较复杂,建议规模上来之后再考虑。
3.3 评估体系:没有评估就没有优化
这是最容易被忽视但最重要的一环。很多人做AI项目,效果好不好全靠"感觉",这是不行的。从零搭建评估体系,我建议分三层:
第一层:自动化指标。分类任务看准确率、召回率、F1;生成任务看BLEU、ROUGE、BERTScore;RAG场景看检索命中率、答案忠实度。这些指标可以自动化跑,每次模型更新都跑一遍,防止回归。
第二层:LLM-as-Judge。用更强的模型来评估弱模型的输出。比如用GPT-4评估你的7B模型生成的答案质量。这个方法有争议,但实测下来在大多数场景下和人工评估的相关性能到0.8以上。关键是评估Prompt要设计好,给出明确的评分标准和示例。
第三层:人工抽检。自动化指标再全,也替代不了人工。我一般会每周抽100条线上请求,人工标注质量,和自动化指标做对比。如果发现偏差大,说明自动化指标需要调整。
评估数据集要单独维护,不能和训练数据混在一起。我建议至少准备200-500条覆盖主要场景的评估样本,每次迭代都跑一遍。
3.4 监控与可观测性:上线只是开始
AI系统的监控和传统后端系统不一样,除了CPU、内存、延迟这些常规指标,还要关注:
- Token吞吐量:每秒处理的输入/输出token数,直接关系到成本
- 首Token延迟:用户感知最明显的指标,流式输出场景下尤其重要
- 输出质量指标:拒答率、格式错误率、重复率
- 成本指标:每千次请求的GPU成本、API调用成本
我一般用Prometheus收集指标,Grafana做面板。日志方面,每次请求的输入输出都要记录,但要注意脱敏和存储成本。可以用采样策略,正常请求采样10%,异常请求全量记录。
实操心得:监控面板不要做太多,一屏能看完最好。我见过一个团队做了十几个面板,结果没人看。核心指标就那几个,放在最显眼的位置。
4. 实操过程:从零搭建一个完整的AI工程Demo
4.1 环境准备与依赖安装
我以一个RAG问答系统为例,把从零搭建的完整流程走一遍。硬件要求:一张24G显存的GPU(3090或4090都行),32G内存,100G硬盘。
基础环境用conda管理:
conda create -n ai-eng python=3.11 conda activate ai-eng pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 pip install vllm transformers sentence-transformers pip install fastapi uvicorn pip install chromadb pip install ragas这里解释一下选型:vLLM做推理,sentence-transformers做embedding,chromadb做向量存储(轻量,适合验证期),ragas做RAG评估。都是经过验证的组合,踩坑少。
4.2 数据准备与向量化
假设我们有一批技术文档,放在docs/目录下。第一步是加载和分块:
from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import DirectoryLoader loader = DirectoryLoader('docs/', glob='**/*.md') documents = loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=800, chunk_overlap=150, separators=["\n## ", "\n### ", "\n\n", "\n", "。", " "] ) chunks = splitter.split_documents(documents) print(f"共切分 {len(chunks)} 个块")分块参数怎么定?chunk_size=800是因为大多数embedding模型的最佳输入长度在256-512 token之间,800字符大约对应400-600 token,留了一定余量。overlap=150是为了防止边界信息丢失。separators的顺序很重要,优先按标题切,保证语义完整性。
然后是向量化和入库:
from sentence_transformers import SentenceTransformer import chromadb model = SentenceTransformer('BAAI/bge-large-zh-v1.5') client = chromadb.PersistentClient(path='./chroma_db') collection = client.get_or_create_collection('tech_docs') for i, chunk in enumerate(chunks): embedding = model.encode(chunk.page_content).tolist() collection.add( ids=[f"chunk_{i}"], embeddings=[embedding], documents=[chunk.page_content], metadatas=[{"source": chunk.metadata.get("source", "")}] )embedding模型选bge-large-zh-v1.5,中文场景下效果稳定,而且维度是1024,不算太大。如果你做英文场景,可以用bge-large-en-v1.5或者e5-large。
4.3 推理服务搭建与RAG管道串联
启动vLLM服务:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --port 8000然后写RAG管道:
import requests from sentence_transformers import SentenceTransformer import chromadb embed_model = SentenceTransformer('BAAI/bge-large-zh-v1.5') client = chromadb.PersistentClient(path='./chroma_db') collection = client.get_collection('tech_docs') def retrieve(query, top_k=5): q_emb = embed_model.encode(query).tolist() results = collection.query(query_embeddings=[q_emb], n_results=top_k) return results['documents'][0] def generate(query, contexts): context_text = "\n\n".join(contexts) prompt = f"""基于以下参考资料回答问题。如果资料中没有相关信息,直接说不知道。 参考资料: {context_text} 问题:{query} 回答:""" resp = requests.post("http://localhost:8000/v1/completions", json={ "model": "qwen", "prompt": prompt, "max_tokens": 512, "temperature": 0.1 }) return resp.json()['choices'][0]['text'] query = "vLLM的PagedAttention是什么?" contexts = retrieve(query) answer = generate(query, contexts) print(answer)这个管道虽然简单,但包含了RAG的核心环节:检索→拼接→生成。temperature设0.1是为了让输出更稳定,RAG场景不需要太多创造性。
4.4 评估与迭代
用ragas做自动化评估:
from ragas import evaluate from ragas.metrics import faithfulness, answer_relevancy, context_precision from datasets import Dataset eval_data = { "question": ["vLLM的PagedAttention是什么?", "如何做数据分块?"], "answer": [answer1, answer2], "contexts": [contexts1, contexts2], "ground_truth": ["标准答案1", "标准答案2"] } dataset = Dataset.from_dict(eval_data) result = evaluate(dataset, metrics=[faithfulness, answer_relevancy, context_precision]) print(result)faithfulness衡量答案是否忠实于检索到的上下文,answer_relevancy衡量答案和问题的相关度,context_precision衡量检索的准确度。这三个指标能覆盖RAG系统的主要质量维度。
跑完评估,根据低分指标针对性优化。比如faithfulness低,说明模型在编造内容,需要调整Prompt或换更强的模型;context_precision低,说明检索不准,需要优化embedding或分块策略。
5. 常见问题与排查技巧实录
5.1 显存不够用怎么办
这是最高频的问题。排查思路按优先级来:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动就OOM | 模型太大 | 用量化版本(GPTQ/AWQ),或换小模型 |
| 运行中OOM | 并发太高 | 降低max_num_seqs,或限制max_model_len |
| 显存碎片 | 频繁变长请求 | 开启vLLM的PagedAttention,设gpu_memory_utilization=0.9 |
量化是最直接的方案。7B模型FP16需要约14G显存,INT4量化后只要4G左右,效果损失在可接受范围内。vLLM支持AWQ和GPTQ,加载时加--quantization awq就行。
5.2 输出格式不稳定怎么治
LLM输出格式不稳定是通病。我的经验是三层防护:
第一层,Prompt里给明确格式要求和示例。比如"请以JSON格式输出,包含answer和confidence两个字段,示例:{"answer": "...", "confidence": 0.95}"。
第二层,用结构化输出工具。vLLM支持guided decoding,可以强制模型按JSON Schema输出:
from vllm import SamplingParams from vllm.sampling_params import GuidedDecodingParams guided = GuidedDecodingParams(json=schema) params = SamplingParams(temperature=0.1, guided_decoding=guided)第三层,后处理校验。解析失败时重试或降级处理。我一般会重试2次,还失败就返回兜底答案。
5.3 检索效果差怎么排查
RAG效果差,80%的问题出在检索环节。排查步骤:
- 先看检索到的内容是否相关。打印top_k结果,人工判断。
- 如果检索内容不相关,检查embedding模型是否适合你的领域。通用模型在专业领域可能表现不好,需要微调或换领域模型。
- 如果检索内容相关但答案不对,检查Prompt模板。上下文太长可能导致模型忽略关键信息,试试把最相关的内容放在最前面。
- 如果都正常但效果还是差,考虑加一个rerank模型。bge-reranker是个不错的选择,对top_k结果做精排,能显著提升准确率。
避坑技巧:分块的时候保留标题信息。我习惯在每个chunk前面加上所属的标题路径,比如"## 3.2 模型推理 > ### 3.2.1 vLLM部署",这样检索时能保留层级语义。
5.4 成本失控怎么控制
成本控制要从三个维度入手:
Token层面:限制max_tokens,别让模型无限生成。RAG场景一般512就够了。输入侧做上下文压缩,只保留最相关的片段。
请求层面:加缓存。相同或相似的query直接返回缓存结果。可以用语义缓存,相似度超过阈值的直接命中。
硬件层面:监控GPU利用率。如果利用率长期低于30%,说明资源浪费,考虑合并服务或换更小的实例。如果长期高于80%,说明需要扩容。
我一般会做一个成本看板,按天统计token消耗和GPU时长,设置预算告警。超过阈值就触发排查。
5.5 模型更新后效果回退怎么办
这是没有评估体系的典型症状。解决方案:
- 建立回归测试集,每次模型更新前跑一遍,对比关键指标。
- 做灰度发布,新模型先接10%流量,观察一周再全量。
- 保留模型版本回滚能力,出问题能快速切回旧版本。
我自己的做法是维护一个"黄金测试集",200条左右,覆盖核心场景和边界情况。每次更新必跑,指标下降超过5%就阻断发布。
6. 我在这条路上踩过的几个坑
第一个坑是过早优化。刚开始做的时候,总想着一步到位,搞了一套复杂的微服务架构,结果光运维就耗掉大半精力,核心功能反而没时间打磨。后来全部推倒重来,用单体加脚本,两周就跑通了。先跑通,再优化,这个顺序不能反。
第二个坑是忽视数据质量。有段时间模型效果怎么调都不行,最后发现训练数据里有30%是重复的,还有大量格式错误。花了一周做数据清洗,效果直接提升了一大截。数据上的投入,回报率远高于调参。
第三个坑是不做评估。早期全靠感觉判断效果,结果每次更新都像开盲盒。后来建了评估体系,才发现之前很多"优化"其实是负优化。没有度量就没有改进,这句话在AI工程里尤其正确。
第四个坑是忽略成本。有一次跑实验忘了设max_tokens,模型生成了上万token,一天烧掉了几百块。从那以后我所有请求都设上限,并且加了成本告警。
这条路走下来,最大的体会是:AI工程不是纯技术问题,是工程思维和系统思维的结合。你得知道每个环节解决什么问题、边界在哪里、什么时候该引入什么工具。从零搭建的意义不在于什么都自己写,而在于你理解了整个系统的运作逻辑,遇到问题知道从哪里下手。这个能力,比会调几个API值钱得多。