# ADR-0049: `kernbench probe` Subcommand — Traffic-Pattern Verification Harness

## Status

Accepted (2026-05-22).

`probes/probe.py` 의 `run_probe(...)` 가 노출하는 traffic-pattern catalog,
formula vs actual 비교, 그리고 monotonicity / D2H≥H2D 같은 invariant
체크의 의미를 명시한다. ADR-0010 (CLI surface) 가 `kernbench probe`
subcommand 를 enumerate 하나, **probe 가 실제로 측정하는 것**과 **어떤
invariant 를 PASS/FAIL 로 판정하는가**는 ADR-level 에 없었다.

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

`run_probe(topology_path, case_filter=None)` 의 첫 4가지 작업:

1. `Path(topology_path).expanduser().resolve()` 로 절대 경로 산출.
2. `load_topology(path)` → `TopologyGraph` 인스턴스 (그래프 + spec).
3. `_build_edge_map(graph)` → `{(src, dst): Edge}` 빠른 lookup 테이블.
4. `AddressResolver(graph)` + `PathRouter(graph)` 인스턴스화.

그 다음 `nbytes = 32768` (= 32 KiB, summary table 의 기준 데이터 크기) 와
`show_all = (case_filter is None or case_filter == "all")` 를 설정.

즉, **probe 의 첫 일은 "토폴로지를 한 번 로드하여 edge map / resolver /
router 를 준비하고, 32 KiB 라는 표준 측정 크기를 픽스하는 것"**. 그 이후
H2D → D2H → PE DMA 세 카테고리의 case 들이 각각 별도의 `GraphEngine`
인스턴스에서 실행된다 (case 간 cross-talk 차단).

## Context

`kernbench probe` 는 다음 의도로 도입된 verification 도구다:

- **수동 분석 ground truth**: 실 시뮬레이션 (`kernbench run --bench ...`)
  결과의 latency 가 비정상으로 보일 때, 단순 traffic pattern 의 정답을 별도
  로 얻어 비교.
- **formula vs actual 비교**: 분석 모델 (wire latency + overhead + drain)
  과 시뮬레이션 결과 (`total_ns`) 가 일치하는지 확인. 일치하지 않으면 모델
  단순화 가정 (ADR-0033) 어디가 빠진 것인지 단서.
- **monotonicity check**: hop 수가 늘면 latency 가 단조 증가해야 한다는
  invariant 의 자동 확인.
- **utilization sweep**: 데이터 크기 (4 KiB ~ 1 MiB) 별 BW 활용률 표.

이 도구의 동작 사양이 ADR-level 에 없으면:

- 다른 형식의 traffic pattern (예: MCpuDma, IPCQ) 을 추가하려는 사람이 기존
  카테고리의 표 포맷 / 측정 단위를 일관되게 따르기 어렵다.
- monotonicity 가 무엇을 기준으로 검사되는지 (hop 수? cube 거리? wire
  길이?) 모호.
- 32 KiB 라는 기준 크기와 `[4 KiB, 16 KiB, 64 KiB, 256 KiB, 1 MiB]` sweep
  의 의미가 코드 grep 으로만 확인 가능.

## Decision

### D1. 세 가지 case category — H2D / D2H / PE DMA

각 category 는 토폴로지 상 별개의 데이터 경로를 가지며, 별도의 summary
table + sweep table + route detail block 으로 출력된다.

- **H2D (Host→Device Write)**: `MemoryWriteMsg(dst_sip=0, dst_cube,
  dst_pe=0, pattern="zero")` 가 `pcie_ep → io_cpu → m_cpu → hbm_ctrl` 경로
  를 흐른다. cube 인덱스로 hop 수가 증가:
  - h2d-1hop: cube=0, hops=1
  - h2d-2hop: cube=4, hops=2
  - h2d-3hop: cube=8, hops=3
  - h2d-4hop: cube=12, hops=4
- **D2H (Device→Host Read)**: `MemoryReadMsg(src_sip=0, src_cube, src_pe=0)`.
  forward command path + reverse data path 의 합 latency. 같은 4 hops
  카테고리.
- **PE DMA (PE-initiated)**: `PeDmaMsg(src_sip, src_cube, src_pe, dst_pa)`.
  5 가지 케이스로 cube/PE 위치 변화:
  - pe-local-hbm: same cube, same PE
  - pe-same-half-hbm: same cube, different PE (PE 1)
  - pe-cross-half-hbm: same cube, far PE (PE 4)
  - pe-cross-cube-hbm-best: adjacent cube (cube 1)
  - pe-cross-cube-hbm-worst: diagonal far cube (cube 15)

