ARTICLE DETAIL

资讯详情

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

从零搭建AI工程能力:告别调包侠,掌握模型训练与部署实战

从零搭建AI工程能力:告别调包侠,掌握模型训练与部署实战 1. 从零搭建AI工程能力为什么我劝你别再当“调包侠”这两年AI岗位的招聘需求翻了何止三倍但真正能扛住面试官追问的人少得可怜。我面过不下五十个候选人简历上清一色写着“熟悉PyTorch、TensorFlow做过图像分类和文本生成”结果一问到“模型推理延迟怎么优化”“显存不够怎么切分”“训练崩了怎么定位”能答上来的人不到两成。这就是典型的“调包侠”困境——会跑demo但不懂工程。ai-engineering-from-scratch这个项目标题说白了就是冲着解决这个问题来的。它不是教你从零推导反向传播公式也不是让你手撸一个Transformer而是帮你补齐从“能跑通notebook”到“能上线服务”之间那条巨大的鸿沟。这条鸿沟里藏着的东西才是AI工程师真正值钱的地方数据管道怎么搭、训练怎么加速、模型怎么压缩、服务怎么部署、监控怎么做、成本怎么控。我写这篇东西是想把我自己从算法转工程那几年踩过的坑、翻过的文档、熬过的夜浓缩成一套可复现的路径。适合谁看如果你是刚入行的算法同学想补工程能力如果你是后端转AI想理解模型侧的特殊性甚至如果你是带团队的技术负责人想给组里搭一套从零开始的培养体系——这篇都能直接抄作业。我不讲虚的每个环节都给具体工具、具体参数、具体命令你跟着做就能跑起来。2. 整体设计思路为什么“从零”不等于“从轮子造起”2.1 核心矛盾学术思维与工程思维的冲突学术界追求的是SOTA是刷榜是在干净数据集上把指标提高0.5个点。工程界追求的是稳定、可维护、低成本、可扩展。这两套思维模式经常打架。我见过太多算法同学模型在Jupyter里跑得飞起一上生产就各种崩内存泄漏、GPU利用率不到30%、请求一多就超时、日志里全是warning但没人知道什么意思。ai-engineering-from-scratch的设计思路核心就是帮你完成这个思维转换。它不是让你重新发明矩阵乘法而是让你理解每一层抽象下面发生了什么。比如你用model.fit()一行代码训练但你知道底层数据是怎么batch的、梯度是怎么累积的、显存是怎么分配的、checkpoint是怎么存的吗不知道这些出了问题就只能靠重启。我的方案是用最小可用的自研组件替换掉框架里的黑盒模块让你亲手摸一遍每个环节的输入输出。不是让你抛弃PyTorch而是在PyTorch的基础上自己写DataLoader、自己写训练循环、自己写推理服务、自己写监控埋点。写完之后你再回头看框架的封装就知道它帮你做了什么、代价是什么、什么时候该用、什么时候该绕开。2.2 技术选型为什么是Python PyTorch FastAPI Docker这套组合是我试过最顺手的。Python不用解释AI领域的通用语言。PyTorch的动态图机制对调试友好而且生态里从训练到部署的工具链最完整。FastAPI做推理服务异步性能好自动生成OpenAPI文档省去写接口文档的功夫。Docker做环境隔离保证从开发到生产的一致性。但选型背后有更细的考量。比如为什么不用TensorFlow Serving因为TF的部署链路太重改个模型要重新导出SavedModel调试成本高。为什么不用TritonTriton确实强但学习曲线陡对新手不友好而且很多小团队根本用不上那么复杂的推理优化。为什么不用FlaskFlask是同步框架并发一高就排队FastAPI的async/await能轻松扛住几百QPS。再比如数据管道为什么不用Spark因为大部分AI团队的数据量根本没到需要Spark的级别。几百万条数据Pandas PyArrow Dask完全够用而且调试方便。上Spark反而增加运维负担。这些取舍我都会在后面的章节里详细展开。2.3 分层架构从数据到服务的五层模型我把整个AI工程能力拆成五层从下往上依次是数据层数据采集、清洗、标注、版本管理、特征存储训练层分布式训练、混合精度、梯度累积、checkpoint管理、超参搜索优化层量化、剪枝、蒸馏、ONNX导出、TensorRT加速服务层模型封装、API设计、批处理、缓存、限流、灰度发布监控层指标采集、日志聚合、告警、A/B测试、数据漂移检测每一层都有独立的工具链和最佳实践但层与层之间的接口设计才是关键。比如训练层输出的checkpoint格式直接决定了优化层能做什么操作优化层导出的模型格式又决定了服务层能用什么推理引擎。这些接口如果一开始没设计好后面改起来就是牵一发动全身。我建议的学习路径是自下而上但实际搭建时可以先从服务层倒推。先想清楚线上要什么再决定训练怎么搞、数据怎么准备。这种“以终为始”的思路能帮你避免很多无用功。3. 核心细节解析每个环节的深水区在哪里3.1 数据管道别让脏数据毁了你三个月的训练数据这块我踩过最大的坑就是没有做数据版本管理。有一次训练了一个月发现效果不对想回滚到之前的版本结果发现数据已经被覆盖了连原始文件都找不回来。从那以后我强制要求所有数据集必须用DVC或者Git LFS做版本控制每次训练必须记录数据集的commit hash。具体怎么做首先原始数据永远不动放在/data/raw目录下只读权限。清洗后的数据放/data/processed每次清洗脚本运行都生成新的版本号。特征工程后的数据放/data/features同样带版本。训练时配置文件里必须写清楚用的哪个版本的数据训练日志里也要打印出来。数据清洗的坑更多。比如缺失值处理很多人直接dropna()结果把正样本全删了。正确的做法是先分析缺失模式是随机缺失还是非随机缺失。如果是非随机缺失可能缺失本身就是一个特征。再比如类别不平衡直接上SMOTE容易过拟合我一般先用class weight不行再考虑focal loss最后才用重采样。还有一个容易被忽略的点数据泄漏。我见过一个项目特征里包含了未来信息离线AUC 0.95上线后直接掉到0.6。排查了一周才发现某个特征的计算用到了未来时间窗口的数据。这种问题在时序数据里特别常见必须用时间序列交叉验证不能随机划分。注意数据管道的每个环节都要有断言检查。比如清洗后样本量不能少于原始数据的80%特征分布不能偏移超过阈值标签比例不能突变。这些检查写进CI/CD每次数据更新自动跑。3.2 训练加速从三天一轮到三小时一轮的实战记录训练慢是新手最头疼的问题。我刚开始做图像分类时一个epoch要跑六个小时调一次参等一周。后来一步步优化同样的模型和数据一个epoch压到了二十分钟。这里面的技巧我按收益从高到低排第一数据加载是最大的瓶颈没有之一。很多人GPU利用率上不去以为是模型太复杂其实是DataLoader在拖后腿。解决方案用num_workers开多进程一般设成CPU核数用pin_memoryTrue加速CPU到GPU的传输用prefetch_factor预取数据如果数据在机械硬盘上先拷贝到SSD或者内存盘。我实测过光是把num_workers从0改成8训练速度就翻了四倍。第二混合精度训练AMP几乎是无脑开。PyTorch的torch.cuda.amp一行代码就能启用显存占用直接减半速度提升30%到50%。但要注意某些操作在FP16下会溢出需要用GradScaler做梯度缩放。还有BN层通常要保持FP32不然统计量会飘。第三梯度累积解决小显存跑大batch的问题。比如你想用batch size 256但显存只够32那就累积8步再更新一次参数。注意梯度累积时BN的统计量还是按32算的如果对BN敏感要换成GroupNorm或者SyncBN。第四分布式训练。单卡不够就上多卡。PyTorch的DDP比DP快得多因为DP是单进程多线程有GIL锁DDP是多进程每个进程独立跑。DDP的坑在于每个epoch要调用sampler.set_epoch(epoch)不然数据顺序会重复。还有保存checkpoint时只在rank 0上存不然会写冲突。第五梯度检查点Gradient Checkpointing。用时间换空间显存不够时的救命稻草。原理是不保存中间激活值反向传播时重新计算。代价是训练速度慢20%左右但显存能省60%以上。Transformer类模型必备。这些技巧叠加起来效果是惊人的。我做过一个对比实验ResNet50在ImageNet子集上训练基线是6小时/epoch优化后是22分钟/epoch加速比超过16倍。具体配置我整理成表格优化项配置加速比显存变化基线num_workers0, FP32, batch321x基准多进程加载num_workers8, pin_memoryTrue4.2x不变混合精度AMP GradScaler1.4x-45%梯度累积accum_steps8, batch321.0x不变DDP多卡4卡DDP3.8x每卡不变梯度检查点对layer2-4启用0.8x-60%综合全部启用16.3x-70%实操心得优化顺序很重要。先解决数据加载再上AMP然后DDP最后才考虑梯度检查点。因为梯度检查点会拖慢速度只在显存实在不够时用。3.3 模型优化量化、剪枝、蒸馏到底该选哪个模型优化这块很多人一上来就问“怎么压缩到1MB”但不知道自己的场景适合什么方案。我一般先问三个问题延迟要求多少精度能掉多少硬件支持什么指令集量化是最通用的方案。PyTorch支持动态量化、静态量化和QAT量化感知训练。动态量化最简单一行代码但只对LSTM和Linear层有效。静态量化需要校准数据精度损失更小。QAT最麻烦要在训练时插入伪量化节点但精度几乎无损。我一般推荐如果模型主要是卷积用静态量化如果是Transformer用动态量化如果精度要求极高上QAT。剪枝分非结构化剪枝和结构化剪枝。非结构化剪枝把不重要的权重置零压缩率高但需要稀疏计算库支持实际加速有限。结构化剪枝直接砍掉整个通道或层硬件友好但精度损失大。我试过对ResNet做通道剪枝剪掉30%的通道精度掉1.2个点推理速度提升1.8倍。剪枝后要fine-tune不然精度回不来。蒸馏是用大模型教小模型。关键是设计好损失函数硬标签损失 软标签损失 中间层特征损失。温度参数T很关键T越大软标签越平滑一般设3到5。蒸馏适合你有大模型但部署不了、只能用小模型的场景。我做过BERT到BiLSTM的蒸馏精度保留了97%推理速度快了20倍。选型建议如果只是想让模型跑快一点先试量化如果模型太大存不下试剪枝如果精度要求高且有大模型可用试蒸馏。三者可以叠加但顺序一般是先剪枝再量化蒸馏单独用。3.4 服务部署从Flask到FastAPI再到Triton的演进推理服务这块我经历了三个阶段。第一阶段用Flask写个/predict接口接收JSON返回结果。简单是简单但并发一高就崩。Flask默认是同步的一个请求处理完才能处理下一个QPS上不去。第二阶段换FastAPI用async def定义接口配合uvicorn多worker启动。性能提升明显单机QPS从50涨到300。但问题来了模型推理是CPU/GPU密集型的async并不能真正并行只是让IO不阻塞。真正的并发还是要靠多进程。第三阶段上Triton Inference Server。Triton支持动态批处理、模型集成、多框架后端而且有完善的监控指标。配置稍微复杂要写config.pbtxt但一旦跑起来QPS能到几千。Triton的核心优势是动态批处理多个请求自动合并成一个batch推理GPU利用率大幅提升。我现在的标准架构是FastAPI做前置网关负责鉴权、限流、参数校验Triton做推理后端负责模型加载和批处理Redis做结果缓存相同输入直接返回Prometheus Grafana做监控。这套架构支撑过日均千万级请求稳定运行一年多。注意模型加载时要注意冷启动问题。Triton支持模型预热在config.pbtxt里配置warmup服务启动时先跑几次推理避免第一个请求超时。4. 实操过程从零搭建一个完整的AI工程流水线4.1 环境准备与项目骨架先建目录结构。我习惯这样组织ai-engineering-from-scratch/ ├── data/ │ ├── raw/ │ ├── processed/ │ └── features/ ├── src/ │ ├── data/ │ │ ├── dataset.py │ │ └── preprocess.py │ ├── models/ │ │ ├── model.py │ │ └── layers.py │ ├── train/ │ │ ├── trainer.py │ │ └── metrics.py │ ├── optimize/ │ │ ├── quantize.py │ │ └── prune.py │ └── serve/ │ ├── app.py │ └── triton_config/ ├── configs/ │ ├── train.yaml │ └── serve.yaml ├── tests/ ├── Dockerfile ├── requirements.txt └── README.md环境用conda管理Python 3.9以上。核心依赖torch2.0、pytorch-lightning可选但我喜欢自己写训练循环、fastapi、uvicorn、onnx、onnxruntime、dvc、mlflow。Docker基础镜像用nvidia/cuda:11.8-cudnn8-runtime别用devel太大。4.2 数据管道搭建从原始CSV到训练可用的DataLoader假设我们有一个图像分类任务原始数据是data/raw/images/下的jpg文件和labels.csv。第一步写preprocess.pyimport pandas as pd from sklearn.model_selection import train_test_split from pathlib import Path import hashlib def create_splits(raw_dir, output_dir, test_size0.2, val_size0.1, seed42): df pd.read_csv(Path(raw_dir) / labels.csv) # 检查缺失 assert df.isnull().sum().sum() 0, 存在缺失值 # 检查类别分布 class_counts df[label].value_counts() print(f类别分布:\n{class_counts}) # 分层划分 train_val, test train_test_split( df, test_sizetest_size, stratifydf[label], random_stateseed ) train, val train_test_split( train_val, test_sizeval_size/(1-test_size), stratifytrain_val[label], random_stateseed ) # 保存 output_dir Path(output_dir) output_dir.mkdir(parentsTrue, exist_okTrue) train.to_csv(output_dir / train.csv, indexFalse) val.to_csv(output_dir / val.csv, indexFalse) test.to_csv(output_dir / test.csv, indexFalse) # 计算数据指纹 fingerprint hashlib.md5( pd.concat([train, val, test]).to_json().encode() ).hexdigest() print(f数据指纹: {fingerprint}) return fingerprint然后写dataset.py自定义Dataset类import torch from torch.utils.data import Dataset, DataLoader from PIL import Image import albumentations as A from albumentations.pytorch import ToTensorV2 class ImageDataset(Dataset): def __init__(self, csv_file, img_dir, transformNone): self.df pd.read_csv(csv_file) self.img_dir Path(img_dir) self.transform transform def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] img_path self.img_dir / row[filename] image np.array(Image.open(img_path).convert(RGB)) label row[label] if self.transform: image self.transform(imageimage)[image] return image, label def get_dataloader(csv_file, img_dir, batch_size, is_trainTrue, num_workers8): if is_train: transform A.Compose([ A.RandomResizedCrop(224, 224), A.HorizontalFlip(p0.5), A.ColorJitter(p0.2), A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ToTensorV2() ]) else: transform A.Compose([ A.Resize(256, 256), A.CenterCrop(224, 224), A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ToTensorV2() ]) dataset ImageDataset(csv_file, img_dir, transform) return DataLoader( dataset, batch_sizebatch_size, shuffleis_train, num_workersnum_workers, pin_memoryTrue, prefetch_factor2, persistent_workersTrue )这里有几个细节persistent_workersTrue避免每个epoch重新启动worker进程prefetch_factor2预取两个batchpin_memoryTrue加速传输。实测下来这些配置能让GPU利用率从40%提到85%以上。4.3 训练循环手写为什么我不推荐用TrainerPyTorch Lightning的Trainer确实方便但封装太厚出了问题不好排查。我建议自己写训练循环代码量不大但可控性极强。核心结构def train_one_epoch(model, dataloader, optimizer, scaler, criterion, device, epoch): model.train() total_loss 0 correct 0 total 0 for batch_idx, (images, labels) in enumerate(dataloader): images images.to(device, non_blockingTrue) labels labels.to(device, non_blockingTrue) optimizer.zero_grad(set_to_noneTrue) with torch.cuda.amp.autocast(): outputs model(images) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) scaler.step(optimizer) scaler.update() total_loss loss.item() _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() if batch_idx % 50 0: print(fEpoch {epoch} [{batch_idx}/{len(dataloader)}] fLoss: {loss.item():.4f} Acc: {100.*correct/total:.2f}%) return total_loss / len(dataloader), 100. * correct / total关键点set_to_noneTrue比zero_grad()更省显存non_blockingTrue配合pin_memory实现异步传输clip_grad_norm_防止梯度爆炸AMP的autocast和GradScaler配合使用。这些细节加起来训练稳定性提升一个档次。4.4 模型导出与量化ONNX ONNXRuntime实战训练完的PyTorch模型要导出成ONNX才能跨平台部署。导出脚本import torch.onnx def export_onnx(model, dummy_input, output_path, opset_version13): model.eval() torch.onnx.export( model, dummy_input, output_path, export_paramsTrue, opset_versionopset_version, do_constant_foldingTrue, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size}, output: {0: batch_size} } ) print(fONNX模型已导出: {output_path})导出后用量化工具压缩from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic( model_inputmodel.onnx, model_outputmodel_quant.onnx, weight_typeQuantType.QUInt8 )量化后模型大小减少约75%推理速度提升2到3倍精度损失通常在1%以内。如果精度掉太多改用静态量化提供校准数据集from onnxruntime.quantization import quantize_static, CalibrationDataReader class DataReader(CalibrationDataReader): def __init__(self, dataloader): self.data iter(dataloader) def get_next(self): try: images, _ next(self.data) return {input: images.numpy()} except StopIteration: return None quantize_static( model_inputmodel.onnx, model_outputmodel_quant_static.onnx, calibration_data_readerDataReader(calib_loader), quant_formatQuantFormat.QDQ )4.5 推理服务搭建FastAPI ONNXRuntime完整代码服务端代码from fastapi import FastAPI, File, UploadFile from fastapi.responses import JSONResponse import onnxruntime as ort import numpy as np from PIL import Image import io app FastAPI(titleAI推理服务) # 全局加载模型 session ort.InferenceSession( model_quant.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider] ) input_name session.get_inputs()[0].name def preprocess(image_bytes): image Image.open(io.BytesIO(image_bytes)).convert(RGB) image image.resize((224, 224)) arr np.array(image).astype(np.float32) / 255.0 mean np.array([0.485, 0.456, 0.406]) std np.array([0.229, 0.224, 0.225]) arr (arr - mean) / std arr arr.transpose(2, 0, 1) return np.expand_dims(arr, 0).astype(np.float32) app.post(/predict) async def predict(file: UploadFile File(...)): image_bytes await file.read() input_data preprocess(image_bytes) outputs session.run(None, {input_name: input_data}) probs softmax(outputs[0][0]) pred_class int(np.argmax(probs)) confidence float(probs[pred_class]) return JSONResponse({ class: pred_class, confidence: confidence, probabilities: probs.tolist() }) app.get(/health) async def health(): return {status: ok}启动命令uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4。注意workers数不要超过CPU核数不然上下文切换开销大。4.6 监控与日志Prometheus Grafana配置在FastAPI里加监控中间件from prometheus_client import Counter, Histogram, generate_latest from starlette.middleware.base import BaseHTTPMiddleware import time REQUEST_COUNT Counter(request_count, Total requests, [method, endpoint, status]) REQUEST_LATENCY Histogram(request_latency_seconds, Request latency, [endpoint]) class MetricsMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): start time.time() response await call_next(request) latency time.time() - start REQUEST_COUNT.labels(request.method, request.url.path, response.status_code).inc() REQUEST_LATENCY.labels(request.url.path).observe(latency) return response app.add_middleware(MetricsMiddleware) app.get(/metrics) async def metrics(): return Response(generate_latest(), media_typetext/plain)Grafana面板配置几个核心图表QPS、P99延迟、错误率、GPU利用率、显存占用。告警规则P99延迟超过500ms持续5分钟错误率超过1%GPU利用率低于30%说明有瓶颈。5. 常见问题与排查技巧实录5.1 训练不收敛从loss曲线看问题根源loss不降先看曲线形状。如果loss震荡剧烈学习率太大降一个数量级试试。如果loss平着不动可能是梯度消失检查激活函数和初始化。如果loss降了又升过拟合了加正则化或早停。如果loss直接NaN梯度爆炸加梯度裁剪或降低学习率。我遇到过一个诡异情况loss正常降但验证集指标不动。排查发现是BN层在训练和验证时的行为不一致验证时忘了model.eval()。这种低级错误写个checklist就能避免。5.2 显存溢出逐层排查与优化策略CUDA OOM是家常便饭。排查步骤先打印torch.cuda.memory_summary()看显存分布然后逐层前向看哪一层显存暴涨如果是激活值太大上梯度检查点如果是参数太多上混合精度如果是batch太大上梯度累积。还有一个隐蔽的坑loss.item()如果写在循环里会强制同步拖慢速度但不影响显存。真正影响显存的是中间变量的引用没释放。比如total_loss loss会保留计算图应该用total_loss loss.item()。5.3 推理延迟高从CPU到GPU的全链路优化推理慢先定位瓶颈。用torch.profiler或者onnxruntime的profiling工具看时间花在哪。常见原因数据预处理在CPU上太慢用GPU加速预处理模型没量化上ONNX Runtime或TensorRT批处理没开请求一个一个来服务框架同步阻塞换异步或Triton。我优化过一个BERT推理服务原始延迟200ms优化后15ms。步骤ONNX导出-30%、动态量化-40%、Triton动态批处理-50%、预处理移到GPU-20%。每一步都有明确收益。5.4 常见问题速查表问题现象可能原因排查方法解决方案loss NaN梯度爆炸打印梯度范数梯度裁剪、降低学习率验证指标不涨过拟合/欠拟合对比训练验证曲线加正则/加数据/调模型GPU利用率低数据加载瓶颈nvidia-smi看波动增num_workers、pin_memory推理延迟高未量化/未批处理profilingONNX量化Triton服务OOM并发太高看内存曲线限流、模型量化、加机器精度掉点量化损失对比量化前后静态量化、QAT数据加载慢磁盘IOiostat数据转SSD/内存盘分布式训练卡住通信死锁看NCCL日志检查端口、网卡、同步点独家避坑所有实验必须记录随机种子。我吃过亏同样的代码跑两次结果不一样排查三天才发现是种子没固定。现在我的模板里random.seed、np.random.seed、torch.manual_seed、torch.cuda.manual_seed_all一个不落。6. 工程化扩展从单机脚本到生产级流水线6.1 CI/CD集成自动化测试与模型验证模型也要写单元测试。测试什么测试输入输出形状、测试边界条件、测试数值稳定性。比如def test_model_output_shape(): model MyModel() x torch.randn(2, 3, 224, 224) y model(x) assert y.shape (2, 10) def test_model_numerical_stability(): model MyModel() x torch.randn(2, 3, 224, 224) * 100 y model(x) assert not torch.isnan(y).any()CI流程代码提交触发GitHub Actions跑lint、跑测试、跑小规模训练验证、导出ONNX、跑推理测试。全部通过才允许合并。这套流程能拦住90%的低级错误。6.2 模型版本管理与回滚策略模型版本用MLflow管理。每次训练记录超参数、指标、模型文件、数据版本、代码commit。上线时模型文件带版本号服务端支持多版本共存。回滚时改配置指向旧版本重启服务即可。关键点模型文件必须和推理代码版本绑定。我见过模型更新了但预处理代码没更新导致输入分布不匹配线上效果崩盘。解决方案把预处理逻辑也打包进模型文件用TorchScript或者ONNX的自定义算子。6.3 成本控制GPU资源调度与自动伸缩GPU贵不能一直开着。我的策略训练用竞价实例便宜但可能被回收所以checkpoint要频繁存推理用自动伸缩根据QPS动态调整实例数。Kubernetes的HPA配合自定义指标如GPU利用率做伸缩。还有一个省钱技巧模型分级。高频请求走小模型低频请求走大模型。比如推荐系统热门商品用轻量模型长尾商品用复杂模型。这样整体成本降一半效果几乎不变。6.4 数据漂移检测与模型再训练触发线上模型效果会随时间衰减因为数据分布变了。监控输入特征的分布用KL散度或者PSIPopulation Stability Index检测漂移。PSI超过0.2就告警超过0.3就触发再训练。再训练流程自动化检测到漂移 - 拉取最新数据 - 自动标注如果有- 训练 - 评估 - 如果指标达标则自动上线否则人工审核。这套流程能把模型迭代周期从一个月缩短到一周。7. 我个人在实际操作中的体会这套东西我从零搭过三遍每遍都有新坑。最大的体会是工程能力不是学出来的是踩出来的。你看再多文档不如自己写一个DataLoader然后被num_workers的坑卡一整天。你听再多分享不如自己部署一个服务然后被OOM搞到凌晨三点。但踩坑要有方法。我的习惯是每个坑解决后写一个简短的复盘记录现象、原因、解决方案。攒了两年就是一份个人版的 troubleshooting 手册。现在组里新人遇到问题我先让他查手册80%的情况都能自己解决。另一个体会是不要追求一步到位。我见过太多人一开始就想搭一套完美的MLOps平台结果三个月过去了模型还没跑通。正确的做法是先用最土的办法把模型跑起来上线然后根据实际痛点逐步优化。先跑通再跑快最后跑稳。这个顺序不能反。最后分享一个小技巧保持一个最小可复现环境。用Docker建一个镜像里面只有最基础的依赖每次遇到环境问题先在这个镜像里复现。如果能复现说明是代码问题如果不能说明是环境问题。这个二分法能帮你快速定位问题边界。这套从零搭建的路径我还在持续迭代。最近在折腾的是用Ray做分布式数据预处理用Triton的ensemble做多模型串联。有新东西再分享。
返回列表