TileLang Kernel 编译与执行流程

1. 概述

本文档详细介绍了 TileLang 中从 Kernel 定义到编译为目标设备二进制代码,再到最终执行的完整流程。该流程主要包括以下几个关键阶段:

  1. Kernel 定义与装饰
  2. JIT 编译触发
  3. 中间表示(IR)生成
  4. IR 优化与代码生成
  5. 二进制代码编译
  6. 执行与结果返回

2. 详细流程分析

2.1 Kernel 定义与装饰

2.1.1 用户定义 Kernel

用户通过 TileLang 提供的 API 定义计算 Kernel,通常使用 @tilelang.jit 装饰器来标记需要 JIT 编译的函数:

import tilelang as tl

@tl.jit
@tl.prim_func
def matmul(A: tl.tensor, B: tl.tensor, C: tl.tensor):
    # Kernel 实现代码
    ...

2.1.2 JIT 装饰器实现

@tilelang.jit 装饰器的实现位于 /tilelang/tilelang/jit/__init__.py 中,它是整个编译流程的入口点。

装饰器主要完成以下工作:

  • 解析用户提供的编译配置参数(如目标设备、执行后端等)
  • 创建 JITImpl 实例,封装用户函数和编译配置
  • 返回一个可调用对象,替代原始函数

关键代码:

def jit(func: Callable[_P, _T] | PrimFunc | None = None, *, ...):
    def decorator(func: Callable[_P, _T]) -> JITImpl[_P, _T]:
        if isinstance(func, (PrimFunc, PrimFuncCreater)):
            orig_func = func.orig_func
        else:
            orig_func = func
        return JITImpl(
            func=func,
            out_idx=out_idx,
            execution_backend=execution_backend,
            target=target,
            target_host=target_host,
            verbose=verbose,
            pass_configs=pass_configs,
            debug_root_path=debug_root_path,
            compile_flags=compile_flags,
            func_source=inspect.getsource(orig_func),
            signature=inspect.signature(orig_func),
            lazy_jit=False,
        )

    if func is not None:
        return decorator(func)
    else:
        return decorator

2.2 JIT 编译触发

当用户调用被装饰的函数时,JITImpl.__call__ 方法会被触发,开始 JIT 编译流程:

def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _Ret:
    # 解析缓存键
    key = self.parse_cache_key(*args, **kwargs)
    
    # 检查缓存中是否已有编译结果
    kernel = self._kernel_cache.get(key, None)
    if kernel is None:
        # 编译函数
        kernel = self.compile(*args, **kwargs)
        # 缓存编译结果
        self._kernel_cache[key] = kernel
    
    # 执行并返回结果
    return kernel

2.3 中间表示(IR)生成

compile 方法中,首先调用 get_tir 方法获取 TIR(Tensor Intermediate Representation)格式的 PrimFunc:

def compile(self, *args: _P.args, **kwargs: _P.kwargs) -> _Ret:
    # 获取 TIR PrimFunc
    func = self.get_tir(*args, **kwargs)
    # 编译 PrimFunc
    kernel_result = compile(
        func,
        out_idx=self.out_idx,
        execution_backend=self.execution_backend,
        target=self.target,
        target_host=self.target_host,
        verbose=self.verbose,
        pass_configs=self.pass_configs,
        compile_flags=self.compile_flags,
    )
    # 返回编译结果
    return kernel_result

get_tir 方法根据函数类型生成或返回 TIR PrimFunc:

def get_tir(self, *args: _P.args, **kwargs: _P.kwargs) -> PrimFunc[_KP, _T]:
    if isinstance(self.func, PrimFuncCreater):
        # 如果是 PrimFunc 创建器,则调用生成 PrimFunc
        tir = self.func(*args, **kwargs)
    elif isinstance(self.func, PrimFunc):
        # 如果已经是 PrimFunc,则直接返回
        tir = self.func
    elif callable(self.func):
        # 如果是可调用对象,则调用生成 PrimFunc
        tir = self.func(*args, **kwargs)
    else:
        raise ValueError(f"Invalid function type: {type(self.func)}")
    assert isinstance(tir, PrimFunc), f"target function must be a PrimFunc but got {type(tir)}"
    return tir

2.4 IR 优化与代码生成

2.4.1 compile 函数调用

compile 函数(位于 tilelang/tilelang/jit/__init__.py)是编译的核心入口,它调用 tilelang.lower 来生成编译产物:

def compile(func: PrimFunc[_KP, _T] = None, ...) -> JITKernel[_KP, _T]:
    # 解析执行后端
    execution_backend = resolve_execution_backend(requested_backend, target)
    
    # 缓存编译结果
    return cached(
        func=func,
        out_idx=out_idx,
        execution_backend=execution_backend,
        target=target,
        target_host=target_host,
        verbose=verbose,
        pass_configs=pass_configs,
        compile_flags=compile_flags,
    )

