跳到主要内容

52 篇博文 含有标签「Rust」

查看所有标签

CandleRust 的极简 ML 框架,重点关注性能(包括 GPU 支持)和易用性。尝试我们的在线演示: whisperLLaMA2T5yoloSegment Anything

模块

Candle项目包括一些crates,如下:

  • candle-book: candle相关的文档
  • candle-core: 核心功能库,核心操作,设备,Tensor结构定义等。
  • candle-nn: 神经网络,构建真实模型的工具
  • candle-examples: 在实际环境中使用库的示例
  • candle-datasets: 数据集和数据加载
  • candle-transformers: Transformer相关实现工具
  • candle-flash-attn: Flash Attention v2实现
  • candle-kernels: CUDA加速实现
  • candle-pyo3: Rust提供的Python接口
  • candle-wasm-examples: Rust WASM示例

其它有用的库:

  • candle-lora: 提供了符合官方peft实现的LoRA实现

特点

  • 语法简单(看起来像PyTorch)
    • 支持模型训练
    • 支持用于自定义操作运算
  • 后端
    • 优化的CPU后端,具有针对x86的可选MKL支持和针对MacAccelerate支持
    • CUDA后端可以再GPU上高效运行,通过NCCL运行多GPU分配
    • WASM支持,在浏览器中运行模型
  • 包含的模型
    • 语言模型
      • LLaMA v1 and v2
      • FaIcon
      • StarCoder
      • Phi v1.5
      • T5
      • Bert
    • Whisper(多语言支持)
    • Stable Diffusion v1.5, v2.1, XL v1.0
    • Wurstchen v2
    • 计算机视觉
      • DINOv2
      • EfficientNet
      • yolo-v3
      • yolo-v8
      • Segmeng-Anything(SAM)
  • 文件格式
    • 加载模型支持的格式如下:
      • safetensors
      • npz
      • ggml
      • PyTorch files
  • 无服务部署
    • 小型且快速的部署
  • 使用llama.cpp量化类型的量化支持

基本用法介绍

  1. 创建张量

    Tensor::new(&[[1f32, 2.], [3., 4.]], &Device::Cpu)?
    Tensor::zeros((2, 2), DType::F32, &Device::Cpu)?
  2. 张量索引

    tensor.i((.., ..4))?
  3. 张量重塑

    tensor.reshape((2, 2))?
  4. 张量矩阵乘法

    a.matmul(&b)?
  5. 张量数据移动到特定设备

    tensor.to_device(&Device::new_cuda(0)?)?
  6. 更改张量数据类型

    tensor.to_dtype(&Device::F16)?
  7. 张量算术运算

    &a + &b
  8. 保存模型

    candle::safetensors::save(&HashMap::from([("A", A)]), "model.safetensors")?
  9. 加载模型

    candle::safetensors::load("model.safetensors", &device)
鱼雪

Rust Prelude是Rust标准库隐式导入到每个Rust程序中。

它就像呼吸一样无需调用就存在,也可以明确调用。

它包含许多常用类型、函数和宏。

为编写Rust程序提供了大量功能。但不是所有情况下都是最佳选择。

什么时候使用Rust Prelude

Rust Prelude时一款出色的默认设置,可满足大多数基本需求,在以下情况下应使用

  • 需要常见的数据结构例如:Strings, Vectors, HashMaps
  • 需要常用的迭代函数,如:map, filter, any, all
  • 需要常见的转换函数,如:from_strto_string
  • 需要使用常见宏,如:println!, panic!, unreachable!
  • 程序比较短小精悍,需要大量功能而不需要很多导入

什么时候不使用Rust Prelude

  • 名称冲突:Prelude到处了许多常用名称,因此导入具有相同名称的另一个crates可能会导致冲突
  • 更专业的功能:对于更多特定领域的用例,最好使用专门的crates
  • 为了提高性能:一些Prelude是通用的,对于高性能需求,专门的crates可能更好
鱼雪

案例来自于Rust Programming by Example,由于按照书籍的操作会存在一些错误,所以用此博客来记录更多的细节。

实验环境

  • MacOS M2: 13.5.2
  • Rust: 1.70.0

初始化开发环境

  1. 创建工程
cargo new tetris
  1. 添加依赖于Cargo.toml
[package]
name = "tetris"
version = "0.0.1"

[dependencies]
sdl2 = "0.34.5"
  1. 安装SDL2
brew install sdl2
  1. 配置环境变量
