拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LLM 网关健康检查探针:基于微测试 Prompt 的后端节点实时可用性嗅探

LLM 网关健康检查探针:基于微测试 Prompt 的后端节点实时可用性嗅探

LLM 网关健康检查探针:基于微测试 Prompt 的后端节点实时可用性嗅探

在生产环境运维大模型推理集群(vLLM、TGI、TensorRT-LLM)时,传统基于 HTTP 状态码的/healthz或 TCP 端口探测存在致命盲区。很多时候,Web 服务进程存活、端口正常响应 200,但底层 GPU 的 CUDA Context 已经僵死,或者由于 KV Cache 碎片化、显存泄漏触发了CUDA error: out of memory/illegal memory access。此时,外部流量一旦路由到该节点,请求会直接卡死在排队队列中直至客户端超时,甚至拉垮整个网关的连接池。

要保障 LLM 统一网关的路由可靠性,必须将探针深度从“进程级”下沉到“推理内核级”。基于微测试 Prompt(Micro-Probe Prompt)的实时可用性嗅探机制,正是为了解决这一痛点。

传统探针的虚假健康陷阱

传统健康检查通常只验证 FastAPI/Triton 是否存活,完全无法覆盖真实的推理管线故障:

  1. CUDA 驱动隐匿崩溃:显卡硬件报错或 Xid 错误发生后,Python 守护进程未退出,/healthz接口依然返回 200,但调用engine.generate()会抛出底层 C++ 异常或永久阻塞。
  2. PagedAttention 显存锁定:连续的高并发请求退去后,显存池中存在死锁句柄,调度器无法为新请求分配 Slot,导致新请求无限排队。
  3. 模型权重静默损坏:分布式张量并行(TP)场景下,某张卡通过 NVLink 同步超时,导致前向计算 hang 住。

普通的探测无法感知这类内核状态。如果网关等到用户真实请求因超时抛错(如 60s 级超时)才将节点摘除,线上早已产生大面积报警和资损。必须由网关发起高频、轻量且具备语义校验能力的微探针。

微测试 Prompt 设计准则

设计用于健康探测的 Prompt,核心原则是极小算力开销、极短耗时、明确确定性结果、严格绕过或隔离业务缓存:

  • 输入输出极小化:输入限制在 1~3 个 Token(如hi或1+1=),要求最大输出 Token 为 1(如回复!或2)。单次探测对 GPU 计算资源的占用应在 0.1% 以下。
  • 强制禁用采样与温度:设置temperature: 0.0,top_p: 1.0,确保推理路径最短且结果完全确定。
  • 隔离 KV 缓存:如果开启了 Prefix Caching(前缀缓存),探针请求必须携带特殊标志或随机前缀头,避免微测试 Prompt 命中缓存导致未真正执行 GPU 矩阵乘法计算,同时避免探测数据挤占业务的高频 KV 缓存页。
  • 严苛的 TTFT(首字延迟)超时阈值:正常节点处理 1 个 Token 的 TTFT 通常在 30ms~150ms 之间。若探针在 300ms 内未收到 首个 Chunk,直接判定该节点处于拥塞或降级状态,无需等待生成结束。

探针状态机与三级判定

网关内部维护每个 GPU 节点的健康状态机:

  • Healthy(健康):探针连续 3 次在 200ms 内完成首字输出,承载全部业务流量。
  • Degraded(降级):探针响应时间在 200ms~800ms 之间,或偶发 1 次超时。网关自动削减该节点的路由权重(降低 70%),限制新并发连接数,仅处理低优先级请求。
  • Unhealthy(下线):连续 2 次探针超时(>800ms)或捕获到 CUDA 级错误代码。网关立即执行硬件级熔断,彻底将节点从活跃池摘除,同时触发后台运维告警,联动 K8s 进行 Pod 重建或热重启推理引擎。

Go 1.27.1 网关层并发嗅探器实现

以下为在 Go 统一网关内部实现的自适应探针核心逻辑,包含定时并发嗅探、TTFT 超时控制以及原子状态迁移:

