Tech_News

Discovery Loop: '실험 루프 자동화'를 인프라 엔지니어 시선으로 뜯어보기

TeEm0 2026. 8. 7. 09:00
728x90

어느 날 아침 Hacker News 상단에 스크롤 애니메이션만 잔뜩 있는 랜딩 페이지 하나가 올라왔다. Discovery Loop. 제품도, 문서도, 가격표도 없다. 있는 건 "과학적 발견은 병목에 걸려 있다"는 문장과, 창업 멤버 네 명의 이름(Jeff Dean, Sanjay Ghemawat, Quoc Le, Oriol Vinyals), 그리고 채용 링크뿐이다.

MapReduce·GFS·BigTable·Spanner·TensorFlow·TPU를 만든 사람들이 "실험 루프를 자동화하겠다"고 하면, 그건 연구 선언문이 아니라 인프라 선언문으로 읽어야 한다. 이 글은 그 관점에서 쓴다. 우리가 이미 사내에서 굴리고 있는 실험 파이프라인과 뭐가 같고, 뭐가 다르고, 스케일을 올릴 때 실제로 어디서 터지는지.

1. 왜 지금 이게 화제인가

원문의 문제 정의는 간결하다. 과학적 방법론 자체는 훌륭한데, 실행 단계가 반복 노동이라는 것.

you propose an experiment, implement and run it, examine the results, then iterate to refine your approach.

실험 제안 → 구현/실행 → 결과 확인 → 개선. 이거 어디서 많이 본 루프다. 우리가 매일 하는 것이기도 하다. 코드 푸시 → CI 빌드 → 테스트 리포트 → 수정. 다른 점은 CI는 이미 자동화됐는데, ML 연구나 과학 실험의 루프는 아직 사람이 "다음에 뭘 해볼까"를 판단하고 손으로 잡을 던진다는 것.

Discovery Loop가 하겠다는 건 그 판단 단계까지 프론티어 모델에 맡기고, 대규모 컴퓨트로 수천 개 실험을 동시에 굴려 반복 주기를 압축하는 것이다. 첫 타깃도 명시돼 있다: ML 리서치/엔지니어링 자체를 먼저 자동화하고, 그걸로 자기네 스택을 최적화한다(act as our own first customer). 그다음이 NAE Grand Challenges — 신약, 헬스 인포매틱스, 태양광 경제성, 깨끗한 물, 사이버스페이스 보안.

참고로 사이트에는 투자 규모, 출시 일정, 벤치마크 수치 같은 건 일절 없다. 지금 시점에서 확인 가능한 건 "미션 + 팀 + 채용"이 전부고, 그 이상은 추측이다. 그러니 여기서는 기술 주장 대신 이 구조를 실제로 만들면 어디가 아픈지를 이야기하는 게 낫다.

2. 핵심 원리: 결국 상태 머신 + 스케줄러 + 평가 하네스

"AI가 실험을 자동으로 설계한다"는 말은 멋있지만, 구현체로 내려오면 놀랄 만큼 익숙한 세 조각이다.

  • 제안기(proposer): 다음에 시도할 설정을 만든다. 하이퍼파라미터든, 코드 패치든, 아키텍처든. 기존에는 베이지안 최적화나 사람이었고, 이제는 LLM이 들어간다.
  • 실행기(runner): 그 설정을 격리된 환경에서 돌린다. 컨테이너, GPU 할당, 타임아웃, 재시도. 딱 잡 스케줄러 영역.
  • 평가기(scorer): 결과를 기계가 읽을 수 있는 숫자로 만든다. 원문이 "learning loop with measurable outcomes"라고 못 박은 이유가 여기 있다. 측정이 안 되면 루프가 안 돈다.

가장 단순한 형태를 코드로 쓰면 이렇다. 실전 파이프라인의 골격도 사실 이것과 크게 다르지 않다(상태 저장을 sqlite로 두는 게 포인트 — 재시작이 공짜가 된다).

import json, sqlite3, subprocess

db = sqlite3.connect("loop.db")
db.execute("CREATE TABLE IF NOT EXISTS trial(id INTEGER PRIMARY KEY, cfg TEXT, score REAL)")

