Files
kernbench2/docs/adr-ko/ADR-0045-prog-bench-module-contract.md
T
mukesh cc1bbd0ab7 eval: fold GEMM/allreduce harnesses into self-contained milestone benches
Move the GEMM + allreduce sweep/render logic out of scripts/ and tests/
into two self-contained eval benches so a user can regenerate every
result + figure with one command:

  kernbench run --bench milestone-1h-gemm   (MILESTONE_FAST=1 reuses JSON)
  kernbench run --bench milestone-1h-ccl

- benches/milestone_1h_{gemm,ccl}.py: single home for each domain; the
  run(torch) entry drives the sweeps and writes figures into
  benches/1H_milestone_output/{gemm,ccl}/ (gitignored), then submits a
  sentinel tensor to satisfy the run_bench contract.
- tests/gemm + tests/sccl helpers and scripts/gemm_sweep.py become thin
  re-export/wrapper shims over the benches (single source preserved); the
  pytest-only param builders + _run_distributed wrapper stay in the shim.
- eval-bench pattern: a bench may drive many configs + build its own
  per-config engines (extends ADR-0045 D5; reverses ADR-0044 D1/D2).

ADR-0054 (EN+KO) records the design; ADR-0043/0044/0045 + CLAUDE.md CLI
Semantics amended; ADR INDEX regenerated. Verified: milestone benches run
clean (ok=True, all artifacts), full suite 67 passed, lang-pairs OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:19:52 -07:00

14 KiB

ADR-0045: Bench Module Contract — registration, dispatch, and authoring

Status

Accepted (2026-05-21).

src/kernbench/benches/ 패키지의 등록 메커니즘(@bench), CLI 디스패치 경로 (kernbench run/list), 그리고 새 bench 모듈 작성 시 따라야 할 계약을 통합 정의한다. ADR-0010 (CLI surface)이 kernbench list/run 인터페이스를 명세하나, bench가 어떻게 등록되고 어떤 함수 시그너처를 따라야 하는가는 ADR 레벨에 없었음.

ADR-0054로 확장됨: D5의 단일 구성 규칙에 세 번째 패턴이 추가된다 — 평가 bench(예: milestone-1h-*)는 여러 구성을 구동하고, 구성별 자체 엔진을 빌드하며, D4를 만족시키기 위해 sentinel 텐서를 제출한다.

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

kernbench.benches 패키지가 임포트되면 __init__.py 가 즉시 _eager_import_and_audit(__path__, __name__) 를 호출한다. 이 함수의 첫 일은 패키지 디렉터리 안의 모든 형제 모듈을 pkgutil.iter_modules(__path__)로 나열한 뒤, 다음 두 조건을 만족하지 않는 모듈을 모두 importlib.import_module(...)즉시 로드하는 것이다:

  • 이름이 registry 인 경우 (인프라 자체)
  • 이름이 _ 로 시작하는 경우 (helper 모듈)

임포트 시점에 각 모듈 안의 @bench(name=..., description=...) 데코레이터가 실행되어 _PENDING 리스트에 (name, description, fn) 튜플이 append 되고, _REGISTERED_MODULES 셋에 fn.__module__ 가 추가된다.

전체 임포트가 끝나면 _audit_modules(imported, _REGISTERED_MODULES) 가 호출되어, 임포트는 되었지만 @bench를 한 번도 호출하지 않은 모듈이 있으면 RuntimeError("Bench module(s) missing @bench decorator: ...") 가 즉시 발생한다. 이 audit이 통과한 시점에 인덱스 할당은 아직 일어나지 않은 상태이며, 첫 list_all() / resolve(...) 호출 시 _finalize() 가 이름 알파벳 정렬 순으로 1-based index를 부여한다.

즉, bench 인프라의 첫 일은 "패키지 디렉터리의 모든 비-helper 모듈을 임포트 하고, 각 모듈이 최소 한 번 @bench를 호출했는지 감사하는 것" 이다.

Context

src/kernbench/benches/ 는 현재 8개의 bench 모듈을 보유한다 (ccl_allreduce, gemm_single_pe, gpt3_qkv, ipcq_allreduce, matmul_composite, qkv_gemm, qkv_gemm_multi_pe, va_offset_verify). 모든 bench는 다음 통합 흐름을 따른다:

kernbench run --topology <T> --bench <N>
   ↓
