currybab's blog

CuTe DSL 개념 정리

Tensor

cute에서 텐서는 Engine과 Layout으로 구성됨. T=EL

Tensor Evaluation

  1. full evaluation: 특정 요소를 접근하는데 사용함.
  2. partial evaluation (slicing)

메모리 뷰로서의 텐서

좌표 텐서 (Coordinates Tensor)

항등 텐서 (Identity Tensor)

idx=c1+i=2n(cij=1i1sj)

cute.make_identity_tensor(shape) 양방향 매핑은 선형 인덱스에서 N차원 좌표로의 효율적인 변환을 가능하게 하여 텐서 연산 및 메모리 접근 패턴을 용이하게 함.

TensorSSA

SSA: Static Single Assignment

일반 코드:                    SSA 형태:
─────────────────────────────────────────────
x = 1                        x₁ = 1
x = x + 2                    x₂ = x₁ + 2
x = x * 3                    x₃ = x₂ * 3

↓ 각 변수가 딱 한 번만 할당됨 (불변)
SSA의 장점: 컴파일러가 최적화하기 쉬움 (데이터 흐름 추적이 명확)

MLIR(Multi-Level Intermediate Representation)

Python 코드
    │
    ▼
┌─────────────────────────────────────────────┐
│                   MLIR                       │
│  (Multi-Level Intermediate Representation)  │
│                                             │
│   고수준 IR  →  중간 IR  →  저수준 IR        │
│   (텐서)       (루프)      (GPU 명령)        │
└─────────────────────────────────────────────┘
    │
    ▼
PTX / CUDA 코드

TensorSSA의 역할

# TensorSSA 없이 (저수준 MLIR 직접 조작)
op1 = mlir.arith.addf(tensor_a, tensor_b)
op2 = mlir.arith.mulf(op1, tensor_c)
result = mlir.math.exp(op2)

# TensorSSA 사용 (Pythonic!)
result = cute.exp((a + b) * c)
┌────────────────────────────────────────────────────────┐
│  Python 표현식        →    MLIR SSA 연산들             │
│                                                        │
│  (a + b) * c          →    %1 = arith.addf %a, %b     │
│                            %2 = arith.mulf %1, %c     │
└────────────────────────────────────────────────────────┘

TensorSSA 사용 시나리오

  1. 메모리에서 로드하고 메모리에 저장: load(), store()
  2. 레지스터 수준 텐서 연산 - 커널 로직을 작성할 때, 레지스터에 로드된 데이터에 대해 다양한 계산, 변환, 슬라이싱 등이 수행.
  3. 산술 연산
    • 이항 연산
      • 이항 연산의 경우, LHS 피연산자는 TensorSSA 이고 RHS 피연산자는 TensorSSA 또는 Numeric 가 될 수 있음.
      • RHS가 Numeric 인 경우, TensorSSA 로 브로드캐스트.
    • 단항 연산
      • cute.math.sqrt, cute.math.exp, cute.math.log, cute.math.sin, cute.math.cos
    • 축소 연산
      • TensorSSA 의 reduce 메서드는 초기 값으로 시작하여 지정된 축소 연산( ReductionOp.ADD , ReductionOp.MUL , ReductionOp.MAX , ReductionOp.MIN )을 적용하고, reduction_profile 에 지정된 차원을 따라 이 축소를 수행함.
      • 결과는 일반적으로 차원이 축소된 새로운 TensorSSA 이거나, 모든 축에 걸쳐 축소되는 경우 스칼라 값.
      • a_vec.reduce(cute.ReductionOp.ADD, 0.0, reduction_profile=0): 전체 축소 (스칼라)
      • a_vec.reduce(cute.ReductionOp.ADD, 0.0, reduction_profile=(None, 1)): 열 방향 축소 (행 유지)
  4. Broadcast
    • TensorSSA 은 NumPy의 브로드캐스팅 규칙에 따라 브로드캐스팅 연산을 지원함.
    • 브로드캐스팅을 사용하면 특정 조건이 충족될 때 서로 다른 모양의 배열에 대해 연산을 수행할 수 있음.
    • 규칙
      1. 소스 형상은 대상 형상의 랭크와 일치하도록 1로 채워짐. (차원수 맞춤)
      2. 소스 형상의 각 모드 크기는 1이거나 대상 형상과 같아야 함. (크기 맞춤)
      3. 브로드캐스팅 후, 모든 모드는 타겟 형상과 일치해야 함. (결과 형상 맞춤)

Layout Algebra

Layout

Layout Algebra Operations로 가능한 것들

Coalesce (병합)

Composition (결합)