def propose(history):            # LLM이든 optuna든 여기만 갈아끼운다
    best = max(history, key=lambda h: h[1], default=({"lr": 1e-3}, 0))[0]
    return {"lr": best["lr"] * 0.5}

def run_and_score(cfg):
    out = subprocess.run(["python", "train.py", "--lr", str(cfg["lr"])],
                         capture_output=True, text=True, timeout=1800)
    return json.loads(out.stdout.strip().splitlines()[-1])["val_acc"]

hist = [(json.loads(c), s) for c, s in db.execute("SELECT cfg, score FROM trial")]
for _ in range(20):
    cfg = propose(hist)
    score = run_and_score(cfg)
    db.execute("INSERT INTO trial(cfg, score) VALUES (?,?)", (json.dumps(cfg), score))
    db.commit()
    hist.append((cfg, score))
    print(f"cfg={cfg} score={score:.4f}")
$ python loop.py
cfg={'lr': 0.0005} score=0.8123
cfg={'lr': 0.00025} score=0.8340
cfg={'lr': 0.000125} score=0.8291

여기서 for _ in range(20)을 순차 실행이 아니라 수천 개 병렬로 바꾸고, propose를 프론티어 모델로 바꾸고, run_and_score를 클러스터 잡 제출로 바꾸면 — 원문이 말하는 그림이 된다. 어려운 건 아이디어가 아니라 병렬도를 3자리수로 올렸을 때 무너지는 주변부다.

3. 실무 관점: 병렬도를 올리면 어디가 먼저 터지나

(1) 스케줄링 — GPU는 CPU처럼 오버커밋되지 않는다

사내에서 튜닝 잡 200개를 한 번에 던지면 제일 먼저 만나는 화면이다.

$ kubectl describe pod trial-0147
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  3m    default-scheduler  0/8 nodes are available: 8 Insufficient nvidia.com/gpu.
           preemption: 0/8 nodes are available: 8 No preemption victims found for incoming pod.

Pending이 수백 개 쌓이면 스케줄러 큐가 지저분해지고, 팀 동료의 대화형 노트북 잡까지 밀린다. 대응은 셋 중 하나다.

  • 큐 자체를 분리한다. Kueue / Volcano / Slurm 파티션으로 "탐색용 저우선순위 큐"를 따로 두고 PriorityClass에 preemption을 허용한다.
  • 동시 실행 상한을 루프 쪽에서 건다. Argo Workflows의 parallelism, Slurm array의 %N 같은 것. 큐에 던지는 개수와 실제 도는 개수를 분리하는 게 핵심이다.
  • 실험 하나가 GPU를 다 안 쓰면 MIG나 time-slicing으로 쪼갠다. 다만 스루풋 측정값이 흔들리므로 성능 비교 실험에는 쓰지 않는다.
# Slurm array로 동시 16개만 유지하며 512개 trial 굴리기
$ sbatch --array=1-512%16 --gres=gpu:1 --time=00:30:00 trial.sbatch
Submitted batch job 918234

$ squeue -u $USER -h -o "%t" | sort | uniq -c
     16 R
    496 PD

(2) 아티팩트 스토리지와 메타데이터 DB

trial 하나가 체크포인트·로그·텐서보드 이벤트를 남긴다. 1000개면 그게 1000배다. 오브젝트 스토리지에 초당 수백 개 PUT을 때리면 이런 걸 본다.

botocore.exceptions.ClientError: An error occurred (SlowDown) when calling the
PutObject operation (reached max retries: 4): Please reduce your request rate.

실전 처방: ① 작은 파일 다발을 tar로 묶어 한 번에 올린다, ② 프리픽스에 해시를 넣어 샤딩한다, ③ 중간 체크포인트는 기본적으로 버린다(상위 N개 trial만 재현용으로 보존). 그리고 MLflow/W&B 같은 트래킹 서버의 DB도 병목이 된다 — 메트릭을 스텝마다 동기 전송하면 트래킹 서버가 먼저 죽는다. 배치 로깅으로 바꾸는 게 첫 수순이다.

(3) 평가가 흔들리면 루프가 거짓말을 학습한다

