candle для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

candle для Rust: минималистичный ML-фреймворк от Hugging Face

candle — это современный фреймворк для машинного обучения, написанный на Rust и разработанный командой Hugging Face. В отличие от тяжеловесных решений вроде PyTorch или TensorFlow, candle ориентирован на производительность, безопасность памяти и минимальное потребление ресурсов. Он позволяет запускать модели нейронных сетей на CPU и GPU (через CUDA и Vulkan) без необходимости устанавливать огромные рантаймы.

Зачем нужен candle? Во-первых, он идеально подходит для встраиваемых систем, веб-ассемблера (WASM) и edge-устройств, где размер бинарного файла и скорость загрузки критичны. Во-вторых, Rust гарантирует потокобезопасность и отсутствие сборщика мусора — это даёт предсказуемую производительность для инференса. В-третьих, candle поддерживает загрузку моделей из форматов PyTorch (safetensors, bin) и Hugging Face Hub, что делает его отличным выбором для продакшена.

На момент написания статьи candle активно развивается: поддерживаются трансформеры (BERT, GPT, LLaMA, Whisper), свёрточные сети (ResNet, ViT) и даже генеративные модели (Stable Diffusion). В этом руководстве мы разберём установку, ключевые методы и напишем полноценный пример классификации текста.

Установка

candle — это библиотека на Rust, поэтому установка производится через Cargo. Добавьте зависимость в Cargo.toml:

[dependencies]
candle-core = "0.7"
candle-nn = "0.7"
candle-transformers = "0.7"
tokenizers = "0.21"  # для работы с токенизаторами
anyhow = "1.0"       # для обработки ошибок

Если вам нужна поддержка CUDA, используйте флаг --features cuda:

cargo add candle-core --features cuda
cargo add candle-nn --features cuda
cargo add candle-transformers --features cuda

Для работы с веб-ассемблером (WASM) добавьте --features wasm.

Быстрый старт: минимальный рабочий пример

Создадим тензор и выполним простую операцию — умножение матриц:

use candle_core::{Device, Tensor};

fn main() -> Result<(), Box> {
    // Создаём тензоры на CPU
    let a = Tensor::new(&[[1.0f32, 2.0], [3.0, 4.0]], &Device::Cpu)?;
    let b = Tensor::new(&[[5.0f32, 6.0], [7.0, 8.0]], &Device::Cpu)?;

    // Умножение матриц
    let c = a.matmul(&b)?;
    println!("Результат: {:?}", c.to_vec2::()?);

    Ok(())
}

Этот код выведет:

Результат: [[19.0, 22.0], [43.0, 50.0]]

Обратите внимание: все операции возвращают Result, поэтому мы используем ? для обработки ошибок.

Основные методы и функции

Рассмотрим 10 ключевых методов candle-core и candle-nn, которые понадобятся вам в 90% проектов.

1. Tensor::new — создание тензора

Сигнатура: pub fn new, D: Into>(data: impl Into>, device: D) -> Result<Tensor>

Описание: Создаёт тензор из плоского вектора чисел с плавающей точкой. Форма задаётся через Shape (например, Shape::from((2, 3))).

use candle_core::{Device, Tensor, Shape};

fn main() -> anyhow::Result<()> {
    // Создаём тензор 2x3 из вектора
    let t = Tensor::new(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &Device::Cpu)?;
    // Изменяем форму
    let t_reshaped = t.reshape(&Shape::from((2, 3)))?;
    println!("{:?}", t_reshaped.to_vec2::()?);
    Ok(())
}

2. Tensor::matmul — умножение матриц

Сигнатура: pub fn matmul(&self, other: &Tensor) -> Result<Tensor>

Описание: Выполняет матричное умножение двух тензоров. Поддерживает бродкастинг для батчей.

use candle_core::{Device, Tensor};

fn main() -> anyhow::Result<()> {
    let a = Tensor::new(&[[1.0, 2.0], [3.0, 4.0]], &Device::Cpu)?;
    let b = Tensor::new(&[[5.0], [6.0]], &Device::Cpu)?;
    let c = a.matmul(&b)?;
    println!("{:?}", c.to_vec2::()?); // [[17.0], [39.0]]
    Ok(())
}

3. Tensor::argmax — индекс максимального элемента

Сигнатура: pub fn argmax(&self, dim: usize) -> Result<Tensor>

Описание: Возвращает тензор с индексами максимальных значений вдоль указанной оси. Критически важно для задач классификации.

use candle_core::{Device, Tensor};

fn main() -> anyhow::Result<()> {
    let logits = Tensor::new(&[[0.1, 0.9, 0.0], [0.8, 0.1, 0.1]], &Device::Cpu)?;
    let predictions = logits.argmax(1)?;
    println!("{:?}", predictions.to_vec1::()?); // [1, 0]
    Ok(())
}

4. nn::Linear::new — полносвязный слой

Сигнатура: pub fn new(in_dim: usize, out_dim: usize, vb: VarBuilder) -> Result<Self>

Описание: Создаёт линейный (полносвязный) слой с заданными размерами входа и выхода. Веса инициализируются случайно.

use candle_core::{Device, Tensor};
use candle_nn::{Linear, VarBuilder, VarMap};

fn main() -> anyhow::Result<()> {
    let device = Device::Cpu;
    let varmap = VarMap::new();
    let vb = VarBuilder::from_varmap(&varmap, candle_core::DType::F32, &device);

    let linear = Linear::new(10, 5, vb.pp("linear"))?;
    let input = Tensor::randn(0.0, 1.0, (3, 10), &device)?;
    let output = linear.forward(&input)?;
    println!("Output shape: {:?}", output.shape()); // [3, 5]
    Ok(())
}

