混合部署架构:CPU+GPU协同推理的任务调度策略

#单纯 GPU 推理成本高,纯 CPU 推理延迟大。在实际工程中,一台服务器往往同时配备大量 CPU 核心和少量 GPU,如何让二者协同工作,最大化整机算力利用率,是推理架构设计的核心问题之一。

本篇聚焦 CPU+GPU 混合部署的架构模式与任务调度策略,从原理到工程实现全面覆盖。


1 混合部署的核心动机

1.1 资源互补

资源 CPU GPU
算力特点 串行/少并行,擅长复杂逻辑 大规模并行,擅长矩阵运算
内存容量 大(数百 GB 系统内存) 小(通常 16-80 GB 显存)
带宽特点 较低(百 GB/s 量级) 高(TB/s 量级 HBM)
成本

混合部署的本质是:用 CPU 的大内存承载模型权重溢出,用 GPU 的高算力加速热路径计算

1.2 典型场景

  1. 超大模型部署:单 GPU 显存不足以容纳全部模型权重,将部分层卸载到 CPU
  2. 成本敏感场景:用少量 GPU 处理高优先级/短 token 请求,CPU 处理低优先级/批处理请求
  3. 峰谷调度:GPU 满载时,溢出请求由 CPU 承接,保证服务可用性

2 架构模式一:层间分割(Layer-wise Offloading)

将 Transformer 模型的不同层分别部署在 CPU 和 GPU 上,数据在层间传递时跨设备流动。

2.1 工作原理

复制代码
输入 Tokens
    ↓
[Embedding Layer]       ← CPU 或 GPU
    ↓
[Layer 1-16]            ← GPU(显存充足的层)
    ↓ (数据传输: GPU→CPU)
[Layer 17-32]           ← CPU(溢出层)
    ↓ (数据传输: CPU→GPU)
[Output Head]           ← GPU
    ↓
输出 Tokens

瓶颈:层间数据传输(PCIe 带宽约 16-64 GB/s)会成为性能瓶颈,需要最小化跨设备传输次数。

2.2 llama.cpp 的 GPU 层卸载

llama.cpp 通过 -ngl(Number of GPU Layers)参数控制卸载层数:

bash 复制代码
# 安装 CUDA 支持版本
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j $(nproc)

# 将前 28 层卸载到 GPU,剩余层在 CPU 上运行
./build/bin/llama-cli \
    -m llama-3-70b.Q4_K_M.gguf \
    -t 16 \
    -ngl 28 \
    -c 4096 \
    -p "你好"

# 完全卸载到 GPU(所有层)
./build/bin/llama-cli -m model.gguf -ngl 999

# 纯 CPU 推理
./build/bin/llama-cli -m model.gguf -ngl 0

调优策略 :逐步增加 -ngl 的值,直到 GPU 显存接近上限(留 10% 余量),此时吞吐量通常最优。


3 架构模式二:请求级分流(Request-level Routing)

不同请求根据优先级、长度或资源预算,路由到不同的计算后端:

复制代码
                    ┌─────────────────┐
请求入口 ──────────► │  路由调度器       │
                    └────────┬────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
        [短序列/高优]   [中等序列]      [长序列/批处理]
              │              │              │
         GPU 后端        CPU 后端        CPU 后端
         (低延迟)        (中等延迟)      (高吞吐)

3.1 调度器实现(Python 示例)

python 复制代码
import asyncio
from dataclasses import dataclass
from enum import Enum

class Backend(Enum):
    GPU = "gpu"
    CPU = "cpu"

@dataclass
class InferenceRequest:
    prompt: str
    max_tokens: int
    priority: int    # 1=高优, 2=普通, 3=低优

class HybridScheduler:
    def __init__(self):
        self.gpu_queue = asyncio.Queue(maxsize=32)
        self.cpu_queue = asyncio.Queue(maxsize=256)
        
        # 路由规则
        self.GPU_MAX_TOKENS = 512      # GPU 处理短生成
        self.HIGH_PRIORITY_THRESHOLD = 1

    def route(self, request: InferenceRequest) -> Backend:
        """决定请求路由到哪个后端"""
        # 规则 1:高优先级请求走 GPU
        if request.priority == self.HIGH_PRIORITY_THRESHOLD:
            return Backend.GPU
        
        # 规则 2:短生成请求走 GPU(延迟敏感)
        if request.max_tokens <= self.GPU_MAX_TOKENS:
            return Backend.GPU
        
        # 规则 3:GPU 队列已满,降级到 CPU
        if self.gpu_queue.full():
            return Backend.CPU
        
        return Backend.CPU

    async def submit(self, request: InferenceRequest):
        backend = self.route(request)
        if backend == Backend.GPU:
            await self.gpu_queue.put(request)
        else:
            await self.cpu_queue.put(request)
        return backend

4 架构模式三:流水线并行(Pipeline Parallelism)

