Files
kernbench2/docs/adr-ko/ADR-0014-dev-pe-pipeline-execution-model.md
T
ywkang 168b0c89f0 ADR: translate adr-ko/ to Korean, fix ADR-0013 slug, refine Status check
Follow-up to the bilingual-structure commit: docs/adr-ko/ now holds
only Korean versions (24 files translated from English placeholders),
ADR-0013 slug uses kebab-case in both folders, and the verify tool
allows translated parenthetical commentary in the Status block.

- Translate 24 English files in docs/adr-ko/ to Korean. The previous
  bilingual-structure commit had left these as English copies because
  their source content was already English; this commit fulfills the
  policy that docs/adr-ko/ contains only Korean.
- Rename ADR-0013 in both adr/ and adr-ko/ from
  ver-verification_strategy.md to ver-verification-strategy.md
  (kebab-case consistency with other ADRs).
- CLAUDE.md (ADR Translation Discipline): clarify that only the
  Status lifecycle keyword (Accepted / Proposed / Stub / Draft /
  Superseded by ADR-NNNN / Merged into ADR-NNNN) must match across
  EN and KO; parenthetical commentary and trailing list items may be
  translated.
- tools/verify_adr_lang_pairs.py: replace byte-equal Status check
  with normalize_status_keyword() which strips parenthetical
  commentary and takes only the first non-empty line.
- tests/test_verify_adr_lang_pairs.py: update existing test names,
  add coverage for translated parenthetical, translated trailing
  list, and Superseded-by-NNNN keyword equality.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:17:56 -07:00

16 KiB

ADR-0014: PE 파이프라인 실행 모델

Status

Accepted

Context

본 ADR은 PE 내부 커널 실행 모델을 정의한다:

  • PE 내부 컴포넌트의 역할 분담
  • 명령 디스패치 경로 (simple / composite / epilogue를 포함한 multi-op composite)
  • TileToken 기반 자가-라우팅 파이프라인 (스케줄러는 디스패치와 완료 처리만 담당)
  • 레지스터 파일을 매개로 한 TCM 중심 데이터플로우
  • 엔진 자원 모델
  • 관측 가능성 및 트레이스 계약
  • 토폴로지 표현

PE 내부 구조 (본 ADR 범위 7개 컴포넌트 + 외부 참조 2개):

  • pe_cpu, pe_scheduler, pe_dma, pe_fetch_store, pe_gemm, pe_math, pe_tcm — 본 ADR에서 정의
  • pe_mmu — VA 모델, ADR-0011 D-VA에서 정의
  • pe_ipcq — 집합 통신, ADR-0023에서 정의

목표는 결정론적이고 트레이스 친화적인 실행 계약을 통해 각 블록이 독립적으로 교체 가능하도록 유지하는 것이다.

Decision

D1. PE 내부 컴포넌트의 역할

PE_CPU

  • 커널 명령어 스트림 / 제어 로직을 실행한다.
  • PE 명령을 생성하여 PE_SCHEDULER에 제출한다 (PeInternalTxn을 통해).
  • 엔진 큐에 직접 작업을 넣지 않는다.

PE_SCHEDULER

  • PE 내부의 유일한 디스패처.
  • PE_CPU로부터 명령을 수신한다. 명령 타입별 디스패치:
    • Simple 명령 (DmaReadCmd, DmaWriteCmd, GemmCmd, MathCmd) → 대상 엔진으로 직접 전달.
    • CompositeCmdTilePlan을 생성하고, 단일 _feed_loop를 통해 파이프라인에 타일을 공급한다 (D6).
  • composite 내부의 stage-to-stage 체이닝에는 관여하지 않는다; 이는 토큰 자가-라우팅(D6)으로 처리된다.

PE_DMA

  • 큐브 NoC를 통해 TCM과 외부 메모리 도메인(HBM, 공유 SRAM, 큐브 간 UCIe) 사이의 메모리 전송을 처리한다.
  • 두 개의 실행 채널:
    • DMA_READ (capacity = 1) 및 DMA_WRITE (capacity = 1) — D4 참조.
  • 추가 가상 채널:
    • vc_compute — GEMM/MATH 타일의 load/store/writeback 트래픽.
    • vc_comm — IPCQ 집합 통신 송신 데이터 (ADR-0023 D8에서 정의).

