메인 콘텐츠로 건너뛰기
이 페이지에서는 W&B Sweeps가 시스템 시그널과 프로세스 종료 코드를 처리하는 방식을 자세히 설명합니다. 이를 통해 SLURM, EC2 Spot, Google Cloud 선점형 VM과 같은 선점형 환경에서도 sweeps를 안정적으로 실행할 수 있습니다. 또한 키보드로 run을 깔끔하게 중단하는 방법과, run이 다시 큐에 들어가는 동작을 이해하고 예측하는 데 도움이 되는 세부 정보도 설명합니다. 선점되었을 때 run이 어떻게 다시 큐에 들어가는지에 대한 자세한 내용은 선점형 Sweeps run 재개를 참조하세요.

종료 상태와 시그널

W&B는 트레이닝 프로세스의 종료 상태를 사용해 run을 requeue할지 여부와 run 상태를 어떻게 기록할지 결정합니다. 종료 코드 규약:
  • 종료 코드 0: run이 성공적으로 완료된 것으로 간주되며 requeue되지 않습니다.
  • 0이 아닌 종료 코드: run은 실패했거나 preempt된 것으로 처리됩니다. mark_preempting()을 사용하면 W&B가 run을 requeue하므로, 다른 agent(또는 재시작 후 동일한 agent)가 이를 재개할 수 있습니다.
이는 프로세스가 시그널 핸들러, 예외 또는 명시적인 sys.exit() 호출로 종료되는 경우 모두에 적용됩니다. 이 규약을 이해하고 이를 전제로 동작하도록 구현하는 것은 preemptible 또는 cluster 환경에서 매우 중요합니다. 프로세스가 포착 가능한 시그널로 인해 종료될 때는 핸들러가 실행되어, run을 requeue하려는 경우 wandb.run.mark_preempting()을 호출하고, 정리 작업(예: checkpoint 저장)을 수행한 뒤 0이 아닌 코드로 종료할 수 있습니다. 시그널에 의해 종료될 때의 일반적인 관례는 sys.exit(128 + signum)입니다. W&B는 해당 종료 코드를 기록하며, 동일한 requeue 규칙이 적용됩니다. 프로세스가 운영 체제 커널에 의해 SIGKILL로 종료되면 종료 훅을 실행할 수 없으므로 최종 summary가 기록되지 않으며, run이 crashed 또는 killed로 표시될 수 있습니다. 그래도 agent는 다음 run을 시작합니다.

오래된 run과 서버 측 시간 초과

run이 오랫동안(약 5분 내외) 종료되지도 않고 새 메트릭도 전송하지 않으면, W&B 서버는 해당 run을 crashed로 표시합니다. 이는 트레이닝 프로세스가 멈추거나, logging이 중단되거나, 정상적으로 종료되지 못한 채 종료될 때(예: SIGKILL 이후) 자주 발생합니다. 메트릭을 일정한 간격으로 logging하거나 정의된 종료 코드로 종료하면 run 상태를 실제 발생한 상황과 일치하게 유지하는 데 도움이 됩니다.

처리 가능한 시그널과 선점

트레이닝 스크립트에서 맞춤형 시그널 핸들러를 등록할 수 있습니다. 처리 가능한 시그널이 전달되면 핸들러가 실행되며, 이미 W&B에 전송된 메트릭은 보존됩니다. 또한 agent는 프로세스 종료를 감지하고 다음 run을 시작합니다. 모범 사례:
  • 핸들러는 가능한 한 일찍 등록하세요(예: 기본 트레이닝 루프에 들어가기 전).
  • 핸들러에서는 선점 후 run이 requeue되도록 하려면 wandb.run.mark_preempting()을 호출하고, 정리 작업(예: checkpoint 저장)을 수행한 다음, 0이 아닌 종료 코드로 종료하세요.
다음 예제에서는 SIGUSR1(일반적인 cluster 선점 시그널)과 SIGTERM에 대한 핸들러를 등록합니다. SIGINT는 대화형 사용(예: 터미널에서 수동 취소)을 위해 그대로 둡니다. 핸들러는 wandb.run.mark_preempting()을 호출한 뒤 128 + signum으로 종료합니다:
import signal
import sys
import wandb


def signal_handler(signum, frame):
    if wandb.run is not None:
        # 선택: 모델 checkpoint 저장, 버퍼 플러시 등.
        print(f"Preempted with signal: {signal.Signals(signum).name}.")
        wandb.run.mark_preempting()
    sys.exit(128 + signum)