2.4.2 tilelang.lower 调用

tilelang.lower 函数(位于 tilelang/tilelang/engine/lower.py)是 IR 优化和代码生成的核心函数:

def lower(func_or_mod: tir.PrimFunc | tvm.IRModule, ...) -> CompiledArtifact:
    # 将函数转换为 IRModule
    if isinstance(func_or_mod, tir.PrimFunc):
        func = func_or_mod
        params = extrac_params(func) if not runtime_only else None
        mod = tvm.IRModule({func.attrs["global_symbol"]: func})
    
    # 确定编译目标
    if isinstance(target, str):
        target = determine_target(target)
    
    # 规范化目标主机
    target_host = canon_target_host(target, target_host)
    
    # 语义检查
    PreLowerSemanticCheck(mod)
    
    # 阶段 1: IR 降低与合法化
    mod = LowerAndLegalize(mod, target)
    
    # 阶段 2: 针对目标设备优化 IR
    mod = OptimizeForTarget(mod, target)
    
    # 分离主机和设备代码
    host_mod = tir.transform.Filter(_is_host_call)(mod)
    device_mod = tir.transform.Filter(_is_device_call)(mod)
    
    # 设备代码生成
    codegen_mod = device_codegen(device_mod, target) if enable_device_compile else device_codegen_without_compile(device_mod, target)
    
    # 主机代码生成
    if enable_host_codegen:
        host_mod = host_codegen(host_mod, target_host)
        host_mod.import_module(codegen_mod)
        return CompiledArtifact(host_mod, device_mod, params, codegen_mod.inspect_source(), rt_mod=host_mod)
    
    return CompiledArtifact(host_mod, device_mod, params, codegen_mod.inspect_source())

2.4.3 host_codegen 函数

host_codegen 函数(位于 tilelang/tilelang/engine/lower.py)负责主机端代码的优化和生成:

def host_codegen(host_mod: tvm.IRModule, target_host: Target) -> tvm.IRModule:
    # 将目标主机信息绑定到 IRModule 上
    host_mod = tir.transform.BindTarget(target_host)(host_mod)
    # 对 FP8 存储格式进行合法化转换
    host_mod = tir.transform.FP8StorageLegalize()(host_mod)
    # 对 BF16 存储格式进行合法化转换
    host_mod = tir.transform.BF16StorageLegalize()(host_mod)
    # 降低 TVM 内建函数调用为更底层的表示
    host_mod = tir.transform.LowerTVMBuiltin()(host_mod)
    # 降低自定义数据类型
    host_mod = tir.transform.LowerCustomDatatypes()(host_mod)
    # 应用 tilelang 特有的内联函数降低
    host_mod = tilelang.transform.LowerIntrin()(host_mod)
    # 降低设备存储访问信息
    host_mod = tilelang.transform.LowerDeviceStorageAccessInfo()(host_mod)
    # 合并上下文调用
    host_mod = tir.transform.CombineContextCall()(host_mod)
    # 根据目标主机类型选择对应的构建函数
    if target_host.kind.name == "llvm":
        host_mod = tvm.ffi.get_global_func("target.build.llvm")(host_mod, target_host)
    elif target_host.kind.name == "c":
        host_mod = tvm.ffi.get_global_func("target.build.tilelang_c")(host_mod, target_host)
    else:
        raise ValueError(f"Target host {target_host.kind.name} is not supported")
    return host_mod

2.5 二进制代码编译

_compile_and_create_adapter 方法(位于 tilelang/tilelang/jit/kernel.py)中,根据执行后端创建相应的 Kernel 适配器:

def _compile_and_create_adapter(self, tilelang_func: PrimFunc, out_idx: list[int]) -> BaseKernelAdapter:
    # 编译函数,生成 CompiledArtifact
    enable_host_codegen = execution_backend == "tvm_ffi"
    enable_device_compile = execution_backend == "tvm_ffi"
    with tvm.transform.PassContext(opt_level=3, config=pass_configs), self.target:
        artifact = tilelang.lower(
            tilelang_func,
            target=target,
            target_host=target_host,
            enable_host_codegen=enable_host_codegen,
            enable_device_compile=enable_device_compile,
        )
    
    # 创建适配器
    if execution_backend == "tvm_ffi":
        # 使用 TVMFFI 后端
        adapter = TVMFFIKernelAdapter(
            params=artifact.params,
            result_idx=out_idx,
            target=target,
            func_or_mod=tilelang_func,
            host_mod=artifact.host_mod,
            device_mod=artifact.device_mod,
            rt_mod=artifact.rt_mod,
            device_kernel_source=artifact.kernel_source,
            verbose=verbose,
            pass_configs=pass_configs,
            compile_flags=compile_flags,
        )
    elif execution_backend == "ctypes":
        # 使用 CTypes 后端
        ...
    elif execution_backend == "cython":
        # 使用 Cython 后端
        ...
    
    return adapter

