注意力后端的选择与自动降级:环境变量与命中证据
在 **** 的 ****(8 卡,单卡 64GB HBM,卡间互联 392 GB/s)上用 **** 的基础软件栈配合 vLLM 0.6.x 起 OpenAI 兼容服务。为固定执行路径,启动前显式 export VLLM_ATTENTION_BACKEND=FLASH_ATTN。服务正常起来,输出正确,但吞吐低于同规格基线,启动日志里却读不到任何「用了哪个注意力后端」的记录:既没有确认命中的行,也没有报错。这个版本里注意力后端由环境变量选择,但环境变量不是终局:selector.py 读到它之后还要经过平台分支与一组降级分支,任一分支命中即改写选择结果。而且证据是不对称的——只有命中非默认后端时才打印一行 info,命中默认的 FLASH_ATTN 时一行都没有,于是「日志里没有后端记录」这件事本身给不出结论。显式指定不等于命中。
机制:selector.py 的两级选择
vLLM 0.6.x 的注意力后端入口是 vllm/attention/selector.py 的 get_attn_backend(num_heads, head_size, num_kv_heads, sliding_window, dtype, kv_cache_dtype, block_size, is_blocksparse=False)。该函数带 lru_cache,实际选择交给 which_attn_to_use(...)。
选择分两级。第一级取进程内强制值 forced_attn_backend(由 global_force_attn_backend 设置),优先级高于环境变量;未设置时读环境变量 VLLM_ATTENTION_BACKEND(常量 STR_BACKEND_ENV_VAR,读入点 envs.VLLM_ATTENTION_BACKEND)。默认值是 _Backend.FLASH_ATTN。第二级是一组降级分支,在平台或输入约束不满足时改写已选值。
环境变量的合法取值是 _Backend 枚举成员名,大小写敏感,取错值抛:
ValueError: Invalid attention backend '<取值>'. Available backends:
FLASH_ATTN, XFORMERS, ROCM_FLASH, TORCH_SDPA, OPENVINO, FLASHINFER, PALLAS, IPEX (case-sensitive).
| 取值 | 实现类 | 适用平台 / 说明 |
|---|---|---|
FLASH_ATTN |
FlashAttentionBackend |
NVIDIA 默认;命中时不打印日志 |
XFORMERS |
XFormersBackend |
降级目标 |
FLASHINFER |
FlashInferBackend |
fp8 KV cache 的推荐后端 |
ROCM_FLASH |
ROCmFlashAttentionBackend |
HIP 平台 |
TORCH_SDPA |
TorchSDPABackend |
仅 CPU |
OPENVINO |
OpenVINOAttentionBackend |
OpenVINO |
IPEX |
IpexAttnBackend |
XPU |
PALLAS |
PallasAttentionBackend |
TPU |
该版本没有对应的 CLI 旗标,后端只能由环境变量或进程内强制值指定。
平台分支先行改写
which_attn_to_use 先处理平台分支。这些分支不看环境变量是否与之冲突,只在冲突时打一行 logger.info 再返回平台固定后端:
| 平台 | 返回 | 冲突时的提示 |
|---|---|---|
| CPU | TORCH_SDPA |
Cannot use %s backend on CPU. |
| OpenVINO | OPENVINO |
Cannot use %s backend on OpenVINO. |
| XPU | IPEX |
Cannot use %s backend on XPU. |
| TPU | PALLAS |
Cannot use %s backend on TPU. |
| HIP | ROCM_FLASH |
原选 FLASH_ATTN 时先改写为 ROCM_FLASH;否则打 %s is not supported in AMD GPUs. |
在非对应平台上设了环境变量,返回值与设置值不同,且只有一行 info 级提示。
NVIDIA 上的逐级降级
平台分支之后,若已选值仍是 FLASH_ATTN,逐条检查下列条件,命中任一条即改写为 XFORMERS:
| 条件 | 结果 | 日志片段(info 级) |
|---|---|---|
| 算力 < 80(Volta / Turing) | XFORMERS |
Cannot use FlashAttention-2 backend for Volta and Turing GPUs. |
dtype 非 torch.float16 / torch.bfloat16 |
XFORMERS |
Cannot use FlashAttention-2 backend for dtype other than ... |
kv_cache_dtype 以 fp8 开头 |
XFORMERS |
Cannot use FlashAttention-2 backend for FP8 KV cache. |
block_size % 16 != 0 |
XFORMERS |
Cannot use FlashAttention-2 backend for block size not divisible by 16. |
sliding_window is not None |
XFORMERS |
Cannot use FlashAttention-2 backend due to sliding window. |
head_size 不在支持集,或 vllm.vllm_flash_attn 导入失败 |
XFORMERS |
... because the vllm.vllm_flash_attn package is not found. |
fp8 KV cache 一条除 info 外多打一行 warning,建议改设 VLLM_ATTENTION_BACKEND=FLASHINFER。
命中证据的不对称
日志只在「非 FLASH_ATTN」时出现。FLASH_ATTN 命中时 get_attn_backend 不打印任何行;XFORMERS、FLASHINFER、ROCM_FLASH、TORCH_SDPA 等命中时各打一行:
# 命中非默认后端时的日志(片段,字段名示意)
INFO Using XFormers backend.
INFO Using Flashinfer backend.
INFO Using ROCmFlashAttention backend.
「读到一行后端提示」说明发生了降级或指定了非默认后端;「一行都没有」只能说明走的是默认值 FLASH_ATTN,不能反推环境变量已生效。两条证据要分开读。
触发条件与现象
- 在非目标平台上设了环境变量:返回平台固定后端,只有一行 info。
block_size取非 16 倍数。0.6.2 的--block-sizechoices 为{8,16,32},其中8不是 16 的倍数,选中即命中% 16 != 0分支,FLASH_ATTN被改为XFORMERS。- 模型含滑动窗口注意力:
sliding_window非空,同样降级。 - KV cache 转 fp8:
FLASH_ATTN失格,需显式改用FLASHINFER。 - 自定义
head_size:不在get_supported_head_sizes()内即降级。 - 现象统一为:功能正常、指标异常,启动日志里唯一的线索是一行 info,随后被权重加载信息覆盖。
定位步骤
- 确认选择的来源:检查进程内是否调用了
global_force_attn_backend(优先级高于环境变量),再看VLLM_ATTENTION_BACKEND是否设置、拼写与大小写是否正确。 - 搜启动日志中的后端提示行:
Using、Cannot use FlashAttention-2 backend等关键字;info 级输出可能被日志级别过滤,必要时先调高VLLM_LOGGING_LEVEL。 - 若完全无提示,记为「默认
FLASH_ATTN命中」,再逐条核对降级条件是否可能触发。 - 核对
block_size:确认生效值是 16 的倍数,命令行取值不等于生效值。 - 核对
kv_cache_dtype:fp8 与FLASH_ATTN不兼容。 - 核对模型特征:
head_size、sliding_window。 - 用单变量对照复测:只改一项,观察后端提示行是否出现或消失。
# 固定路径选择并保留证据(参数名取自框架,取值按本机规格填)
export VLLM_ATTENTION_BACKEND=FLASH_ATTN
python -m vllm.entrypoints.openai.api_server \
--model <model-path> \
--block-size 16 \
--kv-cache-dtype auto \
--enforce-eager # 先关图捕获,便于看清真实命中路径
--enforce-eager 不参与注意力后端选择,它只关闭图捕获。
处置
| 动作 | 作用 | 代价 / 前提 |
|---|---|---|
| 修正环境变量拼写与大小写 | 避免 ValueError 直接退出 |
只在取值合法时有效 |
--block-size 取 16 的倍数 |
满足 FLASH_ATTN 的块约束 |
受后端与碎片权衡约束 |
KV cache 保持 auto |
避免 fp8 触发降级 | 放弃 fp8 的显存收益 |
改用 VLLM_ATTENTION_BACKEND=FLASHINFER |
fp8 KV cache 下命中预期后端 | 需确认版本内置 FlashInfer |
| 逐条复核平台分支 | 确认环境变量不会被平台改写 | 平台固定后端不可改 |
| 固定后复测并与默认对照 | 确认命中路径真的变了 | 关闭图捕获的量级不代表稳态 |
记录字段
# 一条注意力后端选择记录(字段名示意)
selection:
env_var: VLLM_ATTENTION_BACKEND
env_value: # 设置值
forced_by_code: # 是否被 global_force_attn_backend 覆盖
default_backend: FLASH_ATTN
resolved_backend: # 实际命中
degradation_hit: # 命中的降级分支(无则记 none)
inputs:
dtype:
kv_cache_dtype:
block_size: # 生效值
sliding_window:
head_size:
device_capability: # 算力等级
platform:
kind: # CPU / CUDA / HIP / TPU / XPU / OpenVINO
log:
backend_line: # 命中非默认后端时的那一行(原文)
degraded_reason: # Cannot use FlashAttention-2 backend ...(原文)
perf:
prefill_stage:
decode_stage:
status:
小结
vLLM 0.6.x 的注意力后端由环境变量 VLLM_ATTENTION_BACKEND 选择,入口是 selector.py 的 get_attn_backend,选择逻辑在 which_attn_to_use。环境变量不是终局:平台分支与一组 NVIDIA 降级分支会把已选值改写为 XFORMERS 或平台固定后端,证据只有一行 info 级日志。默认后端 FLASH_ATTN 命中时不打印任何行,因此「无日志」只等于「走了默认值」。判定顺序是先确认选择来源,再搜日志提示行,最后逐条核对 block_size、kv_cache_dtype、sliding_window、head_size 与平台。