cli/main.py::cmd_run
   ↓  resolve_topology(T)  + resolve(N)  + resolve_device(device_arg)
   ↓
runtime_api/bench_runner.py::run_bench(topology, bench_fn, device, engine_factory)
   ↓  engine_factory(topology, device) → GraphEngine
   ↓  RuntimeContext(engine, target_device, correlation_id, spec)
   ↓
bench_fn(ctx)        ← bench가 정의한 run(torch) 가 호출됨
   ↓  ctx.empty/zeros/from_numpy/launch/distributed.* 등을 통해 submit
   ↓
ctx.wait_all()       ← 미완료 핸들이 있으면 drain
   ↓
BenchResult(completion, correlation_id, trace, traces, engine)

ADR-0010 은 CLI 표면만 다루고 (run/list/probe/web), ADR-0007 은 runtime API ↔ sim_engine 책임 경계만 다룬다. 정작 "새 bench 파일을 추가하려면 어떤 모양으로 써야 하는가"는 코드 컨벤션만으로 추적해야 한다. 결과적으로:

  • @bench 데코레이터의 호출 규약 (kebab-case 이름, non-empty description)이 코드에만 존재.
  • bench 함수 시그너처 (def run(torch)) 가 사실상 컨벤션인데, CLI 디스패치 측이 spec.run 을 호출한다는 사실로 강제되고 있음.
  • 신규 bench 추가자가 "helper 모듈은 _ 접두로 분리해야 한다"는 것을 audit RuntimeError를 받아본 뒤에야 학습.
  • single-device 컨벤션 (CLAUDE.md Part 2 CLI Semantics)이 bench 작성자 관점에서 어디까지 적용되는지 (CCL 멀티-SIP bench는 예외인가?) 명확하지 않음.

이 ADR이 이런 모호함을 한 곳에 정리한다.

Decision

D1. @bench 데코레이터 계약

from kernbench.benches.registry import bench

@bench(name="my-bench", description="Short, complete-sentence description.")
def run(torch):
    ...
  • name: kebab-case 문자열. 정규식 ^[a-z][a-z0-9]*(-[a-z0-9]+)*$ 통과 필요. 소문자/숫자/대시만 허용; 밑줄(_) 금지; 알파벳으로 시작.
  • description: non-empty 문자열 (strip 후 길이 > 0). CLI list 출력에 그대로 표시됨.
  • 데코레이터는 fn을 변형 없이 반환한다 — 즉 직접 호출도 가능. 부수효과로 _PENDING 에 등록만 추가한다.

위 두 규칙 위반은 즉시 ValueError. duplicate name은 _finalize() 시점에 RuntimeError("duplicate bench name: ...") 로 잡힌다.

D2. 모듈 파일 컨벤션

src/kernbench/benches/<slug>.py 는 다음 중 하나여야 한다:

  • bench 모듈: 최상위 임포트 경로에서 적어도 한 번 @bench(...) 가 실행되어 최소 하나의 bench를 등록한다.
  • helper 모듈: 파일명이 _ 로 시작 (예: _shared_helpers.py). iter_modules 순회에서 스킵된다.

audit (_audit_modules) 는 helper가 아닌데도 @bench를 호출하지 않은 모듈을 허용하지 않는다. 의도된 결과: 새 파일을 benches/ 에 추가하기만 하면 자동 등록되며, helper와의 구분은 파일명 접두사 하나로 명확하게 표시된다.

D3. bench 함수 시그너처는 def run(torch)

데코레이터는 함수 이름을 강제하지 않지만, CLI 디스패치는 spec_entry.run (즉 데코레이트된 callable) 을 호출한다. 따라서 컨벤션은:

  • 함수 이름: run. 다른 이름으로 데코레이트해도 동작은 하지만 readability / grep-ability 측면에서 항상 run.
  • 인자: 단일 위치 인자 torch. 실제로는 RuntimeContext 인스턴스이며 PyTorch 스타일의 namespace (zeros/empty/launch/distributed/...)를 노출한다 (ADR-0024 D3).
  • 반환값: 임의 (Any). 현재 run_bench 는 반환값을 무시하고 ctx.handles() / engine.get_completion() 로 완료를 추적한다.

torch 이름은 PyTorch 호환 idiom을 흉내내기 위함이며, 실제로 PyTorch 모듈이 들어오는 것은 아니다 (ADR-0024 의 "rank = SIP" launcher 컨벤션과 정렬).

