Skip to main content

earlyoom regex silent failure — DGX Spark vLLM freeze ที่ป้องกันไม่อยู่

· 8 min read

บันทึก 17 กันยายน 2569 — เคส OOM debug ที่ทำให้รู้ว่า regex เงียบๆ fail ได้

วันนี้ผม debug เคส vLLM hard-freeze บน DGX Spark (GB10, 119 GiB unified memory) — เครื่องค้าง ต้องถอดปลั๊ก 3 ครั้ง ทั้งๆ ที่ติด earlyoom ไว้แล้ว pattern ก็ดูถูก ผ่านไปหลายชั่วโมงจนเจอว่า ทั้ง --prefer และ --avoid ไม่เคย match process จริงเลย regex silent failure ที่ไม่มี error, ไม่มี warning, ไม่มี log อะไรทั้งสิ้น

TL;DR​

  • DGX Spark vLLM hard-freeze จาก UMA OOM (unified memory exhaustion — CPU/GPU แชร์ memory pool เดียวกัน)
  • earlyoom enabled แล้ว pattern ดูถูก — แต่ ไม่เคย match vllm process
  • Root cause: PCRE2 . ไม่ match NUL (\x00) separator ระหว่าง args ใน /proc/PID/cmdline
  • --prefer (^|/)(python3.*vllm|...) → ต้องการ python3 และ vllm ใน arg เดียวกัน แต่จริงๆ อยู่คนละ arg (คั่นด้วย NUL)
  • --avoid (^|/)(containerd-shim).*$ → bug เดียวกัน process จริงชื่อ containerd-shim-runc-v2 — อันตรายกว่า เพราะทำให้ earlyoom ฆ่า container infra ที่ควรปกป้อง
  • Fix: drop $ anchor + match binary name ตรงๆ — pattern ใหม่ verified บน host + docker

Host freeze​

หลังจาก optimize Qwen3.8-Flash-Next บน DGX Spark ได้ TTFT ลดลง 72% แล้ว ผมรัน Qwen3.8-Flash-Next (180B MoE, 6B active) ที่โหลดเต็มที่ — 256K context, fp8 KV cache บน unified memory 119 GiB — workload ที่ใช้ memory จนเกือบเต็มเป็นเรื่องปกติ

อาการที่เจอ (เกิด 3 ครั้งใน 1 สัปดาห์):

  • vLLM OOM → host freeze ทันที (kernel ไม่ตอบสนองต่อ interrupt)
  • SSH ตาย ทุก connection drop กลาง session
  • Power button ไม่ตอบสนอง (kernel ไม่ handle interrupt ได้)
  • ต้อง ถอดปลั๊ก USB-C power อย่างเดียว
  • NVMe SSD + ext4 journaling → boot กลับมาปกติ ด้วย journal replay อัตโนมัติ

NVIDIA forum มี thread อธิบาย pattern เดียวกัน — Spark hangs requires hard reset — root cause คือ unified memory ของ GB10 ทำให้ OOM ของ GPU process ดูด memory จน kernel ถูก starve ก่อน OOM killer จะทำงานทัน

ปัญหาคือ ผม ติด earlyoom ไว้แล้ว pattern ดูถูก (--prefer (^|/)(python3.*vllm|...)) ทำไมมันไม่ kill vllm ก่อน host ค้าง?

ตรวจ process tree​

อย่างแรก — confirm ว่า vLLM อยู่ใน docker ไม่ใช่ direct process บน host (สำคัญมากสำหรับ earlyoom pattern):

$ docker ps -a
CONTAINER ID IMAGE COMMAND STATUS
f484cab029e3 kongvut/qwen38-flash-next-vllm:v3 "vllm serve /models…" Up 2 hours
$ ps -ef | grep -E "vllm|docker|containerd-shim"
earlyoom 1367 ... /usr/bin/earlyoom -m 10 -s 5 -r 60 --prefer ...
root 1807 ... /usr/bin/dockerd -H fd:// ...
root 27868 ... /usr/bin/containerd-shim-runc-v2 -namespace moby ...
root 27891 ... /usr/bin/python3 /usr/local/bin/vllm serve ...
root 28000 ... /usr/bin/docker-proxy -proto tcp -host-port 8000 ...
$ cat /proc/27891/cgroup
0::/system.slice/docker-f484cab029e36daaa3169804b601c2f5590686c489609519bff4e4db29b4784d.scope
host
+-- dockerd (1807)
| +-- containerd-shim-runc-v2 (27868)
| | +-- python3 vllm serve (27891) <-- target
| +-- docker-proxy (28000, 28007)
+-- earlyoom (81362) <-- matcher (host-level)

