linalg·scf·memref 섞어 쓰기 가능| 계층 | LLVM IR만 쓸 때 문제 | MLIR 해결 |
|---|---|---|
| Tensor | shape · layout 소실 | tensor / memref type |
| Loop | scalar 루프만 | affine / scf region |
| GPU | intrinsic 풀(flat) | gpu · nvgpu dialect |
| Domain | 표현 불가 (FHE, sparse) | 확장 dialect 작성 |
MLIR docs "Motivation" · 원 도입 2019 (Chris Lattner, Google)
tensor/linalg ← 고수준 의미 (행렬곱 그 자체)
↓ tiling
scf + memref ← 중수준 루프·버퍼
↓ vectorize
vector ← SIMD-like tile
↓ gpu lowering
gpu + nvgpu ← GPU 추상화 · Tensor Core
↓ nvvm
nvvm ← NVVM intrinsic 래핑
↓ translate
llvm (NVPTX) ← LLVM IR
↓ NVPTX codegen
PTX assembly
.mlir 파일이 lowering 단계마다 다른 모양이 된다mlir-opt: pass pipeline 한 번에 적용mlir-translate: MLIR ↔ LLVM IR 변환| 프로젝트 | MLIR 사용 |
|---|---|
| Triton | Triton IR · TritonGPU IR ↗ V11 |
| IREE | 모델 → CPU/GPU backend 전체 파이프라인 |
| TorchInductor | 일부 통합 탐색 (주로 Triton 경유) ↗ V13 |
| ONNX-MLIR | ONNX → 여러 backend |
| CIRCT | hardware design (본권 out-of-scope) |
| 요소 | 역할 |
|---|---|
| Operation | 계산 단위 (LLVM의 instruction 일반화) |
| Attribute | 컴파일-타임 상수 (shape, integer, string) |
| Type | Value의 타입 (i32, tensor<4x4xf16>) |
| Region | Block 리스트 (nested IR) |
| Block | Operation 시퀀스 + terminator |
| Value | SSA 결과 또는 Block argument |
// generic form %r = "dialect.op_name"(%a, %b) <{attr = 42 : i32}> : (i32, i32) -> i32 // pretty form (등록된 printer) %r = dialect.op_name %a, %b {attr = 42 : i32} : i32
ModuleOp
└─ Region
└─ Block
└─ func.func
└─ Region
└─ Block (args = %arg0, %arg1)
├─ scf.for
│ └─ Region
│ └─ Block
│ └─ ... ops ...
└─ func.return
Value = OpResult 또는 BlockArgumentcf.br ^bb(%x : i32) → ^bb(%arg : i32)에 바인딩| Type | 예 |
|---|---|
| builtin 정수 | i1, i8, i32, i64 |
| builtin 실수 | f16, bf16, f32, f64 |
| Vector | vector<4xf32> |
| Tensor | tensor<?x16xf16> (shape 포함) |
| MemRef | memref<32x32xf16, 3> (addr space) |
| LLVM | !llvm.ptr<1>, !llvm.struct<...> |
? = dynamic dim · 마지막 정수 = memory space
// integer / float / string {value = 3 : i32} {scale = 1.25 : f32} {name = "kernel_0"} // dense array / affine map {mask = dense<[1,0,1,0]> : vector<4xi1>} {map = affine_map<(d0)->(d0 floordiv 4)>} // symbol ref {callee = @my_func}
arith.addi의 arith).
// .td 파일 def Arith_AddIOp : Arith_Op<"addi", [Pure, Commutative, SameOperandsAndResultType]> { let summary = "integer addition"; let arguments = (ins SignlessIntegerLike:$lhs, SignlessIntegerLike:$rhs); let results = (outs SignlessIntegerLike:$result); let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)"; }
| 개념 | 의미 |
|---|---|
| Trait | op 속성 tag (Pure, Commutative, Terminator) |
| OpInterface | 동적 다형성 API (MemoryEffects, LoopLike) |
| TypeInterface | type 계열 공통 API (ShapedType) |
| AttrInterface | attribute 계열 공통 API |
pass가 isa<LoopLikeOpInterface>로 dialect 모르고도 일반 처리
| dialect | level | 주요 op |
|---|---|---|
func | core | func.func, func.return, func.call |
arith | core | addi, mulf, cmpi |
scf | mid | scf.for, scf.if, scf.yield |
affine | mid | affine.for, affine.load |
memref | mid | memref.alloc, memref.load |
tensor | high | tensor.empty, tensor.extract |
vector | mid | vector.contract, vector.transfer_read |
linalg | high | linalg.matmul, linalg.generic |
gpu | mid | gpu.launch, gpu.thread_id |
nvgpu | low | nvgpu.mma.sync, nvgpu.ldmatrix |
nvvm | target | nvvm.barrier0, nvvm.mma.sync |
llvm | target | llvm.func, llvm.add, llvm.call |
tensor ↔ memref: value semantics ↔ reference semanticslinalg on tensors ↔ linalg on memrefs (bufferization 전후)affine ↔ scf: 정규 루프 ↔ 일반 structured control flowgpu ↔ nvgpu: 벤더 중립 ↔ NVIDIA 특화1. C++ DialectRegistry에 등록 2. mlir-opt 실행 시 필요한 dialect load 3. parse → verify → pass → print 4. 미등록 dialect op는 generic form으로만 print
| 구분 | 예 |
|---|---|
| In-tree | llvm-project/mlir 내부 dialect |
| Out-of-tree | Triton · IREE · CIRCT · ONNX-MLIR |
assemblyFormat으로 pretty form을 지정했어도 dialect 미등록이면 generic form으로만 출력 — tool pipeline에 dialect 누락 자주 발생.
| tier | 관심사 | 대표 dialect |
|---|---|---|
| High | 수학적 연산 · tensor 그 자체 | linalg, tensor, tosa |
| Mid | loop · buffer · region | scf, affine, memref |
| Low | SIMD tile · device 추상 | vector, gpu |
| Target | ISA intrinsic 근접 | nvgpu, nvvm, rocdl, llvm |
┌─────────────────┐
│ tensor · linalg │ HIGH (value · math)
│ (tosa, mhlo) │
└────────┬────────┘
│ tile / fuse / bufferize
┌────────▼────────┐
│ affine / scf │ MID (control flow)
│ memref │ (buffer addr)
└────────┬────────┘
│ vectorize
┌────────▼────────┐
│ vector │ LOW (SIMD tile)
│ gpu │ (device abstr)
└────────┬────────┘
│ nvgpu lowering
┌────────▼────────┐
│ nvgpu │ TARGET-near
│ (mma · ldmat) │
└────────┬────────┘
│ nvvm lowering
┌────────▼────────┐
│ nvvm │ intrinsic wrap
│ (PTX-like) │
└────────┬────────┘
│ translate
┌────────▼────────┐
│ llvm (NVPTX tgt)│ LLVM IR
└────────┬────────┘
│ llc · ptxas
▼
PTX / SASS
| from → to | Pass |
|---|---|
| linalg → loops | convert-linalg-to-loops |
| tensor → memref | one-shot-bufferize |
| affine → scf | lower-affine |
| scf → cf | convert-scf-to-cf |
| vector → gpu | convert-vector-to-gpu |
| gpu → nvvm | convert-gpu-to-nvvm |
| nvgpu → nvvm | convert-nvgpu-to-nvvm |
| nvvm → llvm | convert-nvvm-to-llvm |
| llvm → LLVM IR | mlir-translate --mlir-to-llvmir |
| 축 | tensor | memref |
|---|---|---|
| 의미 | value (불변) | reference (mutable) |
| 메모리 | 없음 (SSA) | 명시적 alloc / load / store |
| addr space | 없음 | integer (GPU: 1, 3, ...) |
| 전환 | bufferization pass가 tensor → memref | |
affine.for: 경계·step 이 affine 식만 허용 → polyhedral 분석 대상scf.for: 일반 SSA 값 허용 (더 자유)affine.load는 memref에서만 동작vector.contract: GEMM-shape SIMDvector.transfer_read/write: 경계 padding 포함vector → nvgpu.mma.sync로 매핑linalg에서 바로 vector로).
| base | scope |
|---|---|
OperationPass<ModuleOp> | 모듈 전체 |
OperationPass<func::FuncOp> | 함수 하나 |
OperationPass<> | any op |
InterfacePass | 특정 interface |
runOnOperation() 구현# 파이프라인 문자열 mlir-opt input.mlir \ --pass-pipeline="builtin.module(\ func.func(convert-linalg-to-loops,\ convert-vector-to-gpu),\ gpu.module(convert-gpu-to-nvvm))" \ -o out.mlir
괄호 = nested pass manager · func.func(...) = 함수 단위 scope
matchAndRewrite(op, rewriter)에서 rewriter.replaceOp / createOp 호출.
struct AddZeroFold : OpRewritePattern<AddIOp> { LogicalResult matchAndRewrite( AddIOp op, PatternRewriter &rw) const { if (matchPattern(op.getRhs(), m_Zero())) { rw.replaceOp(op, op.getLhs()); return success(); } return failure(); } };
| 축 | Greedy | Dialect Conversion |
|---|---|---|
| 목적 | canonicalize / folding | 한 dialect → 다른 dialect |
| 종료 | 고정점 | legal op만 남을 때 |
| type 변환 | 불가 | TypeConverter 사용 |
| 원자성 | op 단위 | 그래프 전체 커밋 |
tensor → memref)target.addLegalDialect<LLVM::LLVMDialect>();
target.addIllegalDialect<nvvm::NVVMDialect>();
if (failed(applyFullConversion(
module, target, patterns)))
signalPassFailure();
| 함수 | 동작 |
|---|---|
applyFullConversion | 모든 illegal op 제거 필수 |
applyPartialConversion | 변환 가능한 만큼만 |
applyAnalysisConversion | IR 수정 없이 분석 |
-canonicalize pass = 각 op의 getCanonicalizationPatterns 모아 greedy 적용-cse와 짝)gpu → nvgpu → nvvmgpu → rocdlgpu → spirvgpu.module @kernels {
gpu.func @add_kernel(
%a : memref<?xf32>,
%b : memref<?xf32>,
%c : memref<?xf32>)
kernel
attributes {gpu.known_block_size = array<i32: 256,1,1>} {
%tid = gpu.thread_id x
%bid = gpu.block_id x
...
gpu.return
}
}
kernel = entry point · known_block_size = compile-time hint
| op | 의미 | → NVVM |
|---|---|---|
gpu.thread_id | threadIdx.{x,y,z} | nvvm.read.ptx.sreg.tid.* |
gpu.block_id | blockIdx.* | nvvm.read.ptx.sreg.ctaid.* |
gpu.block_dim | blockDim.* | nvvm.read.ptx.sreg.ntid.* |
gpu.grid_dim | gridDim.* | nvvm.read.ptx.sreg.nctaid.* |
gpu.barrier | __syncthreads | nvvm.barrier0 |
gpu.shuffle | warp shuffle | nvvm.shfl.sync.* |
gpu.printf | device printf | vprintf |
gpu.alloc | device alloc | cudaMalloc 래퍼 |
gpu.memcpy | H↔D copy | cudaMemcpy |
gpu.launch_func | kernel 호출 | runtime launch |
gpu.launch: region 안에 kernel body를 직접 포함 (outline 전 초기 형태)gpu.launch_func: 별도 gpu.func 참조 (outline 후 host side)gpu-kernel-outlining이 전자 → 후자로 변환%grid = arith.constant 1 : index
%block = arith.constant 256 : index
gpu.launch
blocks(%bx, %by, %bz) in (%grid, %c1, %c1)
threads(%tx, %ty, %tz) in (%block, %c1, %c1) {
%tid = gpu.thread_id x
%idx = arith.addi %tid, %offset : index
%v = memref.load %a[%idx] : memref<?xf32>
memref.store %v, %c[%idx] : memref<?xf32>
gpu.terminator
}
| gpu space | memref addr-space | PTX |
|---|---|---|
| Global | 1 | .global |
| Workgroup (shmem) | 3 | .shared |
| Private (reg/local) | 5 (local) | .local |
| Constant | 4 | .const |
§10에서 LLVM NVPTX 관점으로 재정리 (완전판)
gpu.launch의 host-side 인자가 memref이면 gpu-async-region 경로에서 lowering 중에 gpu.alloc / gpu.memcpy로 분해된다.
gpu보다 아래, nvvm보다 위. Tensor Core · async copy · TMA 같은 기능을 의미 단위로 표현 (NVVM intrinsic 1:1이 아님).
gpu로 표현 못 하는 NV 전용 동작nvvm이 풀어헤친 intrinsic보다 의도가 보존됨 (fragment layout 등)vector → nvgpu → nvvm// m16n8k16 FP16 → FP32
%d = nvgpu.mma.sync(%a, %b, %c)
{ mmaShape = [16, 8, 16] }
: (vector<4x2xf16>,
vector<2x2xf16>,
vector<2x2xf32>)
-> vector<2x2xf32>
%frag = nvgpu.ldmatrix
%smem[%i, %j]
{ transpose = false,
numTiles = 4 : i32 }
: memref<?x?xf16, 3>
-> vector<4x2xf16>
transpose = true: ldmatrix.trans 활용memref<..., 3> = addr space 3 (shared)nvvm.ldmatrix로 하강%tok = nvgpu.device_async_copy
%gmem[%i], %smem[%j], 16
: memref<?xf16> to memref<?xf16, 3>
%g = nvgpu.device_async_create_group %tok
nvgpu.device_async_wait %g { numGroups = 1 : i32 }
cp.async 추상 ↗ V03 §6| op | 매핑 |
|---|---|
nvgpu.tma.async.load | TMA descriptor load ↗ V04 §4 |
nvgpu.mbarrier.create | smem mbarrier alloc |
nvgpu.mbarrier.init | arrive count init |
nvgpu.mbarrier.arrive | arrive signal |
nvgpu.mbarrier.test_wait | try_wait.parity |
nvgpu.warpgroup.mma | wgmma async ↗ V04 §5 |
nvgpu.warpgroup.mma.store | epilogue store |
!nvgpu.mma.ldmatrix.fragment: fragment layout 보존!nvgpu.tensormap.descriptor: TMA descriptor handle!nvgpu.mbarrier.group: mbarrier 배열nvgpu.mma.sync operand type은 특정 shape와 dtype 조합만 유효. mismatch 시 verifier 에러 — mma.sync legal shape 표는 ↗ V03 §7.
llvm.nvvm.* intrinsic과 대부분 1:1.
convert-nvvm-to-llvm으로 LLVM IR| nvvm op | → LLVM intrinsic |
|---|---|
nvvm.read.ptx.sreg.tid.x | llvm.nvvm.read.ptx.sreg.tid.x |
nvvm.read.ptx.sreg.ctaid.x | llvm.nvvm.read.ptx.sreg.ctaid.x |
nvvm.barrier0 | llvm.nvvm.barrier0 |
nvvm.shfl.sync | llvm.nvvm.shfl.sync.* |
nvvm.ldmatrix | llvm.nvvm.ldmatrix.sync.aligned.* |
nvvm.mma.sync | llvm.nvvm.mma.* |
nvvm.cp.async.shared.global | llvm.nvvm.cp.async.* |
nvvm.wmma.mma.* | llvm.nvvm.wmma.mma.* |
// m16n8k16 FP16 → FP32
%d0, %d1, %d2, %d3 = nvvm.mma.sync
A[%a0, %a1, %a2, %a3]
B[%b0, %b1]
C[%c0, %c1, %c2, %c3]
{ shape = #nvvm.shape<m = 16, n = 8, k = 16>,
layoutA = #nvvm.mma_layout<row>,
layoutB = #nvvm.mma_layout<col>,
multiplicandAPtxType = #nvvm.mma_type<f16>,
multiplicandBPtxType = #nvvm.mma_type<f16>,
intOverflowBehavior = #nvvm.mma_int_overflow<satfinite> }
: (vector<2xf16>, ..., f32, ...) -> !llvm.struct<(f32,f32,f32,f32)>
operand가 scalar · vector · struct 혼합 — 이미 LLVM IR의 ABI에 가깝다
llvm.func @my_kernel(%arg0: !llvm.ptr<1>)
attributes { nvvm.kernel } {
...
llvm.return
}
nvvm.kernel attr = entry point 표시!nvvm.annotations metadata 자동 생성| attr | 의미 |
|---|---|
nvvm.maxntid | block 당 최대 thread |
nvvm.minctasm | SM 당 최소 CTA |
nvvm.reqntid | 정확한 block dim |
nvvm.cluster_dim | Hopper cluster 크기 |
nvvm.grid_constant | kernel 인자 중 grid-const |
→ LLVM metadata !nvvm.annotations로 flatten
| 축 | nvgpu | nvvm |
|---|---|---|
| 의미 | mma 의도 | intrinsic 그대로 |
| type | fragment 보존 | scalar 나열 |
| verify | shape 강제 | 느슨 |
| optimization | fusion 쉬움 | 끝단 (거의 안 건드림) |
Module
└─ Function (sig + attrs)
└─ BasicBlock
├─ Instruction (SSA)
└─ Terminator (br, ret, switch, ...)
%tmp 정확히 한 번 정의define i32 @add(i32 %a, i32 %b) {
entry:
%s = add nsw i32 %a, %b
ret i32 %s
}
define i32 @loop(i32 %n) {
entry:
br label %header
header:
%i = phi i32 [0, %entry], [%i.n, %body]
%c = icmp slt i32 %i, %n
br i1 %c, label %body, label %exit
body:
%i.n = add i32 %i, 1
br label %header
exit:
ret i32 %i
}
| 종류 | 예 |
|---|---|
| 정수 | i1, i8, i16, i32, i64 |
| 실수 | half, bfloat, float, double |
| vector | <4 x float> |
| pointer | ptr addrspace(1) opaque ptr |
| struct | { i32, float } |
| array | [256 x i8] |
opaque pointer (2023~) → typed ptr 대신 ptr 한 종류
@llvm.* prefix. backend가 단일 instruction으로 낮추거나 라이브러리 호출로 확장.
@llvm.memcpy.p0.p0.i64@llvm.fma.f32@llvm.nvvm.barrier0 ← NVPTX 전용; 예: branch weight %c = icmp slt i32 %i, %n, !prof !0 !0 = !{!"branch_weights", i32 99, i32 1} ; NVPTX annotation (kernel 표시) !nvvm.annotations = !{!10} !10 = !{ptr @my_kernel, !"kernel", i32 1}
!N 으로 참조| attr | 의미 |
|---|---|
nounwind | 예외 없음 |
readonly | mem 수정 안 함 |
noalias | ptr 간 별칭 없음 |
align N | ptr 정렬 |
dereferenceable(N) | 최소 N byte 역참조 가능 |
llvm dialect IR → mlir-translate --mlir-to-llvmir.ll 생성opt -O3로 추가 최적화 → NVPTX backendbitcast가 pointer 간엔 불필요 · 타입은 instruction이 결정한다 (load i32, ptr %p).
nvptx64-nvidia-cudae-i64:64-i128:128-v16:16-v32:32-n16:32:64.ptx, object 아님| addr space | 이름 | 용도 | PTX state |
|---|---|---|---|
| 0 | generic | default · 어느 space든 참조 | (cast 필요) |
| 1 | global | HBM · device-wide | .global |
| 2 | internal use | (예약) | — |
| 3 | shared | SMEM · block 내 공유 | .shared |
| 4 | const | read-only 상수 뱅크 | .const |
| 5 | local | per-thread stack | .local |
| 6 | (reserved) | — | — |
| 7 | param | kernel 인자 | .param |
src: LLVM NVPTXBaseInfo · PTX ISA §5.1 state spaces
; shared memory buffer @smem = internal addrspace(3) global [256 x i32] undef, align 16 ; global pointer define void @kern(ptr addrspace(1) %X) { %p = getelementptr i32, ptr addrspace(1) %X, i64 %tid64 %v = load i32, ptr addrspace(1) %p ... }
addrspace(N)으로 pointer type 수식; named metadata !nvvm.annotations = !{!10, !11, !12} ; 각 항목 = (함수, 태그, 값) !10 = !{ptr @kern, !"kernel", i32 1} !11 = !{ptr @kern, !"maxntidx", i32 256} !12 = !{ptr @kern, !"minctasm", i32 2}
| tag | 의미 |
|---|---|
kernel | entry point 표시 (= .entry) |
maxntidx/y/z | block 당 최대 thread |
reqntidx | 정확한 block dim (고정) |
minctasm | SM 당 최소 CTA |
grid_constant | unchanged param idx |
cluster_dim_x | Hopper cluster (sm_90) |
# LLVM level llc -march=nvptx64 \ -mcpu=sm_80 \ -mattr=+ptx75 \ input.ll -o out.ptx # mlir-translate → llc mlir-translate \ --mlir-to-llvmir out.mlir | \ llc -march=nvptx64 -mcpu=sm_90a
-mcpu: sm_70/80/86/89/90/90a-mattr=+ptx80: PTX ISA 버전 (backward)sm_90a = Hopper 전용 (TMA·WGMMA) ↗ V04LLVM IR (NVPTX target) → NVPTXLower* passes → ISel (tablegen patterns) → NVPTXMachineFunction → NVPTXAsmPrinter → .ptx (text) → ptxas (외부) → cubin (SASS binary)
addrspacecast 없이 직접 load하면 backend가 PTX ld를 generic load로 내보내 성능 저하. 반드시 specific addrspace로 캐스팅하고 작업.
| group | 예 |
|---|---|
| sreg (special reg) | tid.x, ctaid.x, laneid, warpid |
| barrier | barrier0, bar.sync, membar |
| shuffle | shfl.sync.{idx,up,down,bfly} |
| vote | vote.sync.{all,any,ballot} |
| atomic | atom.{add,cas,min,max,and,or,xor} |
| mma | mma.m16n8k16.*, wmma.mma.* |
| ldmatrix | ldmatrix.sync.aligned.* |
| cp.async | cp.async.{ca,cg}.shared.global |
| TMA (Hopper) | cp.async.bulk.tensor.* |
| tensor mem | tcgen05.* (Blackwell) |
declare i32 @llvm.nvvm.read.ptx.sreg.tid.x() declare i32 @llvm.nvvm.read.ptx.sreg.ctaid.x() declare i32 @llvm.nvvm.read.ptx.sreg.ntid.x() declare i32 @llvm.nvvm.read.ptx.sreg.nctaid.x() declare i32 @llvm.nvvm.read.ptx.sreg.laneid() declare i32 @llvm.nvvm.read.ptx.sreg.warpid() %t = call i32 @llvm.nvvm.read.ptx.sreg.tid.x()
PTX: mov.u32 %r, %tid.x;
; block 전체 sync (= __syncthreads) declare void @llvm.nvvm.barrier0() ; partial barrier: arrive count 지정 declare void @llvm.nvvm.barrier( i32 %barId, i32 %threadCount) ; membar scope declare void @llvm.nvvm.membar.cta() declare void @llvm.nvvm.membar.gl() declare void @llvm.nvvm.membar.sys()
scope 순서: cta ⊂ gl(GPU) ⊂ sys(system) ↗ V03 §12
declare i32 @llvm.nvvm.shfl.sync.idx.i32(
i32 %mask, i32 %val,
i32 %srcLane, i32 %packedCfg)
declare i32 @llvm.nvvm.shfl.sync.down.i32(
i32 %mask, i32 %val,
i32 %delta, i32 %packedCfg)
declare i32 @llvm.nvvm.shfl.sync.bfly.i32(
i32 %mask, i32 %val,
i32 %lane, i32 %packedCfg)
mask: 참여 lane bitmask (0xFFFFFFFF = 전체)packedCfg: clamp/segment bits 포함; m16n8k16 FP16→FP32 sync mma declare {float, float, float, float} @llvm.nvvm.mma.m16n8k16.row.col.f16.f16( <2 x half>, <2 x half>, ; A 4 frags → 8 half <2 x half>, <2 x half>, <2 x half>, <2 x half>, ; B 2 frags → 4 half float, float, float, float) ; C 4 f32
vectorized <N x T> = fragment per-thread layout ↗ V03 §7
declare {i32,i32,i32,i32}
@llvm.nvvm.ldmatrix.sync.aligned.m8n8.x4.b16(
ptr addrspace(3) %smem)
declare void
@llvm.nvvm.cp.async.cg.shared.global.16(
ptr addrspace(3), ptr addrspace(1))
declare void
@llvm.nvvm.cp.async.commit.group()
declare void
@llvm.nvvm.cp.async.wait.group(i32)
bf16 variant 추가 등). NVPTX RST와 IntrinsicsNVVM.td를 버전별로 확인.
| sm_arch | GPU | PTX ISA | 필수 기능 |
|---|---|---|---|
sm_70 | V100 | ptx60+ | wmma |
sm_75 | T4 / Turing | ptx63+ | mma INT8 |
sm_80 | A100 | ptx70+ | mma FP16, cp.async |
sm_86 | A10/A40/RTX30 | ptx71+ | — |
sm_89 | L40 / RTX40 | ptx78+ | FP8 (Ada) |
sm_90 | H100 (공통) | ptx78+ | cluster · TMA (일부) |
sm_90a | H100 전용 | ptx78+ | WGMMA · TMA |
sm_100 | Blackwell | ptx86+ | tcgen05 · FP4 |
sm_XXa suffix = architecture-specific (forward-incompatible)
# 기본 PTX 출력 llc -march=nvptx64 -mcpu=sm_80 \ -mattr=+ptx75 \ in.ll -o out.ptx # fast-math / denormal llc -fp-contract=fast \ -enable-unsafe-fp-math \ -nvptx-f32ftz \ # ftz denormal -nvptx-prec-divf32=0 \ # approx div -nvptx-prec-sqrtf32=0
| flag | 의미 |
|---|---|
-arch=sm_90a | target architecture |
-O0..-O3 | opt level (ptxas 자체) |
-maxrregcount=N | reg 상한 → spill 유도 |
--gpu-name | sm 이름 문자열 |
-v | register · spill · shmem 요약 |
-dlcm=ca/cg | default cache mode |
--compile-only | link 생략 |
-ftz=true | denormal flush-to-zero |
-prec-div=false | FP32 div 근사 |
-prec-sqrt=false | FP32 sqrt 근사 |
| level | 효과 |
|---|---|
| strict | IEEE · 느림 |
| contract | fma 융합만 허용 |
| fast | ftz + approx 전부 |
# 1) MLIR → LLVM IR mlir-translate \ --mlir-to-llvmir kernel.mlir \ -o kernel.ll # 2) LLVM IR → PTX llc -march=nvptx64 -mcpu=sm_90a \ kernel.ll -o kernel.ptx # 3) PTX → cubin ptxas -arch=sm_90a -O3 \ kernel.ptx -o kernel.cubin # 4) cubin → fatbin (선택) fatbinary --image=profile=sm_90,file=kernel.cubin \ --create=kernel.fatbin
-code=sm_80,sm_90 + -code=compute_80 조합sm_90과 sm_90a는 PTX 레벨부터 다름. sm_90으로 빌드한 커널은 WGMMA / TMA PTX instruction을 생성할 수 없다 — ptxas에서 verifier error.
linalg ★// 고수준 의미: D = A·B
func.func @matmul(
%A : tensor<128x64xf16>,
%B : tensor<64x128xf16>,
%C : tensor<128x128xf32>)
-> tensor<128x128xf32> {
%D = linalg.matmul
ins (%A, %B : tensor<128x64xf16>,
tensor<64x128xf16>)
outs(%C : tensor<128x128xf32>)
-> tensor<128x128xf32>
return %D : tensor<128x128xf32>
}
tensor 값 의미 · shape·layout 보존 · 아직 loop 없음
scf.for// tile (32,32,16) + bufferize
scf.for %m = %c0 to %c128 step %c32 {
scf.for %n = %c0 to %c128 step %c32 {
%acc = vector.transfer_read
%Cbuf[%m, %n], %cst0
: memref<128x128xf32>,
vector<32x32xf32>
%r = scf.for %k = %c0 to %c64 step %c16
iter_args(%aI = %acc) -> vector<32x32xf32> {
%a = vector.transfer_read %Abuf[%m, %k], %cst
%b = vector.transfer_read %Bbuf[%k, %n], %cst
%n = vector.contract ... %a, %b, %aI
scf.yield %n : vector<32x32xf32>
}
vector.transfer_write %r, %Cbuf[%m, %n]
}
}
vector.contract → nvgpu// 32x32 contract → 4개의 m16n8k16 mma
%a0 = nvgpu.ldmatrix %Asm[%i0, %k0]
{ numTiles = 4 } : ... -> vector<4x2xf16>
%b0 = nvgpu.ldmatrix %Bsm[%k0, %j0]
{ numTiles = 2, transpose = true }
: ... -> vector<2x2xf16>
%d0 = nvgpu.mma.sync(%a0, %b0, %c0)
{ mmaShape = [16, 8, 16] }
: (vector<4x2xf16>,
vector<2x2xf16>,
vector<2x2xf32>)
-> vector<2x2xf32>
각 thread의 fragment 보존된 type 유지
nvvm%fa = nvvm.ldmatrix %Asm
{ num = 4 : i32, layout = #nvvm.mma_layout<row> }
: (!llvm.ptr<3>)
-> !llvm.struct<(i32, i32, i32, i32)>
%dd = nvvm.mma.sync
A[%a0, %a1, %a2, %a3]
B[%b0, %b1]
C[%c0, %c1, %c2, %c3]
{ shape = #nvvm.shape<m=16, n=8, k=16>,
layoutA = #nvvm.mma_layout<row>,
layoutB = #nvvm.mma_layout<col>,
multiplicandAPtxType = #nvvm.mma_type<f16>,
multiplicandBPtxType = #nvvm.mma_type<f16> }
: (...) -> !llvm.struct<(f32,f32,f32,f32)>
define void @matmul_kernel(
ptr addrspace(1) %A,
ptr addrspace(1) %B,
ptr addrspace(1) %C)
!nvvm.annotations !0 {
entry:
%tid = call i32 @llvm.nvvm.read.ptx.sreg.tid.x()
...
%r = call {float,float,float,float}
@llvm.nvvm.mma.m16n8k16.row.col.f16.f16(
<2 x half> %a0, ..., float %c0, ...)
...
ret void
}
!0 = !{ptr @matmul_kernel, !"kernel", i32 1}
.version 7.8
.target sm_80
.address_size 64
.visible .entry matmul_kernel(
.param .u64 A,
.param .u64 B,
.param .u64 C)
{
.reg .f32 %f<128>;
.reg .b32 %r<128>;
...
ldmatrix.sync.aligned.x4.m8n8.shared.b16
{%r0,%r1,%r2,%r3}, [%sA];
mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32
{%f0,%f1,%f2,%f3},
{%r0,%r1,%r2,%r3},
{%rb0,%rb1},
{%f0,%f1,%f2,%f3};
...
}
tensor → memref → vector → fragment → <N x half> → .reg로 점차 HW 친화로 수렴한다.
| 프로젝트 | 입력 | 출력 | GPU backend |
|---|---|---|---|
| IREE | MLIR (TOSA / StableHLO) | VM bytecode | CUDA · ROCm · Vulkan |
| Polygeist | C/C++/OpenMP | MLIR (affine) | CPU · CUDA (연구) |
| nvfuser | PyTorch JIT IR | CUDA kernel | NVIDIA (MLIR 미사용) |
| Triton | Python DSL | PTX ↗ V11 | NVIDIA (MLIR 사용) |
| ONNX-MLIR | ONNX | LLVM / accel. | CPU 주, GPU 확장 |
| CIRCT | HW IR | Verilog | (GPU 아님) |
TOSA / StableHLO / TorchMLIR
↓ input conversion
linalg on tensors
↓ dispatch region 형성 (CTA 단위)
flow.executable + hal.executable
↓ target-specific pipeline
├─ LLVM-CPU: vector · llvm
└─ LLVM-GPU (CUDA): gpu · nvvm · llvm
IREE VM bytecode + cubin
↓ runtime
execute
affine/scf MLIR로 직접 import. polyhedral 최적화 pass 적용 후 GPU로 내림.
| 전략 | 예 | 특징 |
|---|---|---|
| dispatch-centric | IREE | dispatch = 독립 kernel |
| block-centric | Triton ↗ V11 | 프로그래머가 block 직접 제어 |
| loop-centric | Polygeist | loop nest → GPU map |
| graph-centric | XLA ↗ V14 | HLO op 단위 fusion |
// 선언적 schedule 스크립트 transform.sequence %root : !any_op { ^bb(%arg0: !any_op): %mm = transform.structured.match ops{["linalg.matmul"]} in %arg0 %tiled, %loops = transform.structured.tile_using_for %mm [32, 32, 16] transform.structured.vectorize_children_and_apply_patterns %tiled }
IREE·MLIR에서 codegen 실험의 사실상 표준
| dialect | level | 핵심 |
|---|---|---|
triton (TT) | block-level DSL | tt.load, tt.dot, tt.store |
triton_gpu (TTG) | GPU-aware | layout encoding 포함 |
자세한 설계·pipeline은 ↗ V11 · 여기선 MLIR 관점 특이점만
tl.program_id로 block idx를 직접 준다. MLIR gpu dialect의 gpu.launch를 쓰지 않음#triton_gpu.blocked<...> attribute로 분배 · register fragment · shared layout 삽입linalg 단계 없음nvgpu/nvvm dialect 이용 (최근 일부)// blocked layout: CTA 내 thread 분배 #blocked = #triton_gpu.blocked<{ sizePerThread = [1, 8], threadsPerWarp = [8, 4], warpsPerCTA = [4, 1], order = [1, 0]}> // mma layout: tensor core fragment #mma = #triton_gpu.nvidia_mma<{ versionMajor = 2, warpsPerCTA = [4, 1], instrShape = [16, 8]}> // shared layout: smem swizzle #shared = #triton_gpu.shared<{ vec = 8, perPhase = 2, maxPhase = 8, order = [1, 0]}>
convert_layout 삽입| 축 | upstream MLIR (IREE) | Triton |
|---|---|---|
| 시작점 | linalg on tensors | TT (DSL) |
| block 추상 | gpu.launch → nvvm | TTG (내장) |
| layout | attribute(약함) | type(강제) |
| Tensor Core | nvgpu.mma.sync | tt.dot → mma layout |
| codegen | mlir-translate + llc | 자체 + LLVM NVPTX |
| pipeline stage | 많음 (유연) | 적음 (직행) |
arith, scf 일부 (control flow)nvgpu/nvvm 일부 통합 (TMA / WGMMA)| base | 용도 |
|---|---|
OpRewritePattern<Op> | 한 op 루트 매칭 |
RewritePattern | 일반 (op name·interface) |
ConversionPattern | dialect conversion용 |
OpConversionPattern<Op> | conv + 특정 op |
struct FoldAddZero : OpRewritePattern<arith::AddIOp> { using OpRewritePattern::OpRewritePattern; LogicalResult matchAndRewrite( arith::AddIOp op, PatternRewriter &rewriter) const override { // match Value rhs = op.getRhs(); if (!matchPattern(rhs, m_Zero())) return failure(); // rewrite rewriter.replaceOp(op, op.getLhs()); return success(); } };
| API | 의미 |
|---|---|
create<Op>(loc, args) | 새 op 삽입 |
replaceOp(op, vals) | op 제거 + 사용처 값 치환 |
replaceOpWithNewOp<Op>(...) | 한 줄 치환 |
eraseOp(op) | 삭제만 (no-use) |
startOpModification / finalize | in-place 수정 |
inlineRegionBefore(...) | region 이식 |
getRemappedValue(v) | conversion 중 value 추적 |
struct ConvertMyOp : OpConversionPattern<MyOp> { using OpConversionPattern::OpConversionPattern; LogicalResult matchAndRewrite( MyOp op, OpAdaptor adaptor, // 변환된 operand ConversionPatternRewriter &rw) const override { Value x = adaptor.getInput(); // 이미 새 type Type ty = getTypeConverter()->convertType( op.getType()); rw.replaceOpWithNewOp<LoweredOp>(op, ty, x); return success(); } };
OpAdaptor: 이미 converted된 operand 접근getTypeConverter: 타입 변환 rule 조회RewritePatternSet patterns(ctx);
patterns.add<FoldAddZero,
FoldMulOne,
ConvertMyOp>(ctx);
// greedy
(void)applyPatternsGreedily(op, std::move(patterns));
// conversion
ConversionTarget target(*ctx);
target.addLegalDialect<LLVM::LLVMDialect>();
target.addIllegalOp<MyOp>();
if (failed(applyPartialConversion(
op, target, std::move(patterns))))
signalPassFailure();
pdl.pattern @fold_add_zero : benefit(1) {
%zero = pdl.attribute = 0 : i32
%ty = pdl.type
%c0op = pdl.operation "arith.constant"
{"value" = %zero} -> (%ty : !pdl.type)
%z = pdl.result 0 of %c0op
%lhs = pdl.operand
%add = pdl.operation "arith.addi"
(%lhs, %z : !pdl.value, !pdl.value)
-> (%ty : !pdl.type)
pdl.rewrite %add {
pdl.replace %add with (%lhs : !pdl.value)
}
}
rewriter.create + old op 유지 조합으로 찍으면 infinite loop. 항상 replaceOp / eraseOp로 원 op 제거 확인.
LEVEL DIALECT 주요 op / type
────────────────────────────────────────────────
HIGH (value) tensor tensor.empty/extract
linalg linalg.matmul, generic
(tosa, mhlo) tosa.conv2d
MID (loop) scf scf.for/if/yield
affine affine.for/load
memref memref.alloc/load
LOW (tile) vector vector.contract
vector.transfer_read
gpu gpu.launch, thread_id
gpu.barrier, shuffle
TARGET (NV) nvgpu nvgpu.mma.sync
nvgpu.ldmatrix
nvgpu.device_async_copy
nvvm nvvm.barrier0
nvvm.mma.sync, shfl
nvvm.ldmatrix
LLVM llvm llvm.func, call
llvm.ptr<N> (addrspace)
────────────────────────────────────────────────
mlir-translate → LLVM IR
llc (nvptx64) → PTX
ptxas → cubin (SASS)
linalg → scf: convert-linalg-to-loops tensor → memref: one-shot-bufferize scf → cf: convert-scf-to-cf vector → gpu: convert-vector-to-gpu gpu → nvvm: convert-gpu-to-nvvm nvgpu → nvvm: convert-nvgpu-to-nvvm arith/cf → llvm: convert-arith/cf-to-llvm func → llvm: convert-func-to-llvm
| AS | MLIR type | PTX |
|---|---|---|
| 0 | ptr / memref<T> | generic |
| 1 | ptr addrspace(1) | .global |
| 3 | memref<T, 3> | .shared |
| 4 | addrspace(4) | .const |
| 5 | addrspace(5) | .local |
| 7 | addrspace(7) | .param |
| op | PTX |
|---|---|
read.ptx.sreg.tid.x | %tid.x |
read.ptx.sreg.ctaid.x | %ctaid.x |
barrier0 | bar.sync 0 |
shfl.sync.down | shfl.sync.down.b32 |
mma.m16n8k16.* | mma.sync.aligned.* |
cp.async.cg.shared.global | cp.async.cg.* |
ldmatrix.x4.b16 | ldmatrix.sync.aligned.x4.* |
"kernel" → .entry"maxntidx/y/z""reqntidx""minctasm""cluster_dim_x" (sm_90)# 1. MLIR lower mlir-opt kern.mlir \ --convert-linalg-to-loops \ --one-shot-bufferize \ --convert-scf-to-cf \ --convert-vector-to-gpu \ --convert-gpu-to-nvvm \ --convert-nvgpu-to-nvvm \ --convert-func-to-llvm \ -o lowered.mlir # 2. LLVM IR mlir-translate --mlir-to-llvmir \ lowered.mlir -o k.ll # 3. PTX llc -march=nvptx64 -mcpu=sm_90a \ k.ll -o k.ptx # 4. cubin ptxas -arch=sm_90a -O3 \ k.ptx -o k.cubin