export RUSTFLAGS="-L /opt/homebrew/lib"

此处RUSTFLAGS环境变量的内容需要根据自己的SDL2库的安装路径来配置,由于我的库安装路径是/opt/homebrew/lib下。 执行cargo build编译程序会依赖SDL的动态库;同时我也尝试了其他两种方法并没有成功

其它方法一: 设置环境变量DYLD_LIBRARY_PATH

export DYLD_LIBRARY_PATH=/opt/homebrew/lib

其它方法二: 在Cargo.toml中添加

[build]
rustc-link-search = ["/opt/homebrew/lib"]
  1. 基础示例代码
extern crate sdl2;

use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use std::time::Duration;

fn main() {
// 初始化SDL2
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();

// 创建窗口和画布
let window = video_subsystem
.window("SDL2 Window", 800, 600)
.position_centered()
.build()
.unwrap();

let mut canvas = window.into_canvas().build().expect("Failed to convert window into canvas");

// 渲染代码
canvas.set_draw_color(Color::RGB(255, 0, 0));
canvas.clear();
canvas.present();

// 创建事件处理器
let mut event_pump = sdl_context.event_pump().expect("Failed to get SDL event pump");

// 主循环
'runningloop: loop {
for event in event_pump.poll_iter() {
match event {
Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
break 'runningloop;
}
_ => {}
}
}

// 添加延迟以控制帧率
std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
}
}
  1. 构建程序
cargo build
  1. 运行程序
cargo run

运行结果如下图: 运行结果

  1. 实现过程
  • 导入外部crate SDL2

    extern crate sdl2;
  • 初始化SDL context

    let sdl_context = sdl2::init().expect("SDL initialization failed");
  • 获取video subsystem

    let video_subsystem = sdl_context.video().expect("Couldn't get SDL
    video subsystem");
  • 创建windows

    let window = video_subsystem.window("Tetris", 800, 600)
    .position_centered()
    .opengl()
    .build()
    .expect("Failed to create window");
    • the parameters for the window method
      • title
      • width
      • height
    • .position_centered() method
      • 在屏幕中间获取窗口
    • .opengl()
      • SDL使用opengl渲染
    • .build()
      • 根据前面提供的参数创建窗口
    • .expect()
      • 处理异常
  • 事件循环 正常情况下,展示一个窗口并关闭特别快,我们需要添加时间循环确保窗口一直在运行。

    • 导入必要相关的库
    use sdl2::event::Event;
    use sdl2::keyboard::Keycode;

    use std::thread::sleep;
    use std::time::Duration;
    • 获取时间循环管理器
    let mut event_pump = sdl_context.event_pump().expect("Failed to
    get SDL event pump");
    • 创建无限循环循环事件
    'running: loop {
    for event in event_pump.poll_iter() {
    match event {
    Event::Quit { .. } |
    Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
    break 'running // We "break" the infinite loop.
    },
    _ => {}
    }
    }
    sleep(Duration::new(0, 1_000_000_000u32 / 60));
    }

    running是一个循环跳出的标签(label) 收到一个quit event或者按Esc键,程序退出

