1. 项目概述当大模型在屏幕上“说话”谁在后台拧紧每一颗螺丝“算子”这个词第一次听的人常以为是数学课上的符号——加减乘除那个“算”带个“子”字显得文绉绉。但如果你真去翻过PyTorch的C源码或者调试过一次CUDA kernel launch失败的报错就会明白“算子”不是概念是焊在GPU显存地址上的函数指针是调度器里毫秒级抢占的执行单元是模型跑起来前必须被编译、注册、校验、对齐、量化、融合的最小可执行原子。而“AI Infra”更不是PPT里的时髦缩写——它是一整套沉默运转的工业级系统从凌晨三点自动扩容的K8s集群到GPU显存碎片率超过72%时触发的内存压缩策略从分布式训练中跨千卡通信的NCCL拓扑感知重排到推理服务里请求排队超50ms就自动降级的熔断逻辑。标题里说的“看不见的那群人”不是躲在机房穿白大褂的神秘角色而是每天在CI流水线崩溃日志里逐行比对ncu trace、在OOM错误堆栈里反向定位Tensor生命周期、为一个batch_size1的冷启延迟多压榨出83微秒而重写三次kernel的工程师。他们不写prompt不调temperature不画loss曲线——但他们写的代码决定了你输入的那句“写一首春天的诗”是0.8秒后返回还是根本卡死在预填充阶段。这篇文章不讲大模型怎么炼成也不教你怎么微调LoRA而是带你钻进那堵“看不见的墙”背后看清楚当LLM成为水电一样的基础设施支撑它的底层钢架、铆钉、应力监测仪和备用发电机究竟是怎么设计、怎么部署、怎么天天救火又悄悄升级的。适合所有想真正理解AI系统全链路的开发者、架构师、技术决策者以及那些已经能跑通demo、却总在上线时被“为什么这么慢”“为什么突然OOM”“为什么A卡能跑B卡报错”反复暴击的实战派。2. 内容整体设计与思路拆解为什么“看不见”因为所有可见的都建立在不可见的确定性之上2.1 “算子”不是功能模块而是性能边界的物理刻度很多人把“算子”理解成“模型里的一个计算步骤”比如torch.nn.Linear对应一个矩阵乘加。这没错但远远不够。真正的算子工程本质是在硬件物理约束和数学语义正确性之间找那个最窄的可行缝隙。举个具体例子FlashAttention-2的实现表面看只是优化了attention计算但它的核心突破在于三件事第一把原本需要两次全局内存读写的softmax归一化压缩成一次HBM带宽敏感的tiled计算第二显式管理shared memory生命周期让每个SMStreaming Multiprocessor在计算QK^T时能复用已加载的Q块避免重复访存第三引入split-k策略在长序列场景下把reduce维度拆到多个block并行规避warp-level同步瓶颈。这三步没有一步是纯算法创新——全是针对A100/H100的L2 cache大小40MB、shared memory上限164KB/block、warp调度粒度32线程做的硬编码适配。所以当你看到某个模型在A100上吞吐320 tokens/s在H100上却掉到290 tokens/s问题很可能不在模型本身而在某个自定义算子没做H100的tensor core sparsity指令适配导致本该用FP16INT4混合精度的GEMM退化成了纯FP16计算。这就是为什么Infra团队要自己写算子通用框架提供的op是“能跑”而业务需要的是“跑得刚好卡在硬件吞吐极限上”。2.2 “AI Infra”不是一堆工具拼凑而是以SLA为纲的闭环控制系统市面上讲AI Infra的文章十有八九罗列一堆工具Kubeflow、MLflow、Ray、vLLM……但这就像只告诉你汽车有方向盘、油门、刹车却不提ESP车身稳定系统怎么根据轮速传感器数据每毫秒调整单个车轮制动力。真实的AI Infra是一个以服务等级协议SLA为唯一指挥棒的反馈控制系统。我们某次上线一个7B模型的API服务SLA要求P99延迟≤1.2秒可用性≥99.95%。为达成这个目标Infra层必须同时协调至少五个子系统资源编排层K8s Operator不能只看GPU显存还要结合NVLink带宽利用率85%时触发跨节点拆分和PCIe拥塞指数阈值则拒绝新pod调度计算加速层vLLM的PagedAttention必须开启prefill/decode分离调度且page size严格设为2MB匹配A100的HBM channel宽度否则内存碎片会导致实际可用显存下降18%流量治理层Envoy网关需配置三级熔断连接池满载时降级为异步队列请求排队超800ms触发动态batching合并连续3次decode超时则自动切到CPU fallback模式可观测层除了常规的GPU温度、显存占用必须埋点记录每个request的kv-cache命中率、paged attention page fault次数、CUDA stream stall时长——这些才是预测OOM的黄金指标弹性伸缩层HPA不能只看CPU/GPU利用率而要用自定义metrics当P95 decode延迟连续2分钟900ms且kv-cache miss rate 35%才触发scale-out避免盲目扩容导致NCCL通信雪崩。你看所有工具都是零件而Infra是让这些零件咬合成一个能自我调节的有机体。所谓“看不见”是因为一旦设计到位你只会看到SLA稳稳达标只有当某个环节松动故障才会像多米诺骨牌一样暴露出来——而那时你看到的永远只是最后一张倒下的牌。2.3 为什么这群人必须“隐身”因为他们的工作成果是“不发生故障”这是最反直觉也最关键的一点。一个前端工程师做出炫酷动画产品经理立刻能截图汇报算法研究员调出更高BLEU分数论文马上能发顶会。但Infra工程师的巅峰成就是整个季度没有一次P0事故是线上服务全年平均延迟波动小于±3%。这种“负向价值”导致他们的工作长期被低估——直到某天因为一个未合入的CUDA内存泄漏补丁导致集群GPU显存缓慢爬升第7天凌晨集体OOM所有推理服务中断23分钟。这时所有人突然发现原来那套自动内存回收机制不是理所当然存在的。Infra的价值本质上是把不确定性转化为确定性。比如模型开发者说“我需要支持动态batch size”Infra团队就要回答“在batch_size∈[1,256]范围内P99延迟标准差必须50ms这意味着我们必须在runtime层实现zero-copy的tensor pool且pool size按99.9分位请求长度预分配”。再比如“支持多模态输入”这个需求Infra要拆解为视觉encoder的CUDA graph capture必须与文本decoder的stream同步否则跨模态token对齐会产生亚毫秒级时序漂移导致生成结果错乱。所有这些都不产生新功能只保障已有功能在任何负载下都“如常运行”。所以他们隐身不是因为不重要而是因为工作目标就是让自己变得“不可见”——就像空气只有消失时才被察觉。3. 核心细节解析与实操要点从一行CUDA代码到万卡集群的因果链3.1 算子开发在寄存器级别和数学精度之间走钢丝写一个高性能算子远不止“用CUDA写个kernel”那么简单。以我们实际优化过的RMSNorm为例原始PyTorch实现是x / sqrt(mean(x^2) eps)看似简单但直接翻译成CUDA会踩三个坑第一坑数值稳定性陷阱。GPU的FP16计算单元在求x^2时若x绝对值65504FP16最大值直接溢出为inf。但我们不能简单切到FP32——那样会吃光shared memory带宽。解决方案是在warp内做两级reduction先用FP16算局部平方和再用warp shuffle把8个warp的结果传给warp 0由warp 0用FP32累加并开方。这样99%计算在FP16仅0.1%关键路径用FP32性能损失2%。第二坑内存访问模式。原始实现按行遍历导致global memory访问完全不连续。我们改用“tiling transpose”策略先把32x32 tile的数据转置加载到shared memory再按列计算均值——这样每个warp的32个thread恰好读取连续32个floatHBM带宽利用率从42%提升到89%。第三坑指令级并行ILP浪费。原kernel里sqrt()和div()指令串行但GPU的SFUSpecial Function Unit和ALU可以并行。我们手动展开循环让thread 0计算sqrtthread 1同时计算div通过__syncthreads()协调单次norm计算延迟降低17%。提示所有这些优化必须配合Nsight Compute做详细分析。重点关注achieved_occupancy应85%、inst_per_warp越高越好、gld_efficiencyglobal load效率80%说明访存有问题。别信理论峰值信trace数据。3.2 分布式训练通信不是管道而是需要主动整形的流体很多人以为分布式训练瓶颈在GPU算力其实80%的性能损失来自通信。我们曾为一个13B模型做DDP优化发现即使使用NCCL 2.18all-reduce耗时仍占step时间的37%。根因不是带宽不够而是通信拓扑与物理网络不匹配。A100服务器通常有8卡通过NVSwitch互联但默认NCCL会把所有卡视为平等节点跨NVSwitch的通信走PCIe延迟是片内通信的5倍。解决方案是强制指定NCCL_SOCKET_NTHREADS8和NCCL_NSOCKS_PERTHREAD4提升socket并发更重要的是用NCCL_IB_DISABLE1禁用InfiniBand当时集群没配IB并设置NCCL_SHM_DISABLE0启用共享内存加速进程间通信。但最关键的一步是在启动脚本里注入export NCCL_TOPO_FILE/path/to/custom_topo.xml这个XML文件必须精确描述每台机器的PCIe switch层级、NVSwitch连接关系、NUMA node绑定——我们手动生成的topo文件让all-reduce延迟下降了63%。另一个隐形杀手是梯度稀疏化。当模型参数达百亿级全量梯度all-reduce带宽压力巨大。我们采用Top-K sparsification每step只同步梯度绝对值最大的0.1%参数。但这里有个致命细节——K值不能固定因为不同layer梯度分布差异极大embedding层梯度稀疏FFN层密集。我们改为按layer动态计算KK_layer int(0.001 * num_params_layer * (std_grad_layer / mean_grad_layer))用梯度变异系数自适应调整稀疏度。实测在保持收敛性的前提下通信量减少76%训练速度提升2.1倍。3.3 推理服务延迟不是标量而是概率分布的实时拟合线上推理的“延迟”从来不是单一数字而是一个随时间漂移的概率分布。我们监控过一个72B模型的P99延迟发现它在每天上午10点准时上升120ms——查了一周才发现是公司统一杀毒软件在那个时间点扫描所有容器的/proc目录导致大量page fault。这说明Infra必须把宿主环境当作一等公民建模。为此我们构建了三层延迟防护体系硬件层在K8s node上部署eBPF程序实时采集run_queue长度、page-fault速率、cpu-cycles停顿事件。当run_queue 15且major-faults/sec 200持续30秒自动触发cgroup v2的cpu.weight下调20%优先保障推理进程框架层vLLM的max_num_seqs不能设固定值。我们用滑动窗口统计过去5分钟的request arrival rate动态计算max_num_seqs min(256, int(arrival_rate * 1.8))1.8是经验值平衡吞吐与延迟应用层每个response header里注入X-Latency-Dist: p50321ms,p90789ms,p991240ms让上游服务能根据自身SLA选择是否等待。比如搜索服务可接受p90延迟就只看789ms而金融风控必须p99就严格按1240ms设timeout。注意千万别用“平均延迟”做告警我们吃过亏——某次平均延迟仅上升8ms但p99暴涨至3.2秒原因是少数长尾请求触发了CPU fallback而平均值被海量短请求拉低了。告警必须基于分位数且p99/p999要分开配置。4. 实操过程与核心环节实现一次典型Infra故障的完整复盘4.1 故障现象P99延迟突增300%但GPU显存、CPU、网络一切正常这是Infra工程师最头疼的场景监控面板绿油油用户投诉却像雪片一样飞来。我们某次遇到的情况是一个部署在A100上的13B模型APIP99延迟从1.1秒骤升至4.3秒持续17分钟期间GPU显存占用稳定在78%NVLink带宽30%Prometheus所有指标无异常。第一步我们跳过所有“看起来正常”的指标直奔最原始的日志kubectl logs -f pod-name | grep decode_step。发现decode阶段日志出现大量[WARN] CUDA stream stalled for 120ms。这说明问题不在模型或数据而在CUDA执行流被阻塞。4.2 根因定位从CUDA trace到Linux内核调度的全链路追踪我们立即在pod里执行# 1. 抓取CUDA执行trace需提前安装Nsight Systems nsys profile -t cuda,nvtx --capture-rangecudaProfilerApi -f true -o /tmp/trace python inference.py # 2. 分析trace发现关键线索所有kernel launch间隔出现规律性110ms空白 # 3. 进而检查Linux调度cat /proc/pid/status | grep -E (voluntary|nonvoluntary)_ctxt_switches # 发现nonvoluntary_ctxt_switches每秒激增到1200正常50非自愿上下文切换飙升指向CPU被强占。继续查# 4. 查看进程CPU亲和性 taskset -cp pid # 输出pid 12345s current affinity list: 0-7 # 但服务器有16核为何只绑8核查启动脚本发现Docker run时漏了--cpuset-cpus0-15 # 5. 更致命的是检查cgroup cat /sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod-*/container-id/cpu.shares # 输出1024默认值而GPU进程需要独占CPU带宽真相浮出水面容器启动时未指定CPU亲和性Linux CFS调度器把GPU计算线程和日志刷盘线程混排在同一组CPU core上当logrotate每小时触发一次大量IO wait导致GPU线程被频繁抢占。而cpu.shares1024意味着它只获得1份CPU时间片在16核机器上实际份额不足7%。4.3 解决方案与验证用cgroup和schedtool做手术刀式修复我们没重启服务而是在线热修复# 1. 为GPU进程单独创建cgroup mkdir -p /sys/fs/cgroup/cpu/gpu_workload echo 8000 /sys/fs/cgroup/cpu/gpu_workload/cpu.shares # 占80% CPU份额 # 2. 将进程迁入新cgroup echo pid /sys/fs/cgroup/cpu/gpu_workload/cgroup.procs # 3. 绑定到专用CPU core避开IO密集型core taskset -cp 8-15 pid # 4. 调整调度策略为SCHED_FIFO避免被普通进程抢占 sudo chrt -f 50 pid执行后nonvoluntary_ctxt_switches秒降为0P99延迟回落至1.05秒。但这次故障教会我们Infra的终极战场不在GPU而在CPU调度器和Linux内核参数。此后我们所有GPU容器启动脚本强制加入# Kubernetes pod spec securityContext: privileged: true sysctls: - name: kernel.sched_migration_cost_ns value: 5000000 # 延长迁移成本减少不必要的migration - name: vm.swappiness value: 1 # 极致降低swap倾向4.4 长期加固把经验沉淀为自动化防御规则单次修复治标自动化防御治本。我们基于这次故障新增三条SRE规则CPU亲和性巡检每日凌晨用Prometheus exporter扫描所有GPU pod检查taskset -cp pid输出是否覆盖12个core未达标自动告警并触发修复jobcgroup份额校验在K8s admission webhook里拦截GPU pod创建请求若未设置resources.limits.cpu或cpu.shares5000直接拒绝调度策略监控用eBPF程序实时跟踪chrt -p pid输出若发现GPU进程调度策略不是SCHED_FIFO或优先级40立即修正。现在这套规则已覆盖全部237个GPU服务类似故障归零。Infra的成熟度就体现在能把每一次救火变成下次自动灭火的消防栓。5. 常见问题与排查技巧实录Infra工程师的私藏checklist5.1 “模型在本地跑得飞快一上生产就慢三倍”——五层穿透排查法这个问题太经典我们总结出必须按顺序检查的五层层级检查项快速验证命令典型症状硬件层GPU是否被其他进程占用NVLink是否启用nvidia-smi -q -d MEMORY,UTILIZATION,COMPUTE,NVLINKutilization.gpu低但memory.used高nvl.link显示Inactive驱动/固件层驱动版本是否匹配GPU固件是否最新nvidia-smi -qgrep Driver Version|Firmware容器层是否启用GPU共享cgroup限制是否过严cat /sys/fs/cgroup/memory/kubepods/.../memory.limit_in_bytesnvidia-smi显示显存充足但torch.cuda.memory_allocated()报OOM框架层CUDA context是否泄漏是否启用CUDA graphnvidia-smi --query-compute-appspid,used_memory --formatcsv进程存活但显存持续缓慢增长应用层是否误用torch.no_grad()KV cache是否复用在inference loop里加print(torch.cuda.memory_summary())每次request后显存不释放cache miss率90%实操心得永远从硬件层开始别一上来就怀疑代码。我们曾为一个“变慢”问题折腾三天最后发现是机房空调故障导致GPU温度85℃触发了硬件降频——nvidia-smi里clocks.current.graphics从1410MHz掉到900MHz性能直接腰斩。5.2 “训练loss突然nan但梯度检查全正常”——浮点地狱的七种死法NaN不是bug是硬件在向你哭诉。我们整理过最常见的七种触发场景学习率过大lr1e-2对AdamW训13B模型前3步就nan。解决方案用linear warmupwarmup steps设为总steps的0.5%梯度爆炸clip_grad_norm_1.0不够要设0.3且在optimizer.step()前加torch.nn.utils.clip_grad_value_(model.parameters(), clip_value0.1)双保险Softmax输入过大x.max() 88时FP32 softmax会nan。必须在F.softmax(x, dim-1)前加x x - x.max(dim-1, keepdimTrue)[0]Log(0)陷阱交叉熵里label为0的概率输出log(0)。用torch.nn.CrossEntropyLoss(label_smoothing0.1)LayerNorm输入全零某些初始化bug导致某层输入全0LN的var0导致1/sqrt(0)。在LN forward里加var var 1e-6FP16 underflow极小梯度在FP16下变为0后续计算nan。必须用torch.cuda.amp.GradScaler且init_scale设为2**16CUDA随机数bugtorch.randn()在特定driver版本下nan。改用torch.empty(...).normal_()。关键技巧在训练脚本开头加torch.autograd.set_detect_anomaly(True)它会让nan出现时直接打印出错的backward路径比看loss曲线快十倍。5.3 “为什么我的vLLM服务P99延迟忽高忽低”——动态batching的隐藏开关vLLM的dynamic batching是把双刃剑。我们发现P99抖动的根本原因是max_num_batched_tokens这个参数没设对。它的计算公式应该是max_num_batched_tokens max_seq_len × (target_batch_size 1)其中target_batch_size不是你期望的并发数而是按P95请求长度计算的期望并发。比如95%请求长度≤512P95延迟要求≤1.2秒测得单request decode耗时≈380ms则理论最大并发 1200ms ÷ 380ms ≈ 3.15 → 取整为3所以max_num_batched_tokens 512 × (3 1) 2048如果设成4096系统会强行凑够4096 tokens才dispatch导致短请求等待过久如果设成1024则频繁dispatch小batchNCCL通信开销占比飙升。我们用一个Python脚本实时计算最优值# 根据prometheus metrics动态调整 def calc_optimal_batching(): p95_len get_prom_metric(request_length_p95) # 从Prometheus API获取 p95_latency get_prom_metric(decode_latency_p95) target_concurrent int(1200 / p95_latency) # ms to ms return int(p95_len * (target_concurrent 1))然后通过vLLM的--max-num-batched-tokens参数热更新。实测后P99标准差从±320ms降到±47ms。5.4 Infra工程师的终极武器库不依赖文档的现场诊断术所有官方文档都不会写的救命技巧查CUDA内存泄漏不用nvidia-smi用cuda-memcheck --leak-check full python script.py它能精确定位到哪行cudaMalloc没配对cudaFree抓NCCL通信瓶颈NCCL_DEBUGINFO NCCL_ASYNC_ERROR_HANDLING0 python train.pyINFO日志里会打印每次all-reduce的实际字节数和耗时比nvidia-smi dmon准十倍绕过K8s网络查pod真实IPkubectl exec pod -- ip addr show eth0 | grep inet 有时Calico网络插件配置错误kubectl get pod -o wide显示的IP根本不是pod真实IP强制GPU重置当nvidia-smi卡死sudo nvidia-smi -r无效时用echo 1 | sudo tee /sys/bus/pci/devices/0000:XX:00.0/remove echo 1 | sudo tee /sys/bus/pci/rescan硬重置PCIe设备XX是lspci查到的GPU bus号。最后分享一个血泪教训永远在Infra服务里埋一个/healthz?debugtrue端点。它不返回JSON而是直接打印当前进程的/proc/pid/status、/proc/pid/stack、nvidia-smi -q -d MEMORY三合一快照。某次深夜故障运维同事靠这个端点30秒内定位到是CUDA context泄漏比翻日志快20分钟。Infra的优雅就藏在这些不起眼的debug开关里。6. 从“算子”到“AI Infra”一场关于确定性的漫长修行我第一次写CUDA算子时以为只要kernel跑通就行。结果在A100上测试延迟1.2ms一上生产集群就飙到8.7ms。花两天时间才发现是服务器BIOS里关闭了SR-IOV导致PCIe带宽被虚拟化层截留了40%。那一刻突然明白所谓Infra就是把所有你以为“理所当然”的环节全部拆开、测量、建模、控制。它不像算法研究追求突破也不像应用开发追求体验它追求的是一种近乎偏执的确定性——确定在1000QPS下P99延迟不会漂移超过5%确定在GPU显存剩余12MB时能优雅拒绝新请求而非OOM崩溃确定当某台机器宕机时服务可用性从99.95%跌到99.949%而不是99.5%。这种确定性没有光环它沉默地躺在每行CUDA代码的边界检查里藏在每个K8s Pod的resource limit中凝固在每次NCCL通信的拓扑校验时。所以当别人问“大模型背后那群人在忙什么”答案不是“写代码”或“搭平台”而是他们在用工程的刻度尺一毫米一毫米地丈量混沌世界的边界并把丈量结果锻造成托起智能时代的钢铁基座。这活儿枯燥、琐碎、常被忽视但只要人类还想让AI可靠地运转下去这群人就永远站在光无法照到的地方拧紧每一颗螺丝。