밤새 돌린 로컬 LLM 배치가 끊긴 이유, 열이 아니라 맥 잠자기였습니다
밤사이 걸어 둔 시험이 아침에 보니 끝부분에서 무더기로 실패해 있었습니다. 노무 질문 100문항짜리 시험지를 설정별로 여러 번 돌려 답의 품질을 비교하는 일이었습니다. 설정 하나로 시험지 전체를 한 번 돌리는 것을 '판'이라고 불렀고, 판 하나에 약 1시간 20분이 걸려 밤에 걸어 두었습니다.
판끼리 같은 조건으로 비교해야 하는 시험이라, 빠진 문항은 곧 비교의 구멍입니다. 첫 판은 6문항이 실패로 빠져 93문항으로만 채점됐습니다. 다음 판의 첫 시도도 중간에 끊겨 다시 돌려야 했습니다.
실행 환경은 작업용 맥북의 LM Studio 에 Gemma 4 26B-A4B 를 올리고 동시 처리를 3으로 둔 구성입니다. 검색 DB(Qdrant)는 다른 기계에 있고, Tailscale(기계끼리 사설망으로 잇는 VPN 도구)로 접속합니다. 대량 질의를 로컬 모델로 돌리는 이유는 외부 모델 비용 없이 몇 시간짜리 배치를 걸 수 있기 때문입니다.
다음 날 밤 같은 맥으로 GPU 서버와 답 품질을 견준 측정에서도 같은 일이 났습니다. 밤새 돌리다 맥이 잠들어 마지막 세 문항에서 멈췄고, 아래에서 설명할 이어 돌리기로 끝냈습니다. 그 비교 결과는 따로 정리해 두었습니다.
에러는 두 종류였고, 장비는 멀쩡했습니다
첫 판에서 실패한 6문항의 에러가 둘로 갈렸습니다.
HTTP 400 {"error":"Engine protocol predict stream timed out after 600000ms without receiving data."}
URLError [Errno 60] Operation timed out3건은 LM Studio 가 600,000ms(10분) 동안 토큰을 하나도 못 보냈다는 400 이었습니다. 나머지 3건은 원격 검색 DB 연결 시간 초과였습니다. 다음 판은 더 심해서, 7문항 중 4문항이 문항당 1,000초 넘게 걸린 뒤 400 으로 끝났습니다.
같은 질문을 낮에 다시 보내니 4초 만에 정상 응답이 왔습니다. 장비 고장은 아니었습니다. 밤에, 그것도 어느 시각 이후에만 실패한다는 뜻이었습니다.
첫 짐작은 열이었고, 틀렸습니다
처음엔 열을 의심했습니다. 덮개를 닫아 둔 채 몇 시간 계산을 시켜 열이 안 빠졌다고 봤습니다. 로그에도 'Thermal Emergency'라는 낱말이 찍혀 있었으니 그럴듯한 설명이었습니다.
그런데 확인해 보니 덮개를 닫은 적이 없었습니다. 짐작의 출발점이 사실이 아니었습니다. 그래서 경고 낱말을 따라가는 대신, 실패 시각과 전원 기록의 시각을 나란히 다시 맞췄습니다.
화면이 꺼지고 5초 뒤에 잠들었습니다
맥의 전원 기록은 pmset -g log 로 볼 수 있습니다. 잠자기·깨어남 관련 줄만 걸러 실패 시각 옆에 놓았습니다.
pmset -g log | grep -E "Entering Sleep|DarkWake|Display is turned"23:22:49 화면 꺼짐 — 디스플레이 끄기를 30분으로 둔 설정대로였습니다
5초 뒤 'Entering Sleep state due to Idle Sleep' — 사용자 입력이 없어 잠들었습니다
그 뒤 'DarkWake'(화면 없이 잠깐 깨는 상태)마다 'Thermal Emergency Sleep'으로 다시 잠들었습니다
잠든 동안 LM Studio 는 10분 제한에 걸려 400 을 냈고, 원격 DB 연결은 시간 초과가 났습니다
배치가 돌고 있어도 운영체제는 사용자 입력만 보고 잠들었습니다. 이 기계는 pmset 의 sleep 값이 1이라, 화면이 꺼지면 거의 바로 잠드는 상태였습니다. '일이 돌고 있으니 안 잠들겠지'는 사람의 감각이었습니다.
'Thermal Emergency'는 결과였지 첫 원인이 아니었습니다. 깨자마자 LLM 계산이 다시 시작되면서 열 신호가 났을 것으로 추정하지만, 이 부분은 확인하지 않았습니다. 확실한 것은 순서입니다. 잠자기가 먼저였고, 열 경고는 그 뒤였습니다.

