use std::collections::BTreeMap;
use std::sync::Mutex;
use crate::error::{Result, RuvLLMError};
#[derive(Debug, Clone, PartialEq)]
pub struct RdtConfig {
pub hidden_size: usize,
pub intermediate_size: usize,
pub num_heads: usize,
pub num_kv_heads: usize,
pub vocab_size: usize,
pub max_position_embeddings: usize,
pub rope_theta: f32,
pub rms_norm_eps: f64,
pub num_shared_blocks: usize,
pub max_loops: usize,
pub halt_threshold: f32,
}
impl Default for RdtConfig {
fn default() -> Self {
Self {
hidden_size: 256,
intermediate_size: 688,
num_heads: 8,
num_kv_heads: 8,
vocab_size: 1024,
max_position_embeddings: 2048,
rope_theta: 10_000.0,
rms_norm_eps: 1e-5,
num_shared_blocks: 1,
max_loops: 16,
halt_threshold: 0.9,
}
}
}
impl RdtConfig {
pub fn head_dim(&self) -> usize {
self.hidden_size / self.num_heads
}
pub fn gqa_ratio(&self) -> usize {
self.num_heads / self.num_kv_heads.max(1)
}
pub fn validate(&self) -> Result<()> {
if self.hidden_size == 0 || self.num_heads == 0 {
return Err(RuvLLMError::Config(
"RDT: hidden_size and num_heads must be non-zero".into(),
));
}
if self.hidden_size % self.num_heads != 0 {
return Err(RuvLLMError::Config(format!(
"RDT: hidden_size ({}) must be divisible by num_heads ({})",
self.hidden_size, self.num_heads
)));
}
if self.num_kv_heads == 0 || self.num_heads % self.num_kv_heads != 0 {
return Err(RuvLLMError::Config(format!(
"RDT: num_heads ({}) must be divisible by num_kv_heads ({})",
self.num_heads, self.num_kv_heads
)));
}
if self.num_shared_blocks == 0 {
return Err(RuvLLMError::Config(
"RDT: num_shared_blocks must be >= 1".into(),
));
}
if self.max_loops == 0 {
return Err(RuvLLMError::Config("RDT: max_loops must be >= 1".into()));
}
if !(self.halt_threshold > 0.0 && self.halt_threshold <= 1.0) {
return Err(RuvLLMError::Config(format!(
"RDT: halt_threshold ({}) must be in (0, 1]",
self.halt_threshold
)));
}
if self.max_loops < self.num_shared_blocks {
return Err(RuvLLMError::Config(format!(
"RDT: max_loops ({}) must be >= num_shared_blocks ({})",
self.max_loops, self.num_shared_blocks
)));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RdtCompatibilityError {
pub detected_architecture: String,
pub reason: String,
}
impl std::fmt::Display for RdtCompatibilityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"non-RDT GGUF rejected (architecture='{}'): {}. \
The RDT execution path requires weights natively trained for \
weight-sharing (ALBERT-style cross-layer sharing or an explicit \
RDT fine-tune). Running standard weights through a shared block \
produces garbage tokens.",
self.detected_architecture, self.reason
)
}
}
impl std::error::Error for RdtCompatibilityError {}
impl From<RdtCompatibilityError> for RuvLLMError {
fn from(e: RdtCompatibilityError) -> Self {
RuvLLMError::Model(e.to_string())
}
}
pub const RDT_RECURRENCE_KEYS: &[&str] = &[
"rdt.recurrent",
"rdt.weight_sharing",
"general.weight_sharing",
"recurrent_depth.enabled",
];
pub const RDT_ARCHITECTURES: &[&str] =
&["rdt", "recurrent_depth", "albert", "universal_transformer"];
pub fn validate_rdt_metadata(
metadata: &BTreeMap<String, String>,
) -> std::result::Result<(), RdtCompatibilityError> {
let arch = metadata
.get("general.architecture")
.map(|s| s.trim().to_lowercase())
.unwrap_or_default();
if RDT_ARCHITECTURES.contains(&arch.as_str()) {
return Ok(());
}
for key in RDT_RECURRENCE_KEYS {
if let Some(raw) = metadata.get(*key) {
if is_truthy(raw) {
return Ok(());
}
}
}
let reason = if arch.is_empty() {
"no 'general.architecture' and no recurrence flag found".to_string()
} else {
format!(
"architecture '{}' is not weight-sharing and no recurrence flag \
({:?}) was set",
arch, RDT_RECURRENCE_KEYS
)
};
Err(RdtCompatibilityError {
detected_architecture: if arch.is_empty() {
"<unknown>".to_string()
} else {
arch
},
reason,
})
}
fn is_truthy(raw: &str) -> bool {
matches!(
raw.trim().to_lowercase().as_str(),
"true" | "1" | "yes" | "on"
)
}
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct DepthStats {
pub samples: usize,
pub mean_inference_depth: f32,
pub max_inference_depth: usize,
pub min_inference_depth: usize,
}
impl Default for DepthStats {
fn default() -> Self {
Self {
samples: 0,
mean_inference_depth: 0.0,
max_inference_depth: 0,
min_inference_depth: 0,
}
}
}
#[derive(Debug, Default)]
struct DepthInner {
means: Vec<f32>,
maxes: Vec<usize>,
mins: Vec<usize>,
}
#[derive(Debug, Default)]
pub struct DepthTelemetry {
inner: Mutex<DepthInner>,
}
impl DepthTelemetry {
pub fn new() -> Self {
Self::default()
}
pub fn record(&self, token_depths: &[usize]) {
if token_depths.is_empty() {
return;
}
let sum: usize = token_depths.iter().sum();
let mean = sum as f32 / token_depths.len() as f32;
let max = *token_depths.iter().max().unwrap();
let min = *token_depths.iter().min().unwrap();
let mut inner = self.inner.lock().unwrap();
inner.means.push(mean);
inner.maxes.push(max);
inner.mins.push(min);
}
pub fn stats(&self) -> DepthStats {
let inner = self.inner.lock().unwrap();
let samples = inner.means.len();
if samples == 0 {
return DepthStats::default();
}
let mean = inner.means.iter().sum::<f32>() / samples as f32;
DepthStats {
samples,
mean_inference_depth: mean,
max_inference_depth: inner.maxes.iter().copied().max().unwrap_or(0),
min_inference_depth: inner.mins.iter().copied().min().unwrap_or(0),
}
}
pub fn reset(&self) {
let mut inner = self.inner.lock().unwrap();
inner.means.clear();
inner.maxes.clear();
inner.mins.clear();
}
pub fn report_json(&self) -> String {
serde_json::to_string(&self.stats()).unwrap_or_else(|_| "{}".to_string())
}
}
#[cfg(feature = "candle")]
pub use candle_impl::{HaltingRouter, RdtCache, RdtModel, SharedBlock};
#[cfg(feature = "candle")]
mod candle_impl {
use super::*;
use candle_core::{DType, Device, IndexOp, Tensor, D};
use candle_nn::{ops, Embedding, Linear, Module, RmsNorm, VarBuilder};
pub struct HaltingRouter {
proj: Linear,
threshold: f32,
}
impl HaltingRouter {
pub fn new(proj: Linear, threshold: f32) -> Self {
Self { proj, threshold }
}
pub fn load(vb: VarBuilder, hidden_size: usize, threshold: f32) -> Result<Self> {
let proj = candle_nn::linear(hidden_size, 1, vb.pp("proj")).map_err(cand)?;
Ok(Self::new(proj, threshold))
}
pub fn p_halt(&self, hidden_state: &Tensor) -> Result<Tensor> {
let logits = self.proj.forward(hidden_state).map_err(cand)?;
ops::sigmoid(&logits).map_err(cand)
}
pub fn compute_halt(&self, hidden_state: &Tensor) -> Result<(Tensor, bool)> {
let p_halt = self.p_halt(hidden_state)?;
let max_p = p_halt
.max_all()
.map_err(cand)?
.to_scalar::<f32>()
.map_err(cand)?;
Ok((p_halt, max_p >= self.threshold))
}
}
pub struct SharedBlock {
input_norm: RmsNorm,
q_proj: Linear,
k_proj: Linear,
v_proj: Linear,
o_proj: Linear,
post_attn_norm: RmsNorm,
gate_proj: Linear,
up_proj: Linear,
down_proj: Linear,
num_heads: usize,
num_kv_heads: usize,
head_dim: usize,
}
impl SharedBlock {
pub fn load(vb: VarBuilder, cfg: &RdtConfig) -> Result<Self> {
let h = cfg.hidden_size;
let head_dim = cfg.head_dim();
let q_out = cfg.num_heads * head_dim;
let kv_out = cfg.num_kv_heads * head_dim;
let input_norm =
candle_nn::rms_norm(h, cfg.rms_norm_eps, vb.pp("input_layernorm")).map_err(cand)?;
let attn = vb.pp("self_attn");
let q_proj = candle_nn::linear_no_bias(h, q_out, attn.pp("q_proj")).map_err(cand)?;
let k_proj = candle_nn::linear_no_bias(h, kv_out, attn.pp("k_proj")).map_err(cand)?;
let v_proj = candle_nn::linear_no_bias(h, kv_out, attn.pp("v_proj")).map_err(cand)?;
let o_proj = candle_nn::linear_no_bias(q_out, h, attn.pp("o_proj")).map_err(cand)?;
let post_attn_norm =
candle_nn::rms_norm(h, cfg.rms_norm_eps, vb.pp("post_attention_layernorm"))
.map_err(cand)?;
let mlp = vb.pp("mlp");
let gate_proj =
candle_nn::linear_no_bias(h, cfg.intermediate_size, mlp.pp("gate_proj"))
.map_err(cand)?;
let up_proj = candle_nn::linear_no_bias(h, cfg.intermediate_size, mlp.pp("up_proj"))
.map_err(cand)?;
let down_proj =
candle_nn::linear_no_bias(cfg.intermediate_size, h, mlp.pp("down_proj"))
.map_err(cand)?;
Ok(Self {
input_norm,
q_proj,
k_proj,
v_proj,
o_proj,
post_attn_norm,
gate_proj,
up_proj,
down_proj,
num_heads: cfg.num_heads,
num_kv_heads: cfg.num_kv_heads,
head_dim,
})
}
pub fn forward(
&self,
xs: &Tensor,
cos: &Tensor,
sin: &Tensor,
mask: &Tensor,
) -> Result<Tensor> {
let (out, _kv) = self.forward_cached(xs, cos, sin, mask, None)?;
Ok(out)
}
pub fn forward_cached(
&self,
xs: &Tensor,
cos: &Tensor,
sin: &Tensor,
mask: &Tensor,
past: Option<&RdtKvCache>,
) -> Result<(Tensor, RdtKvCache)> {
let (b, seq, _h) = xs.dims3().map_err(cand)?;
let normed = self.input_norm.forward(xs).map_err(cand)?;
let (attn_out, kv) = self.attention(&normed, cos, sin, mask, past, b, seq)?;
let xs = (xs + attn_out).map_err(cand)?;
let normed = self.post_attn_norm.forward(&xs).map_err(cand)?;
let mlp_out = self.mlp(&normed)?;
let out = (xs + mlp_out).map_err(cand)?;
Ok((out, kv))
}
fn attention(
&self,
xs: &Tensor,
cos: &Tensor,
sin: &Tensor,
mask: &Tensor,
past: Option<&RdtKvCache>,
b: usize,
seq: usize,
) -> Result<(Tensor, RdtKvCache)> {
let q = self.q_proj.forward(xs).map_err(cand)?;
let k = self.k_proj.forward(xs).map_err(cand)?;
let v = self.v_proj.forward(xs).map_err(cand)?;
let q = q
.reshape((b, seq, self.num_heads, self.head_dim))
.map_err(cand)?
.transpose(1, 2)
.map_err(cand)?
.contiguous()
.map_err(cand)?;
let k = k
.reshape((b, seq, self.num_kv_heads, self.head_dim))
.map_err(cand)?
.transpose(1, 2)
.map_err(cand)?
.contiguous()
.map_err(cand)?;
let v = v
.reshape((b, seq, self.num_kv_heads, self.head_dim))
.map_err(cand)?
.transpose(1, 2)
.map_err(cand)?
.contiguous()
.map_err(cand)?;
let q = apply_rope(&q, cos, sin)?;
let k_cur = apply_rope(&k, cos, sin)?;
let n_rep = self.num_heads / self.num_kv_heads;
let (k, v, new_kv) = match past {
Some(RdtKvCache::Prealloc { k: buf_k, v: buf_v, seq_len, max_seq }) => {
let k_cur_rep = repeat_kv(&k_cur, n_rep)?; let v_rep_new = repeat_kv(&v, n_rep)?;
let idx =
Tensor::full(*seq_len as u32, k_cur_rep.shape(), k_cur_rep.device())
.map_err(cand)?;
buf_k.scatter_set(&idx, &k_cur_rep, 2).map_err(cand)?;
buf_v.scatter_set(&idx, &v_rep_new, 2).map_err(cand)?;
let new_seq = seq_len + seq;
let k_v = buf_k.narrow(2, 0, new_seq).map_err(cand)?;
let v_v = buf_v.narrow(2, 0, new_seq).map_err(cand)?;
let cache = RdtKvCache::Prealloc {
k: buf_k.clone(),
v: buf_v.clone(),
seq_len: new_seq,
max_seq: *max_seq,
};
(k_v, v_v, cache)
}
Some(RdtKvCache::Cat(pk, pv)) => {
let k_f = Tensor::cat(&[pk, &k_cur], 2).map_err(cand)?;
let v_f = Tensor::cat(&[pv, &v], 2).map_err(cand)?;
let k_r = repeat_kv(&k_f, n_rep)?;
let v_r = repeat_kv(&v_f, n_rep)?;
(k_r, v_r, RdtKvCache::Cat(k_f, v_f))
}
None => {
let k_r = repeat_kv(&k_cur, n_rep)?;
let v_r = repeat_kv(&v, n_rep)?;
(k_r, v_r, RdtKvCache::Cat(k_cur, v))
}
};
let scale = 1.0 / (self.head_dim as f64).sqrt();
let scores = (q.matmul(&k.transpose(2, 3).map_err(cand)?).map_err(cand)? * scale)
.map_err(cand)?;
let scores = scores.broadcast_add(mask).map_err(cand)?;
let probs = ops::softmax_last_dim(&scores).map_err(cand)?;
let ctx = probs.matmul(&v).map_err(cand)?; let ctx = ctx
.transpose(1, 2)
.map_err(cand)?
.contiguous()
.map_err(cand)?
.reshape((b, seq, self.num_heads * self.head_dim))
.map_err(cand)?;
let out = self.o_proj.forward(&ctx).map_err(cand)?;
Ok((out, new_kv))
}
fn mlp(&self, xs: &Tensor) -> Result<Tensor> {
let gate = self.gate_proj.forward(xs).map_err(cand)?;
let gate = ops::silu(&gate).map_err(cand)?;
let up = self.up_proj.forward(xs).map_err(cand)?;
let hidden = (gate * up).map_err(cand)?;
self.down_proj.forward(&hidden).map_err(cand)
}
}
pub struct RdtModel {
embed_tokens: Embedding,
shared_blocks: Vec<SharedBlock>,
halting_router: HaltingRouter,
ln_f: RmsNorm,
lm_head: Linear,
cfg: RdtConfig,
device: Device,
dtype: DType,
rope_cos: Tensor,
rope_sin: Tensor,
causal_mask_cache: Tensor,
pub telemetry: DepthTelemetry,
}
impl RdtModel {
pub fn load(vb: VarBuilder, cfg: RdtConfig) -> Result<Self> {
cfg.validate()?;
let device = vb.device().clone();
let dtype = vb.dtype();
let embed_tokens =
candle_nn::embedding(cfg.vocab_size, cfg.hidden_size, vb.pp("embed_tokens"))
.map_err(cand)?;
let mut shared_blocks = Vec::with_capacity(cfg.num_shared_blocks);
let blocks_vb = vb.pp("shared_blocks");
for i in 0..cfg.num_shared_blocks {
shared_blocks.push(SharedBlock::load(blocks_vb.pp(i), &cfg)?);
}
let halting_router =
HaltingRouter::load(vb.pp("halting_router"), cfg.hidden_size, cfg.halt_threshold)?;
let ln_f = candle_nn::rms_norm(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("norm"))
.map_err(cand)?;
let lm_head =
candle_nn::linear_no_bias(cfg.hidden_size, cfg.vocab_size, vb.pp("lm_head"))
.map_err(cand)?;
let head_dim = cfg.head_dim();
let max_pos = cfg.max_position_embeddings;
let (rope_cos, rope_sin) = {
let half = head_dim / 2;
let theta = cfg.rope_theta as f64;
let inv_freq: Vec<f32> = (0..half)
.map(|i| (1.0 / theta.powf(2.0 * i as f64 / head_dim as f64)) as f32)
.collect();
let inv_freq =
Tensor::from_vec(inv_freq, (1, half), &device).map_err(cand)?;
let positions: Vec<f32> = (0..max_pos).map(|p| p as f32).collect();
let positions =
Tensor::from_vec(positions, (max_pos, 1), &device).map_err(cand)?;
let freqs = positions.matmul(&inv_freq).map_err(cand)?;
let freqs =
Tensor::cat(&[&freqs, &freqs], D::Minus1).map_err(cand)?;
let cos = freqs.cos().map_err(cand)?.to_dtype(dtype).map_err(cand)?;
let sin = freqs.sin().map_err(cand)?.to_dtype(dtype).map_err(cand)?;
(cos, sin)
};
let causal_mask_cache = {
let n = max_pos;
let mut data = vec![0f32; n * n];
for i in 0..n {
for j in (i + 1)..n {
data[i * n + j] = f32::NEG_INFINITY;
}
}
Tensor::from_vec(data, (n, n), &device)
.map_err(cand)?
.to_dtype(dtype)
.map_err(cand)?
};
Ok(Self {
embed_tokens,
shared_blocks,
halting_router,
ln_f,
lm_head,
cfg,
device,
dtype,
rope_cos,
rope_sin,
causal_mask_cache,
telemetry: DepthTelemetry::new(),
})
}
pub fn config(&self) -> &RdtConfig {
&self.cfg
}
pub fn forward(&self, input_ids: &Tensor) -> Result<Tensor> {
let mut cache = RdtCache::new();
self.forward_cached(input_ids, &mut cache)
}
pub fn forward_cached(&self, input_ids: &Tensor, cache: &mut RdtCache) -> Result<Tensor> {
let (b, seq) = input_ids.dims2().map_err(cand)?;
let offset = cache.seq_len;
let xs = self.embed_tokens.forward(input_ids).map_err(cand)?;
let xs = xs.to_dtype(self.dtype).map_err(cand)?;
let cos = self.rope_cos.narrow(0, offset, seq).map_err(cand)?;
let sin = self.rope_sin.narrow(0, offset, seq).map_err(cand)?;
let mask = self
.causal_mask_cache
.narrow(0, offset, seq)
.map_err(cand)?
.narrow(1, 0, offset + seq)
.map_err(cand)?;
let (hidden, kv) =
self.recurrent_loop(&xs, &cos, &sin, &mask, cache.kv.as_ref(), b, seq)?;
cache.kv = Some(kv);
cache.seq_len += seq;
let normed = self.ln_f.forward(&hidden).map_err(cand)?;
self.lm_head.forward(&normed).map_err(cand)
}
pub fn generate(
&self,
prompt_ids: &[u32],
max_new_tokens: usize,
eos: Option<u32>,
) -> Result<Vec<u32>> {
if prompt_ids.is_empty() {
return Err(RuvLLMError::Generation("empty prompt".into()));
}
let mut cache =
RdtCache::with_prealloc(&self.cfg, 1, &self.device, self.dtype)
.unwrap_or_else(|_| RdtCache::new());
let prompt =
Tensor::from_slice(prompt_ids, (1, prompt_ids.len()), &self.device)
.map_err(cand)?;
let logits = self.forward_cached(&prompt, &mut cache)?;
let mut next = last_argmax(&logits)?;
let mut out = Vec::with_capacity(max_new_tokens);
for _ in 0..max_new_tokens {
out.push(next);
if Some(next) == eos {
break;
}
let step =
Tensor::from_slice(&[next], (1, 1), &self.device).map_err(cand)?;
let logits = self.forward_cached(&step, &mut cache)?;
next = last_argmax(&logits)?;
}
Ok(out)
}
pub fn generate_sampled(
&self,
prompt_ids: &[u32],
max_new_tokens: usize,
eos: Option<u32>,
sampling: crate::models::sampling::SamplingConfig,
) -> Result<Vec<u32>> {
if prompt_ids.is_empty() {
return Err(RuvLLMError::Generation("empty prompt".into()));
}
let top_k_transfer = if sampling.top_k > 0 {
sampling.top_k
} else {
512.min(self.cfg.vocab_size)
};
let mut sampler = crate::models::sampling::Sampler::new(sampling);
let mut cache =
RdtCache::with_prealloc(&self.cfg, 1, &self.device, self.dtype)
.unwrap_or_else(|_| RdtCache::new());
let mut history: Vec<u32> = prompt_ids.to_vec();
let prompt =
Tensor::from_slice(prompt_ids, (1, prompt_ids.len()), &self.device)
.map_err(cand)?;
let logits = self.forward_cached(&prompt, &mut cache)?;
let (vals, idxs) = last_logits_topk(&logits, top_k_transfer, &self.device)?;
let mut next = sampler.sample_topk(&vals, &idxs, &history);
let mut out = Vec::with_capacity(max_new_tokens);
for _ in 0..max_new_tokens {
out.push(next);
history.push(next);
if Some(next) == eos {
break;
}
let step =
Tensor::from_slice(&[next], (1, 1), &self.device).map_err(cand)?;
let logits = self.forward_cached(&step, &mut cache)?;
let (vals, idxs) =
last_logits_topk(&logits, top_k_transfer, &self.device)?;
next = sampler.sample_topk(&vals, &idxs, &history);
}
Ok(out)
}
pub fn generate_stream_sampled(
&self,
prompt_ids: &[u32],
max_new_tokens: usize,
eos: Option<u32>,
sampling: crate::models::sampling::SamplingConfig,
mut on_token: impl FnMut(u32) -> bool,
) -> Result<()> {
if prompt_ids.is_empty() {
return Err(RuvLLMError::Generation("empty prompt".into()));
}
let top_k_transfer = if sampling.top_k > 0 {
sampling.top_k
} else {
512.min(self.cfg.vocab_size)
};
let mut sampler = crate::models::sampling::Sampler::new(sampling);
let mut cache =
RdtCache::with_prealloc(&self.cfg, 1, &self.device, self.dtype)
.unwrap_or_else(|_| RdtCache::new());
let mut history: Vec<u32> = prompt_ids.to_vec();
let prompt =
Tensor::from_slice(prompt_ids, (1, prompt_ids.len()), &self.device)
.map_err(cand)?;
let logits = self.forward_cached(&prompt, &mut cache)?;
let (vals, idxs) = last_logits_topk(&logits, top_k_transfer, &self.device)?;
let mut next = sampler.sample_topk(&vals, &idxs, &history);
for _ in 0..max_new_tokens {
if !on_token(next) {
break;
}
history.push(next);
if Some(next) == eos {
break;
}
let step =
Tensor::from_slice(&[next], (1, 1), &self.device).map_err(cand)?;
let logits = self.forward_cached(&step, &mut cache)?;
let (vals, idxs) =
last_logits_topk(&logits, top_k_transfer, &self.device)?;
next = sampler.sample_topk(&vals, &idxs, &history);
}
Ok(())
}
fn recurrent_loop(
&self,
xs: &Tensor,
cos: &Tensor,
sin: &Tensor,
mask: &Tensor,
past: Option<&RdtKvCache>,
b: usize,
seq: usize,
) -> Result<(Tensor, RdtKvCache)> {
let n = b * seq;
let mut hidden = xs.clone();
let mut running_f32 =
Tensor::ones((b, seq, 1), DType::F32, &self.device).map_err(cand)?;
let mut depth_f32 =
Tensor::zeros((b, seq, 1), DType::F32, &self.device).map_err(cand)?;
let mut last_kv: Option<RdtKvCache> = None;
let max_loops = self.cfg.max_loops;
for step in 0..max_loops {
let block = &self.shared_blocks[step % self.shared_blocks.len()];
let (candidate, kv) = block.forward_cached(&hidden, cos, sin, mask, past)?;
last_kv = Some(kv);
let running_typed = running_f32.to_dtype(self.dtype).map_err(cand)?;
let halted_typed =
(running_typed.ones_like().map_err(cand)? - &running_typed).map_err(cand)?;
hidden = (candidate.broadcast_mul(&running_typed).map_err(cand)?
+ hidden.broadcast_mul(&halted_typed).map_err(cand)?)
.map_err(cand)?;
depth_f32 = (&depth_f32 + &running_f32).map_err(cand)?;
let p_halt = self.halting_router.p_halt(&hidden)?;
let p_halt_f32 = p_halt.to_dtype(DType::F32).map_err(cand)?;
let should_halt = p_halt_f32
.ge(self.cfg.halt_threshold as f64)
.map_err(cand)?
.to_dtype(DType::F32)
.map_err(cand)?;
let newly_halted = (&should_halt * &running_f32).map_err(cand)?;
running_f32 = (&running_f32 - &newly_halted).map_err(cand)?;
tracing::trace!(step, "rdt loop iteration");
let any_running = running_f32
.sum_all()
.map_err(cand)?
.to_scalar::<f32>()
.map_err(cand)?
> 0.5;
if !any_running {
break;
}
}
let depth_vec: Vec<f32> = depth_f32
.reshape((n,))
.map_err(cand)?
.to_vec1()
.map_err(cand)?;
let depth: Vec<usize> = depth_vec.into_iter().map(|d| d as usize).collect();
self.telemetry.record(&depth);
let kv = last_kv.expect("at least one loop iteration runs");
Ok((hidden, kv))
}
}
pub enum RdtKvCache {
Cat(Tensor, Tensor),
Prealloc {
k: Tensor,
v: Tensor,
seq_len: usize,
max_seq: usize,
},
}
impl RdtKvCache {
pub fn seq_len(&self) -> usize {
match self {
RdtKvCache::Cat(k, _) => k.dim(2).unwrap_or(0),
RdtKvCache::Prealloc { seq_len, .. } => *seq_len,
}
}
}
pub struct RdtCache {
pub(crate) kv: Option<RdtKvCache>,
seq_len: usize,
}
impl Default for RdtCache {
fn default() -> Self {
Self { kv: None, seq_len: 0 }
}
}
impl RdtCache {
pub fn new() -> Self {
Self::default()
}
pub fn with_prealloc(
cfg: &RdtConfig,
b: usize,
device: &Device,
dtype: DType,
) -> Result<Self> {
let max_seq = cfg.max_position_embeddings;
let n_heads = cfg.num_heads;
let head_dim = cfg.head_dim();
let k = Tensor::zeros((b, n_heads, max_seq, head_dim), dtype, device).map_err(cand)?;
let v = Tensor::zeros((b, n_heads, max_seq, head_dim), dtype, device).map_err(cand)?;
Ok(Self {
kv: Some(RdtKvCache::Prealloc { k, v, seq_len: 0, max_seq }),
seq_len: 0,
})
}
pub fn len(&self) -> usize {
self.seq_len
}
pub fn is_empty(&self) -> bool {
self.seq_len == 0
}
pub fn reset(&mut self) {
if let Some(RdtKvCache::Prealloc { seq_len, .. }) = &mut self.kv {
*seq_len = 0;
} else {
self.kv = None;
}
self.seq_len = 0;
}
}
fn last_logits_topk(
logits: &Tensor,
k: usize,
_device: &Device,
) -> Result<(Vec<f32>, Vec<u32>)> {
let (_b, seq, vocab) = logits.dims3().map_err(cand)?;
let last = logits
.i((0, seq - 1))
.map_err(cand)?
.to_dtype(DType::F32)
.map_err(cand)?
.contiguous()
.map_err(cand)?;
let k = if k == 0 || k >= vocab { vocab } else { k };
let (vals, idxs) = last.sort_last_dim(false).map_err(cand)?;
let vals_k: Vec<f32> = vals
.narrow(D::Minus1, 0, k)
.map_err(cand)?
.to_vec1()
.map_err(cand)?;
let idxs_k: Vec<u32> = idxs
.narrow(D::Minus1, 0, k)
.map_err(cand)?
.to_vec1()
.map_err(cand)?;
Ok((vals_k, idxs_k))
}
fn last_argmax(logits: &Tensor) -> Result<u32> {
let (_b, seq, _v) = logits.dims3().map_err(cand)?;
let last = logits.i((0, seq - 1)).map_err(cand)?; last.argmax(D::Minus1)
.map_err(cand)?
.to_scalar::<u32>()
.map_err(cand)
}
fn apply_rope(x: &Tensor, cos: &Tensor, sin: &Tensor) -> Result<Tensor> {
let (_b, _n, seq, hd) = x.dims4().map_err(cand)?;
let cos = cos
.narrow(0, 0, seq)
.map_err(cand)?
.reshape((1, 1, seq, hd))
.map_err(cand)?;
let sin = sin
.narrow(0, 0, seq)
.map_err(cand)?
.reshape((1, 1, seq, hd))
.map_err(cand)?;
let rotated = rotate_half(x)?;
let out = (x.broadcast_mul(&cos).map_err(cand)?
+ rotated.broadcast_mul(&sin).map_err(cand)?)
.map_err(cand)?;
Ok(out)
}
fn rotate_half(x: &Tensor) -> Result<Tensor> {
let hd = x.dim(D::Minus1).map_err(cand)?;
let half = hd / 2;
let x1 = x.narrow(D::Minus1, 0, half).map_err(cand)?;
let x2 = x.narrow(D::Minus1, half, hd - half).map_err(cand)?;
let neg_x2 = x2.neg().map_err(cand)?;
Tensor::cat(&[&neg_x2, &x1], D::Minus1).map_err(cand)
}
fn repeat_kv(x: &Tensor, n_rep: usize) -> Result<Tensor> {
if n_rep == 1 {
return Ok(x.clone());
}
let (b, kv_heads, seq, hd) = x.dims4().map_err(cand)?;
x.unsqueeze(2)
.map_err(cand)?
.expand((b, kv_heads, n_rep, seq, hd))
.map_err(cand)?
.reshape((b, kv_heads * n_rep, seq, hd))
.map_err(cand)
}
fn cand(e: candle_core::Error) -> RuvLLMError {
RuvLLMError::Model(format!("candle (rdt): {e}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn meta(pairs: &[(&str, &str)]) -> BTreeMap<String, String> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
#[test]
fn config_default_is_valid() {
assert!(RdtConfig::default().validate().is_ok());
}
#[test]
fn config_rejects_bad_shapes() {
let mut c = RdtConfig::default();
c.num_heads = 7; assert!(c.validate().is_err());
let mut c = RdtConfig::default();
c.halt_threshold = 1.5;
assert!(c.validate().is_err());
let mut c = RdtConfig::default();
c.max_loops = 0;
assert!(c.validate().is_err());
let mut c = RdtConfig::default();
c.num_heads = 8;
c.num_kv_heads = 3; assert!(c.validate().is_err());
}
#[test]
fn honest_boundary_rejects_llama() {
let m = meta(&[("general.architecture", "llama")]);
let err = validate_rdt_metadata(&m).unwrap_err();
assert_eq!(err.detected_architecture, "llama");
assert!(err.to_string().contains("garbage tokens"));
}
#[test]
fn honest_boundary_rejects_qwen2() {
let m = meta(&[("general.architecture", "qwen2")]);
assert!(validate_rdt_metadata(&m).is_err());
}
#[test]
fn honest_boundary_rejects_missing_metadata() {
let m = meta(&[]);
assert!(validate_rdt_metadata(&m).is_err());
}
#[test]
fn honest_boundary_accepts_rdt_architecture() {
for arch in RDT_ARCHITECTURES {
let m = meta(&[("general.architecture", arch)]);
assert!(validate_rdt_metadata(&m).is_ok(), "arch {arch} should pass");
}
}
#[test]
fn honest_boundary_accepts_recurrence_flag() {
let m = meta(&[("general.architecture", "llama"), ("rdt.recurrent", "true")]);
assert!(validate_rdt_metadata(&m).is_ok());
let m = meta(&[("recurrent_depth.enabled", "1")]);
assert!(validate_rdt_metadata(&m).is_ok());
}
#[test]
fn honest_boundary_rejects_falsey_flag() {
let m = meta(&[
("general.architecture", "llama"),
("rdt.recurrent", "false"),
]);
assert!(validate_rdt_metadata(&m).is_err());
}
#[test]
fn telemetry_aggregates() {
let t = DepthTelemetry::new();
t.record(&[2, 4, 6]); t.record(&[1, 1, 1]); let s = t.stats();
assert_eq!(s.samples, 2);
assert_eq!(s.max_inference_depth, 6);
assert_eq!(s.min_inference_depth, 1);
assert!((s.mean_inference_depth - 2.5).abs() < 1e-6);
t.reset();
assert_eq!(t.stats().samples, 0);
}
#[test]
fn telemetry_ignores_empty() {
let t = DepthTelemetry::new();
t.record(&[]);
assert_eq!(t.stats().samples, 0);
}
#[cfg(feature = "candle")]
mod candle_tests {
use super::*;
use candle_core::{DType, Device, IndexOp, Tensor};
use candle_nn::VarBuilder;
fn tiny_cfg() -> RdtConfig {
RdtConfig {
hidden_size: 32,
intermediate_size: 64,
num_heads: 4,
num_kv_heads: 2,
vocab_size: 48,
max_position_embeddings: 64,
rope_theta: 10_000.0,
rms_norm_eps: 1e-5,
num_shared_blocks: 1,
max_loops: 8,
halt_threshold: 0.9,
}
}
#[test]
fn forward_shapes_are_correct() {
let dev = Device::Cpu;
let cfg = tiny_cfg();
let vb = VarBuilder::zeros(DType::F32, &dev);
let model = RdtModel::load(vb, cfg.clone()).expect("load");
let input_ids = Tensor::from_vec(vec![1u32, 2, 3, 4, 5], (1, 5), &dev).unwrap();
let logits = model.forward(&input_ids).expect("forward");
assert_eq!(logits.dims(), &[1, 5, cfg.vocab_size]);
}
#[test]
fn zero_router_runs_to_max_loops() {
let dev = Device::Cpu;
let cfg = tiny_cfg();
let vb = VarBuilder::zeros(DType::F32, &dev);
let model = RdtModel::load(vb, cfg.clone()).unwrap();
let input_ids = Tensor::from_vec(vec![1u32, 2, 3], (1, 3), &dev).unwrap();
let _ = model.forward(&input_ids).unwrap();
let stats = model.telemetry.stats();
assert_eq!(stats.samples, 1);
assert_eq!(stats.max_inference_depth, cfg.max_loops);
assert_eq!(stats.min_inference_depth, cfg.max_loops);
}
#[test]
fn batched_forward_works() {
let dev = Device::Cpu;
let cfg = tiny_cfg();
let vb = VarBuilder::zeros(DType::F32, &dev);
let model = RdtModel::load(vb, cfg.clone()).unwrap();
let input_ids = Tensor::from_vec(vec![1u32, 2, 3, 4, 5, 6], (2, 3), &dev).unwrap();
let logits = model.forward(&input_ids).unwrap();
assert_eq!(logits.dims(), &[2, 3, cfg.vocab_size]);
assert_eq!(model.telemetry.stats().samples, 1);
}
#[test]
fn multi_block_sharing_loads_and_runs() {
let dev = Device::Cpu;
let mut cfg = tiny_cfg();
cfg.num_shared_blocks = 2; let vb = VarBuilder::zeros(DType::F32, &dev);
let model = RdtModel::load(vb, cfg.clone()).unwrap();
let input_ids = Tensor::from_vec(vec![7u32, 8, 9, 10], (1, 4), &dev).unwrap();
let logits = model.forward(&input_ids).unwrap();
assert_eq!(logits.dims(), &[1, 4, cfg.vocab_size]);
}
#[test]
fn forward_output_is_finite() {
let dev = Device::Cpu;
let cfg = tiny_cfg();
let vb = VarBuilder::zeros(DType::F32, &dev);
let model = RdtModel::load(vb, cfg).unwrap();
let input_ids = Tensor::from_vec(vec![1u32, 2, 3, 4], (1, 4), &dev).unwrap();
let logits = model.forward(&input_ids).unwrap();
let flat: Vec<f32> = logits.flatten_all().unwrap().to_vec1().unwrap();
assert!(flat.iter().all(|x| x.is_finite()), "logits must be finite");
}
#[test]
fn generate_produces_tokens() {
let dev = Device::Cpu;
let cfg = tiny_cfg();
let vb = VarBuilder::zeros(DType::F32, &dev);
let model = RdtModel::load(vb, cfg.clone()).unwrap();
let out = model.generate(&[1, 2, 3], 5, None).unwrap();
assert_eq!(out.len(), 5);
assert!(out.iter().all(|&t| (t as usize) < cfg.vocab_size));
}
#[test]
fn cached_decode_matches_full_at_single_loop() {
use candle_nn::VarMap;
let dev = Device::Cpu;
let mut cfg = tiny_cfg();
cfg.max_loops = 1;
let varmap = VarMap::new();
let vb = VarBuilder::from_varmap(&varmap, DType::F32, &dev);
let model = RdtModel::load(vb, cfg.clone()).unwrap();
let ids = vec![3u32, 7, 1, 9, 4];
let full_ids = Tensor::from_vec(ids.clone(), (1, ids.len()), &dev).unwrap();
let full = model.forward(&full_ids).unwrap();
let full_last: Vec<f32> = full.i((0, ids.len() - 1)).unwrap().to_vec1().unwrap();
let mut cache = RdtCache::new();
let mut last: Vec<f32> = vec![];
for (k, &tok) in ids.iter().enumerate() {
let step = Tensor::from_vec(vec![tok], (1, 1), &dev).unwrap();
let logits = model.forward_cached(&step, &mut cache).unwrap();
assert_eq!(cache.len(), k + 1);
last = logits.i((0, 0)).unwrap().to_vec1().unwrap();
}
let max_diff = full_last
.iter()
.zip(last.iter())
.map(|(a, b)| (a - b).abs())
.fold(0f32, f32::max);
assert!(max_diff < 1e-3, "RDT KV-cache decode diverged: {max_diff}");
}
#[test]
fn telemetry_report_json_roundtrips() {
let dev = Device::Cpu;
let cfg = tiny_cfg();
let vb = VarBuilder::zeros(DType::F32, &dev);
let model = RdtModel::load(vb, cfg).unwrap();
let ids = Tensor::from_vec(vec![1u32, 2, 3], (1, 3), &dev).unwrap();
let _ = model.forward(&ids).unwrap();
let json = model.telemetry.report_json();
assert!(json.contains("mean_inference_depth"));
let parsed: DepthStats = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.samples, 1);
}
}
}