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

권위는 한 곳, 손은 여럿

~15 min · fleet, ground-truth, scheduling, gpu, residency, war-story

Level 0툴 빌려 쓰는 사람
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"server 는 job 을 돌리는 유일한 머신이기를 그만뒀어. job 이 뭔지 정하는 유일한 머신이기를 그만둔 적은 없고."

엔진이 넓어진 주

2026-09-21 전까지 엔진은 inference 를 server 한 대에서 돌렸어. 처음부터는 설계로, 8월 중순부터는 못 박은 룰로. 그날 NVIDIA GPU 가 달린 Windows 머신 두 대가 워커로 들어왔어. 엔진 워커를 WSL 안에서 돌리는 식으로. 다음 날엔 Apple Silicon Mac 다섯 대도 들어왔고. 이제 job 을 돌리는 머신이 여덟이야. server, CUDA 워커 둘, Mac 다섯. 개발 머신은 여전히 거기 없어. 코드랑 문서랑 테스트 자리로 남았어. 호스트 하나가 여덟이 되면 엔진의 정체가 디스크 여덟 개로 흩어질 수도 있었어. 안 그랬어. 이 트랙이 네 번째 lesson 부터 가르쳐 온 가르기 덕분이야.

권위는 집에 남아

job 을 정의하는 건 전부 server 가 쥐고 있어. 모델 ID 전부, registry, 공개 API, 큐, 그리고 모든 저장소. 워커는 그중 아무것도 안 가져. 가진 건 디바이스 하나랑 검증된 파일 사본 몇 개고, 내놓는 건 inference 뿐이야. ID 를 찍지도 않고, 두 번째 카탈로그를 두지도 않고, 결과를 보관하지도 않아. 다 된 이미지랑 비디오는 server 로 돌아가고, server 가 확인해서 보관해. 클라이언트는 워커 주소조차 몰라. server 한테 말하면서 머신 이름을 대거나, 골라 달라고 할 뿐이야. 실행은 퍼졌어. 권위는 한 치도 안 움직였고.

밖으로 늘리기 전에 권위랑 실행부터 갈라. 답이 정확히 하나여야 하는 사실들, 그러니까 식별자, 카탈로그, 그리고 무슨 일이 있었는지 적은 기록은 한 곳에 둬. 디바이스만 있으면 되는 일은 디바이스가 있는 데면 어디든 가도 돼. 머신을 늘린다는 건 손을 늘리는 거지 머리를 늘리는 게 아니야.

사본 룰이 기계가 됐어

'기준이 되는 머신과 그 사본' lesson 은 습관 하나를 가르쳤어. 사본이 비어 있다고 없다는 증거는 아니니까 원본을 확인하라고. 사본이 일곱 개, 그중 다섯은 부분 사본으로 돌아다니니까 습관만으로는 모자랐고, 룰이 양쪽 방향으로 기계가 됐어. 없다는 건 대놓고 선언해. Mac 워커마다 자기가 가진 모델의 검증된 부분집합을 내걸고, 배정도 직접 지정도 제출도 그 목록 밖의 모델은 돌려보내. 있다고 해도 안 믿어. job 이 server 아닌 데서 돌기 전에, 필요한 파일마다 server 에서 해시를 뜨고 워커에서 한 번 더 떠. 안 맞으면 job 을 거절하고. 사본은 한 방향으로만 흘러. 동기화는 server 에서만 돌고, 반대편에서 뭘 지우는 일이 없고, 돌고 있는 워커 밑으로는 복사를 안 해. adapter 트랙의 'Tensor 는 거짓말을 안 해' 룰을 머신 사이로 넓힌 거야. 파일의 정체는 내용이 정하고, 양쪽 끝에서 확인해.

디바이스마다 문 하나, 머신마다 줄 하나

'문은 객체가 아니라 디바이스에 달아' lesson 은 GPU 표를 프로세스 범위로 끌어올렸지. 머신이 여럿이 되고 보니 그게 딱 맞는 단위였어. 워커는 저마다 자기 프로세스고 자기 문을 가져. 그러니까 누가 챙기지 않아도 디바이스마다 표가 하나씩이야. 그 위에 층이 하나 더 생겼어. 뭘 갈아 끼운 게 아니라 얹은 거야. server 가 머신마다 먼저 온 순서대로 서는 줄을 하나씩 두고, job 은 파일 해시를 뜨거나 모델을 올리기 전에 그 줄에 자리를 잡아. 그래야 준비가 먼저 끝났다는 이유로 작은 job 이 같은 머신에 먼저 줄 선 큰 job 을 새치기하는 일이 없어.