package probe import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "sync/atomic" "time" ) type NodeState int32 const ( StateHealthy NodeState = iota StateDegraded StateUnhealthy ) type InferenceNode struct { ID string Endpoint string State atomic.Int32 FailCount atomic.Int32 SuccessCount atomic.Int32 LastLatencyMs atomic.Int64 HTTPClient *http.Client } type MicroProbeScheduler struct { nodes []*InferenceNode probeInterval time.Duration timeoutTTFT time.Duration } func NewMicroProbeScheduler(nodes []*InferenceNode, interval, ttftTimeout time.Duration) *MicroProbeScheduler { return &MicroProbeScheduler{ nodes: nodes, probeInterval: interval, timeoutTTFT: ttftTimeout, } } type ProbePayload struct { Model string `json:"model"` Prompt string `json:"prompt"` MaxTokens int `json:"max_tokens"` Temperature float32 `json:"temperature"` Stream bool `json:"stream"` } func (s *MicroProbeScheduler) Start(ctx context.Context) { ticker := time.NewTicker(s.probeInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: for _, node := range s.nodes { go s.probeNode(ctx, node) } } } } func (s *MicroProbeScheduler) probeNode(parentCtx context.Context, node *InferenceNode) { ctx, cancel := context.WithTimeout(parentCtx, s.timeoutTTFT) defer cancel() reqBody, _ := json.Marshal(ProbePayload{ Model: "probe-default", Prompt: "P", MaxTokens: 1, Temperature: 0.0, Stream: true, }) req, err := http.NewRequestWithContext(ctx, http.MethodPost, node.Endpoint+"/v1/completions", bytes.NewReader(reqBody)) if err != nil { s.handleFailure(node, err) return } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-LLM-Probe", "true") // 通知推理引擎旁路此请求,不记录统计指标 start := time.Now() resp, err := node.HTTPClient.Do(req) if err != nil { s.handleFailure(node, err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { s.handleFailure(node, fmt.Errorf("unexpected status code: %d", resp.StatusCode)) return } // 嗅探首个流式分片以验证 TTFT 与 CUDA 计算真实执行 buf := make([]byte, 128) n, err := resp.Body.Read(buf) if err != nil && !errors.Is(err, io.EOF) { s.handleFailure(node, err) return } if n == 0 { s.handleFailure(node, errors.New("empty chunk received")) return } latency := time.Since(start).Milliseconds() node.LastLatencyMs.Store(latency) s.handleSuccess(node, latency) } func (s *MicroProbeScheduler) handleSuccess(node *InferenceNode, latency int64) { node.FailCount.Store(0) successes := node.SuccessCount.Add(1) // 连续 3 次成功恢复健康 if successes >= 3 { if latency > 250 { node.State.Store(int32(StateDegraded)) } else { node.State.Store(int32(StateHealthy)) } } } func (s *MicroProbeScheduler) handleFailure(node *InferenceNode, err error) { node.SuccessCount.Store(0) fails := node.FailCount.Add(1) if fails >= 2 { node.State.Store(int32(StateUnhealthy)) } else { node.State.Store(int32(StateDegraded)) } }

生产避坑防线与开销控制

实操中推行微测试探针,必须防范以下次生问题:

  1. 惊群与算力挤占:如果集群拥有数百个 GPU 节点,网关集群如果采用多实例部署,切忌每个网关节点独立频繁发起探测。否则 10 台网关以 1s 间隔探测同一台推理节点,该节点相当于每秒承受 10 个强制推理请求,会挤占大量 Prefill 阶段算力。生产方案应当由注册中心选主一个探针 Leader,或各网关节点通过分片哈希打散嗅探周期(建议 3s~5s 探测一次)。
  2. 连接泄漏与半关闭:流式探测时,网关在接收到首个 Token Chunk 后即可通过cancel()截断请求。推理引擎端必须支持客户端断开事件感知(如 vLLM 的 Request Abort 监听机制),避免客户端取消后 GPU 仍继续执行无意义的 Decode 循环。
  3. 冷启动与权重热加载避让:在节点刚发布、正在从对象存储拉取大模型权重或构建 TensorRT 引擎时,必须依赖常规容器启动就绪探针(Startup Probe),切忌直接发起 Micro-Probe 判定下线,导致容器被重启循环掐死。
返回列表