두 종류의 에러도 여기서 하나로 묶였습니다. LLM 서버의 400 과 원격 DB 연결 시간 초과는 따로 보면 서로 다른 고장 같습니다. 둘 다 '그 시각에 기계가 자고 있었다'로 설명됩니다.
잠자기만 막지 않고 도구도 고쳤습니다
잠자기를 막는 것으로 이번 원인은 해결됩니다. 다만 다음에 다른 이유로 끊겨도 처음부터 다시 돌리지 않도록 도구 쪽도 같이 고쳤습니다. 진단과 판 실행에 약 150분, 이어 돌리기 기능을 붙이는 데 약 15분이 들었습니다.
무엇 | 어떻게 |
|---|---|
잠자기 방지 | 시스템 설정 → 배터리 → 옵션 → '전원 어댑터 연결 시 디스플레이가 꺼지면 자동으로 잠자기 방지' 켜기 (sudo pmset -c sleep 0 과 같다) |
이어 돌리기 | 실패 없이 끝난 문항은 두고, 실패·남은 문항만 다시 묻는다 |
동시 요청 수 맞추기 | 클라이언트 동시 요청과 서버 동시 처리를 같은 수로 둔다 |
실패 판정 | '실패' 메모가 붙은 행을 종류와 상관없이 전부 센다 |
잠자기 방지는 caffeinate 명령으로 붙잡아 두는 방법도 있습니다. 이번에는 배치마다 명령을 띄우는 대신 설정 한 번으로 끝나는 쪽을 골랐습니다.
가장 큰 변화는 이어 돌리기(--resume)입니다. 같은 회차 번호로 이어 쓰고, 결과 파일에 어디서 이었는지를 남깁니다. 이은 파일·해시·그대로 둔 문항 수·다시 물은 문항이 같이 기록됩니다.
중간 기록 첫 줄에는 판 설정·프롬프트 해시·동시 요청 수를 적어 둡니다. 이어 돌릴 때 설정이 바뀌었으면 도구가 멈춥니다. 사람의 기억에 기대면 조건이 다른 결과가 한 파일에 섞이기 때문입니다.

검증은 두 번 했습니다. 시험 코드를 하나 추가해 테스트 6개가 통과했습니다. 끊긴 판의 기록을 임시 폴더에 옮겨 실제로 돌려 보니, 기존 58문항은 한 글자도 바뀌지 않았고 2문항만 새로 물었습니다. 문항 순서도 맞았습니다.
실제로는 끊긴 판을 58문항에서 이어 끝냈습니다. 설정 기록을 남기기 전에 만든 옛 기록이라 도구가 대조할 수 없어서, 이 판은 '사람이 확인'으로 표시해 두었습니다. 다음 판은 15문항에서 이었고 설정 대조를 통과했습니다.
동시 요청 수는 이번 실패의 원인이었다는 근거가 없습니다. 같이 정리한 예방책입니다. 서버 동시 처리가 1인데 클라이언트가 3개씩 보내면, 남은 요청이 대기열에서 줄을 서다 600초 제한에 걸릴 수 있습니다. 서버 쪽은 lms load <model> --parallel N 으로 맞추고, 판끼리 조건을 맞추려 3으로 둔 뒤 그 수를 결과 파일에 남깁니다.
실패를 세는 기준도 좁았습니다
실패를 세는 방법도 틀려 있었습니다. 처음엔 'LLM 호출 실패' 메모만 세서 연결 시간 초과 3건을 놓쳤습니다. 한 종류의 실패 메시지만 세는 집계는 다른 종류의 실패를 성공처럼 보이게 합니다. 지금은 '실패' 메모가 붙은 행을 종류와 상관없이 전부 셉니다.

밤새 돌리기 전에 확인할 것
전원 어댑터를 연결한 상태에서, 화면이 꺼진 뒤에도 잠들지 않게 설정돼 있는가
밤중에 무더기로 실패했다면, 실패 시각과 잠자기 시각부터 맞춰 봤는가
배치 도구가 행마다 바로 파일에 쓰고, 설정 해시를 같이 남기는가
이어 돌릴 때 설정이 바뀌었으면 도구가 멈추는가
실패 집계가 실패 종류와 상관없이 전부 세는가
이 결론에도 범위가 있습니다. 덮개를 실제로 닫았거나 통풍이 막힌 환경이라면 열이 첫 원인일 수 있습니다. 그래서 규칙은 '열이 아니다'가 아니라 '순서를 본다'입니다. 팬이 도는 것 자체는 정상입니다. 잠자기 방지 설정은 전원 어댑터를 연결한 경우에만 해당하고, 배터리로 돌리는 경우는 이번에 다루지 않았습니다.
가장 눈에 띄는 경고가 첫 원인은 아니었습니다
로그에서 가장 무서워 보이는 낱말은 'Thermal Emergency'였습니다. 진단을 바로잡은 건 그 낱말이 아니라 시각 순서였습니다. 화면 꺼짐, 5초 뒤 잠자기, 그다음이 열 경고였습니다.
몇 시간짜리 일을 노트북에 거는 경우라면 로컬 LLM 이 아니어도 같습니다. 임베딩 색인, 데이터 이관, 긴 테스트도 사용자 입력이 없으면 똑같이 잠듭니다. 저는 이제 이어 돌리기를 나중에 붙이는 편의 기능이 아니라, 장시간 배치 도구를 처음 만들 때 넣는 기본 설계로 봅니다.