> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wandb.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> W&B Sweeps가 UNIX 시그널, 종료 코드, sweep run의 선점을 어떻게 처리하는지 알아보세요.

# 시그널 처리와 sweep run

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

<div id="exit-status-and-signals">
  ## 종료 상태와 시그널
</div>

W\&B는 트레이닝 프로세스의 종료 상태를 사용해 run을 requeue할지 여부와 run 상태를 어떻게 기록할지 결정합니다.

**종료 코드 규약:**

* **종료 코드 0**: run이 성공적으로 완료된 것으로 간주되며 requeue되지 않습니다.
* **0이 아닌 종료 코드**: run은 실패했거나 preempt된 것으로 처리됩니다. [`mark_preempting()`](/ko/models/ref/python/experiments/run#mark_preempting)을 사용하면 W\&B가 run을 requeue하므로, 다른 agent(또는 재시작 후 동일한 agent)가 이를 재개할 수 있습니다.

이는 프로세스가 시그널 핸들러, 예외 또는 명시적인 `sys.exit()` 호출로 종료되는 경우 모두에 적용됩니다. 이 규약을 이해하고 이를 전제로 동작하도록 구현하는 것은 preemptible 또는 cluster 환경에서 매우 중요합니다.

프로세스가 [**포착 가능한** 시그널](#catchable-signals-and-preemption)로 인해 종료될 때는 핸들러가 실행되어, run을 requeue하려는 경우 [`wandb.run.mark_preempting()`](/ko/models/ref/python/experiments/run#mark_preempting)을 호출하고, 정리 작업(예: checkpoint 저장)을 수행한 뒤 0이 아닌 코드로 종료할 수 있습니다. 시그널에 의해 종료될 때의 일반적인 관례는 `sys.exit(128 + signum)`입니다. W\&B는 해당 종료 코드를 기록하며, 동일한 [requeue 규칙](/ko/models/runs/resuming#resume-preemptible-sweeps-runs)이 적용됩니다. 프로세스가 운영 체제 커널에 의해 [**`SIGKILL`**](#sigkill-uncatchable)로 종료되면 종료 훅을 실행할 수 없으므로 최종 summary가 기록되지 않으며, run이 crashed 또는 killed로 표시될 수 있습니다. 그래도 agent는 다음 run을 시작합니다.

<div id="stale-runs-and-server-side-timeouts">
  ## 오래된 run과 서버 측 시간 초과
</div>

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

<div id="catchable-signals-and-preemption">
  ## 포착 가능한 시그널과 선점
</div>

트레이닝 스크립트에서 맞춤형 시그널 핸들러를 등록할 수 있습니다. 포착 가능한 시그널이 전달되면 핸들러가 실행되며, 이미 W\&B에 전송된 메트릭은 보존됩니다. 또한 agent는 프로세스 종료를 감지하고 다음 run을 시작합니다.

**모범 사례:**

* 핸들러는 가능한 한 일찍 등록하세요(예: 기본 트레이닝 루프에 들어가기 전).
* 핸들러에서는 선점 후 run이 requeue되도록 하려면 [`wandb.run.mark_preempting()`](/ko/models/ref/python/experiments/run#mark_preempting)을 호출하고, 정리 작업(예: checkpoint 저장)을 수행한 다음, 0이 아닌 종료 코드로 종료하세요.

다음 예제에서는 `SIGUSR1`(일반적인 클러스터 선점 시그널)과 `SIGTERM`에 대한 핸들러를 등록합니다. `SIGINT`는 대화형 사용(예: 터미널에서 수동 취소)을 위해 그대로 둡니다. 핸들러는 `wandb.run.mark_preempting()`을 호출한 뒤 `128 + signum`으로 종료합니다:

```python theme={null}
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):
            # 트레이닝 단계; 필요에 따라 wandb.log(...) 호출
            pass


if __name__ == "__main__":
    train()
```

<div id="sigkill-uncatchable">
  ## `SIGKILL` (포착 불가)
</div>

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

<div id="forwarding-signals-from-agent-to-child">
  ## agent에서 자식 프로세스로 시그널 전달
</div>

[`wandb agent`](/ko/models/ref/cli/wandb-agent) CLI를 사용하면 agent가 트레이닝 스크립트를 **자식 프로세스**로 실행합니다. **agent**를 중단하면(예: Ctrl+C를 누르거나 스케줄러가 작업에 `SIGTERM`을 보낼 때) **자식 프로세스**(트레이닝 프로세스)는 기본적으로 해당 시그널을 받지 못하므로, 트레이닝 스크립트는 핸들러를 실행하거나 `mark_preempting()`을 호출할 수 없습니다. 이 내용은 [GitHub #3667](https://github.com/wandb/wandb/issues/3667)에서 설명합니다.

자식 프로세스가 정상적으로 종료되고 핸들러에서 `wandb.run.mark_preempting()`을 호출할 수 있게 하려면, CLI agent를 `--forward-signals`와 함께 실행하세요:

```bash theme={null}
wandb agent --forward-signals entity/project/sweep_ID
```

Python API의 [`wandb.agent()`](/ko/models/ref/python/functions/agent)에서는 시그널 포워딩이 **지원되지 않습니다**. 이 방식은 트레이닝 함수를 별도의 자식 프로세스로 실행하지 않고 스레드에서 실행하므로, 동일한 포워딩 동작이 적용되지 않습니다.

포워딩이 활성화된 상태에서 CLI agent가 `SIGINT` 또는 `SIGTERM`을 받으면, 해당 시그널을 자식 프로세스에 전달하여 트레이닝 스크립트의 핸들러가 실행될 수 있습니다. 그러면 필요에 따라 `wandb.run.mark_preempting()` 및 [`wandb.finish()`](/ko/models/ref/python/experiments/run#finish)를 0이 아닌 종료 코드와 함께 호출하고, 0이 아닌 코드로 종료할 수 있습니다. agent 프로세스에서 Ctrl+C를 두 번 누르면 기본적으로 agent는 `SIGTERM`을 받습니다. `--forward-signals`를 사용하면 `SIGINT`를 자식 프로세스에 전달해 핸들러가 실행되도록 할 수 있습니다.

자세한 내용은 [wandb agent](/ko/models/ref/cli/wandb-agent) CLI 레퍼런스를 참조하세요.

<div id="preemptible-clusters-like-slurm">
  ## `SLURM` 같은 선점형 클러스터
</div>

선점이 발생하면 **트레이닝 프로세스**가 시그널을 받아 run을 선점 예정 상태로 표시하고, run이 다시 큐에 들어가도록 0이 아닌 코드로 종료해야 합니다. 그러면 새 agent(또는 작업이 다시 큐에 들어간 뒤 동일한 agent)가 run을 재개할 수 있습니다.

**트레이닝 프로세스가 시그널을 받도록 하세요.**

1. **스케줄러가 agent에 시그널을 보내는 경우**: `wandb agent --forward-signals`로 agent를 실행하세요. 그러면 스케줄러(또는 사용자)가 agent에 시그널을 보낼 때 agent가 해당 시그널을 자식 프로세스로 전달합니다. 그러면 자식 프로세스의 핸들러에서 `wandb.run.mark_preempting()`, [`wandb.finish(exit_code=...)`](/ko/models/ref/python/experiments/run#finish)를 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](/ko/models/runs/resuming#resume-preemptible-sweeps-runs)을 참조하세요.

**Sweep 상태:** agent를 시작하기 전에 `wandb sweep entity/project/sweep_ID --resume`를 실행해 sweep이 재개 모드가 되도록 하고, 다시 큐에 들어간 run을 할당하도록 하세요.

**다중 agent 조정:** 많은 agent가 동시에 실행되면(SLURM 배열 작업 등) 동일한 선점된 run을 먼저 차지하려고 경합할 수 있습니다. 이는 제한 사항입니다. 이 잠재적 문제를 완화하려면 agent 시작 시점을 분산하거나 잠금 같은 외부 조정 메커니즘을 사용하세요.

여러 GPU를 사용하는 SLURM 작업에서 하나의 프로세스만 `wandb.agent()`를 호출해야 한다면 [How should I run sweeps on SLURM?](/ko/support/models/articles/how-should-i-run-sweeps-on-slurm)을 참조하세요.

<div id="wandb-sweep-cancel">
  ## `wandb sweep --cancel`
</div>

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`](/ko/models/sweeps/pause-resume-and-cancel-sweeps#stop-a-sweep)을 사용하세요.

일시 중지, 재개, 중지, 취소 옵션은 [Manage sweeps](/ko/models/sweeps/pause-resume-and-cancel-sweeps)를 참조하세요.

<div id="killing-the-agent-vs-the-run">
  ## agent 종료와 run 종료의 차이
</div>

**agent** 프로세스에 시그널을 보내면(자식 트레이닝 프로세스가 아니라) agent는 종료되더라도 자식 프로세스는 고아 프로세스로 계속 실행될 수 있습니다. 이 고아 프로세스는 터미널에 계속 출력을 남길 수 있으며, Enter를 누르기 전까지 셸에 새 프롬프트가 표시되지 않을 수 있습니다.

CLI agent에서 `--forward-signals`를 사용하지 않는 한, agent를 중지해도 자식 트레이닝 프로세스까지 중지된다고 보장할 수는 없습니다.

agent가 종료되었는지 확인하려면 프롬프트가 나타나는지만 보고 판단하지 말고, `ps -p <agent_pid>` 또는 `pgrep -f "wandb agent"` 같은 OS 명령을 사용하세요.

<div id="reference-mark_preempting-and-final-run-state">
  ## 레퍼런스: `mark_preempting()` 및 최종 run 상태
</div>

아래 표는 `mark_preempting()`을 **언제** 호출하는지와 프로세스가 어떻게 종료되는지에 따라 run 상태가 어떻게 달라지는지 요약한 것입니다. 여기서는 트레이닝 프로그램을 서브프로세스로 사용하는 [`wandb agent`](/ko/models/ref/cli/wandb-agent) CLI를 사용한다고 가정합니다.

| 시나리오                                       | `mark_preempting()` 호출 안 함 | 시그널 핸들러가 `mark_preempting()`을 호출하고 0이 아닌 값으로 종료 | `init()` 직후 항상 `mark_preempting()` 호출 |
| ------------------------------------------ | -------------------------- | ----------------------------------------------- | ------------------------------------- |
| 종료 코드 0으로 run이 정상 완료됨                      | FINISHED                   | FINISHED                                        | FINISHED                              |
| run이 0이 아닌 종료 코드로 실패함                      | FAILED                     | FAILED                                          | PREEMPTED                             |
| run이 `SIGKILL`을 받음                         | 약 5분 후 CRASHED             | 약 5분 후 CRASHED(포착 불가)                           | 약 5분 후 PREEMPTED                      |
| run이 `SIGINT`를 받음                          | KILLED                     | PREEMPTED(`SIGINT` 핸들러가 있는 경우)                  | PREEMPTED                             |
| run이 다른 시그널(예: `SIGTERM` 또는 `SIGUSR1`)을 받음 | 약 5분 후 CRASHED             | PREEMPTED(해당 시그널 핸들러가 있는 경우)                    | 약 5분 후 PREEMPTED                      |

시그널 핸들러 안에서만 `mark_preempting()`을 호출하면, `SIGKILL`처럼 핸들러가 전혀 실행되지 않는 경우는 처리할 수 없습니다.

`wandb.init()` 직후 항상 `mark_preempting()`을 호출하면 모든 실패가 선점으로 처리될 수 있으므로, bug나 잘못된 설정 때문인 경우까지 포함해 run이 반복적으로 다시 큐에 들어갈 수 있습니다.

선점 시그널이 명확하게 정의된 환경에서는 일반적으로 `init()` 후 무조건 호출하기보다, `mark_preempting()`을 호출한 뒤 0이 아닌 값으로 종료하는 **시그널 핸들러** 방식을 사용합니다.
