Rust运行时插件库cubecl-runtime的使用,cubecl-runtime提供高性能运行时扩展与模块化支持

以下是关于Rust运行时插件库cubecl-runtime的使用内容:

CubeCL Logo

CubeCL是一个多平台高性能计算语言扩展库,为Rust提供运行时扩展与模块化支持。它允许您使用Rust编写GPU计算程序,利用零成本抽象来开发可维护、灵活且高效的计算内核。

支持平台

平台 运行时 编译器 硬件
WebGPU wgpu WGSL 大多数GPU
CUDA CUDA C++ (CUDA) NVIDIA GPU
ROCm HIP C++ (HIP) AMD GPU
Metal wgpu C++ (Metal) Apple GPU
Vulkan wgpu SPIR-V Linux/Windows上的大多数GPU

示例代码

内容中提供的示例展示了如何使用CubeCL编写GELU激活函数:

use cubecl::prelude::*;

#[cube(launch_unchecked)]
/// 一个[Line]代表可以执行SIMD操作的连续元素序列
/// 运行时会在可能时自动使用SIMD指令以提升性能
fn gelu_array<F: Float>(input: &Array<Line<F>>, output: &mut Array<Line<F>>) {
    if ABSOLUTE_POS < input.len() {
        output[ABSLUTE_POS] = gelu_scalar(input[ABSOLUTE_POS]);
    }
}

#[cube]
fn gelu_scalar<F: Float>(x: Line<F>) -> Line<F> {
    // 在编译时执行sqrt函数
    let sqrt2 = F::new(comptime!(2.0f32.sqrt()));
    let tmp = x / Line::new(sqrt2);

    x * (Line::erf(tmp) + 1.0) / 2.0
}

然后可以调用自动生成的gelu_array::launch_unchecked函数来启动内核:

pub fn launch<R: Runtime>(device: &R::Device) {
    let client = R::client(device);
    let input = &[-1., 0., 1., 5.];
    let vectorization = 4;
    let output_handle = client.empty(input.len() * core::mem::size_of::<f32>());
    let input_handle = client.create(f32::as_bytes(input));

    unsafe {
        gelu_array::launch_unchecked::<f32, R>(
            &client,
            CubeCount::Static(1, 1, 1),
            CubeDim::new(input.len() as u32 / vectorization, 1, 1),
            ArrayArg::from_raw_parts::<f32>(&input_handle, input.len(), vectorization as u8),
            ArrayArg::from_raw_parts::<f32>(&output_handle, input.len(), vectorization as u8),
        )
    };

    let bytes = client.read_one(output_handle.binding());
    let output = f32::from_bytes(&bytes);

    println!("Executed gelu with runtime {:?} => {output:?}", R::name());
}

要运行这个GELU示例,可以使用以下命令:

cargo run --example gelu --features cuda  # cuda运行时
cargo run --example gelu --features wgpu  # wgpu运行时

完整示例Demo

以下是一个完整的示例,展示如何使用cubecl-runtime:

use cubecl::prelude::*;

#[cube(launch_unchecked)]
fn vector_add<F: Float>(a: &Array<Line<F>>, b: &Array<Line<F>>, c: &mut Array<Line<F>>) {
    if ABSOLUTE_POS < a.len() {
        c[ABSOLUTE_POS] = a[ABSOLUTE_POS] + b[ABSOLUTE_POS];
    }
}

fn main() {
    // 初始化运行时
    let device = cubecl::wgpu::Device::default();
    
    // 创建客户端
    let client = cubecl::wgpu::client(&device);
    
    // 准备输入数据
    let a_data = vec![1.0f32, 2.0, 3.0, 4.0];
    let b_data = vec![5.0f32, 6.0, 7.0, 8.0];
    
    // 创建GPU缓冲区
    let a_handle = client.create(f32::as_bytes(&a_data));
    let b_handle = client.create(f32::as_bytes(&b_data));
    let c_handle = client.empty(a_data.len() * std::mem::size_of::<f32>());
    
    // 启动内核
    unsafe {
        vector_add::launch_unchecked::<f32, cubecl::wgpu>(
            &client,
            CubeCount::Static(1, 1, 1),
            CubeDim::new(a_data.len() as u32, 1, 1),
            ArrayArg::from_raw_parts::<f32>(&a_handle, a_data.len(), 1),
            ArrayArg::from_raw_parts::<f32>(&b_handle, b_data.len(), 1),
            ArrayArg::from_raw_parts::<f32>(&c_handle, a_data.len(), 1),
        );
    }
    
    // 读取结果
    let result_bytes = client.read_one(c_handle.binding());
    let result = f32::from_bytes(&result_bytes);
    
    println!("Result: {:?}", result); // 应该输出 [6.0, 8.0, 10.0, 12.0]
}