2.6 执行与结果返回

编译完成后,生成的 Kernel 适配器会被缓存,当用户再次调用相同的 Kernel 时,可以直接使用缓存的适配器执行,无需重新编译。

执行过程由 Kernel 适配器的 func 方法处理,根据不同的执行后端,将参数传递给编译好的二进制代码,并返回执行结果。

3. 关键组件与接口

3.1 JITImpl 类

JITImpl 类是 JIT 编译的核心组件,它封装了用户函数、编译配置和编译结果,提供了以下关键方法:

  • __call__: 触发 JIT 编译和执行
  • get_tir: 生成或获取 TIR PrimFunc
  • compile: 编译 PrimFunc 为可执行 Kernel
  • par_compile: 并行编译多个配置

3.2 CompiledArtifact 类

CompiledArtifact 类封装了编译结果,包括:

  • host_mod: 主机端 IRModule
  • device_mod: 设备端 IRModule
  • params: Kernel 参数信息
  • kernel_source: 生成的设备端源代码
  • rt_mod: 运行时模块(如果启用了主机代码生成)

3.3 Kernel 适配器

根据执行后端的不同,TileLang 提供了多种 Kernel 适配器:

  • TVMFFIKernelAdapter: 使用 TVM FFI 接口执行
  • CtypesKernelAdapter: 使用 CTypes 接口执行
  • CythonKernelAdapter: 使用 Cython 接口执行
  • NVRTCKernelAdapter: 使用 NVRTC 接口执行
  • TorchKernelAdapter: 使用 PyTorch 接口执行

4. 执行流程图

用户定义 Kernel
    │
    ▼
@tilelang.jit 装饰器
    │
    ▼
创建 JITImpl 实例
    │
    ▼
用户调用装饰后的函数
    │
    ▼
JITImpl.__call__()
    │
    ▼
JITImpl.get_tir() → 获取 TIR PrimFunc
    │
    ▼
JITImpl.compile()
    │
    ▼
compile() 函数
    │
    ▼
tilelang.lower()
    │
    ├───────────────────┐
    ▼                   ▼
阶段 1: IR 降低与合法化   阶段 2: 针对目标设备优化
    │                   │
    ▼                   ▼
分离主机和设备代码       分离主机和设备代码
    │                   │
    ▼                   ▼
host_mod             device_mod
    │                   │
    ▼                   ▼
如果 enable_host_codegen → host_codegen()
                        │
                        ▼
device_codegen() 或 device_codegen_without_compile()
                        │
                        ▼
codegen_mod
    │                   │
    └───────────────────┘
            │
            ▼
创建 CompiledArtifact
            │
            ▼
创建 Kernel 适配器
            │
            ▼
缓存并返回 Kernel 适配器
            │
            ▼
执行 Kernel 并返回结果

5. 优化与加速技术

5.1 编译缓存

TileLang 使用缓存机制避免重复编译相同的 Kernel,提高执行效率。缓存键通常基于以下因素:

  • Kernel 函数定义
  • 输入参数类型和形状
  • 编译目标设备
  • 编译配置参数

5.2 多后端支持

TileLang 支持多种执行后端,根据目标设备和使用场景选择最合适的后端:

  • tvm_ffi: 适用于 CUDA 等 GPU 设备
  • ctypes: 适用于通用 CPU 设备
  • cython: 适用于需要高性能的 CPU 设备
  • nvrtc: 适用于 NVIDIA GPU 设备
  • torch: 适用于与 PyTorch 集成的场景

5.3 并行编译

对于需要编译多个 Kernel 或同一 Kernel 的多个配置的场景,TileLang 提供了并行编译功能,可以显著提高编译效率:

def par_compile(funcs, ...) -> list[JITKernel]:
    with concurrent.futures.ThreadPoolExecutor(num_workers, "tl-par-comp") as executor:
        futures = []
        for func in funcs:
            future = executor.submit(compile, func, ...)
            futures.append(future)
        # 收集编译结果
        ...
    return results

6. 总结

TileLang 的 Kernel 编译与执行流程是一个复杂但高效的系统,它将高级的 TileLang 代码逐步转换为目标设备的二进制代码,并提供了多种优化和加速技术。该流程的设计充分考虑了灵活性和性能,支持多种目标设备和执行后端,可以满足不同场景的需求。

通过理解这个完整流程,用户可以更好地利用 TileLang 的功能,编写高效的计算 Kernel,并根据实际需求选择合适的编译配置和执行后端。

posted on 2025-12-18 14:28  Devin_Peng  阅读(193)  评论(0)    收藏  举报