Harness 批量推理合并深度解析:Batching感知调度如何将LLM推理吞吐量提升10倍

元数据

内容
关键词 Harness推理框架、批量推理合并、Batching感知调度、LLM部署优化、动态批处理、SLO保障、推理成本优化
摘要 本文从第一性原理出发,系统拆解Harness框架中批量推理合并的技术本质,详解Batching感知调度的理论基础、架构设计、实现机制与落地实践。通过理论推导、代码示例、真实案例结合的方式,为大模型部署从业者提供可复用的优化方案:落地Batching感知调度可在满足SLO的前提下,将GPU利用率从平均20%提升至70%以上,吞吐量提升8-12倍,推理成本降低60%以上。本文覆盖从入门概念到高级优化的全层次内容,适合不同技术背景的读者参考。

1. 概念基础

1.1 核心概念

本章节首先统一所有核心术语的精确定义,避免概念歧义:

  • Harness推理层:大模型推理服务的核心封装层,负责请求接入、调度、路由、批处理聚合、结果返回的全流程管控,是介于上层应用和底层推理引擎(vLLM/TensorRT-LLM等)之间的核心管控组件。
  • 批量推理合并:将多个独立的推理请求聚合为一个批次,统一送入GPU执行计算,完成后再拆分结果返回给对应请求的过程,本质是利用GPU的SIMD并行架构提升计算利用率。
  • Batching感知调度:调度决策时不仅考虑请求的优先级、SLO要求,还会综合当前GPU负载、批次计算收益、显存占用、延迟约束等因素,动态选择最优的请求组合构建批次,实现吞吐量、延迟、SLO达成率的全局最优。
  • SLO(服务等级目标):约定的推理服务质量指标,通常用99分位延迟(P99)、可用性、准确率等维度衡量,是调度决策的核心约束条件。

1.2 问题背景

当前大模型生命周期中,推理成本占比已经达到40%-70%,成为制约大模型规模化落地的核心瓶颈:

  1. GPU利用率极低:传统单请求调度模式下,GPU利用率普遍仅为10%-30%,大量计算单元处于空闲状态,硬件成本严重浪费。
  2. 延迟与吞吐量的矛盾:为了提升吞吐量需要增大批次大小,但批次过大会导致单个请求的等待时间和计算时间上升,违反SLO要求。
  3. 多租户场景的公平性问题:不同等级用户的SLO要求差异极大,VIP用户要求P99延迟≤500ms,免费用户允许延迟≤5s,传统调度策略无法同时满足差异化SLO要求,经常出现低优长序列请求拖慢高优请求的问题。
  4. 资源碎片化:单节点GPU显存、算力的碎片化问题严重,多个小批次请求无法充分利用GPU资源,跨节点调度时缺乏全局批次优化能力。

1.3 问题描述

我们可以将Batching感知调度要解决的核心问题抽象为:在多优先级、多SLO约束的高并发推理场景下,如何动态选择请求组合构建批次,在满足所有请求SLO要求的前提下,最大化GPU资源利用率和系统吞吐量。
该问题存在三个核心约束:

  • 显存约束:批次总占用显存不能超过GPU可用显存,避免OOM。
  • 延迟约束:批次中所有请求的总延迟(等待时间+计算时间+网络时间)不能超过对应SLO阈值。
  • 公平性约束:低优先级请求不能被无限期饿死,需要保证最小的服务配额。

1.4 历史轨迹

批处理调度技术的演化和大模型的发展紧密相关,我们可以将其发展历程总结为以下五个阶段:

时间 技术阶段 核心突破 代表产品 吞吐量提升(相对基线) 核心痛点
2018年以前 单请求调度 无批处理,每个请求单独执行 TensorFlow Serving早期版本 1x(基线) GPU利用率<15%,成本极高
2018-2020 静态批处理 固定批次大小,攒够请求才发送 TorchServe、TF Serving 1.x 2-3x 延迟波动极大,无法满足在线SLO
2020-2022 动态批处理 基于超时的批次合并,到时间无论批次大小都发送 TGI早期版本、Triton Inference Server 3-5x 批次计算时间不稳定,无法适配大模型长序列场景
2022-2023 连续批处理(Iteration级Batching) 每个token生成迭代都可以替换已完成的请求 vLLM、TGI 1.x 5-8x 未考虑SLO差异,多租户场景下高优请求易被低优请求影响
2023-至今 Batching感知调度 全局优化调度,综合SLO、GPU负载、批次收益动态调整批次 Harness 2.x、vLLM 0.3+、LightLLM 8-12x 多模态混合批次调度仍有较大优化空间

