[furiosa-opt v0.8.1] Opt 프로파일의 device cycle 단위와 compiler schedule cycle 도메인 문의

## 요약

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가 없으면 그 경고도

보이지 않아 처음에 원인을 찾는 데 시간이 걸렸습니다. 문서에 이 두 조건이 함께

적히면 도움이 될 것 같습니다.

최소 재현 커널 첨부합니다.