AI와 나: 디버깅하는 AI를 보며 느낀 동질감

· 7 분 소요


들어가며

Claude Code를 사용하면서 얘가 작업하는 걸 보고 있으니, 묘한 동질감이 든다.

K8s 위 분산학습 환경에서 트러블슈팅을 진행하던 중이었다. 이전에 정리한 NCCL 트러블슈팅의 후속 작업으로, E2E 파이프라인을 검증하는 단계를 AI와 함께 진행하던 중이었다. 트러블슈팅 계획을 세운 뒤 특정 단계를 진행하라고 시켰는데, Bash 명령 30~40회 실행에 파일 읽기/수정 5~6회를 혼자서 줄줄이 진행하더라.

AI가 인간의 일자리를 위협한다는 요즘, 그 AI의 작업 과정에 공감하는 나 자신이 어딘가 웃겨서 가볍게 적어 본다.


관찰 기록

작업 시작

RayJob manifest를 클러스터에 배포하고 Pod 상태를 확인하는 것까지는 순조로웠다. Head pod이 기동되고 GPU worker 8개가 4개 노드에 분산 배치된 걸 확인하더니, 이미지 pull이 끝날 때까지 30초 정도 기다린 뒤 다시 상태를 확인했다. 여기까지는 평범한 흐름이다.

“너무 빠르다”

모든 Pod이 Running 상태로 올라왔고, RayJob status가 SUCCEEDED로 찍혔다. 여기서 AI의 반응이 인상적이었다.

모든 Pod Running이고 RayJob status가 SUCCEEDED로 나옵니다. 하지만 30초 만에 1 epoch 학습이 끝난 것은 너무 빠릅니다. submitter pod 로그를 확인하겠습니다.

나였으면 SUCCEEDED 뜬 거 보고 “됐네” 하고 넘겼을 수도 있다. 30초 만에 끝났다는 게 이상하다는 걸 바로 캐치한 건 솔직히 대단하다. 로그를 확인하니, submitter pod이 기존 Ray 클러스터에 연결하지 않고 로컬 Ray 인스턴스를 새로 띄우고 있었다. 원인을 파악하고 --ray-address auto 옵션을 추가해서 재배포했다.

삽질이 시작되다

재배포 후 다시 확인했는데 — 또 SUCCEEDED가 너무 빨리 나왔다. 그런데 여기서 AI가 사람 같은 반응을 보인다.

잠깐 — RayJob status가 SUCCEEDED인데 submitter pod이 Failed… RayJob의 SUCCEEDED는 Ray Job Submission에서의 job 상태이지 submitter의 상태와 다릅니다.

“잠깐 —” 으로 시작하는 이 멈춤. 뭔가 이상하다는 걸 감지하고, 자기가 세운 가설을 스스로 의심하면서 재정리하는 모먼트다. 디버깅하다가 “아 잠깐만, 이게 아닌데” 하는 그 순간과 정확히 같다.

이후로 한참 동안 head pod 로그를 확인하고, MLflow에서 실험 기록을 검증하고, submitterPodTemplate 설정을 분석하면서 프로덕션 환경과 비교하는 과정이 이어졌다. 그 와중에 이런 장면도 있었다.

이제 전체 구조가 보입니다. 실제로 job이 SUCCEEDED라면, head에서 실행된 job은 성공했고, submitter pod의 Failed는 별개 문제입니다.

“이제 전체 구조가 보입니다”라고 자신 있게 선언한 직후, 이 가설을 바탕으로 3차 시도를 했다. 결과는 — 또 SUCCEEDED가 너무 빨리 나왔다. “이제 보인다”는 확신 직후의 실패. 디버깅하는 사람이라면 누구나 겪어 본 그 패턴이다.

혼잣말처럼 반박하기

삽질 중간에 또 하나 인상적인 장면이 있었다. submitterPodTemplate을 제거하기로 결정한 직후:

잠깐 — submitterPodTemplate을 제거하면 기본 이미지로 submitter를 만드는데, 이미지 pull 권한이 없어서 못 가져올 수도… 아니, submitter는 학습 이미지가 필요 없습니다. 학습 이미지가 필요한 건 head/worker pod이고, 이들에는 이미 설정이 되어 있습니다.

가설을 세우자마자 스스로 반박하고, 반박을 다시 반박해서 결론을 내리는 과정. “잠깐 —” 멈춤과는 다른 종류의 사람다움이다. 나도 혼자 디버깅할 때 “아 근데 이러면… 아니다 그건 아니고…” 하면서 중얼거리는데, AI도 이걸 한다.

숨 고르기

중간에 정신없이 여기저기 확인하다가, 갑자기 이런 말이 나왔다.

잠깐 정리하겠습니다.

핵심 문제: submitter pod에서 학습 스크립트가 직접 실행되는 것이 문제입니다. RayJob의 submitter는 원래 entrypoint를 head에 제출하는 역할인데…

이게 진짜 사람 같았다. 중간에 막 정신 못 차리고 이것저것 시도하다가, 잠깐 숨 고르고 “다시 정리해 보자”라고 하는 순간. 나도 노션에 디버깅 기록 쓸 때 똑같이 하는 건데, AI도 이걸 한다.

정리 후에 submitterPodTemplate을 제거하고 설정을 바꿔서 3차 시도를 했다. 그런데 —

또 SUCCEEDED가 너무 빠릅니다. 여전히 문제가 해결되지 않았습니다.

또다. 이쯤 되면 지칠 만도 한데, 바로 head에서 job 로그를 확인하러 들어갔다.

진짜 원인 발견

그리고 드디어 원인을 찾았다.

문제 발견! entrypoint='export NCCL_DEBUG=INFO'

YAML의 >- (folded block scalar) 문법에서 &&로 연결된 커맨드가 잘렸습니다. 첫 번째 export NCCL_DEBUG=INFO만 entrypoint로 전달되고, 나머지 && 이후가 무시되었습니다.

그러니까, YAML 파일의 entrypoint에 여러 명령어를 &&로 연결해 뒀는데, Ray Job Submission이 이걸 쉘로 실행하지 않고 첫 번째 토큰만 명령어로 인식한 것이다. export만 실행되니 바로 성공(exit 0)으로 끝나 버린 것이고. 수정은 전체를 bash -c '...'로 감싸는 것.

이 모든 삽질의 끝에 원인이 bash -c 감싸기 하나였다는 게, 허무할 정도로 클래식하다. 4차 시도, Bash 명령 수십 회, 가설 수정 여러 번 — 그 모든 끝에 따옴표 하나.

RUNNING입니다!”

4차 시도 후, 드디어:

Job status가 RUNNING입니다! 이번에는 제대로 실행 중입니다.

느낌표에 볼드체까지. 나도 맨날 노션에 주절주절 쓰다가 마지막에 되면 “일단 됐다!!!!!” 이런 거 쓰는데, 왜 이렇게 나랑 비슷한지.

이후로는 학습 진행 상황을 모니터링하면서 결과를 수집했다. sleep 120, sleep 180, sleep 300, sleep 600 — 대기 시간을 점점 늘려가면서, 매번 로그를 확인하고 브리핑했다.

데이터 로딩이 진행 중입니다… 기다리겠습니다.

학습이 정상적으로 진행 중이고, 1 epoch이 거의 완료되었습니다!

Validation이 완료되면 MLflow 로깅 → job 종료 순서입니다… 기다리겠습니다.

“이번엔 되겠지?” 하면서 점점 여유를 주는 패턴. 결과 브리핑을 위해 데이터를 모으고 있는 모습이, 영락없이 디버깅 성공 후 뿌듯해하며 결과 정리하는 엔지니어의 모습이었다.

