본문 바로가기

GPU Probing Lab

Optimal Accumulator Count Probe

1. 목적

Optimal Accumulator Count Probe는 고정된 FFMA chain length에서 accumulator 수를 바꿨을 때 warp progress가 어떻게 변하는지 측정하기 위해 설계되었다.

앞선 실험 흐름은 다음 질문에서 출발했다.

single accumulator dependent chain은 progress를 낮춘다.
그렇다면 accumulator를 더 많이 늘리면 계속 좋아지는가?

이번 probe의 목적은 이 질문을 직접 확인하는 것이다.

비교 대상은 같은 chain length에서 accumulator count만 바꾼 variant들이다.

acc1, acc2, acc3, acc4, acc5, acc7, acc8, acc16

핵심 질문은 다음이다.

FFMA accumulation lowering에서 최적 partial accumulator 수는 어디인가?


2. 실험 설정

업로드된 결과는 모두 optimal_accumulator_count_probe에서 나온 것이다.

공통 조건은 다음과 같다.

항목값

probe optimal_accumulator_count_probe
blocks 1
threads_per_block 256
warps_per_block 8
repeat 10
clock_budget_cycles 10,000,000
chain lengths 7, 13, 29
accumulator counts 1, 2, 3, 4, 5, 7, 8, 16

각 chain length에서 role은 다음 형태로 구성된다.

chainN_acc1_effective1
chainN_acc2_effective2
chainN_acc3_effective3
chainN_acc4_effective4
chainN_acc5_effective5
chainN_acc7_effective7
chainN_acc8_effectiveN-or-8
chainN_acc16_effectiveN-or-16

accumulator count가 chain length보다 큰 경우 effective accumulator 수는 실제 chain instruction count에 의해 제한된다.


3. 결과 요약

가장 중요한 결론은 다음이다.

chain length 7, 13, 29 모두에서 acc2가 최고 progress를 보였다.

이는 예상보다 강한 결과다. 처음 예상은 chain이 길어질수록 acc4 또는 acc8 같은 더 넓은 split이 유리할 수 있다는 쪽이었다. 그러나 현재 구현과 SASS 기준에서는 2-way accumulator split이 가장 안정적인 sweet spot으로 나타났다.

chainbest roleavg progressacc1 대비

7 acc2 52,404.9 106.8%
13 acc2 43,579.5 112.3%
29 acc2 36,146.7 125.9%

상세 평균은 다음과 같다.

chain  acc1                acc2               acc3               acc4               acc5                acc7               acc8               acc16

7 49,078.0 52,404.9 48,837.6 43,621.8 41,025.7 38,082.8 36,047.8 36,048.0
13 38,814.2 43,579.5 40,377.4 38,458.5 36,869.0 34,252.2 32,010.2 32,148.6
29 28,719.5 36,146.7 35,195.5 34,312.4 34,439.9 32,414.5 30,032.7 29,276.0

4. 핵심 패턴

4.1 acc2가 전 구간에서 peak

세 chain length 모두에서 acc2가 가장 높다.

chain7  : acc2 > acc1 > acc3 > acc4 > acc5 > acc7 > acc8 ≈ acc16
chain13 : acc2 > acc3 > acc1 ≈ acc4 > acc5 > acc7 > acc16 ≈ acc8
chain29 : acc2 > acc3 > acc5 ≈ acc4 > acc7 > acc8 > acc16 > acc1

이 결과는 다음 해석을 강하게 지지한다.

단일 accumulator chain을 2-way로 쪼개는 것만으로도
latency hiding 효과가 크게 생긴다.

하지만 accumulator를 3개 이상 늘리면
live value, register allocation, dispatch path, sink path 비용이
추가 ILP 이득을 빠르게 잡아먹는다.

즉, acc2는 dependency 완화 이득과 register/live-range 비용 사이의 가장 좋은 균형점으로 보인다.


4.2 chain이 길어질수록 acc2 이득은 커진다

acc2가 acc1 대비 얼마나 좋아졌는지 보면 다음과 같다.

chain7  : +6.8%
chain13 : +12.3%
chain29 : +25.9%

이 방향성은 중요하다.

