Rust宏库cubecl-macros的使用:高效代码生成与过程宏开发指南

Rust宏库cubecl-macros的使用:高效代码生成与过程宏开发指南

CubeCL Logo

概述

CubeCL是一个多平台高性能计算语言扩展,用于在Rust中编写GPU程序,利用零成本抽象开发可维护、灵活且高效的计算内核。CubeCL还提供了优化运行时,用于管理内存和执行。

支持平台

平台 运行时 编译器 硬件
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宏库的完整示例:

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[ABSOLUTE_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::<f极客时间, 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);

    // 结果应该是[-0.1587, 0.0000, 0.8413, 5.0000]
    println!("Executed gelu with runtime {:?} => {output:?}", R::name());
}

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

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

设计原理

CubeCL围绕"立方体"概念设计,其拓扑结构如下:

CubeCL拓扑结构

特殊功能

自动向量化

高性能内核应尽可能使用SIMD指令,CubeCL允许你在启动内核时指定每个输入变量的向量化因子。

编译时计算(Comptime)

CubeCL不仅是一种新的计算语言,它允许你编写编译器插件并完全自定义。Comptime是一种在首次编译内核时修改编译器IR的方法。

自动调优(Autotuning)

自动调优通过运行小型基准测试来确定在当前硬件上运行的最佳内核和配置,大大简化了内核选择。

安装

在项目目录中运行以下Cargo命令:

cargo add cubecl-macros

或者在Cargo.toml中添加:

cubecl-macros = "0.6.0"

完整示例

// 引入必要的模块
use cubecl::prelude::*;

// 定义向量化的GELU激活函数数组操作
#[cube(launch_unchecked)]
fn gelu_array<F: Float>(input: &Array<Line<F>>, output: &mut Array<Line<F>>) {
    // 检查当前绝对位置是否在输入范围内
    if ABSOLUTE_POS < input.len() {
        // 对每个元素应用GELU函数
        output[ABSOLUTE_POS] = gelu_scalar(input[ABSOLUTE_POS]);
    }
}

// 定义标量GELU激活函数
#[cube]
fn gelu_scalar<F: Float>(x: Line<F>) -> Line<F> {
    // 编译时计算2的平方根
    let sqrt2 = F::new(comptime!(2.0f32.sqrt()));
    let tmp = x / Line::new(sqrt2);

    // GELU公式: x * (1 + erf(x/√2)) / 2
    x * (Line::erf(tmp) + 1.0) / 2.0
}

