cuBLAS 96%, `unsafe` 없음: cuTile Rust가 증명한 것

cuTile Rust는 Rust 소유권을 GPU 커널에 적용합니다. sm_80+ 요건, 파티션-디스패치 패턴, Grout을 다룹니다.

cuBLAS 96%, `unsafe` 없음: cuTile Rust가 증명한 것
Share

GPU 프로그래밍은 보통 Rust 개발자에게 실행 경계에서 borrow checker를 포기하라고 요구합니다. 참조는 raw pointer로 무너지고, aliasing, synchronization, stream lifetime은 직접 관리해야 하는 불변 조건이 됩니다. NVIDIA Labs의 새 논문은 이런 타협이 필요 없다고 주장합니다.

cuTile Rust가 GPU 디스패치까지 borrow 규율을 확장하는 방식

cuTile Rust는 호스트 코드 안에서만이 아니라 host-to-GPU 실행 경계를 넘어 Rust의 ownership과 borrowing 규칙을 유지하는 tile 기반 DSL입니다. NVIDIA 연구원 Melih Elibol, Jared Roesch, Isaac Gelado, Eric Buehler, Michael Garland가 제출한 "Fearless Concurrency on the GPU"(arXiv:2606.15991)에서 소개된 이 도구는 , 손으로 작성한 unsafe CUDA를 감싸는 대신 커널 자체를 관용적이고 메모리 안전한 Rust로 작성하게 해줍니다.

그 핵심은 런타임 lock이 아니라 타입 구성입니다. 실행 전에 mutable output tensor는 겹치지 않음이 증명된 tile들로 나뉩니다. 그러면 각 tile program은 자기 slice에 대한 독점 &mut view를 받고, input은 shared & reference로 전달됩니다 . partition이 서로 겹칠 수 없기 때문에 커널은 의미론적으로 single-threaded이고 구성 단계부터 data race가 없지만, 여전히 대규모 병렬 GPU 코드로 컴파일됩니다. Melih Elibol의 표현처럼 "each tile program gets an exclusive &mut view of its memory, plus the inputs as shared references" (source: users.rust-lang.org). 더 낮은 수준의 제어가 필요할 때는 지역적으로 opt-out할 수 있는 explicit unchecked type도 그대로 제공됩니다.

처리량을 희생한다면 이런 안전성 이야기는 학술적인 주장에 그쳤겠지만, 보고된 수치는 그렇지 않다고 말합니다. NVIDIA B200에서 cuTile Rust는 memory-bound element-wise operation에서 7 TB/s, GEMM에서 2 PFlop/s에 도달합니다. 이는 cuBLAS의 약 96%이며 cuTile Python과는 측정 오차 범위 안입니다 . End-to-end 기준으로, 함께 제공되는 Qwen3 inference engine인 Grout은 batch-1 decode에서 RTX 5090의 Qwen3-4B에 대해 171 generated tokens/s, B200의 Qwen3-32B에 대해 82 tokens/s를 기록했습니다 . 이는 특정 하드웨어에서 저자들이 직접 측정한 값이며 아직 독립 재현이 확립된 것은 아닙니다. 그래도 이 수치들은 이 글이 풀어낼 핵심 주장, 즉 측정 가능한 성능 손실 없이 안전한 Rust 커널을 작성할 수 있다는 주장의 배경을 이룹니다.

sm_80+, Driver ≥610, Rust 1.89: 크레이트가 요구하는 기준

96% of cuBLAS, no `unsafe`: what cuTile Rust proves

이 모든 것이 실제 하드웨어에서 동작하기 전에, 크레이트는 명확한 하한선을 둡니다. cuTile Rust는 compute capability sm_80 이상인 NVIDIA GPU, 즉 Ampere, Hopper, Blackwell을 대상으로 하며 Volta(V100)와 그 이전 세대는 제외됩니다 . CUDA 13.3, Rust 1.89+, Linux를 기반으로 하며 Ubuntu 24.04에서 테스트되었습니다. Windows와 macOS는 지원되지 않고, 2026년 6월 기준 AMD/ROCm 또는 Metal backend도 없습니다 . CUDA 13.x는 minor-version compatibility를 위해 driver ≥580이 필요하고, CUDA 13.3 GA는 Linux driver ≥610.43.02에 해당합니다 .

요구 사항최소 기준
GPU compute capabilitysm_80+ (Ampere/Hopper/Blackwell)
CUDA toolkit13.3
Linux driver≥610.43.02 (13.x minor compatibility는 ≥580)
Rust1.89+
OSLinux (Ubuntu 24.04에서 테스트)
Tile IR toolchainCMake 3.20+, C++17, Python 3.6+

