你用 vllm serve 启动了 Mistral-7B-Instruct 或 Llama-3.1-70B-Instruct,发送一个 12000 token 的 RAG 请求,服务端却直接拒绝:This model's maximum context length is 8192 tokens. However, you requested 12000 tokens——可模型卡上明明写着 128k。报错里那个数字几乎从来不是模型的真实上限,而是 vLLM 启动时在分页 KV cache 里实际能塞下的长度,这个值通常远小于模型 config 里声明的最大值。
最快修复(覆盖约 80% 的情况): 显式把 --max-model-len 设成显存能装下的值,同时调高 --gpu-memory-utilization 让 KV cache 池更大。先试这个:
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 16k \
--gpu-memory-utilization 0.92
然后用 curl -s http://localhost:8000/v1/models | python3 -m json.tool | grep max_model_len 确认真实生效的长度。如果打印出来的就是你要的,那就搞定了;如果不是,按下面的分类继续排查。
说明:截至 2026 年 6 月,vLLM 默认运行 V1 引擎(V1 自 v0.6 起就是默认,当前主线约 v0.11)。V1 会自动启用 chunked prefill 和 automatic prefix caching,所以以前必须手动加的一些参数(如
--enable-chunked-prefill)现在默认就开着。如果你还在用老的 V0 版本,先升级——绝大多数长上下文相关的小坑都在 V1 里修掉了。
你属于哪一类?
先看清楚确切的报错字符串。vLLM 会抛出三种不同的报错,对应的原因也不同。
| 你看到的报错字符串 | 含义 | 跳到 |
|---|---|---|
This model's maximum context length is X tokens. However, you requested Y tokens (A in the messages, B in the completion) | 你的请求(prompt + max_tokens)超过了服务端配置的 max_model_len | 原因 1 和原因 4 |
The model's max seq len (X) is larger than the maximum number of tokens that can be stored in KV cache (Y). Try increasing gpu_memory_utilization or decreasing max_model_len when initializing the engine | 服务启动失败:显存装不下 max_model_len 对应的 KV cache | 原因 2 |
The prompt (total length 25938) is too long to fit into the model (context length 4096). Make sure that max_model_len is no smaller than the number of text tokens plus multimodal tokens | 离线 LLM(...) 调用路径,或多模态请求里图片 token 把预算撑爆了 | 原因 4 和原因 5 |
常见原因
按命中率从高到低排列。
1. max_model_len 被悄悄截断以适配 KV cache
如果你不传 --max-model-len,vLLM 会从模型 config.json 读取 max_position_embeddings(常见是 32768 或 131072)并尝试用它。但要装下这么多 token 的 KV cache 往往超出显存,于是 vLLM 把可用长度降到 cache 能容纳的水平,并打印一条警告——这条警告通常淹没在启动刷屏的日志里。之后任何超过真实上限的请求,都会报 This model's maximum context length is X tokens。
怎么判断:运行 curl -s http://localhost:8000/v1/models | python3 -m json.tool | grep max_model_len,和你的预期对比。fp16 KV cache 有个粗略经验值:7B 模型大约每 4000 token 占 1 GB,70B 要多得多。如果生效的数字偏小,限制你的是 cache,不是模型本身。
2. 服务启动失败:KV cache 装不下 max_model_len
你设了 --max-model-len 32768,但显存只够放比如 3664 token 的 KV cache,于是 vLLM 在初始化阶段直接退出:The model's max seq len (32768) is larger than the maximum number of tokens that can be stored in KV cache (3664)。在 V1 里,引擎预留的 KV cache 大约是 max_num_seqs × max_model_len 这么多 token 槽位,所以 --max-num-seqs 设得越高,这个问题越严重。
怎么判断:进程是在初始化时退出,而不是在收到请求时报错。报错里直接写明了 KV cache 的 token 上限。把 --max-model-len 降到这个上限之下,或降低 --max-num-seqs,或调高 --gpu-memory-utilization 释放显存。
3. 显存预算不对(utilization 太低,或权重 + cache 太挤)
--gpu-memory-utilization 默认 0.9。如果显卡上还有别的东西占着显存(另一个进程、一个 notebook、桌面合成器),vLLM 分到的那一份就缩水,KV 池也跟着缩。反过来,换大模型后你可能还留着之前测试时设的低 utilization 值。
怎么判断:启动前用 nvidia-smi 看空闲显存。在 vLLM 启动日志里找 # GPU blocks: 那一行——那是 cache 的真实大小。每个 block 默认装 16 token,所以 # GPU blocks: 2048 意味着整台服务总共 32768 token 的预算。
4. prompt + completion 加起来超过 max_model_len
vLLM 是把输入 token 加上最大输出 token 一起算进窗口的。报错里写得很清楚:(A in the messages, B in the completion)。如果你的 prompt 是 28000 token,又设了 max_tokens=4096,加上特殊 token 后总和 32096 就可能超过 32768 的上限。
怎么判断:把 API 调用里的 prompt_tokens + max_tokens 加起来。如果总和达到或超过 max_model_len,vLLM 会在推理开始前就拒绝请求。注意 vLLM 是直接拒绝,不会自动把 max_tokens 砍小,所以得你自己留出余量。
5. 多模态图片 token 撑大了 prompt
对视觉模型(Qwen-VL、Llama-Vision 等),每张图会按分辨率和长宽比展开成几百到几千个 token。一个看似”很短”、只带一张高清 base64 图片的 prompt,可能就超过了 max_model_len,报 The prompt (total length N) is too long to fit into the model。
怎么判断:去掉图片、只发同样的文字再试一次。如果纯文字能装下,那就是图片 token 的锅。把图片降分辨率,或用模型的 --limit-mm-per-prompt / 分辨率相关参数控制。
6. RoPE scaling 没被读到,长上下文上限退回基础长度
像 Llama-3.1 这类模型,是靠 config.json 里声明的 RoPE scaling 把上下文扩展到超出基础训练长度的。如果这段配置缺失,或某个社区二次上传改动了 max_position_embeddings,无论显存多充足,vLLM 都会卡在基础长度(如 8192)。
怎么判断:打开模型的 config.json,把 rope_scaling 和 max_position_embeddings 和官方模型卡对一遍。如果被改小了,重新下载官方权重,或显式传 --rope-scaling 和 --max-model-len。细节见 RoPE scaling 设错让长上下文输出乱掉。
最短修复路径
Step 1:查清楚真实生效的长度
# 服务实际对外声明的长度:
curl -s http://localhost:8000/v1/models | python3 -m json.tool | grep max_model_len
# 它在启动时实际决定了什么(block 数和截断警告):
journalctl -u vllm -n 300 | grep -iE 'GPU blocks|max_model_len|maximum number of tokens|Reducing'
Step 2:显式设置 max-model-len 并提高显存配额
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--max-model-len 32k \
--gpu-memory-utilization 0.92 \
--host 0.0.0.0 \
--port 8000
--max-model-len 支持可读写法(16k、32k、128k);小写 k 是 1000,大写 K 是 1024。--gpu-memory-utilization 小步往上调(0.90 → 0.92 → 0.95)并留意 CUDA OOM——调太高会把激活缓冲区挤没。
Step 3:启动不起来时,降低上限或并发
当你撞上 The model's max seq len is larger than the maximum number of tokens that can be stored in KV cache,瓶颈就是 KV cache。要么把 --max-model-len 降到报错里写的那个数字以下,要么降低 --max-num-seqs(V1 预留的 cache 约为 max_num_seqs × max_model_len):
vllm serve Qwen/Qwen2.5-7B-Instruct \
--max-model-len 16k \
--max-num-seqs 16 \
--gpu-memory-utilization 0.92
Step 4:用 fp8 把 KV cache 砍半,或溢出到 CPU
--kv-cache-dtype fp8 用 8 位存 cache,内存大约减半,让你在同一张卡上保住更长的 --max-model-len(需要 CUDA 11.8+ 或受支持的 AMD GPU):
vllm serve Qwen/Qwen2.5-7B-Instruct \
--max-model-len 64k \
--kv-cache-dtype fp8 \
--gpu-memory-utilization 0.92
如果还是不够,--cpu-offload-gb N 把权重卸载到主机内存,腾出显存给 cache(更慢,但能让大模型先跑起来):
vllm serve meta-llama/Llama-3.1-70B-Instruct \
--max-model-len 32k \
--cpu-offload-gb 16 \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.90
Step 5:量化权重后回收显存
AWQ/GPTQ 会缩小权重,但 KV cache 默认仍是 fp16,除非你另行指定。量化之后,把 --gpu-memory-utilization 往上推,让腾出来的显存变成 cache:
vllm serve TheBloke/Llama-3.1-8B-Instruct-AWQ \
--quantization awq \
--max-model-len 32k \
--gpu-memory-utilization 0.95
对比量化前后的 # GPU blocks: 行——如果 block 数没涨,说明腾出来的显存白白浪费了。
Step 6:在客户端预留输出 token
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
# 若 max_model_len = 16384、prompt 约 12000 token,
# 让 prompt + max_tokens 稳稳低于上限:
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": long_prompt}],
max_tokens=2048, # 12000 + 2048 = 14048,安全地低于 16384
)
如何确认已修复
发一个接近目标长度的请求,确认它能正常返回而不是报错:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
# 约 10k token 的 prompt 来探真实上限
long_text = "Summarize the following text:\n" + ("The quick brown fox. " * 600)
r = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": long_text}],
max_tokens=200,
)
print(r.usage) # prompt_tokens 应与你发送的长度吻合
print(r.choices[0].message.content[:200])
如果 r.usage.prompt_tokens 和预期一致、且没有报错,说明上限确实抬上去了。然后在真实负载下盯一下 /metrics Prometheus 端点里的 vllm:gpu_cache_usage_perc——如果它经常停在 80% 以上,说明你又快到边缘了。
预防建议
- 始终显式传
--max-model-len,按显存大小设定,不要信任模型 config 的默认值;这也能避免换模型时残留旧值。 - 记住 V1 的 cache 经验法则:预留的 KV cache 随
max_num_seqs × max_model_len增长。长上下文和高并发抢的是同一个池子——二选一去优先保障。 - 预留输出预算:把
max_tokens设为不超过max_model_len - 预期 prompt token - 256。 - 对 RAG,在应用层先把检索到的片段截断到固定的 prompt 预算,再发起调用,而不是让服务端来拒绝。
- 给 CI 加一条启动断言:请求
/v1/models并校验max_model_len符合预期,通过后再放流量进来。 - AWQ/GPTQ 量化之后,把
--gpu-memory-utilization调到 0.92-0.95,把腾出的显存变成 cache。 - 监控
vllm:gpu_cache_usage_perc;如果经常超过 80%,就加显存、启用--kv-cache-dtype fp8,或降低--max-num-seqs。
常见问答 (FAQ)
Q:报错说我的上下文是 8192,可模型卡写的是 128k,到底谁对?
A:都对。128k 是架构上的最大值,8192 是 vLLM 在你这张 GPU 上真正能塞进 KV cache 的长度。解决办法不是换模型,而是调高 --gpu-memory-utilization、启用 --kv-cache-dtype fp8、降低 --max-num-seqs,或加显存,直到 cache 能装下你需要的长度。
Q:vLLM 接受了我的 32k prompt,但回答提前就截断了,是同一个问题吗?
A:通常不是。提前截断一般是 max_tokens 设得太小,或者因为聊天模板不匹配、模型提前吐了 EOS token。看响应里的 finish_reason:length 表示撞到了 max_tokens;stop 表示模型自己结束了。
Q:gpu_cache_usage_perc 到 100% 意味着什么?
A:所有分页 KV block 都被占满了。新请求会排队或被拒绝,取决于 --max-num-seqs 和调度配置。降低 --max-num-seqs、降低 --max-model-len、改用 --kv-cache-dtype fp8,或加显存。
Q:能把 KV cache 卸载到 CPU 来获得更长的上下文吗?
A:--cpu-offload-gb N 是把模型权重卸载到主机内存,从而腾出显存给 cache 用,间接让你能保住更长的 --max-model-len。它是拿延迟换容量,所以留给那些确实装不下的场景。
Q:--tensor-parallel-size 会提高最大上下文长度吗?
A:不会直接提高 max_model_len,但它把 KV cache 切片分摊到多张 GPU 上,所以总 cache 大致随卡数增长。这样能分配更多 block,支持更长的上下文或更高的并发。
Q:同一份代码在 A100 80GB 上正常,换到 A6000 48GB 就失败,为什么?
A:显存更少意味着 KV cache 更小,真实上限大致按比例下降。降低 --max-model-len、启用 --kv-cache-dtype fp8,或用 --tensor-parallel-size 切到多卡。
相关阅读
- vLLM 启动报 CUDA 版本不匹配
- 本地模型输出在 token 中间被截断
- RoPE scaling 设错让长上下文输出乱掉
- ChatGPT 上下文窗口超限了怎么办
- Gemini 上下文比预期短
- Claude 长上下文不稳定
标签: #local-llm #vllm #排查