Files
kernbench2/docs/adr-ko/ADR-0050-par-ccl-algorithm-module-contract.md
ywkang bd49c93703 adr: add ADR-0050-0053 — close /report's second-pass G4 candidates
Documents four cross-cutting surfaces one layer deeper than the prior
G4 batch:

- 0050 par-ccl-algorithm-module-contract: how to author a new CCL
  algorithm in src/kernbench/ccl/algorithms/. Pairs with ADR-0045's
  bench-module contract. Pins the four required public symbols
  (kernel, kernel_args, TOPO_NAME_TO_KIND constants, kernel alias),
  the 9 + tl standardized kernel signature, the kernel_args tuple
  format, sip_topo_kind dispatch, and the ccl.yaml entry workflow.

- 0051 lat-routing-helper-api: every public method of AddressResolver
  (resolve, find_m_cpu, find_pcie_ep, find_io_cpu, find_all_pcie_eps)
  and PathRouter (find_path, find_path_with_distance,
  find_mcpu_dma_path, find_memory_path, find_node_path + 2 shims).
  Pins the four adjacency graphs (_adj_all / _adj / _adj_mcpu_dma /
  _adj_local) and the edge-kind exclusion sets they use, plus the
  single-owner naming convention.

- 0052 dev-oplog-memory-store-schemas: OpRecord's 7 fields, the
  per-op_name params matrix (dma_read, dma_write, gemm_*, math, math
  reduction, composite_gemm, ipcq_copy, unknown), snapshot timing
  rules (math = all inputs, dma_write = HBM-only — ADR-0027 race
  avoidance), TileToken stage_type capture, and MemoryStore's
  (space, addr) two-level dict with reference-store semantics.

- 0053 dev-topology-builder-algorithms: the 6-stage compile pipeline,
  cube_mesh.yaml's source_hash cache and its 5 input fields, the
  cube NoC auto-layout algorithm (row/col placement, HBM exclusion
  zone, PE/M_CPU/SRAM attachment via nearest-router, UCIe N/S/E/W
  distribution), the node naming convention (single-owner with
  router.py), the edge-kind catalog, the 4 view projections, and a
  table of spec-field changes vs mesh regeneration.

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:52:42 -07:00

14 KiB
Raw Permalink Blame History

