ARTICLE DETAIL

资讯详情

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

大模型并发连接数与 TPM 联合限流:防范 GPU 显存 OOM 的自适应保护

大模型并发连接数与 TPM 联合限流:防范 GPU 显存 OOM 的自适应保护 大模型并发连接数与 TPM 联合限流防范 GPU 显存 OOM 的自适应保护在高并发大模型推理集群的生产运维中直接沿用传统微服务基于 QPS每秒请求数的限流策略往往会引发灾难性事故。普通 HTTP 接口处理 1000 QPS 可能轻而易举但大模型推理是典型的重资源、长连接、高显存消耗场景。若 10 个请求同时携带了 32k 的超长上下文瞬间爆发的 Prefill 矩阵计算与庞大的 KV Cache 显存分配足以直接将单机 8 卡 H800 的显存打崩引发系统的 OOM 连锁崩溃。必须跳出 QPS 的惯性思维构建“并发活跃连接数Concurrency Slots”与“每分钟 Token 吞吐量Tokens Per Minute, TPM”的双维联合限流防护网并引入基于 GPU 显存水位的自适应动态调谐。单一 QPS 维度的破产分析传统 RPC 服务单次调用耗时通常在毫秒级显存占用固定。而在 Transformer 自回归生成架构下资源开销呈现出极端的不确定性显存占用的非线性与动态性KV Cache 显存大小直接由(Batch_Size × Context_Length)决定。即便采用 PagedAttention 机制分页管理显存一个并发数不高但平均长度达 16k 的长文档分析流量其显存占用也能达到常规短对话请求的数十倍。Decode 过程的长时间资源锁定大模型生成以流式逐字输出为主单次请求持续时间可能从数百毫秒拉长至数分钟。如果仅看 QPS瞬间涌入 50 个请求看似不高但若这 50 个连接全部进入长文本生成并发占用的 KV Cache 显存池将迅速耗尽直接导致引擎触发请求抢占Preemption、显存换入换出Swap to CPU令 P99 延迟飙升至不可用状态。Prefill 与 Decode 的显存争抢Prompt Prefill 属于计算密集型瞬时显存开销巨大Token Decode 属于显存带宽密集型持续占用显存空间。若无 TPM 约束突发的超大 Prompt 会瞬间挤占正在 Decode 的请求显存造成 GPU 显存分配器抛出致命异常。并发连接与 TPM 联合治理模型为了在保障 GPU 吞吐最大化的同时坚决守住不 OOM 的底线网关层必须执行双维联动拦截并发连接数Concurrency Control限制正在推理中的全局最大活跃连接数。此指标直接锚定 GPU 实例可同时承载的批处理上限Max Batch Size确保 KV Cache 页表中始终保留最小安全余量。TPM 漏桶Token Per Minute Control基于 Token 计数对流量进行平滑。区分计费与限流模型中的 Prompt Token可精确计算与 Completion Token基于max_tokens预占流式结束后按实际值回补。显存水位自适应反馈Adaptive Feedback静态配置永远无法适配复杂的生产流量分布。网关通过轻量 Sidecar 或专用 Metrics 接口每秒拉取后端推理节点的gpu_kv_cache_usage_ratioKV 缓存使用率与num_requests_waiting排队请求数。当显存水位超过 85% 告警阈值时网关动态缩减并发窗口与 TPM 配额当水位降至 70% 以下时平滑恢复基准配额。Go 1.27.1 联合自适应限流器实现以下展示网关核心层拦截管道中的双维限流器集成了信号量并发控制、Token 预占回退机制与显存自适应调谐接口package limiter import ( context errors sync sync/atomic time ) var ( ErrConcurrencyLimitExceeded errors.New(inference concurrency slots exhausted) ErrTokenRateLimitExceeded errors.New(tpm rate limit exceeded, try again later) ) type AdaptiveConfig struct { BaseMaxConcurrency int32 BaseTPM int64 MaxKVUsageThreshold float64 // 例如 0.85 MinKVUsageThreshold float64 // 例如 0.70 } type JointAdaptiveLimiter struct { config AdaptiveConfig // 动态调整后的当前上限 currentMaxConcurrency atomic.Int32 currentTPM atomic.Int64 // 活跃连接控制 activeConnections atomic.Int32 // TPM 令牌桶 mu sync.Mutex tokenBucket float64 lastLeakTime time.Time // 后端显存与负载指标 kvUsage atomic.Uint64 // 存储 float64 的位表示 } func NewJointAdaptiveLimiter(cfg AdaptiveConfig) *JointAdaptiveLimiter { l : JointAdaptiveLimiter{ config: cfg, lastLeakTime: time.Now(), } l.currentMaxConcurrency.Store(cfg.BaseMaxConcurrency) l.currentTPM.Store(cfg.BaseTPM) l.tokenBucket float64(cfg.BaseTPM) return l } // UpdateBackendMetrics 接收后端 GPU 监控指标执行自适应调谐 func (l *JointAdaptiveLimiter) UpdateBackendMetrics(kvUsageRatio float64) { l.kvUsage.Store(uint64(kvUsageRatio * 10000)) maxConn : float64(l.config.BaseMaxConcurrency) maxTPM : float64(l.config.BaseTPM) if kvUsageRatio l.config.MaxKVUsageThreshold { // 显存高危激进退避削减 40% 并发与 TPM 阈值 scale : 0.6 l.currentMaxConcurrency.Store(int32(maxConn * scale)) l.currentTPM.Store(int64(maxTPM * scale)) } else if kvUsageRatio l.config.MinKVUsageThreshold { // 显存安全恢复满血配置 l.currentMaxConcurrency.Store(l.config.BaseMaxConcurrency) l.currentTPM.Store(l.config.BaseTPM) } else { // 处于缓冲区间按线性比例动态缩放 ratio : (l.config.MaxKVUsageThreshold - kvUsageRatio) / (l.config.MaxKVUsageThreshold - l.config.MinKVUsageThreshold) scale : 0.6 0.4*ratio l.currentMaxConcurrency.Store(int32(maxConn * scale)) l.currentTPM.Store(int64(maxTPM * scale)) } } // Acquire 尝试预占并发 Slot 与预估 Token 配额 func (l *JointAdaptiveLimiter) Acquire(ctx context.Context, estimatedTokens int64) (func(actualTokens int64), error) { // 1. 活跃并发检查 limit : l.currentMaxConcurrency.Load() if l.activeConnections.Add(1) limit { l.activeConnections.Add(-1) return nil, ErrConcurrencyLimitExceeded } // 2. TPM 令牌消耗检查 l.mu.Lock() now : time.Now() elapsed : now.Sub(l.lastLeakTime).Seconds() l.lastLeakTime now // 补充令牌 currentLimitTPM : float64(l.currentTPM.Load()) ratePerSecond : currentLimitTPM / 60.0 l.tokenBucket elapsed * ratePerSecond if l.tokenBucket currentLimitTPM { l.tokenBucket currentLimitTPM } if l.tokenBucket float64(estimatedTokens) { l.mu.Unlock() l.activeConnections.Add(-1) return nil, ErrTokenRateLimitExceeded } l.tokenBucket - float64(estimatedTokens) l.mu.Unlock() // 3. 返回清理与按实结算闭包 var once sync.Once release : func(actualTokens int64) { once.Do(func() { l.activeConnections.Add(-1) // 若实际消耗 Token 小于预占估算将多扣除的 Token 返还令牌桶 diff : estimatedTokens - actualTokens if diff 0 { l.mu.Lock() l.tokenBucket float64(diff) currentTPM : float64(l.currentTPM.Load()) if l.tokenBucket currentTPM { l.tokenBucket currentTPM } l.mu.Unlock() } }) } return release, nil }生产避坑与架构调优细节在真实的大规模生产环境中落地此联合限流机制需要重点防范以下工程陷阱长 Prompt 预拒绝机制很多业务方会误传超大无效文档例如 128k 纯空格或爬虫脏数据。网关层必须在做 Token 编码TikToken / Tokenizer 预解析之前先设定 Raw Request Body 长度硬上限。如果估算 Prompt Token 已经超过单节点单次推理的最大上下文窗口限制Context Window Limit在网关侧直接响应 400 Bad Request 阻断严禁放行至后端挤占显存。客户端异常断连后的配额补偿流式传输时客户端随时可能关闭网络连接。若网关未监听req.Context().Done()下游 GPU 将持续进行无效计算而网关计数器也会持续被虚挂。网关感知到客户端断连后必须立即通过 Cancel 信号通知后端停止推理并立即触发释放闭包避免连接池假死。多租户借贷与防饿死策略在多业务共用一套大模型集群时核心链路如在线客服、实时代码补全与后台离线链路如离线数据摘要、文档批量 Embedding绝不能使用完全相同的限流配额。必须为高优先级租户预留保底并发通道Guaranteed Slots低优先级流量采用突发共享池Burstable Pool并在显存利用率告警时首先对离线流量实施熔断式降级。
返回列表