# 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은 PE 라우터에 attach된다 현재의 `hbm_ctrl.slice{0-7}` (8개 노드)를 **`hbm_ctrl` 단일 노드**로 통합하고, PE가 attach된 라우터에 HBM access point도 함께 attach한다. - n:1 mode: PE의 local HBM 접근은 자기 라우터에서 바로 (switching overhead만, 0 hop) - remote PE의 HBM 접근: mesh hop을 거쳐 대상 PE의 라우터에 도달 - HBM controller 내부의 read/write resource 모델은 유지 노드 네이밍 변경: | 현재 | 변경 후 | | ---- | ------- | | `sip0.cube0.hbm_ctrl.slice0` ~ `slice7` | `sip0.cube0.hbm_ctrl` (단일) | `mesh_gen.py`에서 PE attachment에 `pe{idx}.hbm`을 추가하여, builder가 해당 라우터와 hbm_ctrl 간 edge를 생성한다. --- ### D2. xbar, bridge, 단일 NOC 노드 완전 제거 기존 다음 노드 및 관련 edge를 모두 제거한다: - `{cube}.xbar_top`, `{cube}.xbar_bot` - `{cube}.bridge.left`, `{cube}.bridge.right` - `{cube}.noc` (단일 TwoDMeshNocComponent 노드) - `noc_to_xbar`, `xbar_to_noc`, `xbar_to_hbm`, `hbm_to_xbar` 종류의 edge - `xbar_to_bridge`, `bridge_to_xbar` 종류의 edge - `pe_to_noc`, `noc_to_pe`, `noc_to_pe_cpu` 등 단일 noc 노드 참조 edge 이들의 역할은 **cube_mesh.yaml 기반의 명시적 라우터 mesh**가 대체한다. 기존 `mesh_gen.py`가 생성하는 6×6 라우터 grid의 각 라우터(r0c0, r0c1, ...)를 별도의 SimPy 노드로 topology graph에 생성하고, 인접 라우터 간 XY mesh edge로 연결한다. --- ### D3. 명시적 라우터 mesh (n:1 / 1:1 공통 기반) #### cube_mesh.yaml 기반 라우터 노드 `mesh_gen.py`가 생성한 cube_mesh.yaml의 각 non-null 라우터를 topology graph의 **별도 SimPy 노드**로 생성한다. - 노드 ID: `{cube}.r{row}c{col}` (e.g., `sip0.cube0.r0c0`) - kind: `noc_router`, impl: `forwarding_v1` - pos_mm: cube_mesh.yaml에서 가져옴 기존 cube_mesh.yaml의 attach 정보에 따라 각 라우터에 component를 연결: - `pe{p}.dma` → PE_DMA ↔ 라우터 edge - `pe{p}.cpu` → PE_CPU ↔ 라우터 edge - `pe{p}.hbm` → HBM_CTRL ↔ 라우터 edge (n:1에서 추가) - `m_cpu` → M_CPU ↔ 라우터 edge - `sram` → SRAM ↔ 라우터 edge - `ucie_{dir}.c{i}` → UCIe conn ↔ 라우터 edge 라우터 간 XY mesh edge: 인접 라우터 간 bidirectional edge. null 라우터(HBM exclusion zone)는 skip. #### 1:1 mode 확장 (나중에 구현) 1:1 mode에서는 각 라우터가 N개 channel mini-router로 분화된다. per-channel routing과 ChannelSplitter (LA → per-channel PA) 도입이 필요. PE당 N개 GEMM engine도 이 시점에 추가. --- ### D4. cross-PE HBM 접근 (n:1 mode) n:1 mode에서 PE가 다른 PE의 local HBM에 접근하는 경우, cube_mesh.yaml의 XY mesh를 통해 대상 PE의 라우터까지 hop한다. 예: PE0(r0c0)이 PE2(r1c4)의 HBM에 접근: ```text PE0.pe_dma → r0c0 → r0c1 → r0c2 → r0c3 → r0c4 → r1c4 → hbm_ctrl ``` Dijkstra router가 mesh에서 최단 경로를 탐색한다. 1:1 mode에서의 cross-PE channel 접근은 D3의 1:1 확장 시 정의한다. --- ### D5. n:1 mode: cube_mesh.yaml 라우터 mesh 사용 n:1 mode에서는 별도의 "aggregated router"를 생성하지 않는다. 기존 cube_mesh.yaml의 라우터 grid가 그 역할을 한다. #### 연결 구조 각 PE가 attach된 라우터에 PE_DMA, PE_CPU, HBM이 함께 연결된다: ```text sip0.cube0.pe0.pe_dma ←→ sip0.cube0.r0c0 (bw: N × channel_bw_gbs) sip0.cube0.hbm_ctrl ←→ sip0.cube0.r0c0 (bw: N × channel_bw_gbs) ``` 라우터 간 XY mesh edge로 연결. PE의 local HBM 접근은 자기 라우터에서 바로 (switching overhead만). #### n:1 mode 전체 데이터 경로 **local HBM (0 hop):** ```text PE0.pe_dma → r0c0 → hbm_ctrl (switching overhead only) ``` **remote HBM (mesh hops):** ```text PE0.pe_dma → r0c0 → r0c1 → ... → r1c4 → hbm_ctrl ``` **M_CPU DMA:** ```text M_CPU → r2c0 → (mesh hops) → r{x}c{y} → hbm_ctrl ``` --- ### D6. 모든 트래픽을 동일 router mesh로 통일한다 - 모든 memory access (DMA data)와 command (PE_CPU)가 동일 router mesh를 사용한다 - local access도 별도의 fast path(xbar)를 사용하지 않는다 - cross-cube (remote) access 경로: ```text PE_DMA → r{x}c{y} → (mesh hops) → ucie_conn → ucie-{PORT} → [UCIe link] → remote ucie → remote conn → remote r{x}c{y} → hbm_ctrl ``` UCIe 연결은 기존 구조를 유지하되, 양쪽 endpoint가 xbar 대신 mesh 라우터가 된다. UCIe line 수는 BW 비율로 결정: `ucie_lines_per_side = ceil(ucie_bw / noc_line_bw)`. --- ### 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 계산이 제거된다. n:1 mode에서 PE_DMA는 자기 라우터에 attach된 hbm_ctrl에 직접 접근한다. 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: router_link_bw_gbs: 256.0 # 라우터 간 XY mesh link BW router_overhead_ns: 2.0 # 라우터 switching overhead pe_to_router_bw_gbs: 256.0 # PE_DMA ↔ 라우터 hbm_to_router_bw_gbs: 256.0 # HBM ↔ 라우터 (= N × channel_bw) ``` --- ### 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 - cube_mesh.yaml 기반 라우터 mesh로 물리적 배치를 정확히 반영한다 - n:1 mode에서 기존 VA 체계를 유지하여 전환 비용이 낮다 - local / remote / command 트래픽이 동일 mesh로 통일되어 단순하다 - graph compiler 기반 topology 생성과 잘 맞는다 - channel 수, PE 수가 모두 파라미터이므로 다양한 구성을 테스트할 수 있다 - 1:1 mode 확장이 라우터 분화로 자연스럽게 가능하다 ### Negative - 명시적 라우터 노드로 인해 SimPy 노드 수가 증가한다 (6×6 = 최대 32개 라우터/cube) - 기존 xbar/bridge/단일 NOC 기반 테스트 전면 재작성 필요 - TwoDMeshNocComponent의 내부 contention 모델을 라우터별 모델로 교체 필요 --- ## 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 경로 변경 영향