PE_FETCH_STORE

  • TCM ↔ 레지스터 파일 전송 유닛.
  • 레지스터 파일 접근 시맨틱을 컴퓨트 엔진으로부터 격리하여 GEMM/MATH가 순수한 컴퓨트 컴포넌트로 유지되도록 한다.
  • BW 기반 레이턴시 모델; TCM 접근 경합은 PE_TCM의 BW 자원을 통해 자연스럽게 직렬화된다.

PE_GEMM

  • MAC 어레이. 레지스터 파일에서 피연산자를 읽고, 결과를 레지스터 파일에 쓴다. PE_TCM에 직접 접근하지 않는다.

PE_MATH

  • 원소별 / 리덕션 / SIMD 유닛. 레지스터 파일을 읽고 쓴다.

PE_TCM

  • BW로 직렬화된 접근을 갖는 tightly-coupled 스크래치패드. 소유권에 따라 두 개의 논리 영역으로 분할된다 (D5 참조).

외부 참조 컴포넌트 (다른 곳에서 정의됨):

  • pe_mmu — 접근마다 VA→PA 변환 (ADR-0011 D-VA).
  • pe_ipcq — 집합 통신 링 버퍼와 피어 엔드포인트 메타데이터 (ADR-0023).

D2. 명령 생명주기와 큐

PE_SCHEDULER는 세 개의 논리적 구조를 유지한다:

SubmissionQueuePE_CPU가 쓰고, 스케줄러가 소비한다.

InflightTablePE_SCHEDULER만 소유하고 변경한다; 전개된 sub-command, 의존성 상태, 엔진 할당, 완료 상태를 추적한다.

CompletionQueuePE_SCHEDULER가 쓴다; 최종 완료 레코드를 보관한다.

Single-writer 규칙: PE_SCHEDULER만이 명령 완료 상태를 변경한다. 엔진은 명시적 이벤트 / 메시지로 완료를 보고하며, 이는 스케줄러가 소비한다.

명령 완료: 모든 sub-command가 완료되면 PE_SCHEDULER가 완료 레코드를 발행한다.

D3. 디스패치 모드

D3.1 Simple 명령

simple 명령은 정확히 하나의 엔진 sub-command로 전개된다:

  • DmaReadCmd / DmaWriteCmdPE_DMA
  • GemmCmdPE_GEMM
  • MathCmdPE_MATH

흐름:

PE_CPU → SubmissionQueue → PE_SCHEDULER → engine queue → engine execution
       → completion → PE_SCHEDULER → CompletionQueue

D3.2 Composite 명령 (단일-op 타일 파이프라인)

기본 CompositeCmd는 단일 컴퓨트 op를 타일 파이프라인 시퀀스로 실행한다:

DMA_READ → FETCH (TCM → RF) → COMPUTE (GEMM | MATH) → STORE (RF → TCM) → DMA_WRITE

PE_SCHEDULER는 DMA 페이로드를 하드웨어 타일로 분할하고, 단조 증가하는 tile_id를 갖는 TileToken을 타일마다 하나씩 발행한다.

타일 의존성 (단일 타일 t 내부):

DMA_READ(t) → FETCH(t) → COMPUTE(t) → STORE(t) → DMA_WRITE(t)

엔진 자원이 허용하는 한 타일 간 오버랩이 허용된다 (D4가 제약을 규정):

DMA_READ(t+1) ∥ COMPUTE(t)
DMA_WRITE(t-1) ∥ COMPUTE(t)

D3.3 Multi-op composite (스코프를 갖는 head + epilogue)

CompositeCmdops: tuple[OpSpec, ...]를 운반하여 multi-op 파이프라인을 표현할 수 있다:

@dataclass(frozen=True)
class OpSpec:
    kind: str         # "gemm" | "math.exp" | "math.bias_add" | ...
    scope: Scope      # "per_k_tile" | "per_output_tile" | "once"
    ...
  • ops[0] (head)이 타일 기하 구조를 정의한다 (예: head GEMM이 M/K/N 분할을 결정).
  • ops[1:] (epilogue)는 후속 stage이며 scope에 따라 실행 빈도가 결정된다:
    • per_k_tile — 모든 K-리덕션 스텝마다.
    • per_output_tile — 출력 타일당 한 번.
    • once — 커널당 한 번.

크로스-엔진 체인(예: GEMM head → MATH epilogue)은 자연스럽다 — 각 stage는 토큰 자가-라우팅(D6)을 통해 디스패치되므로, GEMM과 MATH는 동일한 컴퓨트 슬롯(D4)을 공유하더라도 동일 composite 내에서 직렬적으로 참여한다.

비어 있는 ops 형식은 레거시 단일-op 경로이다.

D4. 엔진 자원 모델

DMA 엔진:

  • DMA_READ: simpy.Resource(capacity=1).
  • DMA_WRITE: simpy.Resource(capacity=1).
  • 두 채널은 동시에 실행된다 (READ ∥ WRITE 허용).
  • 채널 내부에서는 요청이 직렬화된다 (READ ∥ READ 불가; WRITE도 동일).
  • vc_comm은 IPCQ 트래픽을 위한 직교 채널로 ADR-0023 D8에서 정의됨 — 본 ADR 범위 밖.

컴퓨트 엔진:

  • accel_slot: PE_GEMMPE_MATH가 공유하는 simpy.Resource(capacity=1).
  • PE 내에서 동시에 최대 한 개의 컴퓨트 op만 실행된다.
  • Multi-op composite 체인(D3.3)은 이 슬롯을 통해 컴퓨트 stage를 직렬로 실행한다; 토큰 자가-라우팅(D6)이 이전 컴퓨트가 슬롯을 해제한 후에만 다음 stage가 시작되도록 보장한다.

엔진 완료: 각 엔진은 완료 이벤트를 발행하며, 이는 스케줄러 / PipelineContext(D6)가 소비한다.

D5. 데이터플로우

입력 경로 (HBM 소스):

HBM → cube NOC → PE_DMA (DMA_READ) → PE_TCM
PE_TCM → PE_FETCH_STORE → Register File
Register File → PE_GEMM | PE_MATH

입력 경로 (공유 SRAM 소스):

Shared SRAM → cube NOC → PE_DMA (DMA_READ) → PE_TCM
PE_TCM → PE_FETCH_STORE → Register File

출력 경로 (HBM 목적지):

Register File → PE_FETCH_STORE → PE_TCM
PE_TCM → PE_DMA (DMA_WRITE) → cube NOC → HBM

GEMM/MATH는 PE_TCM에 직접 접근하지 않는다 — PE_FETCH_STORE가 TCM↔레지스터 파일의 유일한 게이트웨이이다. 이를 통해 TCM BW 경합이 명시적으로 드러나며, fetch 유닛 정책(예: 프리패치)을 컴퓨트 엔진과 독립적으로 교체할 수 있다.

D5.1 PE_TCM 분할

PE_TCM은 두 개의 논리 영역으로 분할된다:

SchedulerReservedTCM

  • PE_SCHEDULER가 단독으로 소유한다.
  • composite 명령의 타일 버퍼를 보관한다.
  • PE_SCHEDULER가 이 영역을 분할하고, DMA_READ / COMPUTE / DMA_WRITE stage마다 버퍼를 할당하며, 입출력 분리를 보장하고, 타일-버퍼 수명을 관리한다.

AllocatableTCM

  • PEMemAllocator가 관리하는 범용 영역.
  • 호스트 / DP 가시 할당에 사용된다.

가시성 규칙 (강한 격리): PEMemAllocatorSchedulerReservedTCM을 보거나 그 내부에 할당해서는 안 된다. 예약 영역은 구성 시점에 할당자가 관리하는 범위에서 제외된다.

타일 버퍼 규칙:

  • 타일이 활성 수명 동안 SchedulerReservedTCM 내부의 입력 버퍼와 출력 버퍼는 겹쳐서는 안 된다.
  • 타일 버퍼는 해당 DMA_WRITE가 완료될 때까지 유효하다.
  • 버퍼 재사용은 소비하는 타일의 수명이 끝난 후에만 허용된다.