5. nn::Embedding::new — слой эмбеддингов

Сигнатура: pub fn new(vocab_size: usize, hidden_size: usize, vb: VarBuilder) -> Result<Self>

Описание: Создаёт слой эмбеддингов для преобразования дискретных токенов в непрерывные векторы.

use candle_core::{Device, Tensor};
use candle_nn::{Embedding, VarBuilder, VarMap};

fn main() -> anyhow::Result<()> {
    let device = Device::Cpu;
    let varmap = VarMap::new();
    let vb = VarBuilder::from_varmap(&varmap, candle_core::DType::F32, &device);

    let embedding = Embedding::new(1000, 128, vb.pp("embed"))?;
    let tokens = Tensor::new(&[1u32, 2, 3], &device)?;
    let embeds = embedding.forward(&tokens)?;
    println!("Embeddings shape: {:?}", embeds.shape()); // [3, 128]
    Ok(())
}

6. Tensor::softmax — функция softmax

Сигнатура: pub fn softmax(&self, dim: usize) -> Result<Tensor>

Описание: Применяет softmax вдоль указанной оси, нормализуя значения в диапазон [0, 1] с суммой 1.

use candle_core::{Device, Tensor};

fn main() -> anyhow::Result<()> {
    let logits = Tensor::new(&[[1.0, 2.0, 3.0]], &Device::Cpu)?;
    let probs = logits.softmax(1)?;
    println!("{:?}", probs.to_vec2::()?); // [[0.09, 0.24, 0.67]]
    Ok(())
}

7. nn::loss::cross_entropy — кросс-энтропия

Сигнатура: pub fn cross_entropy(logits: &Tensor, targets: &Tensor) -> Result<Tensor>

Описание: Вычисляет функцию потерь кросс-энтропии между предсказаниями и истинными метками.

use candle_core::{Device, Tensor};
use candle_nn::loss;

fn main() -> anyhow::Result<()> {
    let logits = Tensor::new(&[[1.0, 2.0, 3.0], [0.5, 1.5, 2.5]], &Device::Cpu)?;
    let targets = Tensor::new(&[2u32, 1], &Device::Cpu)?;
    let loss = loss::cross_entropy(&logits, &targets)?;
    println!("Loss: {:?}", loss.to_scalar::()?); // ~0.55
    Ok(())
}

8. nn::optim::SGD — стохастический градиентный спуск

Сигнатура: pub fn new(learning_rate: f64, params: Vec<Tensor>, momentum: f64) -> Self

Описание: Создаёт оптимизатор SGD для обновления весов модели.

use candle_core::{Device, Tensor};
use candle_nn::{Linear, VarBuilder, VarMap, optim::SGD};

fn main() -> anyhow::Result<()> {
    let device = Device::Cpu;
    let varmap = VarMap::new();
    let vb = VarBuilder::from_varmap(&varmap, candle_core::DType::F32, &device);
    let linear = Linear::new(10, 1, vb.pp("linear"))?;

    let params = varmap.all_vars();
    let mut sgd = SGD::new(0.01, params, 0.9)?;

    // Шаг оптимизации
    sgd.backward_step(&linear.forward(&Tensor::randn(0.0, 1.0, (1, 10), &device)?)?)?;
    Ok(())
}

9. candle_transformers::models::bert::BertModel::new — загрузка BERT

Сигнатура: pub fn new(vb: VarBuilder, config: &Config) -> Result<Self>

Описание: Загружает предобученную модель BERT из конфига и переменных. Позволяет получать эмбеддинги предложений.

use candle_core::{Device, Tensor};
use candle_transformers::models::bert::{BertModel, Config, DTYPE};
use candle_nn::VarBuilder;
use hf_hub::api::sync::Api;

fn main() -> anyhow::Result<()> {
    let api = Api::new()?;
    let model_path = api.model("bert-base-uncased".to_string())?;
    let config_path = model_path.get("config.json")?;
    let weights_path = model_path.get("model.safetensors")?;

    let device = Device::Cpu;
    let config = Config::from_file(config_path)?;
    let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[weights_path], DTYPE, &device)? };
    let model = BertModel::new(vb, &config)?;

    // Прямой проход
    let input_ids = Tensor::new(&[101u32, 2057, 102], &device)?;
    let attention_mask = Tensor::new(&[1u32, 1, 1], &device)?;
    let output = model.forward(&input_ids.unsqueeze(0)?, &attention_mask.unsqueeze(0)?)?;
    println!("Last hidden state shape: {:?}", output.last_hidden_state.shape());
    Ok(())
}

10. candle_nn::ops::dropout — dropout

Сигнатура: pub fn dropout(x: &Tensor, p: f64) -> Result<Tensor>

Описание: Применяет dropout к тензору: обнуляет случайные элементы с вероятностью p и масштабирует остальные.

use candle_core::{Device, Tensor};
use candle_nn::ops;

fn main() -> anyhow::Result<()> {
    let x = Tensor::ones((2, 3), candle_core::DType::F32, &Device::Cpu)?;
    let dropped = ops::dropout(&x, 0.5)?;
    println!("{:?}", dropped.to_vec2::()?);
    Ok(())
}

Реальный практический пример: классификация текста с BERT

Напишем полноценный пайплайн для классификации тональности текста (положительный/отрицательный) с помощью предобученного BERT. Пример включает загрузку модели, токенизацию и инференс.

use anyhow::Result;
use candle_core::{Device, Tensor};
use candle_nn::VarBuilder;
use candle_transformers::models::bert::{BertModel, Config, DTYPE};
use hf_hub::api::sync::Api;
use tokenizers::Tokenizer;

/// Загружает модель BERT и ток                    

Рекомендаций