// 主函数
fn main() {
    // 初始化CUDA运行时
    let device = cubecl::cuda::Device::new(0).unwrap();
    
    // 准备输入数据
    let input = [-1.0f32, 0.0, 1.0, 5.0];
    let vectorization = 4; // 向量化因子
    
    // 创建客户端
    let client = cubecl::cuda::Client::new(&device);
    
    // 分配内存
    let output_handle = client.empty(input.len() * std::mem::size_of::<f32>());
    let input_handle = client.create(f32::as_bytes(&input));
    
    // 启动内核
    unsafe {
        gelu_array::launch_unchecked::<f32, cubecl::cuda::Runtime>(
            &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!("GELU计算结果: {:?}", output);
}

许可证

MIT OR Apache-2.0


1 回复

Rust宏库cubecl-macros的使用:高效代码生成与过程宏开发指南

介绍

cubecl-macros是一个Rust过程宏库,旨在简化代码生成和元编程任务。它提供了一套强大的工具,可以帮助开发者通过声明式宏和过程宏来减少样板代码,提高开发效率。

主要特性

  1. 代码生成:自动生成重复性代码结构
  2. 派生宏:简化自定义trait的实现
  3. 属性宏:通过属性注解修改代码行为
  4. 函数式宏:类似macro_rules!但更强大的代码转换能力

安装

在Cargo.toml中添加依赖:

[dependencies]
cubecl-macros = "0.3"

基本用法

1. 派生宏示例

use cubecl_macros::CubeDerive;

#[derive(CubeDerive)]
struct Point {
    x: f64,
    y: f64,
    z: f64,
}

// 自动生成的方法
let p = Point::new(1.0, 2.0, 3.0);
println!("Point magnitude: {}", p.magnitude());

2. 属性宏示例

use cubecl_macros::log_execution;

#[log_execution]
fn compute_heavy_thing(a: i32, b: i32) -> i32 {
    // 复杂计算...
    a * b + 42
}

// 调用时会自动记录执行时间和参数
let result = compute_heavy_thing(5, 10);

3. 函数式宏示例

use cubecl_macros::make_struct;

make_struct! {
    #[derive(Debug)]
    pub struct User {
        id: u64,
        name: String,
        email: String,
    }
}

// 相当于手动编写了:
// #[derive(Debug)]
// pub struct User {
//     id: u64,
//     name: String,
//     email: String,
// }

高级用法:自定义过程宏开发

cubecl-macros也提供了工具来简化你自己的过程宏开发:

use cubecl_macros::proc_macro_helpers;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(MyDeriveMacro)]
pub fn my_derive_macro(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    // 使用cubecl-macros提供的工具函数处理
    proc_macro_helpers::expand_derive(&input, |_| {
        // 自定义逻辑
        quote! {
            impl #ident {
                pub fn my_method(&self) -> String {
                    format!("Hello from {:?}", self)
                }
            }
        }
    }).into()
}

性能考虑

cubecl-macros在编译时执行,对运行时性能没有影响。但复杂的宏可能会增加编译时间,建议:

  1. 将大型宏分解为更小的单元
  2. 避免在宏中进行复杂的运行时逻辑
  3. 合理使用缓存机制

最佳实践

  1. 文档注释:为宏生成的代码添加文档
  2. 错误处理:提供清晰的编译错误信息
  3. 测试:为宏编写全面的测试用例
  4. 渐进式:从简单用例开始,逐步增加复杂性

示例项目结构

my_project/
├── Cargo.toml
├── src/
│   ├── main.rs
│   ├── lib.rs
└── macros/
    ├── Cargo.toml
    └── src/
        ├── lib.rs
        └── my_macros.rs

完整示例Demo

下面是一个使用cubecl-macros的完整示例项目:

// src/main.rs

use cubecl_macros::{CubeDerive, log_execution, make_struct};

// 派生宏使用
#[derive(CubeDerive, Debug)]
struct Vector3D {
    x: f64,
    y: f64,
    z: f64,
}

// 属性宏使用
#[log_execution]
fn calculate_distance(a: Vector3D, b: Vector3D) -> f64 {
    let dx = a.x - b.x;
    let dy = a.y - b.y;
    let dz = a.z - b.z;
    (dx * dx + dy * dy + dz * dz).sqrt()
}

// 函数式宏使用
make_struct! {
    #[derive(Debug, Clone)]
    pub struct Player {
        id: u64,
        name: String,
        position: Vector3D,
        health: f32,
    }
}

fn main() {
    // 使用派生宏生成的方法
    let v1 = Vector3D::new(1.0, 2.0, 3.0);
    let v2 = Vector3D::new(4.0, 5.0, 6.0);
    println!("Vector magnitude: {}", v1.magnitude());
    
    // 使用属性宏装饰的函数
    let distance = calculate_distance(v1, v2);
    println!("Distance between vectors: {}", distance);
    
    // 使用函数式宏生成的结构体
    let player = Player {
        id: 1,
        name: "Alice".to_string(),
        position: Vector3D::new(0.0, 0.0, 0.0),
        health: 100.0,
    };
    println!("Player: {:?}", player);
}

总结

cubecl-macros为Rust开发者提供了强大的元编程能力,可以显著减少样板代码,提高开发效率。通过合理使用其提供的各种宏类型,你可以创建更简洁、更易维护的代码库。

回到顶部