模型推理的自适应 Batch Size:根据负载动态调整吞吐
模型推理的自适应 Batch Size根据负载动态调整吞吐一、你的 vLLM 在凌晨 3 点还在满功率跑空推理月账单多了两万AI 推理服务的负载有极强的波谷特征白天办公时段9:00-18:00QPS 高夜间0:00-6:00QPS 趋近于 0。如果你的推理服务在低负载时段仍然保持跟峰值一样的 Batch Size 和 GPU 占用大部分 GPU 时间是在空跑——不是在等请求GPU 利用率 5%就是在处理凑不满 batch 的小请求组合吞吐极低。Batch Size 是推理服务最重要的吞吐杠杆。大 batch 提升吞吐GPU 并行度高但同时增加延迟并发请求排队。小 batch 降低延迟但浪费 GPU 算力。自适应 Batch Size 的核心思想是根据实时 QPS 动态调整 batch size在高负载时用大 batch 提吞吐低负载时用小 batch 降延迟——甚至缩容到 0sleep 模式。实现自适应 batch 需要两个组件负载感知器怎么知道现在忙不忙和调度策略知道了之后怎么调整。二、底层机制与原理剖析自适应 Batch Size 的四个状态和对应策略高负载状态QPS 阈值上限增大 batch size。GPU 利用率 80%请求队列在增长说明需要更高吞吐。增大 batch 让 GPU 单次处理更多请求。但要注意延迟往上走的趋势——batch 不是越大越好。低负载状态QPS 阈值下限减小 batch size。GPU 利用率 30%说明请求太少填不满 GPU。减小 batch 降低单个请求的等待时间不用等凑齐大 batch。如果长时间30 分钟低负载进入缩容模式。空闲状态QPS ≈ 0缩容到 0。释放 GPU 资源。有新的请求到来时通过 KEDA 或 Custom Metrics HPA 自动扩容恢复。冷启动延迟3-5 秒加载模型在这个场景是可接受的——因为全部请求都在 30 分钟的间隔之后到来多等 5 秒用户几乎感觉不到。延迟飙升P95 预警线不管当前负载如何立刻降低 batch size。优先保证用户体验延迟暂时牺牲吞吐。三、生产级代码实现 自适应 Batch Size 调度器 策略四个维度的指标QPS、队列深度、GPU 利用率、P95 延迟 驱动 batch size 的上下调整 import time import threading import logging import statistics from typing import Optional, Dict, List from dataclasses import dataclass, field from enum import Enum from collections import deque logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class LoadState(Enum): HIGH high # 高负载 → 增大 batch NORMAL normal # 正常 → 不变 LOW low # 低负载 → 减小 batch IDLE idle # 空闲 → 缩容 CRITICAL critical # 延迟过高 → 立刻降 batch dataclass class AdaptiveBatchConfig: 自适应 Batch 调度配置 # Batch 范围 min_batch_size: int 1 max_batch_size: int 64 default_batch_size: int 8 current_batch_size: int 8 # QPS 阈值基于 GPU 能力和模型复杂度设定 high_qps_threshold: int 50 # QPS 50 → 高负载 low_qps_threshold: int 5 # QPS 5 → 低负载 idle_qps_threshold: int 0 # QPS 0 → 空闲 idle_timeout_minutes: int 30 # 空闲 30 分钟 → 缩容 # 延迟阈值 p95_latency_warning_ms: float 2000 # P95 2s → 预警 p95_latency_critical_ms: float 5000 # P95 5s → 立刻降 batch # 调整参数 scale_up_factor: float 2.0 # 扩容时 batch 翻倍 scale_down_factor: float 0.5 # 缩容时 batch 减半 cooldown_seconds: int 60 # 两次调整之间的最小间隔防止抖动 # 滑动窗口 metrics_window_seconds: int 60 # 用于计算 QPS/延迟的时间窗口 # GPU 利用率阈值 gpu_high_threshold: float 0.80 # GPU 80% → 可能需要增大 batch gpu_low_threshold: float 0.30 # GPU 30% → 可能需要减小 batch class AdaptiveBatchScheduler: 自适应 Batch 调度器 运行在独立线程中周期性地检查系统状态并调整 batch size def __init__(self, config: AdaptiveBatchConfig, vllm_clientNone): self.config config self.vllm_client vllm_client # vLLM API client用于动态调整参数 # 滑动窗口数据 self._request_times: deque deque() # 记录每个请求的时间戳 self._latency_records: deque deque() # 记录每个请求的延迟 self._lock threading.Lock() # 状态追踪 self.last_adjustment_time: float 0 self.idle_since: Optional[float] None self.current_state: LoadState LoadState.NORMAL # 控制标志 self._running False self._thread: Optional[threading.Thread] None def start(self): 启动调度器 self._running True self._thread threading.Thread(targetself._schedule_loop, daemonTrue) self._thread.start() logger.info(AdaptiveBatchScheduler started (batch%d), self.config.current_batch_size) def stop(self): self._running False if self._thread: self._thread.join(timeout5) def record_request(self, latency_ms: float): 记录一次请求含延迟 with self._lock: now time.time() self._request_times.append(now) self._latency_records.append(latency_ms) def _schedule_loop(self): 主调度循环每 15 秒执行一次 while self._running: time.sleep(15) try: self._evaluate_and_adjust() except Exception as e: logger.error(Scheduler evaluation failed: %s, e) def _evaluate_and_adjust(self): 评估当前负载并决定 batch size 调整 now time.time() # 如果还在冷却期跳过 if now - self.last_adjustment_time self.config.cooldown_seconds: return # 清理过期数据 self._cleanup_expired(now) # 计算当前指标 qps self._compute_qps() p95_latency self._compute_p95_latency() gpu_util self._get_gpu_utilization() queue_depth self._get_queue_depth() # 状态判定 new_state self._determine_state(qps, p95_latency, gpu_util) if new_state self.current_state: # 状态没变但如果是空闲状态需要检查是否超时 if new_state LoadState.IDLE: if self.idle_since and (now - self.idle_since) self.config.idle_timeout_minutes * 60: self._trigger_scale_to_zero() return # 状态发生变化 → 执行调整 logger.info(State transition: %s → %s (QPS%.1f, P95%.0fms, GPU%.0f%%), self.current_state.value, new_state.value, qps, p95_latency, gpu_util * 100 if gpu_util else -1) self._execute_adjustment(new_state, qps, p95_latency) self.current_state new_state self.last_adjustment_time now def _determine_state(self, qps: float, p95_latency: float, gpu_util: Optional[float]) - LoadState: 负载状态判定优先级 1. CRITICAL延迟过高最高优先级 2. IDLE完全无流量 3. LOW / HIGH / NORMAL # 延迟过高 → 不论任何状态优先降 batch if p95_latency self.config.p95_latency_critical_ms: return LoadState.CRITICAL # 空闲 if qps self.config.idle_qps_threshold: if self.idle_since is None: self.idle_since time.time() return LoadState.IDLE else: self.idle_since None # 低负载 if qps self.config.low_qps_threshold: return LoadState.LOW # 高负载 if qps self.config.high_qps_threshold: return LoadState.HIGH # P95 预警——虽然不是 critical但值得关注 if p95_latency self.config.p95_latency_warning_ms: return LoadState.CRITICAL # 预警也走降 batch 逻辑 return LoadState.NORMAL def _execute_adjustment(self, new_state: LoadState, qps: float, latency: float): 执行 batch size 调整 old_batch self.config.current_batch_size if new_state LoadState.CRITICAL: # 延迟高 → 立刻降 batch new_batch max( self.config.min_batch_size, int(self.config.current_batch_size * self.config.scale_down_factor) ) logger.warning(P95 latency %.0fms %.0fms, reducing batch %d→%d, latency, self.config.p95_latency_warning_ms, old_batch, new_batch) elif new_state LoadState.HIGH: # 高负载 → 增大 batch new_batch min( self.config.max_batch_size, int(self.config.current_batch_size * self.config.scale_up_factor) ) elif new_state LoadState.LOW: # 低负载 → 减小 batch new_batch max( self.config.min_batch_size, int(self.config.current_batch_size * self.config.scale_down_factor) ) elif new_state LoadState.NORMAL: new_batch self.config.current_batch_size else: return if new_batch ! old_batch: self._apply_batch_size(new_batch) def _apply_batch_size(self, new_batch: int): 将新的 batch size 应用到推理引擎 self.config.current_batch_size new_batch logger.info(Batch size adjusted: %d, new_batch) # 生产环境通过 vLLM API 动态调整 # 实际 API 取决于推理引擎vLLM/TGI/TensorRT-LLM # 这里记录日志作为示例 if self.vllm_client: try: # vLLM 不支持运行时改 batch但可以改 max_num_seqs # 一些推理引擎支持通过 HTTP API 调整配置 pass except Exception as e: logger.error(Failed to update vLLM config: %s, e) def _trigger_scale_to_zero(self): 触发缩容到 0 logger.warning(Idle for %d minutes, triggering scale to zero, self.config.idle_timeout_minutes) # 生产环境通过 K8s API 缩减 Deployment replicas 到 0 # kubectl scale deployment vllm-service --replicas0 # 配合 KEDA ScaledJob 在下一个请求到来时自动扩容 def _compute_qps(self) - float: 计算滑动窗口内的 QPS with self._lock: now time.time() cutoff now - self.config.metrics_window_seconds recent [t for t in self._request_times if t cutoff] if len(recent) 2: return 0 return len(recent) / (max(recent) - min(recent)) if max(recent) ! min(recent) else len(recent) / 0.001 def _compute_p95_latency(self) - float: 计算滑动窗口的 P95 延迟 with self._lock: if len(self._latency_records) 20: return 0 sorted_latency sorted(self._latency_records) p95_idx int(len(sorted_latency) * 0.95) return sorted_latency[p95_idx] def _get_gpu_utilization(self) - Optional[float]: 获取 GPU 利用率 # 生产环境通过 nvidia-smi 或 DCGM 获取 try: import subprocess result subprocess.run( [nvidia-smi, --query-gpuutilization.gpu, --formatcsv,noheader,nounits], capture_outputTrue, textTrue, timeout5, ) if result.returncode 0: return float(result.stdout.strip()) / 100.0 except Exception: pass return None def _get_queue_depth(self) - int: 获取请求队列深度 # 通过 vLLM API 获取当前排队请求数 return 0 def _cleanup_expired(self, now: float): 清理滑动窗口中的过期数据 with self._lock: cutoff now - self.config.metrics_window_seconds * 2 while self._request_times and self._request_times[0] cutoff: self._request_times.popleft() if self._latency_records: self._latency_records.popleft() # --------------------------------------------------------------------------- # 启动示例 # --------------------------------------------------------------------------- if __name__ __main__: config AdaptiveBatchConfig( min_batch_size1, max_batch_size64, default_batch_size8, high_qps_threshold50, low_qps_threshold5, idle_timeout_minutes30, p95_latency_warning_ms2000, p95_latency_critical_ms5000, ) scheduler AdaptiveBatchScheduler(config) scheduler.start() # 模拟请求通常由 API Gateway 在请求进入时调用 record_request import random try: while True: latency random.gauss(500, 200) # 模拟延迟 scheduler.record_request(max(0, latency)) time.sleep(0.1) except KeyboardInterrupt: scheduler.stop()四、边界分析与架构权衡调节速率问题如果 QPS 突然从 0 跳到 200batch 从 1 翻倍到 2 → 4 → 8 → 16 → 32 需要经过 4 个调度周期60 秒冷却期太慢优化检测到负载跳变时跳过冷却期直接跳到对应 batch size查表缩容到 0 的冷启动问题GPU 推理服务从 0 扩容到 1 需要 30-60 秒模型加载 GPU 初始化如果用户突然在这个窗口内发起请求体验很差优化Keep-Warm 策略——在业务低峰期不缩容到 0而是保留 1 个实例Batch Size ≠ 并发度增大 batch 不是无限的——受 GPU 显存限制。Llama-3-70B 在 A100 80G 上最大 batch 可能只有 16-32当 batch 达到显存上限时更高的负载需要水平扩容加 GPU 节点而非垂直扩 batch五、结语自适应 Batch Size 调度本质是用实时负载指标驱动推理服务的吞吐和延迟平衡。在高 QPS 时增大 batch 提吞吐低 QPS 时减小 batch 降延迟空闲 30 分钟后缩容到 0 省成本。关键是四个指标的权重QPS 和队列深度反映有多忙P95 延迟反映用户感受到的慢GPU 利用率反映硬件用得多满。延迟保护必须是最优先级的——用户不关心你 GPU 多高只关心他多快拿到回复。

