
很多朋友问我ai engineering from scratch到底该怎么起步是不是必须先把手撕Transformer那套理论啃完。我自己的答案可能有点反直觉真正的“从零开始”不是从神经元开始推公式而是从一个空目录开始搭出一套能跑、能测、能迭代的AI系统。这篇文章就把我最近这段从零起步的完整经验写出来适合已经会写点代码、但对AI工程的整体框架还比较模糊的人。我不会讲那种“五天训练大模型”的神话只会讲一个普通工程师如何一步步把一个AI想法变成可运行、可评估、可改进的真实项目。内容里会穿插我的选型理由、踩坑记录和现在回头看的反思希望你能少走一点弯路。1. “从零开始”四个字很多人理解错了1.1 工程意义上的from scratch不是理论意义上的如果你去搜build a large language model from scratch搜出来的大多是三件事数据清洗、模型架构、分布式训练。这些内容很有价值但绝大多数想把AI落到业务里的人其实并不需要亲手训练一个千亿参数模型。这里的“from scratch”更多是指不依赖某个封装好的“一键式AI平台”而是从环境搭建、数据准备、模型接入、服务封装、测试评估这一整条链路里自己亲手控制每一个环节。我见过太多人卡在第一步他们觉得“从零开始”必须从神经网络的反向传播推起于是一头扎进数学推导几个月过去连一个能跑的API都没写出来。真正的AI工程起步是先让一个最小的系统转起来哪怕是调用一个开源小模型只要你能掌控输入输出、评估质量和迭代方向你就已经在做AI工程了。1.2 我的第一个最小闭环从“调用”开始说实话我自己的起点非常朴素。最初的项目是给内部团队做一个文档问答工具需求很简单上传几份PDF然后能问问题。当时我没有立刻去研究训练而是选了一个已经存在的开源小模型花半天时间把它跑起来再用FastAPI包成服务最后写了一个“答案是否靠谱”的人工评分脚本。这个闭环帮我确认了三件最重要的事模型能不能跑、延迟能不能忍、质量能不能评。很多项目死在“模型很强大但不知道怎么接入业务”或者“接入后发现输出完全不可控”就是因为跳过了最小闭环直接追求“完整工程”。所以我会建议每一个想从零开始的人先接受“不够完美但能运转”的第一版。1.3 AI工程和传统软件工程最不一样的地方传统软件工程里函数的输入输出是可预期的单元测试可以精确断言。但AI工程面对的是概率模型同样的输入可能返回不同的结果。这就意味着你不能用“写完后跑一次没问题”的心态来交付而是要建立一套持续观察、评测、校准的工作方式。从零开始做AI工程本质上是在建立三套能力一是让模型在业务场景里跑起来的能力二是设计提示词和数据结构来引导模型输出的能力三是用测试集和监控指标持续追踪模型表现的能力。这三套能力缺一不可缺了任何一个项目都会在后期变形走样。2. 动手前先解决三件事环境、数据、评估基线2.1 开发环境选型为什么我选Python uv DockerAI工程生态基本被Python统治这没什么好纠结的。真正需要纠结的是环境管理。我早期吃过太多乱七八糟的依赖冲突的亏后来固定使用uv来管理虚拟环境和依赖。uv比直接用pip快得多而且能很方便地锁定版本项目换机器、换同事一条命令就能把环境还原出来。Docker我建议从一开始就引入尤其是本地模型推理和线上服务要保持一致。很多人本地能跑部署到服务器上就崩多半是系统库、CUDA版本、Python版本对不上。我的做法是写一个Dockerfile把模型加载、API服务、依赖全部打进去本地开发用uv部署用镜像两边完全一致。2.2 数据从哪里来先用小规模干净数据跑通闭环很多教程一上来就让你上TB级数据集我强烈反对。从零开始的项目第一优先级是把流程跑通而不是把数据跑大。我常用的做法是手工构造一个小型“黄金数据集”比如50到100条典型的用户问题每条对应期望的回答要点。这个数据集用来做两件事验证模型在你场景里的基准表现以及后续每次改动后做回归测试。等基本流程稳定了再考虑扩充数据。扩充来源可以是公开数据集也可以是线上日志里的人工回流。但核心原则没变数据质量优先于数据规模。一屋子脏数据喂给再好的模型也是白搭。2.3 评估基线没有评测体系的AI工程都是自我感觉良好如果你问一个刚做完AI项目的人“效果怎么样”他可能会说“看起来还行”。这样的回答等于没说。从零开始做AI工程第一天就应该定义“怎么算好”。我习惯把评估分成两层第一层是硬性指标比如回答是否包含必要信息、格式是否符合要求、延迟是否可接受第二层是主观质量比如答案是否自然、是否有幻觉。对于主观质量我早期靠人工打分三个人独立打分后取平均。虽然成本高但在项目初期这套简单的基线让我对每次改动都有了明确判断到底是在变好还是变坏。后面再逐步引入自动化评测模型但人工基线永远是我最信任的标尺。3. 从空白目录到第一个AI服务一个最小可运行案例3.1 模型选型与本地推理的权衡当你真正开始搭服务时第一个问题就是模型用哪个。我建议从开源社区里选一个instruct类型的小规模对话模型比如参数在1B到3B之间的版本。这个量级在普通显卡或者CPU上都能跑起来方便你快速迭代。千万不要一上来就追求70B级别的模型那会让你把时间全花在部署和显存优化上而不是业务逻辑里。我这次的示例采用“本地模型FastAPI”的方式好处是数据不出内网、可控性强坏处是需要自己处理模型加载和并发。如果你对基础设施不熟悉也可以先用云端的模型API把流程跑通等确认业务可行后再优化成私有化部署。没有哪个选择是绝对正确的关键是匹配你当前的阶段。3.2 核心代码用FastAPI封装一个LLM推理服务下面这个示例是真实项目中很典型的骨架。我用Hugging Face Transformers加载模型FastAPI暴露HTTP接口。注意我刻意省略了并发控制、请求队列这些生产级细节因为第一步是先让请求能发出去、结果能收回来。from fastapi import FastAPI from pydantic import BaseModel from transformers import AutoModelForCausalLM, AutoTokenizer app FastAPI() class GenerateRequest(BaseModel): prompt: str max_new_tokens: int 128 class GenerateResponse(BaseModel): text: str model_name Qwen/Qwen2.5-1.5B-Instruct tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name) app.post(/generate, response_modelGenerateResponse) async def generate(req: GenerateRequest): messages [{role: user, content: req.prompt}] text tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue ) inputs tokenizer(text, return_tensorspt) outputs model.generate(**inputs, max_new_tokensreq.max_new_tokens) result tokenizer.decode(outputs[0], skip_special_tokensTrue) return GenerateResponse(textresult)这里有几个细节值得说apply_chat_template会把用户输入包成符合模型要求的对话格式这比直接把原始字符串丢给模型的效果好很多max_new_tokens要按你的业务场景调给得太大会让响应变慢太小又会让回答不完整。我在实际项目里通常会设成256然后根据平均回答长度再做调整。3.3 把模型的自由发挥关进“笼子”里跑通这个接口只是第一步接下来你会发现纯文本输出很难被业务吞掉。比如你想让模型返回一个JSON对象它可能给你夹带说明文字“好的以下是JSON{...}”。这些多余的文本对用户无所谓但对程序是灾难。我的做法是强制结构化输出。一种办法是在提示词里写清楚“只输出JSON不要解释”然后用Pydantic模型解析另一种更稳的办法是让模型输出固定格式比如用分隔符包裹JSON再在后处理里抽取。示例代码如下import json from pydantic import BaseModel class AnswerWithSource(BaseModel): answer: str source_doc: str def parse_model_output(raw: str) - AnswerWithSource: start raw.find([[) end raw.find(]]) if start -1 or end -1: raise ValueError(foutput format error: {raw}) payload raw[start 2:end].strip() return AnswerWithSource(**json.loads(payload))配合提示词模板模型就会老老实实输出类似[[{answer: ..., source_doc: xxx.pdf}]]的内容。这样做虽然多了一步解析但换来了可靠的程序可用性。我后来所有涉及业务流程的AI接口基本都走了“固定格式程序校验”这条路。4. 提示工程不是“写几句话”而是一套工程方法4.1 从zero-shot到chain-of-thought什么时候该用哪种提示工程在这个时代几乎成了AI工程的代名词。但很多人对它的理解停留在“措辞技巧”层面实际上它是一套可以系统化、版本化、测试化的工程方法。最基础的是zero-shot也就是不给例子直接问如果模型回答不符合预期就考虑few-shot给一两个典型例子如果任务涉及推理就要上chain-of-thought让模型把思考过程写出来。我自己的经验是能用结构化指令解决的问题不要堆例子。所谓“结构化指令”就是明确角色、任务、输入、输出格式、约束条件。比如让模型做摘要我会写“你是一个技术文档助手。请将下面的文本压缩成三条要点每条不超过20字。不要输出任何解释。”这比给一堆示例更高效。4.2 提示词也要做版本管理和回归测试你可能会觉得奇怪提示词不就是一段文字吗有什么好管理的。但当你开始迭代时就会发现提示词改两个字线上输出可能完全变样。所以我会把提示词模板放进一个单独的目录像代码一样用Git管理。每次改动都提交一次并且在CHANGELOG里记录“为什么改、改了什么、验证结果如何”。这样的好处是当某天线上评测分数突然下跌时你能快速定位是模型版本变了还是提示词变了。我有一次就是改了一个标点符号导致模型开始输出多余的解释因为当时没有版本回滚机制浪费了整整一个下午。从那以后提示词和代码地位平等谁也不能偷偷改。4.3 用代码约束输出函数调用与JSON Schema如果你接触过较新的模型会发现很多模型支持“函数调用”或“工具调用”本质上就是让模型输出一个结构化指令再由程序去执行。这种方式比让模型直接生成最终答案更可靠因为它把“决定做什么”和“执行什么”分开了。比如一个AI Agent先让模型决定“调用搜索还是调用计算器”然后程序执行完再把结果返回给模型由模型整合回答。在工程落地时即便模型不支持原生函数调用我也可以用JSON Schema做约束。具体做法是把输出结构定义成JSON Schema要求模型严格按Schema输出。虽说不一定能100%保证但配合后处理校验已经能覆盖绝大多数业务场景。说白了提示工程的目标不是让模型“更聪明”而是让模型“更听话”。5. 测试、评测与迭代AI工程的“日常搅拌”5.1 单元测试测什么契约、边界、幻觉传统开发者的第一反应可能是AI的输出不确定怎么测但只要你把“模型输出”和“程序逻辑”分开测试就变得清晰了。程序逻辑部分比如解析模型输出、检索文档、组装结果这部分完全可以写确定性单元测试。模型输出部分则做“契约测试”也就是验证输出格式是否合法、关键信息是否存在、数量是否在预期范围内。我经常写这样的断言输出解析后必须是一个合法JSON必须包含answer字段answer长度不得超过500字。这能防止模型“胡言乱语”导致程序崩溃。更麻烦的是幻觉模型可能流畅地编造出文档里没有的内容。我的处理办法是在业务里加一层“溯源校验”要求回答必须带上引用的文档片段程序再检查这个片段是否真的存在于文档库里不存在就判定为幻觉。5.2 自动化评测集建立你的小黄金集我之前提到过手工构造50到100条黄金数据这里再往深说一点。这套数据并不是一次性用完就扔的而是会长期保存在项目里每次模型版本升级、提示词改动、或者数据处理逻辑变化时都要跑一遍全部数据对比前后得分。评分可以先用简单的规则比如“参考答案中的关键词是否出现”“格式是否符合要求”再逐步引入大模型裁判。但最基础的人工抽查永远别丢。我每个迭代周期都会随机抽20条结果自己看一遍哪怕自动化指标没有下跌有些“味道不对”的东西只有人眼能发现。自动化测试保证不回归人工抽检保证有惊喜或惊吓能第一时间看到。5.3 线上监控与数据回流迭代的燃料项目上线后监控比开发更重要。我会记录每一次请求的输入、输出、延迟以及用户是否点了“有用”或“没用”按钮。这些反馈是迭代的燃料。每周我会把低分样本拉出来分析看它们集中在哪类问题。如果发现大量问题是“模型找不到答案”那可能不是提示词的问题而是检索数据缺失。数据回流之后下一步就是充实数据集、调整提示词、甚至微调模型。这一套循环跑起来之后AI工程才算是进入了正轨。我见过很多项目线上用着用着效果越来越差就是因为没有人为反馈闭环模型和业务一起“原地踏步”。6. 如果要从零训练一个模型路该怎么走6.1 先问问自己你真的需要从零训练吗尽管标题里有from scratch但我依然要说真正的“从零预训练大模型”对绝大多数团队是伪需求。原因很简单成本极高数据、算力、算法人才缺一不可。如果你想做的是一个垂直场景的AI产品利用开源基座模型做微调通常比从零训练划算得多。但如果你是想深入了解AI的工作原理或者确实有非常特殊的数据分布需求那么从零训练一个小规模模型是极好的学习路径。这里的关键是降低预期不要想着训练ChatGPT而是训练一个能学会“加法”或者“简单文本分类”的微型Transformer。它能让你完整体验从数据到模型的每个环节却不需要几百块GPU。6.2 用PyTorch训练一个微型Transformer的核心逻辑下面我给出一个极度简化但结构完整的训练骨架。它不做分布式、不做数据并行只为了展示核心逻辑数据采样、前向传播、计算损失、反向传播。import torch import torch.nn as nn from torch.nn import functional as F class MiniTransformer(nn.Module): def __init__(self, vocab_size, n_embd64, n_head4, n_layer2, block_size32): super().__init__() self.token_embedding nn.Embedding(vocab_size, n_embd) self.position_embedding nn.Embedding(block_size, n_embd) self.blocks nn.ModuleList([ nn.TransformerEncoderLayer(d_modeln_embd, nheadn_head) for _ in range(n_layer) ]) self.ln_f nn.LayerNorm(n_embd) self.lm_head nn.Linear(n_embd, vocab_size) def forward(self, idx): B, T idx.shape tok_emb self.token_embedding(idx) pos_emb self.position_embedding(torch.arange(T, deviceidx.device)) x tok_emb pos_emb for block in self.blocks: x block(x) logits self.lm_head(self.ln_f(x)) return logits这个模型非常小但它包含了一个语言模型的基本骨架词嵌入加位置嵌入经过多层Transformer编码器输出每个位置下一个token的预测概率。训练时只需要准备文本数据切成固定长度的序列然后算交叉熵损失。你在自己的笔记本上就能训练出“能模仿文本风格”的小模型虽然没什么实用价值但对理解自回归模型的生成原理帮助巨大。6.3 训练之后的部署比训练更考验工程能力训练完一个小模型之后你才算真正理解“部署”的麻烦。你要决定用CPU还是GPU要处理显存占用、推理速度、batch策略还要写接口、测试、错误处理。很多人在这一步才发现真正的AI工程不是“训练一个模型”而是“让模型在真实环境里稳定工作”。我个人的体会是如果你只是为了学习训练完小模型后把它做成一个简单的命令行工具就够了。如果是为了业务那就老老实实从开源模型加微调开始不要试图重复造轮子。所谓ai engineering from scratch最终会让你理解工程的重心不在“模型内部”而在“模型周围的一切”。7. 一些补充的实践建议和避坑记录7.1 不要被“AI热词”带偏方向现在各种新概念层出不穷什么AI Agent、Harness Engineering、Loop Engineering。我的建议是新概念可以了解但别急着追。先把基础链路做扎实等你真正遇到“多步任务编排”“循环反思”这类问题时自然知道这些概念解决的是什么。7.2 文档和代码要同步维护AI项目里模型版本和提示词版本经常一起变如果文档跟不上两个星期后你自己都会忘记当初为什么这么写。我现在每改一个提示词都会顺手把实际案例、评测结果截图放进项目的docs目录。这看起来笨重但非常管用。7.3 给自己留一个“可回滚”的备份点无论是模型还是提示词每次都可能会带来意想不到的“惊喜”。我的习惯是改动之前导出一份完整可运行的备份可以是Docker镜像标签也可以是Git分支。这样即使折腾失败了你还有一条路退回稳定状态。如果你也是从零开始闯进AI工程这条路的希望这份记录能让你少一点焦虑多一点抓手。我记得自己第一次跑通AI服务的那个晚上输出其实很粗糙但我兴奋是因为我终于看清了这条路到底长什么样。现在你也一样只要开始动手踩坑路就会慢慢清晰起来。