✍️ 메인 교안 집필: 이영빈 (yblee)🧑🔬 연구원 리서치 노트 — 작성자: injokim (김인조) (
알고리즘 & MLIR 하향 변환 종합)💡 핵심 기여 요약: 2D 브로드캐스팅 주소 수식 유도부터 MLIR Dialect(tt → ttg → llvm) 하향 변환 파이프라인까지 학술 보고서 수준으로 체계화했습니다.
Triton 기반 2차원 타일 행렬곱과 컴파일 파이프라인 심화 정리
본 문서는 Triton 행렬곱(
1. 서론 및 핵심 관점
Triton 커널에서 출력 행렬 tl.program_id)가 하나의 출력 타일을 맡아 독립적으로 병렬 처리합니다.
본 보고서는 동일한 행렬곱 커널을 다음 두 가지 핵심 관점에서 다룹니다:
- 알고리즘 및 메모리 재사용 관점: 2차원 포인터 블록 생성, L2 캐시 그룹핑(Grouped Ordering), FP32 누산기(Accumulator), 에필로그 결합(Epilogue Fusion), 오토튜닝 메타파라미터.
- 컴파일러 IR 파이프라인 관점: Python AST
TTIR ( tt dialect)TTGIR ( ttg dialect)LLVM IR PTX CUBIN으로 이어지는 다단계 컴파일 및 하드웨어 레이아웃 매핑.
2. 2차원 포인터 블록과 메모리 브로드캐스팅
2.1 1차원 메모리 주소 구조와 Stride
GPU 메인 메모리(HBM)는 연속적인 1차원 주소 공간이므로 2차원 행렬도 행 우선(Row-major) 방식으로 직렬화되어 저장됩니다. 원소
PyTorch에서 Triton 커널을 호출할 때 텐서 전체 데이터가 아닌 **시작 주소 포인터(ptr), 행렬 크기(stride)**만을 전달합니다.
2.2 None 인덱싱과 2차원 주소 표 생성
2차원 타일 데이터는 메모리상에서 단일 연속 구간이 아니라 중간중간 건너뛰며 띄엄띄엄 배치되어 있습니다. 따라서 1차원 오프셋 벡터에 [:, None]과 [None, :] 차원 확장을 적용한 뒤 브로드캐스팅(Broadcasting)하여
offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M # (BM,)
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N # (BN,)
offs_k = tl.arange(0, BLOCK_SIZE_K) # (BK,)
# 브로드캐스팅을 통한 2차원 포인터 표 생성
a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak) # [BM, BK]
b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn) # [BK, BN]offs_am[:, None]: (BM,) 벡터를 (BM, 1) 세로 열 벡터로 변환.offs_k[None, :]: (BK,) 벡터를 (1, BK) 가로 행 벡터로 변환.- 두 벡터의 더하기 연산 시 Triton 브로드캐스팅에 의해 모든
조합의 메모리 주소를 담은 2차원 주소 표가 자동 생성됩니다.
2.3 -루프 포인터 전진
a_ptrs += BLOCK_SIZE_K * stride_ak
b_ptrs += BLOCK_SIZE_K * stride_bk3. 수치 정밀도 유지 및 에필로그 결합 (Epilogue Fusion)
3.1 FP32 누산기(Accumulator) 사용 이유
입력 텐서 accumulator)는 FP32(32비트 부동소수점) 정밀도로 초기화합니다.
- 수치 안정성: FP16은 가수가 10비트에 불과하여
축 방향으로 수십~수백 번 곱셈 결과를 누적하면 정보 손실(Underflow) 또는 값 넘침(Overflow)이 발생합니다. - 하드웨어 가속 지원: 현대 GPU의 Tensor Core는 FP16 곱셈과 FP32 누산을 동일한 수치 연산 속도로 수행하도록 설계되어 있어 추가 성능 저하 없이 고정밀도 결과를 얻을 수 있습니다.
3.2 Epilogue Fusion과 Leaky ReLU 결합 과정
# 에필로그 구간 (Epilogue)
if ACTIVATION == "leaky_relu":
accumulator = leaky_relu(accumulator) # 1. 레지스터 상태에서 Leaky ReLU 적용
c = accumulator.to(tl.float16) # 2. FP32 -> FP16 변환 (Downcasting)
tl.store(c_ptrs, c, mask=c_mask) # 3. HBM에 단 1회 쓰기메모리 트래픽 최적화 효과
- 분리 실행 시: 행렬곱 커널 결과 쓰기(
Byte) 활성화 커널 읽기( Byte) 활성화 결과 쓰기( Byte)로 총 Byte의 추가 HBM 왕복 트래픽이 발생합니다. - Epilogue Fusion 적용 시: 레지스터에 머물러 있는 동안 Leaky ReLU(
) 연산과 FP16 캐스팅을 마친 후 HBM에 단 1회만 쓰기를 수행하므로 추가 메모리 왕복을 100% 제거합니다.
4. L2 캐시 재사용 최적화: 그룹 순서 (Grouped Ordering)
4.1 행 우선 실행 vs 그룹 순서 실행
GPU는 번호(pid)가 인접한 프로그램 인스턴스들을 동시 실행하며, 이 인스턴스들은 L2 캐시를 공유합니다.
- 행 우선(Row-Major) 배치:
출력 타일 그리드에서 pid = 0~8이 첫 행을 계산할 때, 행렬 1개 행 타일(9개)과 행렬 9개 열 타일 전체(81개)를 읽어 총 90개의 입력 타일이 필요합니다. - 그룹 순서(Grouped Ordering,
GROUP_SIZE_M = 3) 배치:정사각 구역으로 배치 순서를 변경하면, 행렬 3개 행 타일(27개)과 행렬 3개 열 타일(27개)을 읽어 총 54개의 입력 타일로 감소합니다.
4.2 그룹 순서 좌표 계산식
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
group_id = pid // num_pid_in_group
first_pid_m = group_id * GROUP_SIZE_M
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
# 그룹 내부에서 세로 방향(M축) 우선 배치
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m동일 면적 대비 정사각 구역이 겉둘레(필요 입력 타일 수)를 최소화하여 L2 적중률을 극대화합니다. 타일 크기 변경 없이 실행 순서 변경만으로 A100 기준 약 10%의 성능 향상(220 TFLOPS
5. 메타파라미터, 오토튜닝, 소프트웨어 파이프라이닝
5.1 @triton.autotune 탐색 메커니즘
triton.Config 후보 목록을 전달하고 key=['M', 'N', 'K']가 바뀔 때 실행 시간을 벤치마크하여 최적 설정을 선택 및 캐싱합니다. 자원 초과 후보는 OutOfResources로 자동 건너끕니다.
5.2 num_warps와 num_stages 영향
num_warps: 프로그램 인스턴스당 워프(Warp, 32 스레드) 수. 워프 수가 적으면 스레드 당 할당 레지스터 부담이 커져 Register Spill이 발생할 수 있습니다.num_stages(소프트웨어 파이프라이닝):-루프 순회 시 다음 타일 데이터를 비동기 복사( cp.async)로 Shared Memory에 미리 불러와 HBM 대기시간을 감춥니다(Latency Overlap).- stages가 증가하면 대기시간 은닉 효과가 커지지만, Shared Memory 사용량이 단계 수에 비례하여 증가하므로 SM당 Occupancy가 떨어지거나 자원 한계를 초과할 수 있습니다.
6. Triton MLIR 컴파일 파이프라인 및 IR 계층
Triton은 @triton.jit 파이프라인을 다단계 중간 표현(IR)을 거쳐 하향 변환(Lowering)합니다:
+-------------------------------------------------------------------------------+
| Python AST : @triton.jit 함수 파싱 및 구문 뼈대 추출 |
+-------------------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------------------+
| TTIR (tt) : MLIR 기반 블록 텐서 연산 (tt.load, tt.dot). 스레드/배치 정보 없음 |
+-------------------------------------------------------------------------------+
| (하드웨어 정보 num_warps, num_stages, GPU 계층 반영)
v
+-------------------------------------------------------------------------------+
| TTGIR (ttg) : 스레드/워프 레이아웃(#blocked, #mma), cp.async 파이프라이닝 매핑|
+-------------------------------------------------------------------------------+
| (스레드 1개 관점 연산으로 전개)
v
+-------------------------------------------------------------------------------+
| LLVM IR : 스레드 단위 일반 IR 코드 및 Shared Memory 주소 공간 배치 |
+-------------------------------------------------------------------------------+
| (NVPTX 백엔드)
v
+-------------------------------------------------------------------------------+
| PTX : NVIDIA 가상 어셈블리 (mma.sync, wgmma, cp.async 명령어) |
+-------------------------------------------------------------------------------+
| (ptxas 컴파일러)
v
+-------------------------------------------------------------------------------+
| CUBIN : 특정 GPU 세대 전용 최종 실행 바이너리 (예: sm_80) |
+-------------------------------------------------------------------------------+6.1 TTIR과 TTGIR의 핵심 차이 및 Layout 구조
- TTIR (
tt dialect): MLIR 프레임워크 기반의 Triton 전용 추상 연산 집합입니다. 텐서 타입이tensor<64x32xf16>과 같이 블록 모양만 나타내며 스레드 배치는 정해지지 않았습니다. - TTGIR (
ttg dialect): TTIR에 GPU 물리 레이아웃 속성이 부여된 저수준 MLIR 단계입니다.#blocked:sizePerThread,threadsPerWarp,warpsPerCTA,order를 지정하여 메모리 병합(Coalescing) 및 연속 읽기를 구현합니다.#mma: Tensor Core 하드웨어 누산기 전용 스레드 배치 레이아웃입니다.#shared: Shared Memory 타일 저장용 레이아웃입니다.
7. 컴파일 산출물 검사 및 Roofline 벤치마크
7.1 컴파일 객체 메타데이터 확인
compiled = matmul_kernel[grid](a, b, c, M, N, K, ...)
print(compiled.asm['ttgir']) # 레이아웃 및 비동기 복사 확인
print(compiled.asm['ptx']) # mma.sync / wgmma 어셈블리 확인
print(compiled.metadata.shared) # Shared Memory 사용량 (Bytes)
print(compiled.n_regs, compiled.n_spills) # 레지스터 사용 수 및 Spill 수7.2 Roofline 모델: Softmax (GB/s) vs GEMM (TFLOPs/s)
- Softmax: 산술 강도가 낮아 메모리 대역폭에 지배받는 커널(Memory-bound)로 GB/s로 측정합니다.
- MatMul: 연산량이
로 크며, 시 산술 강도가 약 1,365 FLOP/Byte에 도달하여 연산 능력에 지배받는 커널(Compute-bound)이므로 TFLOPs/s ( ) 단위로 측정합니다.
8. 결론
Triton 2차원 타일 행렬곱은 알고리즘적 재사용 최적화(브로드캐스팅 오프셋, Epilogue Fusion, Grouped Ordering)와 컴파일러 하향 변환(TTIR