ADR-0050: CCL Algorithm Module Contract — ccl/algorithms/*.py

Status

Accepted (2026-05-22).

src/kernbench/ccl/algorithms/ 디렉터리 안의 모듈이 AHBM CCL backend (ADR-0047) 에서 collective algorithm 으로 사용되려면 갖춰야 할 인터페이스, kernel 시그너처, 그리고 새 알고리즘 추가 절차를 명시한다. ADR-0047 D3 가 "algorithm 모듈은 kernel, kernel_args, optional TOPO_NAME_TO_KIND 를 expose 해야 한다" 라고만 한 줄로 언급하나, algorithm 모듈 작성자가 따라야 할 contract 는 ADR-level 에서 정리된 적이 없다. ADR-0045 가 bench 모듈 contract 를 다루는 것과 짝을 이룬다.

First action (제일 처음에 하는 일)

알고리즘 모듈이 import 되는 시점은 두 가지다:

  1. AHBM backend 진입: 사용자 코드가 dist.init_process_group(backend="ahbm") 를 호출하면, AhbmCCLBackend.__init__ 안에서 self._algo_module = importlib.import_module(self._merged["module"]) 가 실행된다. 이때 모듈 레벨에서 가장 먼저 일어나는 일:

    • SIP_TOPO_RING/TORUS/MESH 같은 정수 상수가 모듈 namespace 에 노출.
    • TOPO_NAME_TO_KIND 사전이 모듈 namespace 에 노출 — backend 가 topo_map = getattr(self._algo_module, "TOPO_NAME_TO_KIND", None) 로 조회.
    • kernel_args 함수 정의 — 호출 시 호출자가 사용.
    • allreduce_intercube_multidevice 같은 알고리즘 함수 정의.
    • 모듈 마지막 줄에서 kernel = allreduce_intercube_multidevice 로 alias 가 노출.
  2. ccl.yaml install 단계: kernbench.ccl.install.install_ipcq 가 호출 되어 IPCQ neighbor table 을 푸시할 때 같은 알고리즘 모듈이 import 됨.

즉, algorithm 모듈의 첫 일은 "topology-kind 상수, TOPO_NAME_TO_KIND 사전, kernel_args 함수, 그리고 kernel alias 를 모듈 namespace 에 노출 하는 것" 이다. 모든 노출은 import-time 부수효과로 충분하며 별도 초기화 함수 호출이 필요하지 않다.

Context

AhbmCCLBackend (ADR-0047) 는 process group 초기화 시점에 ccl.yamldefaults.algorithm (또는 사용자가 지정한 알고리즘 이름) 으로부터 모듈 경로를 얻어 dynamic import 한다. backend 는 그 모듈로부터 다음 4 가지를 기대한다:

  • kernel: collective 의 진입 함수.
  • kernel_args(world_size, n_elem, cube_w=, cube_h=) -> tuple: kernel 에 넘길 위치 인자 묶음.
  • TOPO_NAME_TO_KIND (optional): topology.yamlsips.topology 문자열 (예: "ring_1d", "torus_2d", "mesh_2d_no_wrap") 을 정수 상수로 매핑하는 dict.
  • (간접) IPCQ neighbor table 설치: configure_sfr_intercube_multisip 가 알고리즘 모듈의 TOPO_NAME_TO_KINDcube_w/h 를 보고 SFR 을 결정.

현재 코퍼스의 유일한 algorithm 모듈은 lrab_hierarchical_allreduce.py (248 줄) 이다. 이름은 "left-right alternating broadcast hierarchical allreduce". 향후 ring_allreduce, tree_allreduce, broadcast 같은 모듈이 추가될 때마다 이 contract 를 따라야 일관된 디스패치가 가능하다.

이 contract 가 ADR-level 에 없으면:

  • 새 algorithm 작성자가 ADR-0047 D3 의 한 줄 만으로 시그너처를 추론해야.
  • kernel 함수 인자 순서 (특히 t_ptr, n_elem, cube_w, cube_h, n_sips, sip_rank, sip_topo_kind, sip_topo_w, sip_topo_h, tl) 의 의미가 코드 grep 없이는 명확하지 않다.
  • kernel_args 가 어떤 인자를 받고 어떤 tuple 을 돌려줘야 하는지 관례 로만 굳어진다.

Decision

D1. algorithm 모듈은 4 가지 public symbol 을 노출한다

# src/kernbench/ccl/algorithms/<name>.py

from __future__ import annotations

# (필수) topology-kind 상수 — 알고리즘 내부에서 사용
SIP_TOPO_RING = 0
SIP_TOPO_TORUS = 1
SIP_TOPO_MESH = 2

# (선택) topology 이름 → kind 매핑. backend 가 ccl.yaml/topology 의
# 문자열 SIP topology 를 정수로 변환하는 데 사용.
TOPO_NAME_TO_KIND = {
    "ring_1d": SIP_TOPO_RING,
    "torus_2d": SIP_TOPO_TORUS,
    "mesh_2d_no_wrap": SIP_TOPO_MESH,
}

# (필수) kernel 인자 빌더
def kernel_args(world_size: int, n_elem: int, *, cube_w: int = 4, cube_h: int = 4) -> tuple:
    return (n_elem, cube_w, cube_h, world_size)

# (필수) kernel 함수 (`tl=...` 키워드를 통해 TLContext 가 주입됨)
def my_allreduce_kernel(t_ptr, n_elem, cube_w, cube_h, n_sips,
                         sip_rank, sip_topo_kind, sip_topo_w, sip_topo_h, *, tl):
    ...

# (필수) kernel alias — backend 가 `module.kernel` 로 접근
kernel = my_allreduce_kernel
  • kernel alias 는 backend 가 직접 호출하는 entry point 다. 함수 이름이 무엇이든 (allreduce_intercube_multidevice 처럼) module.kernel = fn 으로 노출해야 한다.
  • kernel_args 가 없으면 backend 가 알고리즘 인자를 만들 방법이 없다. signature 는 D2 참고.
  • TOPO_NAME_TO_KIND 가 없으면 backend 는 sip_topo_kind = 0 으로 fallback 한다. 단일 topology 만 지원하는 알고리즘이라면 생략 가능.

D2. kernel_args 시그너처 — (world_size, n_elem, *, cube_w, cube_h)

def kernel_args(world_size: int, n_elem: int, *,
                cube_w: int = 4, cube_h: int = 4) -> tuple:
    return (n_elem, cube_w, cube_h, world_size)
  • 위치 인자: world_size (= rank 수), n_elem (= 단일 shard 의 element 수, f16 기준).
  • 키워드 인자: cube_w, cube_h (= cube mesh 크기). default 는 4×4 — topology.yamlsip.cube_mesh 기본값과 정합.
  • 반환: kernel 의 위치 인자 순서대로 묶은 tuple.

backend 의 all_reduce 가 호출 시:

kernel_args_tuple = self._algo_module.kernel_args(
    self._world_size, n_elem, cube_w=eff_cube_w, cube_h=eff_cube_h,
)
extra_args = (sip_rank, sip_topo_kind, sip_topo_w, sip_topo_h)
pending = self.ctx.launch(
    self._merged["algorithm"], kernel_fn, tensor,
    *kernel_args_tuple, *extra_args, _defer_wait=True,
)

즉 kernel 의 최종 위치 인자는: (tensor_ptr, *kernel_args_tuple, sip_rank, sip_topo_kind, sip_topo_w, sip_topo_h) 이며, 거기에 tl=... 가 키워드로 자동 주입된다. kernel_args 가 돌려주는 tuple 의 길이/순서는 kernel signature 와 1:1 일치 해야 한다.

D3. kernel 함수 시그너처 — 정형화된 9 + tl 인자

권장 시그너처:

def my_kernel(
    t_ptr: int,             # VA base of the row-wise-sharded tensor on this SIP
    n_elem: int,            # element count per cube tile (or per shard)
    cube_w: int,            # cube mesh width  (kernel_args 에서 옴)
    cube_h: int,            # cube mesh height (kernel_args 에서 옴)
    n_sips: int,            # world_size 와 동일 (rank = SIP, ADR-0024)
    sip_rank: int,          # 이 SIP 의 rank
    sip_topo_kind: int,     # TOPO_NAME_TO_KIND lookup 결과
    sip_topo_w: int,        # SIP mesh width (ring_1d 면 0)
    sip_topo_h: int,        # SIP mesh height (ring_1d 면 0)
    *, tl,                  # TLContext (auto-injected)
) -> None:

kernel_args 가 다른 위치 인자 순서를 채택하더라도, kernel 의 마지막 4 개 위치 인자는 항상 (sip_rank, sip_topo_kind, sip_topo_w, sip_topo_h) 이며 backend 가 extra_args 로 append 한다 (ADR-0047 D5). 이 4 개 인자는 사용자 정의 algorithm 도 받아야 하지만, 알고리즘이 single-SIP 이라면 그냥 무시하면 된다.

tl 은 위치 인자가 아닌 키워드로 주입된다 — RuntimeContext.launch 가 kernel 호출 직전에 tl=tl_ctx 를 추가한다. 따라서 kernel signature 의 tl 은 keyword-only (*, tl) 또는 마지막 키워드 매개변수 형태여야 한다.

D4. kernel body 의 자유도와 제약

kernel body 안에서 사용 가능한 표면: ADR-0046 D3 의 모든 tl.* primitive.

특히 자주 쓰이는 패턴:

  • cube_id = tl.program_id(axis=1) — 이 PE 가 속한 cube 인덱스.
  • pe_addr = t_ptr + cube_id * nbytes — cube-별 tile 의 VA 계산.
  • acc = tl.load(pe_addr, shape=(n_elem,), dtype="f16") — local 데이터 로드.
  • tl.send(dir=...) / tl.recv(dir=..., shape=, dtype=) — IPCQ collective.
  • acc = acc + recv — TensorHandle 산술 연산자 (ADR-0046 D4).
  • tl.store(pe_addr, acc) — 결과 저장.

kernel body 는 일반 Python 함수이며, branching/looping 자유. 단:

  • SimPy yield 또는 async 금지 (ADR-0046 D1).
  • TensorHandle 의 .data 직접 접근 금지 — phase 1 timing 모델은 데이터 의존을 모른다 (ADR-0020 의 2-pass 분리).
  • kernel 실행은 deterministic 해야 한다 — 같은 입력으로 두 번 실행하면 같은 op 시퀀스 발사. random / external IO 금지.

D5. SIP topology semantics — sip_topo_kind 의 의미

backend 가 topology.yamlsystem.sips.topology 문자열을 algorithm 모듈의 TOPO_NAME_TO_KIND 로 lookup 하여 sip_topo_kind 정수로 변환. algorithm 은 이 정수를 보고 분기:

if sip_topo_kind == SIP_TOPO_RING:
    acc = _inter_sip_ring(...)
elif sip_topo_kind == SIP_TOPO_TORUS:
    acc = _inter_sip_torus_2d(...)
elif sip_topo_kind == SIP_TOPO_MESH:
    acc = _inter_sip_mesh_2d(...)

각 topology branch 는 IPCQ direction 이름 (예: "global_E", "W", "S", "N") 을 통해 peer 와 통신. direction 의 의미는 ADR-0023/0025 가 정의 하며, configure_sfr_intercube_multisip 가 IPCQ neighbor table 을 그에 맞춰 설치한다.

algorithm 모듈은 자기가 지원하지 않는 topology kind 가 들어오면 silent no-op 으로 두기보다 명시적으로 raise ValueError(f"unsupported topology kind {sip_topo_kind}") 하는 것을 권장 — 실수로 backend 에 잘못 dispatch 된 경우 빠르게 fail.

D6. ccl.yaml 의 algorithm entry 구조

algorithm 모듈은 ccl.yaml 의 entry 와 짝을 이룬다 (ADR-0023 D10 + ADR-0047 D3):

defaults:
  algorithm: lrab_hierarchical_allreduce
  n_elem: 8

algorithms:
  lrab_hierarchical_allreduce:
    module: kernbench.ccl.algorithms.lrab_hierarchical_allreduce
    # optional: world_size override
    # optional: per-algorithm parameters consumed by configure_sfr_intercube_multisip
  • module: full Python module path. backend 의 importlib.import_module 가 이 문자열을 그대로 사용.
  • world_size (optional): 명시되면 topology fallback 을 override (ADR-0047 D2).
  • algorithm-specific parameters 는 configure_sfr_intercube_multisip 가 소비.

새 algorithm 추가 시:

  1. src/kernbench/ccl/algorithms/<name>.py 작성 (D1 컨벤션).
  2. ccl.yamlalgorithms 섹션에 entry 추가.
  3. (필요 시) kernbench.ccl.sfr_config 에 SFR 설치 분기 추가.
  4. test 추가 (예: tests/sccl/test_<name>.py, ADR-0043 의 eval harness 확장).

D7. legacy "rank = flat PE index" 모드

ADR-0047 D2 가 명시한 ccl.yamlworld_size override 경로는 legacy "rank = flat PE index" 테스트가 사용한다. algorithm 모듈은 이 모드 에서도 n_sips=world_size 만큼의 rank 가 들어옴을 가정하면 된다 — backend 가 rank↔(SIP, cube, PE) 매핑을 사전에 분리해 두므로 algorithm 본체에서는 modal 분기가 필요 없다.

단, single-cube workload 에서는 cube_w=cube_h=1 이 들어와 mesh-기반 phase 들이 skip 되도록 작성해야 한다 (lrab_hierarchical_allreduce.pysingle_cube = (cube_w == 1 and cube_h == 1) 패턴 참고).

Alternatives Considered

A1. algorithm 모듈을 class 로 구조화 (class Allreduce: kernel(...) 등)

기각. Python 모듈 namespace 자체가 algorithm 의 identity 로 사용 중이며 (ADR-0047 D3 의 importlib.import_module), class 한 겹은 추가 indirection 만 늘리고 dispatch 측 코드를 두텁게 만든다. 모듈-레벨 free function

  • kernel alias 패턴이 충분히 명확.

A2. kernel_args 를 명시적 dataclass 로 typing

기각 (현재). algorithm 마다 인자 갯수가 다른 것이 정상이며, dataclass 한 종류를 강제하면 다양한 algorithm 간 호환이 어려워진다. tuple 반환은 simple 하고 backend 측 *kernel_args_tuple unpacking 과 깨끗이 맞물린다. algorithm 별 자체 타입 강도가 필요해지면 그 algorithm 모듈 안에서 NamedTuple 사용은 자유.

A3. SFR 설치를 algorithm 모듈 안으로

기각. SFR 설치 (configure_sfr_intercube_multisip) 는 topology + algorithm 모두를 보고 IPCQ neighbor table 을 설치하는 cross-module 결정이라, algorithm 모듈 내부보다 kernbench.ccl.sfr_config 같은 전용 위치가 자연스럽다. D6 의 "필요 시 sfr_config 분기 추가" 워크플로우가 책임 분리 측면에서 더 명확.

A4. algorithm name 을 모듈 namespace 에 자동 등록 (decorator)

기각. ADR-0045 (bench) 와 달리 algorithm 은 ccl.yaml entry 와 직접 묶여 있어 추가 등록 레지스트리가 중복이다. module 문자열 매핑 하나면 충분.

Consequences

  • ADR-0047 D3 의 한 줄 contract 가 D1–D7 의 작성자-친화적 가이드로 확장 되어, 새 algorithm 추가 시 시그너처를 grep 으로 추론할 필요 없음.
  • D3 의 9 + tl 인자 시그너처가 표준화되어, backend 의 extra_args append (ADR-0047 D5) 와 자연스럽게 맞물림. 향후 single-SIP-only algorithm 도 4 개의 sip_* 인자를 받아야 함이 명시.
  • D5 의 fail-loud 권장으로, ccl.yaml 의 topology 가 algorithm 미지원 topology 로 잘못 설정되면 backend 가 silent wrong-result 가 아닌 ValueError 로 fail.
  • D6 의 단계별 추가 절차가 명시되어, 새 algorithm 추가가 sfr_config / test / ccl.yaml 어디까지 손대야 하는지 분명.