def train():
    signal.signal(signal.SIGUSR1, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    with wandb.init() as run:
        config = wandb.config
        for epoch in range(100):
            # 트레이닝 step; 필요에 따라 wandb.log(...) 호출
            pass


if __name__ == "__main__":
    train()

SIGKILL (포착 불가)

SIGKILL은 포착하거나 무시할 수 없습니다. 프로세스는 핸들러나 atexit 콜백을 실행할 기회도 없이 즉시 종료됩니다. W&B는 해당 run의 최종 summary를 기록할 수 없습니다. agent는 복구 후에도 sweep을 계속 진행하지만, 해당 run의 데이터는 불완전합니다. SIGKILL은 최후의 수단으로만 사용하세요. 정상 종료가 필요할 때는 SIGTERM 또는 SIGINT를 우선 사용하세요.

agent에서 자식 프로세스로 시그널 전달

wandb agent CLI를 사용하면 agent가 트레이닝 스크립트를 자식 프로세스로 실행합니다. agent를 중단하면(예: Ctrl+C를 누르거나 스케줄러가 작업에 SIGTERM을 보낼 때) 자식 프로세스(트레이닝 프로세스)는 기본적으로 해당 시그널을 받지 못하므로, 트레이닝 스크립트는 핸들러를 실행하거나 mark_preempting()을 호출할 수 없습니다. 이 내용은 GitHub #3667에서 설명합니다. 자식 프로세스가 정상적으로 종료되고 핸들러에서 wandb.run.mark_preempting()을 호출할 수 있게 하려면, CLI agent를 --forward-signals와 함께 실행하세요:
wandb agent --forward-signals entity/project/sweep_ID
Python API의 wandb.agent()에서는 시그널 포워딩이 지원되지 않습니다. 이 방식은 트레이닝 함수를 별도의 자식 프로세스로 실행하지 않고 스레드에서 실행하므로, 동일한 포워딩 동작이 적용되지 않습니다. 포워딩이 활성화된 상태에서 CLI agent가 SIGINT 또는 SIGTERM을 받으면, 해당 시그널을 자식 프로세스에 전달하여 트레이닝 스크립트의 핸들러가 실행될 수 있습니다. 그러면 필요에 따라 wandb.run.mark_preempting()wandb.finish()를 0이 아닌 종료 코드와 함께 호출하고, 0이 아닌 코드로 종료할 수 있습니다. agent 프로세스에서 Ctrl+C를 두 번 누르면 기본적으로 agent는 SIGTERM을 받습니다. --forward-signals를 사용하면 SIGINT를 자식 프로세스에 전달해 핸들러가 실행되도록 할 수 있습니다. 자세한 내용은 wandb agent CLI 레퍼런스를 참조하세요.

SLURM 같은 선점형 클러스터

선점이 발생하면 트레이닝 프로세스가 시그널을 받아 run을 선점 예정 상태로 표시하고, run이 다시 큐에 들어가도록 0이 아닌 코드로 종료해야 합니다. 그러면 새 agent(또는 작업이 다시 큐에 들어간 뒤 동일한 agent)가 run을 재개할 수 있습니다. 트레이닝 프로세스가 시그널을 받도록 하세요.
  1. 스케줄러가 agent에 시그널을 보내는 경우: wandb agent --forward-signals로 agent를 실행하세요. 그러면 스케줄러(또는 사용자)가 agent에 시그널을 보낼 때 agent가 해당 시그널을 자식 프로세스로 전달합니다. 그러면 자식 프로세스의 핸들러에서 wandb.run.mark_preempting(), wandb.finish(exit_code=...)를 0이 아닌 코드와 함께 호출하고, sys.exit(128 + signum)(또는 다른 0이 아닌 종료 코드)을 실행할 수 있습니다.
  2. 스케줄러가 시작 스크립트에 시그널을 보내는 경우(agent에 직접 보내지 않음): 시작 스크립트가 선점 시그널을 트레이닝 프로세스에 직접 보내도록 하세요. 예를 들어 트레이닝 스크립트가 자신의 프로세스 ID를 파일에 기록하고, 시작 스크립트는 클러스터 시그널(예: SIGUSR1)를 트랩한 뒤 kill -SIGUSR1 $(cat $PID_FILE)를 실행해 트레이닝 프로세스의 핸들러가 실행되도록 합니다.
트레이닝 스크립트에서: 클러스터에서 사용하는 시그널(예: SIGTERM 또는 SIGUSR1)에 대한 핸들러를 등록하세요. 핸들러에서 활성 run이 있으면 wandb.run.mark_preempting()를 호출한 다음, run이 다시 큐에 들어가도록 0이 아닌 종료 코드로 run을 종료하고 sys.exit(128 + signum)(또는 다른 0이 아닌 코드)을 호출하세요. run이 언제 requeue되는지와 이것이 mark_preempting()과 어떻게 상호작용하는지는 Resume preemptible Sweeps runs을 참조하세요. Sweep 상태: agent를 시작하기 전에 wandb sweep entity/project/sweep_ID --resume를 실행해 sweep이 재개 모드가 되도록 하고, 다시 큐에 들어간 run을 할당하도록 하세요. 다중 agent 조정: 많은 agent가 동시에 실행되면(SLURM 배열 작업 등) 동일한 선점된 run을 먼저 차지하려고 경합할 수 있습니다. 이는 제한 사항입니다. 이 잠재적 문제를 완화하려면 agent 시작 시점을 분산하거나 잠금 같은 외부 조정 메커니즘을 사용하세요.

wandb sweep --cancel

OS 시그널이 아니라 W&B API를 사용해 sweep을 취소합니다. wandb sweep --cancel entity/project/sweep_ID와 같은 command를 실행하세요. 서버가 agent에 종료하라고 지시하면, agent는 실행 중인 하위 프로세스를 종료한 뒤 중지합니다. 취소가 실제로 적용되기까지는 짧은 지연이 있을 수 있습니다(대략 agent의 API 폴링 간격 기준). 취소하면 run에 **SIGKILL**이 전달됩니다. 하위 프로세스는 사용자 정의 시그널 핸들러를 실행할 기회가 없습니다. Sweeps UI에서 Cancel 컨트롤을 사용할 때도 동일합니다. 전체 sweep을 중지하고 취소된 것으로 표시하려면 --cancel을 사용하세요. 현재 run을 정상적으로 종료하려면 run에 포착 가능한 시그널을 보내세요(또는 CLI agent와 함께 --forward-signals를 사용한 후 agent에 시그널을 보내세요). sweep을 정상적으로 완료하려면 --cancel 대신 wandb sweep --stop을 사용하세요. 일시 중지, 재개, 중지, 취소 옵션은 Manage sweeps를 참조하세요.

agent 종료와 run 종료의 차이

agent 프로세스에 시그널을 보내면(자식 트레이닝 프로세스가 아니라) agent는 종료되더라도 자식 프로세스는 고아 프로세스로 계속 실행될 수 있습니다. 이 고아 프로세스는 터미널에 계속 출력을 남길 수 있으며, Enter를 누르기 전까지 셸에 새 프롬프트가 표시되지 않을 수 있습니다. CLI agent에서 --forward-signals를 사용하지 않는 한, agent를 중지해도 자식 트레이닝 프로세스까지 중지된다고 보장할 수는 없습니다. agent가 종료되었는지 확인하려면 프롬프트가 나타나는지만 보고 판단하지 말고, ps -p <agent_pid> 또는 pgrep -f "wandb agent" 같은 OS 명령을 사용하세요.

레퍼런스: mark_preempting() 및 최종 run 상태

아래 표는 mark_preempting()언제 호출하는지와 프로세스가 어떻게 종료되는지에 따라 run 상태가 어떻게 달라지는지 요약한 것입니다. 여기서는 트레이닝 프로그램을 서브프로세스로 사용하는 wandb agent CLI를 사용한다고 가정합니다.
시나리오mark_preempting() 호출 안 함시그널 핸들러가 mark_preempting()을 호출하고 0이 아닌 값으로 종료init() 직후 항상 mark_preempting() 호출
종료 코드 0으로 run이 정상 완료됨FINISHEDFINISHEDFINISHED
run이 0이 아닌 종료 코드로 실패함FAILEDFAILEDPREEMPTED
run이 SIGKILL을 받음약 5분 후 CRASHED약 5분 후 CRASHED(포착 불가)약 5분 후 PREEMPTED
run이 SIGINT를 받음KILLEDPREEMPTED(SIGINT 핸들러가 있는 경우)PREEMPTED
run이 다른 시그널(예: SIGTERM 또는 SIGUSR1)을 받음약 5분 후 CRASHEDPREEMPTED(해당 시그널 핸들러가 있는 경우)약 5분 후 PREEMPTED
시그널 핸들러 안에서만 mark_preempting()을 호출하면, SIGKILL처럼 핸들러가 전혀 실행되지 않는 경우는 처리할 수 없습니다. wandb.init() 직후 항상 mark_preempting()을 호출하면 모든 실패가 선점으로 처리될 수 있으므로, bug나 잘못된 설정 때문인 경우까지 포함해 run이 반복적으로 다시 큐에 들어갈 수 있습니다. 선점 시그널이 명확하게 정의된 환경에서는 일반적으로 init() 후 무조건 호출하기보다, mark_preempting()을 호출한 뒤 0이 아닌 값으로 종료하는 시그널 핸들러 방식을 사용합니다.