单模型推理原理:模型加载、请求处理与内存管理

聊到多模型并行调度,咱们得先把单模型推理这件事吃透。你想想看,如果连一个模型怎么跑起来的都不清楚,那后面搞并行调度就是空中楼阁。这一章,我就带你走一遍单模型推理的完整链路——从模型加载到请求处理,再到上下文和内存管理。

模型加载流程:从磁盘到显存

模型加载,说白了就是把一堆参数文件从硬盘搬到显存里。这个过程看着简单,但坑不少。

我习惯把加载流程拆成三步:

  1. 文件解析:读取模型文件(比如GGUF、SafeTensors),解析出张量数据和元信息
  2. 内存分配:在CPU内存和GPU显存中申请空间
  3. 参数搬运:把张量数据从磁盘→CPU内存→GPU显存

这里有个关键点——模型文件格式决定了加载策略。Ollama用的是GGUF格式,它把模型参数做了分块存储。为什么要分块?因为大模型动辄几十GB,一次性全加载到内存里,机器直接卡死。

核心原则:按需加载,懒加载

不是所有层都同时需要。比如做推理时,只有当前层和后续几层在显存里就行。前面的层算完就可以释放。

我在项目中遇到过一个问题:加载一个70B的模型,显存只有24GB。按常规做法肯定装不下。后来我用了分片加载——把模型切成4份,每次只加载1份到显存,算完再换下一份。虽然慢了点,但至少能跑起来。

// 伪代码:分片加载流程
for each shard in model.shards:
    load_shard_to_gpu(shard)      // 加载到显存
    compute_forward(shard)        // 前向计算
    unload_shard_from_gpu(shard)  // 释放显存

注意:分片加载会引入额外的I/O开销。如果你的显存够大,尽量一次性加载完整模型。我曾经因为分片太细,导致推理延迟从100ms飙到了500ms。

推理请求处理:从输入到输出

模型加载完了,接下来就是处理推理请求。这个过程我习惯叫它「推理流水线」。

一个典型的推理请求处理流程是这样的:

  1. 请求入队:用户发来的请求先进入队列,等待调度
  2. Token化:把文本转成Token ID序列
  3. 前向计算:模型逐层计算,生成下一个Token的概率分布
  4. 采样:从概率分布中选一个Token(比如用top-k或top-p采样)
  5. 输出:把Token ID转回文本,返回给用户

嗯,这里要注意——推理请求是流式的。不是等全部Token生成完再返回,而是生成一个Token就返回一个。这样用户体验更好,用户能看到文字一个字一个字地蹦出来。

我的经验:流式输出对后端压力更大。因为每个Token都要走一遍完整的推理流水线。我建议在服务端做批处理——把多个请求的Token打包成一个batch,一次推理算出多个结果。这样吞吐量能提升3-5倍。

为什么会这样?因为GPU擅长并行计算。一个batch里塞4个请求,计算时间只比单个请求多20%左右。但吞吐量翻了4倍,划算。

上下文管理:KV Cache的秘密

上下文管理,是单模型推理里最容易出问题的地方。说白了就是KV Cache的管理。

Transformer模型做自回归生成时,每生成一个新Token,都要重新计算前面所有Token的Key和Value。这太浪费了。所以业界搞了个KV Cache——把之前算好的K和V缓存起来,下次直接用。

KV Cache的大小跟序列长度batch size成正比。公式很简单:

KV Cache大小 = 2 × 层数 × 头数 × 序列长度 × 隐藏维度 × 精度字节数

举个例子:一个7B模型,层数32,头数32,隐藏维度4096,用FP16(2字节)。序列长度2048时,KV Cache大小是:

2 × 32 × 32 × 2048 × 4096 × 2 = 34.4 GB

你看,光KV Cache就吃掉34GB显存。模型本身才14GB左右。所以上下文越长,显存压力越大

避坑指南:我曾经在生产环境遇到过OOM(显存溢出),就是因为没控制好KV Cache。用户连续发了几个长文本请求,每个都生成2048个Token,KV Cache直接撑爆了显存。

解决方案有几个:

  • 限制最大上下文长度:比如Ollama默认是2048,你可以调小到1024
  • 使用滑动窗口:只保留最近N个Token的KV Cache,旧的丢弃
  • 共享KV Cache:多个请求如果前缀相同,可以共享一部分KV Cache

内存与显存管理:精细化的艺术

内存管理,说白了就是怎么用最少的内存,跑最大的模型。我在这上面踩过不少坑。

先看一张图,帮你理解整个内存布局:

单模型推理内存布局 磁盘 (Disk) 模型文件 (GGUF) ~14GB (7B模型) 分块存储 加载 CPU内存 (RAM) 模型参数副本 ~14GB 临时缓冲区 搬运 GPU显存 (VRAM) 模型参数 KV Cache 中间激活值 显存占用明细 (以7B模型为例) 项目 大小 说明 模型参数 ~14 GB FP16精度 KV Cache ~34 GB 序列长度2048

从图里你能看到,显存里最占地方的不是模型参数,而是KV Cache。所以显存管理的核心就是管好KV Cache

我常用的几个技巧:

  • 显存池化:提前申请一大块显存,按需分配,避免频繁申请释放带来的碎片
  • 内存映射:用mmap把模型文件映射到虚拟内存,按需加载到物理内存
  • 显存压缩:对KV Cache做量化,比如从FP16降到INT8,显存占用直接减半

我的习惯:在Ollama里,我会设置--num-gpu-layers参数来控制多少层放在GPU上。如果显存不够,就把一部分层放到CPU上跑。虽然慢点,但至少不会OOM。

还有一个容易被忽略的点——显存碎片化。频繁的申请和释放会导致显存出现很多小碎片,明明总剩余显存够,但就是申请不到一块连续的大空间。我建议用显存整理机制,定期把碎片合并一下。

曾经踩过的坑:有一次我跑一个长文本生成任务,显存还剩6GB,但申请一个4GB的KV Cache时却失败了。查了半天,发现是显存碎片太多。后来加了显存整理,问题就解决了。

好了,单模型推理的原理就聊到这儿。你想想看,模型加载、请求处理、上下文管理、内存管理,这四个环节环环相扣。任何一个环节出问题,整个推理链路都会卡住。下一章我们会聊多模型并行调度,到时候这些单模型的知识都会用上。


专注资料整理