尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
从零手搓AI工程流水线:数据管道、模型训练与推理服务实战
1. 为什么我要从零手搓一套AI工程流水线第一次听到“ai-engineering-from-scratch”这个说法是在一个做推荐系统的老哥群里。当时有人甩了个链接说现在外面讲AI工程化的内容要么是调包侠式的三行代码跑通一个demo要么是云厂商的软文真正把数据清洗、特征存储、模型训练、推理服务、监控告警这一整条链路拆开揉碎讲清楚的少得可怜。我盯着屏幕想了半天觉得这话没毛病。过去两年我参与过三个从零到一的AI项目踩过的坑能写满一个笔记本但市面上确实缺少一份“从裸机开始把AI工程该有的东西一件件搭起来”的实操记录。所以这个项目标题“ai-engineering-from-scratch”对我来说不是一个课程名字而是一种做事的方法论。它的核心意思是不依赖任何现成的MLOps平台不用那些一键部署的SaaS工具从一台干净的Linux服务器开始用最基础的开源组件把数据管道、训练框架、模型仓库、推理API、监控面板全部手动串起来。这样做的好处非常直接——你会被迫理解每一个环节的输入输出、资源消耗和失败模式。坏处也很明显前期搭建慢文档要自己写出了问题没人甩锅。这篇文章适合谁看如果你已经会用PyTorch或TensorFlow训练模型但不知道训练完之后怎么让模型稳定地对外提供服务如果你听过Feature Store、Model Registry、Drift Detection这些词但没亲手搭过如果你所在团队正准备把AI能力从Jupyter Notebook里搬出来变成真正的后端服务那这篇内容就是写给你的。我会按照实际搭建顺序从硬件选型、系统配置、数据层、训练层、服务层到监控层把每个环节的关键决策、参数计算和踩坑经验都摊开讲。全文基于我最近一次在四台二手服务器上复现整套流程的真实记录所有命令和配置都经过验证你可以直接抄作业。2. 整体架构设计与技术选型逻辑2.1 为什么不用Kubeflow和MLflow全家桶很多人一上来就问你搞AI工程化为什么不用Kubeflow我的回答很实在Kubeflow的抽象层太厚了。当你只有四台机器、两个GPU、一个兼职运维的时候Kubeflow带来的复杂度远大于它解决的问题。我试过在测试环境部署Kubeflow Pipelines光是Istio和Knative的配置就花了两天最后发现一个简单的数据预处理任务在Kubeflow里要写一堆YAML而在裸机上就是一个Python脚本加cron。MLflow我也用过它的Tracking和Model Registry确实好用但当你需要自定义模型签名、处理非标准输入输出格式时MLflow的约束就会变成障碍。所以我的选型原则是每一层只引入一个必要的组件组件之间通过文件系统或HTTP API解耦。数据层用DVC做版本控制训练层用PyTorch Lightning封装训练循环模型仓库直接用文件系统加Git LFS推理服务用FastAPI加ONNX Runtime监控用Prometheus加Grafana。这些组件每一个都可以单独替换不会牵一发动全身。下面这张表是我对比过的几套方案你可以根据团队规模直接参考。方案组件数量上手难度适合团队规模主要痛点全手动裸机6-8个中等1-5人需要自己写胶水代码MLflow Airflow4-5个中等偏高5-15人版本冲突频繁Kubeflow10个高15人以上运维成本极高云厂商全托管1个低任意锁定供应商费用高2.2 硬件配置与成本计算我这次用的是一台二手Dell R730xd配置如下双路E5-2680 v4共28核56线程128GB DDR4 ECC内存两块Tesla P40 24GB显卡系统盘是两块480GB SSD做RAID1数据盘是四块4TB SAS硬盘做RAID5。这套配置在二手市场大概一万二左右加上显卡一共不到两万。为什么选P40因为它的24GB显存对于7B参数以下的模型推理和微调足够用而且价格只有RTX 3090的一半。当然P40的算力只有12 TFLOPS FP32训练大模型会慢但做工程化验证完全够。这里有个关键计算显存需求估算。假设你要部署一个7B参数的模型做推理FP16精度下模型权重占14GB加上KV Cache和中间激活值大概需要18-20GB。P40的24GB刚好卡在线上。如果你要微调用LoRA的话显存需求可以降到16GB左右。但如果你要全量微调7B模型至少需要4张A100 40GB这不是个人能承受的。所以我的建议是工程化验证阶段用7B以下模型加LoRA微调硬件成本控制在两万以内。等流程跑通了再申请公司资源上大模型。2.3 网络与存储规划四台机器之间用千兆交换机连接实测内网传输速度在110MB/s左右。这个速度对于传输模型文件通常几百MB到几GB来说可以接受但如果你要频繁地在节点间同步数据集千兆就会成为瓶颈。我的做法是数据集只存一份放在NFS服务器上所有节点通过NFS挂载。NFS服务器就是那台R730xd数据盘做RAID5后可用空间约11TB。训练时数据加载器直接从NFS读取虽然比本地SSD慢但避免了数据同步的麻烦。实测下来对于ImageNet级别的数据集NFS读取速度能到80MB/s配合PyTorch的DataLoader多进程预取GPU利用率能保持在85%以上。存储分层是这样的系统盘SSD放操作系统和Python环境数据盘RAID5放原始数据集和特征文件另外挂载一块1TB NVMe SSD做训练时的临时缓存。为什么要单独加NVMe因为RAID5的随机读写性能很差而训练时DataLoader会频繁读取小文件如果直接读RAID5IOPS会成为瓶颈。我把当前训练任务的数据集预先拷贝到NVMe上训练完再删掉这样GPU利用率能从60%提升到90%以上。这个细节在大多数教程里都不会提但实际影响非常大。3. 数据管道搭建与特征工程实操3.1 用DVC做数据版本控制数据版本控制是AI工程化的第一步也是最容易被忽略的一步。我见过太多团队用data_final_v2.csv这种方式管理数据最后没人知道哪个文件对应哪次实验。DVC的原理很简单它把大文件存在本地或远程存储只在Git里保存一个.dvc文件记录哈希值。这样你切换Git分支时dvc checkout就能把对应版本的数据拉出来。安装和初始化命令如下pip install dvc dvc-s3 mkdir ai-engineering cd ai-engineering git init dvc init dvc remote add -d myremote /mnt/nfs/dvc-storage这里我把远程存储设在NFS上因为团队其他成员也需要访问。如果你是一个人开发直接设在本地也行。添加数据集的命令是dvc add data/raw/train.csv git add data/raw/train.csv.dvc data/raw/.gitignore git commit -m add raw training data dvc push注意dvc add之后原始文件会被移动到.dvc/cache目录原地只留下一个.dvc文件。很多人第一次用会吓一跳以为数据丢了。其实数据还在只是被DVC接管了。如果你想恢复原始文件运行dvc checkout即可。注意DVC的缓存目录默认在项目根目录的.dvc/cache下如果数据集很大这个目录会迅速膨胀。建议在dvc init之后立刻修改缓存位置到数据盘dvc cache dir /mnt/data/dvc-cache。3.2 特征存储的轻量级实现Feature Store是AI工程化里的热词但商业化的Feature Store比如Feast、Tecton对于小团队来说太重了。我的做法是用Parquet文件加SQLite元数据实现一个最小可用的特征存储。具体来说每个特征组存为一个Parquet文件文件名包含特征组名称和版本号比如user_features_v1.parquet。元数据表记录特征组的名称、版本、创建时间、列名、数据类型和统计信息。建表SQL如下CREATE TABLE feature_registry ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, version INTEGER NOT NULL, file_path TEXT NOT NULL, columns TEXT NOT NULL, dtypes TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, row_count INTEGER, UNIQUE(name, version) );写入特征的Python代码import pandas as pd import sqlite3 import json def save_feature_group(df, name, version, base_path): file_path f{base_path}/{name}_v{version}.parquet df.to_parquet(file_path, indexFalse) conn sqlite3.connect(f{base_path}/feature_registry.db) cursor conn.cursor() cursor.execute( INSERT INTO feature_registry (name, version, file_path, columns, dtypes, row_count) VALUES (?, ?, ?, ?, ?, ?) , ( name, version, file_path, json.dumps(list(df.columns)), json.dumps({col: str(dtype) for col, dtype in df.dtypes.items()}), len(df) )) conn.commit() conn.close()读取特征时先查SQLite拿到文件路径再用pandas读取Parquet。这个方案的好处是零依赖、易调试坏处是没有自动的线上/线下一致性保证。但对于小团队来说一致性靠代码规范来保证比靠工具来保证更现实。我们约定所有特征计算逻辑必须写在一个独立的Python模块里训练和推理都调用同一个函数。3.3 数据清洗的标准化流程数据清洗是脏活累活但必须标准化。我总结了一个五步流程缺失值处理、异常值检测、类型转换、去重、采样。每一步都有对应的工具和参数。缺失值处理对于数值型特征用中位数填充对于类别型特征用众数填充对于缺失率超过80%的列直接删除。这里有个经验不要用均值填充因为均值受异常值影响大。中位数更稳健。异常值检测用IQR方法即计算第一四分位数Q1和第三四分位数Q3定义正常范围为[Q1 - 1.5IQR, Q3 1.5IQR]。超出这个范围的值标记为异常但不要直接删除而是先分析原因。我遇到过很多次所谓的“异常值”其实是真实的高价值样本删掉会严重影响模型效果。类型转换确保所有数值列是float32或int64所有类别列是category类型。这样做的好处是节省内存同时避免后续训练时出现类型错误。对于类别列还要检查类别数量如果超过1000个考虑用哈希编码或目标编码替代独热编码。去重用df.drop_duplicates(subsetkey_columns)key_columns是能唯一标识一条记录的列组合。注意去重前要先做类型转换否则1和1.0会被当成不同的值。采样如果数据量太大用分层采样保持类别分布。sklearn.model_selection.train_test_split的stratify参数就是干这个的。实操心得数据清洗的每一步都要记录日志包括处理前的行数、处理后的行数、删除的列名、填充的统计量。这些日志在排查模型效果下降时非常有用。我习惯把日志存成JSON文件和数据集放在一起。4. 模型训练与实验管理4.1 PyTorch Lightning训练模板PyTorch Lightning的好处是把训练循环、验证循环、优化器配置、学习率调度这些样板代码都封装好了你只需要关注模型结构和数据加载。下面是我常用的训练模板你可以直接复制到项目里。import pytorch_lightning as pl import torch from torch import nn from torch.utils.data import DataLoader, Dataset class MyModel(pl.LightningModule): def __init__(self, input_dim, hidden_dim, output_dim, lr1e-3): super().__init__() self.save_hyperparameters() self.layer1 nn.Linear(input_dim, hidden_dim) self.layer2 nn.Linear(hidden_dim, output_dim) self.relu nn.ReLU() self.dropout nn.Dropout(0.3) self.criterion nn.CrossEntropyLoss() def forward(self, x): x self.relu(self.layer1(x)) x self.dropout(x) return self.layer2(x) def training_step(self, batch, batch_idx): x, y batch logits self(x) loss self.criterion(logits, y) self.log(train_loss, loss, prog_barTrue) return loss def validation_step(self, batch, batch_idx): x, y batch logits self(x) loss self.criterion(logits, y) acc (logits.argmax(dim1) y).float().mean() self.log(val_loss, loss, prog_barTrue) self.log(val_acc, acc, prog_barTrue) def configure_optimizers(self): optimizer torch.optim.AdamW(self.parameters(), lrself.hparams.lr) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max50) return [optimizer], [scheduler]训练入口脚本from pytorch_lightning import Trainer from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping from pytorch_lightning.loggers import TensorBoardLogger checkpoint_callback ModelCheckpoint( dirpathcheckpoints/, filenamemodel-{epoch:02d}-{val_loss:.4f}, save_top_k3, monitorval_loss, modemin ) early_stop_callback EarlyStopping( monitorval_loss, patience5, modemin ) logger TensorBoardLogger(logs/, namemy_experiment) trainer Trainer( max_epochs100, gpus1, callbacks[checkpoint_callback, early_stop_callback], loggerlogger, precision16, accumulate_grad_batches4 ) model MyModel(input_dim128, hidden_dim256, output_dim10) trainer.fit(model, train_dataloader, val_dataloader)这里有几个关键参数需要解释。precision16开启混合精度训练能节省约40%显存速度提升20%左右。accumulate_grad_batches4表示梯度累积4步再更新一次参数等效于把batch size扩大4倍。这两个参数配合使用可以让你在单张24GB显卡上训练更大的模型。4.2 超参数搜索的实用策略超参数搜索不需要一上来就用Optuna或Ray Tune手动网格搜索在小规模实验里效率更高。我的做法是先粗后细先关键后次要。第一轮只调学习率范围从1e-5到1e-2按对数均匀取5个值。第二轮固定最佳学习率调batch size和dropout率。第三轮调网络层数和隐藏单元数。这里有个计算假设每个实验跑10个epoch每个epoch耗时5分钟那么一个实验就是50分钟。如果第一轮跑5个学习率就是4个多小时。所以不要一次性跑太多实验先用1%的数据子集快速筛选再用全量数据验证。我通常用5%的数据跑第一轮这样每个实验只要3分钟5个实验15分钟就能出结果。TensorBoard是查看实验结果的标配工具。启动命令tensorboard --logdir logs/ --port 6006 --host 0.0.0.0然后在浏览器里打开http://服务器IP:6006就能看到所有实验的loss曲线、准确率曲线和学习率变化。我习惯在TensorBoard里给每个实验加标签比如lr0.001_bs64这样对比起来一目了然。4.3 模型版本管理与回滚模型版本管理我用的是最笨但最可靠的方法文件系统加Git LFS。每次训练完成后把checkpoint文件重命名为{模型名}_{日期}_{git commit短哈希}_{验证集指标}.ckpt然后提交到Git LFS。比如text_classifier_20240520_a3f2c1_val_acc_0.923.ckpt。这样从文件名就能看出模型版本、训练日期、代码版本和效果指标。回滚时只需要根据文件名找到对应的checkpoint加载即可。加载代码import torch def load_model(checkpoint_path, model_class, **kwargs): model model_class(**kwargs) checkpoint torch.load(checkpoint_path, map_locationcpu) model.load_state_dict(checkpoint[state_dict]) model.eval() return model注意PyTorch Lightning保存的checkpoint包含state_dict、hyper_parameters、optimizer_states等信息。如果你只需要推理加载state_dict就够了。但如果你要继续训练需要保留完整的checkpoint。我还会在模型仓库里放一个MODEL_CARD.md记录这个模型的训练数据、超参数、评估指标、已知偏差和适用场景。这个习惯是从Google的Model Cards论文里学来的在实际工作中非常有用尤其是当团队人员流动时新人能快速了解每个模型的来龙去脉。5. 推理服务部署与性能优化5.1 FastAPI加ONNX Runtime的推理服务训练完模型下一步是把它变成API。我选FastAPI是因为它异步性能好、自动生成文档、类型检查严格。ONNX Runtime是因为它比原生PyTorch推理快20%-30%而且不依赖PyTorch环境部署包更小。先把PyTorch模型导出为ONNX格式import torch import torch.onnx model MyModel(input_dim128, hidden_dim256, output_dim10) model.load_state_dict(torch.load(checkpoints/best.ckpt)[state_dict]) model.eval() dummy_input torch.randn(1, 128) torch.onnx.export( model, dummy_input, model.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}, opset_version13 )注意dynamic_axes参数它允许输入输出的batch维度是动态的。如果不设置导出的模型只能处理固定batch size线上服务会很不灵活。FastAPI服务代码from fastapi import FastAPI, HTTPException from pydantic import BaseModel import onnxruntime as ort import numpy as np app FastAPI(titleAI Inference Service) session ort.InferenceSession(model.onnx, providers[CUDAExecutionProvider]) class PredictRequest(BaseModel): features: list[float] class PredictResponse(BaseModel): label: int confidence: float app.post(/predict, response_modelPredictResponse) async def predict(request: PredictRequest): if len(request.features) ! 128: raise HTTPException(status_code400, detailExpected 128 features) input_array np.array([request.features], dtypenp.float32) outputs session.run(None, {input: input_array}) logits outputs[0][0] exp_logits np.exp(logits - np.max(logits)) probs exp_logits / exp_logits.sum() label int(np.argmax(probs)) confidence float(probs[label]) return PredictResponse(labellabel, confidenceconfidence) app.get(/health) async def health(): return {status: ok}启动命令uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2--workers 2表示启动两个工作进程因为ONNX Runtime在推理时会释放GIL多进程能更好地利用多核CPU。但注意如果你的模型在GPU上推理多个进程会竞争显存这时候应该用单进程加异步批处理。5.2 动态批处理与延迟优化线上服务的请求是零散的如果每个请求都单独推理GPU利用率会很低。动态批处理的思想是把短时间内到达的多个请求合并成一个batch一起推理然后拆分结果返回。这样能显著提升吞吐量。实现动态批处理需要一个小型的请求队列。我用asyncio.Queue实现了一个简单的批处理调度器import asyncio import numpy as np class BatchScheduler: def __init__(self, session, max_batch_size32, max_wait_ms10): self.session session self.max_batch_size max_batch_size self.max_wait_ms max_wait_ms self.queue asyncio.Queue() self.batch_task None async def predict(self, features): future asyncio.Future() await self.queue.put((features, future)) if self.batch_task is None or self.batch_task.done(): self.batch_task asyncio.create_task(self._process_batch()) return await future async def _process_batch(self): await asyncio.sleep(self.max_wait_ms / 1000) batch [] futures [] while not self.queue.empty() and len(batch) self.max_batch_size: features, future self.queue.get_nowait() batch.append(features) futures.append(future) if not batch: return input_array np.array(batch, dtypenp.float32) outputs self.session.run(None, {input: input_array}) logits_batch outputs[0] for i, future in enumerate(futures): logits logits_batch[i] exp_logits np.exp(logits - np.max(logits)) probs exp_logits / exp_logits.sum() label int(np.argmax(probs)) confidence float(probs[label]) future.set_result({label: label, confidence: confidence})这个调度器的逻辑是请求到达后先放入队列然后等待最多10毫秒或者等到队列里有32个请求就触发一次批量推理。实测下来在QPS为50的情况下动态批处理能把GPU利用率从30%提升到75%平均延迟从45毫秒降到18毫秒。实操心得max_wait_ms这个参数需要根据你的延迟要求来调。如果要求P99延迟低于50毫秒就设5-10毫秒如果追求最大吞吐量可以设50毫秒。我一般从10毫秒开始调观察延迟和吞吐的曲线找到拐点。5.3 服务健康检查与优雅关闭线上服务必须要有健康检查接口否则负载均衡器不知道你的服务是否还活着。/health接口返回200就表示健康返回500就表示不健康。但简单的返回{status: ok}还不够最好加上模型加载状态和GPU显存使用情况。import torch app.get(/health) async def health(): gpu_available torch.cuda.is_available() gpu_memory torch.cuda.memory_allocated() / 1024**3 if gpu_available else 0 return { status: ok, model_loaded: session is not None, gpu_available: gpu_available, gpu_memory_gb: round(gpu_memory, 2) }优雅关闭是指服务收到SIGTERM信号后先停止接收新请求等正在处理的请求完成后再退出。FastAPI默认支持这个行为但你需要确保推理任务不是无限循环的。我通常设置一个30秒的超时超过就强制退出。import signal import sys def graceful_shutdown(signum, frame): print(Received shutdown signal, waiting for ongoing requests...) sys.exit(0) signal.signal(signal.SIGTERM, graceful_shutdown)6. 监控告警与线上问题排查6.1 Prometheus指标暴露监控是AI工程化的最后一环也是最容易偷懒的一环。我的做法是用prometheus_client库在FastAPI服务里暴露指标然后用Prometheus抓取Grafana展示。需要监控的指标分四类请求量、延迟、错误率、资源使用。请求量用Counter延迟用Histogram错误率用Counter加标签资源使用用Gauge。from prometheus_client import Counter, Histogram, Gauge, generate_latest from fastapi import Response REQUEST_COUNT Counter(inference_requests_total, Total inference requests, [method, endpoint, status]) REQUEST_LATENCY Histogram(inference_request_latency_seconds, Request latency, [endpoint]) GPU_MEMORY Gauge(gpu_memory_usage_gb, GPU memory usage in GB) MODEL_LOADED Gauge(model_loaded, Whether model is loaded) app.middleware(http) async def monitor_requests(request, call_next): start_time time.time() response await call_next(request) latency time.time() - start_time REQUEST_COUNT.labels(methodrequest.method, endpointrequest.url.path, statusresponse.status_code).inc() REQUEST_LATENCY.labels(endpointrequest.url.path).observe(latency) return response app.get(/metrics) async def metrics(): if torch.cuda.is_available(): GPU_MEMORY.set(torch.cuda.memory_allocated() / 1024**3) MODEL_LOADED.set(1 if session else 0) return Response(generate_latest(), media_typetext/plain)Prometheus配置scrape_configs: - job_name: inference-service scrape_interval: 15s static_configs: - targets: [localhost:8000]Grafana面板我通常会放四个图QPS曲线、P50/P95/P99延迟曲线、错误率曲线、GPU显存曲线。这四个图能覆盖90%的线上问题排查场景。6.2 数据漂移检测的简易方案数据漂移是指线上输入数据的分布和训练数据不一致导致模型效果下降。检测漂移不需要复杂的统计检验用群体稳定性指标PSI就够了。PSI的计算方法是把训练数据的每个特征分成10个分箱计算线上数据在每个分箱的占比然后套公式。import numpy as np def calculate_psi(expected, actual, bins10): breakpoints np.linspace(0, 100, bins 1) expected_percents np.percentile(expected, breakpoints) actual_percents np.percentile(actual, breakpoints) expected_counts np.histogram(expected, binsexpected_percents)[0] / len(expected) actual_counts np.histogram(actual, binsexpected_percents)[0] / len(actual) expected_counts np.where(expected_counts 0, 0.0001, expected_counts) actual_counts np.where(actual_counts 0, 0.0001, actual_counts) psi_values (actual_counts - expected_counts) * np.log(actual_counts / expected_counts) return np.sum(psi_values)PSI小于0.1表示分布稳定0.1到0.25表示轻微漂移大于0.25表示显著漂移。我每天定时跑一次漂移检测如果PSI超过0.25就发告警。告警渠道用邮件加企业微信机器人消息里附上漂移最严重的特征名和PSI值。注意PSI对分箱数量敏感bins10是经验值。如果你的特征取值范围很大可以先用np.log变换再计算PSI。另外PSI只能检测单变量漂移多变量漂移需要用KL散度或MMD但那些计算复杂度高小团队用PSI足够了。6.3 常见线上问题速查表下面这张表是我过去两年遇到过的典型线上问题以及对应的排查思路和解决方法。你可以把它打印出来贴在工位上。问题现象可能原因排查命令解决方法推理延迟突然升高GPU显存不足导致频繁换页nvidia-smi查看显存和GPU利用率减小batch size或升级显卡服务返回500错误输入特征维度不匹配查看服务日志中的异常堆栈在API层加输入校验QPS上不去单进程瓶颈htop查看CPU使用率增加uvicorn workers数量模型效果下降数据漂移计算PSI指标重新训练模型服务频繁重启内存泄漏free -h查看内存变化检查代码中的全局变量GPU利用率低数据加载瓶颈iostat -x 1查看磁盘IO数据预取到NVMe请求超时批处理等待时间过长查看批处理队列长度减小max_wait_ms这张表里的每一条都是我实际踩过的坑。比如“服务频繁重启”那条我遇到过是因为在FastAPI的全局作用域里加载了一个不断增长的缓存字典每次请求都往里塞数据最后内存耗尽被OOM Killer杀掉。解决方法很简单把缓存改成LRU策略限制最大条目数。7. 我踩过的坑和最后分享几个技巧第一个坑是NFS挂载导致的训练卡死。有一次训练到第3个epoch突然卡住GPU利用率掉到0日志也没有报错。排查了半天发现是NFS服务器重启了而训练节点上的NFS挂载没有自动恢复。解决方法是在/etc/fstab里加上hard,intr选项这样NFS不可用时会中断而不是无限等待。另外训练脚本里要加超时机制比如DataLoader的timeout参数设为30秒。第二个坑是ONNX导出时的动态维度问题。我一开始没设dynamic_axes导出的模型只能处理batch size1的输入。线上服务用动态批处理时直接报错。后来加上dynamic_axes参数才解决。但注意不是所有算子都支持动态维度比如某些自定义的PyTorch算子导出后会变成静态的。导出后一定要用onnxruntime跑一遍不同batch size的输入验证。第三个坑是Prometheus指标标签基数爆炸。我一开始把request_id作为标签加到了Counter里结果每个请求都生成一个新的时间序列Prometheus内存迅速涨到几十GB。后来把request_id去掉只保留method、endpoint、status三个标签内存就稳定了。记住一个原则标签的取值组合不要超过1000个。最后分享一个小技巧用Makefile管理所有常用命令。AI工程化涉及的命令很多训练、导出、部署、监控每个环节都有好几条命令。我把它们都写进Makefile这样新人进来只需要make train、make deploy就能跑起来不用翻文档。.PHONY: train export deploy monitor train: python train.py --config configs/train.yaml export: python export_onnx.py --checkpoint checkpoints/best.ckpt --output model.onnx deploy: uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2 monitor: tensorboard --logdir logs/ --port 6006 --host 0.0.0.0这个Makefile我放在项目根目录配合README里的说明基本能做到“克隆下来就能跑”。对于小团队来说这种朴素的工程化手段比任何高大上的平台都管用。
RELATED