D4. bench는 최소 한 번의 submit을 수행해야 한다

run_benchctx.handles() 가 비어 있는 경우 BenchResult.completion 을 ok=False, error_code="NO_REQUESTS" 로 반환한다. 따라서 의미 있는 bench는 다음 중 하나 이상을 호출해야 한다:

  • 텐서 생성 API: torch.zeros(...), torch.empty(...) — 내부적으로 MmuMapMsg 와 (zeros 의 경우) MemoryWriteMsg 가 submit 됨.
  • 커널 실행 API: torch.launch(name, fn, *args)KernelLaunchMsg 를 SIP 별로 submit.
  • (예외) 빈 placeholder bench: ipcq_allreduce.py 처럼 print(...) 만 하는 스텁은 NO_REQUESTS 결과를 받게 됨. CI 측에서 placeholder임을 인지하고 별도 처리하는 것을 가정한다.

D5. 단일-디바이스 컨벤션 + 멀티-SIP 예외 (ADR-0024/0027)

CLAUDE.md Part 2 CLI Semantics 가 명시하는 "benchmarks MUST remain single-device" 컨벤션은 다음과 같이 해석된다:

  • 일반 bench (single-SIP 사용): dp = DPPolicy(...) 로 텐서 placement를 정의하고 torch.launch(...) 로 커널 발사. SIP 인덱스는 --device 가 결정한다 (CLI 측 책임).
  • CCL bench (멀티-SIP 사용): 예외적으로 torch.distributed.init_process_group (backend="ahbm") + torch.multiprocessing.spawn(_worker, ..., nprocs=ws) 로 rank = SIP 패턴 (ADR-0024 D3) 을 따른다. --device 는 무시되며 (또는 all 로 가정), 각 spawned worker가 torch.ahbm.set_device(rank) 로 자신의 SIP를 바인딩한다.

이 두 패턴 외의 멀티-디바이스 호출 (예: 한 bench 함수가 동일 process에서 여러 SIP을 직접 launch) 은 본 ADR이 금지한다. CLI 가 --device all 로 호출되어도 bench는 한 번만 실행되며, 그 안에서 멀티-SIP을 다루려면 D5의 두 번째 패턴을 사용한다.

D6. 이름·인덱스 해석 (resolve)