工作原理

CubeCL利用Rust的proc宏系统进行两阶段处理:

  1. 解析:proc宏使用syn crate解析GPU内核代码
  2. 扩展:宏生成一个新的Rust函数,该函数在调用时创建中间表示(IR)

设计

CubeCL围绕立方体(Cubes)设计,使用3D表示来映射硬件架构:

CubeCL拓扑

特殊功能

  1. 自动向量化:可以指定输入变量的向量化因子,运行时能编译内核并使用最佳指令
  2. 编译时计算(Comptime):允许在运行时首次编译内核时修改编译器IR
  3. 自动调优:运行时运行小型基准测试以确定最佳内核配置

CubeCL目前处于alpha阶段,是Burn深度学习框架的一部分。


1 回复

cubecl-runtime: Rust高性能运行时插件库

介绍

cubecl-runtime 是一个为Rust程序提供高性能运行时扩展和模块化支持的库。它允许开发者在运行时动态加载和卸载功能模块,而无需重新编译主程序,非常适合需要插件系统或热更新功能的应用程序。

主要特性:

  • 高性能的模块加载和执行
  • 安全的模块隔离机制
  • 简单的API设计
  • 支持跨平台动态链接库加载
  • 模块间通信支持

完整示例Demo

1. 创建插件项目

首先创建一个插件项目,结构如下:

// 插件项目的Cargo.toml
[package]
name = "my-plugin"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]  # 必须设置为cdylib才能作为动态库使用

[dependencies]
cubecl-runtime = "0.1"
// src/lib.rs
use cubecl_runtime::export_module;

#[export_module]
pub mod plugin {
    pub fn square(x: i32) -> i32 {
        x * x
    }
    
    pub fn greet(name: &str) -> String {
        format!("Hello, {}!", name)
    }
    
    pub fn sum_points(p1: Point, p2: Point) -> Point {
        Point {
            x: p1.x + p2.x,
            y: p1.y + p2.y
        }
    }
}

// 共享数据结构
#[repr(C)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

2. 创建主程序项目

// 主程序的Cargo.toml
[package]
name = "main-app"
version = "0.1.0"

[dependencies]
cubecl-runtime = "0.1"
my-plugin = { path = "../my-plugin" }  # 指向插件项目路径
// src/main.rs
use cubecl_runtime::{Runtime, Module};
use my_plugin::Point;  // 使用共享数据结构

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建运行时环境
    let mut runtime = Runtime::new();
    
    // 加载插件模块
    #[cfg(target_os = "windows")]
    let module_path = "target/debug/my_plugin.dll";
    #[cfg(target_os = "linux")]
    let module_path = "target/debug/libmy_plugin.so";
    #[cfg(target_os = "macos")]
    let module_path = "target/debug/libmy_plugin.dylib";
    
    let module = runtime.load_module(module_path)?;
    
    // 调用平方函数
    if let Some(square) = module.get_function::<fn(i32) -> i32>("square")? {
        println!("5的平方是: {}", square(5));  // 输出: 25
    }
    
    // 调用问候函数
    if let Some(greet) = module.get_function::<fn(&str) -> String>("greet")? {
        let message = greet("World");
        println!("{}", message);  // 输出: Hello, World!
    }
    
    // 使用共享数据结构
    if let Some(sum_points) = module.get_function::<fn(Point, Point) -> Point>("sum_points")? {
        let p1 = Point { x: 1.0, y: 2.0 };
        let p2 = Point { x: 3.0, y: 4.0 };
        let result = sum_points(p1, p2);
        println!("点相加结果: ({}, {})", result.x, result.y);  // 输出: (4.0, 6.0)
    }
    
    // 显式卸载模块(可选)
    runtime.unload_module(module);
    
    Ok(())
}

3. 构建和运行步骤

  1. 先构建插件:
cd my-plugin
cargo build
  1. 然后构建并运行主程序:
cd main-app
cargo run

注意事项

  1. 确保主程序和插件使用相同的Rust版本编译
  2. 跨平台时注意动态库的后缀(.dll/.so/.dylib)
  3. 复杂的泛型类型可能无法在模块边界使用
  4. 插件和主程序需要共享的数据结构要使用#[repr©]标记
  5. 实际项目中要考虑错误处理和模块生命周期管理

这个完整示例展示了如何使用cubecl-runtime创建插件系统,包括函数调用、字符串处理和共享数据结构的使用。

回到顶部