Tile IR toolchain 자체, 즉 MLIR 기반 Tile IR bytecode를 cubin으로 컴파일하는 cuda-tile-translatetileiras는 CMake 3.20+, C++17, Python 3.6+를 요구합니다 . 먼저 driver와 GPU를 확인하세요. 아래 내용은 모두 이 기준을 충족한다고 가정합니다.

cuTile Rust 크레이트에 주석 달기, 분할하기, 디스패치하기

96% of cuBLAS, no `unsafe`: what cuTile Rust proves

cuTile Rust 커널을 작성하려면 #[cutile::module] 블록을 선언하고, 함수에 #[cutile::entry()]를 붙인 뒤, use cutile::prelude::*로 프렐류드를 스코프에 가져오면 됩니다. 이 매크로는 해당 함수를 GPU 커널로 다시 작성하고, 텐서를 분할하는 호스트 측 런처를 자동 생성합니다. 직접 디스패치 코드를 손으로 작성할 필요가 없습니다 . 표준적인 원소별 덧셈은 일반 Rust처럼 보입니다:

#[cutile::module]
mod kernel {
  #[cutile::entry()]
  fn add<const B: i32>(
    z: &mut Tensor<f32, {[B]}>,  // exclusive write
    x: &Tensor<f32, {[-1]}>,     // shared read
    y: &Tensor<f32, {[-1]}>,     // shared read
  ) {
    let tx = load_tile_like(x, z);
    let ty = load_tile_like(y, z);
    z.store(tx + ty);
  }
}

시그니처가 곧 계약입니다. 변경 가능한 출력은 &mut Tensor<f32, {[B]}> 타입이고, 공유 입력은 &Tensor<f32, {[-1]}>입니다. const-generic 형태 매개변수가 타입 수준에서 타일 크기를 인코딩하므로, borrow checker는 타일마다 하나의 독점 writer와 여러 불변 reader를 볼 수 있습니다 .

호스트 쪽 절차는 짧습니다. 텐서를 만들고, 실행 전에 변경 가능한 출력에 .partition([128])을 호출한 다음, 블로킹 실행을 위해 kernel::add(z, x, y).sync()?를 실행하면 됩니다. 생성된 런처는 GPU 작업이 진행되는 동안 피연산자를 붙잡고 있으며, .sync()가 완료된 뒤에야 텐서의 소유권이 다시 돌아옵니다 . 분할이 서로 겹치지 않음이 증명되므로, 각 타일 프로그램은 의미상 단일 스레드이고 구조적으로 데이터 레이스가 없습니다.

추론 파이프라인을 위해 cuTile Rust는 lazy DeviceOp 모델을 제공합니다. 블로킹 디스패치에는 .sync(), 비동기 실행에는 .into_future()(IntoFuture 경유), CUDA 그래프 캡처와 재실행에는 .graph() / CudaGraph::scope를 사용합니다 . 의도된 패턴은 재사용 가능한 레이어 그래프를 한 번 만들고, 기록된 각 op 안에서 임시 버퍼를 변경 가능하게 빌린 뒤, sync 이후 해제하는 방식입니다. 스트림 순서 캡처와 Rust lifetime 덕분에 버퍼 재사용이 타입 시스템에 드러나므로, 수동 주석 없이도 순서가 강제됩니다. 커널은 GPU에 도달하기 전에 MLIR 기반 중간 표현인 CUDA Tile IR을 통해 JIT 컴파일됩니다 .

GPU 없이도 이 안전성 아이디어는 쉽게 감을 잡을 수 있습니다. 아래 예시 Python 코드는(실행된 코드이며, 프로덕션 Rust 경로가 아닙니다) 각 타일의 경계를 한 번 증명한 뒤, 검사된 범위를 통해서만 메모리에 접근합니다. 이는 cuTile Rust가 컴파일 타임에 강제하는 "서로 겹치지 않음을 증명한 뒤, slice를 신뢰한다"는 구조와 같습니다:

from dataclasses import dataclass
from random import Random


@dataclass(frozen=True)
class Tile:
    row: range
    col: range
    red: range

    def proved(self, m: int, n: int, k: int) -> "Tile":
        assert 0 <= self.row.start <= self.row.stop <= m
        assert 0 <= self.col.start <= self.col.stop <= n
        assert 0 <= self.red.start <= self.red.stop <= k
        return self


