1. 什么是“AI Engineering from Scratch”——不是搭积木,是亲手烧制每一块砖
“AI Engineering from Scratch”这个标题乍看像一句技术口号,但真正做过AI系统落地的人一眼就懂:它根本不是教你怎么调用OpenAI API或微调一个LoRA权重,而是回到最原始的起点——从零开始构建一个能稳定跑在生产环境里的AI能力模块。我带过六支AI工程团队,做过金融风控模型平台、工业质检流水线、医疗影像辅助标注系统,所有项目上线前都经历过至少三轮“from scratch”重写。为什么?因为现成框架在真实场景里总在三个地方掉链子:数据流不可见、推理延迟不可控、故障定位像盲人摸象。所谓“from scratch”,核心是把AI从黑箱变成白盒——你得清楚知道每一行代码在哪个CPU核上执行,每1MB数据在内存里存了多久,每次GPU显存分配是否引发抖动。这不是炫技,是当客户凌晨三点打电话说“模型突然不准了”,你能30秒内定位到是特征预处理Pipeline里某个时间戳解析函数溢出了,而不是重启服务再祈祷。
关键词“ai-engineering”和“from-scratch”必须拆开理解:“AI Engineering”不是AI+Engineering的简单叠加,而是把AI当作一种新型基础设施来设计——它需要版本控制(不只是模型权重,还有特征schema、标注协议、评估指标定义);需要可观测性(不是只看accuracy曲线,还要监控输入数据分布漂移、GPU显存碎片率、API P99延迟分位);需要可回滚性(上线新模型时,能精确回退到上一版特征工程代码+模型权重+后处理逻辑的完整组合)。而“from scratch”恰恰是对抗行业浮躁的解药:现在太多团队用LangChain搭个RAG就叫AI工程,结果线上QPS一过50就开始OOM,日志里全是“CUDA out of memory”,却连显存分配器都没碰过。真正的from scratch,是从Linux内核参数调优开始,从glibc内存分配策略选型开始,从PyTorch C++前端注册算子开始。它解决的不是“能不能跑”,而是“能不能在客户服务器上7×24小时不崩”。适合谁?不是刚学完吴恩达课程的新手,而是已经部署过至少两个模型、被线上事故追着跑过三个月的工程师——你缺的不是理论,是让AI在真实世界里活下来的肌肉记忆。
2. 为什么必须放弃“框架优先”思维——从四个真实翻车现场说起
我见过太多团队在项目启动会上信心满满:“我们用Hugging Face Transformers+Ray Serve,两周上线!”结果第三周就在生产环境栽了跟头。这些翻车不是偶然,而是框架抽象层掩盖了底层真相。下面这四个场景,每个都让我团队熬过通宵,也彻底改变了我们做AI Engineering的思路。
2.1 场景一:特征缓存击穿导致P99延迟飙升300%
某电商搜索推荐项目,用Feature Store做实时特征拼接。测试环境一切正常,上线后高峰期P99延迟从80ms暴涨到350ms。排查发现:Feature Store的Redis缓存采用LRU策略,但用户行为特征(如“最近30分钟点击品类”)存在明显长尾分布——20%的用户贡献80%的缓存请求,而他们的特征更新频率极高。LRU把高频更新的key反复踢出,导致缓存命中率从92%暴跌到41%。框架没提供缓存淘汰策略配置入口,我们被迫fork整个Feature Store SDK,在C++层重写缓存管理器,引入LFU+TTL混合策略。关键教训:任何涉及状态管理的组件,必须能控制其内存/缓存行为。from scratch意味着你要亲手写内存池,而不是依赖框架默认的malloc。
2.2 场景二:模型热加载引发CUDA Context崩溃
某工业质检系统要求支持模型热切换(产线换型时无缝切换检测模型)。我们用Triton Inference Server的model repository机制,结果每次reload模型,GPU显存占用增加12MB且永不释放。深挖发现:Triton为每个模型实例创建独立CUDA Context,而Context销毁需显式调用cudaDestroyContext(),但SDK封装层未暴露该接口。最终方案是绕过Triton,用CUDA Driver API直接管理Context生命周期——在模型卸载时强制销毁Context,并用nvtop验证显存回收。实操心得:GPU资源管理不能交给黑盒。from scratch要求你读CUDA Runtime API文档第17章,知道cudaMalloc与cudaMallocManaged的区别,明白Unified Memory的page fault机制如何影响推理延迟。
2.3 场景三:分布式训练梯度同步卡死在NCCL超时
某NLP模型训练卡在DDP的allreduce阶段,NCCL超时错误频发。网络排查显示RDMA带宽充足,TCP重传率<0.1%。最终定位到:NCCL默认使用IB Verbs,但客户集群的Mellanox网卡固件版本过旧,不支持NCCL 2.12+的QP(Queue Pair)复用特性。降级NCCL版本无效,因为PyTorch 2.0已绑定NCCL 2.13。解决方案是手动编译NCCL,打补丁禁用QP复用,并修改PyTorch源码中ncclCommInitAll()的调用参数。避坑提示:分布式训练不是“设置MASTER_ADDR就行”。from scratch必须掌握NCCL通信原语(Send/Recv/Broadcast)、理解Ring-AllReduce拓扑生成逻辑,甚至要会用nccl-tests验证带宽。
2.4 场景四:ONNX Runtime推理精度漂移
某医疗影像分割模型转ONNX后Dice系数下降0.8%。对比发现:PyTorch的F.interpolate默认使用bilinear插值,而ONNX Runtime的Resize算子在opset=16下默认用nearest插值。框架转换工具没报错,因为ONNX spec允许这种实现差异。我们不得不:① 在导出ONNX时显式指定resize_mode='linear';② 重写ONNX图,将Resize节点替换为Custom Op,内联调用cuDNN的cudnnSpatialTfSamplerForward;③ 为每个resize操作添加精度校验hook。核心认知:模型部署不是“导出→加载→推理”。from scratch要求你比框架作者更懂算子语义——比如知道torch.nn.functional.grid_sample在不同CUDA版本下对边界点的处理差异,这直接影响分割边缘精度。
提示:所有框架的“便利性”都以牺牲可控性为代价。当你需要确定性行为(deterministic behavior)时,框架的抽象层反而成为障碍。真正的AI Engineering from Scratch,是主动选择复杂度,换取对系统的完全主权。
3. 核心模块拆解:从零构建AI工程系统的六个支柱
“From scratch”不是从零写所有代码,而是对每个模块有透彻理解、能自主裁剪、可深度定制。我团队沉淀出AI工程系统的六个不可替代支柱,每个都附带最小可行实现(MVP)和必须掌握的底层原理。
3.1 数据管道:超越Apache Beam的轻量级流控引擎
主流方案用Airflow调度批处理、Flink处理流式数据,但它们在AI场景有硬伤:Airflow DAG无法表达特征间的血缘依赖(如“用户画像特征依赖于订单表T+1更新”),Flink的State Backend在模型特征更新时难以保证一致性。我们的方案是自研轻量级Data Orchestrator,核心只有三个组件:
- Schema Registry:用Protocol Buffer定义特征schema,每个字段带version、source、freshness SLA(如“用户最近7天GMV:source=ods_order, freshness=300s”)。不是简单存JSON Schema,而是生成C++ struct代码,供特征计算引擎直接内存映射。
- Watermark Engine:不依赖事件时间戳,而是基于Kafka分区offset计算watermark。例如:topic A有10个分区,当前各分区offset为[1000,1005,998,1002,...],watermark = min(offset) - 100。这样避免时钟漂移问题,且watermark推进速度可配置。
- Backpressure Handler:当下游(如模型服务)处理不过来时,不是丢弃数据,而是动态降低上游Kafka consumer的fetch.min.bytes,让producer自然减速。这需要修改librdkafka源码,在rd_kafka_q_serve()中注入流量控制逻辑。
实操细节:我们用Rust编写Orchestrator核心,因为其所有权模型天然防止数据竞争。特征计算用Python UDF,但通过PyO3暴露C API,确保UDF执行时不会触发GIL。测试表明,相比Flink,端到端延迟降低40%,资源占用减少60%。
3.2 模型服务:零拷贝推理的内存布局设计
模型服务的关键瓶颈常不在计算,而在数据搬运。某OCR服务90%的延迟花在Tensor从CPU内存拷贝到GPU显存。我们的解决方案是设计统一内存布局(Unified Memory Layout):
- 输入缓冲区:预分配2GB pinned memory(cudaMallocHost),按batch size对齐。例如batch=8时,每个样本预留1024×1024×3字节,实际图像用ROI(Region of Interest)指针引用,避免复制。
- 模型权重:用CUDA Unified Memory(cudaMallocManaged)加载,启用迁移策略cudaMemAdviseSetReadMostly。实测显示,相比传统cudaMalloc,小batch推理吞吐提升2.3倍。
- 输出序列化:不走JSON,而是用FlatBuffers生成二进制schema。例如检测框坐标用int16_t存储(归一化到0-65535),比float32节省50%带宽。
参数计算:pinned memory大小 = max_batch_size × (max_image_size + max_text_length × 4) × safety_factor(1.2)。我们用jemalloc替代glibc malloc,因为其arena机制能更好管理大块内存碎片。
3.3 特征存储:面向低延迟的嵌入式KV引擎
Feature Store不是数据库,而是实时决策的神经突触。我们放弃PostgreSQL或Cassandra,用RocksDB定制嵌入式引擎,关键改造:
- LSM Tree优化:禁用memtable flush,改用write-ahead log(WAL)直接落盘。因为特征更新是append-only,无需memtable的写放大。
- Bloom Filter增强:标准Bloom Filter误判率高,我们用Cuckoo Filter替代,支持删除操作,且空间效率提升35%。
- 向量化查询:对embedding特征(如user_id→128维向量),用SIMD指令批量计算余弦相似度。单次查询1000个user_id,耗时从12ms降至3.2ms。
经验技巧:RocksDB的block_cache_size必须设为物理内存的30%,且启用LRU-K cache replacement。我们曾因cache_size设为50%,导致OS page cache被挤占,引发频繁swap。
3.4 模型监控:从accuracy到kernel级指标
传统监控只看accuracy、F1,但线上故障往往始于底层。我们的监控体系分三层:
- 应用层:预测置信度分布(用KL散度检测漂移)、类别不平衡度(Shannon entropy)。
- 系统层:GPU SM utilization(非简单的GPU利用率)、PCIe带宽占用率(用nvidia-smi dmon -s u)、CUDA context切换次数(需解析NVML event stream)。
- 硬件层:GPU温度波动率(stddev over 10s)、显存ECC错误计数(nvidia-smi -q -d MEMORY | grep "ECC Errors")。
实操要点:采集GPU kernel级指标需启用NVIDIA Data Center GPU Manager(DCGM),配置dcgmproftester -t 1001 -d 1000采集SM occupancy。我们发现某次故障源于CUDA kernel launch latency突增,根源是驱动bug,升级driver后解决。
3.5 模型训练:可复现的分布式训练框架
PyTorch DDP太重,Lightning又太抽象。我们用MPI+NCCL构建极简训练框架,核心是三个文件:
- train.py:只包含模型定义、loss计算、optimizer.step(),无任何框架代码。
- launcher.py:用mpi4py启动进程,自动分配GPU(rank 0→GPU 0, rank 1→GPU 1...)。
- sync.py:封装NCCL allreduce,暴露init_communicator()、allreduce_tensor()接口。
关键参数:NCCL_IB_DISABLE=1(禁用InfiniBand,用RoCE)、NCCL_SOCKET_TIMEOUT=1800(避免NCCL hang)、CUDA_LAUNCH_BLOCKING=0(生产环境必须关闭)。我们实测,相比DDP,训练启动时间缩短70%,故障恢复时间从5分钟降至12秒(因无master process单点故障)。
3.6 模型版本:Git for Models的元数据设计
模型版本不是保存.pth文件,而是保存完整的可执行上下文。我们的Model Registry schema包括:
message ModelVersion { string model_id = 1; // 唯一标识 string git_commit = 2; // 训练代码commit hash string docker_image = 3; // 运行环境镜像ID string feature_schema_hash = 4; // 特征schema的sha256 string data_version = 5; // 训练数据集版本(如s3://bucket/data/v20240501) float accuracy = 6; // 验证集指标 map<string, string> metadata = 7; // 自定义标签(如"training_cluster=aws-us-east-1") }避坑经验:feature_schema_hash必须包含字段顺序、数据类型、缺失值处理方式。我们曾因schema中float32字段改为float16,但hash未更新,导致线上推理失败。
4. 实操全流程:从空目录到生产服务的12小时攻坚
下面是我去年帮一家智能仓储公司搭建分拣机器人视觉识别系统的全过程。全程不用任何AI框架,只用Linux命令、C++、CUDA和少量Python。所有步骤均可复现,参数基于实测。
4.1 第1小时:环境奠基与内核调优
目标:让GPU显存分配零抖动。
操作:
- 禁用NVIDIA persistence mode(避免驱动后台进程干扰):
sudo nvidia-smi -dm 0 - 调整内核内存管理:
echo 'vm.swappiness = 1' | sudo tee -a /etc/sysctl.conf echo 'vm.vfs_cache_pressure = 50' | sudo tee -a /etc/sysctl.conf sudo sysctl -p - 创建专用GPU用户组并锁定显存:
sudo groupadd gpuusers sudo usermod -a -G gpuusers $USER # 编辑/etc/modprobe.d/nvidia.conf options nvidia NVreg_RegistryDwords="RMAllocatedNonPagedMemory=0"
原理说明:swappiness=1防止OS swap GPU pinned memory;vfs_cache_pressure=50减少inode cache回收,避免特征文件读取抖动;NVreg_RegistryDwords禁用NVIDIA驱动的非分页内存分配,防止显存碎片。
4.2 第2-3小时:数据管道MVP开发
目标:实时接收摄像头RTSP流,抽帧存入共享内存。
技术栈:FFmpeg C API + POSIX shared memory。
关键代码:
// 创建共享内存段 int shm_fd = shm_open("/camera_frames", O_CREAT | O_RDWR, 0666); ftruncate(shm_fd, 1024 * 1024 * 1024); // 1GB void* shm_ptr = mmap(0, 1024*1024*1024, PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0); // FFmpeg解码回调 static int decode_frame(AVCodecContext* ctx, AVFrame* frame) { uint8_t* yuv_data = (uint8_t*)shm_ptr + frame_count * 1920*1080*1.5; memcpy(yuv_data, frame->data[0], frame->linesize[0] * frame->height); frame_count++; return 0; }实操心得:FFmpeg的AV_PIX_FMT_YUV420P格式比RGB节省50%带宽,且CUDA nv12toRGB kernel原生支持。我们用clock_gettime(CLOCK_MONOTONIC)记录每帧时间戳,精度达微秒级。
4.3 第4-5小时:模型推理引擎搭建
目标:加载YOLOv5s ONNX模型,实现10ms内完成单帧推理。
步骤:
- 用ONNX Runtime C++ API加载模型:
Ort::Env env{ORT_LOGGING_LEVEL_WARNING}; Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); session_options.SetInterOpNumThreads(1); Ort::Session session{env, L"yolov5s.onnx", session_options}; - 输入预处理用CUDA kernel加速:
__global__ void yuv2rgb_kernel(unsigned char* yuv, float* rgb, int w, int h) { int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; if (x < w && y < h) { // YUV420P to RGB conversion in GPU } } - 输出后处理:用Thrust库做NMS,比CPU快8倍。
参数选择:input tensor shape设为[1,3,640,640],batch=1避免显存浪费;ORT_ENABLE_CPU_MEM_AFFINITY=1绑定CPU core,减少NUMA跳变。
4.4 第6-7小时:特征服务嵌入式开发
目标:为每个检测框生成reid embedding,响应时间<5ms。
方案:用RocksDB存储user_id→embedding,但embedding向量太大(512×4=2KB),直接存会拖慢LSM compaction。我们的解法:
- 将embedding切分为16个chunk(每chunk 128 bytes),用chunk_id作为key。
- 查询时并发get 16个key,用std::async实现。
- 内存映射RocksDB SST文件,避免read()系统调用。
性能数据:单次query P99=4.2ms,QPS=2300。对比Redis,内存占用降低70%,因RocksDB的block compression比Redis RDB高效。
4.5 第8-9小时:监控埋点与告警
目标:实时监控GPU SM利用率,异常时自动降级。
实现:
- 用DCGM Python binding采集指标:
import dcgm_agent, dcgm_structs handle = dcgm_agent.dcgmInit() dcgm_structs.dcgmInit() field_values = dcgm_agent.dcgmGetLatestValues(handle, [dcgm_structs.DCGM_FI_DEV_GPU_UTIL], 0) - 当SM utilization > 95%持续5秒,触发降级:将推理batch size从1→1,关闭NMS后处理,只返回bbox坐标。
告警设计:不设固定阈值,用EWMA(Exponentially Weighted Moving Average)计算utilization趋势,当slope > 0.5%/s时预警,比静态阈值提前23秒发现异常。
4.6 第10-12小时:压力测试与故障注入
目标:验证系统在7×24小时下的稳定性。
测试方案:
- 内存泄漏测试:用valgrind --tool=memcheck --leak-check=full运行推理服务24小时,检查definitely lost字节数。
- GPU故障注入:用nvidia-smi -r强制重启GPU,验证服务能否自动恢复(需捕获CUDA_ERROR_DEVICE_UNAVAILABLE异常)。
- 网络分区测试:用tc netem模拟100ms延迟+5%丢包,检验特征服务fallback逻辑。
关键结果:连续运行168小时,内存增长<0.3MB/h,GPU重启后服务恢复时间<800ms,网络分区时降级模式准确率保持82%(原95%)。
5. 常见问题与独家排查技巧实录
在数十个from scratch项目中,我们总结出高频问题及独门解法。这些不是文档里写的,是凌晨三点debug出来的。
5.1 问题速查表:CUDA相关故障TOP5
| 问题现象 | 根本原因 | 排查命令 | 解决方案 |
|---|---|---|---|
cudaErrorMemoryAllocation | 显存碎片化,非总量不足 | nvidia-smi -q -d MEMORY | grep "Used"+cat /proc/driver/nvidia/gpus/*/information | grep "Model" | 启用CUDA_MALLOPT=1,或重启驱动sudo nvidia-smi -r |
cudaErrorLaunchTimeout | Kernel执行超时(默认2s),常因死循环 | nvidia-smi dmon -s u -d 1观察SM utilization是否卡在100% | 用cuda-memcheck --tool racecheck检测race condition |
Segmentation faultatcudnnConvolutionForward | cuDNN版本与CUDA驱动不匹配 | cat /usr/local/cuda/version.txt和nvidia-smi对比 | 强制指定cuDNN路径:export LD_LIBRARY_PATH=/usr/local/cudnn-v8.9/lib:$LD_LIBRARY_PATH |
| 推理结果随机错误 | GPU ECC未开启,单比特错误累积 | nvidia-smi -q -d MEMORY | grep "ECC Errors" | sudo nvidia-smi -e 1开启ECC,重启GPU |
| 多进程CUDA初始化失败 | CUDA context跨进程共享冲突 | strace -e trace=clone,execve python test.py 2>&1 | grep clone | 设置CUDA_VISIBLE_DEVICES=0,或用cudaSetDeviceFlags(cudaDeviceScheduleBlockingSync) |
5.2 独家技巧:三步定位特征漂移
特征漂移常导致模型精度缓慢下降,但日志无报错。我们的诊断流程:
- 采样对比:用
dd if=/dev/urandom bs=1M count=100 | sha256sum生成基准哈希,对线上特征数据流做相同操作,每小时计算一次哈希。若哈希变化率>0.1%,触发告警。 - 分布可视化:不用Matplotlib(太慢),用ASCII histogram:
awk '{print $1}' features.csv | sort -n | awk 'BEGIN{min=1e9;max=-1e9} {if($1<min)min=$1; if($1>max)max=$1} END{range=max-min; for(i=0;i<50;i++) hist[int(($1-min)/range*50)]++} END{for(i in hist) printf "%d:%d\n",i,hist[i]}' | sort -n - 因果分析:用DoWhy库构建因果图,验证“特征X变化”是否导致“label Y变化”,而非相关性。
5.3 经验之谈:那些文档不会告诉你的事
- PyTorch DataLoader的num_workers陷阱:设为0时用主线程加载,但worker_init_fn不执行;设为>0时,每个worker有独立random seed,导致shuffle结果不可复现。解决方案:在worker_init_fn中设置
torch.manual_seed(torch.initial_seed() % 2**32)。 - ONNX opset选择:opset=12支持dynamic axes,但某些硬件(如Jetson)只支持opset=11。必须用
onnx.checker.check_model(model)验证,而非依赖转换工具输出。 - Linux OOM Killer误杀:当GPU进程内存激增,OOM Killer可能kill掉关键服务。解决方案:
echo -17 > /proc/PID/oom_score_adj降低进程被kill优先级。 - glibc malloc vs jemalloc:AI workload多线程malloc频繁,jemalloc的arena机制比glibc减少锁竞争。实测jeprof分析显示,malloc耗时降低65%。
注意:所有技巧都经过生产环境验证。不要盲目套用,先用
strace -c确认瓶颈所在。我见过团队为解决延迟问题调优CUDA,结果strace显示90%时间花在open()系统调用上——根源是特征文件路径没用mmap,每次都要disk I/O。
6. 工具链选型:为什么我们坚持“少即是多”
AI工程工具链不是越多越好,而是越精越稳。我们团队的工具哲学是:每个工具必须能被一个人在2小时内完全理解其源码。以下是核心工具选型逻辑。
6.1 编程语言:C++主导,Python仅作胶水
- C++:用于数据管道、模型服务、CUDA kernel。理由:零成本抽象(zero-cost abstraction),内存布局完全可控,ABI稳定。我们用C++20 modules替代头文件,编译速度提升40%。
- Python:仅用于实验、脚本、胶水代码。禁用全局解释器锁(GIL)相关操作,所有计算密集型任务用Cython或PyO3封装。
- Rust:用于基础设施组件(如Orchestrator)。理由:内存安全+无GC,panic可捕获,适合长期运行服务。
避坑指南:不用Go做AI服务——其GC pause在高吞吐场景下不可控;不用Java——JVM warmup时间长,不适合低延迟场景。
6.2 构建系统:Bazel vs CMake
- Bazel:用于大型项目(>10万行代码),优势是沙盒构建、远程缓存、严格的依赖声明。缺点是学习曲线陡峭。
- CMake:用于中小型模块(如CUDA kernel库),优势是IDE支持好、调试方便。我们用CMake Presets统一构建配置。
实操参数:CMake中设置set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -march=native -DNDEBUG"),-march=native让编译器针对当前CPU生成最优指令。
6.3 容器化:Docker还是Podman?
- Docker:开发环境用,因生态成熟。
- Podman:生产环境用,因无daemon、rootless、兼容OCI标准。我们用Podman build生成镜像,用
podman run --security-opt seccomp=unconfined禁用seccomp,避免CUDA驱动调用被拦截。
关键配置:容器启动时加--device /dev/nvidiactl --device /dev/nvidia-uvm --device /dev/nvidia0,而非--gpus all,后者在多GPU场景下不可控。
6.4 监控栈:Prometheus + Grafana + 自研Exporter
- Prometheus:抓取指标,不存原始数据,只存聚合值。
- Grafana:可视化,模板化dashboard(如“GPU Utilization by Model”)。
- 自研Exporter:用Rust编写,直接读取/proc和/sys文件系统,避免shell命令开销。例如读取GPU温度:
cat /sys/class/hwmon/hwmon2/temp1_input。
性能数据:自研Exporter每秒采集200个指标,内存占用<5MB,而Node Exporter需12MB。
6.5 配置管理:TOML胜过YAML
- TOML:用于服务配置(如model_service.toml),理由:语法简单、无缩进歧义、原生支持日期/数组。
- 禁用YAML:因其解析器(如libyaml)存在安全漏洞,且缩进错误难调试。
配置示例:
[server] host = "0.0.0.0" port = 8080 # GPU设备列表,按PCIe地址排序 gpu_devices = ["0000:01:00.0", "0000:02:00.0"] [model] onnx_path = "/models/yolov5s.onnx" batch_size = 1 warmup_iters = 107. 最后的体会:from scratch不是目的,是回归工程本质
做完第十个from scratch项目后,我渐渐明白:所谓“从零开始”,从来不是为了证明自己能重造轮子。而是当业务提出“我们需要在100ms内完成100路视频流的实时检测”时,你心里清楚——没有现成框架能满足,因为它的设计假设(如单路流、离线批处理)和你的场景根本冲突。这时候,from scratch是一种不得已的清醒:你必须亲手触摸每一层抽象之下的金属,感受电流在硅基上的真实流向。
我见过太多团队在PPT里画着“AI Platform Architecture”,却连CUDA Context的生命周期都讲不清。真正的AI Engineering,是深夜盯着nvidia-smi的输出,发现SM utilization曲线出现诡异的锯齿,然后顺藤摸瓜找到是某个kernel的shared memory bank conflict;是翻遍RocksDB源码,只为搞懂compaction触发条件为何在高写入场景下失效;是在客户机房里,用示波器测量GPU供电纹波,确认不是软件问题而是电源模块老化。
所以,如果你正准备启动一个AI项目,请先问自己:这个项目最关键的三个SLA是什么?延迟?精度?还是可用性?然后诚实回答:现有框架能否在不修改源码的前提下,100%满足这三个SLA?如果答案是否定的,那么from scratch不是选项,而是必经之路。它很苦,但苦过之后,你会获得一种罕见的能力:当别人还在查Stack Overflow时,你已经打开了GitHub,fork了那个库,开始写patch。这种能力,才是AI时代真正的工程师护城河。