chain이 길수록 single accumulator dependency penalty가 커지고, 2-way split의 이득도 커진다.

따라서 codegen 관점에서는 긴 accumulation chain일수록 최소한 2-way partial accumulator 분산을 강하게 고려해야 한다.


4.3 acc3 이후는 대체로 하락한다

acc3도 완전히 나쁘지는 않다.

chain7  : acc3 < acc1
chain13 : acc3 > acc1, but acc2보다 낮음
chain29 : acc3 > acc1, but acc2보다 낮음

chain13과 chain29에서는 acc3도 acc1 대비 이득이 있다. 그러나 acc2보다는 항상 낮다.

acc4 이상은 대부분 더 나빠진다. 특히 chain7에서는 acc4부터 급격히 하락한다.

이 결과는 이전 chain7_tail_distribution_probe의 결론과 일관된다.

chain7에서는 accumulator를 너무 많이 벌리면 tail imbalance와 live range 비용이 커진다.

5. SASS 기반 해석

5.1 spill은 보이지 않음

SASS에서 LDL, STL은 보이지 않는다.

따라서 이번 하락은 명시적인 local memory spill 때문이라고 보기는 어렵다.

더 가능성 높은 원인은 다음이다.

live register 증가
branch-dispatch shape
instruction scheduling shape
sink accumulation path
ptxas register allocation

즉, accumulator count 증가의 비용은 spill처럼 노골적인 형태가 아니라 SASS/register shape와 scheduling 효율 저하로 나타난다.


5.2 acc1은 strict single-register chain

SASS에는 acc1 계열로 보이는 strict chain이 명확하다.

FFMA R16, R24, R16, R25
FFMA R16, R24, R16, R25
FFMA R16, R24, R16, R25
...

이 형태는 의도대로 single accumulator dependency chain을 형성한다.

acc1은 register/live-range 비용은 작지만, FFMA 결과 의존성이 길게 이어진다.


5.3 acc2는 두 accumulator를 교차시키는 형태

SASS에는 R16과 R20 계열이 교차되는 구간이 보인다.

FFMA R16, R24, R16, R25
FFMA R20, R24, R15, R25
FFMA R16, R24, R16, R25
FFMA R20, R24, R20, R25
...

이 구조는 2-way split의 의도와 맞다.

하나의 accumulator에만 의존하지 않고 두 chain을 교차시키면서 ready instruction을 만든다. 동시에 live accumulator 수는 아직 작다.

따라서 acc2는 다음 두 조건을 동시에 만족한다.

dependency depth 감소
live register width 제한

이번 결과에서 acc2가 sweet spot으로 나온 이유는 이 균형 때문이다.


5.4 acc7/acc8/acc16은 register 폭이 넓어진다

SASS에서는 accumulator 수가 늘어난 variant에서 여러 register를 동시에 갱신하는 구간이 보인다.

FFMA R16, ...
FFMA R15, ...
FFMA R14, ...
FFMA R13, ...
FFMA R12, ...
FFMA R11, ...
FFMA R10, ...
FFMA R9,  ...
FFMA R8,  ...
...

이는 accumulator 수를 늘렸을 때 실제로 live accumulator register 폭이 넓어졌다는 뜻이다.

하지만 progress는 오히려 낮아졌다.

따라서 이번 결과는 다음을 보여준다.

accumulator 수 증가는 ILP를 늘리지만, 그에 따른 live register width, sink path, scheduling shape 비용이 더 빨리 커질 수 있다.


6. 이전 실험과의 연결

6.1 Chain 7 Tail Distribution Probe와의 연결

이전 chain7_tail_distribution_probe에서는 다음 패턴이 관찰되었다.

chain7:
2acc > 3acc > dependent > 4acc > 7acc

이번 실험은 이 패턴을 더 넓은 chain length로 확장했다.

chain7  → acc2 peak
chain13 → acc2 peak
chain29 → acc2 peak

따라서 chain7 anomaly는 단발성 예외가 아니라, accumulator 수 증가의 비용을 보여주는 중요한 신호였다고 볼 수 있다.


6.2 Irregular FMA Dependency Shape Probe와의 연결