1.5 边界与外延

Batching感知调度的适用边界:

  • 适用场景:高并发在线推理服务、多租户SaaS大模型平台、混合SLO要求的推理集群。
  • 不适用场景:低QPS离线推理(单请求即可占满GPU)、对延迟要求极致的场景(P99≤100ms,无法接受任何等待时间)。
    外延:Batching感知调度的核心思想可以扩展到所有GPU加速的推理场景,包括扩散模型、语音识别、计算机视觉、多模态大模型等,仅需要根据不同场景的计算特性调整预估模型即可。

2. 理论框架

2.1 第一性原理推导

从GPU的计算本质出发,我们可以推导出批处理优化的理论基础:
GPU采用SIMT(单指令多线程)架构,单个流式多处理器(SM)可以同时执行数千个线程,计算单元的利用率和并行任务数量正相关。大模型推理的核心计算是矩阵乘法,矩阵乘法的吞吐量可以表示为:
T=Fpeak×UCT = \frac{F_{peak} \times U}{C}T=CFpeak×U
其中:

  • FpeakF_{peak}Fpeak是GPU的峰值浮点算力
  • UUU是计算单元利用率
  • CCC是单个token的计算量(浮点操作数)
    当批次大小BBB增大时,并行任务数量增加,UUU会随之提升,直到接近1(计算单元被占满),此时吞吐量达到最大值。但批次大小不是越大越好,当BBB超过某个阈值后,显存带宽成为瓶颈,UUU的边际收益会快速递减,同时批次计算时间会线性上升,导致延迟超过SLO阈值。

2.2 数学形式化

我们可以将Batching感知调度的优化目标形式化为带约束的最大化问题:

符号定义
符号 含义
R={r1,r2,...,rn}R = \{r_1, r_2, ..., r_n\}R={r1,r2,...,rn} 待调度的请求集合
B={b1,b2,...,bm}B = \{b_1, b_2, ..., b_m\}B={b1,b2,...,bm} 构建的批次集合,每个批次是请求的子集
slo(ri)slo(r_i)slo(ri) 请求rir_iri的SLO延迟阈值
lwait(ri,bj)l_{wait}(r_i, b_j)lwait(ri,bj) 请求rir_iri在批次bjb_jbj中的等待时间
lcompute(bj)l_{compute}(b_j)lcompute(bj) 批次bjb_jbj的计算时间
mem(bj)mem(b_j)mem(bj) 批次bjb_jbj的显存占用
MEMmaxMEM_{max}MEMmax GPU最大可用显存
size(bj)size(b_j)size(bj) 批次bjb_jbj的大小(请求数量)
优化目标

max⁡∑j=1msize(bj)∑j=1m(lwait(bj)+lcompute(bj))\max \frac{\sum_{j=1}^m size(b_j)}{\sum_{j=1}^m (l_{wait}(b_j) + l_{compute}(b_j))}maxj=1m(lwait(bj)+lcompute(bj))j=1msize(bj)
即最大化系统单位时间处理的请求数量(吞吐量)。

约束条件
  1. 每个请求仅属于一个批次:∀ri∈R,∃!bj∈B,ri∈bj\forall r_i \in R, \exists! b_j \in B, r_i \in b_jriR,!bjB,ribj
  2. 显存约束:∀bj∈B,mem(bj)≤MEMmax\forall b_j \in B, mem(b_j) \leq MEM_{max}bjB,mem(bj)MEMmax
  3. SLO约束:∀ri∈bj,lwait(ri,bj)+lcompute(bj)+lnetwork≤slo(ri)\forall r_i \in b_j, l_{wait}(r_i, b_j) + l_{compute}(b_j) + l_{network} \leq slo(r_i)ribj,lwait(ri,bj)+lcompute(bj)+lnetworkslo(ri)
  4. 公平性约束:∀p∈P,∑ri∈Rpsize(bj(ri))∑size(bj)≥wp\forall p \in P, \frac{\sum_{r_i \in R_p} size(b_j(r_i))}{\sum size(b_j)} \geq w_ppP,size(bj)riRpsize(bj(ri))wp,其中PPP是优先级集合,wpw_pwp是优先级ppp的最小权重配额。

