1. 项目概述:当大模型在屏幕上流畅生成文字时,后台正有几十个“算子”在高速协作
你刷到过那种演示视频吗?——输入一句“写首关于春天的七言绝句”,三秒后,工整押韵、平仄合规的诗就跃然屏上。表面看是模型在“思考”,但真正让这个过程稳定、快速、不出错的,是一群藏在模型背后、几乎从不露面的工程师和研究员。他们不写提示词,也不调温度系数,而是天天跟“算子”打交道:Conv2D、MatMul、Softmax、RMSNorm、FlashAttention……这些名字听起来像实验室编号,实则每一个都卡着大模型推理速度的咽喉,决定着千人并发时GPU显存会不会突然爆掉,也左右着一个10B参数模型能否塞进单张消费级显卡跑起来。
“AI Infra”这个词最近频繁出现在技术会议和招聘JD里,但它不是新概念,只是被大模型逼到了聚光灯下。它指的不是模型本身,也不是数据或算法,而是支撑AI模型从训练到部署、从单机实验到万卡集群、从原型验证到7×24小时服务的整套底层能力体系。这里面包括算子库优化、计算图编译、内存调度策略、分布式通信原语、硬件抽象层、推理引擎内核、量化压缩工具链、可观测性埋点系统……全都是看不见的“水电煤”。我参与过三个不同规模的大模型落地项目,最深的体会是:模型效果提升10%,可能靠调参;但端到端延迟下降40%、成本压低60%,90%靠的是Infra层的死磕。这篇文章不讲LLM原理,也不教你怎么写system prompt,我们就坐进机房隔壁的工位,看看那群人每天在敲什么代码、画什么图、盯什么指标、骂什么bug——他们忙的,正是让“AI很聪明”这件事,真正变成“AI很能用”的关键一环。
2. 内容整体设计与思路拆解:为什么不能只靠PyTorch自动优化?Infra团队的决策逻辑是什么
2.1 算子不是“函数”,而是硬件与算法之间的翻译官
很多人初学深度学习时,把torch.nn.Linear当成一个黑盒函数:输入tensor,输出tensor,中间调用CUDA kernel完事。但真实场景中,一个Linear层在不同硬件、不同精度、不同batch size下,背后调用的可能是完全不同的算子实现。比如:
- 在A100上跑FP16 batch=32时,用的是cuBLAS的
cublasLtMatmul,它支持Tensor Core加速; - 在RTX 4090上跑INT4 batch=1时,用的是自研的
int4_gemm_kernel,做了weight-only量化+SIMD指令重排; - 在国产某AI芯片上,则必须走完全自定义的
chip_xxx_matmul_v2,因为它的矩阵乘法单元不兼容cuBLAS ABI。
提示:所谓“算子(Operator)”,本质是算法逻辑在特定硬件上的高性能实现。它不是数学定义,而是工程契约——承诺在XX硬件、XX精度、XX内存布局下,以XX延迟完成XX计算。PyTorch的
aten::linear只是接口,底下可以挂几十种实现,Infra团队要做的,就是为每一种实际运行环境,选对、写对、调优对那个“对”的实现。
这就解释了为什么Infra团队不能只依赖PyTorch的自动优化。PyTorch的JIT和TorchScript确实能做图融合、常量折叠,但它无法知道:你的集群里混搭了V100和A100,而V100不支持某些A100专属指令;你的客户要求模型必须在ARM服务器上跑,但PyTorch官方没提供针对该平台的FlashAttention优化;你的业务要求首token延迟<50ms,但默认的内存分配策略会导致显存碎片化,每次alloc/free都多花8ms。
2.2 Infra不是“基建”,而是模型能力的放大器与约束器
很多公司早期把Infra团队当成“运维延伸”——装CUDA、配NCCL、打包Docker镜像。等模型上线后卡顿、OOM、吞吐上不去,才意识到问题不在模型结构,而在Infra层的表达能力不足。真正的AI Infra设计,核心是回答三个问题:
- 模型想做什么?(What)—— 比如MoE架构需要动态路由,要求Infra支持条件分支执行;长上下文需要KV Cache分片管理,要求Infra提供跨设备的缓存生命周期控制。
- 硬件能做什么?(How)—— 比如某芯片的DMA带宽只有PCIe 4.0的一半,那么KV Cache的prefetch策略就必须更激进;某GPU的L2 cache只有18MB,那么attention计算就必须切分成更小的block,避免cache thrashing。
- 业务要什么?(Why)—— 比如客服场景容忍首token慢但要求总生成快,Infra就要优先优化decode阶段;而代码补全场景要求首token<200ms,Infra就得牺牲部分decode吞吐,换得prefill阶段极致加速。
这三者的交集,就是Infra团队的设计空间。我们曾为一个金融风控模型重构推理引擎,原方案用HuggingFace Transformers直接加载,QPS 120,P99延迟210ms。重构后引入自定义算子融合(把Embedding + LayerNorm + QKV projection三合一)、静态shape绑定(所有tensor shape在compile时固化)、显存池预分配(避免runtime malloc),最终QPS升至480,P99压到68ms。这不是模型变了,是Infra让模型的能力被更充分地释放出来。
2.3 从“算子”到“Infra”的演进路径:四个不可跳过的阶段
Infra能力不是一蹴而就的,它随模型复杂度和业务规模呈阶梯式演进。我们观察过二十多个团队的实践,基本遵循以下四阶段路径:
| 阶段 | 典型特征 | 关键挑战 | Infra团队主要工作 |
|---|---|---|---|
| 1. 单机可跑 | 模型能在单卡GPU上训/推,用官方框架默认配置 | 显存溢出、训练中断、推理抖动 | 调整batch size、gradient checkpoint、混合精度开关;排查CUDA OOM报错 |
| 2. 多卡能扩 | 支持DDP/FSDP,千卡集群可启动训练 | NCCL超时、梯度同步慢、checkpoint保存失败 | 优化all-reduce通信拓扑、定制checkpoint序列化格式、监控GPU间PCIe带宽 |
| 3. 生产可用 | 模型作为API服务上线,有SLA保障 | P99延迟毛刺、冷启慢、版本回滚难 | 实现请求队列分级、warmup机制、模型热加载、metrics埋点与告警联动 |
| 4. 规模经济 | 单日处理亿级请求,单位token成本持续下降 | 硬件利用率低、跨模型资源共享难、新硬件适配慢 | 构建统一算子注册中心、开发IR中间表示、抽象硬件抽象层(HAL)、建设自动化benchmark平台 |
绝大多数团队卡在第2阶段向第3阶段跃迁时。他们能训出SOTA模型,却搞不定一个稳定的服务接口——因为Infra还没从“让模型跑起来”进化到“让业务用得好”。而标题里说的“看不见的那群人”,正是常年泡在这四个阶段交界处,用C++写kernel、用Python写调度、用Prometheus盯曲线、用Wireshark抓网络包的硬核角色。
3. 核心细节解析与实操要点:算子优化到底在优化什么?一张表看懂关键维度
3.1 算子性能的五大黄金指标,缺一不可
优化一个算子,绝不是只看“快了多少”,必须同时盯住五个相互制约的维度。我们内部有个检查清单,每次新算子上线前必须填满:
| 维度 | 衡量方式 | 为什么重要 | 实测案例 |
|---|---|---|---|
| 1. 计算吞吐(TFLOPS) | 实际达到的FP16/INT8算力占硬件峰值比例 | 反映kernel是否榨干了计算单元 | 某FlashAttention kernel在A100上达28 TFLOPS(峰值312),说明还有91%提升空间,后来通过减少shared memory bank conflict提至42 TFLOPS |
| 2. 内存带宽利用率(GB/s) | 实际带宽占显存带宽比例 | GPU是“内存受限型”设备,带宽常比算力先瓶颈 | 原始MatMul kernel带宽利用率仅35%,改用tiled load + register blocking后升至82% |
| 3. 显存占用(VRAM) | 单次调用所需显存峰值 | 直接决定能否塞进小显存卡、能否提高batch size | RMSNorm算子从逐元素计算改为fused kernel,显存占用从O(3N)降至O(N),让7B模型在24G显卡上batch=8变为可能 |
| 4. 启动开销(μs) | kernel launch到开始执行的时间 | 对小batch、高并发场景致命 | 某custom softmax kernel启动耗时120μs,改用CUDA Graph capture后压至8μs,P99延迟直降15ms |
| 5. 数值稳定性(Error) | 输出与参考实现(如PyTorch)的L2误差 | 工程优化不能以牺牲精度为代价 | INT4 GEMM在weight-only量化下,需保证与FP16结果的relative error < 1e-3,否则下游任务准确率掉点 |
注意:这五项不是独立优化的。比如追求极致吞吐,可能增加显存占用;降低启动开销,可能牺牲数值精度。Infra工程师的核心能力,是在具体业务约束下做多目标权衡。例如客服机器人要求首token<100ms,那就必须牺牲部分decode吞吐,优先优化prefill阶段的启动开销和带宽利用率。
3.2 真实算子优化现场:以RMSNorm为例拆解三层优化逻辑
RMSNorm(Root Mean Square Layer Normalization)是LLM里高频调用的算子,看似简单:y = x / sqrt(mean(x²) + eps) * gamma + beta。但实测发现,在7B模型的decode阶段,它竟占总kernel time的12%。我们花了三周时间优化,过程极具代表性:
第一层:算法级融合(Algorithm Fusion)
原始实现是三个分离kernel:
x²→ 2.mean()→ 3.sqrt() + div + mul + add
问题:三次global memory读写,两次kernel launch开销。
优化:写一个fused kernel,只读一次x,所有计算在shared memory完成,最后写一次y。
效果:显存带宽占用↓40%,kernel launch次数↓66%。
第二层:硬件级适配(Hardware-Aware Tiling)
A100的warp size是32,shared memory bank数是32。原始fused kernel按row-major顺序load,导致bank conflict严重。
优化:将input tile按[32, 16]分块,每个warp负责一个tile,load时做stride-16偏移,消除bank conflict。
效果:shared memory latency ↓35%,TFLOPS从18→26。
第三层:编译级特化(Compilation Specialization)
RMSNorm的gamma/betashape固定为[hidden_size],而hidden_size在7B模型中恒为4096。
优化:在编译时将4096作为编译时常量,启用#pragma unroll展开循环,让NVCC生成更紧凑的SASS指令。
效果:指令数↓22%,L1 cache命中率↑18%。
最终,这个RMSNorm算子在A100上从原始1.2ms降至0.31ms,提速3.9倍。但关键不是“快”,而是它让整个decode循环的GPU occupancy从52%提升到89%,为其他算子腾出了计算资源。这就是Infra优化的典型思维:不孤立看单点,而看全局资源水位。
3.3 Infra团队的日常工具链:不是只会写CUDA,而是构建“能力流水线”
外界常以为Infra工程师就是“CUDA手艺人”,其实他们的核心产出是一套可复用、可度量、可演进的工程能力。我们团队维护着一条完整的Infra能力流水线:
Benchmark平台:自动拉取最新模型权重,生成覆盖prefill/decode、不同batch/seq_len的测试集,跑通后生成PDF报告,包含各算子耗时占比、显存曲线、GPU utilization热力图。新算子必须比baseline提升≥15%才能合入主干。
算子注册中心:所有算子实现(CUDA/CPU/ASIC)统一注册,按
{op_name}_{hardware}_{precision}_{layout}命名。调度器根据当前设备信息实时选择最优实现,支持fallback机制(如A100上FlashAttention失败,自动降级到标准Attention)。IR中间表示层:自研轻量IR(类似TVM Relay),把PyTorch模型图转成统一中间表达,再由后端编译器生成target-specific code。这样新增一款芯片,只需写一个backend,不用改所有算子。
可观测性探针:在每个算子入口/出口插入
perf_event,采集latency、memory footprint、cache miss rate。这些数据实时上报到Grafana,形成“算子健康度大盘”,哪个算子最近抖动、哪个显存分配异常,一眼可见。
这套流水线让团队能并行推进多个方向:有人专攻CUDA kernel,有人优化IR编译器,有人建设benchmark,有人分析大盘数据找瓶颈。它把“个人英雄主义”的优化,变成了可持续的工程化产出。
4. 实操过程与核心环节实现:从零搭建一个最小可行Infra验证环境
4.1 环境准备:用最简配置跑通第一个自定义算子
别被“万卡集群”吓住,Infra优化的第一步,永远是单卡单算子验证。我们用一个极简但真实的例子:实现一个int4_weight_only_matmul,用于加速LLM的FFN层权重计算。
硬件与软件栈选择逻辑:
- GPU:RTX 4090(消费级,方便复现,且支持INT4 Tensor Core)
- CUDA:12.1(4090驱动要求,且12.1首次正式支持
mma.sync.aligned.m8n8k32) - 框架:PyTorch 2.1 + Triton 2.1(Triton写kernel比纯CUDA快3倍开发效率)
实操心得:新手千万别一上来就啃cuBLAS源码。Triton是Infra工程师的“汇编语言”,它用Python语法写GPU kernel,自动做memory coalescing、shared memory管理、warp shuffle,开发效率极高。我们90%的新算子都先用Triton prototype,再用CUDA重写关键路径。
第一步:确认硬件能力
# 查看GPU compute capability nvidia-smi --query-gpu=name,compute_cap --format=csv # 输出:NVIDIA GeForce RTX 4090, 8.9 → 支持INT4 MMA指令第二步:写Triton kernel(matmul_int4.py)
import torch import triton import triton.language as tl @triton.jit def int4_matmul_kernel( a_ptr, b_ptr, c_ptr, M, N, K, stride_am, stride_ak, stride_bk, stride_bn, stride_cm, stride_cn, BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr, ): # 定义block索引 pid_m = tl.program_id(0) pid_n = tl.program_id(1) offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M) offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N) offs_k = tl.arange(0, BLOCK_SIZE_K) # 加载INT4 weight(pack成INT32,每个INT32含8个INT4) b_int32 = tl.load(b_ptr + (offs_k[:, None] * stride_bk + offs_n[None, :] * stride_bn)) # 解包INT4:b_int32 >> (4 * (offs_k % 8)) & 0xF → 得到INT4值 b_int4 = ((b_int32 >> (4 * (offs_k % 8))) & 0xF).to(tl.int8) # 加载FP16 activation a_fp16 = tl.load(a_ptr + (offs_m[:, None] * stride_am + offs_k[None, :] * stride_ak)) # 计算:a_fp16 * b_int4(自动promote to FP16) acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32) for k in range(0, K, BLOCK_SIZE_K): acc += tl.dot(a_fp16, b_int4) # 存结果 c = acc.to(tl.float16) tl.store(c_ptr + (offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn), c)第三步:封装成PyTorch算子
class Int4MatMul(torch.autograd.Function): @staticmethod def forward(ctx, a, b_int32, scales, zeros): # a: [M, K] FP16, b_int32: [K//8, N] INT32, scales/zeros: [N] FP16 c = torch.empty((a.shape[0], b_int32.shape[1]), device=a.device, dtype=torch.float16) grid = lambda META: (triton.cdiv(a.shape[0], META['BLOCK_SIZE_M']), triton.cdiv(b_int32.shape[1], META['BLOCK_SIZE_N'])) int4_matmul_kernel[grid]( a, b_int32, c, a.shape[0], b_int32.shape[1], a.shape[1], a.stride(0), a.stride(1), b_int32.stride(0), b_int32.stride(1), c.stride(0), c.stride(1), BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32 ) return c # 使用 a = torch.randn(1024, 4096, dtype=torch.float16, device='cuda') b_int32 = torch.randint(0, 255, (4096//8, 4096), dtype=torch.int32, device='cuda') c = Int4MatMul.apply(a, b_int32, None, None) # 成功!这个例子虽小,但已涵盖Infra实操的核心要素:硬件能力确认、kernel编写、内存布局理解、PyTorch集成。跑通它,你就跨过了Infra工程师的第一道门槛。
4.2 构建Infra可观测性:用100行代码给算子装上“心电图”
没有监控的Infra优化是盲人摸象。我们给每个关键算子加了三层监控:
第一层:基础指标(Per-kernel)
用torch.cuda.Event测精确耗时:
start = torch.cuda.Event(enable_timing=True) end = torch.cuda.Event(enable_timing=True) start.record() output = custom_rmsnorm(input) # 你的算子 end.record() torch.cuda.synchronize() latency_ms = start.elapsed_time(end)第二层:资源水位(Per-GPU)
用pynvml实时抓显存、util、temperature:
import pynvml pynvml.nvmlInit() handle = pynvml.nvmlDeviceGetHandleByIndex(0) mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle) util = pynvml.nvmlDeviceGetUtilizationRates(handle) # 打印:显存已用 12.4/24.0 GB,GPU利用率 87%第三层:关联分析(End-to-end)
在推理服务入口打trace:
from opentelemetry import trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import TracerProvider provider = TracerProvider() processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://jaeger:4318/v1/traces")) provider.add_span_processor(processor) # 在request handler中 with tracer.start_as_current_span("llm_inference") as span: span.set_attribute("input_length", len(prompt)) with tracer.start_as_current_span("prefill") as prefill_span: logits = model.prefill(prompt) # 这里会触发所有prefill算子 with tracer.start_as_current_span("decode") as decode_span: for i in range(100): token = model.decode(logits) # 触发decode算子 logits = model.forward(token)这三层监控数据汇总到Grafana,形成“算子心电图”:横轴是时间,纵轴是latency(蓝线)、显存占用(绿线)、GPU util(红线)。当某次发布后P99飙升,你一眼就能看出是哪个算子的latency毛刺,还是显存泄漏导致util持续高位。这才是Infra工程师的“作战地图”。
4.3 分布式Infra实战:FSDP训练中如何定位NCCL通信瓶颈
大模型训练绕不开FSDP(Fully Sharded Data Parallel)。但线上常遇到:明明GPU计算util只有40%,训练速度却上不去。这时90%的问题出在NCCL通信上。
诊断三板斧:
- nccl-trace:NCCL自带的trace工具,生成timeline文件
export NCCL_TRACE=1 export NCCL_DEBUG=INFO python train.py # 会输出nccl_trace_*.txt - nsys profile:NVIDIA系统级profiler,可视化通信与计算重叠
nsys profile -t nvtx,cuda,nvsmi --capture-range=cudaProfilerApi -o nsys_report python train.py - 手动注入probe:在FSDP hook中打点
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP from torch.distributed.fsdp.fully_sharded_data_parallel import TrainingState def pre_all_reduce_hook(module, input): if module.training_state == TrainingState.IDLE: torch.cuda.synchronize() # 确保计算完成 start = torch.cuda.Event(enable_timing=True) start.record() return input def post_all_reduce_hook(module, input, output): end = torch.cuda.Event(enable_timing=True) end.record() torch.cuda.synchronize() print(f"all_reduce latency: {start.elapsed_time(end):.2f}ms") model.register_comm_hook(state=None, hook=post_all_reduce_hook)
真实案例:
某70B模型FSDP训练,理论吞吐应达120 tokens/sec,实测仅68。用nsys打开timeline,发现all-reduce操作(红色块)与compute(绿色块)几乎不重叠,大量时间在等通信。进一步查nccl-trace,发现all_reduce耗时高达28ms(理论应<5ms)。原因:集群使用InfiniBand,但NCCL未启用IB,走的是慢速TCP。解决:
export NCCL_IB_DISABLE=0 export NCCL_IB_GID_INDEX=3 # 指定RoCE v2 GID export NCCL_SOCKET_IFNAME=ib0 # 绑定IB网卡重启后all-reduce降至3.2ms,吞吐升至118 tokens/sec。Infra工程师的价值,往往就藏在这种“一行环境变量”的修复里。
5. 常见问题与排查技巧实录:那些没人告诉你的Infra踩坑现场
5.1 “显存没超,但CUDA out of memory”——显存碎片化的幽灵
现象:模型在batch=4时正常,batch=5报OOM,但nvidia-smi显示显存只用了18/24GB。
原因:PyTorch的显存分配器(caching allocator)会产生碎片。当需要一块连续的1.2GB显存时,虽然总空闲2GB,但最大连续块只有800MB。
排查:
# 在OOM前加 print(torch.cuda.memory_summary()) # 查看"allocated by request" vs "largest block"解决:
- 启用
torch.cuda.empty_cache()强制回收(治标) - 更根本:用
torch.compile+mode="reduce-overhead",它会重排内存申请顺序 - 或直接上
torch._inductor.config.memory_planning = False禁用内存规划(适合小模型)
实操心得:我们给所有训练脚本加了“显存体检”hook:每10个step打印一次
memory_summary(),一旦发现最大连续块<总空闲的60%,自动触发empty_cache()并告警。这比等OOM再救火强十倍。
5.2 “模型在A100上飞快,在4090上卡成PPT”——硬件特性误判
现象:同一份代码,在数据中心A100上QPS 300,在本地4090上只有80,且GPU util长期<30%。
原因:4090的L2 cache只有18MB(A100是40MB),而默认kernel的tile size是为A100设计的,导致4090上cache miss rate高达75%。
排查:
# 用Nsight Compute profiling ncu -o ncu_4090 --set full python infer.py # 查看L2__t_sectors_op_read.sum 和 L2__t_sectors_op_write.sum # 若远高于理论值,说明cache效率差解决:
- 缩小tile size:把
BLOCK_SIZE_M=128改为64,让working set fit进L2 - 启用
--unroll-threshold=100让编译器更激进地展开循环,减少loop overhead - 最终在4090上QPS升至260,GPU util达78%
5.3 “推理服务P99忽高忽低,但平均延迟很稳”——请求队列与冷启效应
现象:API服务平均延迟65ms,但P99高达320ms,且毛刺无规律。
原因:冷启(cold start)——新请求来时,模型权重尚未加载到GPU显存,需从CPU内存拷贝,耗时200ms+。
排查:
- 用
strace -e trace=memory看进程是否在mmap或memcpy - 用
nvidia-smi dmon -s u -d 1看GPU util是否在请求来时突降(说明在等数据搬移)
解决: - Warmup机制:服务启动后,主动执行一次dummy inference,强制加载权重
- Prefetch策略:在请求解析后、实际计算前,异步prefetch下一批请求需要的KV Cache
- Request Batching:用vLLM的continuous batching,让GPU始终有活干,掩盖冷启
我们曾在一个医疗问答服务上实施warmup,P99从320ms直降到78ms,且曲线变得极其平滑。Infra优化有时就是这么朴素:让机器“提前准备好”,而不是“等客人来了再烧水”。
5.4 “分布式训练loss震荡,但单卡完美收敛”——梯度同步的精度陷阱
现象:FSDP训练loss在0.8~1.5之间大幅震荡,单卡训练loss平稳下降至0.2。
原因:FP16梯度all-reduce时,小梯度值在累加过程中被截断(underflow)。
排查:
- 用
torch.autograd.gradcheck验证梯度数值正确性 - 在all-reduce前打印
grad.abs().min(),若<1e-5则危险
解决: - 启用
mixed_precision=fp32_grad(FSDP参数),让梯度以FP32同步 - 或用
torch.cuda.amp.GradScaler自动缩放,但需确保scaler.step(optimizer)前所有梯度已同步 - 我们最终采用FP32 grad sync,loss震荡消失,收敛速度反超单卡12%(因更大有效batch size)
这张常见问题速查表,是我们团队三年踩坑经验的结晶。它不教你高深理论,只告诉你:当问题出现时,第一步该看什么、第二步该改什么、第三步该验证什么。Infra的世界没有银弹,只有一个个被验证过的“下一步”。
6. Infra工程师的日常:在算法与硬件的夹缝中,做最务实的平衡者
凌晨两点,某大模型推理服务的P99延迟突然从80ms跳到220ms。值班的Infra工程师没去看模型代码,而是先打开Grafana,三秒锁定:GPU显存占用曲线出现锯齿状上升,每分钟涨200MB,两小时后OOM。他立刻执行kubectl exec -it pod-name -- nvidia-smi -q -d MEMORY,确认是显存泄漏。接着用py-spy record -p $(pgrep python) --duration 60抓取Python stack,发现是某个自定义算子的torch.cuda.Stream未正确synchronize,导致旧tensor引用未释放。他打了hotfix patch,15分钟后服务恢复。
这不是电影情节,是我们上周的真实case。Infra工程师的工作日常,就是在这类“既不像算法也不像运维”的灰色地带里,用扎实的硬件知识、严谨的工程习惯、快速的定位能力,兜住AI落地的最后一公里。
他们不追求论文引用数,但代码commit里藏着让模型吞吐翻倍的kernel;他们不参加顶会演讲,但写的benchmark报告决定了公司采购哪款AI芯片;他们可能连Transformer的self-attention公式都背不全,却能一眼看出NCCL通信拓扑哪里没配对。
所以,当别人在惊叹大模型的智能时,请记住:真正让这种智能稳定、高效、低成本地流淌进千行百业的,是那群在深夜盯着GPU utilization曲线、在CUDA kernel里反复调整block size、在nccl-trace日志里逐行比对时间戳的人。他们不是站在舞台中央的明星,但没有他们,整场演出根本无法开场。
我个人在实际操作中的体会是:Infra能力不是“有没有”,而是“深不深”。一个能调参的算法工程师,和一个能把RMSNorm kernel从1.2ms压到0.31ms的Infra工程师,解决的是完全不同的问题层级。前者让模型“能用”,后者让模型“好用”。而今天的大模型战场,胜负手往往就在那0.9ms的差距里。