개인적으로 가장 비싸게 배운 부분. 자동화 루프는 "점수를 올리는 방향"으로만 움직이는데, 그 점수가 오염돼 있으면 루프는 오염을 최적화한다. 실제로 겪은 패턴들:

  • 테스트셋 누수. 제안기가 데이터 전처리 코드까지 수정할 권한을 갖는 순간, 검증셋이 학습셋에 섞이는 변경을 "성능 개선"으로 발견해버린다.
  • 노이즈보다 작은 개선. 시드 하나로 측정하면 ±0.5%p 흔들리는 지표에서 0.2%p 개선을 진짜라고 믿는다. 최소 3시드 중앙값, 아니면 이전 최고 기록과의 차이가 표준편차를 넘을 때만 채택하는 게이트가 필요하다.
  • 타임아웃 = 실패 = 낮은 점수 처리. 클러스터가 붐빌 때 죽은 잡이 "나쁜 설정"으로 기록되면, 제안기는 클러스터 혼잡도를 학습한다. 인프라 실패와 실험 실패는 반드시 다른 상태 코드로 구분해야 한다.

(4) 코드 생성이 들어올 때의 격리

LLM이 제안기 자리에 앉으면 제안 내용이 하이퍼파라미터가 아니라 실행 가능한 패치가 된다. 그 순간 이건 MLOps 문제가 아니라 보안 문제다. 네트워크 egress 차단(패키지 미러만 허용), 읽기 전용 데이터 마운트, seccomp 프로파일, 잡별 서비스 어카운트 토큰 비활성화 — CI 러너에 이미 적용해둔 규칙을 그대로 가져오면 된다. 클라우드 크리덴셜이 붙은 노드에서 생성 코드를 그냥 돌리는 건, 인터넷에서 받은 스크립트를 프로덕션에서 sudo로 실행하는 것과 같다.

(5) 지금 당장 쓸 수 있는 대안

Discovery Loop는 아직 쓸 수 있는 제품이 없다. 반면 루프 자동화의 80%는 이미 오픈소스로 가능하다. 탐색 알고리즘은 Optuna/Ray Tune, 워크플로 오케스트레이션은 Argo Workflows나 Slurm, 추적은 MLflow. 여기에 "제안기만 LLM으로 교체"하는 게 현실적인 진입 경로다. 순서를 바꾸면(LLM 먼저 붙이고 인프라 나중) 십중팔구 실패한다 — 평가 하네스와 잡 스케줄링이 안정적이지 않은 상태에서 제안 속도만 올리면, 쓰레기를 더 빨리 만들 뿐이다.

4. 정리

한 줄 요약: Discovery Loop는 "실험의 제안–실행–평가–반영 루프를 사람 손에서 떼어내겠다"는 미션 선언이고, 그 승부처는 모델이 아니라 스케줄러·스토리지·평가 하네스라는 인프라 쪽에 있다.

누가 언제 봐야 하나로 정리하면:

  • 지금 당장 할 것: 실험이 하루 열 개 이상 돌아가는 팀이라면, 측정 가능한 단일 스칼라 지표와 재현 가능한 실행 단위(컨테이너 + 고정 시드 + 잠긴 의존성)를 먼저 만들어라. 이게 없으면 어떤 자동화도 못 붙인다.
  • 주시만 할 것: 제품이 없으니 도입 검토 대상이 아니다. 다만 채용 공고와 향후 공개 자료를 보면 "그들이 실험 루프를 어떤 인프라로 감싸는지"에 대한 힌트가 나올 가능성이 있다. 그건 우리 파이프라인 설계에 그대로 쓸 수 있는 정보다.
  • 경계할 것: 자동화 루프의 결과를 사람이 검증 없이 머지하는 관행. 루프는 지표를 올리지, 문제를 이해하지 않는다.

MapReduce가 "분산 처리를 map/reduce 두 함수로 추상화"해서 판을 바꿨듯이, 이번에도 "실험을 propose/run/score 세 함수로 추상화"하는 그림으로 보인다. 그 추상화가 실제로 어디까지 유효한지는 공개된 결과물이 나와야 판단할 수 있다.

참고 자료

728x90