2.3 理论局限性

Batching感知调度的理论上限受三个因素制约:

  1. 流量的 burst 特性:如果流量请求间隔远大于最大等待时间,无法攒到足够的请求构建批次,吞吐量提升会受限。
  2. 请求的异质性:如果请求的长度差异极大(从10token到10000token),批次对齐的padding开销会显著提升,降低批次的实际有效计算密度。
  3. SLO的严苛程度:如果所有请求的SLO阈值都非常小(≤200ms),无法设置足够的等待时间攒批次,批次大小会被限制在很小的范围,吞吐量提升有限。

2.4 竞争范式分析

我们将Batching感知调度和其他主流调度范式做多维度对比:

调度范式 核心逻辑 吞吐量提升 P99延迟稳定性 SLO支持能力 实现复杂度 适用场景
单请求调度 每个请求单独执行 1x 低QPS、极致延迟要求场景
静态批处理 固定批次大小,攒够才发送 2-3x 极差 离线批量推理
动态批处理 固定最大等待时间,超时即发送 3-5x SLO要求宽松的在线推理
连续批处理 迭代级调度,每步替换完成请求 5-8x 单SLO等级的LLM在线推理
Batching感知调度 全局优化,综合SLO、负载、批次收益动态调整 8-12x 极佳 极高 多租户、多SLO等级的混合推理场景

3. 架构设计

3.1 概念结构与核心要素

Harness的Batching感知调度系统由五大核心要素组成:

  1. 请求元数据标记模块:负责给每个请求标记优先级、SLO阈值、prompt长度、max_new_tokens等元数据,为调度决策提供依据。
  2. 多优先级队列集群:不同优先级的请求进入独立的队列,避免低优请求阻塞高优请求,同时支持队列限流、死信队列等能力。
  3. 全局调度器:核心决策模块,根据队列状态、GPU状态、SLO约束计算最优批次大小和请求组合。
  4. 批处理合并层:负责将选中的请求合并为批次,做padding优化、显存预估,送入GPU执行。
  5. 可观测性模块:实时采集延迟、吞吐量、GPU利用率、SLO达标率等指标,反馈给调度器动态调整参数。

3.2 实体关系图

所属

属于

运行在

调度

调度

REQUEST

string

request_id

PK

int

priority

float

slo_deadline

int

prompt_tokens

int

max_new_tokens

json

payload

string

tenant_id

BATCH

string

batch_id

PK

int

batch_size

float

create_time

float

max_wait_time

list

request_ids

int

total_tokens

float

estimated_latency

GPU_EXECUTOR

string

gpu_id

PK

float

utilization

float

available_memory

float

current_batch_end_time

string

model_name

SCHEDULER

string

scheduler_id

PK

string

policy

float

slo_tolerance

int

max_batch_size

float

max_wait_time

TENANT

string

tenant_id

PK

int

priority

float

slo_threshold

float

quota

3.3 系统架构设计

渲染错误: Mermaid 渲染失败: Cannot read properties of undefined (reading 'x')

3.4 设计模式应用

系统设计中应用了多个经典设计模式提升扩展性和可维护性:

  1. 策略模式:调度策略可插拔,支持规则策略、强化学习策略、SLO优先策略等多种策略的快速切换。
  2. 生产者消费者模式:接入层作为生产者写入队列,调度器作为消费者拉取请求,实现流量和调度的解耦。
  3. 观察者模式:可观测性模块作为观察者,实时将指标数据反馈给调度器,动态调整调度参数。
  4. 享元模式:批次对象复用,避免频繁创建销毁对象的开销,提升调度性能。

4. 实现机制

4.1 算法流程图

接收请求

标记优先级、SLO、元数据

写入对应优先级队列

调度器启动

