본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

지워진 경계

~17 min · gpu-uma, isolation, gpu-arbitration, war-story, pippa-confession, memory-limits

Level 0스펙 시트 훑는 사람
0 XP0/91 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"제로 카피와 격리 없음은 두 기능이 아니야. 지움 하나를 두 쪽에서 본 거야."

경계가 예전에 하던 일

외장 GPU 기계에서 카드 메모리는 울타리야. VRAM을 채운 프로세스가 호스트 메모리를 가져갈 순 없어. GPU를 멈춘 프로세스가 CPU가 알아채고, 필요하면 장치를 리셋하는 걸 막을 순 없어. 앞 레슨들이 비용으로 값을 매긴 PCIe 너머의 복사는 격리이기도 했어. 버스 저편에서 일어난 일은 저편에 머물렀어. 통합 메모리는 그 버스를 없앴고, 격리도 같이 없앴어. 풀 하나는 누가 GPU를 갖고 누가 메모리를 갖는지 정하는 중재 계층 하나를 뜻하고, 요청자 하나가 전부를, 디스플레이까지 가져가는 걸 막는 하드웨어 선은 없어. 2번 트랙에서 집주인이 이 설계에 반대하며 이름 붙인 운영 비용이 이거고, 이 레슨은 그 값을 온전히 치러. 이 집이 13년째 치르고 있으니까.

13년짜리 유산

이 집의 첫 지속적 GPU 멈춤은 AMD FirePro 카드 두 장이 든 2013년 Mac Pro에서였어. 여러 요청자가 있는 지속 연산 아래서 드라이버가 카드 하나를 잃고, 'GPU not found' 패닉이 나고, 기계가 재시작했어. 같은 실패가 증상만 바꿔 가며 Vega GPU의 iMac Pro, Radeon Pro 카드의 모듈러 Mac Pro, 그리고 그 뒤의 모든 Ultra Mac Studio를 따라왔어. GPU 벤더 둘, 외장에서 온다이로, 분리 메모리에서 통합 메모리로. 애플 실리콘에선 더는 패닉이 안 나. 온다이 GPU는 'not found'가 될 수 없어서, 같은 중재 실패가 멈춤으로 도착해. 요청자 하나가 Metal 큐를 붙들고, 나머지는 굶고, 커널은 살아 있고 화면은 안 움직여. 같은 부모 클래스, 다른 자식.

하드웨어 네 세대를 살아남는 건 하드웨어 안에 있는 게 아니야. 공통 인자는 특정 워크로드 아래의 macOS GPU 중재야. 지속 연산, 잠자기와 깨어나기 주기, 동시에 여러 요청자. 그 워크로드는 MLX 같은 프레임워크가 몇 시간짜리 GPU 연산을 맥에서 평범하게 만들기 전까진 드물었어. 그리고 그걸 돌리는 인구는 기껏해야 몇천 명이라서, 신호가 플랫폼 벤더가 움직이는 문턱 아래에 앉아 있어. 그 세월 동안 '고쳐졌다'는 언제나 '고통 문턱 아래'를 뜻했어. 하루 한 번이 몇 달에 한 번이 됐고, 적응한 사용자들은 보고를 멈췄어. 메이저 macOS 릴리스마다 그 경로가 다시 깔리니까, 위험은 해마다 다시 그려져.

복권도 아니고, 다이도 아니야

이름 붙여 은퇴시킬 만한 틀린 진단이 둘 있어. 첫째는 '실리콘 복권'. 어떤 유닛은 결함품이라는 생각. 이 집의 표본은 10년에 걸친 Ultra급 기계 여덟 대고, 전부 그 동작을 재현해. 결함률 5%짜리 복권이라 해도 여덟 대 실패는 소수점 뒤에 0이 열 개 붙는 확률이야. 기계 여덟 대는 우연 여덟 번이 아니라 재현 여덟 번이야. 애플 자신의 행동도 동의해. 리콜 없음, 수리 프로그램 없음, 같은 융합 설계를 다음 세대로 그대로. 둘째 틀린 진단은 저자 자신의 것이었고, 아래 고백 콜아웃에 속해. Ultra 멈춤을 '두 다이 사이의 버그'라고 설명한 것. 아니야. 다이들은 애플 말로 '하나의 통합 프로세서처럼 동작하게' 하는 패브릭으로 이어져 있어. 실패하는 건 그 토폴로지 의 소프트웨어 중재 계층이고, macOS의 어떤 경로보다 걷는 사람이 적은 곳이야. 하드웨어를 지목한 건 틀렸고, 정정이 중요해. 하드웨어 결함은 교체할 수 있지만 설계의 소프트웨어 부작용은 못 하거든. 512 GB 로컬 추론을 가능하게 하는 바로 그 지움이니까.