cube 인덱스가 4/8/12 (H2D), 1/4/15 (PE DMA) 같이 의미 있는 이유는
4x4 cube mesh (sip.cube_mesh.w=4, h=4) 에서의 거리 정의 — 추후 cube_mesh
크기 변경 시 이 값들이 같이 갱신되어야 한다.

### D2. 표준 측정 크기 — `nbytes = 32768` (32 KiB)

모든 case 의 summary table 은 `nbytes=32768` 로 한 번 실행한 결과를
보여준다. 32 KiB 가 선택된 이유:

- DMA overhead 와 BW drain 이 한쪽으로 치우치지 않는 적당한 크기.
- 다수 sub-unit (TCM, register file) 의 1회 transfer 단위와 비교 가능.

크기별 utilization 변화는 별도 sweep table 이 보여준다 (D3).

### D3. Utilization sweep — `[4 KiB, 16 KiB, 64 KiB, 256 KiB, 1 MiB]`

`SWEEP_SIZES = [4096, 16384, 65536, 262144, 1048576]`, `SWEEP_LABELS =
["4KB", "16KB", "64KB", "256KB", "1MB"]`. 매 size 마다 다음 공식:

```
drain   = nbytes / bottleneck_bw
total   = overhead + wire + drain
eff_bw  = nbytes / total
util%   = eff_bw / bottleneck_bw × 100
```

`bn_bw is None or <= 0` 이면 그 컬럼은 0.0 % 로 출력. 의미: hop 수가 늘
수록 작은 transfer 는 overhead-bound, 큰 transfer 는 drain-bound 가 되는
패턴을 한 표에서 확인.

### D4. 측정 항목 — actual / formula / breakdown

각 case 행에 표시되는 컬럼:

- `Actual` (total_ns): SimPy 실행 결과의 `trace["total_ns"]`.
- `Ovhd`: 경로상 모든 node 의 `node.attrs["overhead_ns"]` 합 (formula
  breakdown).
- `Drain`: `nbytes / min(edge.bw_gbs over path)` (formula).
- `Wire`: `Σ edge.distance_mm * (ns_per_mm from spec)`.
- `Ovhd%` / `Drain%`: Ovhd/Drain 이 Actual 에서 차지하는 비율 (formula 의
  Wire 는 통상 매우 작아 표시하지 않음).
- `Eff.BW`: `nbytes / total_ns` (실 측정 BW).
- `BN.BW`: bottleneck bandwidth (formula). path 상 모든 edge 의 BW 중 최소.
  edge BW 가 없으면 "-".
- `Util%`: `Eff.BW / BN.BW × 100`. 100% 면 single-stream BW upper bound 에
  도달.

formula 의 합 (`wire + ovhd + drain`) 과 actual 의 차이가 크면 모델
단순화가 잡지 못하는 요소가 있다는 신호 (ADR-0033 의 가정 점검).

### D5. Invariant 자동 체크 — PASS/FAIL

다음 invariant 들이 자동으로 확인되어 `[v] PASS` / `[x] FAIL` 로 출력:

- **H2D / D2H monotonic increase**: hop 수가 늘면 actual latency 가
  단조 증가해야 함. `all(lats[i] < lats[i+1] for ...)`.
- **D2H ≥ H2D**: 같은 hop 인덱스에서 D2H ≥ H2D (D2H 는 forward command
  + reverse data 두 leg 이므로). `all(d2h[i].total >= h2d[i].total)`.
- **PE DMA best < worst**: cross-cube best (adjacent) latency < cross-cube
  worst (diagonal) latency.
- **PE DMA local vs remote**: local BN BW vs remote BN BW 의 비교 출력
  (PASS/FAIL 이 아닌 정보성).

체크가 FAIL 이면 사람이 즉시 모델/토폴로지 회귀를 인지할 수 있도록 한
줄로 분명하게 출력.

### D6. Route detail — per-hop timestamp trace

summary 와 sweep 표 이후 각 case 의 path 와 per-hop 누적 시간 (
`_hop_timestamps`) 가 별도 섹션에서 출력된다:

- H2D: leg1 (`pcie_ep → io_cpu`) + leg2 (`io_cpu → m_cpu`) + leg3
  (`m_cpu → hbm_ctrl`) + per-hop trace.
- D2H: forward (cmd, no data) + reverse (data) trace 분리 표시.
- PE DMA: `pe_dma → router → hbm_ctrl` path + per-hop trace.

각 hop 의 timestamp 는 cumulative `wire_ns + overhead_ns` 누적. terminal
hop 의 annotation 에 `drain:Xns` 가 붙는다. bottleneck edge 는
`<BN:XXGB/s>` 로 표시되어 시각적으로 식별 가능.