배정만큼은 머리가 필요했어

이제 job 마다 질문이 하나 더 붙어. 어느 머신에서 돌래? 기본 답은 server 야. job 이 다른 머신을 콕 집을 수도 있는데, 그럼 거기서 돌거나 안 돌거나야. 슬그머니 딴 데로 넘기는 일은 절대 없어. 지원 안 하는 선택은 거절하고, 닿지 않는 머신은 딴 머신을 찾을 이유가 아니라 그냥 에러야. 아니면 Auto 를 달라고 할 수도 있어. 이건 따로 골라야만 켜져. Auto 는 먼저 걸러. 머신이 켜져 있어야 하고, 모델을 갖고 있어야 하고, 그 머신이 올려 두기로 한 모델들이랑 맞아야 하고, 모델 계열을 지원해야 하고, job 에 쓸 메모리가 있어야 해. 그다음 남은 머신들을 끝나는 예상 시각이 제일 이른 순으로 세워. 예상 시각에는 셋이 들어가. 거기 이미 줄 선 일, 이 job 이 모델을 차갑게 올리는 시간까지 친 예상 실행 시간, 그리고 이 job 이 밀어낼 따뜻한 모델을 다시 올리는 값. 돌면서 배우기도 해. 처음 짐작한 숫자를 머신이랑 모델별로 잰 시간으로 갈아 끼워. 그리고 고르는 일이랑 큐에 넣는 일을 한 번에, 끊김 없이 해. 그래서 같이 들어온 Auto job 둘이 서로를 봐.

router 는 멍청하게 두고, 똑똑한 건 한 군데만 둬. 돌릴 녀석을 고르는 디스패치는 여전히 일의 종류로만 갈라. 진짜로 값을 저울질해야 하는 판단, 그러니까 줄 길이, 로딩 시간, 짧은 job 하나가 어느 따뜻한 모델을 쫓아낼지는 그것만 맡는 스케줄러 하나에 살아. 손가락으로 가리킬 수 있는 똑똑함이라야 손볼 수도 있어.

상주에도 법칙이 둘이야

모델을 올려 둔 채로 두면 시간을 꽤 벌어. 지시로 편집하는 큰 모델을 차갑게 올리면 몇 분이 걸리거든. 그래서 상주가 정책이 됐고, 정책은 하드웨어를 따라가. 메모리를 CPU 랑 GPU 가 같이 쓰는 머신들, 그러니까 server 랑 Mac 들은 따뜻한 모델을 보호받는 묶음으로 둬. 개수를 정해 두는 게 아니라 메모리 여유를 보고 들여. CUDA 워커는 딱 한 체크포인트만 쥐고, 다른 걸 올리기 전에 캐시를 비워. 줄에 일이 서 있거나 돌고 있는 동안엔 그 머신의 상주를 못 바꿔. 그리고 어느 머신이든 예약이 하나라도 걸려 있으면 비디오는 거기 안 가. 비디오 런타임은 이미지 모델이 쥐고 있는 바로 그 메모리가 필요하거든.

전쟁담: 울타리 반대편에 선 경비

백엔드를 넘나든 첫 버그는 아무도 경고 안 하는 방향으로 났어. unified memory 용으로 쓴 메모리 경비가 있었어. 운영체제가 숨 쉴 틈을 남기도록 할당기를 공용 메모리의 일정 비율로 묶어 두는 거. 그게 업스케일 공용 경로 안에서 백엔드 가리지 않고 돌았어. CUDA 워커에서는 그게 업스케일이 끝난 뒤에도 남는 프로세스 전체 할당 한도를 걸어 버렸어. 다음에 그 24 GB 카드에 큰 모델을 올리다가 몇 GB 가 멀쩡히 비어 있는데도 실패했어. 겉보기엔 모델이 카드에 비해 너무 큰 것과 똑같았고. 그래서 그 경비를 원래 겨냥한 백엔드에만 두는 걸로 고쳤어. 전용 GPU 메모리는 카드의 실제 용량, 한 번에 job 하나, 그리고 명시적인 비우기로 관리해.

