earlyoom regex silent failure — DGX Spark vLLM freeze ที่ป้องกันไม่อยู่
สารบัญ
บันทึก 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 พัง
สรุปสถานการณ์เดิม:
| Pattern | Intent | Actual match | Effective |
|---|---|---|---|
--prefer ... python3.*vllm ...$ | kill vllm ก่อน | no match | 0% |
--avoid ... containerd-shim ...$ | ปกป้อง shim | no match | 0% |
--avoid ... dockerd ...$ | ปกป้อง dockerd | match (/usr/bin/dockerd) | 100% (single-arg process) |
--avoid ... runc ...$ | ปกป้อง runc | no 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+ matchVLLM::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 vllm | no | yes (PID 27891) |
| AVOID match containerd-shim-runc-v2 | no | yes (PID 27868) |
| AVOID match docker-proxy | no (ไม่มีใน pattern เดิม) | yes (PID 28000) |
| AVOID match dockerd | yes (single-arg) | yes |
Effective --prefer | 0% | 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 ตรงๆ ผ่าน(^|/)vllmpattern - 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
อ้างอิง
- Spark hangs requires hard reset — NVIDIA Developer Forums — UMA OOM freeze pattern บน GB10, NVIDIA ยอมรับเป็น known issue
- Impact of Power On/Off Method on DGX SPARK Lifespan — NVIDIA staff ระบุ occasional OK ไม่ใช่ ideal long-term
- earlyoom GitHub — docs เรื่อง
--prefer/--avoidmatching against/proc/PID/cmdline - PCRE2 Pattern Syntax —
.ไม่ match newline/NUL by default
เนื้อหานี้มีประโยชน์ไหม? ช่วยสนับสนุนค่ากาแฟให้ผู้เขียนสักแก้ว
Buy Me a Coffee