gpumode · 강의 아카이브
《GPU Mode》 L094 2025 · 후반 High priority transcript · failed · TVM repo + Tianqi 의 글 보강

tvm-ffi — 한 wheel 로 여러 framework 를 지원하는 ABI

FlashInfer · TileLang · cuteDSL — kernel 라이브러리가 많아질수록 같은 코드를 PyTorch · JAX · PaddlePaddle 에 어떻게 함께 배포할 것인가가 새 문제. tvm-ffi 는 framework-agnostic 한 minimal C ABI 를 제공해 “하나의 wheel 이 모든 framework 와 모든 Python 버전을 지원” 하는 길을 깐다. Apache TVM 기반 최소 runtime 의 디자인 결정을 Tianqi Chen 이 GPU Mode 청중에게 옮긴 강의. transcript 가 실패해서 본 노트는 repo README 와 일반 FFI 도메인 지식 기반으로 재구성.

tvm-ffi PackedFunc DLPack C ABI PyTorch FFI JAX FlashInfer TileLang
T
Speaker
Tianqi Chen
CMU · Apache TVM 의 founder · OctoML · MLC LLM · XGBoost 의 author
강의 번호
L094
스피커
Tianqi Chen
학습 우선순위
High · 정독
자막 상태
failed · README 보강
§ 01왜 새 FFI 가 필요했나· why this lecture exists

“같은 kernel 코드를 PyTorch · JAX · Paddle 에 배포하려면 wheel 을 몇 개 빌드해야 하는가”

강의의 출발점은 kernel 라이브러리 저자의 실제 고통. 한 라이브러리가 PyTorch wheel · JAX plugin · Paddle 확장을 따로 만들고, 거기에 Python 3.9–3.13 까지 곱하면 wheel 매트릭스가 폭발한다. 같은 CUDA kernel 인데.

강의의 frame

“ML system 의 다음 병목은 알고리즘이 아니라 유통이다. 같은 kernel 을 모든 framework / 모든 Python 버전 / 모든 OS 에 안정적으로 보내는 길이 없으면 — kernel 이 빨라도 못 쓴다.”

Tianqi 의 진단 — 현존 FFI 의 두 큰 부류 가 다 이 문제를 풀지 못한다.

Framework-specific FFI
  • PyTorch C++ extension, JAX custom call, Paddle op
  • 같은 kernel 을 각 framework 별로 wrap. 코드 중복 + 빌드 매트릭스 폭발
  • framework 의 ABI 가 바뀌면 다 깨짐
  • Python 버전마다 wheel 새로 빌드
Heavy interop layer
  • pybind11, cffi, Cython
  • language interop 은 풀지만 — runtime 표준이 아님
  • tensor 같은 ML primitive 가 first-class 가 아님
  • data passing 시 copy 가 자주 일어남

tvm-ffi 의 위치는 그 사이 — 최소 C ABI로 framework 사이의 wrapper 비용을 없애면서, tensor 가 first-class인 ML 친화적 디자인.

“하나의 wheel 이 모든 framework 와 모든 Python 버전을 지원한다 — 그게 가능한가? 그렇다, FFI 가 충분히 작으면.”학습 노트
§ 02TVM 의 FFI 동기· historical motivation

10년 동안 다듬어진 PackedFunc 의 정신을 별도 패키지로

tvm-ffi 는 새로 만든 게 아니다. Apache TVM 안에서 10년 동안 써온 PackedFunc 메커니즘을 — TVM 본체와 분리해 — 독립 패키지로 추출한 것. “TVM 을 안 써도 PackedFunc 만 쓸 수 있게”가 핵심.

왜 TVM 에서 분리했나

TVM 은 “compiler + runtime” 의 큰 패키지. kernel 라이브러리는 compiler 가 필요 없다 — runtime 의 작은 부분만 있으면 됨. tvm-ffi 는 그 작은 부분을 독립 minimum 으로 패키지화. dependency 가 가벼워지면서 채택 비용이 떨어진다.

§ 03lightweight runtime — 작은 표면적· minimal C ABI