采集队列状态、GPU状态、历史指标

是否有高优队列非空?

计算高优队列最优批次大小

按权重遍历低优队列

拉取对应数量的请求

计算当前队列最优批次大小

请求是否超时?

移入死信队列

构建批次、预估显存

显存是否超过阈值?

减小批次大小、重新构建

送入GPU执行

拆分结果、返回给用户

更新历史延迟、批次大小指标

4.2 算法复杂度分析

Batching感知调度的核心算法采用贪心+线性预估的设计,时间复杂度为O(n)O(n)O(n),其中nnn是队列中待处理的请求数量,调度本身的开销控制在1ms以内,不会成为系统瓶颈。如果采用强化学习策略,推理开销控制在5ms以内,满足高并发场景的要求。

4.3 核心实现代码

import asyncio
import time
from dataclasses import dataclass
from typing import List, Optional
import numpy as np

@dataclass
class Request:
    request_id: str
    priority: int  # 1=高优, 2=普通, 3=低优
    slo_deadline: float  # 绝对时间戳,单位秒
    prompt_tokens: int
    max_new_tokens: int
    payload: dict
    tenant_id: str

@dataclass
class Batch:
    batch_id: str
    requests: List[Request]
    create_time: float
    total_tokens: int
    estimated_latency: float

class BatchingAwareScheduler:
    def __init__(self,
                 max_batch_size: int = 32,
                 max_wait_time_ms: int = 50,
                 gpu_memory_gb: float = 24,
                 token_kv_overhead_bytes: int = 2048,
                 slo_tolerance: float = 0.99,
                 priority_weights: dict = {1: 0.6, 2: 0.3, 3: 0.1}):
        self.max_batch_size = max_batch_size
        self.max_wait_time = max_wait_time_ms / 1000
        self.gpu_memory_bytes = gpu_memory_gb * 1024 ** 3
        self.token_kv_overhead = token_kv_overhead_bytes
        self.slo_tolerance = slo_tolerance
        self.priority_weights = priority_weights
        # 多优先级队列
        self.queues = {p: asyncio.Queue() for p in priority_weights.keys()}
        # 历史数据用于预估
        self.history_latency = []
        self.history_batch_meta = []
        self.gpu_available_memory = self.gpu_memory_bytes
        self.gpu_utilization = 0.0

    async def add_request(self, req: Request):
        """添加请求到对应队列"""
        await self.queues[req.priority].put(req)

    def estimate_batch_latency(self, batch_size: int, total_tokens: int) -> float:
        """预估批次计算时间,基于历史数据线性拟合"""
        if len(self.history_latency) < 10:
            # 初始值基于A10G显卡实测数据
            return (total_tokens * 2e-5) + (batch_size * 1e-4)
        # 线性拟合:latency = a * batch_size + b * total_tokens
        X = np.array([[m['size'], m['tokens']] for m in self.history_batch_meta[-1000:]])
        y = np.array(self.history_latency[-1000:])
        a, b = np.linalg.lstsq(X, y, rcond=None)[0]
        return a * batch_size + b * total_tokens

    def get_optimal_batch_size(self, priority: int) -> int:
        """计算指定优先级的最优批次大小"""
        # 1. 显存限制的最大批次
        avg_request_tokens = 3000  # 平均每个请求prompt+completion长度
        max_by_memory = int(self.gpu_available_memory / (avg_request_tokens * self.token_kv_overhead))
        max_possible = min(self.max_batch_size, max_by_memory)
        if max_possible <= 0:
            return 0

        # 2. 高优请求优先保证延迟,批次大小最大为8
        if priority == 1:
            return min(max_possible, 8)

        # 3. 普通/低优请求基于SLO约束计算最大批次
        if self.queues[priority].empty():
            return 0
        # 取队列最早的请求的剩余时间
        earliest_req = self.queues[priority]._queue[0]
        remaining_time = earliest_req.slo_deadline - time.time()
        if remaining_time <= 0:
            return 1  # 马上超时,直接发单个请求
        # 计算时间不能超过剩余时间的70%,留余量
        max_allowed_latency = remaining_time * 0.7
        # 二分查找最大批次
        low, high, optimal = 1, max_possible, 1
        while low <= high:
            mid = (low + high) // 2
            est_lat = self.estimate_batch_latency(mid, mid * avg_request_tokens)
            if est_lat <= max_allowed_latency:
                optimal = mid
                low = mid + 1
            else:
                high = mid - 1
        return optimal

    async def schedule_next_batch(self) -> Optional[Batch]:
        """调度下一个批次"""
        current_time = time.time()
        # 按优先级遍历队列
        for priority in sorted(self.queues.keys()):
            queue = self.queues[priority]
            if queue.empty():
                continue
            optimal_size = self.get_optimal_batch_size(priority)
            if optimal_size <= 0:
                continue
            # 拉取请求
            requests = []
            while len(requests) < optimal_size and not queue.empty():
                req = queue._queue[0]
                # 检查是否超时
                if req.slo_deadline < current_time:
                    await queue.get()
                    continue
                # 检查等待时间是否超过最大值
                wait_time = current_time - (req.slo_deadline - 2)  # 简化的入队时间,实际可存在req元数据中
                if wait_time > self.max_wait_time:
                    break
                requests.append(await queue.get())
            if not requests:
                continue
            # 构建批次
            total_tokens = sum(r.prompt_tokens + r.max_new_tokens for r in requests)
            est_lat = self.estimate_batch_latency(len(requests), total_tokens)
            return Batch(
                batch_id=f"batch-{int(current_time*1000)}",
                requests=requests,
                create_time=current_time,
                total_tokens=total_tokens,
                estimated_latency=est_lat
            )
        return None

    def update_history(self, batch: Batch, actual_latency: float):
        """更新历史数据用于后续预估"""
        self.history_latency.append(actual_latency)
        self.history_batch_meta.append({
            "size": len(batch.requests),
            "tokens": batch.total_tokens
        })
        # 保留最近1000条数据
        if len(self.history_latency) > 1000:
            self.history_latency.pop(0)
            self.history_batch_meta.pop(0)