최종적으로 validation 단계에서 공유 디렉토리 문제로 실패했지만, E2E 파이프라인 검증이라는 원래 목적은 달성했다. Training, checkpoint 저장, S3 업로드, MLflow 로깅까지 정상 동작을 확인했고, AI는 단계별 결과를 테이블로 깔끔하게 정리해 줬다.


왜 동질감이 드는가

디버깅이라는 건 결국 이 루프다.

가설 → 실행 → 멘붕 → 다시 가설 → 또 멘붕 → 드디어 발견 → 환호

그리고 AI도 이 루프를 그대로 탔다. 일자리를 빼앗긴다고 위기감을 느껴야 한다고 하는데, 이 순간만큼은 얘가 정말 나 같기도 하고, 동료 같기도 해서, 위기감보다는 동질감이 먼저 들었다.


AI를 무작정 다 믿을 건 아니다

이 모든 과정을 보면 알겠지만, AI도 삽질을 무진장 한다.

4차 시도 만에 겨우 성공했다. 그 사이에 세운 가설 중 상당수는 빗나갔고, “이제 전체 구조가 보입니다”라고 자신 있게 말한 직후에 또 실패하기도 했다. AI가 짜준 코드, AI가 내린 판단을 무조건 믿을 수 없다는 걸 실시간으로 목격한 셈이다.

특히 인상적이었던 건, 최종 원인이 쉘 스크립트의 bash -c 감싸기였다는 점이다. 쉘 스크립트는 AI가 가장 많이 학습하고, 가장 잘 자동화하는 영역 중 하나라고 생각했다. 이런 영역에서조차 AI는 실수한다. AI 코드 부채라는 게 실제로 존재할 수 있다는 걸, 이 로그가 생생하게 보여준다.


그래도 배울 건 있다

비슷한 과정을 밟는 걸 옆에서 관찰했기 때문에, 오히려 내 작업 습관이 더 선명하게 보이기도 했다.

속도

나는 저 모든 과정 — 가설 세우고, 범위 좁히고, 실행해 보고, 결과 수집하고, 원인 파악하고 — 을 며칠씩 걸려 가며 했을 텐데, AI는 한 세션 안에서 끝냈다. 물론 삽질도 한 세션 안에서 다 했지만, 시행착오의 사이클 자체가 빠르다는 건 분명하다.

그리고 지치지도 않는다. 나였으면 3차 시도쯤에서 “오늘은 여기까지” 하고 산책을 갔다 오거나, 러닝을 하거나, 아예 다음 날로 미뤘을 것이다. 이건 배울 수 있는 건 아니지만, 부러운 건 맞다.

이상 감지 능력

사람이든 AI든, 결국 실력의 차이는 “이상한 걸 이상하다고 빨리 감지하는 능력”이 아닐까. SUCCEEDED가 떴는데 30초 만에 끝난 걸 “너무 빠르다”고 바로 캐치한 건, 나였으면 한참 뒤에야 알아챘을 수도 있는 부분이다. 그 감지 능력은 결국 이상 상황을 많이 마주하고 해결해 본 경험에서 나오는 직관, 그리고 로그를 꼼꼼히 보는 습관에서 나온다.

로그

개발을 처음 시작할 때부터 맨날 “로그 잘 봐라, 에러 메시지 잘 봐라”라는 말을 들었고, 그걸 잘하는 분들이 실력 있는 분들이라는 걸 알았다. AI도 똑같다. 무조건 로그를 본다. 매 단계마다 pod 로그를 확인하고, job 상태를 조회하고, MLflow API를 찍어보고. 실력의 핵심은 — AI에게도 만약 실력이라는 게 있다면 — 결국 로그다.


내가 고민해 봐야 할 점

한 가지 아직도 의문인 건, 중간중간 “이제 구조를 파악했다”, “이제 전체 그림이 보인다” 같은 말이 반복적으로 나왔다는 것이다. 작업 계획도 꽤 상세하게 세웠고 컨텍스트도 충분히 줬다고 생각했는데, 왜 AI는 중간에 “뒤늦은 깨달음”을 반복했을까.