将一个请求的处理拆分为多个阶段,CPU 和 GPU 同时处理不同阶段的不同请求,形成流水线:

复制代码
时间线:
GPU: [请求A推理]  [请求B推理]  [请求C推理]
CPU: [请求A后处理] [请求B预处理] [请求C后处理]
                  ↑同时进行↑

CPU 负责:tokenization、prompt 格式化、后处理、结果序列化

GPU 负责:模型前向推理

这种模式下,CPU 与 GPU 几乎不存在等待,整体吞吐量接近纯 GPU 推理的上限。


5 PCIe 数据传输优化

混合部署的隐性瓶颈是 CPU-GPU 之间的数据传输。

5.1 使用锁页内存(Pinned Memory)

python 复制代码
import torch

# 普通内存(需要先拷贝到锁页内存再传输)
cpu_tensor = torch.randn(1000, 1000)

# 锁页内存(直接走 DMA,传输速度提升 2-3x)
pinned_tensor = torch.randn(1000, 1000).pin_memory()

# 异步传输到 GPU(不阻塞 CPU 计算)
gpu_tensor = pinned_tensor.to("cuda", non_blocking=True)

# CPU 和 GPU 可以同时工作
do_cpu_work()

# 同步点:确保传输完成
torch.cuda.synchronize()

5.2 重叠传输与计算

python 复制代码
import torch

stream_compute = torch.cuda.Stream()
stream_transfer = torch.cuda.Stream()

for batch in dataloader:
    # 在传输流中异步上传下一批数据
    with torch.cuda.stream(stream_transfer):
        next_batch_gpu = batch.pin_memory().to("cuda", non_blocking=True)
    
    # 在计算流中处理当前批次
    with torch.cuda.stream(stream_compute):
        output = model(current_batch_gpu)
    
    # 等待传输完成后切换
    stream_compute.wait_stream(stream_transfer)
    current_batch_gpu = next_batch_gpu

6 显存不足时的动态卸载策略

当推理过程中 GPU 显存耗尽时,需要动态将部分数据卸载到 CPU:

python 复制代码
import torch

class MemoryAwareModel:
    def __init__(self, model, cpu_offload_threshold=0.9):
        self.model = model
        self.threshold = cpu_offload_threshold
    
    def get_gpu_memory_ratio(self) -> float:
        """获取 GPU 显存使用率"""
        if not torch.cuda.is_available():
            return 0.0
        used = torch.cuda.memory_allocated()
        total = torch.cuda.get_device_properties(0).total_memory
        return used / total
    
    def maybe_offload(self):
        """显存使用率超阈值时,将 KV Cache 卸载到 CPU"""
        if self.get_gpu_memory_ratio() > self.threshold:
            torch.cuda.empty_cache()
            # 将历史 KV Cache 移到 CPU
            for layer in self.model.layers:
                if hasattr(layer, "kv_cache") and layer.kv_cache is not None:
                    layer.kv_cache = layer.kv_cache.cpu()
    
    def forward(self, input_ids):
        self.maybe_offload()
        return self.model(input_ids)

7 总结与选型建议

场景 推荐架构
70B+ 模型,显存不足 层间分割(llama.cpp -ngl 调参)
高并发,请求特征差异大 请求级分流(GPU 短请求 + CPU 长请求)
吞吐量最大化,延迟要求宽松 流水线并行
GPU 显存充足,CPU 做预处理 锁页内存 + 异步传输

核心原则

  • 最小化 PCIe 传输次数,将跨设备数据传输与计算重叠
  • GPU 做计算密集操作,CPU 做逻辑密集操作(路由、格式化、后处理)
  • 监控 GPU 显存利用率和 PCIe 带宽占用,这两个是混合部署的核心瓶颈指标
相关推荐
无忧智库1 小时前
智库级深度复盘:智慧能源管理云平台解决方案——从“源-网-荷”互动到“产-融-数”一体化的架构演进(PPT)
架构
人工智能AI技术1 小时前
Karpathy LLM Wiki:一种将RAG从解释器模式升级为编译器模式的架构
人工智能
weixin_457260501 小时前
Linux 命令精讲(博客案例)
linux·运维·服务器
娟宝宝萌萌哒1 小时前
Claude Code 核心架构和源码解析
人工智能·agent
AI服务老曹1 小时前
源码级赋能:基于 Spring Boot 的 AI 视频管理平台二次开发指南与架构解耦实践
人工智能·spring boot·音视频
上海云盾第一敬业销售1 小时前
2026年企业DDoS防护指南:高防CDN与高防IP架构选型与实战
tcp/ip·架构·ddos
mit6.8241 小时前
记线下黑客松有感
人工智能
Jay-r2 小时前
AI、机器人、量子计算:大脑、身体与超级算力的三重奏
人工智能·机器人·量子计算·ai助手
wefg12 小时前
【计算机网络】应用层协议(序列化与反序列化/HTTP/HTTPS)
服务器·网络·计算机网络