相关新闻

分层强化学习(HRL)技术解析与工程实践

分层强化学习(HRL)技术解析与工程实践

1. 分层强化学习的技术演进背景强化学习作为机器学习的重要分支,近年来在游戏AI、机器人控制、自动驾驶等领域取得了突破性进展。但传统强化学习方法在面对复杂任务时,往往会遇到"维度灾难"和"信用分配"两大核心挑战。我在实际项目中…

2026/10/13 6:47:35 阅读更多 →
细胞亚铁离子含量检测:打开铁代谢研究与细胞氧化还原调控的微观窗口

细胞亚铁离子含量检测:打开铁代谢研究与细胞氧化还原调控的微观窗口

细胞亚铁离子(Fe⁺)含量检测试剂盒在细胞铁死亡与代谢研究中的前沿应用铁是生命体必需的微量元素,在氧气运输、能量代谢、DNA合成和抗氧化防御等核心生理过程中扮演着不可替代的角色。在细胞内,铁以Fe⁺(亚铁离子&…

2026/10/3 21:37:14 阅读更多 →
UHF RFID手持终端选型:5dBi陶瓷天线凭什么把读距拉到25米?

UHF RFID手持终端选型:5dBi陶瓷天线凭什么把读距拉到25米?

在智慧物流、铁路货运和能源巡检等场景里,UHF RFID手持终端已经成了标配工具。选对一台PDA,盘点效率能翻好几倍;选错了,现场掉链子、读不到标签的事天天发生。这篇文章把选型的核心维度拆开讲透,帮你快速锁定适合项目的…

2026/10/6 4:47:14 阅读更多 →

最新新闻

树莓派4B实战:Nginx+FFmpeg实现RTMP推流与录制

树莓派4B实战:Nginx+FFmpeg实现RTMP推流与录制

1. 项目缘起与整体设计思路1.1 为什么要在树莓派上折腾推流和录音手头有一块树莓派4B,4GB内存版本,平时跑着一些轻量服务,大部分时间处于闲置状态。某天需要把一路摄像头画面推到局域网内多台设备上观看,同时还要把音频录下来存档…

2026/10/13 13:40:23 阅读更多 →
Typora+Gitee+PicGo云笔记图床实战配置指南

Typora+Gitee+PicGo云笔记图床实战配置指南

1. 这不是“又一篇Typora同步教程”,而是我踩了7次坑后整理的云笔记生产流 你是不是也经历过:写完一篇Markdown笔记,想发到网上分享,结果图片全挂了?本地预览好好的,一粘贴到公众号、博客平台,…

2026/10/13 13:40:23 阅读更多 →
Android AudioTrack播放PCM音频:原理、实践与延迟优化

Android AudioTrack播放PCM音频:原理、实践与延迟优化

去年做一个语音通信的小项目,中间要把网络上下来的音频数据播放出去。一开始图省事,直接塞给MediaPlayer,结果发现它压根不是干这个的:它内部会自己去解复用、解压缩,你根本没法控制它的数据送到扬声器的时机&#xff…

2026/10/13 13:40:23 阅读更多 →
Cifar-10 数据集深度解析:从加载陷阱到 CNN 鲁棒性验证

Cifar-10 数据集深度解析:从加载陷阱到 CNN 鲁棒性验证

简介:Cifar-10数据集是计算机视觉入门与CNN模型实践的核心基准资源,面向深度学习初学者、算法工程师及高校课程实验者,用于快速构建、训练与评估图像分类模型。本zip包共8个文件,含5个训练批次(data_batch_1至5&#x…

2026/10/13 13:40:23 阅读更多 →
Claude Code 实战指南:从零构建轻量级代码审查工具

Claude Code 实战指南:从零构建轻量级代码审查工具

简介:本资源是面向软件开发者的 Claude Code CLI 命令行工具权威使用手册源码包,专为希望高效管理代码、提升终端开发效率的中高级开发者设计。手册系统覆盖快速入门、常用命令详解、Skill 创建与调用技巧、多文件编辑与代码分析、配置文件定制&#xff…

2026/10/13 13:40:23 阅读更多 →
SystemView.zip解压安装全攻略:识别、校验与伪加密排查

SystemView.zip解压安装全攻略:识别、校验与伪加密排查

简介:SystemView是一款面向ARM及单片机开发者的嵌入式实时分析工具,专注于CPU活动监控、任务调度和中断处理的可视化调试。包内收录Windows可直接运行的EXE及相关DLL,并包含C/H源码示例、txt说明文档、SVDat运行记录数据、补丁、PDF用户手册等…

2026/10/13 13:39:19 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →