Documents four cross-cutting surfaces that previously had no ADR backing, each surfaced as a G4 candidate by /report: - 0046 prog-tl-context-contract: the kernel-side tl.* API. Enumerates all primitives (ref/load/store/dot/composite/math/reduction/IPCQ/...), the two execution modes (command-list vs greenlet runner), scratch allocator semantics, dispatch-overhead model, and the kernel registry. - 0047 par-ahbm-ccl-backend: torch.distributed.init_process_group (backend="ahbm") install path. world_size priority (algorithm > defaults > topology), the 4-step init sequence (load ccl.yaml, import algorithm module, derive world_size, install SFR + IPCQ), greenlet- local rank registry, all_reduce dispatch via _defer_wait, barrier no-op rationale, and the explicit list of unsupported dist.* APIs. - 0048 mem-allocator-algorithms: VirtualAllocator + PEMemAllocator free-list semantics. Offset-keyed first-fit with coalescing, the no-validation trust model for free(), HBM/TCM channel separation, page-aligned VA allocation, the page_size dual-default (VirtualAllocator 2 MiB / _ensure_allocators 4 KiB fallback), and one-allocator-per-sub-unit rule. - 0049 ver-probe-subcommand: kernbench probe traffic-pattern catalog. H2D / D2H / PE DMA categories with their exact cube-index choices, the 32 KiB reference size, the 5-point utilization sweep, the formula vs actual column meanings, automatic invariant checks (monotonicity, D2H >= H2D, best < worst), per-case GraphEngine isolation, and the human-readable (not machine-parsable) output contract. Bilingual pair verifier passes for all four EN/KO pairs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 KiB
ADR-0048: Memory Allocator Algorithms — VirtualAllocator + PEMemAllocator
Status
Accepted (2026-05-22).
policy/address/allocator.py 의 _FreeList / PEMemAllocator 와
va_allocator.py 의 VirtualAllocator 가 사용하는 free-list 알고리즘,
페이지 정렬, coalescing 규칙을 명시한다. ADR-0001 (PhysAddr 레이아웃) 과
ADR-0011 (PA/VA/LA 모델) 이 주소 스킴을 정의하나, 할당 알고리즘은 별도
ADR 이 없었다.
First action (제일 처음에 하는 일)
_FreeList(capacity)
생성 즉시 self._capacity = capacity, self._used = 0, self._free = [(0, capacity)] 로 초기화. 첫 일은 전 영역을 single free block 으로
세우는 것 — 즉 (offset=0, size=capacity) 한 튜플이 free list 의 유일한
원소다.
PEMemAllocator(sip_id, die_id, pe_id, cfg)
생성 즉시 두 개의 _FreeList 를 만든다:
self._hbm = _FreeList(cfg.hbm_slice_bytes)— 이 PE 가 소유한 HBM slice 의 바이트 크기 (hbm_bytes_per_cube // hbm_slices_per_cube) 만큼.self._tcm = _FreeList(cfg.tcm_allocatable_bytes)—tcm_bytes_per_pe - tcm_scheduler_reserved_bytes만큼 (scheduler 예약분은 사전 분리).
따라서 PEMemAllocator 의 첫 일은 이 PE 의 HBM slice 와 사용자 TCM 영역을 각각 단일 free block 으로 세우는 것.
VirtualAllocator(va_base, va_size, page_size=2*1024*1024)
생성 즉시 self._va_base = va_base, self._va_size = va_size,
self._page_size = page_size, self._used = 0, self._free = [(va_base, va_size)]. 첫 일은 VA base 부터 size 까지 single block 으로 세우고
page_size 를 회수.
Context
runtime_api/context.py::_ensure_allocators 는 다음 단계로 allocator 세트를
구성한다:
- spec 으로부터
hbm_total_gb_per_cube,hbm_slices_per_cube,tcm_size_mb, target_device 별 SIP 범위 등을 읽음. AddressConfig로 모든 파라미터를 frozen 하게 패킹.- target SIP 범위 × cube × PE 의 모든 조합에 대해
PEMemAllocator(sip, cube, pe, cfg)인스턴스를 1개씩 생성. VirtualAllocator(va_base=0x1_0000_0000, va_size=64 GiB, page_size=pe_mmu.page_size)를 1개 생성.
allocator 들의 책임:
- PEMemAllocator: PE-로컬 HBM slice / TCM 의 PA-공간 할당 (PhysAddr encoding 까지 포함).
- VirtualAllocator: device-wide VA 공간을 페이지 정렬로 할당. 이후
RuntimeContext._create_tensor가 VA → PA 매핑을MmuMapMsg로 fabric 에 push.
이 알고리즘들은:
- first-fit 으로 단순.
- 자유 블록 리스트는 offset 정렬 (sorted by start) 유지.
free()시 양쪽 인접 블록과 coalesce.
이런 결정의 근거가 어디에도 없으므로, 향후 누군가 "왜 best-fit 이 아닌가", "왜 buddy allocator 가 아닌가", "왜 partial overlap free 가 silently 허용되는가" 라는 질문에 답할 기준이 필요. 본 ADR 이 그 기준을 마련한다.
Decision
D1. _FreeList — offset-기반 first-fit + coalescing
policy/address/allocator.py::_FreeList:
- 내부 표현:
list[tuple[int, int]]=[(start_offset, size), ...]— start offset 으로 정렬된 자유 블록의 sorted list. alloc(nbytes):- free list 를 앞에서부터 순회 (first-fit).
- 처음 만나는
size >= nbytes인 블록에서 앞부분을 잘라 사용. - 정확히 일치하면 블록 통째로 제거; 아니면
(start+nbytes, size-nbytes)로 축소. _used += nbytes, 잘라낸start반환.- 맞는 블록이 없으면
AllocationError("overflow ... largest free block ...").
free(offset, nbytes):_used -= nbytes.bisect_left(self._free, (offset,))로 삽입 위치 결정.- 직전 블록과 인접 (
prev_start + prev_size == offset) 하면 흡수. - 직후 블록과 인접 (
offset+nbytes == next_start) 하면 흡수. - coalesced range 를 정렬 위치에 insert.
이 알고리즘은 fragmentation 에 약점이 있으나 (best-fit / buddy 대비), 본 시뮬레이터의 워크로드 특성상 (deploy/free 패턴이 거의 stack-like) 충분 하다는 것이 디자인 가정이다. 워크로드가 변하면 D1 supersede 후보.
D2. partial overlap free 는 검사하지 않는다
_FreeList.free(offset, nbytes) 는 호출자가 정확한 (offset, nbytes) 를
넘긴다고 신뢰한다. 다음을 검증하지 않는다:
- 그 range 가 실제로 alloc 된 것인지.
- 그 range 가 다른 alloc 된 영역과 겹치지 않는지.
이유: 시뮬레이터 컨텍스트에서 호출자는 항상 alloc() 의 반환값을 그대로
저장했다가 free() 에 넘기는 패턴이며, 외부 사용자 입력이 아니다. 안전성
검사를 추가하면 매 free 마다 O(N) 비용이 들어 시뮬 wall-clock 에 영향.
이 신뢰 모델이 깨지면 (예: 두 텐서가 같은 PA 를 가리키는 코드 경로 도입) 즉시 ADR-level 으로 재검토.
D3. PEMemAllocator — HBM/TCM 두 채널 분리
PEMemAllocator(sip_id, die_id, pe_id, cfg) 는 두 _FreeList 를 보유:
_hbm:cfg.hbm_slice_bytes크기._tcm:cfg.tcm_allocatable_bytes(=tcm_bytes_per_pe - tcm_scheduler_reserved_bytes) 크기.
alloc_hbm(nbytes) -> PhysAddr:
_hbm.alloc(nbytes)로 offset 획득.PhysAddr.pe_hbm_addr(sip_id, die_id, pe_id, pe_local_hbm_offset=offset, slice_size_bytes=cfg.hbm_slice_bytes)로 PA 인코딩.- 실패 시
AllocationError("HBM overflow ...").
free_hbm(pa, nbytes):
pa.hbm_offset - pe_id * cfg.hbm_slice_bytes로 PE-local offset 복원._hbm.free(offset, nbytes).
alloc_tcm(nbytes) -> PhysAddr: 유사하게 PhysAddr.pe_tcm_addr 로 인코딩.
free_tcm(pa, nbytes): pa.sub_offset 을 그대로 사용 (TCM 은 PE-local
offset 이 곧 sub_offset).
scheduler-reserved TCM 영역 (cfg.tcm_scheduler_reserved_bytes) 은
allocator 가 인지하지 않는다 (_tcm 의 capacity 에서 사전 차감되어 있음).
이는 ADR-0014 의 PE_SCHEDULER 내부 buffer 예약과 정합된다.
D4. VirtualAllocator — 페이지 정렬 first-fit + coalescing
policy/address/va_allocator.py::VirtualAllocator:
- 내부 표현:
_FreeList와 동일한 sortedlist[tuple[int, int]]. 최초:[(va_base, va_size)]. _align_up(nbytes) = ceil(nbytes / page_size) * page_size.alloc(nbytes) -> int:aligned = _align_up(nbytes).- first-fit 으로
size >= aligned인 블록 탐색. - 블록 앞부분
aligned만큼 잘라 사용. 정확히 일치하면 제거. _used += aligned. 블록start(= aligned 된 VA) 반환.- 실패 시
VaAllocationError.
free(va, nbytes):_align_up(nbytes)단위로 free. _FreeList 와 동일한 coalesce 알고리즘.
page_size 의 실제 값은 두 곳에서 다른 기본을 갖는다:
VirtualAllocator.__init__의 매개변수 기본값:2 MiB. 직접 호출하는 테스트가 그대로 받는다.RuntimeContext._ensure_allocators가 인스턴스화할 때:pe_mmu.attrs.get("page_size", 4096)—topology.yaml의pe_mmu.attrs.page_size가 있으면 그 값, 없으면 fallback 4 KiB.
두 기본이 다른 이유: VirtualAllocator 의 standalone 기본은 ADR-0039 의
PE_MMU stopgap 기본 (2 MiB) 과 정합되어 직접 테스트가 자연스럽고, context
fallback 의 4 KiB 는 topology 미설정 시 안전한 minimum page 다. 실제 사용
경로는 항상 후자이며 (_ensure_allocators 가 인스턴스화하므로),
topology.yaml 에서 page_size 가 명시되면 그 값이 양쪽 (MMU + VA
allocator) 으로 일관되게 흐른다.
만약 이 일치가 깨지면 (예: VirtualAllocator 의 page_size 를 PE_MMU 와
다르게 인스턴스화) MMU map() 가 서브-페이지 region 모드 (ADR-0039 D3) 로
흐른다.
VA 기본 범위: va_base = 0x1_0000_0000 (= 4 GiB), va_size = 64 GiB. 이
값은 _ensure_allocators 에 하드코딩되어 있으며 ADR-0011 의 VA 모델에서
직접적인 의미를 갖지는 않는다 — 단지 host 코드와 충돌하지 않을 만큼 큰
주소 공간을 device-wide 로 잡아둔 것.
D5. allocator 인스턴스의 lifecycle
RuntimeContext._ensure_allocators가 lazy 하게 호출됨 (_create_tensor의 첫 호출 시점).- 한 번 생성된 allocator dict (
self._allocators) 는 RuntimeContext 의 lifetime 동안 재사용. 같은 process 안의 두 번째 deploy 는 새 객체를 만들지 않는다. RuntimeContext.cleanup()이 모든 living tensor 의_free_tensor()를 호출 → MMU unmap +va_allocator.free+pemem_allocator.free_hbm으로 free list 가 원상복구. 다음 RuntimeContext 가 다시 만들면 초기 상태부터.
allocator 상태가 RuntimeContext 간에 공유되지 않는 점이 단일 process 안의 연속 실행에서 deploy → cleanup → deploy 의 결정성을 보장한다.
D6. Allocator 실패는 raise 한다 (silent OOM 금지)
_FreeList.alloc / VirtualAllocator.alloc 모두 충분한 free block 이
없으면 AllocationError / VaAllocationError 를 던진다. 메시지에는
"required size + largest available block" 가 포함되어, fragmentation
인지 진짜 OOM 인지 진단 가능.
silent fallback (예: 가장 큰 블록만큼만 alloc) 는 절대 금지 — 부분 할당된 텐서가 SimPy 단계에 들어가면 라우팅·DMA 가 잘못된 PA 를 인지하여 시뮬 정확도가 깨진다.
D7. address space 와 allocator 의 1:1 대응
물리 주소 공간 분리는 PhysAddr 의 sub-unit (ADR-0001 D2.3) 으로 표현되며, 각 sub-unit 마다 별도 allocator 인스턴스를 둔다:
- HBM slice →
PEMemAllocator._hbm. - PE TCM →
PEMemAllocator._tcm. - (현재 미사용) M_CPU local memory, CUBE SRAM → 별도 allocator 필요. 현재 구현은 아직 IPCQ-only slot 으로 처리 (ADR-0023 D9.7) 하며 PA 공간을 share 하지 않으므로 별도 free-list 가 없음.
cube-level SRAM allocator 가 필요해지면 _FreeList(cfg.sram_bytes_per_cube)
인스턴스를 cube 단위로 추가한다 (cfg.sram_bytes_per_cube 는 이미
AddressConfig 에 정의되어 있어 데이터 모델은 준비됨).
Alternatives Considered
A1. best-fit / buddy allocator
기각 (현재). 워크로드의 alloc/free 패턴이 stack-like (deploy 순서 = free 순서) 라 first-fit + coalescing 으로 fragmentation 이 충분히 통제된다. LLM kernel sweep 에서 long-running fragmentation 이 관찰되면 buddy 로 교체하는 ADR 을 별도로 만든다.
A2. partial overlap free 검증 추가
기각. D2 의 신뢰 모델 + O(N) 검사 비용. 단, 디버그 모드 (KERNBENCH_DEBUG
env var 등) 에서 활성화하는 옵션은 후속 작업으로 가능.
A3. VA 와 PA 의 통합 allocator
기각. VA 공간 (64 GiB device-wide) 과 PA 공간 (slice 별 ~6 GiB) 는 의미 차원이 다르다. VA 는 host kernel 의 view, PA 는 device sub-unit 의 view. ADR-0011 의 VA 모델 정신 (MMU 가 둘 사이를 매핑) 과 정합하기 위해 allocator 도 분리.
A4. page_size 의 multi-tier 지원 (large page + small page)
기각 (현재). 단일 page_size (현재 2 MiB) 가 LLM kernel 의 텐서 단위 (수 MiB~수 GiB) 에 맞고, ADR-0039 D3 의 서브-페이지 region 으로 작은 매핑이 필요할 때 흡수된다. multi-tier page 는 MMU 자체 모델을 확장해야 하므로 별도 ADR 후보.
Consequences
- allocator 알고리즘이 ADR-level 에서 굳어져 (D1·D3·D4), 새로운 시뮬 시나리오에서 fragmentation 이슈가 발생할 때 "여기서 first-fit + coalesce 를 쓰고 있다" 가 명확.
- D2 의 신뢰 모델이 명시되어, 향후 사용자 입력으로부터 직접 alloc/free 를 받는 경로가 도입되면 본 ADR supersede 가 필요함을 일찍 인지 가능.
- D7 의 sub-unit별 allocator 1:1 대응이 명시되어, M_CPU/SRAM 별도 영역이 필요해질 때 어디에 free-list 를 추가해야 하는지 명확.
VirtualAllocator의 page_size 가 PE_MMU 설정과 일치해야 함이 D4 에 적혀 있어, 향후 topology.yaml 의 page_size 변경 시 ADR-0039 stopgap 동작 과의 상호작용을 빠르게 가늠 가능.