2. GPU推理服务运行机制
要定位GPU推理服务的故障,先要建立一条清晰的“请求生命周期”心智模型。一次推理请求从客户端发出到拿到结果,通常要穿过网关、排队与批处理调度、模型前向执行、结果后处理与返回五个阶段。每个阶段都有独立的资源约束和典型故障模式,理解它们之间的耦合关系,比记住某个报错更有价值。
一、请求接入:从HTTP到推理队列
推理服务对外通常暴露HTTP/gRPC接口。以Triton Inference Server为例,客户端POST到/v2/models/<model>/infer,请求体包含输入张量、数据类型和形状。接入层负责协议解析、鉴权、请求校验,然后把请求封装成内部任务投递到调度队列。
这一层的常见故障点包括:请求体过大导致解析超时;输入shape与模型签名不匹配被直接拒绝;gRPC连接数打满导致新请求排队在TCP层而非应用层,表现为客户端超时但服务端日志“无请求”。排查时应先看接入层QPS与错误码分布,再确认是否进入应用队列。
二、批处理调度:动态批处理与优先级
GPU擅长并行,但单条请求往往无法填满算力。动态批处理(dynamic batching)把短时间内到达的多条请求合并成一个batch,显著提升吞吐。调度器维护一个等待窗口,例如max_batch_size=32、max_queue_delay_microseconds=5000,在窗口内凑批,超时则用当前已有请求发车。
调度层的核心矛盾是延迟与吞吐的权衡:窗口越大,batch越满、吞吐越高,但尾延迟上升;窗口越小,响应快但GPU利用率低。常见故障是“批处理饥饿”——请求到达稀疏,每个batch只有1~2条,GPU利用率长期低于30%;或“批处理拥塞”——突发流量下队列积压,P99延迟飙升。判断依据是观察batch size分布与队列深度的时间序列。
三、模型执行:GPU上的真实开销
调度器把batch交给模型后,执行层要做三件事:把输入张量拷贝到显存、按计算图依次启动CUDA Kernel、把输出拷回主机内存。以ResNet-50为例,一个batch=32的推理大约需要15~25ms,其中Kernel执行占大头,但H2D/D2H拷贝和Kernel启动开销也不可忽略。
执行层最典型的故障是显存OOM。它可能发生在模型加载阶段(权重+激活值超出显存),也可能发生在运行阶段(batch过大或并发流过多)。另一个隐蔽问题是CUDA流同步:如果多个请求共享同一个流且未正确同步,会出现结果错乱或死锁。排查时可用nvidia-smi观察显存与利用率,用Nsight Systems抓取Kernel时间线。
四、结果返回与端到端延迟分解
后处理阶段负责把GPU输出转换成业务可读的结果,例如分类任务取argmax、检测任务做NMS、生成任务做token解码。这一步通常在CPU上完成,如果逻辑复杂或数据量大,可能成为新的瓶颈。
把端到端延迟拆开看,有助于快速定位:总延迟 = 网络传输 + 排队等待 + 批处理窗口 + 前向计算 + 后处理 + 返回传输。假设一次请求总耗时120ms,其中排队40ms、批处理窗口5ms、前向计算50ms、后处理15ms、网络往返10ms,那么优化重点显然在排队和计算,而不是后处理。
五、故障定位的链路思维
很多故障不是单点问题,而是链路耦合的结果。例如客户端报超时,可能是接入层连接池满,也可能是调度层排队过长,还可能是执行层某张卡掉卡导致batch卡住。正确的做法是沿着请求链路逐段验证:先确认请求是否到达服务端,再确认是否进入队列,再确认是否发车执行,最后确认结果是否写出。
下表给出一个简化的排查对照,帮助把现象映射到可能的阶段:
| 现象 | 优先怀疑阶段 | 验证手段 |
|---|---|---|
| 客户端超时,服务端无日志 | 接入层 / 网络 | 检查连接数、TCP重传、网关日志 |
| 延迟高但GPU利用率低 | 调度层 | 观察队列深度与batch size分布 |
| GPU利用率高但吞吐上不去 | 执行层 | 检查Kernel效率、显存带宽、是否频繁同步 |
| 结果错误或乱序 | 执行层 / 后处理 | 检查流同步、批处理索引映射、解码逻辑 |
| 偶发OOM | 执行层 | 监控显存峰值、限制并发batch数 |