ARTICLE DETAIL

资讯详情

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

从零搭建AI工程体系:分层解耦与全链路实践指南

从零搭建AI工程体系:分层解耦与全链路实践指南 1. 从零搭建AI工程体系为什么我劝你别急着调库ai-engineering-from-scratch这个标题第一次看到的时候我愣了一下。市面上讲AI的文章十篇有八篇在教你pip install之后怎么调API剩下两篇在讲Transformer的数学推导。但真正从零把一套AI工程体系搭起来这件事反而很少有人系统性地讲清楚。我自己带过几个从零起步的AI项目也见过太多团队在能跑通demo和能上线服务之间反复横跳。这个标题背后要解决的核心问题其实很明确当你手上没有任何现成的AI基础设施时怎么一步步把数据、训练、评估、部署、监控这条链路完整地建起来。它适合那些不想只做调包侠、希望真正理解每一层在干什么的工程师也适合技术负责人用来规划团队的基础设施建设路径。这里说的from scratch不是让你从写矩阵乘法开始造轮子而是指从工程视角出发把AI系统的每个环节都自己掌控一遍。你最终可能还是会用PyTorch、用vLLM、用各种开源工具但你会清楚地知道每个工具在解决什么问题、为什么选它、不选它会怎样。这种掌控感是调库调不出来的。我打算按我自己实际搭过的一套流程来拆从目录结构设计一直讲到线上监控中间会穿插大量踩坑记录和参数选择的思考过程。你不需要全部照搬但每一块的决策逻辑你应该都能用得上。2. 整体架构设计与技术选型思路2.1 为什么我坚持分层解耦而不是一个脚本跑到底刚开始做AI项目的人最容易犯的错就是把所有逻辑塞进一个train.py或者一个Jupyter Notebook里。数据加载、模型定义、训练循环、评估指标、模型保存全混在一起改一行代码要跑半小时才能验证。这种写法在实验阶段勉强能用但一旦要复现实验、要换数据集、要部署上线就会变成灾难。我的做法是按职责分层每一层只做一件事层与层之间通过明确的接口通信。具体分成这么几层数据层负责原始数据的读取、清洗、切分、缓存输出统一的Dataset对象模型层只定义网络结构和前向传播不关心数据从哪来、训练怎么跑训练层负责优化器、学习率调度、梯度累积、混合精度、断点续训评估层独立的评估逻辑支持多种指标和训练过程解耦服务层模型加载、推理接口、批处理、并发控制监控层日志、指标采集、告警这么分的好处是当你想换一个模型结构时只需要动模型层当你想加一个新指标时只需要动评估层。每一层的改动范围可控测试也好写。注意分层不是目的可替换性才是。如果你分完层发现换任何一层都要改其他层的代码那说明接口设计有问题得回头重新划边界。2.2 工具选型每个选择背后的取舍工具选型这块我踩过的坑最多这里直接给结论和理由。深度学习框架选PyTorch。不是因为TensorFlow不好而是PyTorch的动态图在调试时太友好了。你可以在forward里直接print中间结果可以用pdb打断点这对从零搭建的人来说太重要了。TensorFlow的静态图在部署时确实有优势但现在PyTorch的TorchScript和ONNX导出也够用了。配置管理用Hydra或OmegaConf。不要用argparse堆几十个参数也不要用一个config.py硬编码。Hydra支持配置继承和命令行覆盖实验管理起来清爽很多。我试过用纯argparse管理一个30多个参数的项目后来加参数加到想砸键盘。实验追踪用MLflow或WandB。从零搭建最容易忽略的就是实验记录。你跑了20组超参数一周后完全不记得哪组对应哪个结果。MLflow可以本地部署WandB体验更好但需要联网。我一般用MLflow因为数据在自己手里踏实。服务框架用FastAPI。Flask也能用但FastAPI的异步支持和自动文档生成在AI服务场景下优势明显。推理服务经常需要处理并发请求异步能显著提升吞吐。推理加速用ONNX Runtime或TensorRT。PyTorch原生推理在小批量下还行但要做生产级部署ONNX Runtime的CPU推理和TensorRT的GPU推理都能带来数倍提升。具体选哪个看你的硬件和模型类型。下面这张表是我总结的选型对照你可以根据自己的场景调整环节推荐方案备选方案选择理由框架PyTorchTensorFlow动态图调试友好配置HydraOmegaConf支持继承和覆盖追踪MLflowWandB可本地部署服务FastAPIFlask异步自动文档推理ONNX RuntimeTensorRT跨平台/极致性能监控PrometheusGrafana自建生态成熟2.3 目录结构让项目自己说话一个好的目录结构新人进来半小时就能知道代码在哪、数据在哪、配置在哪。我常用的结构是这样的project/ ├── configs/ # 所有配置文件 │ ├── default.yaml │ ├── model/ │ └── experiment/ ├── data/ # 数据目录gitignore │ ├── raw/ │ ├── processed/ │ └── cache/ ├── src/ │ ├── data/ # 数据层 │ ├── models/ # 模型层 │ ├── training/ # 训练层 │ ├── evaluation/ # 评估层 │ ├── serving/ # 服务层 │ └── utils/ # 通用工具 ├── scripts/ # 入口脚本 ├── tests/ # 测试 ├── notebooks/ # 探索性分析 └── artifacts/ # 模型、日志等产出这个结构的关键在于src下面按层分目录和前面说的分层解耦对应。configs独立出来是因为配置会经常改混在代码里容易乱。artifacts单独放产出物方便清理和备份。实操心得data/raw目录设成只读所有处理后的数据写到data/processed。这样你永远不会因为一个bug把原始数据覆盖掉。我有个朋友就是因为脚本写错把标注了三个月的原始数据洗没了血的教训。3. 核心模块的细节实现与避坑指南3.1 数据管道别让IO成为训练瓶颈数据管道是AI工程里最容易被低估的部分。很多人模型调得飞起结果训练速度卡在数据加载上。我见过一个项目GPU利用率只有30%排查半天发现是DataLoader的num_workers设成了0数据加载在主进程里串行执行。数据管道的核心目标是让GPU永远不空转。要做到这一点你需要关注几个关键点第一预处理要离线做。能在训练前算好的东西绝对不要放在训练循环里。比如分词、归一化、特征工程这些都应该在数据准备阶段完成训练时只做轻量的张量转换。我一般会把处理好的数据存成内存映射格式如numpy的.npy或HDF5加载速度比读原始文件快一个数量级。第二合理设置num_workers。经验值是CPU核心数的70%到80%。比如16核的机器设12左右比较合适。设太高会导致进程切换开销设太低则数据供给不足。你可以通过监控GPU利用率来调整目标是让利用率稳定在90%以上。第三使用pin_memory和prefetch。pin_memoryTrue可以让数据从CPU内存到GPU显存的传输走更快的通道prefetch_factor控制每个worker预取的batch数量。这两个参数配合使用能显著减少数据传输的等待时间。from torch.utils.data import DataLoader loader DataLoader( dataset, batch_size64, shuffleTrue, num_workers12, pin_memoryTrue, prefetch_factor4, persistent_workersTrue, drop_lastTrue )persistent_workersTrue这个参数很多人不知道它能让worker进程在epoch之间保持存活避免每个epoch重新创建进程的开销。在数据量大的时候这个优化能省下不少时间。注意num_workers大于0时Dataset里不要做任何有状态的操作因为每个worker是独立进程状态不共享。我踩过这个坑在Dataset里做随机增强结果每个worker的随机种子一样增强效果完全重复。3.2 训练循环那些教科书不会告诉你的细节训练循环看起来简单无非是前向、反向、更新三步。但真正要跑得稳、跑得快里面门道不少。梯度累积是显存不够时的救命稻草。当你的batch size受限于显存时可以通过累积多个小batch的梯度再更新达到等效大batch的效果。实现上就是在backward之后不立即step而是累积若干次后再step并清零。accumulation_steps 4 optimizer.zero_grad() for i, batch in enumerate(loader): outputs model(batch) loss criterion(outputs, batch.target) / accumulation_steps loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()注意loss要除以accumulation_steps否则梯度会放大对应倍数。这个细节很多人第一次写会漏掉导致训练不稳定。混合精度训练AMP能省显存、提速但要用对。PyTorch的torch.cuda.amp提供了自动混合精度核心是autocast和GradScaler配合使用。autocast自动把部分运算转成float16GradScaler负责防止梯度下溢。from torch.cuda.amp import autocast, GradScaler scaler GradScaler() for batch in loader: optimizer.zero_grad() with autocast(): outputs model(batch) loss criterion(outputs, batch.target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()实操心得混合精度不是万能的。某些模型特别是涉及大量小数值运算的用float16会掉点这时候可以只对部分层用autocast或者干脆用bfloat16如果硬件支持。我一般会先跑一个baseline再跑一个AMP版本对比验证集指标掉点超过0.5%就放弃AMP。学习率调度的策略选择也很关键。CosineAnnealing适合大多数场景OneCycle在训练轮数固定时收敛更快ReduceLROnPlateau适合不确定训练多久的情况。我个人的默认选择是warmup加cosine衰减warmup步数设为总步数的5%到10%。断点续训是必须有的功能。训练到一半机器挂了、被抢占、或者你发现超参不对想调整没有断点续训就得从头再来。保存的内容至少包括模型参数、优化器状态、学习率调度器状态、当前epoch和step、随机数种子状态。少存一样续训结果就可能和连续训练不一致。3.3 评估体系别只看一个指标从零搭建AI系统时评估体系往往是最敷衍的部分。很多人就打印一个准确率或者loss然后凭感觉判断模型好坏。这种做法在简单任务上勉强能用但一旦任务复杂一点就会出问题。评估指标要分层。以分类任务为例至少要看这几层整体指标准确率、F1、AUC反映模型整体表现类别指标每个类别的精确率、召回率发现类别不平衡问题混淆矩阵看模型在哪些类别之间混淆指导后续优化置信度分布看模型预测的置信度是否合理判断是否需要校准评估集要独立且固定。不要用训练集评估也不要在评估集上反复调参。我一般会切三份训练集、验证集、测试集。验证集用于调参和早停测试集只在最终确定模型时用一次。如果数据量小至少也要保证验证集和训练集不重叠。评估要可复现。同样的模型、同样的数据跑两次评估结果应该完全一致。这意味着评估时要固定随机种子、关闭dropout、使用确定性算法。PyTorch里可以设置torch.use_deterministic_algorithms(True)但注意这可能会降低性能。def evaluate(model, loader, device): model.eval() all_preds [] all_labels [] with torch.no_grad(): for batch in loader: inputs batch.inputs.to(device) labels batch.labels.to(device) outputs model(inputs) preds outputs.argmax(dim-1) all_preds.append(preds.cpu()) all_labels.append(labels.cpu()) all_preds torch.cat(all_preds) all_labels torch.cat(all_labels) return compute_metrics(all_preds, all_labels)注意model.eval()和torch.no_grad()要同时用。前者切换dropout和batchnorm的行为后者关闭梯度计算省显存。只用一个都可能出问题。3.4 模型服务从实验室到生产的关键一跃模型训练好了怎么让其他系统用上这是从零搭建AI工程最容易被忽视的一环。我见过太多项目模型在notebook里跑得挺好一到要提供服务就各种问题。服务化的第一步是模型导出。不要直接在服务里加载训练时的checkpoint那个文件包含优化器状态等一堆推理用不到的东西又大又慢。正确的做法是导出成推理专用格式TorchScriptPyTorch原生torch.jit.script或torch.jit.trace导出加载快ONNX跨框架标准配合ONNX Runtime推理CPU上性能好TensorRTNVIDIA专用GPU上性能极致但导出麻烦我一般先用ONNX导出做CPU服务如果性能不够再上TensorRT。ONNX导出的坑主要是动态维度处理需要在导出时指定dynamic_axes。torch.onnx.export( model, dummy_input, model.onnx, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size, 1: seq_len}, output: {0: batch_size} }, opset_version13 )服务接口设计要考虑几个问题批处理、超时、限流、版本管理。批处理能显著提升吞吐但会增加延迟需要根据业务场景权衡。超时和限流是保护服务不被拖垮的必要手段。版本管理让你能灰度发布新模型出问题快速回滚。FastAPI的服务骨架大概长这样from fastapi import FastAPI from pydantic import BaseModel import onnxruntime as ort app FastAPI() session ort.InferenceSession(model.onnx) class Request(BaseModel): text: str class Response(BaseModel): label: str confidence: float app.post(/predict, response_modelResponse) async def predict(req: Request): inputs preprocess(req.text) outputs session.run(None, {input: inputs}) label, confidence postprocess(outputs) return Response(labellabel, confidenceconfidence)实操心得服务启动时加载模型不要每次请求都加载。模型加载是IO密集型操作放在请求路径里会让延迟爆炸。另外ONNX Runtime的session不是线程安全的多线程环境下要么加锁要么每个线程一个session。我一般用intra_op_num_threads控制单session的线程数然后起多个worker进程。4. 实操全流程从空目录到可服务系统4.1 环境准备与依赖锁定从零开始的第一步是环境。我强烈建议用conda或者venv创建独立环境不要用系统Python。依赖冲突是AI项目最常见的环境问题隔离环境能省掉大量排查时间。conda create -n ai-eng python3.10 conda activate ai-eng pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install hydra-core mlflow fastapi uvicorn onnx onnxruntime依赖锁定是很多人忽略的一步。pip freeze requirements.txt只能锁定直接依赖间接依赖的版本可能在不同时间安装出不同结果。更好的做法是用pip-compile来自pip-tools生成完全锁定的依赖文件或者用poetry管理。我一般会维护两个文件requirements.in写直接依赖requirements.txt由pip-compile生成包含所有间接依赖的精确版本。这样在任何机器上pip install -r requirements.txt都能得到完全一致的环境。注意CUDA版本和PyTorch版本要匹配。装之前先nvidia-smi看驱动支持的CUDA版本然后去PyTorch官网查对应的安装命令。装错了会出现CUDA error: no kernel image is available这类让人抓狂的错误。4.2 数据准备从原始文件到训练就绪假设你手上是一堆原始文本文件需要经过清洗、分词、切分、缓存四步才能用于训练。清洗的目标是去掉噪声HTML标签、特殊字符、重复内容、过短或过长的样本。这一步没有标准答案取决于你的数据特点。我一般会先抽样看100条统计一下长度分布和字符分布再决定清洗规则。分词如果用预训练模型直接用对应的tokenizer。如果从零训练需要先训练一个tokenizer比如用sentencepiece或tokenizers库。分词后的结果存成整数ID序列训练时直接查表。切分要注意几点训练集、验证集、测试集的比例一般是8:1:1或7:1:2切分前要打乱避免数据本身有序导致分布偏移如果数据有时间属性要按时间切分而不是随机切分。缓存是把处理好的数据存成内存映射格式。我一般用numpy的.npy或者HDF5。HDF5的好处是支持部分读取适合超大数据集numpy简单直接适合能全部放进内存的场景。import numpy as np # 保存 np.save(data/cache/train_inputs.npy, train_inputs) np.save(data/cache/train_labels.npy, train_labels) # 加载内存映射模式不占内存 train_inputs np.load(data/cache/train_inputs.npy, mmap_moder)mmap_moder让numpy以内存映射方式加载数据还在磁盘上访问时才读入内存。这样即使数据集比内存大也能用。4.3 训练启动与过程监控训练启动脚本我一般用Hydra管理配置命令行可以覆盖任何参数python scripts/train.py \ modeltransformer \ model.hidden_size512 \ training.batch_size64 \ training.lr3e-4 \ training.epochs20训练过程中要监控的指标分两类性能指标和资源指标。性能指标包括loss、学习率、梯度范数、验证集指标资源指标包括GPU利用率、显存占用、数据加载时间、每秒处理样本数。我一般用MLflow记录性能指标用nvidia-smi或pynvml采集资源指标。关键是要能实时看到而不是训练完才分析。如果发现loss不降或者梯度爆炸能及时中断调整。import mlflow from pynvml import nvmlInit, nvmlDeviceGetHandleByIndex, nvmlDeviceGetUtilizationRates nvmlInit() handle nvmlDeviceGetHandleByIndex(0) for epoch in range(epochs): for step, batch in enumerate(loader): # 训练步骤... if step % log_interval 0: util nvmlDeviceGetUtilizationRates(handle) mlflow.log_metrics({ loss: loss.item(), lr: scheduler.get_last_lr()[0], gpu_util: util.gpu, grad_norm: grad_norm }, stepglobal_step)梯度范数是个很重要的监控指标。如果梯度范数突然变大说明可能遇到了异常样本或者学习率太高如果一直很小说明可能梯度消失。我一般会设一个阈值超过就裁剪gradient clipping防止梯度爆炸。4.4 模型导出与服务部署训练完成后选验证集上最好的checkpoint导出。导出前先加载checkpoint切换到eval模式然后用dummy input跑一遍确认输出正确。model.load_state_dict(torch.load(artifacts/best.pt)) model.eval() dummy_input torch.randn(1, max_seq_len, hidden_size) with torch.no_grad(): output model(dummy_input) print(output.shape) torch.onnx.export(model, dummy_input, artifacts/model.onnx, ...)服务部署我一般用Docker容器把模型文件和服务代码一起打包。Dockerfile大概这样FROM python:3.10-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY artifacts/model.onnx . COPY src/serving/ . EXPOSE 8000 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000, --workers, 4]--workers 4启动4个worker进程配合ONNX Runtime的线程设置能充分利用多核CPU。GPU场景下一般一个worker就够因为GPU本身是并行的。实操心得容器里跑GPU服务需要nvidia-docker或者--gpus all参数。另外模型文件如果很大构建镜像时COPY会很慢可以考虑用volume挂载或者从对象存储下载。我一般把模型放volume镜像只包含代码这样更新模型不用重新构建镜像。5. 常见问题排查与独家避坑技巧5.1 训练不收敛的排查路径训练不收敛是最常见也最让人头疼的问题。我一般按这个顺序排查第一步检查数据。把batch里的样本打印出来看看标签对不对、输入有没有异常值。我遇到过标签全错位的情况查了一天才发现是数据加载时shuffle和标签没同步。第二步检查loss。用一个极小的数据集比如10条过拟合如果loss降不下去说明模型或loss函数有问题。如果小数据集能过拟合但全量数据不行说明是优化或正则化的问题。第三步检查学习率。学习率太大会震荡太小会收敛慢。可以跑一个学习率扫描从1e-6到1e-1看哪个区间loss下降最快。第四步检查梯度。打印每层的梯度范数看有没有梯度消失或爆炸。如果有考虑加batchnorm、换激活函数、用梯度裁剪。下面这张表是我总结的常见现象和对应原因现象可能原因排查方法loss不降学习率太小/数据有问题学习率扫描/检查数据loss震荡学习率太大/batch太小降低学习率/增大batchloss变NaN梯度爆炸/除零梯度裁剪/检查loss函数训练好验证差过拟合加正则/早停/增数据训练差验证好数据泄露检查切分逻辑5.2 显存不足的应对策略显存不足是GPU训练的常见瓶颈。解决思路按优先级排第一减小batch size。这是最直接的方法但可能影响训练稳定性。配合梯度累积可以缓解。第二用混合精度。float16比float32省一半显存配合GradScaler基本不掉点。第三梯度检查点。用时间换空间在前向时只保存部分中间结果反向时重新计算。PyTorch的torch.utils.checkpoint可以一行代码启用。第四模型并行或ZeRO。模型大到单卡放不下时需要把模型切分到多卡。DeepSpeed的ZeRO系列是成熟方案但配置复杂建议先用前三种方法。from torch.utils.checkpoint import checkpoint class Model(nn.Module): def forward(self, x): x checkpoint(self.layer1, x) x checkpoint(self.layer2, x) return x注意梯度检查点会增加约30%的计算时间但能省下大量显存。只在显存瓶颈时用不要无脑开。5.3 服务性能优化的几个抓手服务上线后如果延迟高、吞吐低可以从这几个方向优化批处理把多个请求攒成一个batch一起推理能显著提升吞吐。但会增加延迟需要根据业务SLA权衡。我一般设一个最大等待时间比如10ms攒够或超时就走一次推理。模型量化把float32转成int8模型体积缩小4倍推理速度提升2到3倍精度损失通常在1%以内。ONNX Runtime和TensorRT都支持量化。缓存对于重复的输入直接返回缓存结果。在推荐、搜索这类场景下缓存命中率往往很高。异步FastAPI的async接口配合异步推理能在等待IO时处理其他请求。但注意ONNX Runtime的run是同步的要用线程池包装。from concurrent.futures import ThreadPoolExecutor executor ThreadPoolExecutor(max_workers4) app.post(/predict) async def predict(req: Request): loop asyncio.get_event_loop() result await loop.run_in_executor(executor, inference, req.text) return result5.4 我踩过的几个印象深刻的坑坑一随机种子没固定全。PyTorch、numpy、Python内置random各有各的种子只固定一个不够。而且DataLoader的worker进程种子也要单独设。我写了个set_seed函数把所有能设的都设上才做到完全可复现。坑二验证集指标比训练集高。一开始以为模型泛化好后来发现是验证集的dropout没关。model.eval()忘了调dropout在验证时还在随机丢弃反而起了集成效果。这个坑很隐蔽因为指标看起来更好。坑三ONNX导出后结果对不上。PyTorch和ONNX的某些算子实现有细微差异特别是涉及动态shape和padding的时候。导出后一定要用同样的输入对比两边输出差异超过1e-4就要查。坑四服务内存泄漏。推理服务跑一段时间后内存持续增长最后OOM。排查发现是每次请求都创建了新的numpy数组没有及时释放。Python的GC对这种大对象回收不及时需要手动del或者用对象池。坑五Docker里GPU用不了。镜像里装了CUDA版的PyTorch但运行时没加--gpus all结果fallback到CPU性能差了几十倍。这个错误很隐蔽因为服务能跑只是慢。6. 监控体系与持续迭代6.1 线上监控要盯哪些指标模型上线不是终点而是起点。线上监控至少要覆盖三个层面服务层QPS、延迟分布P50/P95/P99、错误率、超时率。这些指标反映服务是否健康。我一般用Prometheus采集Grafana展示超过阈值就告警。模型层输入分布、输出分布、置信度分布。输入分布偏移data drift是模型效果下降的主要原因。可以通过统计输入特征的均值、方差、分位数来监控和训练集对比。业务层点击率、转化率、用户反馈。这些是最终衡量模型价值的指标。模型指标好但业务指标差的情况不少见可能是离线评估和线上目标不一致。from prometheus_client import Histogram, Counter inference_latency Histogram(inference_latency_seconds, Inference latency) request_count Counter(request_total, Total requests, [status]) app.post(/predict) async def predict(req: Request): with inference_latency.time(): try: result inference(req.text) request_count.labels(statussuccess).inc() return result except Exception: request_count.labels(statuserror).inc() raise6.2 模型迭代的节奏把控模型迭代不是越频繁越好。每次更新都有风险需要权衡收益和成本。我一般按这个节奏紧急修复线上出严重问题比如模型输出明显错误立即回滚到上一个版本然后离线排查。小版本迭代积累了一定量的新数据或者发现了明显的优化点每周或每两周更新一次。更新前在影子模式跑一段时间对比新旧模型输出。大版本迭代模型结构变更、训练方法改进每月或每季度一次。需要完整的离线评估和A/B测试。影子模式是个很实用的技巧新模型上线但不接流量只是把请求复制一份给新模型记录它的输出。对比新旧模型的差异确认新模型没有异常后再切流量。实操心得模型版本要可追溯。每次训练的配置、数据版本、代码commit、评估结果都要记录。出问题时能快速定位是哪个环节的变化导致的。我一般用MLflow的run来关联这些信息模型文件里也嵌入版本号。6.3 数据闭环的建立从零搭建的AI系统最终要形成数据闭环线上服务产生新数据新数据经过标注进入训练集训练出的新模型再上线。这个闭环转起来模型才能持续进化。闭环的关键是数据回流和标注。服务端要记录每次请求的输入和输出特别是低置信度的样本和用户反馈的样本。这些数据最有价值标注后加入训练集能显著提升模型。标注环节要考虑成本和质量的平衡。全量标注太贵我一般用主动学习策略选模型最不确定的样本、选多样性最高的样本、选对业务影响最大的样本。这样用最少的标注量获得最大的提升。数据回流还要注意隐私和合规。用户数据要脱敏敏感信息不能进训练集。这个环节要有明确的流程和检查不能靠自觉。7. 一些关于从零的真心话写了这么多最后说几句掏心窝的话。ai-engineering-from-scratch这件事最大的价值不在于你用了什么高级工具而在于你对每个环节都有掌控力。你知道数据从哪来、经过了什么处理、模型为什么这么设计、服务怎么扛住流量、出问题怎么排查。这种掌控力是调包调不出来的也是AI工程师和AI调包侠的分水岭。我自己从零搭过几套系统最大的体会是慢就是快。前期在架构设计、接口定义、测试覆盖上多花的时间后期会以数倍的效率还回来。反过来前期图快堆出来的代码后期维护和扩展的成本会高得吓人。还有一点不要追求一步到位。从零搭建不是一次把所有东西都建好而是先跑通最小闭环然后逐步完善。先有一个能训练、能评估、能服务的简单版本再逐步加监控、加优化、加自动化。每一步都验证过再往下走比一口气搭个大框架然后发现方向错了要稳妥得多。这套流程我用了几年也在不同项目上调整过很多次。你不需要完全照搬但希望里面的决策逻辑和踩坑记录能帮你少走些弯路。真正动手搭一遍比看十篇文章都有用。
返回列表