Files
kernbench2/docs/adr/ADR-0025-ipcq-direction-addressing.md
T
ywkang 22fd0d2b9d ADR: introduce docs/history/, merge 0011+0018, prune migration cruft
- CLAUDE.md: add ADR Lifecycle subsection (superseded → docs/history/,
  immutable numbering, no renumber)
- ADR-0011: merge ADR-0018 content as "Address Model: LA" section
  alongside PA / VA; status notes VA model is currently implemented
- ADR-0018 / 0029 / 0031: moved to docs/history/ with status updates
  (0018 merged into 0011, 0029 superseded by 0032, 0031 absorbed
  into 0001 rev 2)
- ADR-0019: rewrite Context as PE-HBM connectivity decision
  (self-contained, no LA model framing)
- ADR-0019/0020/0021/0023/0025/0027: Status Proposed → Accepted
  (code verified) and prune Implementation Notes / Affected files /
  Test strategy / "현재 상태" sub-sections describing pre-impl state
- ADR-0024/0026: same migration-flavor cleanup; 0026 also drops D6
  Migration and D8 docs-update sub-decisions
- ADR-0030: status simplified (blocker ADR-0031 now superseded)
- SPEC.md: R10 + §0.2 reflect PA / VA / LA model names
- ADR-0008/0012/0013: refresh ADR-0011 subtitle in Links

21 files changed, 553 insertions(+), 1290 deletions(-).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 11:42:45 -07:00

11 KiB
Raw Blame History

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:

# 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

호출부:

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:

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:

@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_padst_rx_base_pa로 실어 보냄 (이게 상대방이 sender 당시 썼던 peer.rx_base_pa).

수신 측 (_credit_worker):

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. IpcqInitEntrypeer_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에 검증 가능:

# 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가 수신)는 불변.