昇腾empty_cache原理
这是个非常好的观察,而且你没有看错 👍
在 vLLM-Ascend(NPU) 上,torch.npu.empty_cache() 确实可能导致 non-torch 显存下降,这和 CUDA 的行为本质不同。
下面我分「结论 → 原因 → 机制细节 → vLLM 场景」把这件事讲清楚。
一句话结论(先给你)
在 Ascend / torch-npu 上:
torch.npu.empty_cache()不仅仅是 PyTorch cache 操作,
它会间接触发 Ascend Runtime / ACL 的内存回收路径,
从而 释放部分 non-torch(runtime / kernel / workspace)显存。
这在 CUDA 上几乎不会发生,但在 Ascend 上是“设计如此”。
1️⃣ 为什么 CUDA 上不行,Ascend 上却行?
CUDA(torch.cuda.empty_cache)
只操作 PyTorch CUDACachingAllocator
不会触发:
- CUDA Runtime 内部 pool
- cuBLAS / cuDNN / FlashAttention workspace
non-torch 显存几乎完全不受影响
Ascend(torch.npu.empty_cache)
Ascend 的内存体系是:
1 | PyTorch (torch-npu) |
关键点在这里👇
torch-npu 的 allocator 和 Ascend Runtime 的 allocator 是“半耦合”的
2️⃣ 真正发生了什么?(机制层)
当你调用:
1 | torch.npu.empty_cache() |
在 torch-npu 内部会发生几件事(不是官方文档写清楚的那种 😄):
① PyTorch NPU caching allocator 释放空闲 block
这一步和 CUDA 类似。
② 触发 ACL runtime 的「内存同步 / GC 信号」
这一步是关键差异点:
torch-npu 在 empty_cache 后:
- 会调用 aclrtSynchronize
- 并向 Ascend runtime 发送 内存释放 hint
Ascend runtime 会检查:
当前 device memory pool 的使用水位
是否存在:
- 已完成 kernel 的 workspace
- 可回收的临时 buffer
- profiling / graph compile 残留
👉 这些全部是 non-torch 显存
③ Ascend runtime 是“激进回收型”
Ascend 的策略是:
只要你明确表示“我现在不缺性能,可以回收”
runtime 就会尝试 shrink 自己的内存池
torch.npu.empty_cache()
在 Ascend 上被当成了这个信号。
3️⃣ 为什么 vLLM-Ascend 特别明显?
因为 vLLM-Ascend 正好踩中了 Ascend runtime 的几个“重缓存点”。
vLLM-Ascend 中的 non-torch 显存主要来自:
- Attention / FlashAttention NPU kernel workspace
- ViT / Vision encoder 大 workspace
- HCCL 通信 buffer
- ACL Graph compile / kernel autotune 缓存
- Runtime 级别的 memory pool 高水位
这些都有一个特点:
生命周期不跟 Tensor 绑定
在什么时刻你会“看到它降下来”?
通常是:
- dummy_run / profiling 之后
- 第一批大请求跑完
- 图像尺寸固定后
- batch 下降
- request 数减少
- 或你刚
del llm/del model
然后你调用:
1 | torch.npu.empty_cache() |
👉 runtime 发现:
kernel 都结束了
workspace 没人用
PyTorch 都在回收
那我也回收吧
于是:
non-torch 显存明显下降
4️⃣ 这是不是“bug”?能不能依赖?
❗ 不是 bug,是实现选择
但有两个重要 caveat:
⚠️ 1. 不保证每次都生效
如果:
- kernel 仍在飞
- graph 被固定
- workspace 被下次推理复用
runtime 不会回收
⚠️ 2. 有性能代价
下一次推理时:
- kernel workspace 可能要重新申请
- autotune / graph cache 可能重建
empty_cache = 用显存换性能
5️⃣ 在 vLLM-Ascend 中如何“正确”使用它?
✅ 推荐用法(你这种场景)
1 | # 只在这些时刻用 |
例如:
1 | llm = LLM(...) |
❌ 不推荐
1 | # 每个 request 都 empty_cache |
6️⃣ 对比总结(重点记住)
| 行为 | CUDA | Ascend |
|---|---|---|
| empty_cache 清 PyTorch cache | ✅ | ✅ |
| 影响 runtime 内存池 | ❌ | ⚠️ 可能 |
| 回收 kernel workspace | ❌ | ✅(条件满足时) |
| non-torch 显存下降 | 几乎不可能 | 你看到的这种情况 |
| 是否稳定 | 稳定 | 启发式 / 依赖时机 |
7️⃣ 给你一句“工程级结论”
在 vLLM-Ascend 上:
torch.npu.empty_cache()是一个
“runtime 级别的显存整形(defragment + shrink)触发器”,
而不仅仅是 PyTorch cache API。
如果你愿意,下一步我可以直接帮你做三件更“狠”的事之一:
- 精确区分:到底是哪一类 non-torch 被回收了(HCCL / kernel / graph)
- 设计一套 只 shrink 一次、之后显存不再抖动的 vLLM-Ascend 启动模板
- 帮你判断:你现在看到的 non-torch 下降,是“好事”还是“隐藏性能坑”
你这个问题,已经是 Ascend 显存调优的核心层级了。