據管道、訓練流水線與模型部署全流程實戰(zhàn))
1. 從零搭建AI工程體系為什么我勸你別一上來就調包“ai-engineering-from-scratch”這個標題第一次看到的時候我愣了一下。市面上講AI的教程鋪天蓋地但絕大多數(shù)都是教你import torch然后跑一個預訓練模型或者調個API就完事。真正講“從零開始搭建AI工程體系”的內容少得可憐。我自己在這個行業(yè)摸爬滾打了十來年帶過不少新人也面試過幾百個號稱“會AI”的候選人。一個很普遍的現(xiàn)象是很多人能背出Transformer的結構圖能說清楚Attention的計算公式但你讓他從零搭一個能用的訓練流水線他立刻卡殼。數(shù)據怎么組織顯存怎么優(yōu)化訓練中斷了怎么恢復模型上線后怎么監(jiān)控這些問題調包教程里不會講但實際工作中天天遇到。所以這個項目標題吸引我的地方在于它瞄準的是一個真實存在的巨大缺口從“會調包”到“能工程化落地”之間的鴻溝。這篇文章我想基于自己這些年的實際經驗把這個標題背后的核心內容拆開揉碎講清楚一個完整的AI工程體系到底包含哪些環(huán)節(jié)每個環(huán)節(jié)的關鍵決策怎么做以及那些只有踩過坑才知道的細節(jié)。適合誰看如果你是剛入行的算法工程師正在從“跑通demo”向“能交付項目”過渡這篇文章能幫你少走至少半年的彎路。如果你是有經驗的開發(fā)者想轉AI方向這里面的工程思路對你來說反而是優(yōu)勢。如果你只是好奇AI工程到底在干什么也能從中看到一個真實的行業(yè)切面。2. 整體架構設計AI工程體系到底包含什么2.1 為什么“從零”不等于“從輪子造起”先澄清一個概念?!癮i-engineering-from-scratch”里的“from scratch”不是讓你用NumPy手寫一個Transformer——那是深度學習框架開發(fā)者干的事。這里的“從零”指的是從零搭建一套完整的、可運行的、能支撐實際業(yè)務的AI工程體系。打個比方。你要開一家餐廳“從零”不是讓你去種小麥、養(yǎng)牛、煉鐵鍋而是讓你從選址、菜單設計、供應鏈搭建、廚房動線規(guī)劃、人員分工、品控流程這一整套東西開始搭。AI工程也是一樣PyTorch、TensorFlow這些框架就是你的“廚具”你不需要自己造但你需要知道怎么選、怎么用、怎么把它們組織成一個高效運轉的系統(tǒng)。一個完整的AI工程體系我習慣把它拆成六個層次數(shù)據層數(shù)據的采集、清洗、標注、版本管理、特征存儲訓練層實驗管理、分布式訓練、超參調優(yōu)、模型檢查點評估層離線評估、在線A/B測試、回歸測試、偏差檢測部署層模型壓縮、推理優(yōu)化、服務化、灰度發(fā)布監(jiān)控層性能監(jiān)控、數(shù)據漂移檢測、告警機制迭代層反饋閉環(huán)、持續(xù)訓練、版本回滾這六層缺一不可。很多團隊的問題就在于只關注訓練層覺得模型訓出來就萬事大吉了結果上線后各種問題層出不窮。2.2 技術選型的核心邏輯場景驅動而非技術驅動在搭建這套體系之前最重要的一步是明確你的場景需求。我見過太多團隊一上來就選最時髦的技術棧結果發(fā)現(xiàn)根本不匹配自己的業(yè)務。選型的時候我一般會問自己四個問題第一個問題數(shù)據量級和更新頻率是多少如果你的數(shù)據是TB級別的每天新增幾十萬條那你的數(shù)據管道必須支持流式處理特征存儲要能支撐高并發(fā)讀寫。如果數(shù)據量不大一周更新一次那用簡單的批處理腳本就夠了沒必要上KafkaSpark那一套。第二個問題訓練頻率和延遲要求是什么一天訓一次模型和一個月訓一次對基礎設施的要求完全不同。前者需要自動化的訓練流水線后者手動觸發(fā)就行。推理延遲要求是10ms還是1s直接決定了你要不要做模型量化、蒸餾、TensorRT優(yōu)化。第三個問題團隊的技術儲備如何如果團隊里沒人懂Kubernetes你硬上K8s做模型部署運維成本會高到離譜。選型要匹配團隊能力而不是匹配招聘JD。第四個問題預算約束是什么訓練用A100還是3090推理用T4還是CPU這些都要算賬。我后面會給出具體的成本估算方法?;谶@四個問題的答案你的技術棧選型基本就確定了。下面這張表是我總結的常見場景對應的推薦方案場景特征數(shù)據層訓練層部署層監(jiān)控層小數(shù)據量、低頻更新文件系統(tǒng)SQLite單機GPUFlask API日志文件中等數(shù)據量、日更新PostgreSQLMinIO單機多卡FastAPITritonPrometheusGrafana大數(shù)據量、實時更新KafkaFeature Store分布式訓練K8sTriton全鏈路監(jiān)控邊緣部署本地緩存云端訓練ONNX Runtime端側日志回傳注意這張表是起點不是終點。實際選型一定要結合具體業(yè)務做調整不要照搬。2.3 目錄結構設計讓項目可維護的第一步一個AI工程項目最容易爛掉的地方就是目錄結構。我接手過很多“祖?zhèn)鞔a”打開一看根目錄下幾十個文件混在一起train.py、train_v2.py、train_final.py、train_final_真的最終版.py找代碼全靠搜索。從項目第一天起就定好目錄結構后面能省無數(shù)時間。我推薦的結構是這樣的project/ ├── configs/ # 配置文件 │ ├── base.yaml │ ├── train.yaml │ └── deploy.yaml ├── data/ # 數(shù)據相關 │ ├── raw/ # 原始數(shù)據 │ ├── processed/ # 處理后數(shù)據 │ └── schemas/ # 數(shù)據schema定義 ├── src/ # 源代碼 │ ├── data/ # 數(shù)據處理模塊 │ ├── models/ # 模型定義 │ ├── training/ # 訓練邏輯 │ ├── evaluation/ # 評估邏輯 │ └── serving/ # 服務化代碼 ├── experiments/ # 實驗記錄 ├── notebooks/ # 探索性分析 ├── tests/ # 測試代碼 ├── scripts/ # 運維腳本 └── docs/ # 文檔這個結構的關鍵在于關注點分離。配置和代碼分離數(shù)據和代碼分離訓練和服務分離。這樣做的好處是當你要改一個超參數(shù)時不需要動代碼當你要換一個模型時不需要動數(shù)據管道當你要部署時不需要把訓練代碼也打包進去。3. 數(shù)據管道搭建AI工程的地基3.1 數(shù)據版本管理別再用文件名區(qū)分了數(shù)據版本管理是AI工程里最容易被忽視的環(huán)節(jié)。我見過太多團隊用data_v1.csv、data_v2_fixed.csv這種方式管理數(shù)據結果三個月后沒人說得清哪個版本對應哪次實驗。正確的做法是引入數(shù)據版本控制工具。DVCData Version Control是我用得最順手的它和Git無縫集成大文件存在遠端存儲Git里只存元數(shù)據?;居梅? 初始化DVC dvc init # 添加數(shù)據文件到DVC管理 dvc add data/raw/train.csv # 提交DVC元數(shù)據到Git git add data/raw/train.csv.dvc data/raw/.gitignore git commit -m add training data v1 # 推送數(shù)據到遠端存儲 dvc remote add -d storage s3://my-bucket/dvc-store dvc push這樣每次實驗都能精確對應到數(shù)據版本。當你發(fā)現(xiàn)三個月前那個效果特別好的模型時能一鍵還原當時的數(shù)據。實操心得DVC的緩存機制會占用大量本地磁盤建議定期執(zhí)行dvc gc清理不再需要的緩存。另外遠端存儲建議用對象存儲而不是NFS性能和可靠性都好很多。3.2 數(shù)據清洗的工程化方法數(shù)據清洗不是寫個Jupyter Notebook跑一遍就完事它需要工程化。核心原則是清洗邏輯要可復用、可測試、可追溯。我一般把清洗分成三個階段階段一數(shù)據探查。用Pandas Profiling或Great Expectations這類工具自動生成數(shù)據質量報告。重點看缺失值比例、異常值分布、類別不平衡程度。這一步在Notebook里做就行目的是了解數(shù)據長什么樣。階段二清洗規(guī)則定義。把探查階段發(fā)現(xiàn)的每個問題轉化成一條明確的規(guī)則。比如“age字段不能為負數(shù)”、“category字段只能是A/B/C三種值”。這些規(guī)則用代碼寫出來放在src/data/validators.py里。階段三清洗流水線。把規(guī)則串成流水線用Airflow或Prefect這類工具調度。每次新數(shù)據進來自動跑一遍清洗輸出清洗報告。# src/data/cleaners.py import pandas as pd from typing import Dict, Any class DataCleaner: def __init__(self, config: Dict[str, Any]): self.config config def remove_duplicates(self, df: pd.DataFrame) - pd.DataFrame: 去重保留第一條 before len(df) df df.drop_duplicates(subsetself.config[dedup_keys]) after len(df) print(f去重: {before} - {after}, 移除 {before - after} 條) return df def handle_missing(self, df: pd.DataFrame) - pd.DataFrame: 處理缺失值 for col, strategy in self.config[missing_strategy].items(): if strategy drop: df df.dropna(subset[col]) elif strategy mean: df[col] df[col].fillna(df[col].mean()) elif strategy median: df[col] df[col].fillna(df[col].median()) elif strategy.startswith(constant:): value strategy.split(:)[1] df[col] df[col].fillna(value) return df def clip_outliers(self, df: pd.DataFrame) - pd.DataFrame: 截斷異常值 for col, (lower, upper) in self.config[clip_ranges].items(): df[col] df[col].clip(lower, upper) return df注意清洗規(guī)則一定要寫單元測試。我踩過的坑是某次改了清洗邏輯導致某個字段的分布完全變了但因為沒有測試直到模型效果暴跌才發(fā)現(xiàn)。現(xiàn)在我的習慣是每條清洗規(guī)則至少配一個測試用例。3.3 特征存儲從混亂到有序當項目從單模型發(fā)展到多模型時特征管理就會變成噩夢。同一個特征推薦模型算一遍風控模型又算一遍兩邊邏輯還不一致。這時候就需要特征存儲Feature Store。特征存儲的核心價值是特征復用和一致性保證。訓練時用的特征和推理時用的特征必須來自同一套計算邏輯否則會出現(xiàn)訓練-服務偏差Training-Serving Skew。我推薦用Feast作為特征存儲方案它輕量、開源、和主流框架集成好?;炯軜嬍请x線存儲存歷史特征用于訓練。一般用Parquet文件或數(shù)據倉庫。在線存儲存最新特征用于推理。一般用Redis或DynamoDB。特征定義用Python代碼定義特征的計算邏輯保證線上線下一致。# feature_repo/features.py from feast import Entity, Feature, FeatureView, FileSource from feast.types import Float32, Int64 from datetime import timedelta # 定義實體 user Entity(nameuser_id, join_keys[user_id]) # 定義數(shù)據源 user_stats_source FileSource( pathdata/user_stats.parquet, timestamp_fieldevent_timestamp, ) # 定義特征視圖 user_stats_fv FeatureView( nameuser_stats, entities[user], ttltimedelta(days7), schema[ Feature(nametotal_orders, dtypeInt64), Feature(nameavg_order_value, dtypeFloat32), Feature(namedays_since_last_order, dtypeInt64), ], sourceuser_stats_source, )這樣定義之后訓練時用get_historical_features拿歷史特征推理時用get_online_features拿實時特征兩邊邏輯完全一致。4. 訓練流水線從手動腳本到自動化系統(tǒng)4.1 實驗管理讓每次實驗都可追溯沒有實驗管理的團隊工作狀態(tài)是這樣的小王跑了個模型準確率92%很高興地跟組長匯報。組長問“用的什么超參數(shù)”小王說“等我翻一下Notebook?!狈税胄r找到了但發(fā)現(xiàn)那個Notebook后來又被改過已經復現(xiàn)不出來了。實驗管理要解決的就是這個問題。核心要求是任何一次實驗結果都能精確復現(xiàn)。這意味著要記錄代碼版本、數(shù)據版本、超參數(shù)、環(huán)境依賴、隨機種子、硬件信息。我常用的方案是MLflow它輕量、易用、功能全。基本用法import mlflow import mlflow.pytorch # 設置實驗 mlflow.set_experiment(my-ai-project) with mlflow.start_run(run_namebaseline-v1): # 記錄超參數(shù) mlflow.log_params({ learning_rate: 1e-4, batch_size: 32, epochs: 50, optimizer: adamw, }) # 記錄指標 for epoch in range(50): train_loss train_one_epoch() val_loss, val_acc validate() mlflow.log_metrics({ train_loss: train_loss, val_loss: val_loss, val_acc: val_acc, }, stepepoch) # 記錄模型 mlflow.pytorch.log_model(model, model) # 記錄數(shù)據版本 mlflow.log_param(data_version, v1.2.0) mlflow.log_param(git_commit, get_git_commit())實操心得MLflow的Tracking Server建議單獨部署不要用本地文件存儲。團隊協(xié)作時所有人都往同一個Server打點才能對比實驗。另外模型文件不要直接存MLflow存到對象存儲MLflow里只存路徑。4.2 分布式訓練什么時候需要怎么選分布式訓練不是銀彈。很多場景下單卡就夠用硬上分布式反而增加復雜度和故障率。我的判斷標準是單卡能訓完且時間可接受比如8小時內單卡單卡訓不完或時間太長上分布式模型大到單卡放不下必須分布式分布式訓練有三種主流方案數(shù)據并行Data Parallelism每個GPU持有完整模型副本數(shù)據切分到各GPU梯度匯總后更新。適合模型能放進單卡顯存、但數(shù)據量大的場景。PyTorch的DDP是標準方案。模型并行Model Parallelism模型切分到多個GPU每個GPU持有部分層。適合模型大到單卡放不下的場景。實現(xiàn)復雜一般用Megatron-LM或DeepSpeed。流水線并行Pipeline Parallelism模型按層切分成多個階段不同階段在不同GPU上數(shù)據像流水線一樣流過。適合超大規(guī)模模型。PyTorch的Pipe是入門方案。實際項目中最常見的是數(shù)據并行混合精度。下面是一個DDP的啟動腳本# 單機8卡訓練 torchrun --nproc_per_node8 \ --master_port29500 \ src/training/train.py \ --config configs/train.yaml \ --batch_size 64 \ --learning_rate 1e-4# src/training/train.py 關鍵部分 import torch.distributed as dist from torch.nn.parallel import DistributedDataParallel as DDP def setup_ddp(): dist.init_process_group(backendnccl) local_rank int(os.environ[LOCAL_RANK]) torch.cuda.set_device(local_rank) return local_rank def main(): local_rank setup_ddp() model MyModel().to(local_rank) model DDP(model, device_ids[local_rank]) # 數(shù)據加載器要用DistributedSampler sampler DistributedSampler(dataset) dataloader DataLoader(dataset, samplersampler, batch_size...) for epoch in range(epochs): sampler.set_epoch(epoch) # 重要每個epoch要重新shuffle for batch in dataloader: ...注意DDP下只有rank 0的進程應該做日志記錄和模型保存否則會重復寫文件導致沖突。另外sampler.set_epoch(epoch)這行很容易漏漏了的話每個epoch的數(shù)據順序都一樣影響訓練效果。4.3 超參數(shù)調優(yōu)別再用網格搜索了超參數(shù)調優(yōu)是很多團隊的痛點。網格搜索太慢隨機搜索碰運氣手動調參靠經驗。我推薦用貝葉斯優(yōu)化工具選Optuna。Optuna的核心優(yōu)勢是用更少的試驗次數(shù)找到更好的參數(shù)。它通過建立代理模型預測哪些參數(shù)組合可能更好從而指導下一步搜索方向。import optuna def objective(trial): # 定義搜索空間 lr trial.suggest_float(lr, 1e-5, 1e-2, logTrue) batch_size trial.suggest_categorical(batch_size, [16, 32, 64, 128]) dropout trial.suggest_float(dropout, 0.1, 0.5) num_layers trial.suggest_int(num_layers, 2, 8) # 訓練模型 model build_model(num_layers, dropout) val_acc train_and_evaluate(model, lr, batch_size) return val_acc # 創(chuàng)建study study optuna.create_study( directionmaximize, sampleroptuna.samplers.TPESampler(seed42), pruneroptuna.pruners.MedianPruner(n_startup_trials5), ) # 開始優(yōu)化 study.optimize(objective, n_trials100, timeout3600*8) # 輸出最佳參數(shù) print(fBest trial: {study.best_trial.params})實操心得Optuna的Pruner功能非常有用。它會在訓練過程中自動終止那些明顯不好的試驗節(jié)省大量算力。我一般設置n_startup_trials5讓前5個試驗完整跑完之后才開始剪枝。另外搜索空間的定義很關鍵范圍不要設太寬否則大部分試驗都在無效區(qū)域浪費算力。5. 模型部署與服務化讓模型真正產生價值5.1 推理優(yōu)化從100ms到10ms的實戰(zhàn)路徑模型訓出來只是第一步能不能高效推理才是關鍵。我做過一個項目原始模型推理延遲120ms經過一系列優(yōu)化后降到8ms提升了15倍。下面是我用的優(yōu)化路徑第一步模型量化。把FP32轉成FP16或INT8顯存占用減半推理速度提升1.5-2倍。PyTorch的torch.quantization或ONNX Runtime的量化工具都能做。# PyTorch動態(tài)量化 import torch.quantization model MyModel() model.eval() quantized_model torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtypetorch.qint8 ) torch.save(quantized_model.state_dict(), model_quantized.pt)第二步算子融合。把多個連續(xù)的小算子合并成一個減少kernel launch開銷。TensorRT和ONNX Runtime都有自動融合功能。第三步推理引擎優(yōu)化。用TensorRT或ONNX Runtime替換原生PyTorch推理。這一步通常能帶來2-3倍的提升。# ONNX Runtime推理 import onnxruntime as ort import numpy as np # 導出ONNX torch.onnx.export(model, dummy_input, model.onnx, opset_version13) # 創(chuàng)建推理會話 session ort.InferenceSession( model.onnx, providers[CUDAExecutionProvider], ) # 推理 input_name session.get_inputs()[0].name output session.run(None, {input_name: input_data})第四步批處理。把多個請求攢成一批一起推理GPU利用率能提升好幾倍。但要注意延遲和吞吐的權衡。第五步緩存。對于重復的輸入直接返回緩存結果。這個在推薦系統(tǒng)里特別有效熱門物品的embedding可以緩存。優(yōu)化手段延遲降低實現(xiàn)難度適用場景FP16量化1.5-2x低所有GPU場景INT8量化2-4x中對精度不敏感場景算子融合1.2-1.5x低所有場景TensorRT2-3x中NVIDIA GPU批處理3-5x中高吞吐場景緩存10x低重復輸入多5.2 服務化框架選型FastAPI vs Triton vs TorchServe模型服務化框架的選擇直接決定了你的運維復雜度。我三個都用過說說各自的適用場景。FastAPI最靈活適合自定義邏輯多的場景。你可以完全控制請求處理流程做各種預處理和后處理。缺點是性能一般高并發(fā)下需要自己優(yōu)化。from fastapi import FastAPI import torch app FastAPI() model load_model() app.post(/predict) async def predict(request: PredictRequest): # 預處理 input_tensor preprocess(request.data) # 推理 with torch.no_grad(): output model(input_tensor) # 后處理 result postprocess(output) return {prediction: result}Triton Inference ServerNVIDIA出品性能最強支持多框架、多模型、動態(tài)批處理。適合大規(guī)模部署場景。缺點是需要寫配置文件靈活性不如FastAPI。TorchServePyTorch官方方案和PyTorch生態(tài)集成好。適合純PyTorch模型的部署。缺點是社區(qū)活躍度一般遇到問題不好找資料。我的建議是小規(guī)模用FastAPI大規(guī)模用Triton純PyTorch生態(tài)用TorchServe。不要一上來就上Triton它的學習曲線不陡但也不平小項目用它是殺雞用牛刀。5.3 灰度發(fā)布與回滾上線不是終點模型上線最怕的是什么是新模型效果不如舊模型但你已經全量發(fā)布了。所以灰度發(fā)布是必須的。我的做法是分三個階段階段一影子模式Shadow Mode。新模型和舊模型同時運行但只有舊模型的結果返回給用戶。新模型的結果只記錄不生效。這個階段跑一周對比兩個模型的輸出差異。階段二小流量灰度。把1%的流量切給新模型觀察核心指標。如果指標沒有明顯下降逐步擴大到5%、10%、50%。階段三全量發(fā)布。灰度到100%后舊模型保留一周作為回滾備份確認沒問題后再下線。# 灰度發(fā)布邏輯 import random def route_request(user_id: str) - str: 根據用戶ID決定用哪個模型 # 用user_id的hash做分流保證同一用戶始終用同一模型 bucket hash(user_id) % 100 if bucket current_gray_ratio: return new_model else: return old_model注意分流要用user_id的hash不要用隨機數(shù)。否則同一用戶一會兒用新模型一會兒用舊模型體驗會非常割裂。另外灰度比例要動態(tài)可調不要寫死在代碼里。6. 監(jiān)控與迭代讓系統(tǒng)持續(xù)進化6.1 數(shù)據漂移檢測模型效果下降的預警器模型上線后效果下降最常見的原因是數(shù)據漂移。輸入數(shù)據的分布變了模型還按老規(guī)律預測自然就不準了。數(shù)據漂移檢測的核心是對比訓練數(shù)據和線上數(shù)據的分布。常用的方法有PSIPopulation Stability Index衡量兩個分布的差異PSI0.1表示穩(wěn)定0.1-0.25表示輕微漂移0.25表示顯著漂移。KS檢驗統(tǒng)計兩個分布是否有顯著差異。KL散度衡量兩個概率分布的差異。import numpy as np from scipy import stats def calculate_psi(expected, actual, buckets10): 計算PSI # 分桶 breakpoints np.percentile(expected, np.linspace(0, 100, buckets 1)) breakpoints[0] -np.inf breakpoints[-1] np.inf expected_percents np.histogram(expected, breakpoints)[0] / len(expected) actual_percents np.histogram(actual, breakpoints)[0] / len(actual) # 避免除零 expected_percents np.clip(expected_percents, 1e-6, None) actual_percents np.clip(actual_percents, 1e-6, None) psi np.sum((actual_percents - expected_percents) * np.log(actual_percents / expected_percents)) return psi # 使用 psi calculate_psi(train_data[feature_1], online_data[feature_1]) if psi 0.25: alert(特征feature_1發(fā)生顯著漂移PSI{:.3f}.format(psi))實操心得PSI對分桶數(shù)量敏感一般用10-20個桶。另外不要對所有特征都設同樣的閾值有些特征天然波動大閾值要放寬。我一般先跑一周基線看每個特征PSI的正常波動范圍再定閾值。6.2 模型性能監(jiān)控不只是準確率模型上線后準確率不是唯一要看的指標。我一般會監(jiān)控四類指標業(yè)務指標點擊率、轉化率、GMV等。這是最終目標但反饋慢不能作為唯一依據。模型指標準確率、召回率、AUC等。需要標注數(shù)據才能算有延遲。系統(tǒng)指標QPS、延遲、錯誤率、GPU利用率。實時性強能快速發(fā)現(xiàn)問題。數(shù)據指標特征分布、缺失率、異常值比例。能提前預警數(shù)據問題。這四類指標要放在同一個Dashboard上看才能快速定位問題。比如業(yè)務指標下降同時數(shù)據指標顯示某個特征漂移了那大概率是數(shù)據問題。6.3 持續(xù)訓練讓模型跟上變化模型不是訓一次就完事需要持續(xù)迭代。持續(xù)訓練的核心是自動化和安全。自動化指的是數(shù)據更新后自動觸發(fā)訓練訓練完成后自動評估評估通過后自動部署。這個流水線用Airflow或Kubeflow Pipelines搭建。安全指的是新模型必須經過嚴格評估才能上線。我一般設三道關卡離線評估在留出集上新模型的核心指標必須不低于舊模型的95%?;貧w測試在一組固定的測試用例上新模型的輸出不能有異常變化。影子測試新模型和舊模型并行跑24小時輸出差異在可接受范圍內。三道關卡都通過才允許灰度發(fā)布。# 持續(xù)訓練流水線偽代碼 def continuous_training_pipeline(): # 1. 檢查數(shù)據更新 if not new_data_available(): return # 2. 數(shù)據清洗和驗證 cleaned_data clean_and_validate(new_data) # 3. 訓練新模型 new_model train(cleaned_data) # 4. 離線評估 metrics evaluate(new_model, test_set) if metrics[auc] baseline[auc] * 0.95: alert(新模型效果不達標終止發(fā)布) return # 5. 回歸測試 if not regression_test(new_model): alert(回歸測試失敗終止發(fā)布) return # 6. 影子測試 shadow_test(new_model, duration_hours24) # 7. 灰度發(fā)布 gradual_rollout(new_model)7. 常見問題與排查技巧實錄7.1 訓練相關的高頻問題問題一Loss不下降或震蕩嚴重排查思路先看學習率是不是太大用學習率掃描找合適值。再看數(shù)據有沒有問題比如標簽錯了、特征沒歸一化。最后看模型結構是不是太深或太淺。我踩過的坑有一次loss一直震蕩調了一周參數(shù)都沒用最后發(fā)現(xiàn)是數(shù)據里混了一批錯誤標注的樣本。所以遇到問題先查數(shù)據再查模型。問題二過擬合排查思路看訓練loss和驗證loss的差距。差距大就是過擬合。解決方法加正則化、加dropout、加數(shù)據增強、減小模型。問題三顯存不夠排查思路先看batch size能不能減。再看能不能用梯度累積。再看能不能用混合精度。最后看能不能用梯度檢查點。# 梯度累積示例 accumulation_steps 4 optimizer.zero_grad() for i, batch in enumerate(dataloader): output model(batch) loss criterion(output, batch[label]) loss loss / accumulation_steps # 歸一化 loss.backward() if (i 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()7.2 部署相關的高頻問題問題一推理延遲高排查思路先看是不是模型太大考慮量化或蒸餾。再看是不是批處理沒開開批處理。再看是不是CPU推理換GPU。最后看是不是預處理太慢優(yōu)化預處理。問題二內存泄漏排查思路用memory_profiler定位泄漏點。常見原因是全局變量累積、緩存沒清理、循環(huán)引用。問題三服務不穩(wěn)定排查思路看日志、看監(jiān)控、看資源使用。常見原因是并發(fā)太高、超時設置不合理、依賴服務掛了。7.3 問題速查表問題現(xiàn)象可能原因排查方法解決方案Loss不下降學習率過大/數(shù)據問題學習率掃描/數(shù)據檢查調小學習率/清洗數(shù)據過擬合模型太復雜/數(shù)據太少對比訓練驗證loss加正則/加數(shù)據顯存不夠batch太大/模型太大nvidia-smi監(jiān)控減batch/梯度累積/混合精度推理延遲高模型大/沒批處理分階段計時量化/批處理/換引擎內存泄漏全局變量/緩存memory_profiler清理全局變量/加緩存淘汰數(shù)據漂移線上數(shù)據分布變了PSI/KS檢驗重新訓練/加漂移檢測最后分享一個小技巧遇到任何問題先復現(xiàn)再定位最后解決。不要一上來就改代碼那樣只會引入新問題。復現(xiàn)的意思是用最小的例子穩(wěn)定重現(xiàn)問題。定位的意思是用二分法或日志逐步縮小問題范圍。解決的意思是改完之后要驗證問題真的解決了而不是碰巧不出現(xiàn)了。這套AI工程體系我從零搭過三次每次都有新的體會。第一次搭的時候追求技術先進什么新用什么結果運維成本高到離譜。第二次搭的時候追求簡單結果擴展性不夠業(yè)務一增長就要重構。第三次才找到平衡點夠用就好留好擴展接口。技術選型沒有絕對的對錯只有適不適合當下的場景。希望這些經驗能幫你少走一些彎路。