# ADR-0009: 커널 실행 메시징 및 완료 시맨틱

## Status

Accepted

## Context

커널 실행은 호스트에서 시작되어 디바이스 측 제어 컴포넌트를 통해 진행된다:

Host → IO_CPU → M_CPU → PE_CPU → 스케줄러 → 엔진

완료는 역방향으로 전파된다.

벤치마크를 단순하고 토폴로지에 비의존적으로 유지하기 위해, 커널 실행은
엔드포인트 기반(endpoint-driven)이어야 하며 완료 집계는 결정론적이어야 한다.

---

## Decision

### D1. 커널 런치는 엔드포인트 요청이다

커널 런치는 IO_CPU 엔드포인트에 단일 KernelLaunch 요청을 제출함으로써
시작된다.

runtime API는 반드시:

- 커널 런치 요청을 구성하고,
- 이를 IO_CPU로 제출하며,
- 단일 완료 결과를 대기해야 한다.

runtime API는 내부 팬아웃(fan-out)을 직접 조율해서는 안 된다.

---

### D2. 텐서 인자는 메타데이터로 전달된다

KernelLaunch 요청은 텐서 인자를 다음을 통해 참조해야 한다:

- 호스트가 소유한 텐서 핸들, 또는
- 그러한 핸들로부터 해석된 디바이스 주소 맵.

대용량 텐서 데이터는 커널 런치 메시지에 임베드되어서는 안 된다.

---

### D3. 팬아웃과 집계는 컴포넌트의 책임이다

- IO_CPU는 작업을 M_CPU들에게 팬아웃한다.
- M_CPU는 작업을 PE_CPU들에게 팬아웃한다.
- PE_CPU는 커널 실행과 엔진 디스패치를 관리한다.

완료 시맨틱:

- M_CPU는 대상 PE들이 모두 완료되거나 실패 정책이 트리거되면 완료된다.
- IO_CPU는 대상 큐브들이 모두 완료되거나 실패 정책이 트리거되면 완료된다.

---

### D4. 완료 및 실패 전파

- 모든 메시지는 correlation ID를 포함해야 한다.
- 완료와 실패는 호스트로 결정론적으로 전파되어야 한다.
- 시뮬레이션 엔진은 완료를 관찰할 수 있는 future/handle을 제공한다.

---

### D5. 런치 타이밍은 엔드포인트 동기화된다

단일 커널 런치가 지정한 모든 PE는 런치 진입점으로부터의 디스패치 경로 길이와
무관하게, 동일한 시뮬레이션 시각에 커널 본문 실행을 시작해야 한다.

근거. 디스패치 트리 Host → IO_CPU → M_CPU → PE_CPU는 모든 레벨에서 가변
레이턴시를 가진다. M_CPU에 가까운 PE는 멀리 있는 PE보다 런치를 더 일찍
수신하고, IO_CPU에 가까운 큐브는 먼 큐브보다 더 일찍 수신한다. 동기화가
없으면 각 PE의 커널은 서로 다른 `env.now`에서 시작되어, `pe_exec_ns`와 같은
PE별 메트릭이 커널 자체의 동작이 아니라 디스패치 경로 기하 구조의 함수가
된다 — 그 결과 커널 내부 대기(예: 큐브 간 또는 SIP 간 홉에서의 `tl.recv`)를
타이밍하는 벤치마크에서 측정 아티팩트가 발생한다.

메커니즘.

- `KernelLaunchMsg`는 선택적 `target_start_ns: float | None`을 포함한다.
- **IO_CPU**가 정식 스탬프 주체이다. M_CPU들로 팬아웃할 때, 모든 대상
  (sip, cube, pe) 튜플에 대한 **두 단계 디스패치 체인**의 최대값을
  `max_latency`로 하여 `target_start_ns = env.now + max_latency`를
  계산한다:

  ```
  max_latency(sip, cube, pe) =
      compute_path_latency_ns(find_node_path(io_cpu, m_cpu(sip, cube)))
    + compute_path_latency_ns(find_node_path(m_cpu(sip, cube), pe_cpu))
    - io_cpu.overhead_ns
    - m_cpu.overhead_ns
  ```

  이는 실제 디스패치를 **두 개의 순차적 Transaction**(IO_CPU → M_CPU,
  그리고 M_CPU → PE_CPU)으로 모델링한다. 각 구간의
  `compute_path_latency_ns`는 양 끝점의 `overhead_ns`를 더하는데,
  `io_cpu.overhead_ns`는 이 메서드가 실행되기 전 IO_CPU가 이미 지불했으므로
  차감하고, `m_cpu.overhead_ns`는 구간1의 끝점인 동시에 구간2의 시작점으로
  나타나지만 런타임에는 한 번만 지불되므로 한 번 차감한다. 단일
  `find_node_path(io_cpu, pe_cpu)` 순회는 **동등하지 않다** — M_CPU를
  우회하는 그래프 경로를 선택할 수 있어 먼 큐브에 대해 예측값이 조용히
  과소평가되며, D5 불변식을 위반하게 된다.

  팬아웃된 하위 Transaction은 `KernelLaunchMsg`에 대해
  **`nbytes = 0`**을 운반한다(제어 메시지에 한함). 이를 적용하지 않으면
  큰 커널 런치 페이로드가 공유되는 첫 홉의 패브릭 대역폭을 점유하여
  큐브별 디스패치를 직렬화하고, 먼 M_CPU들이 `target_start_ns`를
  지나가게 되어 늦은 도착 위반이 다시 발생한다.
- **M_CPU**는 이미 스탬프된 `target_start_ns`를 변경 없이 그대로 전달한다.
  값이 없는 경우(예: M_CPU로 직접 런치하는 단위 테스트)에만 M_CPU가 큐브별
  배리어 `env.now + max(로컬 명령 경로 레이턴시)`를 계산한다.
- **PE_CPU**는 `_execute_kernel`의 최상단에서 `pe_exec_start`를 기록하고
  커널 본문을 호출하기 전에 `env.timeout(target_start_ns - env.now)`를
  yield한다.
- `target_start_ns is None`인 경우 PE_CPU는 레거시 비동기 동작으로 빠진다
  — 하위 호환성을 보존한다.

IO_CPU 레벨 스탬핑은 모든 대상 큐브의 모든 PE가 동일한 배리어 시뮬레이션
시각을 사용하도록 보장하여, 큐브 내 디스패치 오프셋 아티팩트와 다중 큐브
런치에서의 큐브 간 오프셋 아티팩트를 모두 제거한다. 실제 하드웨어의
타이밍 브로드캐스트 런치(레이턴시 등화 디스패치 트리)를 모델링한다.

이 동기화는 엔진 / IO_CPU / M_CPU / PE_CPU 제어 평면 내부에서 수행된다 —
runtime API와 애플리케이션 커널은 변경되지 않는다.

---

## Links

- SPEC R1, R2, R7, R8
- ADR-0007 (Runtime API 경계)
- ADR-0008 (텐서 배치)
- ADR-0013 (검증 전략 — V2 팬아웃 테스트)
- ADR-0015 D4 (커널 런치의 구체적 패브릭 경로)