หมายเหตุตรงๆ — ผมดูแค่ cmdline ตอนแรกแล้วสรุปเร็วไปว่า "vLLM รันบน host" เพราะเห็น /usr/bin/python3 /usr/local/bin/vllm ใน cmdline ข้ามไปที่ cgroup ไม่ได้เช็ค ลอง docker ps -a อีกรอบถึงเจอว่ามันอยู่ใน container จริง บทเรียน — process tree + cgroup ต้องตรวจทั้งสองอย่าง ไม่ใช่แค่ cmdline

Surprise: earlyoom on แต่ไม่ทำงาน​

config ปัจจุบันใน /etc/default/earlyoom:

EARLYOOM_ARGS="-m 10 -s 5 -r 60 \
--prefer '(^|/)(python3.*vllm|python3.*sglang|VLLM::EngineCore.*)$' \
--avoid '(^|/)(init|sshd|systemd|containerd|containerd-shim|dockerd|runc)$'"

ดูดี — มี --prefer vllm ครอบคลุม vllm process — น่าจะทำงาน

แต่พอลองทดสอบ pattern จริงๆ กับ cmdline ของ vllm process:

$ cat /proc/27891/cmdline | tr '\0' '\n'
python3
/usr/local/bin/vllm
serve
/models/Qwen3.8-Flash-Next
--port 8000
--served-model-name qwen3.8-flash-next
--moe-backend marlin
...
--async-scheduling

$ cat /proc/27891/cmdline | tr '\0' '\n' | grep -P "(^|/)(python3.*vllm|python3.*sglang|VLLM::EngineCore.*)$"
# (no output)

Pattern ไม่ match vllm เลย ทั้งที่ pattern มีคำว่า vllm อยู่

Root cause: . ไม่ match NUL separator​

earlyoom match regex กับ /proc/PID/cmdline ซึ่ง args คั่นด้วย NUL (\x00) ไม่ใช่ space ใน PCRE2 (default) . ไม่ match control characters — รวมถึง NUL

cmdline bytes: p y t h o n 3 \0 / u s r / l o c ...
pattern tries: p y t h o n 3 . * v l l m $
(NUL cannot match .) (literal vllm + $)

ดังนั้น pattern python3.*vllm$:

  • ต้องการ python3 แล้วตามด้วย chars (ไม่มี NUL) แล้ว vllm แล้วจบ
  • แต่ cmdline = python3\x00/usr/local/bin/vllm\x00serve\x00...
  • python3 อยู่ arg 1, vllm อยู่ arg 2 — มี NUL คั่น → pattern ไม่เคย match เลย

ทุก regex ที่ใช้ .* ข้าม args ใน /proc/PID/cmdline จะ silent fail ทันที ไม่มี error, ไม่มี warning ผ่าน PCRE2 compile ปกติ, run ไม่มี crash, return 0 matches เงียบๆ

Two bugs ไม่ใช่แค่หนึ่ง​

ตอน verify เพิ่มเติม ผมเจอว่า --avoid ก็มี bug เดียวกัน — และ อันตรายกว่า เพราะมันทำให้ earlyoom ฆ่า process ที่ไม่ควรฆ่า

$ cat /proc/27868/cmdline | tr '\0' '\n' | head -1
/usr/bin/containerd-shim-runc-v2

Pattern --avoid (^|/)containerd-shim$ ก็ไม่ match — process จริงชื่อ containerd-shim-runc-v2 ลงท้ายด้วย -runc-v2 ไม่ใช่ containerd-shim ถ้าเกิด earlyoom trigger OOM kill (ไม่ว่าจะโดย vllm หรือ process อื่น) — containerd-shim-runc-v2 จะโดน kill → container ทั้ง host พัง

สรุปสถานการณ์เดิม:

PatternIntentActual matchEffective
--prefer ... python3.*vllm ...$kill vllm ก่อนno match0%
--avoid ... containerd-shim ...$ปกป้อง shimno match0%
--avoid ... dockerd ...$ปกป้อง dockerdmatch (/usr/bin/dockerd)100% (single-arg process)
--avoid ... runc ...$ปกป้อง runcno match (ถ้ามี args)0%

bug ใน --avoid อันตรายกว่า bug ใน --prefer เพราะ --prefer แค่ไม่ kill vllm (host ค้างเหมือนเดิม) แต่ --avoid ที่ silent fail ทำให้ earlyoom kill process อะไรก็ได้ ที่ไม่ได้ระบุชื่อไว้ชัดเจน

Fix​

เปลี่ยน pattern ให้ match ตรงๆ กับ binary name ใน cmdline ไม่ใช้ . ข้าม args:

# Backup (ตาม convention .bak.YYYYMMDD_HHMMSS)
sudo cp /etc/default/earlyoom /etc/default/earlyoom.bak.$(date +%Y%m%d_%H%M%S)