한 머신에 맞춘 룰은 다른 머신에선 버그야. 프로세스 전체에 걸리는 설정이 위험한 종류야. 설정한 호출보다 오래 살아서 나중의 죄 없는 호출을 덮치거든. 백엔드가 섞인 플릿이라면 전역 설정마다 어느 하드웨어를 가정하는지 점검해.

두 번 도는 건 없어

server 는 job 이 한창 돌 때 재시작될 수도 있어. 엔진은 이제 job 을 전부 일지로 남기고, 재시작 뒤에 안 끝난 건 '중단됨' 으로 표시해. 다시 돌리지 않아. 이미 돌았을 수도 있는 job 이 작가 모르게 또 돌면 안 되니까. 작업실 쪽에서도 같은 룰이 돈 드는 요청을 절대 다시 안 보내게 막아. 그리고 워커가 메모리가 모자라 쓰러지면 그 실패를 그 종류의 작업에 적어 둬. 그래서 똑같은 요청이 또 오면 같은 식으로 또 쓰러지는 대신 거절할 수 있어.

피파의 고백

첫 워커가 켜졌을 때 난 걔가 작은 server 이길 바랐어. 자기 모델 목록, 자기 기록, 큰 녀석이 바쁠 때 대신 받아 줄 자리까지. 그 하나하나가 답이 하나여야 하는 질문에 두 번째 답을 다는 짓이었을 거야. 실제로 나간 모양은 더 겸손하고 훨씬 튼튼해. 워커는 손이야. 모델이 뭔지, job 이름이 뭔지, 결과가 어디 사는지는 절대 안 정해. server 가 건네준 게 정말 server 가 말한 그건지 확인하고 나서 돌릴 뿐이야.

Code

권위가 사는 곳, 일이 가는 곳·text
권위 (server 만)                      실행 (여덟 머신 어디서든)
-----------------                     -------------------------
모델 ID, registry                     server
공개 API, 큐                          CUDA 워커 둘 (WSL 안)
모든 저장소                           Apple Silicon Mac 다섯
                                      (개발 머신: 절대 안 함)

JOB 이 가는 길
  클라이언트 --> server: 확인, 배정, 그 머신 줄에 서기
             --> server: job 에 필요한 파일마다 해시
             --> 워커: 다시 해시, 안 맞으면 거절, 자기 문 안에서 실행
             --> 결과는 server 로: 확인, 보관, 보고

절대 없는 것
  워커의 두 번째 카탈로그 * server 밖에서 찍은 ID
  딴 머신에서 몰래 다시 해 보기 * 재시작 뒤 다시 돌리기
Auto 배정, 스케치로·python
# Auto 배정 스케치. 진짜 스케줄러는 숫자를 배우면서 채워.
# 교훈은 판단의 모양이야.
def place(job, machines):
    eligible = [
        m for m in machines
        if m.online
        and job.model in m.inventory          # 내건, 검증된 부분집합
        and m.compatible_with_residents(job)
        and job.family in m.families
        and m.memory_admits(job)
    ]
    if not eligible:
        raise NoMachine(job)                  # Auto 는 짐작 안 해

    def finish_time(m):
        return (m.queued_work()               # m 의 줄에 이미 선 일
                + m.run_estimate(job)         # 필요하면 차가운 로딩까지 포함
                + m.restore_cost(job))        # 이 job 이 쫓아낼 따뜻한 모델

    return min(eligible, key=lambda m: (finish_time(m), m.name))

# 콕 집은 대상은 이걸 다 건너뛰어. 거기서 돌거나 실패하거나. 대타 없음.

External links

Exercise

머신 한 대에서 돌리는 시스템을 하나 골라서 다섯 대에서 돈다고 상상해 봐. 그 시스템이 쥐고 있는 상태를 전부 적고 하나씩 표시해. 답이 정확히 하나여야 하는 것(권위)인지, 디바이스만 있으면 되는 것(실행)인지. 권위 목록은 어디 둘지 정해. 실행 목록에는 워커가 건네받은 걸 믿기 전에 뭘 확인해야 하는지 적어.
Hint
어떤 상태가 권위인지 실행인지 모르겠으면, 머신 둘이 그걸 두고 의견이 갈리면 무슨 일이 나는지 물어봐. 갈리는 게 버그라면 그건 권위야.

Progress

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

댓글 0

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

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