Di Linux, memori sebuah job bisa dibatasi dengan ulimit -v, systemd-run -p MemoryMax= atau cgroup, dan job yang tumbuh terlalu besar gagal sendiri alih-alih menjatuhkan seluruh mesin. macOS tidak punya systemd maupun cgroups, dan ulimit menolak. Ini macOS 26.5.2:
$ /bin/bash -c 'ulimit -v 1048576'
/bin/bash: line 0: ulimit: virtual memory: cannot modify limit: Invalid argument
$ /bin/bash -c 'ulimit -m 1048576'
/bin/bash: line 0: ulimit: max memory size: cannot modify limit: Invalid argumentresource.setrlimit(resource.RLIMIT_DATA, ...) di Python melempar ValueError, dan di shell tempat ulimit -m gagal, sebuah skrip kemudian tetap mengalokasikan dan menyentuh 3 GiB. Yang dilakukan macOS sebagai gantinya adalah memampatkan memori dan menulisnya ke swap. Model yang tidak muat tidak gagal: ia melambat, begitu pula semua hal lain di Mac.
Jadi pertanyaan yang perlu diajukan berubah: adakah ruang sebelum job dimulai, dan apakah swap bertambah selama job berjalan?
Periksa swap selama job berjalan
sysctl vm.swapusage # compare "used" before and after a run
sysctl kern.memorystatus_level # system-wide free memory, in percentFootprint proses, angka yang ditampilkan Monitor Aktivitas, tidak menghitung halaman yang sudah ada di swap. Dalam satu run Qwen-Image-2.1 dengan model CPU offload dari diffusers di M5 Pro 48 GB milik saya, footprint tidak pernah melewati 18,5 GB sementara swap bertambah 9,6 GB. Grafik footprint saja akan menganggap run itu muat.
Hentikan job sebelum swap
stageload, pustaka kecil yang saya tulis untuk memuat model satu tahap demi satu tahap, menyertakan guard untuk ini (pip install stageload):
stageload guard --wait-free 40 --swap-budget 8G -- python generate.pyGuard menunggu sampai kern.memorystatus_level setidaknya 40 %, lalu menjalankan perintah. Jika swap bertambah lebih dari 8 GB dibanding yang terpakai saat mulai, atau memori bebas tetap di bawah 10 % selama dua sampel, guard mengirim SIGTERM ke grup proses perintah itu dan, lima detik kemudian, SIGKILL ke apa pun yang tersisa. Guard tidak butuh root, dan satu-satunya proses yang pernah dihentikannya adalah perintah itu dan grup prosesnya. Kode keluar 3 berarti guard menghentikan perintah, dan 4 berarti guard tidak pernah menemukan ruang untuk memulainya; selain itu Anda mendapat kode keluar perintah itu sendiri.
Begitulah run eager Qwen-Image berakhir: run itu menyelesaikan semua 20 langkah, decode VAE membawa footprint ke 43,6 GB, swap bertambah 8 GB, dan guard menghentikannya sebelum Mac macet.
Dua job berat sekaligus
Setiap guard memutuskan sendiri. Dua guard yang menemukan ruang pada saat yang sama sama-sama menjalankan job-nya, padahal tiap job diukur untuk Mac yang tidak menjalankan apa-apa lagi. Pada 5 Oktober, salah satu run saya mulai di celah antara dua job video dari skrip lain, dan job video berikutnya membuat swap melewati batasnya dalam 20 detik.
stageload run menaruh job-job itu dalam satu antrean untuk seluruh mesin. Flag-nya sama dengan guard. Di sini dua job memegang 3 dan 2 GiB, dijalankan dari dua terminal:
stageload run --who images -- python3 alloc.py 3 10 # holds 3 GiB for 10 s
stageload run --who upscale -- python3 alloc.py 2 4 # holds 2 GiB for 4 s$ stageload queue
ram 0 running images pid 32488 since 2026-10-12T14:08:56+00:00 python3 alloc.py 3 10
ram 1 waiting upscale pid 32499 since 2026-10-12T14:08:58+00:00 python3 alloc.py 2 4Job kedua mulai ketika job pertama selesai, lalu pemeriksaan guard berjalan seperti biasa:
14:08:58Z queue: waiting in ram (1 ahead: images)
14:09:08Z queue: start in ram after 10 s, swap 6.0 GB, stop above 14.0 GB or under 10% available twice$ stageload queue --history 2
done ram exit 0 images waited 0 s, ran 10 s, peak 3.0 GB python3 alloc.py 3 10
done ram exit 0 upscale waited 10 s, ran 4 s, peak 2.0 GB python3 alloc.py 2 4
Detail yang penting di Mac yang dipakai bersama:
- Job masuk ke sebuah lane,
ramsecara bawaan. Lane tidak saling menunggu, jadi job yang menunggu kuota API jarak jauh bisa masuk lane sendiri (--lane api --wait-free 0) dan tidak menahan job yang butuh memori. - Giliran adil di antara pemilik. Pemilik yang paling lama tidak memulai job mendapat giliran berikutnya, jadi skrip yang mengantrekan dua puluh job bergantian dengan yang lain alih-alih menguasai lane. Pemiliknya adalah
--owner, atau bagian--whosebelum tanda hubung pertama. - Setiap run menulis trace memori dari perintah dan setiap proses di bawahnya, dan menambahkan satu baris ke
~/.stageload/history.jsonlberisi waktu tunggu, waktu jalan, footprint puncak, dan swap. - Engine yang dijalankan di luar antrean dimasukkan ke
~/.stageload/busy.txt, satu pola per baris. Setiap run di lanerammenunggu sampai tidak satu pun dari engine itu terlihat selama 20 detik.
Batasan
- Guard dan meter membaca penghitung macOS (
libproc,sysctl), jadi hanya berjalan di Mac. - Guard menghentikan job, tetapi tidak membuat job lebih kecil. Di sisi model, memuat satu model per tahap itulah yang membawa Qwen-Image-2.1 dari 43,6 GB ke 19,0 GB di Mac yang sama: Cara menjalankan Qwen-Image-2.1 di Mac 48 GB tanpa swap.
- Sebagai jaring pengaman untuk seluruh mesin, macos-oom-guard berjalan sebagai root dan mematikan proses terbesar sebelum kernel panic. Keduanya bisa berjalan bersamaan.