의도적으로 작은 ABI — “수십 개의 함수 시그니처 만으로 모든 것을”

tvm-ffi 의 디자인 핵심은 의도적인 작음. C ABI 가 작아야 — 모든 컴파일러, 모든 OS, 모든 Python 버전에서 안정적. 큰 ABI 는 한 군데가 깨지면 전체가 깨진다.

L1 · TVMValue union 값 — int64, float64, void*, TVMObjectHandle. 모든 함수 호출의 인자/반환은 이 union 한 종류로 표현. type erasure 의 가장 작은 형태.
L2 · TVMArgValue TVMValue + type code. type tag 가 함께. wrapping 비용 거의 없음.
L3 · DLPack tensor tensor 는 별도 standard DLManagedTensor 로. zero-copy 교환이 핵심 — PyTorch 의 tensor 가 그대로 JAX 로 (메타데이터만 복사).
L4 · PackedFunc 함수 호출의 단일 시그니처 — void (*)(TVMValue* args, int* type_codes, int num_args, TVMValue* ret, ...). 모든 함수가 이 한 형태.
L5 · Object system refcounted handle. tensor 외 다른 ML primitive (string, list, dict) 도 같은 방식으로.
L6 · 멀티언어 binding Python · C++ · Rust 가 같은 ABI 위에 layered. 새 언어 추가는 binding layer 만 작성.
왜 union + type code 인가

이 디자인은 ABI 안정성의 비결. 새 type 을 추가해도 — type code 하나가 늘 뿐, 구조체 layout 은 그대로. “이미 빌드된 wheel 이 새 버전에서도 그대로 동작” 의 기반.

§ 04multi-language interop· Python · C++ · Rust

같은 함수가 Python 에서 부르건 Rust 에서 부르건 — 같은 호출 비용

tvm-ffi 의 또 다른 핵심 — language-agnostic. 함수의 진짜 구현은 C++ 또는 Rust 에 있고, Python 은 thin wrapper. 그러나 그 반대도 가능 — Python 함수를 C++ 에서 호출.

# Python 측 — kernel 라이브러리 함수 호출
import tvm_ffi
import torch

flash = tvm_ffi.load_module("libflashinfer.so")

q = torch.randn(1, 8, 1024, 128, device="cuda")
k = torch.randn(1, 8, 1024, 128, device="cuda")
v = torch.randn(1, 8, 1024, 128, device="cuda")

# tvm_ffi 가 torch tensor 를 DLPack 으로 view → kernel 로
out = flash.attention(q, k, v)
# out 도 torch tensor 처럼 보임 (DLPack import)
// C++ 측 — kernel 함수 정의
#include "tvm/ffi/function.h"

void attention(NDArray q, NDArray k, NDArray v,
               NDArray out) {
  // q.data, q.shape, q.strides 직접 접근
  // CUDA kernel 호출
  launch_attention_kernel(q, k, v, out);
}

// 등록 — 한 줄
TVM_FFI_REGISTER_GLOBAL("flashinfer.attention")
    .set_body_typed(attention);
호출 비용

Python → C++ kernel 의 호출 overhead 가 일반 pybind 보다 낮다. 그 이유 — pybind 는 매 호출마다 type 변환 코드가 도는데, tvm-ffi 는 union + type code 의 단일 dispatch. 특히 작은 kernel 을 자주 부르는 LLM serving 에서 차이가 누적. (확인 필요 — 정확한 비율)

그리고 Rust binding 의 의의 — Rust 로 짠 kernel 라이브러리가 Python 에서 그대로 호출 가능. 새 시스템 언어 (Rust, Mojo, Zig) 가 ML kernel 영역에 들어오는 길이 자연스럽게 열림.

§ 05PackedFunc 추상· core abstraction

모든 함수가 같은 시그니처 — 그 작은 결정이 큰 결과를 만든다

PackedFunc 는 tvm-ffi 의 핵심 추상. 모든 함수가 같은 C 시그니처를 가진다. 인자가 5개든 0개든, 반환값이 tensor 든 string 이든. 호출 시점에 type code 로 dispatch.