resolve(identifier: str) 는 다음 순서로 BenchSpec을 반환한다:

  1. identifier.isdigit() → 정수 변환 후 _REGISTRY 의 entries에서 index == 인 spec 반환. 없으면 ValueError("No bench with index ...")`.
  2. identifier in _REGISTRY → 직접 lookup.
  3. 그 외 → ValueError("Unknown bench ...").

빈/공백 identifier 는 ValueError("bench identifier must be a non-empty string.").

CLI 는 --bench 의 인자를 그대로 resolve 에 넘긴다. 따라서 사용자는 kernbench run --bench gemm-single-pe 또는 kernbench run --bench 2 형식 모두 사용 가능.

D7. 인덱스는 안정 API가 아니다

_finalize()_PENDING이름 알파벳 정렬 후 1-based index를 부여하므로, 새 bench 가 추가되면 기존 bench의 index가 밀릴 수 있다. 따라서:

  • 사람-친화적 인터랙티브 사용: 인덱스 OK.
  • 스크립트 / CI 자동화: 반드시 이름을 사용한다.

이 사실은 registry.py 모듈 docstring 에 명시되어 있다.

D8. RuntimeContext 가 bench에 노출하는 표면

bench 함수가 torch 파라미터를 통해 정상적으로 사용할 수 있는 표면:

  • 텐서 생성: torch.empty(shape, dtype=..., dp=DPPolicy(...), name=...), torch.zeros(...), torch.from_numpy(arr). 모두 host-side 메타 + 디바이스 배포 (MmuMap + MemoryWrite) 를 submit 한다.
  • 커널 발사: torch.launch(kernel_name, kernel_fn, *args)(Tensor, int, float) 위치 인자를 TensorArg / ScalarArg 로 변환하여 SIP 별 KernelLaunchMsg 발행 후 drain.
  • 동기화: torch.wait(handle), torch.wait_all() (run_bench 가 자동 호출).
  • 분산: torch.distributed.init_process_group(backend="ahbm"), torch.distributed.get_world_size(), torch.distributed.all_reduce(t, op=...) (ADR-0024/0027).
  • 멀티-프로세스 (rank=SIP): torch.multiprocessing.spawn(_worker, ..., nprocs=ws) (ADR-0024 D3 / ADR-0027).
  • 디바이스 바인딩: torch.ahbm.set_device(rank) 또는 torch.accelerator.set_device_index(rank) (둘 다 같은 namespace를 가리킴).
  • IPCQ 설치: torch.install_ipcq(algorithm=..., ccl_yaml=...) (ADR-0023 D10).
  • 스펙 조회: torch.spec — 토폴로지 빌더가 만든 dict (시스템·cube_mesh·HBM 파라미터 등). bench가 toplogy.yaml 파라미터에 의존하지 않게 짜기 위함.

bench는 위에 열거되지 않은 RuntimeContext 의 private 멤버 (_handles, _traces, _allocators 등) 에 직접 접근해선 안 된다. ADR-0007 의 layer boundary 정신과 정렬: bench → runtime API → sim_engine 한 방향만 허용.

D9. 환경 변수로 파라미터화는 허용된다

matmul_composite.py 처럼 os.environ.get("MATMUL_M", ...) 등으로 bench 파라미터를 외부에서 주입하는 패턴은 허용한다. 이유:

  • bench 함수 시그너처는 D3 에 의해 def run(torch) 로 고정되어 있어 위치/키워드 인자로 파라미터를 받기 곤란.
  • 환경 변수 패턴은 MATMUL_VARIANT 같은 운영-시 스윕을 위한 자연스러운 hook.
  • scripts/gemm_sweep.py 같은 외부 드라이버 (ADR-0044) 가 이 hook을 사용한다.

단, 환경 변수가 bench의 동작을 바꾼다면 모듈 docstring 에 모든 변수를 명시할 것 (matmul_composite.py 가 그 예시).

Alternatives Considered

A1. 명시적 manifest 파일 (YAML)에 bench 목록 두기

기각. @bench 데코레이터 + audit 패턴은 "파일 추가 = 자동 등록" 을 보장하여 신규 bench 작성자의 인지 비용을 한 곳 (파일 작성)으로 집중시킨다. 별도 manifest는 유지보수 측에서 drift 위험이 크고, helper 분리는 이미 _ 접두로 명확하다.

A2. bench 함수 이름을 데코레이터 인자로 받기 (@bench(name=..., entry="run_xxx"))

기각. 디스패치 측에서 spec.run 하나만 호출하면 되는 단순함을 깬다. run 컨벤션 하나로 충분하며, 변종이 필요하면 같은 모듈에 여러 함수를 등록하면 된다 (각각 @bench 데코레이트).

A3. CCL bench를 위한 별도 @multi_device_bench 데코레이터

기각. D5에서 명시한 두 패턴 (single + ADR-0024 멀티-SIP) 만으로 현재 8개 bench가 모두 표현 가능. 별도 데코레이터는 디스패치 측에서 분기를 강제하여 복잡도를 늘리며, 멀티-SIP 사용 의도는 bench 함수 본문의 init_process_group(...) 호출로 충분히 드러난다.

A4. 인덱스를 안정 API로 만들기 (등록 순서 / explicit index= 인자)

기각. D7에서 명시한 trade-off — 사용자 친화성 (알파벳 정렬된 인덱스가 list 출력 에서 자연스럽게 1, 2, 3...) 우선. 스크립트는 이름으로 지정하면 충분.

Consequences

  • "bench 추가 방법" 이 한 ADR로 정리됨 → 신규 작성자가 코드 grep 없이 D1-D3, D8 만 따르면 됨.
  • helper 모듈을 _ 접두로 분리하는 패턴이 ADR-level에서 정당화되어, 향후 benches/_*.py 식의 공유 helper 작성이 자유로워짐.
  • CLAUDE.md Part 2 CLI Semantics 의 single-device 컨벤션이 멀티-SIP CCL bench 와 모순되지 않음을 D5 가 명시 — 둘은 직교한다.
  • ADR-0044 (GEMM eval harness) 의 scripts/gemm_sweep.py 가 환경 변수 hook을 사용하는 근거 (D9) 가 본 ADR에 굳어짐.
  • 인덱스가 불안정함 (D7) 이 명시되어, CI 측 kernbench run --bench 3 같은 코드는 본 ADR 수락 직후 점검 대상.