def tiled_matmul(a, b, block=8):
    m, k, n = len(a), len(a[0]), len(b[0])
    c = [[0.0] * n for _ in range(m)]
    proofs = 0
    for i in range(0, m, block):
        for j in range(0, n, block):
            for p in range(0, k, block):
                t = Tile(range(i, min(i + block, m)),
                         range(j, min(j + block, n)),
                         range(p, min(p + block, k))).proved(m, n, k)
                proofs += 1
                for r in t.row:
                    for q in t.red:
                        arq = a[r][q]
                        for s in t.col:
                            c[r][s] += arq * b[q][s]
    return c, proofs


def plain_matmul(a, b):
    return [[sum(x * y for x, y in zip(row, col)) for col in zip(*b)] for row in a]


rng = Random(0)
size = 24
a = [[rng.random() for _ in range(size)] for _ in range(size)]
b = [[rng.random() for _ in range(size)] for _ in range(size)]
got, proofs = tiled_matmul(a, b)
want = plain_matmul(a, b)
err = max(abs(got[i][j] - want[i][j]) for i in range(size) for j in range(size))

print("cuTile idea in Python: prove tile bounds once, then use only checked ranges.")
print(f"tiles proved: {proofs}; unsafe operations: 0")
print(f"max error vs reference: {err:.2e}")
print("The 96%-of-cuBLAS claim is about Rust/CUDA performance; this shows the safety proof shape.")

예상해야 할 마찰: AMD 미지원, 바뀌는 매크로, 아직 검증되지 않은 동시성

96% of cuBLAS, no `unsafe`: what cuTile Rust proves

현재 cuTile Rust는 NVIDIA/CUDA 전용이며, 이 제약은 깊게 박혀 있습니다. AMD/ROCm 경로도, Metal 백엔드도, 이식 가능한 WebGPU 대체 경로도 없습니다. 모든 커널은 CUDA Tile IR을 거쳐 cubin으로 JIT 컴파일됩니다 . 요구되는 compute capability 하한도 명확합니다. CUDA 13.3, Rust 1.89 이상, Linux와 함께 sm_80(Ampere) 이상이 필요합니다 . Ampere 이전 카드는 완전히 제외됩니다.

표면 API는 명시적으로 초기 단계입니다. Tensor<f32, {[B]}> const-generic 형태 문법과 #[cutile::module]/#[cutile::entry()] 매크로 형식은 릴리스 사이에 바뀔 수 있습니다 . CI에 올리기 전 Cargo.lock에서 의존성을 고정하세요. API 변동은 예외가 아니라 예상되는 일로 보는 편이 맞습니다.

헤드라인 수치는 정확히 읽어야 합니다. cuBLAS 대비 96% GEMM 결과와 RTX 5090에서 Qwen3-4B batch-1 decode 171 tokens/s는 B200을 포함한 특정 하드웨어에서 저자들이 직접 측정한 값입니다 . CUDA Tile Python 스택에 대한 독립 평가는 RTX PRO 6000 Blackwell Server Edition에서 GEMM이 cuBLAS의 52-79%, FlashAttention-2 처리량은 53%에 그쳤다고 보고했습니다. 워크로드와 아키텍처에 따라 결과가 달라집니다 . 다중 배치 처리량, prefill latency, Qwen3 외 모델 커버리지는 아직 특성이 충분히 밝혀지지 않았습니다. 성숙한 추론 스택을 교체하기 전에 목표 GPU, 배치 분포, 컨텍스트 길이에서 직접 검증하세요.

cuTile Rust 크레이트 작성자가 참고할 추론 구현, Grout

마이크로벤치마크가 아니라 실제 디코드 경로에서 cuTile Rust가 어떻게 쓰이는지 보고 싶다면 Grout를 읽어보세요. Grout는 mistral.rs를 관리하는 Eric Buehler가 공동 작성한 cuTile-Rust 기반 Qwen3 추론 엔진이며, 프로덕션 호출부 패턴의 표준 참고 사례로 볼 수 있습니다. lazy DeviceOp 그래프를 어떻게 구성하는지, CudaGraph::scope 캡처 안에서 임시 버퍼를 가변으로 빌리는지, 그리고 .sync() 이후에야 소유권을 회수하는지 살펴보세요. 이 순서가 추론 파이프라인에서 의도된 관용구입니다. 스트림 순서 캡처와 Rust 라이프타임이 결합되어 버퍼 재사용이 타입 시스템에 드러나기 때문입니다.

