Files
kernbench2/docs/adr-ko/ADR-0048-mem-allocator-algorithms.md
ywkang 9a02955770 adr: add ADR-0046-0049 — close G4 coverage gaps from /report
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>
2026-05-22 10:25:04 -07:00

12 KiB
Raw Permalink Blame History

ADR-0048: Memory Allocator Algorithms — VirtualAllocator + PEMemAllocator

Status

Accepted (2026-05-22).

policy/address/allocator.py_FreeList / PEMemAllocatorva_allocator.pyVirtualAllocator 가 사용하는 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 세트를 구성한다:

  1. spec 으로부터 hbm_total_gb_per_cube, hbm_slices_per_cube, tcm_size_mb, target_device 별 SIP 범위 등을 읽음.
  2. AddressConfig 로 모든 파라미터를 frozen 하게 패킹.
  3. target SIP 범위 × cube × PE 의 모든 조합에 대해 PEMemAllocator(sip, cube, pe, cfg) 인스턴스를 1개씩 생성.
  4. 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):
    1. free list 를 앞에서부터 순회 (first-fit).
    2. 처음 만나는 size >= nbytes 인 블록에서 앞부분을 잘라 사용.
    3. 정확히 일치하면 블록 통째로 제거; 아니면 (start+nbytes, size-nbytes) 로 축소.
    4. _used += nbytes, 잘라낸 start 반환.
    5. 맞는 블록이 없으면 AllocationError("overflow ... largest free block ...").
  • free(offset, nbytes):
    1. _used -= nbytes.
    2. bisect_left(self._free, (offset,)) 로 삽입 위치 결정.
    3. 직전 블록과 인접 (prev_start + prev_size == offset) 하면 흡수.
    4. 직후 블록과 인접 (offset+nbytes == next_start) 하면 흡수.
    5. 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 와 동일한 sorted list[tuple[int, int]]. 최초: [(va_base, va_size)].
  • _align_up(nbytes) = ceil(nbytes / page_size) * page_size.
  • alloc(nbytes) -> int:
    1. aligned = _align_up(nbytes).
    2. first-fit 으로 size >= aligned 인 블록 탐색.
    3. 블록 앞부분 aligned 만큼 잘라 사용. 정확히 일치하면 제거.
    4. _used += aligned. 블록 start (= aligned 된 VA) 반환.
    5. 실패 시 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.yamlpe_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 동작 과의 상호작용을 빠르게 가늠 가능.