Composition 공식

  A ∘ s:d 계산법:
  1. A / d  →  stride d만큼 건너뛰기 (나누기)
  2. % s    →  shape s만큼만 유지 (모듈로)
  A = (4, 2, 3):(2, 1, 8)일 때, A ∘ 4:2 계산

  step 1:"첫 번째 모드에서 2개를 나눠라"

  Shape:  4/2 = 2  →  (2, 2, 3)
  Stride: 2*2 = 4  →  (4, 1, 8)

  결과: (2, 2, 3):(4, 1, 8)

  step 2: "앞에서부터 4개만 유지"

  2 × 2 = 4  ← 딱 맞음!

  Shape:  (2, 2, 1)  →  (2, 2)
  Stride: (4, 1, _)  →  (4, 1)

  결과: (2, 2):(4, 1)

Division (Splitting into Tiles)

  연산                    결과 shape                          특징
  ----------------------------------------------------------------------
  logical_divide    ((TileM,RestM), (TileN,RestN))   원본 모드 구조 유지
  zipped_divide     ((TileM,TileN), (RestM,RestN))   타일 / 나머지 분리
  tiled_divide      ((TileM,TileN), RestM, RestN)    타일 묶고 나머지 분리
  flat_divide       (TileM, TileN, RestM, RestN)     전부 평탄화 


상황                          추천 함수
-------------------------------------------------
타일 단위로 순회                 `zipped_divide`
각 축 독립 처리                  `logical_divide`
타일 내부 + 타일 좌표 분리 접근     `tiled_divide`

logical_divide

logical_divide(A,B):=A(B,B*)   where  B*=complement(B,size(A))

  질문: 4:2를 어떻게 배치하면 24개를 다 채울까?

  4:2의 cosize = 8 (0~6 사용, 다음은 8부터)

  24개를 채우려면:
  ├── stride 내 구멍: 2개 (0,1 / 2,3 / 4,5 / 6,7 중 짝수만 선택하니까)
  └── 반복 횟수: 24 / 8 = 3번


  방법 1: 구멍 먼저 채우기
  ────────────────────────
  4:2가    : 0     2     4     6
  구멍     :    1     3     5     7
          └─ offset +1 하면 됨

  = 2:1 (2개, stride 1로 오프셋)


  방법 2: 그 다음 반복
  ────────────────────────
  블록0: 0~7   (offset 0)
  블록1: 8~15  (offset 8)
  블록2: 16~23 (offset 16)

  = 3:8 (3개, stride 8로 오프셋)

Product (타일 복제)

Divide vs Product 차이

    logical_divide(A, B)                              
    ─────────────────────                             
    "A를 B로 나눈다"                                  
                                                    
    A가 전체 레이아웃, B가 타일 패턴                   
    → 결과: ((타일 내부), (타일 배치))                
                                                    
    size(결과) = size(A)                              


    logical_product(A, B)                                     
    ──────────────────────                                     
    "A를 B만큼 복제한다"                                        
                                                              
    A가 타일, B가 복제 패턴                                     
    → 결과: ((타일), (복제 배치))                               
                                          
    size(결과) = size(A) × size(B)                   

CUDA와 커널 호출 비교

Host-ish 코드 (JIT 컴파일 시점)

@cute.jit
def solution(A, B, C):
    # 패턴/설계도 정의 (이게 핵심 차이!)
    tiled_mma = make_tiled_mma(...)      # "어떻게 연산할지"
    tiled_copy = make_tiled_copy(...)    # "어떻게 복사할지"
    smem_layout = make_layout(...)       # "메모리 구조"
    
    kernel(tiled_mma, tiled_copy, ...)   # 패턴 전달

Device 코드 (GPU) - 실행만

@cute.kernel
def kernel(tiled_mma, tiled_copy, ...):
    # 패턴 "사용"만 함
    cute.copy(tiled_copy, src, dst)
    cute.gemm(tiled_mma, a, b, c)

더 정확한 비유

전통 CUDA:
  Host   = 매니저 (일 시키기만)
  Device = 노동자 (모든 판단 + 실행)

CuTe:
  @cute.jit = 설계사 (설계도 작성)
  @cute.kernel = 노동자 (설계도대로 실행)

왜 이렇게 됐나?

텐서코어 시대:
- 패턴이 복잡함 (MMA, TMA, 파이프라이닝)
- 컴파일 타임에 알아야 최적화 가능
- 런타임에 결정하면 너무 느림

→ "설계"를 JIT 시점으로 끌어올림

정리

@cute.jit = Host + 컴파일러 힌트 (설계)

@cute.kernel = Device (실행)

전통 CUDA보다 @cute.jit의 역할이 훨씬 큼!

#blog #cuda #cutedsl #gpu