168b0c89f0
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>
442 lines
16 KiB
Markdown
442 lines
16 KiB
Markdown
# 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`)
|
|
→ 대상 엔진으로 직접 전달.
|
|
- `CompositeCmd` → `TilePlan`을 생성하고, 단일 `_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`는 세 개의 논리적 구조를 유지한다:
|
|
|
|
**SubmissionQueue** — `PE_CPU`가 쓰고, 스케줄러가 소비한다.
|
|
|
|
**InflightTable** — `PE_SCHEDULER`만 소유하고 변경한다; 전개된 sub-command,
|
|
의존성 상태, 엔진 할당, 완료 상태를 추적한다.
|
|
|
|
**CompletionQueue** — `PE_SCHEDULER`가 쓴다; 최종 완료 레코드를 보관한다.
|
|
|
|
**Single-writer 규칙**: `PE_SCHEDULER`만이 명령 완료 상태를 변경한다.
|
|
엔진은 명시적 이벤트 / 메시지로 완료를 보고하며, 이는 스케줄러가
|
|
소비한다.
|
|
|
|
**명령 완료**: 모든 sub-command가 완료되면 `PE_SCHEDULER`가 완료 레코드를
|
|
발행한다.
|
|
|
|
### D3. 디스패치 모드
|
|
|
|
#### D3.1 Simple 명령
|
|
|
|
simple 명령은 정확히 하나의 엔진 sub-command로 전개된다:
|
|
|
|
- `DmaReadCmd` / `DmaWriteCmd` → `PE_DMA`
|
|
- `GemmCmd` → `PE_GEMM`
|
|
- `MathCmd` → `PE_MATH`
|
|
|
|
흐름:
|
|
|
|
```text
|
|
PE_CPU → SubmissionQueue → PE_SCHEDULER → engine queue → engine execution
|
|
→ completion → PE_SCHEDULER → CompletionQueue
|
|
```
|
|
|
|
#### D3.2 Composite 명령 (단일-op 타일 파이프라인)
|
|
|
|
기본 `CompositeCmd`는 단일 컴퓨트 op를 타일 파이프라인 시퀀스로 실행한다:
|
|
|
|
```text
|
|
DMA_READ → FETCH (TCM → RF) → COMPUTE (GEMM | MATH) → STORE (RF → TCM) → DMA_WRITE
|
|
```
|
|
|
|
`PE_SCHEDULER`는 DMA 페이로드를 하드웨어 타일로 분할하고, 단조 증가하는
|
|
`tile_id`를 갖는 `TileToken`을 타일마다 하나씩 발행한다.
|
|
|
|
타일 의존성 (단일 타일 `t` 내부):
|
|
|
|
```text
|
|
DMA_READ(t) → FETCH(t) → COMPUTE(t) → STORE(t) → DMA_WRITE(t)
|
|
```
|
|
|
|
엔진 자원이 허용하는 한 타일 간 오버랩이 허용된다
|
|
(D4가 제약을 규정):
|
|
|
|
```text
|
|
DMA_READ(t+1) ∥ COMPUTE(t)
|
|
DMA_WRITE(t-1) ∥ COMPUTE(t)
|
|
```
|
|
|
|
#### D3.3 Multi-op composite (스코프를 갖는 head + epilogue)
|
|
|
|
`CompositeCmd`는 `ops: tuple[OpSpec, ...]`를 운반하여 multi-op
|
|
파이프라인을 표현할 수 있다:
|
|
|
|
```python
|
|
@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_GEMM`과 `PE_MATH`가 공유하는 `simpy.Resource(capacity=1)`.
|
|
- PE 내에서 동시에 최대 한 개의 컴퓨트 op만 실행된다.
|
|
- Multi-op composite 체인(D3.3)은 이 슬롯을 통해 컴퓨트 stage를 직렬로
|
|
실행한다; 토큰 자가-라우팅(D6)이 이전 컴퓨트가 슬롯을 해제한 후에만
|
|
다음 stage가 시작되도록 보장한다.
|
|
|
|
**엔진 완료**: 각 엔진은 완료 이벤트를 발행하며, 이는 스케줄러 /
|
|
`PipelineContext`(D6)가 소비한다.
|
|
|
|
### D5. 데이터플로우
|
|
|
|
**입력 경로 (HBM 소스)**:
|
|
|
|
```text
|
|
HBM → cube NOC → PE_DMA (DMA_READ) → PE_TCM
|
|
PE_TCM → PE_FETCH_STORE → Register File
|
|
Register File → PE_GEMM | PE_MATH
|
|
```
|
|
|
|
**입력 경로 (공유 SRAM 소스)**:
|
|
|
|
```text
|
|
Shared SRAM → cube NOC → PE_DMA (DMA_READ) → PE_TCM
|
|
PE_TCM → PE_FETCH_STORE → Register File
|
|
```
|
|
|
|
**출력 경로 (HBM 목적지)**:
|
|
|
|
```text
|
|
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 가시 할당에 사용된다.
|
|
|
|
**가시성 규칙 (강한 격리)**: `PEMemAllocator`는 `SchedulerReservedTCM`을
|
|
보거나 그 내부에 할당해서는 안 된다. 예약 영역은 구성 시점에 할당자가
|
|
관리하는 범위에서 제외된다.
|
|
|
|
**타일 버퍼 규칙**:
|
|
|
|
- 타일이 활성 수명 동안 `SchedulerReservedTCM` 내부의 입력 버퍼와 출력
|
|
버퍼는 겹쳐서는 안 된다.
|
|
- 타일 버퍼는 해당 `DMA_WRITE`가 완료될 때까지 유효하다.
|
|
- 버퍼 재사용은 소비하는 타일의 수명이 끝난 후에만 허용된다.
|
|
|
|
### D6. TileToken 자가-라우팅 파이프라인
|
|
|
|
composite의 stage-to-stage 진행은 스케줄러를 거치지 **않고** 일어난다.
|
|
각 컴포넌트는 토큰의 `plan`을 사용해 토큰을 다음 stage의 컴포넌트로
|
|
직접 전달한다:
|
|
|
|
```text
|
|
Scheduler → DMA → Fetch → GEMM → Math (epi) → Store → DMA_WB → (complete)
|
|
↑ chaining: no scheduler hop ↑
|
|
PipelineContext.complete_tile()
|
|
```
|
|
|
|
이는 실제 HW의 done-wire 체인을 반영한다. 스케줄러는 **초기 디스패치 +
|
|
완료 집계**만 담당한다.
|
|
|
|
#### TilePlan / Stage
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
@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 (정확히 한 번 완료)
|
|
|
|
```python
|
|
@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 처리는 계속된다.
|
|
|
|
#### 토큰 라우팅 패턴 (기본 클래스)
|
|
|
|
```python
|
|
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`에 선언된다:
|
|
|
|
```yaml
|
|
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).
|
|
|
|
## Links
|
|
|
|
- ADR-0011 D-VA (PE_MMU 컴포넌트, VA 변환)
|
|
- ADR-0015 D4 (컴포넌트 포트/와이어 모델)
|
|
- ADR-0020 (greenlet 커널 실행 / two-pass)
|
|
- ADR-0023 (PE_IPCQ + PE_DMA 가상 채널)
|
|
- SPEC R3, R4
|