D6. TileToken 자가-라우팅 파이프라인

composite의 stage-to-stage 진행은 스케줄러를 거치지 않고 일어난다. 각 컴포넌트는 토큰의 plan을 사용해 토큰을 다음 stage의 컴포넌트로 직접 전달한다:

Scheduler → DMA → Fetch → GEMM → Math (epi) → Store → DMA_WB → (complete)
              ↑ chaining: no scheduler hop                          ↑
                                                  PipelineContext.complete_tile()

이는 실제 HW의 done-wire 체인을 반영한다. 스케줄러는 초기 디스패치 + 완료 집계만 담당한다.

TilePlan / Stage

class StageType(Enum):
    DMA_READ = 0
    FETCH = 1
    GEMM = 2
    MATH = 3
    STORE = 4
    DMA_WRITE = 5

@dataclass(frozen=True)
class Stage:
    stage_type: StageType
    component: str         # topology node id (e.g., "sip0.cube0.pe0.pe_dma")
    params: dict           # stage-specific parameters

@dataclass(frozen=True)
class TilePlan:
    tile_id: int
    stages: tuple[Stage, ...]

TileToken

@dataclass
class TileToken:
    tile_id: int
    pipeline_ctx: PipelineContext
    plan: TilePlan
    stage_idx: int
    params: dict             # cached current stage params
    data_op: bool = True     # op_log opt-in (ADR-0020 D4)

단일 소유자 불변식: 토큰은 한 시점에 정확히 한 컴포넌트가 소유한다. 생명주기: 스케줄러가 stage_idx=0으로 생성 → 컴포넌트 _process()stage_idx 증가 → 다음 stage의 in_port에 put → 마지막 stage가 pipeline_ctx.complete_tile() 호출.

PipelineContext (정확히 한 번 완료)

@dataclass
class PipelineContext:
    id: str
    total_tiles: int
    completed_tiles: int = 0
    done_event: simpy.Event = None

    def complete_tile(self) -> None:
        self.completed_tiles += 1
        if self.completed_tiles == self.total_tiles:
            self.done_event.succeed()

각 타일의 마지막 stage는 complete_tile()을 정확히 한 번 호출해야 한다. 중복 호출은 버그이다 (SimPy Event는 최대 한 번만 succeed 가능).

Feed 순서

PE_SCHEDULER_pending_feeds FIFO를 소비하는 _feed_loop 프로세스를 정확히 하나 갖는다. composite 명령은 제출 순서대로 인큐되며, 한 명령의 타일 feed는 다음 명령의 feed가 시작되기 전에 완료까지 실행된다. 명령 간 타일-feed 인터리빙은 허용되지 않는다.

단일 명령의 타일들 내부에서는 다운스트림 파이프라인 오버랩이 자연스럽게 발생한다 — 이전 타일이 후행 stage를 진행하는 동안 feeder는 남은 타일을 첫 stage 큐로 계속 푸시한다 (SimPy Store 백프레셔가 흐름 제어를 관장한다). 첫 stage 큐가 가득 차면 feeder만 블록되며, 스케줄러 워커의 inbox 처리는 계속된다.

토큰 라우팅 패턴 (기본 클래스)

def _pipeline_worker(self, env):
    while True:
        token = yield self._inbox.get()
        yield from self._process(env, token)       # stage-specific logic
        next_idx = token.stage_idx + 1
        if next_idx < len(token.plan.stages):
            next_stage = token.plan.stages[next_idx]
            token.stage_idx = next_idx
            token.params = next_stage.params
            yield self.out_ports[next_stage.component].put(token)
        else:
            token.pipeline_ctx.complete_tile()

각 컴포넌트는 _process()만 구현한다; 체이닝은 기본 클래스에 존재한다.

D7. 관측 가능성 및 트레이스 계약

시뮬레이터는 결정론적 트레이스 이벤트를 발행한다:

  • command_submitted
  • sub_command_dispatched
  • engine_start
  • engine_complete
  • tile_ready
  • command_complete

