容器内存显示 100%、进程莫名被 Killed 解决方案

现象

环境: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 降级后再回收。
  • 共享内存(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()

逐段说明:

  1. os.sync():把脏页写回磁盘。内核只会丢弃干净页,不先 sync 的话,刚写入的文件缓存清不掉。
  2. 遍历 /,跳过 /proc、/sys、/dev:缓存可能来自任何读写过的文件,全盘扫描才不会遗漏。三个虚拟文件系统里没有对应的磁盘缓存,跳过它们。
  3. 只处理普通文件:跳过符号链接(目标文件本身会被遍历到),也避开 FIFO 等特殊文件。
  4. O_RDONLY | O_NONBLOCK:只读打开,保证不改动文件;非阻塞模式作为防止卡死的兜底。
  5. posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED):核心调用。长度传 0 表示"从偏移 0 到文件末尾"。
  6. except OSError: pass:遇到无权限、遍历途中文件被删除等情况直接跳过,不影响整体执行。
  7. 前后对比 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

是否接近上限?
平台按含缓存的占用杀进程

→ dropcache.py

→ 每个任务前执行
其他外部原因

保留 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()。

    推荐的加载方式:

    python 复制代码
    model = AutoModelForCausalLM.from_pretrained(
        path, torch_dtype=torch.bfloat16,
        low_cpu_mem_usage=True, device_map="cuda")

总结

  1. 容器内存 100% ≠ 内存不够。先看 cgroup 的 memory.stat,区分 anon(真实占用) 和 file(页缓存)。
  2. 用 memory.events 中的 oom_kill 判断进程是否被内核 OOM 杀掉。为 0 却仍被 Killed,说明是外部机制按"含缓存的占用"在杀进程。
  3. 容器内没有权限使用 drop_caches,改用 posix_fadvise(POSIX_FADV_DONTNEED) 逐文件丢弃缓存,无需特权且安全。
  4. 把清理步骤放到每个任务启动前,问题即可稳定解决。
相关推荐
硅谷秋水1 小时前
DynaHarness 方法总结
人工智能·机器学习·语言模型·机器人
禁默1 小时前
玩客云部署EasyVoice实战:HiNas、Docker、ARMv7、9001与固定地址访问(1)
docker·容器·语音识别·cpolar
fengkai45451 小时前
2、Kubernetes 介绍+安装+卸载
运维·容器·kubernetes
羑悻的小杀马特1 小时前
超越Bridge:全面掌握Docker网络隔离与通信的底层逻辑!
运维·网络·docker·容器
lucas_AI2 小时前
Muon 到底什么时候赢?等 token 全胜、等时间翻车:换优化器前先算的三本账
机器学习
小马同学-2 小时前
K8s 节点管理:查看、维护与集群删除操作
容器·kubernetes
DQQzero2 小时前
84.66% 的“实锤“是怎么来的:拆解模型指纹工具的闭集陷阱
人工智能·机器学习·ai·模型指纹
在所不辞兄1 天前
为什么神经网络对对抗扰动和分布偏移往往比较脆弱,而人类感知相对更鲁棒?
人工智能·深度学习·神经网络·机器学习
szxinmai主板定制专家1 天前
储能 EMS、微网控制器选型:RK3588、RK3576、RK3568+FPGA 怎么选?多网、多 CAN、EMC 设计权衡
人工智能·深度学习·机器学习·fpga开发·工业网关·rk3576·电力网关