Add ADR-0018 (LA/BAAW addressing) and ADR-0019 (NOC per-channel HBM)

ADR-0018: LA replaces VA, BAAW segment-based mapping in PE_DMA,
1:1 (per-channel) and n:1 (aggregated) modes with parameterized
channel count.

ADR-0019: xbar/bridge removal, channel router topology with
horizontal line layout, aggregated router for n:1 mode,
unified NOC path for local/remote HBM access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 01:05:27 -07:00
parent 114510d4b9
commit 31c7110da7
2 changed files with 953 additions and 0 deletions
+513
View File
@@ -0,0 +1,513 @@
# ADR-0019: CUBE NOC 내 Per-Channel 및 Aggregated HBM 연결 모델
## Status
Proposed
## Context
ADR-0018에서는 LA 기반 주소 추상화와 BAAW를 도입하여,
logical memory access가 다음 두 형태의 request로 변환되도록 정의하였다.
- 1:1 mode: 하나의 logical access → N개의 per-channel request
- n:1 mode: 하나의 logical access → 하나의 aggregated request
여기서 N = `hbm_pseudo_channels / pes_per_cube` (= `channels_per_pe`)이며,
topology 파라미터로 결정된다.
### 기존 구조의 문제
현재 구현(`topology/builder.py`)에서는:
- PE_DMA → NOC → xbar_top/xbar_bot → HBM_CTRL.slice{0-7} 경로를 사용
- HBM은 8개 slice(= PE 수) 노드로 모델링됨
- local/remote access가 서로 다른 경로를 사용:
- local: NOC → xbar → HBM slice
- cross-half: NOC → xbar_top → bridge → xbar_bot → HBM slice
- remote cube: NOC → UCIe → remote NOC → remote xbar → remote HBM slice
이 구조의 한계:
- pseudo-channel 단위 모델링 불가 (slice = PE 단위, channel 단위 아님)
- xbar/bridge가 local/remote 경로를 이원화
- 1:1 / n:1 mode를 일관되게 표현할 수 없음
---
## Decision
### D1. HBM controller는 CUBE당 단일 endpoint로 정의한다
현재의 `hbm_ctrl.slice{0-7}` (8개 노드)를 **`hbm_ctrl` 단일 노드**로 통합한다.
- pseudo channel은 HBM controller 노드 자체가 아니라,
controller에 연결되는 **link의 단위**로 표현한다
- HBM controller 내부의 read/write resource 모델은 유지하되,
mode에 따라 contention 단위가 달라진다:
- 1:1 mode: per-channel link가 BW contention point (controller는 terminal)
- n:1 mode: aggregated link가 BW contention point (controller는 terminal)
노드 네이밍 변경:
| 현재 | 변경 후 |
| ---- | ------- |
| `sip0.cube0.hbm_ctrl.slice0` ~ `slice7` | `sip0.cube0.hbm_ctrl` (단일) |
---
### D2. xbar, bridge 완전 제거
기존 다음 노드 및 관련 edge를 모두 제거한다:
- `{cube}.xbar_top`, `{cube}.xbar_bot`
- `{cube}.bridge.left`, `{cube}.bridge.right`
- `noc_to_xbar`, `xbar_to_noc`, `xbar_to_hbm`, `hbm_to_xbar` 종류의 edge
- `xbar_to_bridge`, `bridge_to_xbar` 종류의 edge
이들의 역할(PE→HBM 라우팅, cross-half 연결)은
channel router 및 horizontal line 연결이 대체한다 (D3, D4 참조).
---
### D3. 1:1 mode: per-channel router 기반 연결
#### channel router 정의
1:1 mode에서 graph compiler는 pseudo-channel 수만큼의 **channel router** 노드를
생성한다. channel router는 NOC의 일부이다.
```text
파라미터 예: hbm_pseudo_channels=64, pes_per_cube=8
→ channels_per_pe = 8, 총 64개 channel router 생성
```
노드 네이밍: `{cube}.ch_r{global_channel_id}`
| PE | 소유 channel routers |
| -- | -------------------- |
| PE0 | ch_r0, ch_r1, ..., ch_r7 |
| PE1 | ch_r8, ch_r9, ..., ch_r15 |
| ... | ... |
| PE7 | ch_r56, ch_r57, ..., ch_r63 |
일반화: PE `p`는 channel `p * channels_per_pe` ~ `(p+1) * channels_per_pe - 1`을 소유.
#### PE_DMA ↔ channel router 연결
각 PE_DMA는 자신의 local channel router N개와 양방향 link로 연결된다:
```text
sip0.cube0.pe0.pe_dma ←→ sip0.cube0.ch_r0 (bw: channel_bw_gbs)
sip0.cube0.pe0.pe_dma ←→ sip0.cube0.ch_r1 (bw: channel_bw_gbs)
...
sip0.cube0.pe0.pe_dma ←→ sip0.cube0.ch_r7 (bw: channel_bw_gbs)
```
- edge kind: `pe_to_ch_router` / `ch_router_to_pe`
- BW: `hbm_channel_bw_gbs` (e.g., 32 GB/s)
- distance: PE에서 channel router까지의 물리적 거리 (layout 기반)
#### channel router ↔ HBM controller 연결
각 channel router는 cube의 hbm_ctrl과 양방향 link로 연결된다:
```text
sip0.cube0.ch_r0 ←→ sip0.cube0.hbm_ctrl (bw: channel_bw_gbs)
sip0.cube0.ch_r1 ←→ sip0.cube0.hbm_ctrl (bw: channel_bw_gbs)
...
sip0.cube0.ch_r63 ←→ sip0.cube0.hbm_ctrl (bw: channel_bw_gbs)
```
- edge kind: `ch_router_to_hbm` / `hbm_to_ch_router`
- BW: `hbm_channel_bw_gbs` (e.g., 32 GB/s)
#### 1:1 mode 전체 데이터 경로
```text
PE0.pe_dma
├→ ch_r0 → hbm_ctrl (32 GB/s)
├→ ch_r1 → hbm_ctrl (32 GB/s)
├→ ...
└→ ch_r7 → hbm_ctrl (32 GB/s)
총 PE0 local BW = N × channel_bw_gbs
```
---
### D4. 1:1 mode: horizontal line 연결 (cross-PE channel 접근)
#### 배치 규칙
같은 **logical index**를 가지는 channel router들을 동일한 horizontal row에 배치한다.
logical index 정의: `logical_idx = global_channel_id % channels_per_pe`
```text
파라미터 예: channels_per_pe=8, pes_per_cube=8
Row 0: ch_r0 (PE0) ↔ ch_r8 (PE1) ↔ ch_r16 (PE2) ↔ ... ↔ ch_r56 (PE7)
Row 1: ch_r1 (PE0) ↔ ch_r9 (PE1) ↔ ch_r17 (PE2) ↔ ... ↔ ch_r57 (PE7)
Row 2: ch_r2 (PE0) ↔ ch_r10 (PE1) ↔ ch_r18 (PE2) ↔ ... ↔ ch_r58 (PE7)
...
Row 7: ch_r7 (PE0) ↔ ch_r15 (PE1) ↔ ch_r23 (PE2) ↔ ... ↔ ch_r63 (PE7)
```
일반화: Row `r`에는 `{ch_r(p * N + r) | p ∈ 0..pes_per_cube-1}`이 위치.
여기서 `N = channels_per_pe`.
#### horizontal line edge
같은 row에서 인접한 channel router끼리 양방향 edge로 연결:
```text
ch_r0 ↔ ch_r8 ↔ ch_r16 ↔ ... ↔ ch_r56
```
- edge kind: `ch_horizontal`
- BW: `hbm_channel_bw_gbs` (or configurable inter-PE channel BW)
- distance: PE 간 물리적 거리
#### cross-PE HBM 접근 경로 (1:1 mode)
PE0이 PE1의 local channel (ch_r8)에 접근하는 경우:
```text
PE0.pe_dma → ch_r0 → ch_r8 (horizontal hop) → hbm_ctrl
```
Dijkstra router가 horizontal line을 통해 최단 경로를 탐색한다.
#### 설계 의도
이 배치 규칙은:
- routing 규칙 단순화: horizontal = cross-PE, vertical = PE-local
- 거리 계산 단순화: row 내 hop 수 = |src_pe - dst_pe|
- 구조적 반복성 확보: 모든 row가 동일한 구조
---
### D5. n:1 mode: aggregated router 기반 연결
#### aggregated router 정의
n:1 mode에서 graph compiler는 PE당 1개의 **aggregated router** 노드를 생성한다.
aggregated router는 NOC의 일부이다.
노드 네이밍: `{cube}.pe{p}.agg_router`
#### 연결 구조
```text
sip0.cube0.pe0.pe_dma ←→ sip0.cube0.pe0.agg_router (bw: N × channel_bw_gbs)
sip0.cube0.pe0.agg_router ←→ sip0.cube0.hbm_ctrl (bw: N × channel_bw_gbs)
```
- edge kind: `pe_to_agg_router` / `agg_router_to_pe`, `agg_to_hbm` / `hbm_to_agg`
- BW: `channels_per_pe × hbm_channel_bw_gbs` (e.g., 8 × 32 = 256 GB/s)
#### cross-PE 접근 (n:1 mode)
PE0이 PE1의 local HBM에 접근하는 경우:
```text
PE0.pe_dma → PE0.agg_router → PE1.agg_router → hbm_ctrl
```
aggregated router 간 연결:
```text
pe0.agg_router ↔ pe1.agg_router ↔ pe2.agg_router ↔ ... ↔ pe7.agg_router
```
- edge kind: `agg_horizontal`
- BW: configurable (inter-PE aggregated BW)
#### n:1 mode 전체 데이터 경로
```text
PE0.pe_dma → PE0.agg_router → hbm_ctrl
(BW = N × channel_bw_gbs = 256 GB/s)
```
---
### D6. local / remote access를 NOC로 통일한다
- 모든 memory access는 NOC(channel router 또는 aggregated router)를 통해 전달된다
- local access도 별도의 fast path(xbar)를 사용하지 않는다
- cross-cube (remote) access 경로:
```text
1:1 mode: PE_DMA → ch_r{local} → ch_r{...} → UCIe → remote_ch_r → remote_hbm_ctrl
n:1 mode: PE_DMA → agg_router → UCIe → remote_agg_router → remote_hbm_ctrl
```
UCIe 연결은 기존 구조를 유지하되,
양쪽 endpoint가 xbar 대신 channel router 또는 aggregated router가 된다.
---
### D7. AddressResolver 변경
현재 `AddressResolver.resolve()`:
```python
# 현재: HBM offset → pe_slice → "sip{s}.cube{c}.hbm_ctrl.slice{pe_slice}"
pe_slice = PhysAddr.hbm_pe_id(addr.hbm_offset, self._slice_size_bytes)
return f"sip{s}.cube{c}.hbm_ctrl.slice{pe_slice}"
```
변경 후:
```python
# 변경: HBM → 단일 endpoint
return f"sip{s}.cube{c}.hbm_ctrl"
```
pe_slice 계산이 제거된다.
BAAW가 이미 dst_node를 결정하므로, PE_DMA의 1:1 mode에서는
resolver를 거치지 않고 BAAW가 직접 channel router node_id를 반환한다.
n:1 mode에서도 BAAW가 aggregated router node_id를 반환한다.
resolver.resolve()는 외부 접근(M_CPU DMA 등) 및 backward compatibility용으로 유지한다.
---
### D8. topology.yaml 설정 변경
#### 추가 설정
```yaml
cube:
memory_map:
hbm_mapping_mode: n_to_one # one_to_one | n_to_one
hbm_pseudo_channels: 64 # 전체 pseudo channel 수
hbm_channels_per_pe: 8 # PE당 local channel 수 (= pseudo_channels / pes_per_cube)
hbm_channel_bw_gbs: 32.0 # per-channel bandwidth (GB/s)
hbm_total_gb_per_cube: 48 # 유지
```
#### 제거 설정
```yaml
# 제거 대상
links:
xbar_to_hbm_bw_gbs: 256.0 # → channel_bw_gbs × channels_per_pe로 대체
xbar_to_hbm_mm: 2.5 # → ch_router_to_hbm_mm으로 대체
xbar_to_bridge_bw_gbs: 128.0 # → 제거 (bridge 없음)
xbar_to_bridge_mm: 3.0 # → 제거
noc_to_xbar_bw_gbs: ... # → 제거
noc_to_xbar_mm: ... # → 제거
```
#### 추가 link 설정
```yaml
links:
pe_to_ch_router_bw_gbs: 32.0 # PE_DMA ↔ channel router
pe_to_ch_router_mm: 1.0 # 물리적 거리
ch_router_to_hbm_bw_gbs: 32.0 # channel router ↔ hbm_ctrl
ch_router_to_hbm_mm: 2.0 # 물리적 거리
ch_horizontal_bw_gbs: 32.0 # channel router 간 horizontal link
ch_horizontal_mm: 1.5 # PE 간 horizontal 거리
# n:1 mode용
pe_to_agg_router_bw_gbs: 256.0 # PE_DMA ↔ aggregated router
agg_to_hbm_bw_gbs: 256.0 # aggregated router ↔ hbm_ctrl
agg_horizontal_bw_gbs: 256.0 # aggregated router 간 link
```
---
### D9. 대역폭 수치 정합
| 구성 | 값 |
| ---- | --- |
| pseudo channels per cube | 64 (파라미터) |
| PEs per cube | 8 (파라미터) |
| channels per PE (N) | `pseudo_channels / pes_per_cube` = 8 |
| per-channel BW | 32 GB/s (파라미터) |
| per-PE local BW | N × 32 = 256 GB/s |
| cube total HBM BW | 64 × 32 = 2048 GB/s |
두 모드에서 PE당 effective BW는 동일:
- 1:1 mode: N개 channel link × channel_bw_gbs = N × 32 = 256 GB/s
- n:1 mode: 1개 aggregated link = N × channel_bw_gbs = 256 GB/s
---
## Consequences
### Positive
- 1:1 mode에서 pseudo-channel 단위 BW contention 모델링이 자연스럽다
- n:1 mode에서 aggregated bandwidth 모델이 단순하다
- local / remote access 경로가 NOC로 통일된다
- graph compiler 기반 topology 생성과 잘 맞는다
- channel 수, PE 수가 모두 파라미터이므로 다양한 구성을 테스트할 수 있다
### Negative
- 1:1 mode에서 router 및 link 수가 크게 증가한다
(64 channel routers + 64 edges to HBM + 56 horizontal edges per cube)
- local access도 NOC 경로를 사용하므로 모델이 더 일반화된다
- 기존 xbar 기반 테스트 전면 재작성 필요
- SimPy 노드 수 증가에 따른 시뮬레이션 성능 영향 가능
---
## Alternatives
### A1. 기존 xbar + HBM slice 유지
- local/remote 경로가 이원화됨
- pseudo-channel 단위 모델링 불가
- 1:1/n:1 mode 전환 불가
### A2. per-channel link를 항상 생성하고 n:1에서만 집계
- topology 구조가 항상 1:1 크기
- n:1 semantics를 link aggregation으로 표현하기 복잡
- router 노드 수 감소 효과 없음
### A3. 단계적 전환 (xbar 유지 + NOC 경로 추가)
- 호환성은 높으나 두 경로 공존으로 복잡도 증가
- 최종적으로 xbar 제거가 필요하므로 중간 단계의 가치가 낮음
---
## Implementation Notes
### topology/builder.py 변경 상세
#### 제거할 코드 (현재 `_instantiate_cube()` 내)
- xbar_top, xbar_bot 노드 생성 (~line 495-508)
- bridge.left, bridge.right 노드 생성
- noc ↔ xbar edge 생성 (~line 540-555)
- xbar ↔ hbm_ctrl.slice edge 생성 (~line 510-538)
- xbar ↔ bridge edge 생성 (~line 557-572)
#### 추가할 코드
1:1 mode:
```python
N = hbm_channels_per_pe # from topology config
total_ch = hbm_pseudo_channels
# channel router 노드 생성
for ch_id in range(total_ch):
pe_id = ch_id // N
nodes[f"{cp}.ch_r{ch_id}"] = Node(
id=f"{cp}.ch_r{ch_id}", kind="noc_router", impl="noc_v1",
attrs={}, pos_mm=(...), # horizontal row = ch_id % N
)
# PE_DMA ↔ local channel router edges
for pe_id in range(pes_per_cube):
for local_ch in range(N):
ch_id = pe_id * N + local_ch
edges.append(Edge(
src=f"{cp}.pe{pe_id}.pe_dma", dst=f"{cp}.ch_r{ch_id}",
bw_gbs=channel_bw, kind="pe_to_ch_router", ...))
edges.append(Edge(
src=f"{cp}.ch_r{ch_id}", dst=f"{cp}.pe{pe_id}.pe_dma",
bw_gbs=channel_bw, kind="ch_router_to_pe", ...))
# channel router ↔ hbm_ctrl edges
for ch_id in range(total_ch):
edges.append(Edge(
src=f"{cp}.ch_r{ch_id}", dst=f"{cp}.hbm_ctrl",
bw_gbs=channel_bw, kind="ch_router_to_hbm", ...))
edges.append(Edge(
src=f"{cp}.hbm_ctrl", dst=f"{cp}.ch_r{ch_id}",
bw_gbs=channel_bw, kind="hbm_to_ch_router", ...))
# horizontal line edges (same logical index)
for row in range(N):
for p in range(pes_per_cube - 1):
ch_a = p * N + row
ch_b = (p + 1) * N + row
edges.append(Edge(
src=f"{cp}.ch_r{ch_a}", dst=f"{cp}.ch_r{ch_b}",
bw_gbs=ch_horizontal_bw, kind="ch_horizontal", ...))
edges.append(Edge(
src=f"{cp}.ch_r{ch_b}", dst=f"{cp}.ch_r{ch_a}",
bw_gbs=ch_horizontal_bw, kind="ch_horizontal", ...))
```
n:1 mode:
```python
# aggregated router 노드 생성
for pe_id in range(pes_per_cube):
nodes[f"{cp}.pe{pe_id}.agg_router"] = Node(
id=f"{cp}.pe{pe_id}.agg_router", kind="noc_router", impl="noc_v1",
attrs={}, pos_mm=(...),
)
agg_bw = N * channel_bw # aggregated BW
# PE_DMA ↔ aggregated router
for pe_id in range(pes_per_cube):
edges.append(Edge(
src=f"{cp}.pe{pe_id}.pe_dma", dst=f"{cp}.pe{pe_id}.agg_router",
bw_gbs=agg_bw, kind="pe_to_agg_router", ...))
edges.append(Edge(
src=f"{cp}.pe{pe_id}.agg_router", dst=f"{cp}.pe{pe_id}.pe_dma",
bw_gbs=agg_bw, kind="agg_router_to_pe", ...))
# aggregated router ↔ hbm_ctrl
for pe_id in range(pes_per_cube):
edges.append(Edge(
src=f"{cp}.pe{pe_id}.agg_router", dst=f"{cp}.hbm_ctrl",
bw_gbs=agg_bw, kind="agg_to_hbm", ...))
edges.append(Edge(
src=f"{cp}.hbm_ctrl", dst=f"{cp}.pe{pe_id}.agg_router",
bw_gbs=agg_bw, kind="hbm_to_agg", ...))
# aggregated router 간 horizontal link
for p in range(pes_per_cube - 1):
edges.append(Edge(
src=f"{cp}.pe{p}.agg_router", dst=f"{cp}.pe{p+1}.agg_router",
bw_gbs=agg_horizontal_bw, kind="agg_horizontal", ...))
edges.append(Edge(
src=f"{cp}.pe{p+1}.agg_router", dst=f"{cp}.pe{p}.agg_router",
bw_gbs=agg_horizontal_bw, kind="agg_horizontal", ...))
```
### 영향받는 기존 테스트
| 테스트 파일 | 영향 |
| ---------- | ---- |
| `tests/test_topology_compile.py` | xbar/bridge 노드 참조 제거, channel router 검증 추가 |
| `tests/test_topology_load.py` | topology.yaml 설정 변경 반영 |
| `tests/test_pe_components.py` | PE_DMA 라우팅 경로 변경 |
| `tests/test_sip_parallel.py` | cross-PE 접근 경로 변경 |
| xbar/bridge를 직접 테스트하는 케이스 | 제거 |
---
## Test Requirements
- 1:1 mode에서 channel별 link로 request가 전달되는지 확인
- n:1 mode에서 aggregated link로 request가 전달되는지 확인
- 두 mode에서 topology가 올바르게 생성되는지 검증:
- 1:1: `total_ch`개 channel router + per-PE link + horizontal link
- n:1: `pes_per_cube`개 aggregated router + per-PE link
- 동일 workload에서 effective BW가 두 모드에서 일관적인지 확인
- cross-PE 접근 시 horizontal line routing이 동작하는지 확인
- cross-cube 접근 시 UCIe를 통한 routing이 동작하는지 확인
- 파라미터 변경 (channels_per_pe = 4, 8, 16 등)에서 topology 생성이 정상인지 확인
---
## Links
- ADR-0018 (LA + BAAW) → addressing 측 연동
- ADR-0017 (Cube NOC 2D Mesh) → 본 ADR이 xbar/bridge 부분을 대체
- ADR-0004 (Memory Semantics) → BW 모델 재정의
- ADR-0014 (PE Internal Execution Model) → PE_DMA 경로 변경 영향