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,441 @@
|
||||
# ADR-0018: LA-Based Memory Address Abstraction and HBM Channel Mapping Mode Introduction
|
||||
|
||||
## Status
|
||||
|
||||
Merged into ADR-0011 (Address Model: LA section).
|
||||
|
||||
## Context
|
||||
|
||||
Kernbench simulates memory access between PE_DMA and Local-HBM within a CUBE.
|
||||
Currently, a VA-based access path is used; however, the following two channel mapping models
|
||||
are difficult to represent consistently.
|
||||
|
||||
### Background: Local-HBM Pseudo Channel Structure
|
||||
|
||||
The HBM in a CUBE consists of 32 or 64 pseudo channels.
|
||||
In the PE-Local-HBM model, each PE is responsible for an equal number of pseudo channels.
|
||||
|
||||
Example: 64 pseudo channels, 8 PEs per cube -> each PE accesses 8 pseudo channels as local HBM
|
||||
|
||||
Both the number of pseudo channels and the number of PEs are topology parameters.
|
||||
`N = hbm_pseudo_channels / pes_per_cube` (= channels_per_pe) determines
|
||||
the number of local channels per PE.
|
||||
|
||||
The routing path BW between DMA and each pseudo channel matches the BW of each pseudo channel
|
||||
(e.g., 32 GB/s), so if a PE sends simultaneous requests to N channels, it can utilize the
|
||||
maximum memory BW.
|
||||
|
||||
### Limitations of the Current VA Model
|
||||
|
||||
When channels are divided into 8, requests must also be generated per channel and sent to DMA.
|
||||
However, in the current architecture, the kernel generates requests with VA (`tl.load`)
|
||||
and passes them directly to DMA, making it difficult for PE_CPU to generate per-channel DMA requests.
|
||||
|
||||
Therefore, instead of VA, we propose using **Logical Address (LA)**,
|
||||
where the **BAAW (Logical-to-Physical Mapping Unit)** inside PE_DMA
|
||||
converts LA to PA or a list of PAs based on segment-based mapping.
|
||||
|
||||
### Two Channel Mapping Modes
|
||||
|
||||
- **1:1 mode**: Creates and executes per-channel requests. Precise per-channel modeling.
|
||||
- **n:1 mode (default)**: Assumes interleaving across local HBM channels. Aggregated BW modeling.
|
||||
|
||||
By supporting both modes, the overhead of the n:1 mode can be measured and evaluated.
|
||||
|
||||
### Core Requirements
|
||||
|
||||
- The effective bandwidth semantics of PE_DMA -> HBM_CTRL must be identical in both modes
|
||||
- The difference must only be in the request representation and resource modeling approach
|
||||
- The kernel programming model must not be changed
|
||||
- Physical channel information must not be exposed to the kernel
|
||||
|
||||
### Existing Physical Address
|
||||
|
||||
The current system's 51-bit Physical Address is defined in `policy/address/phyaddr.py`:
|
||||
|
||||
```
|
||||
[50:47] rack_id (4 bit)
|
||||
[46:43] sip_id (4 bit)
|
||||
[42:38] cube_id (5 bit, sip_seg)
|
||||
[37] hbm_selector (1=HBM window)
|
||||
[36:0] hbm_offset (37 bit, 128GB per cube)
|
||||
```
|
||||
|
||||
PA is used to represent the final routable canonical physical destination,
|
||||
and this role is preserved.
|
||||
However, the timing and policy of logical access -> physical request conversion are not clearly separated.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
### D1. Introduction of LA (Logical Address) — Replacing VA
|
||||
|
||||
The existing VA (Virtual Address) infrastructure is replaced with LA (Logical Address).
|
||||
|
||||
#### Characteristics of LA
|
||||
|
||||
- Like VA, tensors can be mapped to a contiguous memory space
|
||||
- Represents logical buffer + offset
|
||||
- Does not directly contain physical channel information
|
||||
- An intermediate abstraction maintained until physical resolution
|
||||
- The sole address scheme used by kernel code (`tl.load`, `tl.store`, `tl.composite`)
|
||||
|
||||
#### LA Space Definition
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| LA start address | `0x1_0000_0000` (4 GB, preserving the existing VA start point) |
|
||||
| LA space size | 64 GB per PE |
|
||||
| Alignment unit | Segment-based (see D3 below) |
|
||||
|
||||
LA is a PE-local address space.
|
||||
Even if different PEs use the same LA value, they resolve to different PAs
|
||||
because each PE has a different BAAW segment table.
|
||||
|
||||
#### VA Infrastructure Removal Scope
|
||||
|
||||
With the introduction of LA, the following existing code will be replaced/removed:
|
||||
|
||||
| Removal Target | Replacement |
|
||||
|----------------|-------------|
|
||||
| `policy/address/va_allocator.py` (VirtualAllocator) | LA allocator (same free-list approach, name/role changed) |
|
||||
| `policy/address/pe_mmu.py` (PeMMU) | BAAW segment table (inside PE_DMA) |
|
||||
| `components/builtin/pe_mmu.py` (PeMmuComponent) | Removed — BAAW is internal PE_DMA logic, not a separate component |
|
||||
| `runtime_api/kernel.py`: MmuMapMsg, MmuUnmapMsg | Replaced with BaawSegmentInstallMsg |
|
||||
| `runtime_api/context.py`: VA alloc + MMU mapping install | LA alloc + BAAW segment install |
|
||||
| `runtime_api/tensor.py`: `va_base` field | `la_base` field |
|
||||
| `topology.yaml`: pe_mmu component entry | Removed |
|
||||
|
||||
---
|
||||
|
||||
### D2. Mapping Mode Configuration
|
||||
|
||||
The mapping mode is configured at the cube level in topology.yaml:
|
||||
|
||||
```yaml
|
||||
cube:
|
||||
memory_map:
|
||||
hbm_mapping_mode: n_to_one # one_to_one | n_to_one
|
||||
hbm_pseudo_channels: 64 # total pseudo channel count
|
||||
hbm_channels_per_pe: 8 # local channel count per PE
|
||||
hbm_channel_bw_gbs: 32.0 # per-channel bandwidth
|
||||
```
|
||||
|
||||
This configuration is referenced during graph compilation (topology builder) and BAAW initialization.
|
||||
|
||||
---
|
||||
|
||||
### D3. Segments and BAAW
|
||||
|
||||
#### Segment Definition
|
||||
|
||||
A segment is a logical allocation unit that partitions the LA space so that each segment
|
||||
maps to a specific HBM channel or channel group.
|
||||
|
||||
Segments are created by the runtime allocator during tensor deployment,
|
||||
and BAAW uses them to convert LA into physical requests.
|
||||
|
||||
#### BAAW Segment Table Entry
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class BaawSegment:
|
||||
la_base: int # segment start LA
|
||||
la_size: int # segment size (bytes)
|
||||
mode: str # "one_to_one" | "n_to_one"
|
||||
# 1:1 mode fields
|
||||
channel_count: int # number of channels assigned to this segment (e.g., 8)
|
||||
pa_bases: list[int] # per-channel PA start address list (len = channel_count)
|
||||
channel_ids: list[int] # per-channel logical IDs (e.g., [0,1,2,...,7])
|
||||
channel_size: int # per-channel size (la_size // channel_count)
|
||||
# n:1 mode fields
|
||||
agg_pa_base: int # aggregated PA start address
|
||||
agg_node_id: str # aggregated router node_id (for routing)
|
||||
```
|
||||
|
||||
#### Segment Lifecycle
|
||||
|
||||
1. **Allocation time** (tensor deploy):
|
||||
- RuntimeContext allocates LA space from the LA allocator
|
||||
- PEMemAllocator allocates per-channel PA (1:1) or aggregated PA (n:1)
|
||||
- Sends `BaawSegmentInstallMsg` to PE_DMA to register in the segment table
|
||||
|
||||
2. **Usage time** (kernel execution):
|
||||
- Kernel issues `tl.load(la_ptr)` -> DmaReadCmd(src_addr=LA)
|
||||
- PE_DMA looks up the segment corresponding to the LA in BAAW
|
||||
- Converts to PA(s) according to the mode
|
||||
|
||||
3. **Deallocation time** (tensor free):
|
||||
- Removed from the segment table
|
||||
- LA space returned, PA deallocated
|
||||
|
||||
---
|
||||
|
||||
### D4. BAAW (Logical-to-Physical Mapping Unit)
|
||||
|
||||
#### Location
|
||||
|
||||
BAAW is placed as a front-end stage inside PE_DMA.
|
||||
It is not a separate SimPy component; it is synchronous address resolution logic
|
||||
executed at the beginning of PE_DMA's `handle_command()`.
|
||||
|
||||
#### Input
|
||||
|
||||
- LA (Logical Address) — DmaReadCmd.src_addr or DmaWriteCmd.dst_addr
|
||||
- access size (bytes)
|
||||
|
||||
#### Output
|
||||
|
||||
- 1:1 mode: `list[PhysicalRequest]` — each request is (PA, nbytes, channel_node_id)
|
||||
- n:1 mode: 1 `PhysicalRequest` — (agg_PA, nbytes, agg_node_id)
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class PhysicalRequest:
|
||||
pa: int # 51-bit Physical Address
|
||||
nbytes: int # transfer size for this request
|
||||
dst_node: str # target node_id (channel router or aggregated router)
|
||||
```
|
||||
|
||||
#### BAAW Resolve Logic
|
||||
|
||||
```python
|
||||
def resolve(self, la: int, nbytes: int) -> list[PhysicalRequest]:
|
||||
seg = self._find_segment(la) # la_base <= la < la_base + la_size
|
||||
offset = la - seg.la_base
|
||||
|
||||
if seg.mode == "n_to_one":
|
||||
pa = seg.agg_pa_base + offset
|
||||
return [PhysicalRequest(pa=pa, nbytes=nbytes, dst_node=seg.agg_node_id)]
|
||||
|
||||
elif seg.mode == "one_to_one":
|
||||
requests = []
|
||||
per_ch_size = seg.channel_size
|
||||
for i, (pa_base, ch_id) in enumerate(zip(seg.pa_bases, seg.channel_ids)):
|
||||
ch_offset = offset % per_ch_size # interleaved or striped
|
||||
ch_nbytes = nbytes // seg.channel_count
|
||||
pa = pa_base + ch_offset
|
||||
dst_node = f"{self._pe_prefix}.ch_r{ch_id}"
|
||||
requests.append(PhysicalRequest(pa=pa, nbytes=ch_nbytes, dst_node=dst_node))
|
||||
return requests
|
||||
```
|
||||
|
||||
#### Scope of Responsibility
|
||||
|
||||
BAAW is responsible for:
|
||||
- Converting logical accesses into physical request units
|
||||
- Performing fan-out (1:1) or pass-through (n:1) according to the mapping mode
|
||||
- Generating Physical Addresses and determining target nodes
|
||||
|
||||
BAAW is NOT responsible for:
|
||||
- Performing actual data movement
|
||||
- Executing NOC routing
|
||||
- Simulating bandwidth consumption (this is the role of downstream components)
|
||||
|
||||
#### Output Contract
|
||||
|
||||
The output of BAAW must be request units that can be directly used by the simulator's
|
||||
routing and resource model without any additional address decoding.
|
||||
|
||||
---
|
||||
|
||||
### D5. PE_DMA handle_command() Changes
|
||||
|
||||
#### Current Flow (VA-based)
|
||||
|
||||
```
|
||||
DmaReadCmd.src_addr (VA)
|
||||
-> MMU.translate(VA) -> PA
|
||||
-> PhysAddr.decode(PA) -> PhysAddr object
|
||||
-> resolver.resolve(PhysAddr) -> dst_node_id (e.g., "sip0.cube0.hbm_ctrl")
|
||||
-> router.find_path(pe_prefix, dst_node_id) -> path
|
||||
-> 1 sub-Transaction created -> fabric inject
|
||||
```
|
||||
|
||||
#### New Flow (LA-based)
|
||||
|
||||
```
|
||||
DmaReadCmd.src_addr (LA)
|
||||
-> BAAW.resolve(LA, nbytes) -> list[PhysicalRequest]
|
||||
-> For each PhysicalRequest:
|
||||
-> router.find_path(pe_prefix, req.dst_node) -> path
|
||||
-> compute_drain_ns(path, req.nbytes) -> drain
|
||||
-> sub-Transaction created -> fabric inject
|
||||
-> Wait for all sub-Transactions to complete
|
||||
-> pe_txn.done.succeed()
|
||||
```
|
||||
|
||||
Key changes:
|
||||
- MMU reference removed -> replaced with BAAW resolve
|
||||
- PhysAddr.decode() + resolver.resolve() -> BAAW directly returns dst_node
|
||||
- 1 request -> N requests injected in parallel (1:1 mode)
|
||||
|
||||
---
|
||||
|
||||
### D6. 1:1 Mode Details
|
||||
|
||||
- One logical access -> N (= `channels_per_pe`) physical requests
|
||||
- N is a parameter determined by `hbm_pseudo_channels / pes_per_cube`
|
||||
- Each request:
|
||||
- Fully resolved 51-bit PA
|
||||
- Targets a specific channel router (`{pe_prefix}.ch_r{channel_id}`)
|
||||
- BW contention modeling via per-channel links
|
||||
- PE_DMA injects N sub-transactions simultaneously
|
||||
|
||||
#### 1:1 Mode Example
|
||||
|
||||
Configuration: `hbm_pseudo_channels=64`, `pes_per_cube=8`
|
||||
-> `channels_per_pe=8`, PE0 owns ch0-7
|
||||
|
||||
```text
|
||||
Tensor A (4 KB) -> LA 0x1_0000_0000, size=4096 bytes
|
||||
BAAW segment: {
|
||||
la_base: 0x1_0000_0000, la_size: 4096,
|
||||
mode: "one_to_one", channel_count: 8, # = channels_per_pe
|
||||
pa_bases: [PA_ch0, PA_ch1, ..., PA_ch7],
|
||||
channel_ids: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||
channel_size: 512, # = la_size / channel_count
|
||||
}
|
||||
|
||||
BAAW resolve result (N=8 requests):
|
||||
-> PhysicalRequest(pa=PA_ch0, nbytes=512, dst_node="sip0.cube0.pe0.ch_r0")
|
||||
-> PhysicalRequest(pa=PA_ch1, nbytes=512, dst_node="sip0.cube0.pe0.ch_r1")
|
||||
-> ...
|
||||
-> PhysicalRequest(pa=PA_ch7, nbytes=512, dst_node="sip0.cube0.pe0.ch_r7")
|
||||
|
||||
PE_DMA: N sub-transactions injected in parallel
|
||||
Each accesses HBM via channel router -> hbm_ctrl link (channel_bw_gbs)
|
||||
Total effective BW = N x channel_bw_gbs
|
||||
```
|
||||
|
||||
Examples with different N values:
|
||||
- `hbm_pseudo_channels=32`, `pes_per_cube=8` -> `channels_per_pe=4`, 4 requests
|
||||
- `hbm_pseudo_channels=64`, `pes_per_cube=4` -> `channels_per_pe=16`, 16 requests
|
||||
|
||||
---
|
||||
|
||||
### D7. n:1 Mode Details
|
||||
|
||||
- One logical access -> one aggregated request
|
||||
- Target: aggregated router -> hbm_ctrl (see ADR-0019)
|
||||
- Aggregated link BW = `channels_per_pe` x `channel_bw_gbs` (e.g., 8 x 32 = 256 GB/s)
|
||||
- Modeled as a single queue / resource
|
||||
- No per-channel PA decomposition
|
||||
|
||||
#### n:1 Mode Example
|
||||
|
||||
```
|
||||
Tensor A (4 KB) -> LA 0x1_0000_0000, size=4096 bytes
|
||||
BAAW segment: {
|
||||
la_base: 0x1_0000_0000, la_size: 4096,
|
||||
mode: "n_to_one",
|
||||
agg_pa_base: PA_agg,
|
||||
agg_node_id: "sip0.cube0.pe0.agg_router",
|
||||
}
|
||||
|
||||
BAAW resolve result:
|
||||
-> PhysicalRequest(pa=PA_agg, nbytes=4096, dst_node="sip0.cube0.pe0.agg_router")
|
||||
|
||||
PE_DMA: 1 sub-transaction injected
|
||||
Accesses HBM via aggregated router -> hbm_ctrl link (256 GB/s)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### D8. Kernel Model Preservation
|
||||
|
||||
- The kernel still issues only single memory ops (`tl.load`, `tl.store`, `tl.composite`)
|
||||
- LA is the address scheme passed to the kernel
|
||||
- Channel decomposition/aggregation is performed by BAAW inside PE_DMA
|
||||
- Physical channel information is not exposed to kernel code
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- 1:1 vs n:1 semantics are clearly separated at a single point: BAAW
|
||||
- Kernel abstraction is preserved — no kernel code changes required
|
||||
- Topology-based policy control is possible (mode switching via yaml)
|
||||
- Improved simulation model consistency and debuggability
|
||||
- Segment-based mapping is simpler and has lower overhead compared to page tables
|
||||
|
||||
### Negative
|
||||
|
||||
- Full refactoring of VA/MMU-based code is required
|
||||
- Increased complexity in the request generation path (managing N requests in 1:1 mode)
|
||||
- Reduced per-channel visibility in n:1 mode
|
||||
- Existing VA-related tests must be rewritten
|
||||
|
||||
---
|
||||
|
||||
## Alternatives
|
||||
|
||||
### A1. Keep VA + Fan-out at MMU
|
||||
|
||||
- Extend MMU to return per-channel PAs
|
||||
- Problem: MMU's role expands beyond address translation to include request decomposition
|
||||
- Problem: Aggregation representation is difficult in n:1 mode
|
||||
|
||||
### A2. Kernel Generates Channel-Aware Requests
|
||||
|
||||
- Kernel directly calls per-channel load/store
|
||||
- Problem: Abstraction leakage, reduced portability
|
||||
- Problem: All benchmark code must be modified
|
||||
|
||||
### A3. Always Use PA (Without LA)
|
||||
|
||||
- Runtime directly passes per-channel PA to the kernel
|
||||
- Problem: Conflicts with the aggregation model
|
||||
- Problem: Conversion timing is unclear, channel information exposed to kernel
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Implementation Order
|
||||
|
||||
1. Introduce LA type (`policy/address/la_allocator.py`)
|
||||
2. Implement BAAW segment table (`policy/address/baaw.py`)
|
||||
3. Add `BaawSegmentInstallMsg` message type (`runtime_api/kernel.py`)
|
||||
4. Integrate BAAW into PE_DMA (`components/builtin/pe_dma.py` handle_command changes)
|
||||
5. Modify RuntimeContext: LA alloc + segment install (`runtime_api/context.py`)
|
||||
6. Change Tensor.va_base -> la_base (`runtime_api/tensor.py`)
|
||||
7. Remove VA/MMU code
|
||||
8. Remove pe_mmu from topology.yaml, add mapping mode configuration
|
||||
9. Test migration
|
||||
|
||||
### Affected Existing Tests
|
||||
|
||||
| Test File | Impact |
|
||||
|-----------|--------|
|
||||
| `tests/test_mmu_component.py` | Remove -> replace with BAAW segment install test |
|
||||
| `tests/test_mmu_fabric.py` | Remove -> replace with BAAW + fabric integration test |
|
||||
| `tests/test_pe_mmu.py` | Remove |
|
||||
| `tests/test_va_allocator.py` | Replace with LA allocator test |
|
||||
| `tests/test_va_integration.py` | Replace with LA + BAAW integration test |
|
||||
| `tests/test_va_offset.py` | Replace with LA offset test |
|
||||
|
||||
---
|
||||
|
||||
## Test Requirements
|
||||
|
||||
- For the same logical access:
|
||||
- 1:1 -> verify N requests are generated
|
||||
- n:1 -> verify 1 aggregated request is generated
|
||||
- Verify effective bandwidth consistency across both modes
|
||||
- 1:1 -> verify per-channel contention modeling
|
||||
- n:1 -> verify aggregated bandwidth is reflected
|
||||
- Verify operation without kernel code changes
|
||||
- Verify correct BAAW segment install/uninstall operation
|
||||
- Verify no conflicts when multiple tensors are assigned to different segments
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- ADR-0011 (Memory Addressing Simplification — PA-first, VA/MMU introduction) -> superseded by this ADR
|
||||
- ADR-0019 (NOC Per-Channel HBM Connection Model) -> topology-side integration
|
||||
- ADR-0014 (PE Internal Execution Model) -> PE_DMA change impact
|
||||
@@ -0,0 +1,440 @@
|
||||
# ADR-0018: LA 기반 메모리 주소 추상화 및 HBM Channel Mapping Mode 도입
|
||||
|
||||
## Status
|
||||
|
||||
Merged into ADR-0011 (Address Model: LA section).
|
||||
|
||||
## Context
|
||||
|
||||
Kernbench는 CUBE 내부에서 PE_DMA와 Local-HBM 간의 메모리 접근을 시뮬레이션한다.
|
||||
현재는 VA 기반 접근 경로를 사용하고 있으나, 다음 두 가지 channel mapping 모델을
|
||||
일관되게 표현하기 어렵다.
|
||||
|
||||
### 배경: Local-HBM pseudo channel 구조
|
||||
|
||||
CUBE의 HBM은 32개 또는 64개의 pseudo channel로 구성된다.
|
||||
PE-Local-HBM 모델에서는 각 PE가 동일한 수의 pseudo channel을 담당한다.
|
||||
|
||||
예: 64 pseudo channel, 8 PE per cube → 각 PE가 8개 pseudo channel을 local HBM으로 접근
|
||||
|
||||
pseudo channel 수와 PE 수는 모두 topology 파라미터이다.
|
||||
`N = hbm_pseudo_channels / pes_per_cube` (= channels_per_pe)가
|
||||
PE당 local channel 수를 결정한다.
|
||||
|
||||
각 pseudo channel의 BW(예: 32 GB/s)만큼 DMA와 pseudo channel 사이의 라우팅 경로 BW도
|
||||
맞춰지므로, PE가 N개 채널에 동시 request를 보내면 최대 메모리 BW를 활용할 수 있다.
|
||||
|
||||
### 현재 VA 모델의 한계
|
||||
|
||||
채널을 8개로 나누면 request도 채널별로 생성되어 DMA에 보내져야 한다.
|
||||
그러나 현재 구조에서는 커널이 VA를 가지고 request를 생성한 뒤(`tl.load`)
|
||||
DMA에 바로 전달하므로, PE_CPU가 채널별 DMA request를 생성하기 어렵다.
|
||||
|
||||
따라서 VA 대신 **Logical Address(LA)** 를 사용하고,
|
||||
PE_DMA 내부의 **BAAW(Logical-to-Physical Mapping Unit)** 가
|
||||
segment-based mapping을 기반으로 LA → PA 또는 PA 리스트로 변환하는 구조를 제안한다.
|
||||
|
||||
### 두 가지 channel mapping mode
|
||||
|
||||
- **1:1 mode**: 채널별 request를 만들어 실행. 정밀한 per-channel 모델링
|
||||
- **n:1 mode (default)**: local HBM 채널 간 인터리빙 가정. aggregated BW 모델링
|
||||
|
||||
두 모드를 지원하여 n:1 모드의 오버헤드를 측정/검토할 수 있게 한다.
|
||||
|
||||
### 핵심 요구사항
|
||||
|
||||
- PE_DMA → HBM_CTRL의 effective bandwidth semantics는 두 모드에서 동일해야 한다
|
||||
- 차이는 request 표현 방식과 resource 모델링 방식에만 있어야 한다
|
||||
- kernel programming model은 변경하지 않는다
|
||||
- physical channel 정보는 kernel에 노출되지 않아야 한다
|
||||
|
||||
### 기존 Physical Address
|
||||
|
||||
현재 시스템의 51-bit Physical Address는 `policy/address/phyaddr.py`에 정의되어 있다:
|
||||
|
||||
```
|
||||
[50:47] rack_id (4 bit)
|
||||
[46:43] sip_id (4 bit)
|
||||
[42:38] cube_id (5 bit, sip_seg)
|
||||
[37] hbm_selector (1=HBM window)
|
||||
[36:0] hbm_offset (37 bit, 128GB per cube)
|
||||
```
|
||||
|
||||
PA는 최종 라우팅 가능한 canonical physical destination을 표현하는 데 사용되며,
|
||||
이 역할은 유지된다.
|
||||
하지만 logical access → physical request 변환 시점과 정책이 명확히 분리되어 있지 않다.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
### D1. LA (Logical Address) 도입 — VA를 대체
|
||||
|
||||
기존 VA(Virtual Address) 인프라를 LA(Logical Address)로 대체한다.
|
||||
|
||||
#### LA의 특징
|
||||
|
||||
- VA처럼 Tensor를 연속적인 메모리 공간에 매핑할 수 있다
|
||||
- logical buffer + offset을 표현
|
||||
- physical channel 정보를 직접 포함하지 않음
|
||||
- physical resolution 이전까지 유지되는 중간 추상화
|
||||
- 커널 코드(`tl.load`, `tl.store`, `tl.composite`)가 사용하는 유일한 주소 체계
|
||||
|
||||
#### LA 공간 정의
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| LA 시작 주소 | `0x1_0000_0000` (4 GB, 기존 VA 시작점 유지) |
|
||||
| LA 공간 크기 | PE당 64 GB |
|
||||
| 정렬 단위 | segment 단위 (아래 D3 참조) |
|
||||
|
||||
LA는 PE-local 주소 공간이다.
|
||||
서로 다른 PE가 동일한 LA 값을 사용해도 BAAW의 segment table이 다르므로
|
||||
서로 다른 PA로 resolve된다.
|
||||
|
||||
#### VA 인프라 제거 범위
|
||||
|
||||
LA 도입에 따라 다음 기존 코드를 대체/제거한다:
|
||||
|
||||
| 제거 대상 | 대체 |
|
||||
|-----------|------|
|
||||
| `policy/address/va_allocator.py` (VirtualAllocator) | LA allocator (동일 free-list 방식, 이름/역할 변경) |
|
||||
| `policy/address/pe_mmu.py` (PeMMU) | BAAW segment table (PE_DMA 내부) |
|
||||
| `components/builtin/pe_mmu.py` (PeMmuComponent) | 제거 — BAAW는 별도 컴포넌트가 아닌 PE_DMA 내부 로직 |
|
||||
| `runtime_api/kernel.py`: MmuMapMsg, MmuUnmapMsg | BaawSegmentInstallMsg로 대체 |
|
||||
| `runtime_api/context.py`: VA alloc + MMU mapping install | LA alloc + BAAW segment install |
|
||||
| `runtime_api/tensor.py`: `va_base` 필드 | `la_base` 필드 |
|
||||
| `topology.yaml`: pe_mmu 컴포넌트 항목 | 제거 |
|
||||
|
||||
---
|
||||
|
||||
### D2. Mapping Mode 설정
|
||||
|
||||
topology.yaml의 cube 레벨에서 mapping mode를 설정한다:
|
||||
|
||||
```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 수
|
||||
hbm_channel_bw_gbs: 32.0 # per-channel bandwidth
|
||||
```
|
||||
|
||||
이 설정은 graph compiler(topology builder)와 BAAW 초기화 시 참조된다.
|
||||
|
||||
---
|
||||
|
||||
### D3. Segment 및 BAAW
|
||||
|
||||
#### Segment 정의
|
||||
|
||||
Segment는 LA space를 partition하여, 각 segment가 특정 HBM channel 또는
|
||||
channel group에 매핑되도록 하는 logical allocation 단위이다.
|
||||
|
||||
Segment는 runtime allocator가 tensor deploy 시 생성하며,
|
||||
BAAW는 이를 기반으로 LA를 physical request로 변환한다.
|
||||
|
||||
#### BAAW Segment Table Entry
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class BaawSegment:
|
||||
la_base: int # segment 시작 LA
|
||||
la_size: int # segment 크기 (bytes)
|
||||
mode: str # "one_to_one" | "n_to_one"
|
||||
# 1:1 mode fields
|
||||
channel_count: int # 이 segment에 할당된 channel 수 (e.g., 8)
|
||||
pa_bases: list[int] # per-channel PA 시작 주소 리스트 (len = channel_count)
|
||||
channel_ids: list[int] # per-channel 논리적 ID (e.g., [0,1,2,...,7])
|
||||
channel_size: int # per-channel 크기 (la_size // channel_count)
|
||||
# n:1 mode fields
|
||||
agg_pa_base: int # aggregated PA 시작 주소
|
||||
agg_node_id: str # aggregated router node_id (for routing)
|
||||
```
|
||||
|
||||
#### Segment 라이프사이클
|
||||
|
||||
1. **할당 시점** (tensor deploy):
|
||||
- RuntimeContext가 LA allocator에서 LA 공간 할당
|
||||
- PEMemAllocator가 per-channel PA 할당 (1:1) 또는 aggregated PA 할당 (n:1)
|
||||
- `BaawSegmentInstallMsg`를 PE_DMA로 전송하여 segment table에 등록
|
||||
|
||||
2. **사용 시점** (kernel 실행):
|
||||
- 커널이 `tl.load(la_ptr)` → DmaReadCmd(src_addr=LA)
|
||||
- PE_DMA가 BAAW에서 LA에 해당하는 segment를 lookup
|
||||
- mode에 따라 PA(들)로 변환
|
||||
|
||||
3. **해제 시점** (tensor free):
|
||||
- segment table에서 제거
|
||||
- LA 공간 반환, PA 해제
|
||||
|
||||
---
|
||||
|
||||
### D4. BAAW (Logical-to-Physical Mapping Unit)
|
||||
|
||||
#### 위치
|
||||
|
||||
BAAW는 PE_DMA 내부의 front-end stage로 배치된다.
|
||||
별도의 SimPy 컴포넌트가 아니며, PE_DMA의 `handle_command()` 시작 부분에서 실행되는
|
||||
동기적 address resolution 로직이다.
|
||||
|
||||
#### 입력
|
||||
|
||||
- LA (Logical Address) — DmaReadCmd.src_addr 또는 DmaWriteCmd.dst_addr
|
||||
- access size (bytes)
|
||||
|
||||
#### 출력
|
||||
|
||||
- 1:1 mode: `list[PhysicalRequest]` — 각 request는 (PA, nbytes, channel_node_id)
|
||||
- n:1 mode: `PhysicalRequest` 1개 — (agg_PA, nbytes, agg_node_id)
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class PhysicalRequest:
|
||||
pa: int # 51-bit Physical Address
|
||||
nbytes: int # 이 request의 transfer size
|
||||
dst_node: str # target node_id (channel router or aggregated router)
|
||||
```
|
||||
|
||||
#### BAAW Resolve 로직
|
||||
|
||||
```python
|
||||
def resolve(self, la: int, nbytes: int) -> list[PhysicalRequest]:
|
||||
seg = self._find_segment(la) # la_base <= la < la_base + la_size
|
||||
offset = la - seg.la_base
|
||||
|
||||
if seg.mode == "n_to_one":
|
||||
pa = seg.agg_pa_base + offset
|
||||
return [PhysicalRequest(pa=pa, nbytes=nbytes, dst_node=seg.agg_node_id)]
|
||||
|
||||
elif seg.mode == "one_to_one":
|
||||
requests = []
|
||||
per_ch_size = seg.channel_size
|
||||
for i, (pa_base, ch_id) in enumerate(zip(seg.pa_bases, seg.channel_ids)):
|
||||
ch_offset = offset % per_ch_size # interleaved or striped
|
||||
ch_nbytes = nbytes // seg.channel_count
|
||||
pa = pa_base + ch_offset
|
||||
dst_node = f"{self._pe_prefix}.ch_r{ch_id}"
|
||||
requests.append(PhysicalRequest(pa=pa, nbytes=ch_nbytes, dst_node=dst_node))
|
||||
return requests
|
||||
```
|
||||
|
||||
#### 역할 범위
|
||||
|
||||
BAAW의 책임:
|
||||
- logical access를 physical request 단위로 변환
|
||||
- mapping mode에 따른 fan-out (1:1) 또는 pass-through (n:1) 수행
|
||||
- Physical Address 생성 및 target node 결정
|
||||
|
||||
BAAW의 책임이 아닌 것:
|
||||
- 실제 data movement 수행
|
||||
- NOC routing 실행
|
||||
- bandwidth 소비 시뮬레이션 (downstream component의 역할)
|
||||
|
||||
#### Output Contract
|
||||
|
||||
BAAW의 출력은 추가적인 address decoding 없이
|
||||
simulator의 routing 및 resource 모델에서 직접 사용 가능한 request 단위여야 한다.
|
||||
|
||||
---
|
||||
|
||||
### D5. PE_DMA handle_command() 변경
|
||||
|
||||
#### 현재 흐름 (VA 기반)
|
||||
|
||||
```
|
||||
DmaReadCmd.src_addr (VA)
|
||||
→ MMU.translate(VA) → PA
|
||||
→ PhysAddr.decode(PA) → PhysAddr object
|
||||
→ resolver.resolve(PhysAddr) → dst_node_id (e.g., "sip0.cube0.hbm_ctrl")
|
||||
→ router.find_path(pe_prefix, dst_node_id) → path
|
||||
→ 1개 sub-Transaction 생성 → fabric inject
|
||||
```
|
||||
|
||||
#### 새 흐름 (LA 기반)
|
||||
|
||||
```
|
||||
DmaReadCmd.src_addr (LA)
|
||||
→ BAAW.resolve(LA, nbytes) → list[PhysicalRequest]
|
||||
→ 각 PhysicalRequest에 대해:
|
||||
→ router.find_path(pe_prefix, req.dst_node) → path
|
||||
→ compute_drain_ns(path, req.nbytes) → drain
|
||||
→ sub-Transaction 생성 → fabric inject
|
||||
→ 모든 sub-Transaction 완료 대기
|
||||
→ pe_txn.done.succeed()
|
||||
```
|
||||
|
||||
핵심 변경:
|
||||
- MMU 참조 제거 → BAAW resolve로 대체
|
||||
- PhysAddr.decode() + resolver.resolve() → BAAW가 직접 dst_node 반환
|
||||
- 1개 request → N개 request 병렬 inject (1:1 mode)
|
||||
|
||||
---
|
||||
|
||||
### D6. 1:1 Mode 상세
|
||||
|
||||
- 하나의 logical access → N개(= `channels_per_pe`)의 physical request
|
||||
- N은 `hbm_pseudo_channels / pes_per_cube`로 결정되는 파라미터
|
||||
- 각 request:
|
||||
- fully resolved 51-bit PA
|
||||
- 특정 channel router를 target (`{pe_prefix}.ch_r{channel_id}`)
|
||||
- per-channel link에 의한 BW contention 모델링
|
||||
- PE_DMA는 N개 sub-transaction을 동시에 inject
|
||||
|
||||
#### 1:1 Mode 예시
|
||||
|
||||
구성: `hbm_pseudo_channels=64`, `pes_per_cube=8`
|
||||
→ `channels_per_pe=8`, PE0이 ch0-7 소유
|
||||
|
||||
```text
|
||||
Tensor A (4 KB) → LA 0x1_0000_0000, size=4096 bytes
|
||||
BAAW segment: {
|
||||
la_base: 0x1_0000_0000, la_size: 4096,
|
||||
mode: "one_to_one", channel_count: 8, # = channels_per_pe
|
||||
pa_bases: [PA_ch0, PA_ch1, ..., PA_ch7],
|
||||
channel_ids: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||
channel_size: 512, # = la_size / channel_count
|
||||
}
|
||||
|
||||
BAAW resolve 결과 (N=8개 request):
|
||||
→ PhysicalRequest(pa=PA_ch0, nbytes=512, dst_node="sip0.cube0.pe0.ch_r0")
|
||||
→ PhysicalRequest(pa=PA_ch1, nbytes=512, dst_node="sip0.cube0.pe0.ch_r1")
|
||||
→ ...
|
||||
→ PhysicalRequest(pa=PA_ch7, nbytes=512, dst_node="sip0.cube0.pe0.ch_r7")
|
||||
|
||||
PE_DMA: N개 sub-transaction 병렬 inject
|
||||
각각 channel router → hbm_ctrl link (channel_bw_gbs)를 통해 HBM 접근
|
||||
총 effective BW = N × channel_bw_gbs
|
||||
```
|
||||
|
||||
N이 다른 구성의 예:
|
||||
- `hbm_pseudo_channels=32`, `pes_per_cube=8` → `channels_per_pe=4`, 4개 request
|
||||
- `hbm_pseudo_channels=64`, `pes_per_cube=4` → `channels_per_pe=16`, 16개 request
|
||||
|
||||
---
|
||||
|
||||
### D7. n:1 Mode 상세
|
||||
|
||||
- 하나의 logical access → 하나의 aggregated request
|
||||
- target: aggregated router → hbm_ctrl (ADR-0019 참조)
|
||||
- aggregated link BW = `channels_per_pe` × `channel_bw_gbs` (e.g., 8 × 32 = 256 GB/s)
|
||||
- single queue / resource로 모델링
|
||||
- per-channel PA 분해 없음
|
||||
|
||||
#### n:1 Mode 예시
|
||||
|
||||
```
|
||||
Tensor A (4 KB) → LA 0x1_0000_0000, size=4096 bytes
|
||||
BAAW segment: {
|
||||
la_base: 0x1_0000_0000, la_size: 4096,
|
||||
mode: "n_to_one",
|
||||
agg_pa_base: PA_agg,
|
||||
agg_node_id: "sip0.cube0.pe0.agg_router",
|
||||
}
|
||||
|
||||
BAAW resolve 결과:
|
||||
→ PhysicalRequest(pa=PA_agg, nbytes=4096, dst_node="sip0.cube0.pe0.agg_router")
|
||||
|
||||
PE_DMA: 1개 sub-transaction inject
|
||||
aggregated router → hbm_ctrl link (256 GB/s)를 통해 HBM 접근
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### D8. Kernel Model 유지
|
||||
|
||||
- kernel은 여전히 단일 memory op만 발행 (`tl.load`, `tl.store`, `tl.composite`)
|
||||
- LA가 커널에 전달되는 주소 체계
|
||||
- channel 분해/집계는 PE_DMA 내부 BAAW에서 수행
|
||||
- kernel 코드에 physical channel 정보가 노출되지 않음
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- 1:1 vs n:1 semantics가 BAAW라는 단일 지점에서 명확히 분리됨
|
||||
- kernel abstraction 유지 — 커널 코드 변경 불필요
|
||||
- topology 기반 정책 제어 가능 (yaml에서 mode 전환)
|
||||
- simulation 모델 일관성 및 디버깅 용이성 향상
|
||||
- segment-based mapping은 page table 대비 단순하고 overhead가 낮음
|
||||
|
||||
### Negative
|
||||
|
||||
- VA/MMU 기반 코드 전체 리팩토링 필요
|
||||
- request 생성 경로 복잡도 증가 (1:1 mode에서 N개 request 관리)
|
||||
- n:1 mode에서 per-channel visibility 감소
|
||||
- 기존 VA 관련 테스트 재작성 필요
|
||||
|
||||
---
|
||||
|
||||
## Alternatives
|
||||
|
||||
### A1. VA 유지 + MMU에서 fan-out
|
||||
|
||||
- MMU가 per-channel PA를 반환하도록 확장
|
||||
- 문제: MMU의 역할이 address translation을 넘어 request 분해까지 확장됨
|
||||
- 문제: n:1 mode에서 aggregation 표현이 어려움
|
||||
|
||||
### A2. Kernel이 channel-aware request 생성
|
||||
|
||||
- 커널이 직접 채널별 load/store를 호출
|
||||
- 문제: abstraction leakage, portability 저하
|
||||
- 문제: 모든 벤치마크 코드 수정 필요
|
||||
|
||||
### A3. 항상 PA 사용 (LA 없이)
|
||||
|
||||
- runtime이 직접 per-channel PA를 커널에 전달
|
||||
- 문제: aggregation 모델과 충돌
|
||||
- 문제: 변환 시점이 불명확, 커널에 channel 정보 노출
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### 구현 순서
|
||||
|
||||
1. LA 타입 도입 (`policy/address/la_allocator.py`)
|
||||
2. BAAW segment table 구현 (`policy/address/baaw.py`)
|
||||
3. `BaawSegmentInstallMsg` 메시지 타입 추가 (`runtime_api/kernel.py`)
|
||||
4. PE_DMA에 BAAW 통합 (`components/builtin/pe_dma.py` handle_command 변경)
|
||||
5. RuntimeContext 변경: LA alloc + segment install (`runtime_api/context.py`)
|
||||
6. Tensor.va_base → la_base 변경 (`runtime_api/tensor.py`)
|
||||
7. VA/MMU 코드 제거
|
||||
8. topology.yaml에서 pe_mmu 제거, mapping mode 설정 추가
|
||||
9. 테스트 마이그레이션
|
||||
|
||||
### 영향받는 기존 테스트
|
||||
|
||||
| 테스트 파일 | 영향 |
|
||||
|------------|------|
|
||||
| `tests/test_mmu_component.py` | 제거 → BAAW segment install 테스트로 대체 |
|
||||
| `tests/test_mmu_fabric.py` | 제거 → BAAW + fabric 통합 테스트로 대체 |
|
||||
| `tests/test_pe_mmu.py` | 제거 |
|
||||
| `tests/test_va_allocator.py` | LA allocator 테스트로 대체 |
|
||||
| `tests/test_va_integration.py` | LA + BAAW 통합 테스트로 대체 |
|
||||
| `tests/test_va_offset.py` | LA offset 테스트로 대체 |
|
||||
|
||||
---
|
||||
|
||||
## Test Requirements
|
||||
|
||||
- 동일 logical access에 대해:
|
||||
- 1:1 → N개 request 생성 확인
|
||||
- n:1 → 1개 aggregated request 생성 확인
|
||||
- 두 모드에서 effective bandwidth 일관성 검증
|
||||
- 1:1 → per-channel contention 모델링 확인
|
||||
- n:1 → aggregated bandwidth 반영 확인
|
||||
- kernel 코드 변경 없이 동작 확인
|
||||
- BAAW segment install/uninstall 정상 동작
|
||||
- 여러 tensor가 서로 다른 segment에 할당될 때 충돌 없음
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
- ADR-0011 (Memory Addressing Simplification — PA-first, VA/MMU 도입) → 본 ADR이 대체
|
||||
- ADR-0019 (NOC Per-Channel HBM 연결 모델) → topology 측 연동
|
||||
- ADR-0014 (PE Internal Execution Model) → PE_DMA 변경 영향
|
||||
@@ -0,0 +1,5 @@
|
||||
# ADR-0019: Per-Channel and Aggregated HBM Connection Models within CUBE NOC
|
||||
|
||||
## Status
|
||||
|
||||
Merged into ADR-0017 (Cube NOC and HBM Connectivity).
|
||||
@@ -0,0 +1,5 @@
|
||||
# ADR-0019: CUBE NOC 내 Per-Channel 및 Aggregated HBM 연결 모델
|
||||
|
||||
## Status
|
||||
|
||||
Merged into ADR-0017 (Cube NOC and HBM Connectivity).
|
||||
@@ -0,0 +1,5 @@
|
||||
# ADR-0021: PE Pipeline Refactoring — Component Separation + Scheduler-Based Routing
|
||||
|
||||
## Status
|
||||
|
||||
Merged into ADR-0014 (PE Pipeline Execution Model).
|
||||
@@ -0,0 +1,5 @@
|
||||
# ADR-0021: PE 파이프라인 리팩토링 — 컴포넌트 분리 + Scheduler 기반 라우팅
|
||||
|
||||
## Status
|
||||
|
||||
Merged into ADR-0014 (PE Pipeline Execution Model).
|
||||
@@ -0,0 +1,421 @@
|
||||
# ADR-0029: Hierarchical All-Reduce — 3-level intra/inter-SIP 알고리즘
|
||||
|
||||
## Status
|
||||
|
||||
Superseded by ADR-0032 (Intercube all-reduce). The 3-level kernel and
|
||||
`hierarchical_allreduce.py` module have been removed. The cube-mesh
|
||||
intercube + inter-SIP path is now the single all-reduce algorithm.
|
||||
|
||||
## Context
|
||||
|
||||
### 목표
|
||||
|
||||
"Rank = SIP" 모델 (ADR-0024) 위에서 각 SIP 내부의 모든 PE를 참여시키는
|
||||
**3-level 계층 all-reduce** 알고리즘을 정의한다. 각 레벨이 서로 다른 물리
|
||||
연결(intra-cube ring, inter-cube NoC, inter-SIP UCIe)을 활용해 대역폭을
|
||||
극대화한다.
|
||||
|
||||
### 왜 hierarchical인가
|
||||
|
||||
단순 ring/mesh/tree all-reduce는 SIP당 1 PE만 참여 (ADR-0024의 `leader_only`
|
||||
mapper). 이는 inter-SIP 단계는 잘 모델링하지만:
|
||||
|
||||
- **Intra-SIP PE가 노는 시간이 발생**. Leader PE가 inter-SIP 통신 중이면
|
||||
나머지 7 PE / 16 cube는 유휴.
|
||||
- **Intra-cube/inter-cube 연결 대역폭 미활용**. Cube NoC는 매우 빠르지만
|
||||
단일 leader 사용 시 이 자원이 노출되지 않음.
|
||||
- **실제 NCCL 등은 hierarchical**: NVLink(intra-node) + InfiniBand(inter-node)
|
||||
의 bandwidth 차이를 활용. KernBench 토폴로지도 동일 구조
|
||||
(intra-cube / inter-cube / inter-SIP의 bandwidth·latency 차이).
|
||||
|
||||
### 현재 상태
|
||||
|
||||
- `src/kernbench/ccl/algorithms/hierarchical_allreduce.py` 이미 존재
|
||||
(git log `10b33b4` — "Tensor indexing + hierarchical 3-level all-reduce
|
||||
kernel"). PE-level로 world_size = total PE를 가정하는 옛 모델 기반 구현.
|
||||
- ADR-0024에 의해 launcher는 rank = SIP로 바뀜.
|
||||
- Hierarchical 커널은 **재해석 필요**: 이제 각 worker(1 per SIP)가 자기 SIP의
|
||||
모든 PE를 참여시키고, kernel은 intra-cube → inter-cube → inter-SIP 순으로
|
||||
3-level reduce + broadcast.
|
||||
|
||||
### 풀어야 할 문제
|
||||
|
||||
1. **ADR-0024 framework 위에 hierarchical 알고리즘 맞추기**
|
||||
- Mapper: `all_pes` (ADR-0024 D5 제공)
|
||||
- Validator: `multi_pe_sip_local` (ADR-0024 D8 제공)
|
||||
- Kernel: 기존 `hierarchical_allreduce.py` 수정 — rank 계산 방식을 SIP 내
|
||||
local (cube, pe)로 바꿈
|
||||
2. **PE-level neighbor graph 생성**
|
||||
- Intra-cube: `(sip, cube, pe) ↔ (sip, cube, pe±1 mod N_PE)` (ring 내부)
|
||||
- Inter-cube: `(sip, cube, 0) ↔ (sip, cube±1 mod N_CUBE, 0)` (cube leader만)
|
||||
- Inter-SIP: `(sip, 0, 0) ↔ (sip±1 mod N_SIP, 0, 0)` (SIP leader만)
|
||||
3. **Tensor layout**: 각 PE가 1 tile을 소유하고 시작 (`multi_pe_sip_local`
|
||||
validator가 이 layout 강제). DPPolicy(cube="column_wise",
|
||||
pe="column_wise")로 달성 가능.
|
||||
4. **PE-level topology 표현 부족** (ADR-0024 D6의 "책임 분산" 이슈 구체화)
|
||||
- Ring/mesh/tree 같은 단순 패턴은 rank-level topology_fn + mapper 조합으로
|
||||
충분.
|
||||
- Hierarchical은 레벨마다 다른 peer 매핑이라 `_build_pe_installs`에서
|
||||
multi-level 해석을 해야 함.
|
||||
- 장기적으로는 topology 모듈이 PE-level을 직접 표현하는 편이 명시적.
|
||||
|
||||
### Non-problem (이 ADR 밖)
|
||||
|
||||
- Launcher / barrier / rank-to-SIP / mapper-validator registry → ADR-0024
|
||||
- IPCQ direction addressing → ADR-0025
|
||||
- DPPolicy 필드 정리 → ADR-0026
|
||||
- Megatron TP → ADR-0027
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
### D1. 알고리즘 구조 — 3-level reduce + 역순 broadcast
|
||||
|
||||
```
|
||||
Level 1 (intra-cube, E/W ring):
|
||||
각 cube의 N_PE개 PE가 bidirectional ring reduce → cube 내 PE 0에 부분합 집중
|
||||
Level 2 (inter-cube within SIP, N/S ring, PE 0만 참여):
|
||||
N_CUBE개 cube-leader가 bidirectional ring reduce → SIP 내 (cube 0, PE 0)에
|
||||
SIP 전체 부분합 집중
|
||||
Level 3 (inter-SIP, N_SIP peers, (cube 0, PE 0)만 참여):
|
||||
Ring 또는 pair exchange로 전역 합산 완료
|
||||
Broadcast:
|
||||
역순 — Level 3 결과를 (cube 0, PE 0)에서 SIP 내 모든 cube-leader로, 다시
|
||||
각 cube 내 모든 PE로 전파
|
||||
```
|
||||
|
||||
세부는 기존 `hierarchical_allreduce.py`의 커널 구현과 일치. ADR-0024 이후
|
||||
변경점은 **rank 계산 방식**과 **n_elem 해석**뿐:
|
||||
|
||||
- 기존 (rank=PE 모델): `rank = cube_id * pes_per_cube + local_pe`, `pe_addr =
|
||||
t_ptr + rank * nbytes`
|
||||
- 신규 (rank=SIP 모델): 커널은 SIP-local 좌표 `(cube_id, local_pe)`로만 동작.
|
||||
텐서의 per-PE slice는 backend가 per-PE `TensorArg`로 전달 (ADR-0024 D3).
|
||||
커널 내부 rank 계산 자체가 불필요해짐 — `tl.program_id(0/1)`로 충분.
|
||||
|
||||
### D2. Framework integration — ADR-0024 infrastructure 재활용
|
||||
|
||||
`ccl.yaml`:
|
||||
|
||||
```yaml
|
||||
algorithms:
|
||||
hierarchical_allreduce:
|
||||
module: kernbench.ccl.algorithms.hierarchical_allreduce
|
||||
topology: hierarchical_3level # NEW — D3 참고
|
||||
mapper: all_pes # ADR-0024 D5 built-in
|
||||
validator: multi_pe_sip_local # ADR-0024 D8 built-in
|
||||
buffer_kind: tcm
|
||||
n_elem: 128
|
||||
```
|
||||
|
||||
Framework 관점에서 hierarchical은 **특별한 알고리즘이 아니라, 특정
|
||||
topology / mapper / validator 조합**. 본 ADR은 그 조합과 topology 패턴을
|
||||
정의.
|
||||
|
||||
### D3. `hierarchical_3level` topology (신규)
|
||||
|
||||
`kernbench/ccl/topologies.py`에 신규 추가:
|
||||
|
||||
```python
|
||||
def hierarchical_3level(rank: int, world_size: int, spec: dict) -> dict:
|
||||
"""3-level hierarchical neighbor pattern.
|
||||
|
||||
Returns a nested structure describing intra-cube + inter-cube + inter-SIP
|
||||
neighbors. Unlike ring_1d / mesh_2d which are rank → {dir: peer_rank},
|
||||
hierarchical is PE-level and requires spec for cube_mesh / pe_layout.
|
||||
"""
|
||||
```
|
||||
|
||||
반환 스키마 (초안):
|
||||
|
||||
```python
|
||||
{
|
||||
"intra_cube": {
|
||||
# 각 cube 내 ring neighbors: (cube, pe) → {"E": (cube, pe_e), "W": (cube, pe_w)}
|
||||
...
|
||||
},
|
||||
"inter_cube": {
|
||||
# cube-leader 간 ring: (cube, 0) → {"N": (cube_n, 0), "S": (cube_s, 0)}
|
||||
...
|
||||
},
|
||||
"inter_sip": {
|
||||
# SIP-leader 간: rank → {"parent": peer_rank} (또는 ring 방식)
|
||||
...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
이 구조는 `_build_pe_installs`가 해석하여 각 PE의 neighbor table 엔트리
|
||||
(4-direction)에 대응시킨다.
|
||||
|
||||
**Rank-level `topologies.py` 현 API와의 관계**: 기존 단순 패턴은
|
||||
`(rank → {dir: peer_rank})` 단일 레벨. Hierarchical은 multi-level이므로
|
||||
기존 API와 schema가 다름. `_resolve_topology`는 **알고리즘이 어떤 schema를
|
||||
쓰는지 선언**하고, builder가 그에 맞춰 해석하도록 확장 필요 (open question).
|
||||
|
||||
### D4. PE-level neighbor graph — `_build_pe_installs` 확장
|
||||
|
||||
기존 (ring/mesh/tree): topology_fn이 반환한 `(rank → {dir: peer_rank})`를
|
||||
각 참여 PE에 그대로 매핑 (leader_only일 경우 peer PE도 leader).
|
||||
|
||||
신규 (hierarchical): `hierarchical_3level`의 3단 구조를 per-PE neighbor
|
||||
table로 펼침:
|
||||
|
||||
```python
|
||||
def _build_pe_installs_hierarchical(rank, world_size, sip, pes, topo, spec):
|
||||
"""Hierarchical 전용 PE neighbor table 빌더."""
|
||||
result = []
|
||||
for (cube, pe) in pes:
|
||||
entries = []
|
||||
# Level 1: intra-cube ring (E/W)
|
||||
for d, peer in topo["intra_cube"][(cube, pe)].items():
|
||||
entries.append(NeighborTableEntry(direction=d, ...))
|
||||
# Level 2: inter-cube ring (N/S) — cube leader (pe == 0)만
|
||||
if pe == 0:
|
||||
for d, peer in topo["inter_cube"][(cube, 0)].items():
|
||||
entries.append(NeighborTableEntry(direction=d, ...))
|
||||
# Level 3: inter-SIP — SIP leader (cube == 0 and pe == 0)만
|
||||
if cube == 0 and pe == 0:
|
||||
for d, peer_rank in topo["inter_sip"][rank].items():
|
||||
# peer_rank → peer SIP의 (0, 0)
|
||||
entries.append(NeighborTableEntry(
|
||||
direction=d, peer_sip=peer_rank, peer_cube=0, peer_pe=0, ...))
|
||||
result.append(PeInstallSpec(cube=cube, pe=pe, neighbors=tuple(entries)))
|
||||
return tuple(result)
|
||||
```
|
||||
|
||||
`build_install_plans`에서 algorithm_config의 `topology`에 따라 적절한 builder
|
||||
선택 (기존 simple builder vs hierarchical builder).
|
||||
|
||||
### D5. Kernel 재해석 — SIP-local 좌표로
|
||||
|
||||
`src/kernbench/ccl/algorithms/hierarchical_allreduce.py`를 ADR-0024 D3에
|
||||
맞춰 수정:
|
||||
|
||||
```python
|
||||
def kernel_args(*, n_elem: int, world_size: int, pes_per_cube: int,
|
||||
cubes_per_sip: int, num_sips: int, **kw) -> tuple:
|
||||
"""world_size (= num_sips), pes_per_cube, cubes_per_sip를 스칼라로."""
|
||||
return (n_elem, pes_per_cube, cubes_per_sip, num_sips)
|
||||
|
||||
def kernel(t_ptr, n_elem, pes_per_cube, cubes_per_sip, num_sips, tl):
|
||||
"""SIP-local 좌표 기반.
|
||||
|
||||
이전 (rank=PE 모델):
|
||||
rank = cube_id * pes_per_cube + local_pe
|
||||
pe_addr = t_ptr + rank * nbytes
|
||||
현재 (rank=SIP 모델):
|
||||
per-PE tensor slice는 backend가 TensorArg로 전달 → t_ptr은 이미 local.
|
||||
intra-cube ring은 tl.program_id(0) 사용.
|
||||
inter-cube ring은 pe_id == 0 조건으로 제한.
|
||||
inter-SIP reduce는 cube_id == 0 and pe_id == 0 조건으로 제한.
|
||||
"""
|
||||
local_pe = tl.program_id(axis=0)
|
||||
cube_id = tl.program_id(axis=1)
|
||||
|
||||
# Level 1: intra-cube ring
|
||||
for _ in range(intra_rounds(pes_per_cube)):
|
||||
tl.send(dir="E", src=acc)
|
||||
recv = tl.recv(dir="W", shape=(n_elem,), dtype="f16")
|
||||
acc = acc + recv
|
||||
|
||||
# Level 2: inter-cube (cube leader only)
|
||||
if local_pe == 0:
|
||||
for _ in range(inter_cube_rounds(cubes_per_sip)):
|
||||
tl.send(dir="N", src=acc)
|
||||
recv = tl.recv(dir="S", shape=(n_elem,), dtype="f16")
|
||||
acc = acc + recv
|
||||
|
||||
# Level 3: inter-SIP (SIP leader only)
|
||||
if local_pe == 0 and cube_id == 0:
|
||||
for _ in range(inter_sip_rounds(num_sips)):
|
||||
tl.send(dir="parent", src=acc)
|
||||
recv = tl.recv(dir="parent", shape=(n_elem,), dtype="f16")
|
||||
acc = acc + recv
|
||||
|
||||
# Broadcast (reverse chain)
|
||||
# ...
|
||||
tl.store(t_ptr, acc)
|
||||
```
|
||||
|
||||
`kernel_args`는 ADR-0024 D4의 keyword-only signature 계약을 따른다.
|
||||
|
||||
### D6. Validator — `multi_pe_sip_local`
|
||||
|
||||
ADR-0024 D8의 built-in 그대로 활용. `ccl.yaml`에서 `validator:
|
||||
multi_pe_sip_local` 지정 시 backend가 각 SIP에 `cubes × pes_per_cube`개
|
||||
shard가 있는지 검증.
|
||||
|
||||
### D7. Bench — 기본 all-reduce bench 확장
|
||||
|
||||
`benches/ccl_allreduce.py`의 worker는 `ccl.yaml`이 `hierarchical_allreduce`를
|
||||
선택하면 자동으로:
|
||||
|
||||
```python
|
||||
# Worker 예
|
||||
dp = DPPolicy(cube="column_wise", pe="column_wise")
|
||||
tensor = torch.zeros((1, intra_sip_pes * n_elem), dp=dp, name="in")
|
||||
# tensor는 각 SIP의 모든 PE에 1 tile씩 분산 (multi_pe_sip_local validator 통과)
|
||||
dist.all_reduce(tensor, op="sum")
|
||||
```
|
||||
|
||||
Worker 코드 자체는 알고리즘 종류를 모름 (`ccl.yaml` 선택에 의존). 단,
|
||||
**DPPolicy가 hierarchical 요구와 일치해야** 함 — `cube/pe="column_wise"`
|
||||
같은 SIP-내 분산을 하는 DPPolicy여야 `multi_pe_sip_local` 검증 통과. 이
|
||||
DPPolicy 선택은 bench 설정 또는 sample bench에서 결정.
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **ADR-0024**: Launcher, `all_pes` mapper, `multi_pe_sip_local` validator,
|
||||
registry + import path. 본 ADR 구현의 전제.
|
||||
- **ADR-0025**: IPCQ direction addressing — cube/pe/SIP 간 다중 direction을
|
||||
동시 사용하므로 정확한 direction 매칭 필수.
|
||||
- **ADR-0023**: IPCQ protocol (neighbor table, send/recv, credit return).
|
||||
- **기존 `hierarchical_allreduce.py`**: 본 ADR은 그 커널의 재해석 + 주변
|
||||
framework integration.
|
||||
|
||||
---
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **ADR-0024 framework 변경**: 재활용만.
|
||||
- **Alternative reduce topology (tree-in-tree 등)**: 3-level ring이 첫 구현.
|
||||
- **Dynamic level count**: 현재 SIP/cube/PE 3단 고정. 2단 (SIP + PE, cube
|
||||
skip) 또는 4단 이상은 future.
|
||||
- **Bandwidth-optimal schedule tuning**: reduce round 수 / chunk size 조정
|
||||
같은 tuning은 별도.
|
||||
- **Pipelined hierarchical**: 여러 chunk를 파이프라인으로 겹쳐서 돌리는
|
||||
NCCL-style 최적화는 future.
|
||||
|
||||
---
|
||||
|
||||
## Open questions
|
||||
|
||||
### 🟠 중간 영향 — 구현 시 결정 필요
|
||||
|
||||
- **`topologies.py` 스키마 확장**: 기존 `ring_1d` 등은 단일 레벨 `(rank →
|
||||
{dir: peer})`. `hierarchical_3level`은 multi-level. `_resolve_topology`가
|
||||
둘을 모두 반환할 수 있도록 schema를 일반화할지, 아니면 hierarchical 전용
|
||||
return type을 두고 builder가 분기할지.
|
||||
- Option A: 모든 topology를 neighbor-list 형태로 단일화
|
||||
(`[{direction, peer_sip, peer_cube, peer_pe}, ...]`)
|
||||
- Option B: topology 모듈이 `kind` 필드 제공, builder가 분기
|
||||
- 권장: Option A (single source of truth, ADR-0024 Open Q의
|
||||
"PE-level topology 일원화" 방향과 일치)
|
||||
|
||||
- **`hierarchical_3level` vs algorithm별 topology 모듈**: 향후 mesh-based
|
||||
hierarchical 등 variant이 생기면? `hierarchical_3level` 같은 이름이 이미
|
||||
topology-specific. 변형은 새 key 추가 (`hierarchical_mesh_3level` 등) 또는
|
||||
알고리즘 모듈에서 topology 생성 override.
|
||||
|
||||
### 🟡 Nice-to-have
|
||||
|
||||
- **Reduce round 수 최적화**: Bidirectional ring은 `ceil((N-1)/2)` round.
|
||||
Non-power-of-2 group size에서 idle PE 발생 가능.
|
||||
- **Non-uniform topology 대응**: cube_mesh가 w != h일 때 inter-cube ring
|
||||
balance.
|
||||
- **Single SIP 케이스**: world_size = 1 (SIP 1개)일 때 Level 3 skip. Degenerate
|
||||
case 검증.
|
||||
|
||||
### 🟢 Framework evolution 시사점 (ADR-0024로부터 이관)
|
||||
|
||||
- **PE-level topology 일원화 (중장기)**: 현 설계는
|
||||
- topology (rank graph 또는 level-separated)
|
||||
- mapper (per-SIP PE set)
|
||||
- `_build_pe_installs` (actual edges)
|
||||
|
||||
의 3단 분산. Hierarchical이 이 분산을 가장 스트레스 받는 케이스. 중장기로는
|
||||
`topologies.py`가 PE-level neighbor list를 직접 반환하고 mapper는 단순히
|
||||
"어느 PE가 참여하느냐"만 결정, `_build_pe_installs`는 flat
|
||||
mapping으로 단순화되는 방향이 자연스러움. **본 ADR에서 Option A를 채택**하면
|
||||
이 방향으로 이미 정합.
|
||||
|
||||
---
|
||||
|
||||
## Test strategy
|
||||
|
||||
### T1. Topology generator
|
||||
|
||||
`tests/test_hierarchical_topology.py` (new):
|
||||
- `hierarchical_3level(rank, world_size, spec)` → 각 level의 neighbor set이
|
||||
예상 구조인지 (intra-cube는 ring, inter-cube는 cube-leader만 참여, inter-SIP은
|
||||
SIP-leader만 참여)
|
||||
- 2 SIP × 4 cubes × 4 PEs 같은 작은 토폴로지로 수작업 검증 가능
|
||||
- Symmetry: rank r의 E neighbor가 peer에서 W로 역포인팅
|
||||
|
||||
### T2. Install plan — hierarchical × all_pes
|
||||
|
||||
`tests/test_ccl_install_plan.py` (확장):
|
||||
- `build_install_plans(algorithm="hierarchical_allreduce", mapper="all_pes",
|
||||
validator="multi_pe_sip_local")` 호출 시
|
||||
- 각 SIP의 모든 PE가 `participating_pes`에 포함
|
||||
- PE 0 (cube leader)만 inter-cube neighbor를 가짐
|
||||
- (cube 0, pe 0) (SIP leader)만 inter-SIP neighbor를 가짐
|
||||
- Non-leader PE는 intra-cube neighbor만
|
||||
|
||||
### T3. Kernel unit — mock runtime
|
||||
|
||||
`tests/test_hierarchical_mock_runtime.py` (new):
|
||||
- `run_kernel_in_mock` (kernbench.ccl.testing)을 확장해 multi-level 지원
|
||||
- 2 SIP × 2 cubes × 4 PEs (총 16 PE) 토폴로지에서 초기 tile을 rank+1로 채우고
|
||||
hierarchical all-reduce 실행
|
||||
- 모든 PE의 최종 결과가 `sum(1..16)`인지
|
||||
|
||||
### T4. E2E — 실제 SimPy backend
|
||||
|
||||
`tests/test_ccl_allreduce_matrix.py` (확장):
|
||||
- `hierarchical @ ws=SIP_count`: multi_pe_sip_local layout + 3-level 알고리즘
|
||||
전체 stack 통과 검증
|
||||
|
||||
### T5. Validator enforcement
|
||||
|
||||
- `multi_pe_sip_local` validator가 wrong layout (예: leader_only 스타일 1
|
||||
shard per rank) 입력에 raise
|
||||
|
||||
### T6. 회귀
|
||||
|
||||
기존 ring/mesh/tree 알고리즘 모두 그대로 통과. 본 ADR은 그들을 건드리지 않음.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- **Intra-SIP PE 활용도 증가**: Inter-SIP 통신 중에도 intra-cube / inter-cube
|
||||
reduce가 진행되어 전체 PE 가동률 향상.
|
||||
- **Multi-level bandwidth 활용**: cube NoC, UCIe 모두 작동 → 더 정확한 HW 모델.
|
||||
- **ADR-0024 framework 검증**: `all_pes` mapper + `multi_pe_sip_local`
|
||||
validator의 첫 non-trivial use case. Framework 설계 타당성 확인.
|
||||
- **기존 커널 재활용**: `hierarchical_allreduce.py` 큰 구조 유지, SIP-local
|
||||
좌표만 재해석.
|
||||
|
||||
### Negative
|
||||
|
||||
- **`topologies.py` schema 확장 필요**: Single-level vs multi-level 표현.
|
||||
해결안(Option A)은 기존 ring/mesh/tree의 마이그레이션 비용 유발.
|
||||
- **Validator / mapper 조합 요구**: 사용자가 DPPolicy를
|
||||
`multi_pe_sip_local`에 맞춰 선택해야 함 (bench 설정 복잡도 증가).
|
||||
|
||||
### Neutral
|
||||
|
||||
- 본 ADR 구현 전까지 `hierarchical_allreduce.py`는 deprecated 상태 유지 또는
|
||||
ADR-0024 matrix test에서 제외. 현재 파일을 곧바로 삭제하지는 않음.
|
||||
|
||||
---
|
||||
|
||||
## Affected files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/kernbench/ccl/topologies.py` | D3: `hierarchical_3level` topology 함수 추가. (Option A 채택 시) 기존 topology 출력 format 통일 |
|
||||
| `src/kernbench/ccl/install_plan.py` | D4: hierarchical builder 분기 (또는 단일 builder가 level 개수로 dispatch) |
|
||||
| `src/kernbench/ccl/algorithms/hierarchical_allreduce.py` | D5: SIP-local 좌표로 kernel 재작성, `kernel_args` keyword-only signature |
|
||||
| `ccl.yaml` | D2: `hierarchical_allreduce` 엔트리 추가 (`mapper: all_pes`, `validator: multi_pe_sip_local`, `topology: hierarchical_3level`) |
|
||||
| `tests/test_hierarchical_topology.py` (new) | T1 |
|
||||
| `tests/test_ccl_install_plan.py` | T2 확장 |
|
||||
| `tests/test_hierarchical_mock_runtime.py` (new) | T3 |
|
||||
| `tests/test_ccl_allreduce_matrix.py` | T4: hierarchical row 추가 |
|
||||
@@ -0,0 +1,261 @@
|
||||
# ADR-0031: PhysAddr PE-Resource Extension
|
||||
|
||||
## Status
|
||||
|
||||
Superseded by ADR-0001 (Revision 2, 2026-04-27).
|
||||
PE_LOCAL / MCPU_LOCAL / CUBE_SRAM sub-unit tables are now defined in
|
||||
ADR-0001 D2.3.3-D2.3.5.
|
||||
|
||||
Previous status: Stub (Blocker for ADR-0030 — specific range allocations TBD)
|
||||
|
||||
## Context
|
||||
|
||||
### 목표
|
||||
|
||||
ADR-0001의 `PhysAddr` schema를 **PE 내부의 다양한 resource**를 체계적으로
|
||||
표현할 수 있도록 확장한다. ADR-0030 (IPCQ PhysAddr integration) 및 향후의
|
||||
PE-local resource 추가 (scratchpad, register file, status register, 등)의
|
||||
기반을 제공한다.
|
||||
|
||||
### 현재 상태 (ADR-0001)
|
||||
|
||||
51-bit PhysAddr layout:
|
||||
|
||||
```
|
||||
[50:47] rack_id (4)
|
||||
[46:43] sip_id (4)
|
||||
[42:38] sip_seg (5) # cube_id
|
||||
[37:0] local_offset (38)
|
||||
```
|
||||
|
||||
`local_offset` (38 bits) 내부:
|
||||
|
||||
- `[37]` selector: 1 = HBM window (128GB), 0 = PE resource window
|
||||
- PE resource window는 `unit_type` (3 bits: PE | MCPU | SRAM) +
|
||||
`pe_id` (4 bits) + `ext` (1 bit) + `sub_offset` (29 bits)
|
||||
|
||||
Factory API:
|
||||
- `PhysAddr.hbm_addr(...)` — HBM generic
|
||||
- `PhysAddr.pe_hbm_addr(...)` — PE-local HBM slice
|
||||
- `PhysAddr.pe_tcm_addr(...)` — PE TCM (via `UnitType.PE` + `sub_offset`)
|
||||
- `PhysAddr.cube_sram_addr(...)` — Cube-shared SRAM
|
||||
|
||||
### 풀어야 할 문제
|
||||
|
||||
1. **PE 내부 resource 구분의 명시적 체계 부재**: 현재 `local_offset` (38 bits)
|
||||
이 평면 공간으로 취급되고, PE TCM / IPCQ ring / scratchpad / 향후 register
|
||||
file 등이 관습적 offset 범위로만 구분됨. Schema 레벨에서 명확하지 않음.
|
||||
2. **IPCQ 주소의 PhysAddr 표현 부재**: ADR-0030이 IPCQ ring buffer를 PhysAddr로
|
||||
표현하려면 "이 주소가 IPCQ 영역"을 decode 가능해야 함. 현재는 불가.
|
||||
3. **향후 PE resource 확장 경로**: register file, performance counter 등
|
||||
추가 시 일관된 위치 할당 규칙 필요.
|
||||
|
||||
### 설계 방향 — local_offset을 PE 컴포넌트별 range로 분할
|
||||
|
||||
`local_offset` (38 bits = 256GB per PE segment)을 **PE 컴포넌트마다 고정
|
||||
range**로 나누어 할당한다. 각 range는 해당 컴포넌트 전용 주소 공간이며,
|
||||
`PhysAddr.decode()`가 주소가 어느 range에 속하는지 판별해 해당하는 `kind` /
|
||||
`unit_type` / `sub_type` 필드를 채운다.
|
||||
|
||||
개념적 구조 (구체적 bit 할당은 **TBD**):
|
||||
|
||||
```
|
||||
local_offset [37:0] (38 bits total)
|
||||
├── HBM window [37] = 1 (기존 128GB)
|
||||
├── PE component ranges [37] = 0
|
||||
│ ├── TCM [range_1]
|
||||
│ ├── IPCQ rings [range_2]
|
||||
│ ├── Scratchpad [range_3]
|
||||
│ ├── Register file [range_4]
|
||||
│ ├── (reserved) ...
|
||||
│ └── Sideband / status [range_N]
|
||||
```
|
||||
|
||||
### 왜 range-based partition인가
|
||||
|
||||
- **Schema-level 명시성**: 주소 하나 보고 어느 컴포넌트의 자원인지 decode 가능.
|
||||
"Routing consumes decoded domains" (ADR-0001 D5) 계약 충족.
|
||||
- **Unit type enum 확장보다 유연**: 3-bit `UnitType` 공간을 고갈시키지 않고
|
||||
세분화 가능. 미래 추가 컴포넌트도 빈 range 할당.
|
||||
- **Allocator 통합 자연**: 각 PE-level allocator가 관리하는 하위 pool을
|
||||
address range와 1:1 매칭 (e.g., `reserve_ipcq_tcm()` → IPCQ range 안에서만
|
||||
할당).
|
||||
- **Decode routing 단순**: `PhysAddr.decode(addr)`가 range table을 참조해
|
||||
`kind` + sub-field를 채움. 기존 HBM selector bit 패턴의 일반화.
|
||||
|
||||
### 왜 지금 다루는가
|
||||
|
||||
- ADR-0030 (IPCQ PhysAddr 통합)이 이 확장에 **의존**. ADR-0030 단독 진행 시
|
||||
`sub_offset` 공간을 불투명하게 재사용하게 되어 ADR-0001 계약 미충족.
|
||||
- PE 내부 자원이 더 추가될 가능성 — 지금 구조를 정리해두면 일관된 확장 경로 확보.
|
||||
|
||||
---
|
||||
|
||||
## Decision (pending specific range allocation)
|
||||
|
||||
### D1. Range-based local_offset partition — approach
|
||||
|
||||
`local_offset`을 고정 byte range로 분할하고, 각 range를 PE 컴포넌트에 할당한다.
|
||||
주소의 어느 range에 속하는가로 `kind` / component type을 결정.
|
||||
|
||||
```python
|
||||
# src/kernbench/policy/address/phyaddr.py (conceptual, post-extension)
|
||||
@dataclass(frozen=True)
|
||||
class PeResourceRange:
|
||||
name: str # e.g. "tcm", "ipcq", "scratchpad", "regfile"
|
||||
start_offset: int # local_offset 내 시작
|
||||
end_offset: int # exclusive
|
||||
byte_size: int # end - start
|
||||
|
||||
PE_RESOURCE_MAP: tuple[PeResourceRange, ...] = (
|
||||
# TBD — 구체적 range 할당은 사용자가 별도 업데이트
|
||||
)
|
||||
```
|
||||
|
||||
`PhysAddr.decode(addr)`의 PE resource 경로는:
|
||||
|
||||
```python
|
||||
def decode_pe_resource(local_offset: int) -> dict:
|
||||
for r in PE_RESOURCE_MAP:
|
||||
if r.start_offset <= local_offset < r.end_offset:
|
||||
return {
|
||||
"kind": "pe_resource",
|
||||
"component": r.name, # NEW: "tcm"/"ipcq"/...
|
||||
"component_offset": local_offset - r.start_offset, # within range
|
||||
}
|
||||
raise PhysAddrError(f"local_offset {local_offset} not in any PE range")
|
||||
```
|
||||
|
||||
### D2. Specific range allocations — **TBD**
|
||||
|
||||
> 사용자가 구체적 byte 할당을 별도로 정의한 뒤 본 ADR에 업데이트.
|
||||
>
|
||||
> 필요 정보:
|
||||
> - 각 컴포넌트 (TCM, IPCQ, scratchpad, regfile, ...)의 이름 / byte size
|
||||
> - `local_offset` 내 시작 offset (align 고려)
|
||||
> - 현재 하드웨어 사양 / 시뮬레이션 요구 반영
|
||||
|
||||
이 섹션이 채워진 뒤 ADR status: **Stub → Proposed → Accepted** 승격.
|
||||
|
||||
### D3. Factory API — per-component 함수
|
||||
|
||||
기존 `PhysAddr.pe_tcm_addr(...)` 패턴을 일반화:
|
||||
|
||||
```python
|
||||
# 기존 (이미 존재)
|
||||
PhysAddr.pe_tcm_addr(rack_id, sip_id, cube_id, pe_id, tcm_offset)
|
||||
|
||||
# 신규 (ADR-0031 후 추가)
|
||||
PhysAddr.pe_ipcq_addr(rack_id, sip_id, cube_id, pe_id, ipcq_offset)
|
||||
PhysAddr.pe_scratchpad_addr(...)
|
||||
PhysAddr.pe_regfile_addr(...)
|
||||
# ...
|
||||
```
|
||||
|
||||
각 factory는 해당 컴포넌트의 range 내에서 `component_offset`만 받아 최종
|
||||
PhysAddr encoding. 호출자는 어느 range인지 몰라도 됨.
|
||||
|
||||
### D4. Backward compatibility
|
||||
|
||||
- 기존 `pe_tcm_addr()` signature / semantic 유지.
|
||||
- 내부 인코딩만 신규 range table을 참조하도록 변경.
|
||||
- 기존 `UnitType.PE` decoding 경로는 `PE_RESOURCE_MAP`에서 "tcm" range를
|
||||
대응하도록 매핑 → 기존 코드 transparent.
|
||||
- 기존 코드가 `PhysAddr.decode(addr).unit_type == UnitType.PE`를 체크하는
|
||||
경우는 여전히 유효 (TCM 주소는 계속 PE unit_type).
|
||||
|
||||
---
|
||||
|
||||
## Open questions
|
||||
|
||||
### 🔴 Pending user input (ADR 승격 blocker)
|
||||
|
||||
- **D2의 specific range allocation**: 사용자가 구체적 byte 할당 테이블을
|
||||
제공해야 Stub → Proposed 승격 가능. 필요 정보:
|
||||
- 컴포넌트 목록 (TCM, IPCQ, scratchpad, regfile 등)
|
||||
- 각 컴포넌트의 byte size / 시작 offset
|
||||
- Alignment 요구사항 (4KB / page-aligned 등)
|
||||
|
||||
### 🟡 설계 세부 — range allocation 결정 과정에서 함께 결정
|
||||
|
||||
- **총 local_offset space 배분**: HBM window (bit 37 = 1, 128GB)을 유지할지,
|
||||
아니면 PE resource space를 확장하기 위해 HBM window 축소할지.
|
||||
- **Range padding / reserved space**: 미래 컴포넌트 추가를 위한 "reserved"
|
||||
range 몇 개를 미리 확보할지.
|
||||
- **Address alignment**: 각 range의 시작 offset이 특정 alignment (page /
|
||||
cache line) 만족해야 하는지.
|
||||
- **Diagnostic / debug 포맷**: `PhysAddr.decode()` 출력에서 component 이름 +
|
||||
component_offset을 사람이 읽기 좋게 표시 (e.g., "IPCQ ring sip=0 cube=0 pe=3
|
||||
offset=0x1234").
|
||||
- **기존 `UnitType` enum의 role**: Range-based 접근 후에도 `unit_type` 필드
|
||||
유지할지 (decode 결과에 `component` 추가), 또는 enum 대체할지.
|
||||
|
||||
### 🟢 ADR-0030 연동 질문
|
||||
|
||||
- **IPCQ range 내 direction/slot 표현**: PhysAddr는 `component_offset` 단위
|
||||
까지만 표현. "direction=E, slot=2"는 IPCQ range 내 offset 계산으로 도출
|
||||
(`direction_idx * slot_region_size + slot_idx * slot_size`) — 이 공식은
|
||||
ADR-0030 scope에서 구체화.
|
||||
- **Allocator pool 구조**: `PEMemAllocator`가 여러 range (TCM, IPCQ,
|
||||
scratchpad)를 개별 pool로 관리할지, 단일 pool에서 kind별 reserved만 관리
|
||||
할지. Range-based schema면 개별 pool이 자연스러움.
|
||||
|
||||
---
|
||||
|
||||
## Non-goals (this ADR)
|
||||
|
||||
- **51-bit 전체 layout 재작성**: 본 ADR은 `local_offset` (38 bits) 내부의
|
||||
subdivision만 다룬다. Rack / SIP / cube segment 같은 상위 bit 구조는
|
||||
불변.
|
||||
- **`UnitType` enum 재설계**: range-based 접근으로 대체 가능하지만, 기존 enum
|
||||
(PE / MCPU / SRAM)은 backward compat 위해 유지.
|
||||
- **Dynamic range allocation**: runtime에 range 크기 바꾸는 기능 불필요. 모든
|
||||
range는 컴파일 / 설정 시점에 고정.
|
||||
- **Multi-process / multi-rack partitioning**: PE 내부 resource만 다룸.
|
||||
|
||||
---
|
||||
|
||||
## Action
|
||||
|
||||
### Phase 1 — User 입력: specific range allocation (**Blocker**)
|
||||
- 사용자가 정의한 PE 컴포넌트별 byte range를 D2에 기입:
|
||||
- `PE_RESOURCE_MAP` 테이블 내용 (name, start_offset, byte_size per 컴포넌트)
|
||||
- 각 컴포넌트의 hardware spec 근거 note
|
||||
|
||||
### Phase 2 — ADR Stub → Proposed 승격
|
||||
- D2 채워지면 status 변경.
|
||||
- Open questions의 "🔴 Pending user input" 블록 제거.
|
||||
- ADR-0001에 amendment note 초안 작성.
|
||||
|
||||
### Phase 3 — 구현
|
||||
- `PhysAddr` range-based decode 구현.
|
||||
- 신규 factory 함수 (`pe_ipcq_addr`, `pe_scratchpad_addr` 등 컴포넌트별)
|
||||
추가.
|
||||
- 기존 `pe_tcm_addr` 내부 인코딩만 신규 range table 참조하도록 수정
|
||||
(signature 불변).
|
||||
- 기존 코드 경로 회귀 확인.
|
||||
|
||||
### Phase 4 — ADR-0030 unblock
|
||||
- ADR-0030 "Blocked" 상태 해제.
|
||||
- Install_plan builder가 `pe_ipcq_addr(...)` 등 확장된 factory 호출하도록
|
||||
수정.
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **ADR-0001** (PhysAddr layout): 본 ADR은 ADR-0001의 확장.
|
||||
- **ADR-0023** (IPCQ protocol): IPCQ ring buffer의 주소 체계를 PhysAddr로
|
||||
통합할 수 있게 하는 기반.
|
||||
- **ADR-0030** (IPCQ PhysAddr integration): 본 ADR에 blocked.
|
||||
|
||||
---
|
||||
|
||||
## Affected files (future, after promotion to Proposed)
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/kernbench/policy/address/phyaddr.py` | Range table (`PE_RESOURCE_MAP`), range-based decode, 신규 component-specific factory들 (`pe_ipcq_addr` 등), 기존 `pe_tcm_addr` 내부 인코딩 갱신 |
|
||||
| `src/kernbench/policy/address/allocator.py` | Range-aware pool 분리 (TCM pool / IPCQ pool / scratchpad pool 등 per-PE) |
|
||||
| `docs/adr/ADR-0001-mem-physaddr-layout.md` | Amendment note: range-based PE resource partition |
|
||||
| `tests/test_phyaddr.py` | Range table 검증, 각 factory의 encode/decode round-trip, 기존 `pe_tcm_addr` 회귀 |
|
||||
Reference in New Issue
Block a user