Files
kernbench2/docs/adr-proposed/ADR-0064-perf-cpu-issue-cost-model-ko.md
T
ywkang a8c50238c6 gqa(adr): ADR-0064 Rev2 (structural dispatch cost) + ADR-0065/DDD-0065 (flat-ops composite + softmax_merge recipe)
ADR-0064 Revision 2: replace per-op-type calibration table with a
structural formula `FIXED_PER_CMD + cmd.logical_bytes × R`, defaults
anchored at typical composite ≈ 45 ns. Topology yaml override under
`pe_cost_model:` block; logical_bytes property per PE command.

ADR-0065: implement ADR-0060 §5.6 / §8 item 4 carve-out as a flat-ops
CompositeCmd (no head/epilogue structural fields — position + scope
drives placement) + first stateful recipe `softmax_merge` (MATH-only
8-step). RECIPE_DESCRIPTORS lives in TLContext-adjacent module only;
PE_SCHEDULER stays recipe-free and auto-inserts DMAs from operand
`space`. Strict-FIFO RW hazard tracker; ≤1 GEMM per composite
invariant. User-facing `tl.composite(prologue=[...], op=, epilogue=[...])`
API preserved; existing benches unchanged (meaning-preserving refactor).

DDD-0065: implementation-ready phased plan (P0=ADR-0064 Rev2 first,
P1-P6 for ADR-0065), file plan, recipe engine sequence, scheduler
plan-gen algorithm, RW tracker design, test matrix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-10 16:11:20 -07:00