### D7. case_filter 인자의 의미

- `None` 또는 `"all"`: 모든 case 실행 (default).
- 다른 문자열: 그 이름과 정확히 일치하는 case 만 실행. 예: `kernbench
  probe --case h2d-2hop`.

각 카테고리 안에서 `name != case_filter` 면 skip 되며, 그 카테고리의
monotonicity / D2H≥H2D 비교는 데이터가 1개일 때 자연히 skip 된다.

CLI parser 의 `--case` 기본값은 `"all"`이라 인자 생략 시 전체 실행.

### D8. 매 case 별 fresh GraphEngine

H2D 4개, D2H 4개, PE DMA 5개의 case 가 각각 **새로운 GraphEngine**
인스턴스에서 실행된다 (`engine = GraphEngine(graph)`). 이유:

- case 간 누적 상태 (op_log, completion 추적, allocator 등) 가 cross-talk
  하지 않도록 격리.
- 한 case 의 traffic 이 다른 case 의 BW 측정에 영향을 주지 않도록 보장.

이 격리는 probe 의 측정 결과를 **각 case 단독 single-flow** 의 latency 로
해석할 수 있게 한다. multi-flow contention 측정은 별도 도구 (예:
`pe2pe_overview` 플롯, ADR-0033 의 multi-flow merging 모델) 책임.

### D9. 출력 포맷의 안정성

probe 의 stdout 출력은 사람이 읽기 위함이며, 정확한 컬럼 폭/구분자/공백 은
machine-readable contract 가 아니다. 자동화된 도구가 probe 결과를 파싱
하려면 별도 JSON 출력 모드를 추가해야 한다 (현재 미구현).

PASS/FAIL 줄의 `[v]` / `[x]` 접두사는 CI grep 용 anchor 로 안정 보장.

## Alternatives Considered

### A1. Probe 를 별도 bench 로 등록 (`@bench(name="probe")`)

기각. probe 는 bench 가 아니라 verification 도구로 의도된다 — sweep / 분석
용 multi-engine 실행과 invariant PASS/FAIL 출력이 본질이며, ADR-0045 의
"단일 디바이스 + 단일 RuntimeContext" bench 모델과 맞지 않는다.

### A2. monotonicity 위반 시 exit code 1

기각 (현재). 인간 검사 도구 위주로 의도되어 있어 PASS/FAIL 줄을 출력하고
exit 0 로 종료. CI 가 violation 으로 fail 하길 원하면 별도 wrapper 가
`grep "\[x\]"` 결과로 판단하면 됨. 후속으로 strict-mode flag (`--strict`)
도입 가능.

### A3. probe 의 case 정의를 외부 YAML 로

기각 (현재). 8개 case (4 H2D + 4 D2H + 5 PE DMA — 합 13개) 는 코드에
하드코딩되어 있고 의미가 토폴로지 mesh 구조에 단단히 묶여 있다. 외부
YAML 로 옮기면 cube 인덱스의 의미 (4, 8, 12 / 1, 4, 15) 를 별도로 문서화
해야 하므로 응집도 손실. 케이스 추가가 잦아지면 그때 별도 ADR 로 도입.

### A4. multi-flow contention 측정 추가

기각 (probe 범위 밖). D8 에서 명시한 single-flow 격리 모델이 probe 의 핵심
의도. multi-flow contention 은 ADR-0033 latency model 의 다른 영역으로,
별도 도구 또는 별도 case category 로 처리.

## Consequences

- probe 의 case catalog (D1) 와 측정 단위 (D2/D3) 가 ADR-level 에서 명시
  되어, 새 traffic 카테고리 추가 시 어떤 표 포맷을 따라야 하는지 분명.
- formula vs actual 의 컬럼 의미 (D4) 가 굳어져, probe 결과를 보고 "왜
  Drain% 가 5% 인가 / 70% 인가" 같은 질문을 빠르게 ADR-0033 가정 점검으로
  연결 가능.
- invariant 자동 체크 (D5) 가 ADR 에 굳어져, 향후 latency 모델 변경 시
  monotonicity / D2H≥H2D 회귀를 probe 가 즉시 잡아낸다는 안전망 정착.
- D8 의 case 간 격리가 명시되어, probe 결과를 single-flow 측정으로 안전
  하게 해석 가능. multi-flow 측정이 필요해지면 별도 도구 트랙이 필요함이
  분명.
- A2 의 strict-mode flag 가 후속 작업 후보로 기록되어, CI 통합 요구 시
  최소 추가 작업으로 도입 가능.
