ADR housekeeping: category prefixes, lifecycle folders, retroactive 0034-0037
Filename + lifecycle:
- ADR rename to ADR-NNNN-<cat>-title.md with 8 3-letter category prefixes
(dev / mem / lat / prog / algo / par / api / ver). Numbers stay immutable.
- ADR Lifecycle split into 3 folders, documented in CLAUDE.md Part 2:
docs/adr/ (Accepted), docs/adr-proposed/ (Proposed/Stub/Draft),
docs/adr-history/ (Superseded/Merged). Status field gains "Draft" for
retroactive docs pending verification.
Merges (one ADR per topic, no change-history annotations):
- ADR-0017 absorbs ADR-0019 (Cube NOC + per-PE HBM connectivity, 10 D-items)
- ADR-0014 absorbs ADR-0021 (PE pipeline execution model, 8 D-items incl.
TileToken self-routing and multi-op composite epilogue scope)
- ADR-0023 absorbs docs/ipcq-dma-codesign-hw.md as new "HW Realization
Notes (Informative)" section (D16-D23 + Open HW Questions). codesign-hw.md
deleted; ADR-0019/0021 moved to adr-history with one-line stub status
Retroactive documentation (G4 closures, code-verified):
- ADR-0037 forwarding component (TransitComponent: first-flit overhead,
serial worker, path-based routing, single impl/multiple names)
- ADR-0036 IO_CPU component (target_start_ns global barrier stamping,
per-cube fan-out, response aggregation)
- ADR-0035 M_CPU & M_CPU.DMA component (3 fan-out paths, DMA Resources,
target_start_ns passthrough)
- ADR-0034 HBM controller internal design (per-PC state, address-based
selection, flit-aware per-flit commit, async finalize, command-only
fallback path)
Content updates:
- ADR-0010 expanded to full CLI surface (run/probe/web), retitled
"Command Line Interface and Execution Semantics"
- ADR-0007 D2 rewritten to current state; ADR-0015 supersession notes pruned
- ADR-0005 wrapped in Decision header with D1-D5; ADR-0022 metadata
block replaced with standard Status header
- ADR-0024 trimmed to rank=SIP launcher essentials (D1-D4);
ADR-0027 cleaned of supersession history
- ADR-0033 D6 cleanup: address-based PC selection moved out of future-work
(now documented in ADR-0034 D3); related D1/D3 wording realigned
- Cross-references back-filled in 5 ADRs (G3 gaps closed)
Onboarding docs split:
- docs/onboarding/ created
- moved: hw-architecture-overview.md, latency-model.md, di-presentation.md,
ccl-author-guide{,.en}.md
- references updated in README, ADR-0023{,.en}, src/kernbench/ccl/__init__.py
Source / test / yaml: ADR-NNNN cross-references in docstrings and YAML
comments updated after the merges (ADR-0021->0014 D6, ADR-0019->0017 D8).
No behavior change.
Tooling:
- tools/verify_adr_lang_pairs.py + tests/test_verify_adr_lang_pairs.py
(ADR EN/KO pair invariant checker)
- .claude/commands/report.md tracked (/report slash command)
- .gitignore: allow .claude/commands/*.md while keeping settings files ignored
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
# ADR-0025: IPCQ Direction Addressing — address-based matching
|
||||
|
||||
## Status
|
||||
|
||||
Accepted (Revision 2 — Address-based matching; peer_direction field dropped)
|
||||
|
||||
## Context
|
||||
|
||||
### 목표
|
||||
|
||||
ADR-0023의 IPCQ protocol에서 **"어느 direction pair를 통한 전송인가"의 식별**을
|
||||
topology / dict-order에 의존하지 않고 **주소 기반**으로 일관되게 한다.
|
||||
2-rank bidirectional ring (또는 여러 direction이 동일 peer를 가리키는
|
||||
topology 일반)에서 정확히 동작하도록 한다.
|
||||
|
||||
### 드러난 버그 — 2-rank bidirectional ring
|
||||
|
||||
`ring_1d(rank, world_size=2)` → `{"E": 1, "W": 1}` (rank 0). 양쪽 방향이 같은 peer.
|
||||
|
||||
**버그 1 (install)**:
|
||||
- `reverse_direction(0, 1)` → dict order로 "E" 반환 (틀림, "W"가 맞음 — opposite
|
||||
direction convention)
|
||||
- rank 0의 E entry가 `peer.rx_base_pa = rx_base(sip1, cube0, pe0, d="E")`로 설정
|
||||
- tl.send(E) → data가 sip1의 E-rx buffer로 landing (should be W-rx)
|
||||
|
||||
**버그 2 (runtime)**:
|
||||
- 설령 install이 올바른 주소로 설정해도, receiver의 `_handle_meta_arrival`이
|
||||
sender 좌표만으로 direction 매칭 → 첫 direction (E) 승
|
||||
- peer_head_cache[E] 증가, peer_head_cache[W]는 불변
|
||||
- Kernel의 tl.recv(W)는 peer_head_cache[W] 대기 → 영원히 블록 → IpcqDeadlock
|
||||
|
||||
### 근본 원인
|
||||
|
||||
두 축에서 동일 문제:
|
||||
1. **Install-time pairing**: "내 direction과 peer의 어느 direction이 짝인가"
|
||||
결정이 dict-iteration-order에 의존 → 여러 direction이 같은 peer를 가리킬 때
|
||||
fragile
|
||||
2. **Runtime identification**: "어느 qp를 업데이트해야 하는가" 결정이 sender
|
||||
좌표만으로 이루어짐 → direction 중복 시 ambiguous
|
||||
|
||||
### 해결 방향 — address-based matching
|
||||
|
||||
각 PE의 rx buffer는 **direction별로 고유한 주소 range**에 위치 (rx_base_pa +
|
||||
direction_idx × bytes_per_direction). 따라서:
|
||||
|
||||
- **Runtime**: sender coord 대신 **dst_addr 범위**로 매칭 → unambiguous
|
||||
- **Install**: opposite-direction 우선 선택 heuristic (ring / mesh의 자연스러운
|
||||
대칭성)
|
||||
- `peer_direction` 같은 이중 메타데이터 불필요 — **주소가 single source of
|
||||
truth**
|
||||
|
||||
이 설계는 **PhysAddr 전환 (ADR-0030)과 독립적**으로 작동. 현재 synthetic
|
||||
주소든 PhysAddr든 direction별 range 유일성만 지켜지면 동일하게 적용 가능.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
### D1. Install — `reverse_direction` opposite-preference
|
||||
|
||||
`src/kernbench/ccl/install.py`:
|
||||
|
||||
```python
|
||||
# Extended in ADR-0032 with global_* pairs for inter-SIP directions,
|
||||
# which were introduced by configure_sfr_intercube_multisip to keep
|
||||
# intercube (N/S/E/W) and inter-SIP (global_N/S/E/W) namespaces disjoint.
|
||||
_OPPOSITE_DIR = {
|
||||
"E": "W", "W": "E", "N": "S", "S": "N",
|
||||
"global_E": "global_W", "global_W": "global_E",
|
||||
"global_N": "global_S", "global_S": "global_N",
|
||||
}
|
||||
|
||||
def reverse_direction(my_rank: int, peer_rank: int, my_dir: str) -> str | None:
|
||||
"""Find peer's direction that reciprocates my_dir→peer_rank.
|
||||
|
||||
Prefer the OPPOSITE direction (E↔W, N↔S) when the peer has it
|
||||
pointing back to us. This matters in 2-rank bidirectional rings
|
||||
where both E and W on one side point to the same peer — without
|
||||
the preference, the first-match-wins iteration would route data
|
||||
into the wrong rx slot. Falls back to any direction pointing back
|
||||
for topologies without an opposite convention (tree_binary's
|
||||
parent/child).
|
||||
"""
|
||||
nt = neighbor_table[peer_rank]
|
||||
opp = _OPPOSITE_DIR.get(my_dir)
|
||||
if opp is not None and nt.get(opp) == my_rank:
|
||||
return opp
|
||||
for d, target in nt.items():
|
||||
if target == my_rank:
|
||||
return d
|
||||
return None
|
||||
```
|
||||
|
||||
호출부:
|
||||
|
||||
```python
|
||||
for d, peer_rank in nbrs.items():
|
||||
peer_dir = reverse_direction(r, peer_rank, d) # my_dir 전달
|
||||
if peer_dir is None:
|
||||
continue
|
||||
...
|
||||
```
|
||||
|
||||
### D2. Runtime — `_handle_meta_arrival` dst_addr 매칭
|
||||
|
||||
`src/kernbench/components/builtin/pe_ipcq.py`:
|
||||
|
||||
```python
|
||||
def _handle_meta_arrival(self, msg: IpcqMetaArrival) -> None:
|
||||
"""Match incoming token to the receiver-side direction by dst_addr range.
|
||||
|
||||
Each direction has a unique rx buffer address range
|
||||
(my_rx_base_pa + n_slots * slot_size). The token's dst_addr (set by
|
||||
the sender's IPCQ when computing peer's slot address) falls within
|
||||
exactly one such range. This address-based matching is unambiguous
|
||||
even when multiple directions have the same peer (2-rank ring).
|
||||
"""
|
||||
token = msg.token
|
||||
dst_addr = token.dst_addr
|
||||
for d, qp in self._queue_pairs.items():
|
||||
base = qp["my_rx_base_pa"]
|
||||
size = qp["n_slots"] * qp["slot_size"]
|
||||
if base <= dst_addr < base + size:
|
||||
qp["peer_head_cache"] = max(qp["peer_head_cache"],
|
||||
token.sender_seq + 1)
|
||||
self._arrived_tokens.setdefault(d, []).append(token)
|
||||
waiters = self._recv_waiters.get(d, [])
|
||||
self._recv_waiters[d] = []
|
||||
for ev in waiters:
|
||||
if not ev.triggered:
|
||||
ev.succeed()
|
||||
any_waiters = self._any_recv_waiters
|
||||
self._any_recv_waiters = []
|
||||
for ev in any_waiters:
|
||||
if not ev.triggered:
|
||||
ev.succeed()
|
||||
return
|
||||
# Unknown dst_addr — diagnostic log (should not happen under correct install)
|
||||
```
|
||||
|
||||
Sender 좌표 검사는 **제거**. `dst_addr`가 이미 direction을 결정.
|
||||
|
||||
### D3. Credit — `dst_rx_base_pa` 필드 추가
|
||||
|
||||
`src/kernbench/common/ipcq_types.py`:
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class IpcqCreditMetadata:
|
||||
consumer_seq: int
|
||||
dst_rx_base_pa: int # NEW: 원 sender의 peer.rx_base_pa와 매칭용
|
||||
# 기존 필드 (diagnostic / log 용도로 유지)
|
||||
src_sip: int
|
||||
src_cube: int
|
||||
src_pe: int
|
||||
src_direction: str
|
||||
```
|
||||
|
||||
Credit 생성 시 (`_delayed_credit_send`): 자기 direction의 `my_rx_base_pa`를
|
||||
`dst_rx_base_pa`로 실어 보냄 (이게 상대방이 sender 당시 썼던 `peer.rx_base_pa`).
|
||||
|
||||
수신 측 (`_credit_worker`):
|
||||
|
||||
```python
|
||||
def _credit_worker(self, env):
|
||||
while True:
|
||||
credit = yield self._credit_inbox.get()
|
||||
for d, qp in self._queue_pairs.items():
|
||||
# peer의 rx_base_pa와 credit의 dst_rx_base_pa가 일치하는 qp 찾기
|
||||
if qp["peer"].rx_base_pa == credit.dst_rx_base_pa:
|
||||
qp["peer_tail_cache"] = max(qp["peer_tail_cache"],
|
||||
credit.consumer_seq)
|
||||
waiters = self._send_waiters.get(d, [])
|
||||
self._send_waiters[d] = []
|
||||
for ev in waiters:
|
||||
if not ev.triggered:
|
||||
ev.succeed()
|
||||
break
|
||||
```
|
||||
|
||||
Sender 좌표 검사 제거. `dst_rx_base_pa` 매칭으로 unambiguous.
|
||||
|
||||
### D4. `IpcqInitEntry`에 `peer_direction` 필드를 **추가하지 않음**
|
||||
|
||||
ADR-0025 rev 1에서 제안했던 `IpcqInitEntry.peer_direction`은 **불필요**.
|
||||
이유:
|
||||
- Meta arrival은 dst_addr로 매칭 (D2)
|
||||
- Credit은 dst_rx_base_pa로 매칭 (D3)
|
||||
- qp에 peer_direction 저장 필요 없음
|
||||
- Install은 rx_base_pa 계산 시 내부적으로만 peer_dir 사용 (`reverse_direction`)
|
||||
|
||||
IpcqInitEntry schema 변경 없음. Rev 1 대비 **단순화**.
|
||||
|
||||
### D5. `IpcqDmaToken.src_direction` 유지 (diagnostic only)
|
||||
|
||||
기존 `src_direction` 필드는 제거하지 않는다. 다음 용도로 유지:
|
||||
- Logging / trace: `KERNBENCH_CCL_TRACE=1` 출력의 `(rank, t, dir, nbytes)`
|
||||
- Diagnostics: pointer_dump 등에서 direction 표시
|
||||
- 미래 확장 여지
|
||||
|
||||
Runtime matching은 `dst_addr`만 사용.
|
||||
|
||||
### D6. Invariants (ADR-0023 I3 강화)
|
||||
|
||||
**I3 (엄격)**: 각 방향 pair `(my_direction, peer_direction)`에 대해 my
|
||||
rx_base와 peer rx_base는 **별개의 direction slot**을 가리켜야 함. Install은
|
||||
이를 보장해야 한다 (reverse_direction opposite-preference).
|
||||
|
||||
**I3.1 (신규)**: 모든 qp에 대해 `qp["my_rx_base_pa"]`와 `qp["peer"].rx_base_pa`는
|
||||
서로 disjoint한 주소 range를 점유한다 (다른 direction의 buffer는 절대 겹치지
|
||||
않음). 이것이 D2/D3의 주소-기반 매칭의 전제.
|
||||
|
||||
Install time에 검증 가능:
|
||||
```python
|
||||
# ccl/install_plan.py: build_install_plans 끝에 assertion
|
||||
all_rx_ranges = set()
|
||||
for plan in plans:
|
||||
for pe_install in plan.pe_installs:
|
||||
for entry in pe_install.neighbors:
|
||||
r = (entry.my_rx_base_pa,
|
||||
entry.my_rx_base_pa + plan.n_slots * plan.slot_size)
|
||||
overlap = any(_ranges_overlap(r, e) for e in all_rx_ranges)
|
||||
assert not overlap
|
||||
all_rx_ranges.add(r)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **ADR-0023** (IPCQ protocol): 본 ADR은 ADR-0023의 runtime 매칭 로직 수정
|
||||
(D2, D3) + install heuristic 개선 (D1). IPCQ 프로토콜의 semantic layer
|
||||
변경은 없음.
|
||||
- **ADR-0024** (launcher): 2-rank bidirectional ring이 실제 쓰이는 경우가
|
||||
ADR-0024의 ws=SIP_count 모델. 본 ADR이 그 케이스를 작동시킴.
|
||||
- **ADR-0030** (PhysAddr transition, stub): **독립적** — ADR-0025의
|
||||
주소-기반 매칭은 현재 synthetic 주소든 PhysAddr이든 동일하게 작동.
|
||||
|
||||
---
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **IPCQ 주소 체계를 PhysAddr로 전환**: ADR-0030 scope. 본 ADR은 주소가 어떻게
|
||||
인코딩되는가와 무관.
|
||||
- **Multi-hop routing**: ADR-0023 D5의 single-hop DMA write 전제 유지.
|
||||
- **Unidir ring 특수화**: `ring_1d_unidir`는 direction 하나만 있으므로 본 버그
|
||||
무관.
|
||||
|
||||
---
|
||||
|
||||
## Open questions
|
||||
|
||||
- **주소 매칭 성능**: `_handle_meta_arrival`과 `_credit_worker`가 qp를 선형
|
||||
순회 (max 4 direction). 성능 영향 무시 가능 수준. 문제 시 dict lookup으로
|
||||
전환 가능 (`_qp_by_rx_base`).
|
||||
- **`IpcqDmaToken.src_direction` 필요성 재평가**: diagnostic 용도로만 남긴
|
||||
필드를 계속 유지할지, 또는 logging 외부로 분리할지. 현재는 유지.
|
||||
- **Install-time invariant 검증 cost**: D6의 I3.1 검증은 O(N_PE × N_direction)^2.
|
||||
대형 topology에서 느려질 수 있음 → interval tree 등 자료구조로 개선 가능.
|
||||
단순 구현 먼저.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- **단순함**: `peer_direction` 이중 메타데이터 제거. 주소가 single source of truth.
|
||||
- **Unambiguous matching**: 모든 topology (direction 중복 포함)에서 동작.
|
||||
- **Schema 변경 최소**: `IpcqInitEntry` 불변, `IpcqCreditMetadata`에 1 필드 추가.
|
||||
- **PhysAddr 전환 (ADR-0030) 독립**: 주소-기반 매칭은 주소 인코딩 방식과 무관.
|
||||
- **Diagnostic 유지**: `IpcqDmaToken.src_direction`은 로깅 용도로 존치.
|
||||
|
||||
### Negative
|
||||
|
||||
- Runtime 매칭이 주소 비교로 바뀌어서 디버깅 시 "왜 peer_head_cache[E]가 아닌
|
||||
W가 업데이트됐나" 같은 질문에 address range를 추적해야 함 (기존엔 direction
|
||||
이름으로 충분). 해결: pointer_dump에 "direction ↔ rx_base_pa" 매핑 포함.
|
||||
|
||||
### Neutral
|
||||
|
||||
- IPCQ protocol의 semantic layer (sender가 dst_addr 계산, receiver가 수신)는
|
||||
불변.
|
||||
Reference in New Issue
Block a user