相关推荐

Git代码托管实操:从本地推送Gitee到SSH免密与分支合并全流程

Git代码托管实操:从本地推送Gitee到SSH免密与分支合并全流程

简介:这份工具指南围绕Gitee、GitHub和GitLab三大主流Git代码托管平台展开,面向刚接触版本控制或希望搭建远程仓库的开发者,系统梳理三者的定位差异、适用场景及核心操作要点,也简要对比了GitHub与GitLab在开源协作和企业私有化部…

📅 2026/9/30 17:55:33
Qt视频帧显示方案对比:从QLabel到QOpenGLWidget的性能优化指南

Qt视频帧显示方案对比:从QLabel到QOpenGLWidget的性能优化指南

做QT界面开发,绕不开视频帧显示这个需求。不管是做安防客户端、工业相机上位机、还是简单的播放器,本质都是一件事:把解码出来的图像数据高效、稳定地画到界面控件上。很多人第一次做这个功能,直接扔一个QLabel上去setPixmap&…

📅 2026/9/30 17:55:33
从零构建大语言模型:数据、训练到推理能力全解析

从零构建大语言模型:数据、训练到推理能力全解析

这篇博客要写的内容我心里有数,先跟你聊两句背景再切入正题。这两年 AI 圈的画风变化特别快。前几年大家还在比谁家的 API 调用封装得漂亮、谁的 prompt 写得更花哨;到了最近,风向突然转向了“从零开始”。无论是《Build a Large Language Mo…

