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
Описание: Создаёт тензор из плоского вектора чисел с плавающей точкой. Форма задаётся через 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 и ток