# Write new config
sudo tee /etc/default/earlyoom > /dev/null <<'EOF'
EARLYOOM_ARGS="-m 10 -s 5 -r 60 \
--prefer '(^|/)vllm|VLLM::EngineCore' \
--avoid '(^|/)(init|sshd|systemd|dockerd|docker-proxy|containerd|containerd-shim|runc)'"
EOF

# Restart (systemd re-source EnvironmentFile ใหม่)
sudo systemctl restart earlyoom

สิ่งที่เปลี่ยน:

  • ลบ $ ทุกที่ (ไม่จำเป็น + silent fail เพราะ . ไม่ cross NUL)
  • --prefer: จับ /usr/local/bin/vllm ตรงๆ ผ่าน (^|/)vllm + match VLLM::EngineCore สำหรับ engine subprocess
  • --avoid: เพิ่ม docker-proxy (PID 28000/28007 ที่ผมเห็นใน ps) + match ทุก docker infra ที่เกี่ยวข้อง

Verify​

# PREFER: vllm ต้อง match
$ cat /proc/27891/cmdline | tr '\0' '\n' | grep -P "(^|/)vllm"
/usr/local/bin/vllm # MATCH

# AVOID: docker infra ต้อง match (อันที่มี args ใน cmdline)
$ for pid in 1807 27868 28000; do \
cat /proc/$pid/cmdline | tr '\0' '\n' | head -1 | \
grep -P "(^|/)(dockerd|docker-proxy|containerd|containerd-shim|runc)" \
&& echo "PID $pid AVOID OK" \
|| echo "PID $pid NOT avoided"; \
done
/usr/bin/dockerd # PID 1807 AVOID OK
/usr/bin/containerd-shim-runc-v2 # PID 27868 AVOID OK
/usr/bin/docker-proxy # PID 28000 AVOID OK

# vllm ต้องไม่โดน avoid (false positive check)
$ cat /proc/27891/cmdline | tr '\0' '\n' | grep -P "(^|/)(dockerd|docker-proxy|containerd|containerd-shim|runc)"
# (no output) # NOT avoided (correct)

earlyoom log ยืนยันเองหลัง restart:

Sep 17 06:28:20 earlyoom[81362]: earlyoom v1.7
Sep 17 06:28:20 earlyoom[81362]: Preferring to kill process names that match regex '(^|/)vllm|VLLM::EngineCore'

เปรียบเทียบ before/after​

Before (silent fail)After (verified)
PREFER match vllmnoyes (PID 27891)
AVOID match containerd-shim-runc-v2noyes (PID 27868)
AVOID match docker-proxyno (ไม่มีใน pattern เดิม)yes (PID 28000)
AVOID match dockerdyes (single-arg)yes
Effective --prefer0%100%
Effective --avoid (กับ args)0%100%

สรุป​

สิ่งที่ผมเจอและแก้:

  • earlyoom enabled แต่ silent fail — pattern ดูถูกแต่ regex ไม่ match เลย
  • Root cause: PCRE2 . ไม่ cross NUL separator ใน /proc/PID/cmdline — ทุก pattern ที่ใช้ .* ข้าม args จะ fail ทันที
  • 2 bugs: ทั้ง --prefer (ฆ่า vllm ไม่ได้) และ --avoid (ปล่อยให้ containerd-shim โดนฆ่า) silent fail พร้อมกัน — --avoid ที่ fail อันตรายกว่าเพราะ earlyoom ฆ่า process อะไรก็ได้
  • Fix: drop $ + match binary name ตรงๆ ผ่าน (^|/)vllm pattern
  • Verify: ทดสอบ pattern กับ process จริงก่อน deploy — ใช้ cat /proc/PID/cmdline | tr '\0' '\n' | grep -P "pattern"

ข้อเรียนรู้ — silent failure ของ regex อันตรายกว่า crash เพราะมันไม่มี error, ไม่มี warning, config ดูถูกทุกอย่าง แต่ทำงานเป็น 0% effective การ verify pattern กับ process จริงก่อน trust เป็นสิ่งจำเป็น — โดยเฉพาะ daemon ที่ทำงาน background อย่าง earlyoom ที่ไม่ค่อยมีใครไป check log

ถ้าใครกำลังทดลอง OOM-related hardening บน DGX Spark หรือ single-board computer ที่ใช้ unified memory — verify pattern ก่อน trust เสมอ ครั้งหน้าผมจะใส่ verify step ตั้งแต่แรกใน checklist

อ้างอิง​

แชร์บทความ
☕

เนื้อหานี้มีประโยชน์ไหม? ช่วยสนับสนุนค่ากาแฟให้ผู้เขียนสักแก้ว

Buy Me a Coffee
Loading...