Index
2026-08-23 — Analysis

Frame Sparsity × torch.compile — 추론 속도 재분석

X-WAM MoT | bs1 정책 추론에서 sparsity가 왜 안 먹히는지 3중 측정 후, compile을 켜서 재검증

TL;DR

1.00×
bs1 sparsity (초기)
1.20×
eager sparsity (정정)
2.99×
compile reduce-overhead

1 배경 / 목적

frame sparsity의 원래 동기는 정책 추론 가속이었다. 토큰이 33%(drop)/24%(mae) 줄면 계산량도 그만큼 준다는 건 분명한데, 문제는 그것이 wall-clock에 반영되는가였다. 세 층위(forward 1회 / policy 호출 1회 / end-to-end 평가)에서 각각 측정했더니 전부 이득이 없었다 — 그런데 그 측정치가 노이즈에 오염되어 있었다는 것이 이 카드의 핵심.

재검증의 계기: 진단 과정에서 evaluation/policy_server.py:165torch.compile(model.model)을 호출하도록 되어 있는데, scripts/eval_robocasa.sh:75export TORCHDYNAMO_DISABLE=1이 이를 무력화하고 있었다는 것을 발견(주석: 워커 ubuntu:22.04에 C 컴파일러가 없어 inductor가 실패). 즉 지금까지의 모든 추론 측정은 compile OFF 상태였다. "오버헤드를 먼저 제거하면 토큰 감소가 비로소 효과를 낸다"는 예측이 성립하는지 compile을 켜서 검증했다.

2 작업 내용

1차 (3중 측정): scripts/bench_forward.py(job 85009, bs 1/4/8, warmup 5+측정 30회) → scripts/bench_generate.py(job 92531, XWAMRunner.generate(early_stop=True, cfg=0) = policy server가 액션 청크마다 실행하는 경로, action denoise 10스텝, warmup 2+측정 10회) → end-to-end 24×50 wall-clock 비교.

2차 (compile × sparsity 매트릭스): scripts/bench_compile.py{eager, compile default, compile reduce-overhead} × {none, drop_middle} 6조합 측정, warmup 3+측정 10회, torch.cuda.synchronize(). 실행: job 92645, --container=nvcr.io/nvidia/pytorch:25.04-py3(gcc 13.3 확인), TORCHDYNAMO_DISABLE unset, 1 GPU.

주의 (구현 함정): compile된 OptimizedModule은 속성 읽기만 원본에 위임하고 쓰기는 래퍼에 저장됨 → sparsity 마스크는 원본 모듈(base_model)에 직접 설정해야 forward에 도달

3 결과 (수치)

① forward 1회 (ms, compile OFF)

Bnonemae25dropmae 배속drop 배속
155.955.856.11.00×1.00×
486.472.769.61.19×1.24×
8152.6123.1114.91.24×1.33×

② policy 호출 1회 (denoise 10스텝, bs1, compile OFF, 노드 경합 있음)

armms/call배속
none699.9 ± 69.21.00×
drop_middle703.6 ± 67.40.995× (→ 정정됨, 아래 참조)
mae_middle722.3 ± 56.50.969×

end-to-end 평가 wall-clock (24×50, 4 GPU): mot_v1b 1:00:55 vs dropmid 1:01:41 (+46초, 사실상 무이득).

③ compile × sparsity 클린 재측정 (job 92645, ±8.7ms 수준의 낮은 노이즈)

none (761토큰)drop_middle (521토큰)sparsity 이득
eager766.9 ± 8.7 ms638.6 ± 8.8 ms1.20×
compile (default)355.1 ± 1.6 ms357.0 ± 2.4 ms0.995×
compile (reduce-overhead)256.3 ± 2.9 ms255.5 ± 0.6 ms1.003×

eager/none(767ms) 대비 compile 배속: default 2.16×, reduce-overhead 2.99×. 첫 호출 컴파일 비용은 67~75s(정책 서버는 프로세스당 1회라 무시 가능).

측정치 정정: job 92531(②)에서 eager sparsity 이득을 0.995×로 보고했으나, 표준편차가 ±69ms(10%)로 노드 경합이 심했던 결과였다. job 92645의 클린 측정(±8.7ms, 1%)에서는 1.20×다. 1.20×를 채택하고 0.995×는 철회한다.
핵심 발견 (roofline): bs1 forward 55.9ms 중 토큰 비례 몫은 ~16ms, 나머지 ~40ms는 배치·토큰 무관 커널 실행 오버헤드(30 레이어 × 레이어당 norm/chunk/autocast/modulation이 1,500개 이상 커널로 분해). 실제 연산량 기준 761토큰×6.4B=9.7 TFLOP → B200 bf16(~2.2 PFLOPS)에서 4.4ms면 끝날 일이 56ms 걸림(효율 8%). ①의 bs4/bs8 회귀(T = fixed + B×slope) 기준 slope 비율이 토큰 비율과 소수점 셋째 자리까지 일치(drop 0.684 vs 0.685, mae 0.761 vs 0.763) — 계산량 감소는 실재하며 시간에도 정확히 비례해 반영된다는 증거.
compile 이후 sparsity 소멸: compile 하에서는 sparsity 이득이 1.00×로 사라진다. 예측(오버헤드 제거 후 토큰 감소가 살아난다)과 정반대.

4 Takeaway

의미

이 레짐에서 frame sparsity는 추론 가속 수단이 아니다. 정책 추론(bs1) 1.00~1.20×(측정 노이즈에 따라 다르지만 어느 쪽이든 미미), 학습(4 GPU bs4×accum8) 1.01×, end-to-end 평가 1.00×. 반면 compile은 코드에 이미 있었는데 인프라 제약(워커에 C 컴파일러 없음)으로 꺼져 있던 2.16~2.99× 레버였다 — 사용자의 원래 목표("joint denoise + ANS 유지하면서 추론 감당 가능하게")는 sparsity가 아니라 compile 복원만으로 이미 달성된다(767ms→256ms).

compile 이후 sparsity 이득이 사라지는 이유는 두 가지로 추정된다: (a) 남은 시간(25.6ms/스텝, roofline 4.4~1.6ms의 4~5배)이 러너의 denoise 루프(UniPC 스케줄러 multistep 연산, 조건부 마스킹)로 이동했는데 이는 full latent 그리드에서 동작해 sparsity와 무관, (b) sparsity 구현 자체의 per-forward 오버헤드(build_keep_index.nonzero() device sync)가 모델이 빨라지면서 절약분과 맞먹게 됐을 가능성. 둘 다 미검증.

5 Next Steps

미해결 한계

compile 이후 sparsity 소멸의 두 가설(러너 루프 이동 vs. keep_idx 재계산 오버헤드) 미분리. compile 하 SR 동등성(inductor/CUDA graphs가 출력을 바꾸지 않는지)도 아직 24×50으로 검증하지 않음.

다음 실험

가장 실용적: 평가 파이프라인에 compile 복원 — eval_robocasa.shTORCHDYNAMO_DISABLE=1을 걷어내고 --container=nvcr.io/nvidia/pytorch:25.04-py3로 제출하면 RoboCasa 평가가 ~3배(1시간→20분대 예상) 빨라질 것. 첫 pull + compile 67s 오버헤드만 확인.

keep_idx를 generate 호출당 1회 캐시 후 compile+sparsity 재측정 — 단 modules/frame_sparsity.py는 mae25 학습(job 91863)이 현재 사용 중이라 완주 후 진행.