이 결정이 만들어내는 4가지 결과.

  1. ABI 안정성 — 함수 시그니처가 바뀔 일이 없음. 새 함수 추가 = 같은 시그니처의 새 entry. 기존 ABI 깨지지 않음.
  2. generic dispatch — 한 dispatcher 가 모든 함수를. 새 함수 등록 시 dispatcher 코드 안 바꿈.
  3. language bridge 의 단순함 — 새 언어 추가 시 “이 한 시그니처를 어떻게 호출할 것인가” 만 풀면 됨.
  4. introspection — 모든 함수의 인자/반환 type code 를 runtime 에 검사 가능. type-safe 호출이 dynamic 하게 보장.
// PackedFunc 의 C 시그니처 (단순화)
typedef int (*PackedCFunc)(
    TVMValue* args,
    int* type_codes,
    int num_args,
    TVMValue* ret,
    int* ret_type_code,
    void* resource_handle
);

// 모든 함수가 이 한 형태
// add(a, b), attention(q, k, v), tokenize(s) — 다 같은 시그니처
# Python 측 typed 호출 — 외피는 익숙한 형태
add: tvm_ffi.PackedFunc = registry.get("my.add")
result = add(3, 5)

# 내부적으로는
# [TVMValue(3), TVMValue(5)] + [int_code, int_code]
# → C 함수 호출 → ret 으로 결과 반환
“같은 시그니처 안에 모든 함수를 욱여넣는다 — 이게 dispatch 비용을 낮추고 ABI 를 안정화한다.”학습 노트

그리고 PackedFunc 와 함께 다니는 추상 — Object. tensor 외 string · list · dict · module 등 다양한 ML primitive 가 같은 refcounted handle 위에 올라간다. PyTorch 의 tensor 든 numpy 의 ndarray 든 — DLPack 으로 같은 PackedFunc 를 통과.

§ 06채택 사례· FlashInfer · TileLang · cuteDSL

같은 wheel 이 PyTorch 와 JAX 양쪽에서 도는 풍경

repo README 가 자랑스럽게 보여주는 채택 사례. tvm-ffi 가 풀려는 문제가 가짜가 아닌, 실제 라이브러리 저자들이 마주친 문제임을 증명.

실제 ergonomic 의 차이

FlashInfer 같은 라이브러리가 PyTorch 의 매 minor 버전 release 마다 wheel 을 새로 빌드해야 했던 시절이 있었다. tvm-ffi 채택 후 — 한 번 빌드한 wheel 이 PyTorch 2.0–2.6 에서 그대로 동작. 라이브러리 저자의 인지 부담이 한 자리수 줄어든다. (확인 필요 — 정확한 호환 범위)

§ 07PyTorch FFI 와의 비교· positioning

같은 일을 다른 가정 위에서

차원
PyTorch C++ ext
pybind11
tvm-ffi
scope
PyTorch 한정
Python ↔ C++ generic
framework-agnostic ML
tensor 1급 시민
torch::Tensor
아님 (numpy 외부)
DLPack standard
ABI 안정성
PyTorch 버전 결합
Python ABI 결합
자체 stable C ABI
Python 버전
매 버전 wheel
매 버전 wheel
한 wheel 다 지원
멀티 framework
불가능
framework 의존 안 함
native 지원
호출 비용
중간
중간 ~ 높음
낮음 (single dispatch)
학습 곡선
PyTorch 사용자 친숙
C++ 익숙해야
새 추상 학습 필요

각자가 자기 자리가 있다. 한 framework 에서 한 op 만 추가는 PyTorch ext 가 빠르고 쉬움. generic Python ↔ C++ 는 pybind. 여러 framework 에 같은 kernel 을 배포는 tvm-ffi. 강의의 메시지는 “하나가 다른 둘을 대체한다” 가 아니라 “각자의 자리가 있다”.

언제 tvm-ffi 를 채택할 가치

1. 두 개 이상의 framework 를 지원해야 할 때. 2. 한번 빌드한 wheel 이 오래 살기를 원할 때. 3. Python 외 다른 언어 (Rust, C++) 사용자가 있을 때. 셋 중 하나라도 강하면 채택 가치. 아니면 PyTorch ext 가 더 단순.