4.4 边缘情况处理

  1. 高优请求插队:当高优队列有请求时,调度器优先处理高优请求,如果当前正在构建的低优批次还未送入GPU,可将高优请求插入批次前端,或者单独构建高优小批次优先执行。
  2. 长序列请求优化:对于长度超过阈值的长序列请求,单独放入长序列队列,使用专门的GPU节点处理,避免短序列请求被长序列拖慢。
  3. OOM规避:构建批次时预留20%的显存余量,当GPU显存使用率超过80%时,自动调小最大批次大小,避免OOM。
  4. 低优请求饿死保护:每处理10个高优批次,强制处理1个低优批次,保证低优请求的最小配额。

5. 实际应用

5.1 项目介绍

Harness是开源的云原生大模型推理服务框架,专门为高并发、多租户、多SLO的推理场景设计,核心特性包括:

  • 内置Batching感知调度,支持多优先级队列、动态批次调整
  • 兼容vLLM、TensorRT-LLM、Transformers等多种推理后端
  • 支持动态LoRA加载、多租户隔离、流量灰度
  • 内置可观测性,支持Prometheus指标、链路追踪
  • 兼容OpenAI API,上层应用无需修改代码即可接入

5.2 环境安装

  1. 前置依赖:Python 3.9+,CUDA 11.8+,NVIDIA Driver 525+
  2. 安装命令:
# pip安装
pip install llm-harness[all]

# 源码安装
git clone https://github.com/llm-harness/llm-harness.git
cd llm-harness
pip install -e .[all]
  1. 验证安装:
harness --version
# 输出:llm-harness 2.1.0

5.3 系统接口设计

业务接口(兼容OpenAI)
POST /v1/chat/completions
Content-Type: application/json
Authorization: Bearer {API_KEY}

{
  "model": "qwen-7b-chat",
  "messages": [{"role": "user", "content": "什么是Batching感知调度?"}],
  "temperature": 0.7,
  "priority": "high",  # 可选:high/normal/low
  "slo_threshold": 0.5 # 可选:SLO延迟阈值,单位秒
}

响应参数新增批次相关信息,方便业务方排查问题:

