
现象
环境:AutoDL 容器实例,内存上限 90GB,用 Qwen3-8B 做批量评估。
- 控制台提示内存占用 100%,但自己没有在跑任何程序。
- 运行评估脚本,模型刚加载到第 2 个分片就被杀:
text
Loading checkpoint shards: 40%|███▌ | 2/5 [00:01<00:02, 1.42it/s]
main.sh: line 77: 904357 Killed python main.py ...
Killed 意味着进程收到了 SIGKILL。第一反应是"内存不够",但 8B 模型用 bf16 加载只需要约 16GB,离 90GB 很远。问题出在哪?
排查过程
别看 free / top,要看 cgroup
容器里的 free、top 读的是 /proc/meminfo,通常反映的是宿主机 的内存,而不是容器。容器的内存限额和用量记录在 cgroup 里:
| 内容 | cgroup v2 | cgroup v1 |
|---|---|---|
| 内存上限 | /sys/fs/cgroup/memory.max |
/sys/fs/cgroup/memory/memory.limit_in_bytes |
| 当前用量 | /sys/fs/cgroup/memory.current |
/sys/fs/cgroup/memory/memory.usage_in_bytes |
| 用量构成 | /sys/fs/cgroup/memory.stat |
/sys/fs/cgroup/memory/memory.stat |
| OOM 事件 | /sys/fs/cgroup/memory.events |
/sys/fs/cgroup/memory/memory.oom_control |
判断版本:stat -fc %T /sys/fs/cgroup,输出 cgroup2fs 即为 v2。
读 memory.stat:内存到底被谁占了
bash
grep -E '^(anon|file|shmem|file_dirty|file_writeback|active_file|inactive_file) ' /sys/fs/cgroup/memory.stat
实际输出(换算后):
| 字段 | 含义 | 数值 |
|---|---|---|
anon |
进程自己申请的内存(malloc、张量等) | 0.37 GB |
file |
页缓存:内存中的文件副本 | 88.5 GB |
active_file |
其中"最近被访问过"的缓存 | 87.0 GB |
inactive_file |
其中"较久未访问"的缓存 | 1.5 GB |
shmem |
共享内存(/dev/shm 等) |
0 |
file_dirty / file_writeback |
尚未写回磁盘的缓存 | 0 / 0 |
#mermaid-svg-TLSbNeypKdgUFVAW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TLSbNeypKdgUFVAW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TLSbNeypKdgUFVAW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TLSbNeypKdgUFVAW .error-icon{fill:#552222;}#mermaid-svg-TLSbNeypKdgUFVAW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TLSbNeypKdgUFVAW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TLSbNeypKdgUFVAW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TLSbNeypKdgUFVAW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TLSbNeypKdgUFVAW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TLSbNeypKdgUFVAW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TLSbNeypKdgUFVAW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TLSbNeypKdgUFVAW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TLSbNeypKdgUFVAW .marker.cross{stroke:#333333;}#mermaid-svg-TLSbNeypKdgUFVAW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TLSbNeypKdgUFVAW p{margin:0;}#mermaid-svg-TLSbNeypKdgUFVAW .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-TLSbNeypKdgUFVAW .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-TLSbNeypKdgUFVAW .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-TLSbNeypKdgUFVAW .slice{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-TLSbNeypKdgUFVAW .legend text{fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-TLSbNeypKdgUFVAW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 98%1%容器内存 90GB 的构成 页缓存 file(可回收) 88.5 进程内存 anon 0.4 空闲 1.1
结论一目了然:真正被进程占用的只有约 0.4GB,其余几乎全是页缓存。
缓存来自哪些文件
用 fincore(util-linux 自带)可以查看某个文件有多少内容驻留在内存中:
bash
find /root/autodl-tmp -type f -size +100M -print0 | xargs -0 -r fincore -o RES,SIZE,FILE
text
RES SIZE FILE
3.7G 3.7G .../models--Qwen--Qwen3-8B/blobs/31d6a8...
3.7G 3.7G .../models--Qwen--Qwen3-8B/blobs/599123...
3.7G 3.7G .../models--Qwen--Qwen3-8B/blobs/c5185c...
3G 3G .../models--Qwen--Qwen3-8B/blobs/b5ee7d...
1.2G 1.2G .../models--Qwen--Qwen3-8B/blobs/20c2d6...
Qwen3-8B 的 5 个权重分片 100% 在内存里(RES = SIZE),合计约 15GB。剩下约 70GB 来自其他被读写过的文件:数据集、提取的特征、checkpoint、conda 包等。每跑一次评估,缓存就涨一截。
是内核 OOM 杀的吗?
内核的 OOM killer 每杀一个进程,都会在 cgroup 里记一笔:
bash
cat /sys/fs/cgroup/memory.events
被 Killed 后立刻查看:
text
low 0
high 0
max 1083607
oom 0
oom_kill 0
oom_kill 0:内核 一次都没有 因为内存不足杀过进程。max 1083607:用量触碰上限的次数。每次触碰,内核都会回收缓存来腾地方,而且都成功了(否则oom会增加)。file_dirty、file_writeback都是 0,说明缓存也不存在"写不回去、回收不了"的问题。
那么 SIGKILL 是谁发的?
结论:平台监控按"含缓存的占用"杀进程
内核这边一切正常,杀进程的只能是容器外部 的机制。结合现象------监控显示 100%、进程刚开始加载就被杀、内核没有任何 OOM 记录------可以推断:平台监控读取的是包含页缓存的内存用量,用量接近上限时,直接向容器内的进程发送 SIGKILL。
(这是根据上述证据得出的推断,平台未必有公开文档说明。)
平台监控(容器外) 内核(cgroup 内存控制) PyTorch 进程 平台监控(容器外) 内核(cgroup 内存控制) PyTorch 进程 #mermaid-svg-QYfxZAbxKcoNZRHb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QYfxZAbxKcoNZRHb .error-icon{fill:#552222;}#mermaid-svg-QYfxZAbxKcoNZRHb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QYfxZAbxKcoNZRHb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QYfxZAbxKcoNZRHb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QYfxZAbxKcoNZRHb .marker.cross{stroke:#333333;}#mermaid-svg-QYfxZAbxKcoNZRHb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QYfxZAbxKcoNZRHb p{margin:0;}#mermaid-svg-QYfxZAbxKcoNZRHb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QYfxZAbxKcoNZRHb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-QYfxZAbxKcoNZRHb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QYfxZAbxKcoNZRHb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-QYfxZAbxKcoNZRHb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-QYfxZAbxKcoNZRHb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-QYfxZAbxKcoNZRHb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-QYfxZAbxKcoNZRHb .sequenceNumber{fill:white;}#mermaid-svg-QYfxZAbxKcoNZRHb #sequencenumber{fill:#333;}#mermaid-svg-QYfxZAbxKcoNZRHb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-QYfxZAbxKcoNZRHb .messageText{fill:#333;stroke:none;}#mermaid-svg-QYfxZAbxKcoNZRHb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QYfxZAbxKcoNZRHb .labelText,#mermaid-svg-QYfxZAbxKcoNZRHb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-QYfxZAbxKcoNZRHb .loopText,#mermaid-svg-QYfxZAbxKcoNZRHb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-QYfxZAbxKcoNZRHb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-QYfxZAbxKcoNZRHb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-QYfxZAbxKcoNZRHb .noteText,#mermaid-svg-QYfxZAbxKcoNZRHb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-QYfxZAbxKcoNZRHb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QYfxZAbxKcoNZRHb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QYfxZAbxKcoNZRHb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-QYfxZAbxKcoNZRHb .actorPopupMenu{position:absolute;}#mermaid-svg-QYfxZAbxKcoNZRHb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-QYfxZAbxKcoNZRHb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-QYfxZAbxKcoNZRHb .actor-man circle,#mermaid-svg-QYfxZAbxKcoNZRHb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-QYfxZAbxKcoNZRHb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用量 ≈ 90GB,其中 88.5GB 是页缓存 oom_kill 仍为 0:内核从未判定内存不足 加载权重分片,申请内存触碰上限 → 回收旧缓存(memory.events 中 max +1)分配成功,进程正常运行读取用量(含缓存)≈ 100%SIGKILL
两种视角的差异就是问题所在:
| 视角 | 如何看待 88.5GB 页缓存 | 结果 |
|---|---|---|
| 内核 | 可回收,需要时随时让出 | 分配成功,不 OOM |
| 平台监控 | 已用内存 | 判定 100%,杀进程 |
原理
页缓存是什么
Linux 读文件时,不会读完就扔掉,而是在内存里保留一份副本。下次再读同一文件,直接从内存取,不用再访问磁盘。写文件也是先写进缓存,再异步刷回磁盘。
#mermaid-svg-kV7Wa5fQ78emdbxJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kV7Wa5fQ78emdbxJ .error-icon{fill:#552222;}#mermaid-svg-kV7Wa5fQ78emdbxJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kV7Wa5fQ78emdbxJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .marker.cross{stroke:#333333;}#mermaid-svg-kV7Wa5fQ78emdbxJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kV7Wa5fQ78emdbxJ p{margin:0;}#mermaid-svg-kV7Wa5fQ78emdbxJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .cluster-label text{fill:#333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .cluster-label span{color:#333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .cluster-label span p{background-color:transparent;}#mermaid-svg-kV7Wa5fQ78emdbxJ .label text,#mermaid-svg-kV7Wa5fQ78emdbxJ span{fill:#333;color:#333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .node rect,#mermaid-svg-kV7Wa5fQ78emdbxJ .node circle,#mermaid-svg-kV7Wa5fQ78emdbxJ .node ellipse,#mermaid-svg-kV7Wa5fQ78emdbxJ .node polygon,#mermaid-svg-kV7Wa5fQ78emdbxJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kV7Wa5fQ78emdbxJ .rough-node .label text,#mermaid-svg-kV7Wa5fQ78emdbxJ .node .label text,#mermaid-svg-kV7Wa5fQ78emdbxJ .image-shape .label,#mermaid-svg-kV7Wa5fQ78emdbxJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-kV7Wa5fQ78emdbxJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kV7Wa5fQ78emdbxJ .rough-node .label,#mermaid-svg-kV7Wa5fQ78emdbxJ .node .label,#mermaid-svg-kV7Wa5fQ78emdbxJ .image-shape .label,#mermaid-svg-kV7Wa5fQ78emdbxJ .icon-shape .label{text-align:center;}#mermaid-svg-kV7Wa5fQ78emdbxJ .node.clickable{cursor:pointer;}#mermaid-svg-kV7Wa5fQ78emdbxJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .arrowheadPath{fill:#333333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kV7Wa5fQ78emdbxJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kV7Wa5fQ78emdbxJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kV7Wa5fQ78emdbxJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kV7Wa5fQ78emdbxJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kV7Wa5fQ78emdbxJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kV7Wa5fQ78emdbxJ .cluster text{fill:#333;}#mermaid-svg-kV7Wa5fQ78emdbxJ .cluster span{color:#333;}#mermaid-svg-kV7Wa5fQ78emdbxJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kV7Wa5fQ78emdbxJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kV7Wa5fQ78emdbxJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-kV7Wa5fQ78emdbxJ .icon-shape,#mermaid-svg-kV7Wa5fQ78emdbxJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kV7Wa5fQ78emdbxJ .icon-shape p,#mermaid-svg-kV7Wa5fQ78emdbxJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kV7Wa5fQ78emdbxJ .icon-shape .label rect,#mermaid-svg-kV7Wa5fQ78emdbxJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kV7Wa5fQ78emdbxJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kV7Wa5fQ78emdbxJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kV7Wa5fQ78emdbxJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 首次读取
② 内容存入
③ 返回数据
④ 再次读取:直接命中缓存
PyTorch
from_pretrained()
磁盘
safetensors
页缓存
(内存中的文件副本)
这对性能很友好:第二次加载同一个模型会快很多。代价是只要内存还有空,内核就会尽量多留缓存------"空闲内存就是浪费的内存"。于是跑过几轮任务后,缓存就会把内存填满。
cgroup 如何记账
cgroup 把容器内所有进程的内存分成几类:
- 匿名内存(anon):进程自己分配的,没有对应文件,只能靠进程释放或换出。这才是"真正的占用"。
- 文件缓存(file) :有磁盘文件对应,丢掉了随时能重新读回来,所以可回收 。
- 内核用两条 LRU 链表管理它:
active_file(最近访问过)和inactive_file(较久未访问)。回收时优先清理 inactive,必要时把 active 降级后再回收。
- 内核用两条 LRU 链表管理它:
- 共享内存(shmem) :
/dev/shm和 tmpfs 中的文件,不对应磁盘,不会自动回收。
memory.current(或 v1 的 usage_in_bytes)是这几类之和。所以"用量 100%"并不等于"内存不够"。
小提示 :网上常用
usage - inactive_file估算真实占用,但在本例中 87GB 缓存都处于 active 状态,这样算会得出"真实占用 89GB"的错误结论。更可靠的估算是anon + shmem + 被 mmap 的文件页。
清理缓存的几种方法
| 方法 | 原理 | 容器内可用? | 说明 |
|---|---|---|---|
echo 1 > /proc/sys/vm/drop_caches |
一次性丢弃全系统的干净缓存 | ❌ | 需要宿主机特权,容器里通常是只读的 |
| 重启实例 | 内存清空 | ✅ | 最彻底,但要中断工作;之后缓存仍会再涨 |
posix_fadvise(..., POSIX_FADV_DONTNEED) |
逐个文件告诉内核"这些缓存我不需要了" | ✅ | 无需特权,只要能只读打开文件即可 |
vmtouch -e |
同上(内部也是 fadvise) | ✅ | 需另行安装 |
posix_fadvise 是 POSIX 标准的文件访问建议接口,本意是让程序告诉内核自己的访问模式。传入 POSIX_FADV_DONTNEED 表示"这段内容近期不会再用",Linux 的实现会立即丢弃该文件对应的干净缓存页。
它只作用于缓存,不修改文件本身,因此对普通用户开放。唯一的代价是需要一个文件一个文件地调用。
为什么要先 sync
缓存页分两种:
- 干净页:内容与磁盘一致,丢了可以重新读,可以直接丢弃。
- 脏页 :刚写入、还没刷回磁盘,丢了就会丢数据,内核不会丢弃。
sync 把所有脏页写回磁盘,让它们变成干净页,随后就能被一并清掉。所以这套流程不会造成任何数据丢失。
#mermaid-svg-mTdelKscktdbAOL3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mTdelKscktdbAOL3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mTdelKscktdbAOL3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mTdelKscktdbAOL3 .error-icon{fill:#552222;}#mermaid-svg-mTdelKscktdbAOL3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mTdelKscktdbAOL3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mTdelKscktdbAOL3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mTdelKscktdbAOL3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mTdelKscktdbAOL3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mTdelKscktdbAOL3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mTdelKscktdbAOL3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mTdelKscktdbAOL3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mTdelKscktdbAOL3 .marker.cross{stroke:#333333;}#mermaid-svg-mTdelKscktdbAOL3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mTdelKscktdbAOL3 p{margin:0;}#mermaid-svg-mTdelKscktdbAOL3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mTdelKscktdbAOL3 .cluster-label text{fill:#333;}#mermaid-svg-mTdelKscktdbAOL3 .cluster-label span{color:#333;}#mermaid-svg-mTdelKscktdbAOL3 .cluster-label span p{background-color:transparent;}#mermaid-svg-mTdelKscktdbAOL3 .label text,#mermaid-svg-mTdelKscktdbAOL3 span{fill:#333;color:#333;}#mermaid-svg-mTdelKscktdbAOL3 .node rect,#mermaid-svg-mTdelKscktdbAOL3 .node circle,#mermaid-svg-mTdelKscktdbAOL3 .node ellipse,#mermaid-svg-mTdelKscktdbAOL3 .node polygon,#mermaid-svg-mTdelKscktdbAOL3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mTdelKscktdbAOL3 .rough-node .label text,#mermaid-svg-mTdelKscktdbAOL3 .node .label text,#mermaid-svg-mTdelKscktdbAOL3 .image-shape .label,#mermaid-svg-mTdelKscktdbAOL3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-mTdelKscktdbAOL3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mTdelKscktdbAOL3 .rough-node .label,#mermaid-svg-mTdelKscktdbAOL3 .node .label,#mermaid-svg-mTdelKscktdbAOL3 .image-shape .label,#mermaid-svg-mTdelKscktdbAOL3 .icon-shape .label{text-align:center;}#mermaid-svg-mTdelKscktdbAOL3 .node.clickable{cursor:pointer;}#mermaid-svg-mTdelKscktdbAOL3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mTdelKscktdbAOL3 .arrowheadPath{fill:#333333;}#mermaid-svg-mTdelKscktdbAOL3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mTdelKscktdbAOL3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mTdelKscktdbAOL3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mTdelKscktdbAOL3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mTdelKscktdbAOL3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mTdelKscktdbAOL3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mTdelKscktdbAOL3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mTdelKscktdbAOL3 .cluster text{fill:#333;}#mermaid-svg-mTdelKscktdbAOL3 .cluster span{color:#333;}#mermaid-svg-mTdelKscktdbAOL3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mTdelKscktdbAOL3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mTdelKscktdbAOL3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-mTdelKscktdbAOL3 .icon-shape,#mermaid-svg-mTdelKscktdbAOL3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mTdelKscktdbAOL3 .icon-shape p,#mermaid-svg-mTdelKscktdbAOL3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mTdelKscktdbAOL3 .icon-shape .label rect,#mermaid-svg-mTdelKscktdbAOL3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mTdelKscktdbAOL3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mTdelKscktdbAOL3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mTdelKscktdbAOL3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} sync
脏页写回磁盘 → 变为干净页
遍历文件系统
(跳过 /proc /sys /dev)
只读打开普通文件
O_RDONLY | O_NONBLOCK
posix_fadvise
POSIX_FADV_DONTNEED
内核丢弃该文件的干净缓存页
cgroup 的 file 用量下降
平台监控显示恢复正常
实现
诊断脚本 memcheck.sh(只读)
一次性输出:cgroup 内存构成、OOM 历史、内存占用最高的进程、正在 mmap 模型文件的进程、/dev/shm 使用情况,以及缓存占用最多的文件。脚本只读取信息,不修改、不删除任何内容。
bash
#!/usr/bin/env bash
# memcheck.sh --- 诊断容器内存占用(只读,不修改任何东西)
# 用法: bash memcheck.sh
set -u
SCAN_DIRS=(/root/autodl-tmp /root/.cache /root/autodl-fs)
EXIST_DIRS=(); for d in "${SCAN_DIRS[@]}"; do [[ -d $d ]] && EXIST_DIRS+=("$d"); done
h(){ [[ "$1" =~ ^[0-9]+$ ]] && numfmt --to=iec --suffix=B "$1" || echo "$1"; }
line(){ printf '\n==================== %s ====================\n' "$1"; }
# ---------- 1. cgroup 内存构成 ----------
line "1. cgroup 内存构成"
if [[ -f /sys/fs/cgroup/memory.current ]]; then # cgroup v2
D=/sys/fs/cgroup; STAT=$D/memory.stat; EVENTS=$D/memory.events
LIMIT=$(cat $D/memory.max); USAGE=$(cat $D/memory.current)
st(){ awk -v k="$1" '$1==k{print $2}' "$STAT"; }
ANON=$(st anon); FILE=$(st file); SHMEM=$(st shmem); MAPPED=$(st file_mapped)
ACT=$(st active_file); INACT=$(st inactive_file); DIRTY=$(st file_dirty)
elif [[ -f /sys/fs/cgroup/memory/memory.usage_in_bytes ]]; then # cgroup v1
D=/sys/fs/cgroup/memory; STAT=$D/memory.stat; EVENTS=$D/memory.oom_control
LIMIT=$(cat $D/memory.limit_in_bytes); USAGE=$(cat $D/memory.usage_in_bytes)
st(){ awk -v k="$1" '$1==k{print $2}' "$STAT"; }
ANON=$(st total_rss); FILE=$(st total_cache); SHMEM=$(st total_shmem); MAPPED=$(st total_mapped_file)
ACT=$(st total_active_file); INACT=$(st total_inactive_file); DIRTY=$(st total_dirty)
else
echo "未找到 cgroup 内存统计"; exit 1
fi
for v in ANON FILE SHMEM MAPPED ACT INACT DIRTY; do printf -v "$v" '%s' "${!v:-0}"; done
REAL=$(( ANON + SHMEM + MAPPED ))
if [[ "$LIMIT" =~ ^[0-9]+$ ]] && (( LIMIT < 1<<50 )); then
pct(){ awk -v a="$1" -v b="$LIMIT" 'BEGIN{printf "%.1f%%", a*100/b}'; }
else
pct(){ echo "-"; }; LIMIT="无限制"
fi
printf '%-32s %10s\n' "容器内存上限" "$(h "$LIMIT")"
printf '%-32s %10s (%s)\n' "已用 (含缓存,监控看到的)" "$(h "$USAGE")" "$(pct "$USAGE")"
printf '%-32s %10s (%s)\n' "真实占用 (anon+shmem+mmap)" "$(h "$REAL")" "$(pct "$REAL")"
printf '%-32s %10s\n' " 进程匿名内存 anon" "$(h "$ANON")"
printf '%-32s %10s\n' " 页缓存 file (可回收)" "$(h "$FILE")"
printf '%-32s %10s\n' " active / inactive" "$(h "$ACT") / $(h "$INACT")"
printf '%-32s %10s\n' " 脏页 (待写回)" "$(h "$DIRTY")"
printf '%-32s %10s\n' " 被进程 mmap (释放不掉)" "$(h "$MAPPED")"
printf '%-32s %10s\n' " 共享内存 shmem (不自动回收)" "$(h "$SHMEM")"
# ---------- 2. OOM 历史 ----------
line "2. 内核 OOM 历史"
cat "$EVENTS" 2>/dev/null
OOMK=$(awk '$1=="oom_kill"{print $2}' "$EVENTS" 2>/dev/null); OOMK=${OOMK:-0}
# ---------- 3. 进程 ----------
line "3. 占内存最多的进程 (RSS)"
ps -eo pid,rss,etime,args --sort=-rss | head -12 | \
awk 'NR==1{printf "%-8s %10s %12s %s\n","PID","RSS","运行时长","命令";next}
{cmd=""; for(i=4;i<=NF;i++) cmd=cmd" "$i;
printf "%-8s %9.1fM %12s %s\n",$1,$2/1024,$3,substr(cmd,1,100)}'
line "4. 正在 mmap 模型文件的进程"
found=0
for m in /proc/[0-9]*/maps; do
pid=${m#/proc/}; pid=${pid%/maps}
files=$(grep -oE '/[^ ]*(blobs/[0-9a-f]{20,}|\.safetensors|\.bin|\.pt|\.pth|\.gguf|\.ckpt)( \(deleted\))?$' "$m" 2>/dev/null | sort -u)
if [[ -n "$files" ]]; then
found=1
echo "PID $pid: $(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null | cut -c1-100)"
echo "$files" | head -5 | sed 's/^/ /'
fi
done
(( found )) || echo "无"
line "5. 共享内存 /dev/shm"
df -h /dev/shm | tail -1
du -sh /dev/shm/* 2>/dev/null | sort -h | tail -10
line "6. 页缓存占用最多的文件 (>100M)"
if command -v fincore >/dev/null && (( ${#EXIST_DIRS[@]} )); then
find "${EXIST_DIRS[@]}" -xdev -type f -size +100M -print0 2>/dev/null | \
xargs -0 -r fincore -b -n -o RES,FILE 2>/dev/null | awk '$1>0' | sort -nr | head -15 | \
numfmt --field=1 --to=iec --suffix=B
else
echo "跳过 (无 fincore 或扫描目录不存在)"
fi
# ---------- 结论 ----------
line "结论"
GB=$((1<<30))
(( OOMK > 0 )) && echo "* 内核发生过 ${OOMK} 次 OOM kill:程序真实内存超限,检查 anon/shmem 与代码"
(( ANON > 4*GB )) && echo "* 进程匿名内存较大 ($(h $ANON)):见第 3 步,关闭不用的进程 / Jupyter kernel"
(( SHMEM > 1*GB )) && echo "* 共享内存较大 ($(h $SHMEM)):确认无程序使用后清理第 5 步列出的残留"
(( MAPPED > 1*GB )) && echo "* 有 $(h $MAPPED) 文件被 mmap:关闭第 4 步列出的进程才能释放"
(( FILE > 1*GB )) && echo "* 页缓存 $(h $FILE):内核可回收,但平台监控可能把它算作占用 → 运行 dropcache.py"
exit 0
清理脚本 dropcache.py(核心)
python
#!/usr/bin/env python3
"""
dropcache.py --- 在无特权容器中逐文件丢弃页缓存。
用法:
python3 dropcache.py # 扫描整个文件系统
python3 dropcache.py /root/data # 只处理指定目录(更快)
只丢弃缓存,不修改、不删除任何文件。
"""
import os
import sys
import time
SKIP = {"/proc", "/sys", "/dev"} # 虚拟文件系统,没有页缓存,打开还可能阻塞
GB = 2 ** 30
def cgroup_mem():
"""返回 (页缓存字节数, 进程匿名内存字节数),兼容 cgroup v1 / v2。"""
for path in ("/sys/fs/cgroup/memory.stat", "/sys/fs/cgroup/memory/memory.stat"):
try:
with open(path) as f:
st = dict(line.split() for line in f)
except OSError:
continue
file_ = int(st.get("file", st.get("total_cache", 0)))
anon = int(st.get("anon", st.get("total_rss", 0)))
return file_, anon
return None, None
def drop_cache(roots):
done = 0
for root in roots:
for dirpath, dirnames, filenames in os.walk(root): # 默认不跟随目录符号链接
dirnames[:] = [d for d in dirnames
if os.path.join(dirpath, d) not in SKIP]
for name in filenames:
path = os.path.join(dirpath, name)
try:
# 只处理普通文件:跳过符号链接(目标会被单独处理)、FIFO、socket 等
if os.path.islink(path) or not os.path.isfile(path):
continue
# 只读 + 非阻塞打开:绝不修改文件,也不会卡在特殊文件上
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
try:
# offset=0, len=0 表示整个文件;DONTNEED → 内核丢弃其干净缓存页
os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_DONTNEED)
done += 1
finally:
os.close(fd)
except OSError:
pass # 无权限 / 已被删除等,跳过
return done
def main():
roots = sys.argv[1:] or ["/"]
before, _ = cgroup_mem()
os.sync() # 先把脏页写回磁盘,让它们可被丢弃
t0 = time.time()
n = drop_cache(roots)
after, anon = cgroup_mem()
msg = f"dropcache: 处理 {n} 个文件, 用时 {time.time() - t0:.1f}s"
if before is not None:
msg += (f", 页缓存 {before / GB:.1f}G → {after / GB:.1f}G"
f", 进程内存 {anon / GB:.1f}G")
print(msg)
if __name__ == "__main__":
main()
逐段说明:
os.sync():把脏页写回磁盘。内核只会丢弃干净页,不先 sync 的话,刚写入的文件缓存清不掉。- 遍历
/,跳过/proc、/sys、/dev:缓存可能来自任何读写过的文件,全盘扫描才不会遗漏。三个虚拟文件系统里没有对应的磁盘缓存,跳过它们。 - 只处理普通文件:跳过符号链接(目标文件本身会被遍历到),也避开 FIFO 等特殊文件。
O_RDONLY | O_NONBLOCK:只读打开,保证不改动文件;非阻塞模式作为防止卡死的兜底。posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED):核心调用。长度传 0 表示"从偏移 0 到文件末尾"。except OSError: pass:遇到无权限、遍历途中文件被删除等情况直接跳过,不影响整体执行。- 前后对比
memory.stat:直接打印清理效果,便于确认。
运行效果示例:
text
$ python3 dropcache.py
dropcache: 处理 412873 个文件, 用时 38.2s, 页缓存 88.5G → 3.1G, 进程内存 0.4G
如果挂载了较慢的网络盘(如
/root/autodl-fs),全盘扫描会变慢。可以只传入缓存主要来源的目录:python3 dropcache.py /root/autodl-tmp /root/miniconda3。
集成到批量任务
缓存会随着每次加载模型、读取数据重新涨回来,所以需要在每个任务启动前清理一次。用一个小函数包装每条命令:
bash
#!/usr/bin/env bash
set -x
cd /root/autodl-tmp/
run() { # 每个任务前先清缓存
sync
python3 /root/autodl-tmp/dropcache.py /root/autodl-tmp
"$@"
}
for lam in 0.01 0.02 0.05; do
run python main.py
done
第一次建议用全盘扫描(不带参数)把历史缓存清干净。之后在循环里只清理工作目录即可,速度快很多。
排查流程速查
#mermaid-svg-o9Rv8sqiWdErDr5i{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o9Rv8sqiWdErDr5i .error-icon{fill:#552222;}#mermaid-svg-o9Rv8sqiWdErDr5i .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o9Rv8sqiWdErDr5i .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o9Rv8sqiWdErDr5i .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o9Rv8sqiWdErDr5i .marker.cross{stroke:#333333;}#mermaid-svg-o9Rv8sqiWdErDr5i svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o9Rv8sqiWdErDr5i p{margin:0;}#mermaid-svg-o9Rv8sqiWdErDr5i .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o9Rv8sqiWdErDr5i .cluster-label text{fill:#333;}#mermaid-svg-o9Rv8sqiWdErDr5i .cluster-label span{color:#333;}#mermaid-svg-o9Rv8sqiWdErDr5i .cluster-label span p{background-color:transparent;}#mermaid-svg-o9Rv8sqiWdErDr5i .label text,#mermaid-svg-o9Rv8sqiWdErDr5i span{fill:#333;color:#333;}#mermaid-svg-o9Rv8sqiWdErDr5i .node rect,#mermaid-svg-o9Rv8sqiWdErDr5i .node circle,#mermaid-svg-o9Rv8sqiWdErDr5i .node ellipse,#mermaid-svg-o9Rv8sqiWdErDr5i .node polygon,#mermaid-svg-o9Rv8sqiWdErDr5i .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o9Rv8sqiWdErDr5i .rough-node .label text,#mermaid-svg-o9Rv8sqiWdErDr5i .node .label text,#mermaid-svg-o9Rv8sqiWdErDr5i .image-shape .label,#mermaid-svg-o9Rv8sqiWdErDr5i .icon-shape .label{text-anchor:middle;}#mermaid-svg-o9Rv8sqiWdErDr5i .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o9Rv8sqiWdErDr5i .rough-node .label,#mermaid-svg-o9Rv8sqiWdErDr5i .node .label,#mermaid-svg-o9Rv8sqiWdErDr5i .image-shape .label,#mermaid-svg-o9Rv8sqiWdErDr5i .icon-shape .label{text-align:center;}#mermaid-svg-o9Rv8sqiWdErDr5i .node.clickable{cursor:pointer;}#mermaid-svg-o9Rv8sqiWdErDr5i .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o9Rv8sqiWdErDr5i .arrowheadPath{fill:#333333;}#mermaid-svg-o9Rv8sqiWdErDr5i .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o9Rv8sqiWdErDr5i .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o9Rv8sqiWdErDr5i .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o9Rv8sqiWdErDr5i .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o9Rv8sqiWdErDr5i .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o9Rv8sqiWdErDr5i .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o9Rv8sqiWdErDr5i .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o9Rv8sqiWdErDr5i .cluster text{fill:#333;}#mermaid-svg-o9Rv8sqiWdErDr5i .cluster span{color:#333;}#mermaid-svg-o9Rv8sqiWdErDr5i div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-o9Rv8sqiWdErDr5i .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o9Rv8sqiWdErDr5i rect.text{fill:none;stroke-width:0;}#mermaid-svg-o9Rv8sqiWdErDr5i .icon-shape,#mermaid-svg-o9Rv8sqiWdErDr5i .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o9Rv8sqiWdErDr5i .icon-shape p,#mermaid-svg-o9Rv8sqiWdErDr5i .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o9Rv8sqiWdErDr5i .icon-shape .label rect,#mermaid-svg-o9Rv8sqiWdErDr5i .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o9Rv8sqiWdErDr5i .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o9Rv8sqiWdErDr5i .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o9Rv8sqiWdErDr5i :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
否
进程被 Killed / 内存显示 100%
bash memcheck.sh
memory.events 中
oom_kill 增加了吗?
程序自身内存超限
看 anon / shmem,优化代码:
bf16 加载、减少 num_workers、
避免整体读入数据集
页缓存 file
是否接近上限?
平台按含缓存的占用杀进程
→ 每个任务前执行
其他外部原因
保留 memory.events 证据
联系平台客服
缓存降下来了吗?
控制台关机再开机
(数据盘内容保留)
注意事项与局限
-
安全性 :
dropcache.py只丢弃干净缓存,配合sync不会造成数据丢失,也不改动任何文件。 -
性能代价:清理后,下一次加载模型需要重新从磁盘读取,首次加载会慢几秒到几十秒。
-
mmap 中的文件清不掉:正在被进程映射使用的文件页无法丢弃。请在任务之间执行清理,而不是在任务运行过程中。
-
共享内存不在此列 :
/dev/shm中的残留属于 shmem,fadvise对它无效,需要确认无程序使用后手动删除。 -
治标不治本 :缓存还会再长回来。根本问题在于平台的计量方式,如果频繁遇到,可以带着
oom_kill 0的证据向平台反馈。 -
如果
oom_kill确实增加了:那就是真正的内存不足,清缓存没有用。需要排查代码,例如:- 以 fp32 加载到 CPU:8B 模型约 32GB,加载过程中可能出现两份副本。
- DataLoader 的
num_workers过多,每个 worker 都复制一份数据。 - 把每一步的张量累积进 list 却没有
.detach().cpu()。
推荐的加载方式:
pythonmodel = AutoModelForCausalLM.from_pretrained( path, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True, device_map="cuda")
总结
- 容器内存 100% ≠ 内存不够。先看 cgroup 的
memory.stat,区分 anon(真实占用) 和 file(页缓存)。 - 用
memory.events中的oom_kill判断进程是否被内核 OOM 杀掉。为 0 却仍被 Killed,说明是外部机制按"含缓存的占用"在杀进程。 - 容器内没有权限使用
drop_caches,改用posix_fadvise(POSIX_FADV_DONTNEED)逐文件丢弃缓存,无需特权且安全。 - 把清理步骤放到每个任务启动前,问题即可稳定解决。