§ 08한계 — RFC 단계의 제약· limitations

v0.1 의 솔직함 — 아직 완성 아님

repo README 가 명시 — v0.1.0 이후 RFC stage. C ABI 안정성을 우선시하지만 일부는 여전히 변경 가능. 0.X.Y 의 버전 표기 자체가 “X 가 breaking change” 라고 선언.

디자인 의도와 한계의 trade-off

tvm-ffi 의 한계는 거의 다 “작게 유지하기” 의 비용. 큰 ABI 면 ergonomic 이 좋지만 안정성이 무너진다. 작은 ABI 가 안정성을 주는 대가로 ergonomic 이 거칠다. 1.0 이후 ergonomic 을 외부 라이브러리(예: pybind11-style wrapper)로 layered 시킬 가능성.

§ 09다음 — stable ABI 의 미래· future

Python 의 stable ABI 처럼 — ML 의 stable ABI

강의에서 Tianqi 가 짚는 야망. CPython 의 limited API 가 “한 wheel 이 여러 Python 버전을 지원” 을 가능케 한 것처럼 — tvm-ffi 가 ML 영역의 limited API가 되는 길.

§ 10기억할 메모와 실습· key takeaways

다시 열었을 때 5분 안에 잡혀야 할 것

PackedFunc
모든 함수가 같은 C 시그니처. union + type code 로 dispatch. ABI 안정성의 핵심.
DLPack tensor
framework 사이 zero-copy tensor 교환의 표준. PyTorch · JAX · Paddle · Numpy/Cupy 모두 native.
한 wheel · 모든 framework
PyTorch 2.x, JAX, Paddle 의 minor 버전 변화에 wheel 을 새로 안 빌드. 라이브러리 저자의 인지 부담 절감.
multi-language
Python · C++ · Rust 가 같은 ABI 위에. 새 언어 추가는 binding layer 만.
호출 비용 낮음
single dispatch — pybind 보다 작은 함수 호출에서 빠름. LLM serving 에서 누적 효과.
RFC stage v0.1
아직 ABI freeze 전. 1.0 이전 minor 버전 사이 binary 호환 안 보장.
FlashInfer · TileLang · cuteDSL
실제 채택 사례. 추상이 가짜가 아니라 라이브러리 저자가 실제로 골랐음의 증거.
PyTorch ext 의 대체 아님
한 framework 한 op 추가는 PyTorch ext 가 더 빠름. tvm-ffi 는 multi-framework 일 때.

실습 시퀀스

  1. tvm-ffi hello world — repo example 의 첫 “add” 함수. C++ 로 짜고 등록, Python 에서 호출. 빌드 매트릭스 확인.
  2. tensor 통과 — torch.Tensor 를 인자로 받는 C++ 함수. DLPack import/export 가 어떻게 일어나는지 코드에서.
  3. 같은 wheel JAX 에서 호출 — 같은 빌드된 .so 를 JAX 에서 import. 둘 다 동작 확인.
  4. FlashInfer attention 호출 — 실제 채택 사례 코드 읽기. tvm-ffi 가 어떻게 wrap 되는지 패턴 학습.
  5. Rust binding 시도 — Rust crate 로 같은 함수 호출. cross-language 가 실제로 어떻게 도는지 손에 잡아 보기.
TVMtvm.apache.org · 본가 프로젝트
§ 11다른 강의로 이어지는 길· connections

compiler / FFI 시리즈 안에서

§ 12열린 질문· open questions

다음에 다시 들었을 때 직접 검증해야 할 것들

검증 메모

본 노트의 PackedFunc C 시그니처 코드는 TVM 의 일반적 형태를 단순화한 것. 정확한 시그니처는 tvm-ffi 의 include/tvm/ffi/c_api.h 에서 확인. 강의에서 보여준 정확한 예시는 영상 직접 확인 필요.

← Lecture 093Cornserve Lecture 095 →Single controller programming with Monarch