243 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0064: 구조적 CPU dispatch cost 모델 (`logical_bytes` + FIXED + R)
## Status
Proposed (Revision 2)
> **ADR-0060** (AHBM GQA Fused Attention) 와 **ADR-0065** (flat-ops
> composite + 첫 stateful recipe) 의 보조 ADR. 그 hybrid 의 핵심 — "GEMM 은
> `tl.composite` 로, softmax merge 는 커널에서" — 은 **PE_SCHEDULER 로
> tiling 을 offload 해서 CPU 가 굵은 descriptor 만 issue 하고 앞서가며
> 엔진을 포화시킴** 으로 이깁니다. 그 win 이 현재 시뮬레이터에선 보이지
> 않습니다 (per-op CPU issue cost = 0).
>
> Revision 2 는 원안의 **op-type calibration 표** 를 **logical_bytes 기반
> 구조 공식** 으로 대체합니다 — op-type 별 calibration 불필요, 새 op_kind
> 도 자동 적용.
## Context
### 현재 코드
- 모든 `tl.*` op 가 cmd emit 전에 `_emit_dispatch_overhead()` 호출
(`tl_context.py:196-212`) → `dispatch_cycles > 0` 일 때만
`PeCpuOverheadCmd(cycles=dispatch_cycles)` 발행. knob 은 op kind 무관
**uniform** 이며 두 실행 경로 모두 **0** 하드코딩
(`pe_cpu.py:101` greenlet, `:195` replay).
- ⇒ 명령 issue (descriptor 구성 + scheduler 큐 push) 가 PE_CPU 에서
**0 ns** 비용.
- `PeCpuOverheadCmd` 는 PE_CPU 에서 `yield env.timeout(cmd.cycles)`
소비 (`kernel_runner.py:131-132`).
### uniform-zero 가 hybrid 에 부적합한 이유
ADR-0060 §1 의 핵심: composite 1 개가 `N_tiles` 분량의 GEMM tiling 을
offload → CPU 는 `O(1)` 개의 굵은 cmd 만 issue, primitive 경로는
`O(N_tiles × ops/tile)`. issue cost = 0 이면 모델은:
- primitive 경로의 *CPU 가 push 를 못 따라가 엔진 idle* 을 못 보임
- composite 의 *구성 비용 ≫ primitive 1 개이지만 ≪ 대체된 primitive 들의 합* 을 못 보임
### Revision 1 의 op-type calibration 이 과한 이유
원안은 `cost_table[kind]` (kind = `composite`, `load`, `dot`, `math`, …)
형태였습니다. 비용:
- kind 마다 값 필요 (calibration ≥ |kinds|)
- 새 kind 추가 시마다 entry 추가
- 그런데 잡으려던 *ratio* — "composite ≫ primitive, ≪ 대체된 primitive 들의 합" —
*op kind* 가 아니라 *cmd 가 들고 있는 필드 수* 의 함수.
cmd 의 *byte 발자국* 이 자연 proxy: N OpSpec composite 는 1 OpSpec primitive 의 ~N 배 bytes.
*고정* 부분 (큐 tail 업데이트, completion 등록, MMIO-급 latency) 은 cmd 마다.
둘 합치면: `FIXED + bytes × R`.
## Decision
### D1. 구조적 dispatch cost 공식
PE_SCHEDULER 로 가는 모든 PE command 가 PE_CPU 에서 dispatch cycles 소모:
```
dispatch_cycles(cmd) = FIXED_PER_CMD + cmd.logical_bytes × R
```
- `FIXED_PER_CMD` (cycles/cmd): 큐 tail 업데이트, MMIO-급 RTT,
completion-event 등록 — cmd 크기와 무관 고정 비용.
- `R` (cycles/byte): scheduler 큐에 cmd 를 직렬화하는 큐-write 대역폭.
- `cmd.logical_bytes` (int): cmd 의 *HW-논리* byte 수 — D2 룰로 계산,
Python `sys.getsizeof` 가 아님.
PE_CPU 는 기존 hook 그대로 `PeCpuOverheadCmd(cycles=dispatch_cycles(cmd))`
를 dispatch 전에 발행 — 사이클 값만 변경.
### D2. `logical_bytes` 룰
각 PE command dataclass 가 `logical_bytes: int` (property) 노출. 룰
(HW 친화적, Python overhead 무시):
| 필드 종류 | bytes |
|---|---|
| cmd framing (cmd 종류 discriminator + completion id 참조) | 4 |
| Opcode (op kind enum) | 1 |
| Enum (scope 등) | 1 |
| `TensorHandle` 참조 (address only — shape/dtype 은 descriptor table 가정) | 8 |
| Scalar (int/float) | 4 |
| Tuple length marker | 1 |
`CompositeCmd``ops``rw_handles` 재귀 합산:
```python
@property
def logical_bytes(self) -> int:
return (
4 # framing
+ 1 + sum(op.logical_bytes for op in self.ops)
+ 1 + 8 * len(self.rw_handles)
)
```
`OpSpec`:
```python
@property
def logical_bytes(self) -> int:
return (
1 + 1 # opcode + scope
+ 1 + 8 * len(self.operands) # named operand handles
+ (8 if self.out is not None else 0) # out handle
+ 1 + sum(4 for _ in self.extra.values()) # extra scalars
)
```
(`DmaReadCmd`, `MathCmd` 등도 같은 룰 — dataclass 당 property 1 개,
약 3 줄.)
### D3. Default — 일반 composite ≈ 45 ns 기준
anchor: 일반 1-op DMA→GEMM→DMA composite 의
`logical_bytes ≈ 52` (framing 4 + GEMM OpSpec 39 + rw_handles 9). dispatch
목표 45 ns. on-die producer→consumer 큐 4 bytes/cycle 가정.
```
clock = 1 GHz # 1 cycle = 1 ns
FIXED_PER_CMD = 32 cycles
R = 0.25 cycles/byte
```
검증: `32 + 52 × 0.25 = 45 cycles ≈ 45 ns`
### D4. topology config override
default 는 `pe_cpu.py` 에 내장. topology yaml 의 PE 노드 attrs 아래
`pe_cost_model:` 절로 override:
```yaml
pe:
attrs:
pe_cost_model:
fixed_per_cmd_cycles: 32
byte_cycles_recip: 0.25
clock_freq_ghz: 1.0
```
누락된 키는 default. dispatch 공식은 PE_CPU init 시
`node.attrs["pe_cost_model"]` 에서 읽음.
### D5. Scope — 무엇이 cost 를 내고 무엇이 안 내나
| 경로 | dispatch cost? |
|---|---|
| PE_CPU → PE_SCHEDULER 의 모든 `PeCommand` | **예** |
| `PeCpuOverheadCmd` 자체 (이미 cycles 명시) | **아니오** (공식 우회) |
| PE_SCHEDULER 가 자동 생성한 Stage (DMA_READ/WRITE/FETCH/STORE) | **아니오** (PE_SCHEDULER 내부) |
| 엔진 compute latency (PE_DMA `drain_ns`, GEMM/MATH `_compute_ns`) | **변화 없음** — 엔진에 그대로 (SPEC §0.1) |
"latency 는 모델링된 컴포넌트에서" invariant 유지 — dispatch cost 는
*추가* CPU-side 시간, 엔진 시간에 fold 안 함.
### D6. 설정 가능 값; goldens 재생성
issue cost 를 0 → non-zero 로 바꾸면 **모든** bench 의 latency 변화.
이 ADR land 시 골든 latency **한 번** 재생성 — ADR-0062 D3 lazy-load 와
같은 패턴. 재생성 후 동일 calibration 이 ADR-0065 opt2 측정에 적용.
## Alternatives
### A1. Revision 1 의 op-type calibration 표 유지
기각: calibration 비용이 |kinds| 에 비례, 그리고 표가 잡으려던 *ratio*
구조적으로 cmd 크기의 함수. 구조 공식은 같은 정성적 동작을 N 개가 아닌
2 개의 calibratable 숫자로 달성.
### A2. byte-only 공식 (FIXED 항 없음)
기각. FIXED = 0 이면 opt2 (ADR-0065 Option Y) 가 opt3 를 **이기지 못함**
— per-tile dispatch *총 bytes* 가 비슷 (opt3 ≈ 232, opt2 ≈ 380); win 은
전적으로 *per-cmd fixed cost 횟수 감소* 에서 옴. byte-only 는 모델이
보여야 할 신호를 지움.
### A3. PE_SCHEDULER 에 dispatch 비용 부과
기각: saturation 질문은 *"CPU 가 descriptor 를 push 하는 속도가 엔진을
계속 바쁘게 할 수 있는가?"* — **PE_CPU** 의 issue-bandwidth 특성.
scheduler 에 부과하면 CPU back-pressure 모델링 안 됨.
### A4. DMA program/setup 시간을 별도 fixed per-descriptor 비용으로 모델
연기: 처음엔 descriptor-program 비용을 **issuing op 의** dispatch cost 에
fold. calibration 이 분리 필요성을 보이면 PE_DMA fixed setup 으로 분리.
## Consequences
### Positive
- hybrid 의 CPU-offload / saturation win (ADR-0060 §1) 이 **측정 가능**,
구조적으로 정직한 모델 (calibration 표 없음).
- 새 op_kind 추가 (예: ADR-0065 의 `softmax_merge` 8-step recipe) 가
*zero cost* — 같은 공식 자동 적용.
- 하드웨어에 더 충실 (queue-head MMIO RTT + queue-write 대역폭).
### Negative
- **모든** bench goldens 변화 → 일회성 재생성 (D6); CI 골든 fixtures 업데이트.
- 두 calibration knob (FIXED, R) 필요; default 는 문서화된 가정에 기반 —
HW reference 없는 동안엔 절대 latency 를 잠정 취급, **ratio** 만 방어.
- 각 PE command dataclass 에 작은 `logical_bytes` property 추가.
## Open review items
1. **FIXED 와 R 의 calibration 출처.** default 는 "일반 composite = 45 ns
+ on-die 큐 4 bytes/cycle"; on-die producer→consumer 큐로 합리.
HW reference 등장 시 재방문.
2. **Scheduler plan-gen 비용.** 0 유지 — D5 가 PE_SCHEDULER 의 plan
생성을 dispatch 공식 밖에 둠. scheduler-bound 행동이 보이면 기존
`overhead_ns` 로 노출.
3. **Override 위치.** topology yaml 의 PE 노드 attrs 아래 `pe_cost_model:`
block — 모든 knob 을 한 곳에, 리뷰 가능.
4. **경로 parity.** greenlet (`_execute_legacy`, `kernel_runner`) 와
replay 둘 다 같은 cost model 읽어야 함. 검증.
## Test Requirements
1. **Anchor 보존.** 일반 DMA→GEMM→DMA composite (1 op,
`logical_bytes ≈ 52`) 가 default 값에서 45 ns 에 dispatch.
2. **구조적 ratio.** opt3 vs opt2 dispatch (ADR-0065 §verification 의):
default calibration 에서 `opt3 / opt2 ≈ 2.4×`.
3. **Override 경로.** topology yaml 의 `pe_cost_model:` block 이 per-PE
dispatch cost 변경; block 누락 시 default 복귀.
4. **`PeCpuOverheadCmd` 우회.** 수동 `tl.cycles(n)` 는 정확히 `n` cycles,
`n + dispatch_cycles(...)` 아님.
5. **double-count 없음.** PE_DMA `drain_ns`, PE_GEMM/MATH `_compute_ns`
pre-ADR 값과 동일.
6. **결정성.** 동일 입력 → 동일 op_log + latency (SPEC §0.1).
7. **경로 parity.** greenlet 과 replay 가 동일 커널에 대해 동일
dispatch-cycle 회계.
## Migration
ADR-0064 Revision 2 는 단일 PR 로 land:
-`PeCommand` dataclass 의 `logical_bytes` property
- `pe_cpu.py` dispatch 경로의 공식 적용
- PE_CPU init 의 `pe_cost_model:` override read
- 일회성 골든 재생성
land 후 ADR-0065 가 위에 쌓임 — 기존 bench 추가 골든 churn 없음
(ADR-0065 는 기존 경로의 `CompositeCmd` 의미 보존 refactor; opt2 만 새 bench).