동일한 입력에 대해 트레이스 순서는 결정론적이어야 한다.

D8. 토폴로지 표현

PE 내부 컴포넌트는 cube.pe_template에 선언된다:

pe_template:
  components:
    pe_cpu:         { kind: pe_cpu,         impl: builtin.pe_cpu,         attrs: { overhead_ns: ... } }
    pe_scheduler:   { kind: pe_scheduler,   impl: builtin.pe_scheduler,   attrs: { overhead_ns: ... } }
    pe_dma:         { kind: pe_dma,         impl: builtin.pe_dma,         attrs: { rd_engines: 1, wr_engines: 1 } }
    pe_fetch_store: { kind: pe_fetch_store, impl: builtin.pe_fetch_store, attrs: { ... } }
    pe_gemm:        { kind: pe_gemm,        impl: builtin.pe_gemm,        attrs: { shared_resource: accel_slot, ... } }
    pe_math:        { kind: pe_math,        impl: builtin.pe_math,        attrs: { shared_resource: accel_slot, ... } }
    pe_tcm:         { kind: pe_tcm,         impl: builtin.pe_tcm,         attrs: { size_mb: ..., read_bw_gbs: ..., write_bw_gbs: ... } }
    pe_mmu:         { kind: pe_mmu,         impl: builtin.pe_mmu,         attrs: { ... } }   # ADR-0011 D-VA
    pe_ipcq:        { kind: pe_ipcq,        impl: builtin.pe_ipcq,        attrs: { ... } }   # ADR-0023
  links:
    # Scheduler dispatch edges (initial)
    scheduler_to_dma_mm:         0.0
    scheduler_to_fetch_store_mm: 0.0
    scheduler_to_gemm_mm:        0.0
    scheduler_to_math_mm:        0.0
    # Pipeline chaining edges (token self-routing per D6)
    dma_to_fetch_store_mm:       0.0
    fetch_store_to_gemm_mm:      0.0
    fetch_store_to_math_mm:      0.0
    gemm_to_fetch_store_mm:      0.0
    gemm_to_math_mm:             0.0
    math_to_fetch_store_mm:      0.0
    fetch_store_to_dma_mm:       0.0
    fetch_store_to_tcm_bw_gbs:   ...

템플릿은 PE마다 한 번 인스턴스화된다. PE 인스턴스는 cube.pe_layout (코너 배치)으로부터 파생된다. 외부 연결성(PE_DMA ↔ cube NoC ↔ HBM 등)은 큐브 수준에서 모델링된다 (ADR-0017 D4).

Consequences

Positive

  • 각 블록이 독립적인 토폴로지 노드이다 — DI(ADR-0015)를 통해 개별 교체 가능하다.
  • PE 내부 구조가 토폴로지 그래프에 가시화된다.
  • 컴포넌트는 자신의 다운스트림을 알지 못한다 — plan 기반 라우팅이 유연성을 제공한다 (예: epilogue 체인에 스케줄러 변경이 불필요).
  • DMA와 컴퓨트가 SimPy Store 백프레셔를 통해 자연스럽게 오버랩된다.
  • Multi-op composite가 융합 연산(예: GEMM + bias_add)을 엔진 수준 결합 없이 표현한다.
  • TCM 접근 경합이 현실적이다 — PE_FETCH_STORE가 TCM↔RF의 유일한 게이트웨이이다.

Negative

  • PE 내부 컴포넌트 수가 더 거친 모델보다 많다 (기본 7개 + 외부 참조 2개) — 더 많은 토폴로지 노드/엣지.
  • PE 내부 토큰 전달이 트레이스에 명시적으로 드러난다 (HW 충실도와의 허용 가능한 trade-off).
  • ADR-0011 D-VA (PE_MMU 컴포넌트, VA 변환)
  • ADR-0015 D4 (컴포넌트 포트/와이어 모델)
  • ADR-0020 (greenlet 커널 실행 / two-pass)
  • ADR-0023 (PE_IPCQ + PE_DMA 가상 채널)
  • SPEC R3, R4