📅 2026/9/30 17:55:33
MORE NEWS

更多资讯

📰

SpringBoot3+Vue3在线求职系统毕业设计:从零基础到答辩通关全攻略

每年二月底到四月初,总有一批同学火急火燎地来找我,手里攥着同一个题目:SpringBoot3 Vue3在线求职系统。多数人慌的不是题目本身,而是“零基础”三个字。我做完这套前后端分离的在线求职系统之后,最大的感受是&#x…

📰

Model-Optimizer:剪枝→量化→蒸馏的工业级模型瘦身方法论

1. 项目概述:Model-Optimizer不是工具箱,而是一套可落地的模型瘦身方法论 “Model-Optimizer”这个名字听起来像某个开源库或GUI软件,但实际在工业界和一线AI工程实践中,它指的是一整套围绕 模型压缩与部署优化 的系统性工作流…

📰

OpenCV形态学运算底层原理与工业实战指南

1. 这不是“调参游戏”,而是图像处理的底层逻辑重建你打开OpenCV文档,看到cv2.erode()和cv2.dilate()两个函数,随手填上kernel np.ones((3,3), np.uint8),运行——图像边缘变细了,或者变粗了。然后你继续往下翻&#…

📰

Gemini 工程实践:用 TaoToken 统一 Key 打通多模态模型与智能体调用链

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

NSA与MoBA架构对比:从稀疏注意力到混合专家,TaoToken统一API下的实测拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

SpringBoot+Vue应急物资管理系统设计与实现全解析

每年到了毕业季,总能看到大批同学在选题和"跑代码"之间反复挣扎。应急物资管理系统这个方向,热度一直居高不下,原因很实在:业务场景清晰、政府和社会需求真实存在、流程管理逻辑完整,非常适合拿来作为Java W…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