초기 Irregular FMA Dependency Shape Probe에서는 chain29에서 independent variant가 dependent보다 크게 높게 나타났다.

그때의 핵심 결론은 다음이었다.

긴 single accumulator dependency chain은 progress를 강하게 제한한다.
independent accumulator 분산은 ready instruction 공급을 회복시킬 수 있다.

이번 실험은 그 결론을 더 정교하게 만든다.

분산은 필요하다.
하지만 과도한 분산은 손해다.
현재 구현/SASS 기준의 안정적 기본값은 2-way split이다.

즉, 최종 해석은 다음이다.

dependency chain은 분산해야 하지만, accumulator count는 chain length만 보고 크게 늘리면 안 된다. register/live-range/sink cost까지 함께 고려해야 한다.


7. Codegen implication

7.1 틀린 규칙

다음 규칙은 틀렸다.

accumulator를 많이 만들수록 좋다.

이번 결과에서는 chain7, chain13, chain29 모두에서 acc2가 최고였고, acc3 이상은 acc2보다 낮았다.


7.2 더 정확한 규칙

더 정확한 규칙은 다음이다.

single accumulator chain은 길어질수록 손해가 커진다.
2-way split은 짧은 chain부터 긴 chain까지 안정적으로 이득을 준다.
그러나 accumulator 수를 과도하게 늘리면 register live range,
scheduling shape, sink/reduction 비용이 ILP 이득을 상쇄한다.

7.3 AICF/codegen에 적용 가능한 기본값

AICF 또는 kernel codegen에서 accumulation lowering을 설계할 때, 현재 결과 기준의 기본 전략은 다음이다.

default partial accumulation split = 2-way

4-way 이상 partial accumulator는 chain length만 보고 선택하면 안 된다.

선택 조건은 다음 항목을 함께 봐야 한다.

chain length
register budget
surrounding fused ops
sink/reduction cost
occupancy constraint
SASS lowering shape

적용 가능성이 높은 연산은 다음이다.

GEMM inner accumulation
softmax denominator accumulation
layernorm mean/variance accumulation
bias grad reduce_sum
streaming reduction kernels

8. Heuristic draft

현재까지의 실험 결과를 codegen heuristic으로 정리하면 다음과 같다.

1. accumulation chain이 매우 짧으면 acc1도 충분할 수 있다.
2. chain이 길어질수록 acc1의 dependency penalty가 커진다.
3. acc2는 chain7, chain13, chain29 모두에서 가장 안정적인 선택이었다.
4. acc3은 중간/긴 chain에서 acc1보다 나을 수 있지만 acc2보다 낮았다.
5. acc4 이상은 현재 구현/SASS 기준에서 live range와 sink 비용이 커져 손해가 났다.
6. acc7/acc8/acc16처럼 넓은 split은 dependency를 줄여도 progress를 크게 떨어뜨릴 수 있다.

간단한 lowering 정책은 다음처럼 시작할 수 있다.

if accumulation_chain_length <= small_threshold:
    use acc1 or acc2 depending on surrounding ops
else:
    use acc2 as default

consider acc3_or_more only if:
    register budget is safe
    sink cost is isolated or amortized
    surrounding instructions can benefit from wider ILP
    occupancy is not harmed

9. 다음 실험: Sink Isolation Accumulator Probe

이번 결과에서 남은 핵심 질문은 다음이다.

acc2가 정말 loop body 구조상 최적인가?
아니면 acc3 이상이 sink/reduction path 때문에 손해를 보는가?

이를 분리하기 위해 다음 probe는 sink_isolation_accumulator_probe가 적절하다.

비교 구성은 다음과 같다.

A: acc1/2/3/4/8 + full sink sum
B: acc1/2/3/4/8 + only one accumulator sink
C: acc1/2/3/4/8 + store all accumulators separately

목적은 acc3 이상이 낮아지는 원인을 분리하는 것이다.

loop body issue/scheduling 문제인가?
loop 이후 sink sum/reduction 비용 때문인가?
register allocation/live-range shape 때문인가?

이 후속 실험까지 진행하면 accumulator count heuristic을 더 안전하게 codegen policy로 옮길 수 있다.