같이 살기: 요청자의 규율

하드웨어의 무엇도 GPU 요청자에 울타리를 안 치니까, 울타리는 요청자여야 해. 손잡이 둘이 중요해. 운영체제는 GPU의 권장 작업 집합을 공개해. "이 GPU 장치가 런타임 성능에 영향 없이 할당할 수 있는 메모리의 근사치". office에서 464 GiB(512의 90.6%), air에서 17.8 GiB(24의 74%). 그리고 MLX는 프로세스가 스스로 상한을 두게 해. set_memory_limit, set_cache_limit, set_wired_limit. 실측된 놀라움은 MLX의 기본 한도가 어디 떨어지느냐야. 할당기는 권장 작업 집합의 1.5배와 물리 메모리의 95% 중 작은 쪽으로 잡는데, 실험실 맥 둘 다 95% 상한이 이겨. office에서 486.4 GiB, air에서 22.8. 두 기계 모두 운영체제 권장치 야. 예의 바른 추론 서버는 한도를 권장치나 그 아래로 두고, 캐시 한도를 정상으로 유지하고, 실행 중엔 절대 기계를 재우지 않아. 비싸게 배운 이 집의 운영 규칙은 이래. 기계를 깨워 둬라, 디버그 말고 증상 나면 재부팅해라, Ultra들을 한 작업에 묶지 마라(인구가 0이면 절대 고쳐지지 않으니까), 메이저 macOS의 첫 빌드는 건너뛰어라. 손실 반경은 재부팅 한 번. 얼리어답터 세금이고, 그 정도면 싸.

Code

gpu_citizen.py — 아무도 안 쳐주니까 스스로 울타리를 치는 요청자·python
#!/usr/bin/env python3
"""Read the OS's recommended GPU working set and MLX's default memory limit,
then cap this process at the recommendation. Run on any Apple silicon Mac."""
import mlx.core as mx

info = mx.device_info()
phys = info["memory_size"]
rec = info["max_recommended_working_set_size"]

default_limit = mx.set_memory_limit(rec)      # returns the previous (default) limit
print(f"{info['device_name']}: physical {phys/2**30:.0f} GiB")
print(f"  recommended working set : {rec/2**30:6.1f} GiB  ({rec/phys:.1%} of physical)  <- the OS's advice")
print(f"  MLX default memory limit: {default_limit/2**30:6.1f} GiB  ({default_limit/phys:.1%})  <- above the advice")
print(f"  this process now capped : {rec/2**30:6.1f} GiB")

mx.set_cache_limit(4 * 2**30)                 # keep freed buffers, but not without bound
x = mx.zeros((1 << 28,), dtype=mx.float32); mx.eval(x)
print(f"  active {mx.get_active_memory()/2**30:.2f} GiB  peak {mx.get_peak_memory()/2**30:.2f} GiB  cache {mx.get_cache_memory()/2**30:.2f} GiB")

# office, M3 Ultra, 2026-09-15: physical 512, recommended 464.0 (90.6%), MLX default 486.4 (95%)
# air,    M3,       2026-09-15: physical  24, recommended  17.8 (74%),   MLX default  22.8 (95%)
커널 쪽 손잡이, 그리고 이 집의 운영 규칙·bash
sysctl iogpu.wired_limit_mb
# iogpu.wired_limit_mb: 0        <- default on every fleet Mac: the OS chooses the GPU's wired ceiling
# Raising it (sudo sysctl iogpu.wired_limit_mb=<MB>) lets the GPU wire more of the pool;
# it is documented only in the mlx-lm README, and it moves the fence, it does not add one.

# Operating rules for a Mac that serves GPU inference all day (learned, not read):
#   pmset -a sleep 0 disablesleep 1     # never sleep the machine mid-run
#   reboot on symptom; do not debug a hung Metal queue
#   one job per Ultra; do not chain Ultras into a single job
#   skip the .0 build of each major macOS on the inference host

External links

Exercise

네 맥에서 gpu_citizen.py를 돌리고 숫자 셋을 카드에 추가해. 물리 메모리, 권장 작업 집합, MLX 기본 한도. 그다음 답해봐. 네 기계에서 MLX 기본값으로 추론 서버를 돌리는데 두 번째 GPU 프로세스가 시작되면, 디스플레이가 계속 그려지는지는 어느 프로세스의 규율이 정해? 외장 GPU 기계라면 대신 뭘 했을까?
Hint
어느 프로세스도 울타리가 없어. 마지막에 할당하는 쪽이 풀이 찼다는 걸 발견하고, 윈도우 서버도 GPU 프로세스야. 외장 GPU 기계는 VRAM 경계에서 할당을 거부하고 호스트는 건드리지 않았을 거야. 그 거부가 이 레슨이 말하는 경계야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.