중요한 차이는 여기에 있습니다. Candle, Burn, mistral.rs는 대체로 손으로 작성한, 종종 unsafe인 커널을 FFI로 호출하거나 감싸는 방식입니다. 반면 cuTile Rust는 성능 손실 측정 없이 커널 자체를 safe Rust로 작성할 수 있는 경로를 제공합니다. 주 저자인 Melih Elibol은 이 보장을 이렇게 설명합니다. "each tile program gets an exclusive &mut view of its memory, plus the inputs as shared references" .

구체적인 다음 단계는 이렇습니다. Grout를 클론하고, Qwen3-4B 디코드 경로를 실행해 보세요. 저자들은 RTX 5090에서 batch-1 디코드 기준 171 generated tokens/s를 보고했습니다 . 이를 A100이나 RTX 4090에서 돌린 뒤 vllm>=0.8.4 기준선과 tok/s를 비교하세요 . 진짜 신호는 헤드라인 숫자가 아니라 그 차이의 크기, 혹은 차이가 없다는 사실입니다.

자주 묻는 질문

cuTile Rust를 실행하려면 어떤 NVIDIA GPU가 필요한가요?

compute capability sm_80(Ampere) 이상인 NVIDIA GPU가 필요하며, CUDA 13.3, Rust 1.89 이상, Linux(테스트 환경은 Ubuntu 24.04)도 필요합니다 . 이 기준에는 RTX 3000/4000/5000 시리즈, A100, H100, B200이 포함되지만 Volta(V100)와 Turing(RTX 2000)은 제외됩니다. 드라이버 측면에서 CUDA 13.3 GA는 Linux 드라이버 610.43.02 이상에 해당합니다 .

cuTile Rust는 런타임 락 없이 어떻게 데이터 레이스를 막나요?

이 보장을 컴파일 타임으로 옮깁니다. 가변 출력 텐서는 디스패치 전에 호스트에서 서로 겹치지 않음이 증명 가능한 타일로 분할되고, 각 타일 프로그램은 자기 슬라이스에 대한 독점 &mut 뷰를 받으며 입력은 공유 & 참조로 전달됩니다 . 파티션이 alias될 수 없기 때문에, 하나의 가변 참조 또는 여러 개의 불변 참조만 허용하는 Rust borrow checker가 충돌하는 쓰기를 정적으로 배제합니다 . 런타임 동기화 원시 연산은 삽입되지 않습니다. 커널의 의미론은 단일 스레드이지만, 컴파일 결과는 대규모 병렬 GPU 코드가 됩니다.

cuTile Rust는 프로덕션에 바로 쓸 수 있나요?

아직은 아닙니다. 저자들은 이를 초기 단계로 설명하므로, Tensor<f32, {[B]}> const-generic shape 문법과 매크로 형태를 포함한 API 표면은 바뀔 수 있습니다 . CUDA/Linux 전용(sm_80 이상, CUDA 13.3)이며, multi-batch 처리량, prefill, Qwen3를 넘어선 더 넓은 모델 지원 범위는 아직 특성이 충분히 밝혀지지 않았습니다. Grout는 유용한 참고 호출부이지만, vLLM이나 SGLang 같은 성숙한 스택을 대체하기 전에 목표 GPU, 드라이버, 모델, 배치 크기, graph-capture 동작을 반드시 검증해야 합니다.

cuTile Rust는 AMD GPU나 Apple Silicon에서도 작동하나요?

아니요. cuTile Rust는 NVIDIA 하드웨어(sm_80 이상)만 대상으로 하는 CUDA Tile IR을 통해 JIT 컴파일되며, 2026년 6월 기준 ROCm, Metal, WebGPU 백엔드는 없습니다 . 이식 가능한 Rust-on-GPU 생태계인 Rust GPU와 wgpu는 AMD와 Apple Silicon까지 도달하지만, cuTile의 launch 간 ownership 모델을 가져가는 방식이 아니라 CUDA와 다른 접근을 취합니다.

RTX 5090에서 Grout의 171 tok/s는 vLLM과 비교해 어떤가요?

저자들은 RTX 5090에서 Qwen3-4B batch-1 디코드 기준 171 generated tokens/s, B200에서 Qwen3-32B 기준 82 tokens/s를 보고했으며, 두 결과가 vLLM 및 SGLang과 경쟁력 있고 memory-bound 디코딩에서 HBM roofline에 가깝다고 설명합니다 . 이는 저자들의 자체 측정값으로 보아야 하며, 독립 재현 결과는 아직 공개되지 않았습니다. 자체 기준선을 잡을 때 Qwen은 vllm>=0.8.4 또는 sglang>=0.4.6.post1을 권장합니다 .