# 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 는 `` 로 표시되어 시각적으로 식별 가능. ### 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 통합 요구 시 최소 추가 작업으로 도입 가능.