## 요약
Opt 0.8.1로 컴파일한 커널을 물리 RNGD에서 프로파일링하면서 cycle 수치를 해석하는
데 필요한 정보가 문서에서 확인되지 않아 문의드립니다.
핵심 질문 세 가지입니다.
1. `FURIOSA_OPT_PROFILE`이 보고하는 span의 `begin`/`end`는 어느 주파수 기준입니까?
측정으로는 약 2.0 GHz이며 PE clock 1.0 GHz가 아닙니다.
2. Opt compiler schedule의 cycle은 어느 클럭 도메인입니까? 1.0 GHz로 추정했습니다.
3. 커널 내부를 여러 span으로 나눠 프로파일할 방법이 있습니까?
두 개의 서로 다른 커널로 측정했습니다. 하나는 저희가 구현 중인 보안 관련 연산의
커널(명령 1,224개)이고
다른 하나는 이 문의를 위해 만든 명령 5개짜리 복사 커널입니다. 후자는 전체 소스를
첨부할 수 있으며 그대로 재현하실 수 있습니다. 뒤쪽에 부수 질문 세 가지를 덧붙였습니다.
## 환경
| 항목 | 값 |
| — | — |
| Opt | `furiosa-opt-std 0.8.1`, `cargo-furiosa-opt 0.8.1` |
| Rust | `nightly-2026-05-01` |
| OS | Ubuntu 22.04.5 LTS, kernel 5.15.0-177 |
| Topology | `chips: 1, pes: 8` (cluster 2개) |
측정에 쓴 커널 두 가지입니다.
| | 명령 | critical path | 내용 |
| — | —: | —: | — |
| A. 실제 워크로드 | 1,224 | 292,520 cycles | 저희가 구현 중인 보안 관련 연산 |
| B. 복사 | 5 | 6,047 cycles | HBM 2 MiB를 DM으로 읽어 그대로 HBM에 씀. 연산 없음 |
B는 `#[device]` 함수 본문이 세 줄입니다.
```rust
#[device(chip = 1)]
pub fn profile_probe(ctx: &mut Device, input: &IoTensor) → IoTensor {
let dm = input.to_dm::<Cluster, Slice, Tile>(&mut ctx.tdma);
let mut output: IoTensor = HbmTensor::new();
dm.view().to_hbm_view(&mut ctx.tdma, output.view_mut());
output
}
```
## 측정 방법
`FURIOSA_OPT_PROFILE=info`와 `span::npu`를 받는 tracing subscriber를 함께 두고
실행했습니다. span의 `dur`은 SDK가 보고한 값을 그대로 쓰고 launch wall은 host에서
`Instant`로 launch 구간을 쟀습니다.
`warmup=2, iterations=20`, 결과 readback 없음(`D2H=0`)으로 반복 측정했습니다.
아래 통계는 warmup 2회를 뺀 20개 표본 기준입니다.
## 질문 1 — span의 cycle은 어느 주파수입니까
커널 A의 정상상태 `dur` 평균은 788,429 cycles인데, 이를 공개 스펙의 PE clock
1.0 GHz로 나누면 0.788 ms입니다. 같은 구간의 launch wall이 0.413 ms이므로 1.0 GHz는
성립하지 않습니다. 커널 시간이 launch wall보다 클 수 없습니다.
연속 launch의 span 시작 간격을 host 측 평균 launch wall로 나눠 역산했습니다.
| 커널 | cluster | 간격 | ÷ launch wall | 유도 주파수 |
| — | — | —: | —: | —: |
| A. 실제 워크로드 | 0 | 826,620 cycles | 0.413 ms | 2.002 GHz |
| A. 실제 워크로드 | 1 | 826,749 cycles | 0.413 ms | 2.002 GHz |
| B. 복사 | 0 | 31,378 cycles | 0.0158 ms | 1.986 GHz |
| B. 복사 | 1 | 31,558 cycles | 0.0158 ms | 1.997 GHz |
명령 수가 245배 차이 나는 두 커널에서 같은 값이 나오므로 카운터는 워크로드와
무관하게 **약 2.0 GHz**로 보입니다.
- 이 값이 맞습니까?
- 이 카운터는 PE clock과 고정 비율(2배)입니까, 아니면 별도 reference timer입니까?
- 주파수 스케일링이나 칩·펌웨어 버전에 따라 달라집니까?
덧붙여 `begin` 값의 기준점도 궁금합니다. 커널 A를 단발로 두 번 실행했을 때 `begin`이
cluster 0은 825,387,506과 825,651,434, cluster 1은 509,315,547과 509,244,963으로 서로
매우 가까웠습니다. 부팅 이후 계속 도는 카운터라면 실행 시점이 다른데 이렇게 가깝기
어려워 보입니다. 이 값은 무엇을 기준으로 0에서 시작합니까? 지금은 cluster 간 비교가
불가능하다고 보고 `dur`만 쓰고 있습니다.
## 질문 2 — compiler schedule cycle은 어느 도메인입니까
schedule JSON에 클럭 정보가 없어 DMA 대역폭으로 추정했습니다. 커널 B의 schedule은
명령이 5개뿐이라 계산이 단순합니다.
| | 바이트 | 모델 소요 |
| — | —: | —: |
| DmaLoad | 2,097,152 | 2,277 cycles |
| DmaStore | 2,097,152 | 2,167 cycles |
두 가정으로 읽으면 함의 대역폭이 갈립니다.
| 가정 | DmaLoad | DmaStore | HBM3 peak(1.5 TB/s) 대비 |
| — | —: | —: | —: |
| 1.0 GHz | 921 GB/s | 968 GB/s | 61% / 65% (가능) |
| 2.0 GHz | 1,842 GB/s | 1,936 GB/s | 123% / 129% (불가능) |
커널 A에서도 같은 결론이 나옵니다(DmaLoad 102,957,056 바이트 / 86,844 cycles →
1.0 GHz에서 1,186 GB/s, 2.0 GHz에서 2,371 GB/s로 peak의 158%).
2.0 GHz면 두 커널 모두 peak를 초과하므로 **1.0 GHz PE clock 기준**으로
판단했습니다.
- 이 해석이 맞습니까?
- schedule의 cycle과 프로파일의 device cycle을 비교하려면 어떤 환산이 정식입니까?
## 질문 3 — 커널 내부를 더 잘게 프로파일할 수 있습니까
`furiosa-opt-rt`의 `profile.rs`에 "the image names spans by the hits that begin
and end them"이라고 되어 있습니다. 커널 A와 B 모두 이미지가 `Task` 하나만 이름
붙여 cluster당 span이 1개씩만 나옵니다. 명령이 5개인 커널에서도 같으므로 커널
구조와 무관해 보입니다.
`furiosa-opt-std`에서 커널 코드용 span 선언 API를 찾지 못했습니다
(`control.rs`의 마커는 loop unroll 힌트였습니다).
- 커널 소스에서 프로파일 구간을 선언할 방법이 있습니까?
- `FURIOSA_OPT_PROFILE`을 `debug`/`trace`로 올리면 더 많은 span이 나옵니까?
(`ProfileRequest`가 level 3~5를 받아 장치로 전달하는 것은 확인했습니다.
아직 `info` 외의 값은 시도해 보지 않았습니다.)
- `image.profile().depth`를 늘리는 컴파일 옵션이 있습니까?
## 배경 — 왜 이게 필요한가
저희는 보안 관련 연산을 RNGD에 올리는 중이고 커널별 compiler cycle 합으로 전체
성능을 추정해 왔습니다. 그 추정이 실제와 얼마나 벌어지는지가 이번 측정의 동기입니다.
위 두 도메인을 확정하고 PE cycles로 환산하면 두 커널의 결과가 이렇게 갈립니다.
| 커널 | compiler 예측 | 실측(PE 환산) | 실제 / 예측 |
| — | —: | —: | —: |
| A. 실제 워크로드 | 292,520 | 394,214 | 1.35배 |
| B. 복사 | 6,047 | 5,733 | 0.95배 |
순수 DMA 복사는 모델이 거의 맞고 연산 집약 커널에서 벌어집니다. 비율이 커널에
의존하므로 상수로 쓸 수 없다는 것까지는 확인했습니다.
커널 A의 차이 +101,694 PE cycles는 대부분 Main이 대기하는 시간으로 보입니다. 이
schedule은 critical path 292,520 중 Main을 255,782(87.4%)까지 채워 여유가 12.6%뿐인데,
실측에서는 Main 외 시간이 138,432 cycles(35.1%)로 늘어납니다. 커널 B는 Main을 쓰지
않으므로 이 현상이 없습니다.
DMA 실효 대역폭, 두 cluster의 HBM 공유, refresh/bank conflict를 후보로 보고 있으나
커널 내부 구간별 시간이 없어 어느 것이 얼마인지 가르지 못하고 있습니다. 질문 3이
해결되면 저희가 직접 좁힐 수 있습니다.
## 부수 질문
핵심 질문과 별개이며 급하지 않습니다.
### 4. launch당 고정 오버헤드 10~20 µs가 정상입니까
launch wall에서 커널이 차지하는 비중이 두 커널에서 크게 달랐는데, 차이의 절대값은
비슷했습니다.
| 커널 | launch wall | 커널(2.0 GHz 환산) | 나머지 |
| — | —: | —: | —: |
| A. 실제 워크로드 | 413 µs | 394 µs | 19 µs (4.6%) |
| B. 복사 | 15.8 µs | 5.76 µs | 10.0 µs (63.5%) |
커널 길이가 26배 차이 나는데 나머지는 10~19 µs로 비슷합니다. 제출·완료대기에 붙는
고정 비용으로 보이는데, 이 범위가 정상입니까? 줄일 방법이 있습니까?
참고로 커널 A를 warmup 없이 단발로 실행하면 launch wall이 1.525 ms로 커지고 그중
1.124 ms(73.7%)가 커널 밖입니다. 첫 launch에만 붙는 비용이 따로 있는 듯합니다.
`warmup=2`면 충분해 보이는데 권장값이 있습니까?
### 5. `Device::new`가 317 ms 걸립니다
`Device::new(Topology { chips: 1, pes: 8 })` 한 번에 317 ms가 걸립니다. 서로 다른 두
바이너리에서 317.478 ms와 317.589 ms로 0.035% 안에서 일치했습니다. 워크로드와 무관한
고정 비용으로 보입니다.
프로세스당 한 번이라 반복 워크로드에서는 문제가 아니지만 단발 job으로 돌리는
환경에서는 큽니다. 이 값이 정상 범위입니까? 프로세스 간 재사용이나 초기화를 줄이는
방법이 있습니까?
### 6. cluster 간 부하가 갈리는 경우가 있습니까
커널 B에서 두 cluster의 `dur`이 상당히 달랐습니다.
| cluster | 평균 `dur` | 표준편차 |
| — | —: | —: |
| 0 | 11,465 cycles | 1,019 (8.9%) |
| 1 | 9,115 cycles | 337 (3.7%) |
cluster 0이 25.8% 더 오래 걸리고 변동폭도 두 배 넘게 큽니다. 같은 복사를 나눠 하는
커널인데 왜 갈리는지 궁금합니다. 커널 A에서는 788,429와 785,369로 0.31% 차이에
그쳤습니다.
## 재현 방법
커널 B의 전체 소스와 schedule을 첨부합니다. 크레이트 하나이며 소스는 500줄
남짓입니다.
```bash
RUST_MIN_STACK=67108864 cargo furiosa-opt build --release --bin rngd_opt_profile_probe
PROBE_BENCH=1 PROBE_WARMUP=2 PROBE_ITERS=20 \
FURIOSA_OPT_PROFILE=info PROBE_TRACE=stdout ./target/release/rngd_opt_profile_probe
```
출력에 host 구간과 NPU span이 Chrome trace JSON으로 함께 찍힙니다. 연속 launch의
span `begin` 간격과 `[bench] avg launch wall`을 비교하면 질문 1의 역산을 그대로
재현하실 수 있습니다.
SDK가 프로파일을 요청하려면 두 조건이 **동시에** 필요합니다
(`furiosa-opt-std/src/backend/npu/function.rs`).
```rust
profile_level() // FURIOSA_OPT_PROFILE >= info
.filter(|\_| tracing::enabled!(target: "span::npu", Level::INFO)) // span::npu subscriber
```
첨부한 `src/trace.rs`가 두 번째 조건을 충족하는 최소 subscriber입니다. 한 가지
덧붙이면, subscriber 없이 `FURIOSA_OPT_PROFILE`만 설정하면 경고 없이 프로파일링이
꺼진 채 실행됩니다. `log::warn!`으로 알리는 경로가 있으나 logger가 없으면 그 경고도
보이지 않아 처음에 원인을 찾는 데 시간이 걸렸습니다. 문서에 이 두 조건이 함께
적히면 도움이 될 것 같습니다.
최소 재현 커널 첨부합니다.