# 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 (커널 런치의 구체적 패브릭 경로)