画布(Canvas)

  • 当我们有了一个窗口(window)时,我们需要获取(get)窗口的画布(window's canvas)
let mut canvas = window.into_canvas()
.target_texture()
.present_vsync()
.build()
.expect("Couldn't get window's canvas");

以上代码的简单说明:

  • into_canvas: 将窗口(window)转换为画布(canvas),以便我们可以轻松操作它
  • target_texture: 激活纹理渲染支持
  • present_vsync: 允许v-sync(竖直同步操作)限制
  • build: 应用前面设置的参数创建画布(canvas)

纹理(Texture)

当我们有了一个窗口的画布时,我们可以创建纹理,粘贴(paste onto)在其上。 获取(get)一个纹理创造器(texture creator)

  • 添加包含的结构

    use sdl2::render::{Canvas, Texture, TextureCreator};
  • 获取纹理创造器

    let texture_creator: TextureCreator<_> = canvas.texture_creator();

创建矩形

首先创建一个常量确定矩形的尺寸,然后使用纹理创造器创建一个矩形的纹理

  • 设置矩形矩形的尺寸

    const TEXTURE_SIZE: u32 = 32; // 矩形的尺寸大小 
  • 使用纹理创造器创建矩形

    const TEXTURE_SIZE: u32 = 32; // 矩形的尺寸大小
    let mut square_texture: Texture =
    texture_creator.create_texture_target(None, TEXTURE_SIZE,
    TEXTURE_SIZE)
    .expect("Failed to create a texture");

设置颜色

要为纹理设置颜色,需要引入颜色模块的结构,然后使用画布设置纹理的颜色,颜色绘制完成后需要清空纹理;

  • 引入颜色结构

    use sdl2::pixels::Color;
  • 使用画布设置矩形纹理的颜色

    canvas.with_texture_canvas(&mut square_texture, |texture| {
    texture.set_draw_color(Color::RGB(0, 255, 0));
    texture.clear();
    })

代码的简单说明:

为了更新窗口的渲染内容,我们需要在将代码包含在main loop(and after the event loop). clear()纹理,以至于它被填充绿色

  • 将窗口填充红色

    canvas.set_draw_color(Color::RGB(255, 0, 0));
    canvas.clear();
  • 将纹理拷贝到窗口中

    canvas.copy(&square_texture, None, Rect::new(0, 0, TEXTURE_SIZE, TEXTURE_SIZE))
    .expect("Couldn't copy texture into window");
  • 最后更新窗口的展示

    canvas.present();

在红色画布上绘制绿色矩形的完整代码

  • 代码如下:

    extern crate sdl2;

    use sdl2::event::Event;
    use sdl2::keyboard::Keycode;
    use sdl2::pixels::Color;
    use sdl2::render::{Texture, TextureCreator};
    use sdl2::rect::Rect;
    use std::time::Duration;

    fn main() {
    // 初始化SDL2
    let sdl_context = sdl2::init().expect("SDL initialization failed");
    let video_subsystem = sdl_context.video().expect("Couldn't get SDL video subsystem");

    // 创建窗口和画布
    let window = video_subsystem
    .window("Tetris", 800, 600)
    .position_centered()
    .build()
    .expect("Failed to create window");

    let mut canvas = window.into_canvas()
    .target_texture()
    .present_vsync()
    .build()
    .expect("Couldn't get window's canvas");

    let texture_creator: TextureCreator<_> = canvas.texture_creator();
    const TEXTURE_SIZE: u32 = 32;

    // create a texture with a 32*32 size
    let mut square_texture: Texture = texture_creator.create_texture_target(None, TEXTURE_SIZE, TEXTURE_SIZE)
    .expect("Failed to create a texture");

    // use the canvas to draw into our square texture.
    canvas.with_texture_canvas(&mut square_texture, |texture| {
    // set the draw color to green
    texture.set_draw_color(Color::RGB(0, 255, 0));
    texture.clear();
    }).expect("Failed to clear a texture");

    // 创建事件处理器
    let mut event_pump = sdl_context.event_pump().expect("Failed to get SDL event pump");

    // 主循环
    'runningloop: loop {
    for event in event_pump.poll_iter() {
    match event {
    Event::Quit { .. } | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
    break 'runningloop;
    }
    _ => {}
    }
    }

    // We set fulfill our window with red
    canvas.set_draw_color(Color::RGB(255, 0, 0));
    // We draw it
    canvas.clear();
    // Copy our texture into the window
    canvas.copy(&square_texture, None, Rect::new(0, 0, TEXTURE_SIZE, TEXTURE_SIZE))
    .expect("Couldn't copy texture into window");
    canvas.present();

    // 添加延迟以控制帧率
    std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
    }
    }

代码执行结果如下图所示: 运行结果

鱼雪

rustup常用命令

rustup是Rust语言的一个安装器。

可以管理Rust Toolchain以及rustup等工具链组件

  1. 查看当前编译器信息
rustup show
  1. 更新Rust Toolchain
rust update
  1. 检查更新Rust Toolchainrustup
rustup check
  1. 设置默认工具链
rustup default -h
  1. 管理component
  • 安装组件
rust component add <component_name>
  • 删除组件
rust component remove <component_name>
  • 列出所有可用组件
rust component list
  • 列出以安装组件
rust component list --installed

Clippy

一系列lints,用于捕获常见错误并改进Rust代码。

安装

rustup update
rustup component add clippy

运行Clippy

cargo clippy

或者

cargo check

自动应用Clippy建议

cargo clippy --fix

配置文件

  • clippy.toml
  • .clippy.toml

Rustfmt

Rust代码格式化工具

安装

rustup component add rustfmt

运行

cargo fmt

配置文件

  • rustfmt.toml
  • .rustfmt.toml

Rust-Analyzer

rust-analyzer是Rust编程语言的语言服务器协议的实现。

鱼雪