
1. 从零搭建AI工程体系为什么我劝你别急着调包ai-engineering-from-scratch这个标题第一次看到的时候我愣了一下。市面上讲AI的教程铺天盖地但绝大多数都是教你import torch然后跑个预训练模型或者调个API接口就完事。真正从工程角度、从零开始把一套AI系统搭起来的内容少得可怜。我自己在这个行业摸爬滚打了十来年带过团队也踩过无数坑。最深的体会就是能跑通一个demo和能上线一套AI系统中间隔着一整个工程体系。前者可能只需要二十行代码后者需要你考虑数据管道、特征存储、模型版本管理、推理服务、监控告警、灰度发布、回滚机制……这些东西没有一个pip install能帮你搞定。所以当我看到ai-engineering-from-scratch这个方向的时候我是很兴奋的。它瞄准的不是怎么训一个模型而是怎么从零构建一套能支撑AI应用的工程基础设施。这个定位非常精准因为市面上真正缺的就是这类内容。大部分做算法的人不懂工程做工程的人不懂算法而AI工程恰恰是两者的交叉地带。这篇文章我想基于自己实际做过的项目经验把这个标题背后的核心领域、技术栈选型、实操路径、常见坑点尽可能完整地拆解一遍。不管你是刚入行的算法工程师想补工程能力还是后端工程师想切入AI方向或者技术负责人想搭建团队的基础设施应该都能从中找到可以直接抄作业的东西。注意本文讨论的是AI工程化落地的通用方法论和实操经验不涉及任何特定平台或工具的推广所有技术选型均基于开源生态和通用实践。2. 核心领域拆解AI工程到底在工程什么2.1 先搞清楚AI工程和MLOps的区别很多人把AI工程和MLOps混为一谈其实两者有明确的分工差异。MLOps更偏向模型生命周期的管理——实验追踪、模型注册、版本对比、A/B测试这些。而AI工程的范围更大它涵盖了从数据接入到服务上线的全链路MLOps只是其中的一个子集。打个比方如果AI系统是一辆车MLOps管的是发动机的研发和调校而AI工程管的是整车的设计和制造——包括底盘、传动、电气、内饰以及这辆车能不能在真实道路上稳定跑起来。具体来说AI工程的核心领域包括数据工程层数据采集、清洗、特征工程、数据版本管理模型工程层训练管道、超参调优、模型评估、模型压缩服务工程层推理服务、批处理、流处理、API网关运维工程层监控、日志、告警、容量规划、成本控制平台工程层CI/CD、实验管理、资源调度、权限管理这五层每一层都有大量的技术决策要做而from scratch意味着你需要从最底层开始一层一层往上搭。2.2 为什么从零搭建比调包更有价值我见过太多团队一上来就用各种现成的平台和框架结果遇到问题的时候完全不知道从哪里排查。因为整个链路对他们来说是个黑盒——数据怎么流的、模型怎么加载的、请求怎么路由的全都不清楚。从零搭建的最大价值不是让你重复造轮子而是让你理解每个轮子为什么是圆的。当你自己写过一个简易的特征存储你就知道为什么Feast要那样设计当你自己实现过一个模型推理服务你就理解Triton的那些配置项到底在解决什么问题。而且从零搭建还有一个很现实的好处成本可控。现成的平台往往绑定特定的云服务商用起来方便但账单吓人。自己搭的话每一分钱花在哪里都清清楚楚。2.3 技术选型的核心原则在开始动手之前有几个选型原则我想先明确第一优先选社区活跃的开源方案。这不是说商业方案不好而是开源方案的可控性更强遇到问题能自己改源码不会被供应商锁定。第二能用简单方案就别上复杂方案。我见过太多团队数据量还没到GB级别就上了KafkaQPS还没过百就上了K8s。过度设计是AI工程落地失败的头号原因。第三每个组件都要有替代方案。不要把所有鸡蛋放在一个篮子里关键组件一定要有Plan B。下面这张表是我在实际项目中总结的选型参考层级推荐方案轻量替代适用场景数据管道Airflow / DagsterPrefect / 定时脚本复杂DAG用前者简单任务用后者特征存储FeastRedis 自研特征复用需求强用Feast模型服务Triton / TorchServeFastAPI ONNX高并发用Triton快速原型用FastAPI实验追踪MLflowWeights Biases自建用MLflow团队协作可用WB监控Prometheus Grafana自研埋点标准方案几乎无替代必要容器编排K8sDocker Compose单机用Compose集群用K8s3. 核心细节解析从零搭建的五个关键环节3.1 数据管道别小看这一层80%的AI故障出在这里我做过一个统计在我经手的AI项目里线上故障有80%以上跟数据有关——要么是数据延迟导致特征过期要么是数据格式变更导致解析失败要么是数据量突增导致管道堵塞。模型本身出问题的概率反而很低。所以从零搭建AI工程第一优先级是把数据管道做扎实。一个健壮的数据管道需要具备以下能力幂等性同一条数据重复处理多次结果应该一致。这个听起来简单但实际做的时候很容易忽略。比如你用时间戳做分区如果任务重跑可能会产生重复数据。解决方案是引入唯一键去重或者在写入时用upsert而不是insert。可回溯任何一条数据你都要能追溯到它的来源和处理过程。这意味着你需要记录数据的血缘关系包括原始数据的位置、经过哪些处理步骤、每个步骤的输入输出是什么。容错性管道中的某个环节失败了不能导致整个管道崩溃。需要设计重试机制、死信队列、降级策略。可观测管道的运行状态要能实时监控包括数据量、延迟、错误率等指标。我一般会用这样的架构来搭数据管道# 简化的数据管道示例基于Python SQLite做演示 import sqlite3 import hashlib from datetime import datetime class DataPipeline: def __init__(self, db_path): self.conn sqlite3.connect(db_path) self._init_tables() def _init_tables(self): # 原始数据表 self.conn.execute( CREATE TABLE IF NOT EXISTS raw_data ( id TEXT PRIMARY KEY, content TEXT, source TEXT, created_at TIMESTAMP ) ) # 处理记录表用于幂等控制 self.conn.execute( CREATE TABLE IF NOT EXISTS process_log ( data_id TEXT, step TEXT, status TEXT, processed_at TIMESTAMP, PRIMARY KEY (data_id, step) ) ) def _generate_id(self, content): # 用内容哈希作为唯一ID保证幂等 return hashlib.md5(content.encode()).hexdigest() def ingest(self, content, source): data_id self._generate_id(content) try: self.conn.execute( INSERT INTO raw_data VALUES (?, ?, ?, ?), (data_id, content, source, datetime.now()) ) self.conn.commit() return data_id except sqlite3.IntegrityError: # 数据已存在直接返回ID return data_id def process(self, data_id, step, processor): # 检查是否已处理 cursor self.conn.execute( SELECT status FROM process_log WHERE data_id? AND step?, (data_id, step) ) row cursor.fetchone() if row and row[0] success: return # 已成功处理跳过 try: processor(data_id) self.conn.execute( INSERT OR REPLACE INTO process_log VALUES (?, ?, ?, ?), (data_id, step, success, datetime.now()) ) except Exception as e: self.conn.execute( INSERT OR REPLACE INTO process_log VALUES (?, ?, ?, ?), (data_id, step, ffailed: {e}, datetime.now()) ) self.conn.commit()这个示例虽然简单但包含了数据管道的核心设计思想用内容哈希做幂等键、用处理日志做状态追踪、用异常捕获做容错。实际生产中你可以把SQLite换成PostgreSQL把单机处理换成分布式任务队列但核心逻辑是一样的。实操心得数据管道的监控一定要做细。我一般会监控四个指标——每分钟处理量、平均处理延迟、错误率、积压量。前三个指标异常说明管道本身有问题第四个指标异常说明下游消费能力不足。3.2 特征工程从零搭建特征存储的取舍特征工程是AI工程里最容易被低估的环节。很多人觉得特征就是把数据喂给模型但实际上特征的管理和复用是一个独立的工程问题。从零搭建特征存储你需要解决三个核心问题离线特征和在线特征的一致性。离线训练用的特征和在线推理用的特征计算逻辑必须一致否则会出现训练-推理偏差training-serving skew。这个问题的经典解决方案是一次定义两处计算——用同一份特征定义代码分别生成离线特征和在线特征。特征的时间旅行。训练模型的时候你需要的是当时的特征值而不是现在的特征值。比如你要预测用户明天的购买行为训练数据里用的应该是用户昨天的特征而不是今天的。这就要求特征存储支持时间戳查询。特征的版本管理。特征的定义会变计算逻辑会变你需要能追溯每个版本的特征定义以及每个模型用的是哪个版本的特征。我自己的做法是用一个轻量的特征注册表来管理# 特征注册表示例 from dataclasses import dataclass from typing import Callable, Optional import json dataclass class FeatureDefinition: name: str version: str dtype: str description: str compute_fn: Optional[Callable] None dependencies: list None class FeatureRegistry: def __init__(self): self._features {} def register(self, feature: FeatureDefinition): key f{feature.name}:{feature.version} self._features[key] feature def get(self, name: str, version: str latest): if version latest: # 找最新版本 candidates [k for k in self._features if k.startswith(f{name}:)] if not candidates: raise KeyError(fFeature {name} not found) key sorted(candidates)[-1] else: key f{name}:{version} return self._features[key] def compute(self, name: str, version: str, context: dict): feature self.get(name, version) if feature.compute_fn is None: raise ValueError(fFeature {name} has no compute function) return feature.compute_fn(context) # 使用示例 registry FeatureRegistry() registry.register(FeatureDefinition( nameuser_avg_order_value, versionv1, dtypefloat, description用户过去30天平均订单金额, compute_fnlambda ctx: sum(ctx[orders]) / max(len(ctx[orders]), 1) )) registry.register(FeatureDefinition( nameuser_avg_order_value, versionv2, dtypefloat, description用户过去7天平均订单金额缩短窗口, compute_fnlambda ctx: sum(ctx[recent_orders]) / max(len(ctx[recent_orders]), 1) ))这个注册表虽然简单但已经能解决版本管理和一致性计算的问题。实际项目中你可以把特征定义存到数据库里把计算逻辑封装成独立的服务但核心思路不变。注意事项特征存储不要过度设计。我见过团队在数据量只有几万条的时候就上了Feast结果维护成本比收益还高。一般来说特征数量少于50个、日增数据少于百万条的用Redis 定时任务就够了。3.3 模型服务从Flask到Triton的演进路径模型服务是AI工程里最显性的部分因为它是直接对外提供能力的。从零搭建模型服务我建议按照以下路径演进阶段一Flask/FastAPI快速原型。这个阶段的目标是快速验证不用考虑性能。一个简单的接口加载模型接收请求返回结果。阶段二引入批处理和异步。当QPS上来之后单条推理扛不住了需要做批处理。把多个请求攒成一批一起送给模型能大幅提升吞吐量。阶段三专用推理服务器。当性能要求更高时用Triton或TorchServe这类专用服务器。它们内置了动态批处理、模型集成、多框架支持等能力。阶段四多模型多版本管理。当模型数量多了之后需要做模型的路由、灰度、A/B测试。我重点说一下阶段二的批处理实现因为这是从原型到生产的关键一步# 动态批处理推理服务示例 import asyncio from collections import deque import numpy as np class BatchInferenceServer: def __init__(self, model, max_batch_size32, max_wait_ms10): self.model model self.max_batch_size max_batch_size self.max_wait_ms max_wait_ms self.queue deque() self.lock asyncio.Lock() async def predict(self, input_data): future asyncio.Future() async with self.lock: self.queue.append((input_data, future)) # 如果队列满了立即触发批处理 if len(self.queue) self.max_batch_size: asyncio.create_task(self._process_batch()) # 否则等待一小段时间攒更多请求 elif len(self.queue) 1: asyncio.create_task(self._delayed_process()) return await future async def _delayed_process(self): await asyncio.sleep(self.max_wait_ms / 1000) async with self.lock: if self.queue: await self._process_batch() async def _process_batch(self): async with self.lock: batch list(self.queue) self.queue.clear() if not batch: return inputs np.stack([item[0] for item in batch]) try: outputs self.model(inputs) for i, (_, future) in enumerate(batch): if not future.done(): future.set_result(outputs[i]) except Exception as e: for _, future in batch: if not future.done(): future.set_exception(e)这个实现的核心思想是用异步队列攒请求用超时控制延迟用批处理提升吞吐。max_batch_size和max_wait_ms是两个关键参数需要根据实际场景调优。一般来说延迟敏感的场景把max_wait_ms设小一点5-10ms吞吐敏感的场景设大一点50-100ms。实操心得批处理的大小不是越大越好。我实测下来大多数模型在batch size超过64之后吞吐量的提升就趋于平缓了但延迟会线性增长。所以找到那个拐点很重要一般通过压测来确定。3.4 监控告警模型上线只是开始模型上线之后真正的挑战才开始。你需要监控的东西比传统后端服务多得多因为除了系统指标还有模型指标。系统指标CPU、内存、GPU利用率、请求延迟、错误率、QPS。这些是基础用Prometheus Grafana就能搞定。模型指标预测分布、特征分布、置信度分布。这些指标用来检测模型是否退化了。比如你发现最近一周的预测结果中某个类别的占比从30%突然变成了60%那很可能有问题。业务指标转化率、点击率、客单价等。这些是最终衡量模型价值的指标但往往有延迟不能用来做实时告警。我一般会设置三层告警告警层级触发条件响应方式示例P0服务不可用电话短信推理服务宕机P1指标异常短信IM延迟突增3倍P2趋势异常IM通知预测分布偏移注意事项告警一定要设置静默期和聚合。我踩过的坑是模型服务重启的时候会瞬间产生几百条告警把手机炸了。后来加了5分钟的静默期和告警聚合世界清净了。3.5 CI/CD让模型迭代像代码迭代一样顺畅AI工程的CI/CD和传统软件不一样因为除了代码还有数据和模型。一个完整的AI CI/CD流程应该包括代码检查lint、类型检查、单元测试。这个和传统软件一样。数据检查数据质量校验、分布检查、schema验证。这个环节经常被忽略但非常重要。模型检查模型性能回归测试、推理速度测试、模型大小检查。集成测试端到端的测试从数据输入到预测输出。部署蓝绿部署或金丝雀发布支持快速回滚。我用GitHub Actions搭过一个简易的AI CI/CD流程核心配置如下# .github/workflows/ai-pipeline.yml name: AI Pipeline on: push: branches: [main] pull_request: branches: [main] jobs: code-check: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Setup Python uses: actions/setup-pythonv4 with: python-version: 3.10 - name: Install deps run: pip install -r requirements.txt - name: Lint run: ruff check . - name: Type check run: mypy src/ - name: Unit test run: pytest tests/unit/ -v ># src/data/pipeline.py import yaml import logging from pathlib import Path from datetime import datetime logger logging.getLogger(__name__) class PipelineConfig: def __init__(self, config_path): with open(config_path) as f: self.config yaml.safe_load(f) property def batch_size(self): return self.config.get(batch_size, 1000) property def retry_times(self): return self.config.get(retry_times, 3) property def retry_delay(self): return self.config.get(retry_delay, 5) class RobustPipeline: def __init__(self, config: PipelineConfig): self.config config self.stats {processed: 0, failed: 0, retried: 0} def run_with_retry(self, func, *args, **kwargs): for attempt in range(self.config.retry_times): try: result func(*args, **kwargs) self.stats[processed] 1 return result except Exception as e: self.stats[retried] 1 logger.warning(fAttempt {attempt1} failed: {e}) if attempt self.config.retry_times - 1: self.stats[failed] 1 logger.error(fAll retries failed for {func.__name__}) raise import time time.sleep(self.config.retry_delay * (attempt 1)) def report(self): logger.info(fPipeline stats: {self.stats}) return self.stats这里的关键设计是指数退避重试。第一次失败等5秒第二次等10秒第三次等15秒。这样做的原因是很多失败是暂时性的比如网络抖动、下游服务重启等一会儿再试往往能成功。但如果立即重试可能会加剧下游的压力。4.3 模型训练与评估的工程化模型训练部分我重点讲工程化的部分而不是算法本身。工程化的核心是可复现和可比较。可复现意味着给定相同的代码、数据和配置训练结果应该完全一致。这需要你固定随机种子、记录所有依赖的版本、保存完整的配置。可比较意味着不同实验的结果应该能放在一起对比。这需要你统一评估指标、统一数据划分、统一评估流程。# src/models/train.py import random import numpy as np import torch import mlflow from dataclasses import dataclass, asdict dataclass class TrainConfig: seed: int 42 learning_rate: float 1e-3 batch_size: int 32 epochs: int 10 model_name: str default def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) def train(config: TrainConfig, train_data, val_data): set_seed(config.seed) with mlflow.start_run(): # 记录所有配置 mlflow.log_params(asdict(config)) model build_model(config) optimizer torch.optim.Adam(model.parameters(), lrconfig.learning_rate) for epoch in range(config.epochs): train_loss train_one_epoch(model, train_data, optimizer, config.batch_size) val_loss, val_metrics evaluate(model, val_data) # 记录每个epoch的指标 mlflow.log_metrics({ train_loss: train_loss, val_loss: val_loss, **val_metrics }, stepepoch) # 保存最佳模型 if val_loss best_val_loss: best_val_loss val_loss mlflow.pytorch.log_model(model, best_model) return model用MLflow的好处是每次实验的参数、指标、模型都自动记录你可以在UI里直观地对比不同实验。我一般会记录这几类信息参数学习率、batch size、epochs、模型结构参数指标训练损失、验证损失、准确率、F1、AUC工件模型文件、配置文件、特征重要性图环境Python版本、依赖包版本、GPU型号实操心得MLflow的tracking server最好单独部署不要跟训练任务混在一起。我试过在训练脚本里直接启动MLflow结果训练任务一多tracking server就卡死了。后来改成独立的服务稳定多了。4.4 推理服务的部署与压测推理服务我用FastAPI搭一个然后加上前面说的动态批处理# src/serving/app.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel import numpy as np import time from .batch import BatchInferenceServer app FastAPI() model load_model() server BatchInferenceServer(model, max_batch_size32, max_wait_ms10) class PredictRequest(BaseModel): features: list class PredictResponse(BaseModel): prediction: list latency_ms: float app.post(/predict, response_modelPredictResponse) async def predict(request: PredictRequest): start time.time() try: input_data np.array(request.features, dtypenp.float32) result await server.predict(input_data) latency (time.time() - start) * 1000 return PredictResponse( predictionresult.tolist(), latency_mslatency ) except Exception as e: raise HTTPException(status_code500, detailstr(e)) app.get(/health) async def health(): return {status: ok}部署好之后一定要做压测。我一般用Locust或者wrk来做重点看三个指标P50延迟、P99延迟、吞吐量。P50延迟反映的是典型用户体验P99延迟反映的是最差体验吞吐量反映的是系统的承载能力。压测的时候要注意从低并发开始逐步增加。我见过有人一上来就用1000并发压结果服务直接挂了什么数据都没拿到。正确的做法是从10并发开始每次翻倍直到延迟或错误率超过阈值。4.5 监控面板的搭建监控我用Prometheus Grafana。Prometheus负责采集指标Grafana负责展示。推理服务需要暴露一个/metrics接口让Prometheus来抓取。# src/monitoring/metrics.py from prometheus_client import Counter, Histogram, Gauge, generate_latest from fastapi import Response # 定义指标 REQUEST_COUNT Counter( inference_requests_total, Total inference requests, [status] ) REQUEST_LATENCY Histogram( inference_latency_seconds, Inference latency, buckets[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0] ) BATCH_SIZE Histogram( inference_batch_size, Batch size distribution, buckets[1, 2, 4, 8, 16, 32, 64] ) MODEL_LOADED Gauge( model_loaded, Whether model is loaded ) app.get(/metrics) async def metrics(): return Response(generate_latest(), media_typetext/plain)Grafana面板我一般会放这几个图请求量趋势按分钟聚合的QPS延迟分布P50、P95、P99三条线错误率按状态码分类的错误率批处理大小分布反映批处理的效果GPU利用率如果有GPU的话注意事项Prometheus的指标基数不要太高。我踩过的坑是把用户ID作为标签结果指标数量爆炸Prometheus直接OOM了。标签的取值应该是有限的、可枚举的比如状态码、模型版本、接口路径。5. 常见问题与排查技巧实录5.1 数据管道常见问题速查问题现象可能原因排查方法解决方案数据延迟突增上游数据源变慢检查上游接口响应时间增加缓冲队列设置超时数据量突然翻倍上游重复推送检查数据唯一性加幂等键去重管道频繁失败下游服务不稳定查看下游服务日志增加重试和降级数据格式错误上游schema变更对比历史数据格式加schema校验和告警处理速度变慢数据量增长查看处理耗时趋势扩容或优化处理逻辑5.2 模型服务常见问题速查问题现象可能原因排查方法解决方案延迟突然升高批处理等待过久查看批处理大小分布调小max_wait_ms内存持续增长内存泄漏用memory profiler分析修复泄漏点加内存限制预测结果异常特征计算错误对比离线和在线特征统一特征计算逻辑服务频繁重启OOM或崩溃查看容器退出码加内存限制修复崩溃GPU利用率低批处理太小查看GPU利用率曲线增大批处理或并发5.3 我踩过的三个大坑第一个坑特征时间旅行没做对。有一次训练了一个风控模型离线AUC 0.95上线之后效果惨不忍睹。排查了半天才发现离线训练的时候用了未来的特征——比如用了用户当天的交易次数来预测当天的欺诈行为。这个特征在离线的时候是能拿到的但在线推理的时候根本拿不到。后来加了严格的时间戳校验确保训练时只能用预测时间点之前的特征。第二个坑模型版本管理混乱。早期没有做模型版本管理模型文件直接用时间戳命名。结果有一次回滚的时候发现找不到上一版的模型文件了因为被覆盖了。后来引入了MLflow的模型注册表每个模型都有唯一的版本号支持回滚和对比。第三个坑监控指标选错了。一开始只监控了系统指标CPU、内存、延迟没监控模型指标。结果模型退化了两周都没发现直到业务方反馈效果变差。后来加了预测分布监控一旦分布偏移超过阈值就告警。5.4 性能优化的几个实用技巧技巧一用ONNX加速推理。把PyTorch模型导出成ONNX格式推理速度能提升20%-50%。特别是对于小模型效果更明显。技巧二用量化减小模型体积。FP32转FP16或者INT8模型体积能减小一半到四分之三推理速度也能提升。精度损失一般在1%以内大多数场景可以接受。技巧三用缓存减少重复计算。对于相同的输入直接返回缓存的结果。这个在特征计算和推理服务里都适用。我用Redis做缓存命中率能到30%以上。技巧四用异步减少等待。推理服务里IO操作比如查数据库、调外部接口用异步能大幅提升并发能力。Python里用asyncioGo里用goroutine。实操心得性能优化一定要有数据支撑。我见过有人凭感觉优化结果优化了半天性能反而下降了。正确的做法是先用profiler找到瓶颈然后针对瓶颈优化优化完再测一遍确认有效果。6. 从原型到生产还需要补哪些能力6.1 安全与权限原型阶段不用考虑安全但生产环境必须考虑。至少需要做这几件事API鉴权每个请求都要带token验证身份和权限输入校验防止恶意输入导致模型崩溃或数据泄露输出过滤防止模型输出敏感信息审计日志记录所有请求和操作便于追溯6.2 高可用与容灾单点故障是生产环境的大忌。需要做多副本部署至少两个实例避免单点故障健康检查定期检查服务状态自动摘除不健康的实例熔断降级下游服务不可用时自动降级返回兜底结果数据备份定期备份模型和配置支持快速恢复6.3 成本控制AI系统的成本往往比传统系统高因为涉及GPU等昂贵资源。控制成本的手段包括自动扩缩容根据负载自动调整实例数量Spot实例对延迟不敏感的任务用Spot实例成本能降低60%-70%模型压缩用更小的模型达到相近的效果请求合并把多个小请求合并成一个大请求提升资源利用率6.4 团队协作AI工程不是一个人的事需要算法、工程、运维、产品多方协作。需要建立代码规范统一的代码风格和review流程文档规范每个模块都要有文档包括设计文档和API文档实验规范实验要有记录结果要可复现发布规范发布要有流程支持回滚我在实际项目中最大的体会是AI工程化的难点不在技术而在协作。技术问题总有解决方案但协作问题往往涉及流程、习惯、文化改变起来更难。所以从零搭建AI工程体系的时候一定要把协作机制设计进去而不是只关注技术栈。最后分享一个我一直在用的小技巧每个模块都要有最小可运行示例。不管是数据管道、特征计算还是模型服务都要有一个能在本地跑起来的最小示例。这样新人入职的时候能快速理解每个模块是干什么的也能快速验证环境是否配置正确。这个习惯帮我省了很多沟通成本也让我在排查问题的时候能快速定位是哪个模块出了问题。