Metal has had a fully device-resident attention chain — QKV split, RoPE, causal softmax, KV-cache append, head merge, zero trips back to the host — for a while now. As of 0.2.0, CUDA finally has the same chain, and the workspace’s last C++ tensor backend is gone.
Today we released TrustformeRS 0.2.0 — a release that closes CUDA’s attention-pipeline gap with Metal, adds batched and NumPy-style broadcasting CUDA matmul, gives GPU buffers a real refcounted lifecycle, mounts three subsystems and ten pipeline scaffolds that were sitting unwired in the tree, fixes a silent CUDA correctness race, and removes the torch/tch (libtorch) backend from the workspace entirely.
TrustformeRS is Pure Rust Hugging Face Transformers: transformer/LLM loading and inference, tokenizers, training, and serving — no Python required, no PyTorch, and as of 0.2.0, no libtorch anywhere in the workspace either. The torch Cargo feature, the tch dependency, and the Tensor::Torch variant are gone from trustformers-core, trustformers-training, trustformers, and trustformers-c — tch links libtorch, a C++ shared library, and cutting it is a direct step forward on the Pure-Rust default feature tree. candle remains as the one optional non-CPU tensor-framework integration, off by default. TrustformeRS still compiles to a single static binary — or to WASM, or onto mobile — and runs anywhere Rust runs.
Why TrustformeRS 0.2.0 is a game changer
Running TrustformeRS on CUDA before 0.2.0 meant living with a few real gaps:
- The CUDA backend’s attention math still round-tripped through the host for exactly the operations Metal had already learned to keep on-device: QKV head splitting, RoPE, causal softmax, KV-cache updates, head merging.
- Anything beyond a plain 2D matmul — batched heads, NumPy-style broadcasting shapes — fell back to a host round trip; there was no batched/broadcasting CUDA matmul dispatcher.
- GPU buffers leaked by design: a cached device allocation stayed resident until you explicitly called
clear_buffer_cache(), never freeing itself when the code holding it was actually done. - A real correctness bug: several CUDA device-to-host reads had no stream synchronization before their memcpy, so a fast host thread could occasionally read back a buffer before the kernel filling it had finished — silently returning zero or garbage data.
Device::best_available()andcuda_if_available()always returnedDevice::CPU, even on CUDA- or Metal-capable hardware, because they read an upstream capability flag that’s hardcodedfalse.- Multi-GPU setups could silently compute on the wrong device — tensors carried no
device_idof their own.
TrustformeRS 0.2.0 ends most of that:
- CUDA gets Metal’s resident attention chain. A new
gpu_ops::cuda::oxicuda::attentionmodule — head-gather, RoPE, causal softmax/attention, KV-cache concat, residual add, all device-resident. GPT-2 (Gpt2Attention::cuda_resident_attention) uses it for zero-host-round-trip prefill and incremental KV-cached decode; GPT-NeoX (GPTNeoXAttention::cuda_resident_forward) gets prefill (itsLayertrait carries no cache yet, so decode still falls back to the CPU-download path). - Batched, broadcasting CUDA matmul.
BatchedMatmulPlanis a NumPy-style broadcasting-batch shape planner;Tensor::matmulnow routes N-D batched/broadcast F32 matmuls — and any pair involving a resident CUDA tensor — straight through the CUDA backend instead of a 2D-only host dispatcher. - GPU buffers are refcounted RAII now.
BufferHandlereplaces the raw buffer-id fields onCudaTensorDataandLinear’s cached CUDA weights; the device allocation frees itself the moment the last clone drops. - The stream-sync race is fixed, and device detection tells the truth.
Device::best_available()/cuda_if_available()now probe TrustformeRS’s ownoxicuda/Metal backends directly instead of a flag that’s hardcoded off upstream. - Multi-GPU addressing is per-tensor.
CudaTensorDatacarries its owndevice_id, andLayerNorm/Linear/gelu/Tensor::add/matmulall operate on the buffer’s actual device. - Swin Transformer and DeiT are buildable for the first time, behind new
swin/deitfeatures — both were fully implemented and both were completely unreachable before this release.
Technical Deep Dive: what 0.2.0 actually changed under the hood
1. trustformers-core — the GPU backend layer. The new gpu_ops::cuda::oxicuda::attention module (gather_heads_gpu_to_gpu, rope_neox_gpu_to_gpu, softmax_causal_gpu_to_gpu, attention_prefill_gpu_to_gpu, attention_decode_gpu_to_gpu, concat_v_cache_gpu_to_gpu, add_gpu_to_gpu) sits alongside a new gpu_ops::cuda::oxicuda::batched module (BatchedMatmulPlan, dispatch_oxicuda_matmul/dispatch_oxicuda_matmul_resident). CudaTensorData is restructured around the new BufferHandle — a breaking change: the old public buffer_id: BufferId field is now buffer: BufferHandle plus buffer_id()/device_id() accessors. OxicudaCudaBackend::new validates a requested device_id against the real enumerated device count instead of surfacing an opaque driver failure, and default_cuda_device_id() reads a new TRUSTFORMERS_CUDA_DEVICE environment variable for host-dispatched matmuls on multi-GPU machines. Underneath it all, oxicuda-{blas,dnn,memory,driver,metal,backend} moved 0.4.0 → 0.4.1, fixing a GEMM transpose-flag bug and a batched-GEMM launch-tuple corruption bug that made gemm_strided_batched produce incorrect results in 0.4.0 — part of the motivation for shipping the resident-attention and batched-matmul work in the same release.
2. trustformers-models — two new architecture families, and an honest breaking fix. swin/deit Cargo features mount SwinModel/SwinForImageClassification and DeiTModel/DeiTForImageClassification (with distillation-token support). Separately, mamba, rwkv, s4, falcon, stablelm, and linformer had Cargo features that existed but were never attached via #[cfg(feature = ...)] to their pub mod declarations, so they compiled unconditionally regardless of the flag; that’s fixed now, which means a build that relied on the old, accidental always-on behavior needs to add the feature explicitly. The all feature aggregate was also missing llama3_2 and mistral_v3 — --features all now actually builds every supported architecture.
3. trustformers (the umbrella crate) — three subsystems and ten pipelines come out of the basement. cache, finetuning, and loading were fully implemented modules that simply were never pub mod-declared; 0.2.0 mounts all three, plus ten trustformers::pipeline task modules — audio generation, document classification, feature extraction, image segmentation, speech recognition, table QA, text-to-image, video classification, visual grounding, zero-shot audio classification — that were in the same boat. Worth being precise about what shipped: the pipeline mounts are a real API-surface milestone, not a production-inference one — all ten currently return deterministic mock output pending real model backends. cache and finetuning are the real thing today (see Tips below).
4. Training, optimizers, tokenizers. trustformers-training::hpo is now mounted: MultiObjectiveHpo/ParetoFront/compute_pareto_front/hypervolume_indicator/non_domination_sort for Pareto-front search, plus AutoLrSelector/LrRangeTest for automatic learning-rate range tests. trustformers-optim re-exports 21 fsdp/optimizer_surgery/per_layer_quant types directly at its crate root (locked in by a new tests/crate_root_reexports.rs), and trustformers-tokenizers gains NFKCNormalizer/NFKDNormalizer alongside the existing NFC/NFD pair.
Getting Started
cargo add trustformers
use trustformers::prelude::*;
use trustformers::{AutoModel, AutoTokenizer, Tensor};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load model and tokenizer
let tokenizer = AutoTokenizer::from_pretrained("bert-base-uncased")?;
let model = AutoModel::from_pretrained("bert-base-uncased")?;
// Tokenize input
let tokenized = tokenizer.encode("Hello, Rust world!")?;
// AutoModel's `Model` impl is Tensor-in/Tensor-out, so wrap the token IDs
// as a Tensor before running inference.
let ids: Vec<f32> = tokenized.input_ids.iter().map(|&id| id as f32).collect();
let len = ids.len();
let inputs = Tensor::from_vec(ids, &[len])?;
let outputs = model.forward(inputs)?;
println!("Output shape: {:?}", outputs.shape());
Ok(())
}
To exercise 0.2.0’s new resident CUDA attention chain, move a GPT-2 model onto a GPU device explicitly:
// Cargo.toml: trustformers-core = { version = "0.2", features = ["cuda"] } // or "metal"
use trustformers_core::Device;
use trustformers_models::gpt2::{Gpt2Config, Gpt2Model};
let device = Device::CUDA(0); // or Device::Metal(0)
let mut model = Gpt2Model::new_with_device(Gpt2Config::default(), device)?;
model.weights_to_gpu_cuda(&device)?; // Metal: `weights_to_gpu`
let outputs = model.forward(inputs)?; // prefill AND decode now run the full
// resident attention chain on CUDA
On a multi-GPU host, point host-dispatched matmuls at a specific card without touching code:
TRUSTFORMERS_CUDA_DEVICE=1 cargo run --release --features cuda
What’s New in 0.2.0
Added
- CUDA gains a GPU-resident attention pipeline (new
gpu_ops::cuda::oxicuda::attentionmodule) — GPT-2 and GPT-NeoX use it for prefill, GPT-2 also for incremental KV-cached decode. - Batched/broadcasting CUDA matmul (
BatchedMatmulPlan), replacing the old 2D-only host-round-trip dispatcher. - Refcounted
BufferHandleGPU buffers that free automatically instead of leaking until an explicit cache clear;TRUSTFORMERS_CUDA_DEVICEenv var for multi-GPU ordinal selection. - Swin Transformer and DeiT buildable for the first time (
swin/deitfeatures);AutoModelForObjectDetection/AutoModelForImageSegmentationadded toautomodel_tasks. cache,finetuning, andloadingsubsystems, plus ten pipeline task modules, mounted into the umbrella crate (pipelines are mock implementations for now — see Tips).- Multi-objective hyperparameter optimization (Pareto-front search, automatic LR range tests) in
trustformers-training. trustformers-optimre-exports 21 FSDP/optimizer-surgery/per-layer-quantization types at its crate root;NFKCNormalizer/NFKDNormalizerland intrustformers-tokenizers.
Changed
- The
torch/tch(libtorch) backend is removed workspace-wide;candleremains the optional non-CPU tensor-framework integration. CudaTensorDatarestructured aroundBufferHandle(breaking);mamba/rwkv/s4/falcon/stablelm/linformerare now properly feature-gated (breaking — see Technical Deep Dive).oxicuda-{blas,dnn,memory,driver,metal,backend}0.4.0 → 0.4.1;oxiarc-{zstd,deflate,lz4,archive}0.3.3 → 0.3.5.- Dependency cleanup: dropped unused
scirs2-linalgworkspace-wide,scirs2-core’sgpufeature (GPU now runs entirely through oxicuda/Metal), and unusedhangul/scirs2integrations in the tokenizer/wasm crates.
Fixed
- A CUDA GPU-to-host read race that could silently return zero or garbage data (missing stream sync before device→host memcpy).
Device::best_available()/cuda_if_available()always returningDevice::CPUeven on capable hardware; multi-GPU tensors that could address the wrong device.SentencePieceTokenizer::from_pretrainedignoring its path argument and always returning a fabricated vocabulary; theallfeature aggregate intrustformers-modelsmissingllama3_2/mistral_v3.- Smaller honesty fixes: WASM quantization was a constant-multiply placeholder (now real affine quantize-dequantize), WASM device-capability probes returned hardcoded results (now query the real browser/device), and
OfflineModelPackManager::get_model_inforeturned fixed mock metadata (now queries the real HuggingFace Hub API).
Removed
trustformers-mobile::android_renderscript(~750 lines) — pre-Vulkan Android RenderScript, deprecated upstream by Google.- Roughly 9,500 more combined lines of orphaned, never-mounted duplicate source across
trustformers-models(a duplicateqwen2module),trustformers-optim, andtrustformers-mobile’s old federated-learning code — dead-code cleanup; none of it was reachable from any crate’slib.rseven before this release, so nothing shipped is lost.
Tips
- Pin your CUDA device on multi-GPU boxes.
TRUSTFORMERS_CUDA_DEVICE=1now steers host-dispatched matmuls to the ordinal you mean viadefault_cuda_device_id(), instead of always targeting device 0. - Re-check builds that relied on
mamba,rwkv,s4,falcon,stablelm, orlinformercompiling in without their feature flag. Those modules are now correctly feature-gated; add the feature (or build with--features all) if you were depending on the old, unintended always-on behavior. - LoRA and bottleneck adapters are mounted and real.
use trustformers::finetuning::{LoraConfig, LoraLinear};—LoraConfig::new(rank, alpha)plusLoraLinear::new(in_features, out_features, rank, &config)gets you a parameter-efficient adapter over a frozen base weight, no more digging through un-exported modules to find it. - Reach for
VersionedCacheinstead of hand-rolling eviction.trustformers::cache::{VersionedCache, VersionedCacheConfig, CacheEvictionPolicy}gives you LRU, LFU, TTL, and size-based eviction (CacheEvictionPolicy::{Lru, Lfu, Ttl, Size}) for weights or inference results. - Don’t route production traffic through the ten new pipeline modules yet.
audio_generation,text_to_image,speech_recognition, and the rest currently return deterministic mock output pending real model backends — 0.2.0 mounted their API surface, not their inference, so treat them as a scaffolding milestone. - If your
Cargo.tomlstill references thetorchfeature ortch, drop it. Both are gone workspace-wide;candleis the remaining optional non-CPU tensor-framework integration if you need one.
This is the foundation
TrustformeRS 0.2.0 rides on OxiCUDA 0.4.1 (bumped this release, fixing the GEMM transpose-flag and batched-GEMM launch-tuple bugs that motivated the new resident-attention and batched-matmul work above) for CUDA compute, oxicuda-metal for Apple Silicon, SciRS2 0.6.0 for numerics, OxiBLAS for Pure-Rust BLAS/LAPACK, OxiCode for serialization, and OxiARC 0.3.5 (oxiarc-zstd/-deflate/-lz4/-archive) for compression. It sits beside ToRSh, SkleaRS, and TenfloweRS in the COOLJAPAN model-training and serving stack.
Repository: https://github.com/cool-japan/trustformers
Star the repo if you want a CUDA backend that doesn’t quietly cut the corners Metal already learned not to cut — and a tensor stack with no C++ library left to link against. The era of leaking GPU caches and an untrusted libtorch dependency is over. Pure Rust GPU-accelerated transformers are here — fast, safe, and sovereign.
— KitaSan at COOLJAPAN OÜ
July 9, 2026