1. Por qué Candle
Secciones
Ajustes de lectura
Ninguna voz para este idioma en este navegador.
Más voces
Código y términos técnicos en inglés
Desactivado: la voz de la página lo lee todo, sin pausa entre dos voces.
Ninguna voz para este idioma en este navegador.
Más voces
Más voces: Microsoft Edge ofrece voces naturales en línea, y Windows (Configuración › Hora e idioma › Voz), macOS y Android instalan otras.
Candle es un framework de aprendizaje automático escrito en Rust por Hugging Face. Te da tensores, diferenciación automática, capas de redes neuronales e implementaciones de modelos conocidos, y se compila dentro de tu programa como cualquier otro crate: sin Python, sin un runtime de C++ que instalar al lado. Esta lección lo sitúa entre las herramientas que un desarrollador C# o Java quizá ya conoce, desmonta el workspace y ejecuta un primer programa.
Dónde se sitúa Candle
Sección titulada «Dónde se sitúa Candle»| Lenguaje en el que escribes | Qué calcula | Entrenamiento | Qué despliegas | |
|---|---|---|---|---|
| PyTorch | Python | libtorch, en C++ y CUDA | sí | un entorno Python, o un modelo exportado para otro runtime |
| tch-rs | Rust | libtorch, a través de una API en C | sí | tu binario más las bibliotecas de libtorch |
| TorchSharp | C#, F# | libtorch, a través de paquetes nativos | sí | tu aplicación .NET más un paquete NuGet libtorch-* |
| ONNX Runtime | C#, Java, Python, C++, Rust a través de ort |
su propio motor en C++ | ante todo inferencia | tu aplicación más el runtime nativo y un archivo .onnx |
| ML.NET | C#, F# | entrenadores .NET, y ONNX Runtime o TorchSharp para el aprendizaje profundo | sí | una aplicación .NET |
| DJL | Java | un motor que eliges: PyTorch, ONNX Runtime y otros | depende del motor | una aplicación JVM más las bibliotecas nativas del motor |
| Burn | Rust | backends detrás de un trait Backend, de CUDA a un backend de CPU en Rust puro |
sí | un binario Rust |
| Candle | Rust | código Rust, con CUDA, Metal, MKL o Accelerate opcionales | sí | un binario Rust, o un módulo WebAssembly |
Fuentes: los README de tch-rs, TorchSharp, ort, DJL, Burn y Candle.
La columna que importa para este curso es la última. Con tch-rs o TorchSharp, las cuentas con tensores ocurren en libtorch, una biblioteca nativa de varios cientos de megabytes que despliegas junto a tu programa. Con ONNX Runtime, despliegas su biblioteca nativa y un modelo exportado desde Python. El propio README de Candle plantea sus objetivos en los mismos términos: hacer posible la inferencia serverless con binarios pequeños que arrancan rápido, y sacar Python de las cargas de trabajo en producción.
Lo que pierdes es amplitud. PyTorch tiene miles de operaciones y un gran ecosistema; Candle tiene las operaciones que necesitan sus modelos. La lección 2 se topa con algunos de sus límites, como operaciones que provocan un panic sobre tensores enteros en lugar de devolver un error.
El workspace
Sección titulada «El workspace»Candle se publica como varios crates. La versión estudiada aquí es la 0.11.0, la última en crates.io (26 de junio de 2026), cuyas fuentes son la etiqueta 0.11.0, commit 31f35b1. Cada enlace al código de Candle en este curso apunta a ese commit.
| Crate | Qué contiene | Lecciones |
|---|---|---|
candle-core |
Tensor, DType, Device, los backends de CPU, CUDA y Metal, autodiff, tensores cuantizados, los formatos safetensors y GGUF |
2 a 4, 8 |
candle-nn |
capas (Linear, Embedding, normalizaciones), activaciones, pérdidas, optimizadores, VarBuilder |
5, 6 |
candle-transformers |
implementaciones de modelos publicados: 125 entradas bajo models/, de BERT a Qwen3 cuantizado |
7, 8 |
candle-datasets |
cargadores de conjuntos de datos habituales como MNIST | 5 |
candle-examples |
111 programas de línea de comandos, uno por modelo o tarea | 7 a 9 |
candle-onnx |
un evaluador de grafos ONNX | 11 |
candle-pyo3 |
bindings de Python, la dirección inversa a la de este curso | — |
candle-kernels, candle-metal-kernels, candle-flash-attn |
kernels de GPU en CUDA y Metal | 9 |
candle-wasm-examples |
los mismos modelos compilados a WebAssembly para el navegador | 10 |
candle-core activa sus backends con features de Cargo, declaradas en candle-core/Cargo.toml, líneas 48-62. Ninguna está activada por defecto, así que una dependencia sin más te da solo el backend de CPU.
| Feature | Qué añade | Dónde |
|---|---|---|
cuda |
el backend CUDA, a través del crate cudarc, y los kernels CUDA |
GPU NVIDIA, necesita el CUDA toolkit |
cudnn |
cuda más cuDNN para las convoluciones |
ídem |
nccl |
cuda más NCCL, para repartir un modelo entre varias GPU |
ídem |
metal |
el backend Metal | GPU de Apple, macOS |
accelerate |
el framework Accelerate de Apple para los productos de matrices en la CPU | macOS |
mkl |
la Math Kernel Library de Intel para los productos de matrices en la CPU | CPU x86 |
candle-nn y candle-transformers transmiten las mismas features a candle-core. La lección 9 trata las features de GPU; el código de este curso no usa ninguna.
Ejecutar el código
Sección titulada «Ejecutar el código»El código del curso es un workspace de Cargo en code/candle, con un ejemplo por tema y uno por cada conjunto de soluciones de ejercicios. Necesitas Rust y un compilador de C, por una razón que se explica más abajo: en Windows bastan las Visual Studio Build Tools que pide rustup.
cd code\candlecargo run --release --example l01_hellobash check.shcd code/candlecargo run --release --example l01_hellobash check.shcd code/candlecargo run --release --example l01_hellobash check.shcheck.sh ejecuta cargo fmt --check, clippy, las pruebas (incluidos los doctests compile_fail de los fragmentos que este curso muestra rechazados), luego cada ejemplo, y compara cada salida con el archivo de expected/. En Windows, ejecútalo desde Git Bash.
El primer programa
Sección titulada «El primer programa»El libro de Candle añade candle-core con cargo add --git, que sigue la rama main. Este curso fija en cambio la versión publicada, para que las salidas de abajo sigan siendo ciertas: Cargo.toml pide exactamente la 0.11.0 con =, como describe la referencia de Cargo, y Cargo.lock está en el repositorio.
[workspace.dependencies]candle-core = "=0.11.0"candle-nn = "=0.11.0"El programa es el del README de Candle, un producto de matrices, con un cambio. El README rellena las matrices con Tensor::randn, números aleatorios, y el generador aleatorio de la CPU de Candle no admite semilla (la lección 2 muestra el error), así que su salida cambia en cada ejecución. examples/l01_hello.rs usa arange en su lugar:
use candle_core::{Device, Tensor};
fn main() -> Result<(), Box<dyn std::error::Error>> { let device = Device::Cpu;
// arange en lugar del randn del README: los mismos números en cada ejecución y en cada SO let a = Tensor::arange(0f32, 6., &device)?.reshape((2, 3))?; let b = Tensor::arange(0f32, 12., &device)?.reshape((3, 4))?;
let c = a.matmul(&b)?; println!("{c}"); Ok(())}[[20., 23., 26., 29.], [56., 68., 80., 92.]]Tensor[[2, 4], f32]Cuatro cosas en las que fijarse, viniendo de C# o Java:
- Cada operación devuelve un
Result.arange,reshapeymatmulpueden fallar, por ejemplo con una forma que no encaja, y lo informan como un valorcandle_core::Erroren lugar de lanzar una excepción. El?tras cada llamada devuelve el error a quien llama, como explica el curso de Rust. Donde TorchSharp lanza una excepción, Candle te entrega un valor. - El dispositivo es explícito. Cada tensor se crea en un
Device:Device::Cpuaquí, un dispositivo CUDA o Metal en la lección 9. - La forma y el tipo de elemento son valores en tiempo de ejecución.
Tensores un único tipo para cualquier forma y cualquier tipo de elemento; la última línea de la salida imprime ambos,[2, 4]yf32. Un error de forma se descubre cuando el programa se ejecuta, no cuando compila. 0f32, 6.da al primer número un sufijo de tipo; Rust infieref32para el segundo a partir de él.
Una matriz (2, 3) por una matriz (3, 4) da una matriz (2, 4): el primer elemento es 0·0 + 1·4 + 2·8 = 20.
Dos errores con los que se topa todo principiante
Sección titulada «Dos errores con los que se topa todo principiante»Imprimir el resultado de matmul sin ? imprime un Result, no un tensor, y Result no implementa Display:
let c = a.matmul(&b);println!("{c}");error[E0277]: `Result<candle_core::Tensor, candle_core::Error>` doesn't implement `std::fmt::Display` --> course\examples\x_result.rs:8:15 |8 | println!("{c}"); | ^^^ `Result<candle_core::Tensor, candle_core::Error>` cannot be formatted with the default formatterEl segundo viene de la chuleta del propio README de Candle, línea 278, que convierte un tensor con tensor.to_dtype(&DType::F16)?. to_dtype recibe el DType por valor:
error[E0308]: mismatched types --> course\examples\x_readme.rs:5:32 | 5 | let half = tensor.to_dtype(&DType::F16)?; | -------- ^^^^^^^^^^^ expected `DType`, found `&DType` | | | arguments to this method are incorrectDType es un enum Copy pequeño, así que no hay razón para pasar una referencia; la sugerencia del compilador, quitar el &, es la corrección. Ambos fragmentos son doctests compile_fail en src/lib.rs, así que el curso se entera si una versión posterior de Candle o de Rust los acepta.
Lo que cuesta compilar
Sección titulada «Lo que cuesta compilar»El primer cargo build --release de este workspace tardó 73 segundos y compiló 128 crates, en un Intel Core Ultra 9 285K con Windows 11 y Rust 1.94.0, limitado a 4 trabajos en paralelo (-j 4) porque la máquina era compartida. El ejecutable l01_hello ocupa 1,4 MB. Las compilaciones siguientes solo recompilan el crate del curso, en un segundo más o menos.
La mayoría de esos crates son los que cabría esperar: gemm para los productos de matrices, rayon para los hilos, half para los flotantes de 16 bits, safetensors para los archivos de modelo. Uno no. cargo tree muestra por dónde entra el compilador de C:
> cargo tree -p candle-course -e normal -i onig_sysonig_sys v69.9.3└── onig v6.5.3 └── tokenizers v0.22.2 └── candle-core v0.11.0candle-core 0.11.0 depende del crate tokenizers de Hugging Face en todos los targets salvo WebAssembly (Cargo.toml, líneas 37-38), y con su feature onig, que compila la biblioteca de expresiones regulares Oniguruma desde fuentes en C. Solo la usa un archivo, quantized/tokenizer.rs, que reconstruye un tokenizador a partir de los metadatos de un archivo GGUF (lección 8). De los 119 crates detrás de candle-core, 72 están en el árbol de dependencias de tokenizers. En main, después de 0.11.0, la dependencia sigue ahí, pero con la feature fancy-regex, en Rust puro, en lugar de onig (Cargo.toml en ddf1b87), así que el requisito del compilador de C debería desaparecer con la próxima versión (por verificar cuando se publique).
Pedir una GPU
Sección titulada «Pedir una GPU»examples/l01_devices.rs pregunta a la compilación qué backends tiene, y luego pide un dispositivo CUDA y uno Metal:
println!("cuda feature: {}", utils::cuda_is_available());match Device::new_cuda(0) { Ok(d) => println!("Device::new_cuda(0): {d:?}"), Err(e) => println!("Device::new_cuda(0): error: {e}"),}// Recurre a la CPU en lugar de fallarlet device = Device::cuda_if_available(0)?;cuda feature: falsemetal feature: falsemkl feature: falseaccelerate feature: falseDevice::new_cuda(0): error: the candle crate has not been built with cuda supportDevice::new_metal(0): error: the candle crate has not been built with metal supportDevice::cuda_if_available(0): CpuLa salida es la misma en una máquina con una GPU NVIDIA: utils::cuda_is_available indica si la feature cuda se compiló, no si hay una GPU. Sin la feature, Candle compila un backend sustituto cuyos constructores devuelven este error. cuda_if_available es la forma habitual de escribir un programa que usa una GPU cuando se compiló para ella, y la CPU en caso contrario.
El backend de CPU tiene un interruptor más que no es una feature. examples/l01_machine.rs imprime lo que la compilación sabe del procesador; check.sh lo ejecuta sin comparar, ya que depende de la máquina:
os / arch: windows / x86_64compiled for avx2: falsecompiled for neon: falsecompiled for f16c: falsethreads (rayon): 24El procesador tiene AVX2, pero el programa no se compiló para él: Rust apunta a una CPU x86-64 de base salvo que pases -C target-cpu=native. El propio repositorio de Candle lo hace, en .cargo/config.toml, así que sus ejemplos se compilan para la máquina en la que se ejecutan, y un crate que depende de Candle no. La lección 3 mide lo que eso cambia.
En GA, IX y TARS
Sección titulada «En GA, IX y TARS»Ninguno de los tres repositorios que estudia este sitio depende hoy de Candle.
- GA ejecuta sus modelos de embeddings en C# con ONNX Runtime:
GA.Business.ML.csprojreferenciaMicrosoft.ML.OnnxRuntime1.23.2 yMicrosoft.ML.Tokenizers2.0.0, yOnnxEmbeddingService.csabre unaInferenceSession. Es la fila “ONNX Runtime” de la tabla de arriba; la lección 7 calcula embeddings con Candle, y la lección 11 llama a un modelo de Candle desde C#. - TARS hace lo mismo desde F#:
TarsEngine.FSharp.Cli.fsprojreferencia ONNX Runtime 1.19.2 yMicrosoft.ML.Tokenizers0.22.0. - IX está escrito en Rust y decidió no construir sobre Candle. Su crate de diferenciación automática,
ix-autograd, está escrito desde cero sobrendarray; su documentación deja constancia de la decisión (lib.rs, línea 27) y reserva un lugar para “a future Candle or GPU backend” (tensor.rs, líneas 9-19). La lección 4 pasa el mismo gradiente por los dos.
La guía de bibliotecas Rust de IX describe Candle como “optimised for LLM inference on NVIDIA GPUs via cuTENSOR and cuDNN” (code-analysis-tools.md, línea 129). Las features CUDA del workspace de Candle piden a cudarc cuBLAS, cuBLASLt, cuRAND y NVRTC, con cuDNN como opción (Cargo.toml, líneas 50-61); cuTENSOR no está entre ellas, y no se mencionan los backends de CPU, Metal y WebAssembly. El diario lo recoge.
Puntos clave
Sección titulada «Puntos clave»- Candle es un framework Rust de tensores, autodiff y modelos, compilado dentro de tu binario. Su punto fuerte frente a tch-rs, TorchSharp y ONNX Runtime es que no hay ningún runtime nativo que desplegar.
candle-corecontiene los tensores y los backends,candle-nnlas capas y los optimizadores,candle-transformerslos modelos. Los backends de GPU y MKL o Accelerate son features de Cargo opcionales.- Cada operación sobre tensores devuelve un
Result; las formas y los tipos de elemento se comprueban cuando el programa se ejecuta. - Fija la versión de crates.io con
=para obtener resultados reproducibles. La versión 0.11.0 necesita un compilador de C porquecandle-corearrastratokenizerscon Oniguruma. cuda_is_availableinforma de una feature compilada, no del hardware. Sin-C target-cpu=native, los kernels específicos de CPU detrás dewith_avxno se compilan.
Ejercicios
Sección titulada «Ejercicios»Las soluciones están en examples/l01_exercises.rs, y su salida en expected/l01_exercises.txt.
- En
l01_hello, calculab.matmul(&a)en lugar dea.matmul(&b). Lee el error, y luego encuentra un producto de matrices traspuestas (t()) que esté definido y dé los números de la lección, traspuestos.
Solución
outcome("b.matmul(&a)", b.matmul(&a));// (3, 4) x (4, 3) está definido si trasponemos b en su lugar: b.t() es (4, 3), a.t() es (3, 2)let c = b.t()?.matmul(&a.t()?)?;println!("{c}");// (AB)^T = B^T A^T: los mismos números que a.matmul(&b) de la lección, traspuestoslet same = c.eq(&a.matmul(&b)?.t()?)?.min_all()?.to_scalar::<u8>()? == 1;== exercise 1: b x a, then b x a transposedb.matmul(&a): error: shape mismatch in matmul, lhs: [3, 4], rhs: [2, 3][[20., 56.], [23., 68.], [26., 80.], [29., 92.]]Tensor[[4, 2], f32]b.t() x a.t() equals (a x b).t(): trueUna matriz (3, 4) solo puede multiplicar a una matriz de 4 filas. La traspuesta de un producto es el producto de las traspuestas en el orden inverso, así que b.t() por a.t(), una (4, 3) por una (3, 2), da la traspuesta (4, 2) del resultado de la lección. eq compara elemento a elemento y devuelve un tensor de u8, cuyo mínimo es 1 solo si todos los elementos coinciden.
- Convierte
aybaF64,F16,BF16,U32,I64yU8conto_dtype, y pruebamatmulcon cada uno. ¿Qué tipos de elemento multiplica el backend de CPU?
Solución
for dtype in [DType::F64, DType::F16, DType::BF16, DType::U32, DType::I64, DType::U8] { let result = a.to_dtype(dtype)?.matmul(&b.to_dtype(dtype)?); match result { Ok(c) => println!("{dtype:?}: ok, first row {:?}", c.to_dtype(DType::F64)?.to_vec2::<f64>()?[0]), Err(e) => println!("{dtype:?}: error: {e}"), }}== exercise 2: matmul on each dtypeF64: ok, first row [20.0, 23.0, 26.0, 29.0]F16: ok, first row [20.0, 23.0, 26.0, 29.0]BF16: error: unsupported dtype BF16 for op matmulU32: error: unsupported dtype U32 for op matmulI64: error: unsupported dtype I64 for op matmulU8: error: unsupported dtype U8 for op matmulSolo F16, F32 y F64: el producto de matrices en CPU llama a gemm, y cpu_backend/mod.rs, líneas 1382-1385 rechaza cualquier otro tipo. BF16, el formato “brain float” en el que se guardan muchos modelos, hay que convertirlo antes a F32 en la CPU. Los tensores enteros contienen índices y máscaras, no pesos.
Fuentes
Sección titulada «Fuentes»- Candle en la etiqueta
0.11.0: README,Cargo.tomldel workspace,candle-core/Cargo.toml,utils.rs - El libro de Candle, capítulo de instalación
candle-core0.11.0 en docs.rs- La referencia de Cargo: especificar dependencias y features
- rustc:
target-cpu