돌아보니, AI가 삽질한 지점은 전부 KubeRay의 런타임 동작이었다. submitterPodTemplate이 있을 때 submitter pod에서 entrypoint를 직접 실행하는지, YAML >- 블록이 Ray Job Submission에서 어떻게 파싱되는지, RayJob의 SUCCEEDED가 submitter의 exit code인지 head의 job status인지 — 이건 문서화가 잘 안 되어 있는 내부 구현 디테일이고, 직접 돌려봐야 알 수 있는 것들이다. 내 계획에는 목표와 Phase, 실행 방식, blocking points까지 있었지만, 이런 런타임 동작까지 커버하려면 이 문제를 이미 한 번 겪어봤어야 한다. 그리고 이번 Phase 0-4의 목적 자체가 “직접 체험”이었으니, 어떻게 보면 이런 발견이 나오는 것 자체가 정상이다.

그래도 다음에 개선할 수 있는 부분이 하나 있다면, 프로덕션 환경과 테스트 YAML의 차이점을 사전에 diff로 주입하는 것이다. 실제로 AI가 삽질 중간에 프로덕션의 RayJob을 확인하러 가서 “submitterPodTemplate이 없다”는 걸 발견했는데, 이걸 처음부터 컨텍스트로 줬으면 시도 2~3의 삽질은 줄었을 수 있다.


여담: 토큰은 얼마나 썼을까

이 정도 삽질을 시키면 토큰을 얼마나 쓰는 걸까.

“삽질을 시켰다”고 봐야 할지, 스스로 “삽질을 했다”고 봐야 할지는 모르겠다. 내가 시킨 작업의 일환이고, 내가 작업 컨텍스트를 잘못 줬다면 내가 삽질을 시켰다고 보는 게 맞아 보인다.

궁금해서 세션 종료 전에 /context 명령으로 확인해 봤다.

Context Usage
    Opus 4.6
    124.9k / 200k tokens (62%)

    Estimated usage by category
      Messages (대화 + 도구 결과):  102.5k tokens (51.3%)
      Autocompact buffer:           33k tokens (16.5%)
      Free space:                   42.1k (21.1%)
      System (prompt, tools 등):    15.2k tokens (7.6%)
      기타 (MCP, memory, skills):    6.9k tokens (3.5%)

전체 200K 컨텍스트 윈도우 중 62%를 소진한 상태였다. Messages가 102.5K(51.3%)로 절반 이상을 차지하는데, 매 턴마다 kubectl 출력이 쌓이니 토큰이 빠르게 불어난 것이다. Autocompact buffer를 빼면 실질적 여유는 21% 정도. 세션이 조금만 더 길었으면 autocompact가 발동해서 초반 삽질 기록이 요약되기 시작했을 것이다.

Sonnet이 아니라 Opus 모델이었으니, API 기준으로 커피 한 잔 값은 확실히 넘겼을 것이다. 아슬아슬하게 삽질 기록이 전부 살아있는 상태에서 끝난 게 다행이다 — 덕분에 이 글을 쓸 수 있었으니까.


결론

AI 도구를 “나를 대체할 것”으로 보는 것보다 “나랑 비슷하게 삽질하는 놈”으로 보면, 오히려 무작정 두려워하는 감정이 덜해지기도 하고, 한편으로 결과를 더 제대로 검증하게 되기도 한다.

동질감을 느꼈다는 건, 결국 내가 AI의 작업 과정을 판단할 수 있어야 한다는 뜻이기도 하다. 같은 삽질을 해봤으니까 어디서 잘못 가고 있는지 보여야 한다. AI가 “이제 전체 구조가 보입니다”라고 자신 있게 말할 때 “진짜?” 하고 의심할 수 있어야 하고, 그건 나도 그렇게 말한 직후에 틀려 본 경험이 있어야 가능하다. 그게 공존의 출발점 아닌가 싶다.




hit count

댓글남기기