← Kembali ke Lab

ulimit tidak membatasi memori di macOS: hentikan job sebelum swap

apple-siliconmemoryguide

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 argument

resource.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 percent

Footprint 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.py

Guard 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 4

Job 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

Footprint kedua job dari waktu ke waktu menurut trace memorinya: images memegang 3 GB selama 10 s sementara upscale menunggu di antrean, lalu upscale memegang 2 GB selama 4 s

Detail yang penting di Mac yang dipakai bersama:

Batasan