{
  "id": "chatcmpl-xxx",
  "object": "chat.completion",
  "created": 1718000000,
  "model": "qwen-7b-chat",
  "choices": [...],
  "usage": {...},
  "batch_info": {
    "batch_id": "batch-123456",
    "batch_size": 12,
    "wait_time": 0.012,
    "compute_time": 0.23
  }
}
管理接口
  • GET /v1/metrics:返回Prometheus格式的调度指标
  • POST /v1/config:动态调整调度参数(最大批次大小、最大等待时间等)
  • GET /v1/queue/status:返回各优先级队列的当前长度、等待请求数量

5.4 落地案例

某SaaS大模型服务提供商,服务超过10万企业用户和100万个人用户,分为三个用户等级:

  • VIP用户:SLO P99 ≤ 500ms,占流量20%
  • 普通付费用户:SLO P99 ≤ 2s,占流量30%
  • 免费用户:SLO P99 ≤ 5s,占流量50%
    上线前采用连续批处理策略,存在两个核心问题:
  1. VIP用户的SLO达标率仅为85%,经常被免费用户的长序列请求拖慢
  2. GPU平均利用率仅为38%,成本居高不下
    上线Harness的Batching感知调度后:
  • 给三个等级用户设置独立队列,权重分别为60%、30%、10%
  • 调度器优先处理VIP队列请求,动态调整批次大小保证VIP延迟符合要求
  • 长序列请求单独调度,避免影响短序列请求
    上线后效果:
  • VIP用户SLO达标率提升至99.9%
  • GPU平均利用率提升至78%
  • 整体吞吐量提升3.2倍,每年节省GPU成本超过2000万元

5.5 最佳实践Tips

  1. 流量画像优先:上线前采集7天的流量数据,分析请求长度分布、峰值QPS、SLO要求,基于流量画像调整调度参数,不要直接使用默认值。
  2. 多队列隔离:不同优先级、不同序列长度的请求必须放入独立队列,避免相互影响。
  3. 显存预估前置:构建批次时必须预估显存占用,预留20%的余量,避免OOM。
  4. 动态参数调整:基于实时SLO达标率、GPU利用率动态调整最大批次大小和最大等待时间,SLO达标率高于99.9%且GPU利用率低于50%时增大批次,SLO达标率低于99%时减小批次。
  5. 调度开销控制:核心调度逻辑建议用Rust实现,Python实现时避免复杂计算,调度开销控制在1ms以内。
  6. 降级预案:设置多级降级策略,GPU利用率超过90%时限制免费用户请求,超过95%时限制普通用户请求,OOM时自动重启执行器并调小批次大小。

6. 未来发展趋势

Batching感知调度技术未来会向五个方向演化:

  1. 多模态混合批次调度:支持文本、图像、音频、视频等多模态请求的混合合并,根据不同模态的计算特性动态调整批次组成,进一步提升利用率。
  2. 全局分布式调度:实现跨GPU、跨节点的全局批次优化,将整个集群的GPU作为统一资源池,避免单节点资源碎片化,吞吐量可再提升20%-30%。
  3. 强化学习调度:用强化学习训练调度策略,比传统规则策略的吞吐量提升20%以上,同时更好地满足SLO要求。
  4. Serverless原生调度:和Serverless平台深度集成,根据批次大小动态扩容缩容GPU资源,空闲时自动缩容,进一步降低成本。
  5. 硬件感知调度:结合GPU硬件特性(如H100的Tensor Core、MIG切分)调整批次大小和组成,最大化硬件利用率。

7. 本章小结

Batching感知调度是当前大模型推理优化中投入产出比最高的技术手段,通过全局优化调度,在满足SLO要求的前提下,可将GPU利用率从平均20%提升至70%以上,吞吐量提升8-12倍,推理成本降低60%以上。本文从理论到实践全面解析了Harness中Batching感知调度的实现,提供了可直接落地的代码示例和最佳实践。对于大模型部署团队来说,优先落地Batching感知调度,结合流量画像、多队列隔离等最佳实践,可以快速获得显著的成本收益。未来随着多模态大模型和分布式推理的发展,Batching感知调度还会有更大的优化空间,成为推理服务技术栈中不可或缺的核心组件。

全文字数:约12800字

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