use crate::inference_context::{DecodeSession, DecodeTokenData, InferenceContext, KvCache, KvSlot};
use crate::lazy::Tensor;
use crate::{Device, Result};
use fuel_ir::{DType, Shape};
use std::sync::Arc;
pub trait PersistentDecodeModel {
fn decode_n_layers(&self) -> usize;
fn build_decode_token_data(
&self,
device: &Device,
cached_len: usize,
tokens: &[u32],
session: &DecodeSession,
cache: &KvCache,
rope_inv_freq: Option<&[f64]>,
) -> Result<DecodeTokenData>;
}
pub fn rebind_and_realize_prebuilt<M: PersistentDecodeModel + ?Sized>(
model: &M,
tokens: &[u32],
cache: &mut KvCache,
ctx: &InferenceContext,
session: &Option<DecodeSession>,
rope_inv_freq: Option<&[f64]>,
) -> Result<Vec<f32>> {
let seq = tokens.len();
let cached_len = cache.cached_len;
let device = ctx.device().clone();
let s = session.as_ref().expect("session is Some");
let data =
model.build_decode_token_data(&device, cached_len, tokens, s, cache, rope_inv_freq)?;
let sym_env = s.per_token_sym_env(cached_len)?;
let logits_vec = s.realize_token(&device, data, &sym_env)?;
cache.cached_len += seq;
for li in 0..model.decode_n_layers() {
cache.bump_version(li, KvSlot::K);
cache.bump_version(li, KvSlot::V);
}
Ok(logits_vec)
}
mod shared_build_path_docs {}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum DataConsts {
Baked,
Rebindable,
}
impl DataConsts {
fn entry(self) -> &'static str {
match self {
Self::Baked => "forward_with_kv_context",
Self::Rebindable => "forward_with_kv_context_persistent",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DecodeDims {
pub n_layers: usize,
pub n_kv_heads: usize,
pub head_dim: usize,
pub hidden: usize,
pub vocab: usize,
pub rope_width: usize,
pub embed_scale: Option<f64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MaskPlan {
windows: Vec<Option<usize>>,
per_layer: Vec<usize>,
}
impl MaskPlan {
pub fn dense(n_layers: usize) -> Self {
Self {
windows: vec![None],
per_layer: vec![0; n_layers],
}
}
pub fn new(windows: Vec<Option<usize>>, per_layer: Vec<usize>) -> Result<Self> {
if windows.is_empty() {
return Err(
fuel_ir::Error::Msg("MaskPlan: needs at least one variant".to_string()).bt(),
);
}
if let Some((li, v)) = per_layer
.iter()
.enumerate()
.find(|(_, v)| **v >= windows.len())
{
return Err(fuel_ir::Error::Msg(format!(
"MaskPlan: layer {li} selects mask variant {v} but only {} \
variant(s) are defined",
windows.len(),
))
.bt());
}
Ok(Self { windows, per_layer })
}
pub fn split_window(n_layers: usize, split: usize, window: usize) -> Self {
let split = split.min(n_layers);
if split == 0 {
return Self::dense(n_layers);
}
if split == n_layers {
return Self {
windows: vec![Some(window)],
per_layer: vec![0; n_layers],
};
}
Self {
windows: vec![Some(window), None],
per_layer: (0..n_layers).map(|i| usize::from(i >= split)).collect(),
}
}
pub fn per_layer_window(
n_layers: usize,
window: usize,
uses_window: impl Fn(usize) -> bool,
) -> Self {
let per_layer: Vec<usize> = (0..n_layers)
.map(|i| usize::from(!uses_window(i)))
.collect();
match (per_layer.contains(&0), per_layer.contains(&1)) {
(true, false) => Self {
windows: vec![Some(window)],
per_layer: vec![0; n_layers],
},
(false, true) => Self::dense(n_layers),
_ => Self {
windows: vec![Some(window), None],
per_layer,
},
}
}
pub fn n_variants(&self) -> usize {
self.windows.len()
}
pub fn n_layers(&self) -> usize {
self.per_layer.len()
}
pub fn variant_for_layer(&self, layer_idx: usize) -> usize {
self.per_layer[layer_idx]
}
pub fn window_for_layer(&self, layer_idx: usize) -> Option<usize> {
self.windows[self.per_layer[layer_idx]]
}
pub fn mix_into(&self, h: &mut crate::decode_shape::ShapeKeyHasher) {
h.mix_u64(self.windows.len() as u64);
for v in &self.per_layer {
h.mix_u64(*v as u64);
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RopePlan {
bases: Vec<f64>,
per_layer: Vec<usize>,
}
impl RopePlan {
pub fn single(base: f64, n_layers: usize) -> Self {
Self {
bases: vec![base],
per_layer: vec![0; n_layers],
}
}
pub fn new(bases: Vec<f64>, per_layer: Vec<usize>) -> Result<Self> {
if bases.is_empty() {
return Err(fuel_ir::Error::Msg("RopePlan: needs at least one base".to_string()).bt());
}
if let Some((li, v)) = per_layer
.iter()
.enumerate()
.find(|(_, v)| **v >= bases.len())
{
return Err(fuel_ir::Error::Msg(format!(
"RopePlan: layer {li} selects RoPE variant {v} but only {} base(s) \
are defined",
bases.len(),
))
.bt());
}
Ok(Self { bases, per_layer })
}
pub fn per_layer_base(
n_layers: usize,
base_when_true: f64,
base_when_false: f64,
uses_first: impl Fn(usize) -> bool,
) -> Self {
if base_when_true == base_when_false {
return Self::single(base_when_true, n_layers);
}
let per_layer: Vec<usize> = (0..n_layers).map(|i| usize::from(!uses_first(i))).collect();
match (per_layer.contains(&0), per_layer.contains(&1)) {
(true, false) => Self::single(base_when_true, n_layers),
(false, true) => Self::single(base_when_false, n_layers),
_ => Self {
bases: vec![base_when_true, base_when_false],
per_layer,
},
}
}
pub fn n_variants(&self) -> usize {
self.bases.len()
}
pub fn n_layers(&self) -> usize {
self.per_layer.len()
}
pub fn variant_for_layer(&self, layer_idx: usize) -> usize {
self.per_layer[layer_idx]
}
pub fn mix_into(&self, h: &mut crate::decode_shape::ShapeKeyHasher) {
h.mix_u64(self.bases.len() as u64);
for v in &self.per_layer {
h.mix_u64(*v as u64);
}
}
}
fn build_rope_variants(
plan: &RopePlan,
width: usize,
cached_len: usize,
seq: usize,
rope_inv_freq: Option<&[f64]>,
) -> (Vec<f32>, Vec<f32>) {
if let Some(inv) = rope_inv_freq {
return fuel_graph::build_rope_tables_with_inv_freq(inv, cached_len, seq, width);
}
let mut cos = Vec::with_capacity(plan.n_variants() * seq * width);
let mut sin = Vec::with_capacity(plan.n_variants() * seq * width);
for base in &plan.bases {
let (c, s) = fuel_graph::build_rope_tables(*base, cached_len, seq, width);
cos.extend_from_slice(&c);
sin.extend_from_slice(&s);
}
(cos, sin)
}
pub fn build_decode_mask_variants(
plan: &MaskPlan,
cached_len: usize,
seq: usize,
max_seq_len: usize,
) -> Vec<f32> {
let mut out = Vec::with_capacity(plan.n_variants() * seq * max_seq_len);
for window in &plan.windows {
match window {
None => out.extend_from_slice(&crate::lazy::build_decode_causal_mask(
cached_len,
seq,
max_seq_len,
)),
Some(window) => out.extend_from_slice(&crate::lazy::build_decode_causal_mask_windowed(
cached_len,
seq,
max_seq_len,
*window,
)),
}
}
out
}
pub trait DecodeBackbone: PersistentDecodeModel {
fn decode_family(&self) -> &'static str;
fn decode_dims(&self) -> DecodeDims;
fn decode_shape_key(&self) -> u64;
fn decode_mask_plan(&self) -> MaskPlan;
fn decode_rope_plan(&self) -> RopePlan;
fn decode_token_embedding(&self) -> Arc<[f32]>;
fn decode_apply_layer(
&self,
layer_idx: usize,
inputs: &DecodeLayerInputs<'_>,
) -> Result<Tensor>;
fn decode_final_norm_and_head(&self, h: &Tensor) -> Result<Tensor>;
}
pub struct DecodeLayerInputs<'a> {
pub x: &'a Tensor,
pub k_cache: &'a Tensor,
pub v_cache: &'a Tensor,
pub cached_len_sym: fuel_ir::SymId,
pub attended_len_sym: fuel_ir::SymId,
pub offset: Option<&'a Tensor>,
pub rope_cos: &'a Tensor,
pub rope_sin: &'a Tensor,
pub mask: &'a Tensor,
pub attn_window: Option<usize>,
}
pub struct DecodeTokenHost {
pub token_ids: Vec<u32>,
pub rope_cos: Vec<f32>,
pub rope_sin: Vec<f32>,
pub mask: Vec<f32>,
}
pub fn compute_decode_token_host<M: DecodeBackbone + ?Sized>(
model: &M,
cached_len: usize,
tokens: &[u32],
max_seq_len: usize,
rope_inv_freq: Option<&[f64]>,
) -> DecodeTokenHost {
let dims = model.decode_dims();
let seq = tokens.len();
let (rope_cos, rope_sin) = build_rope_variants(
&model.decode_rope_plan(),
dims.rope_width,
cached_len,
seq,
rope_inv_freq,
);
DecodeTokenHost {
token_ids: tokens.to_vec(),
rope_cos,
rope_sin,
mask: build_decode_mask_variants(&model.decode_mask_plan(), cached_len, seq, max_seq_len),
}
}
pub(crate) struct BuiltDecodeGraph {
logits_root: Tensor,
kv_nodes: Vec<(fuel_graph::NodeId, fuel_graph::NodeId)>,
rebindable: Option<RebindableNodes>,
cached_len_sym: fuel_ir::SymId,
attended_len_sym: fuel_ir::SymId,
}
pub(crate) struct RebindableNodes {
token_ids_node: fuel_graph::NodeId,
rope_cos_node: fuel_graph::NodeId,
rope_sin_node: fuel_graph::NodeId,
mask_node: fuel_graph::NodeId,
offset_node: Option<fuel_graph::NodeId>,
first_token: DecodeTokenData,
}
#[allow(clippy::too_many_arguments)]
fn build_decode_graph<M: DecodeBackbone + ?Sized>(
model: &M,
consts: DataConsts,
tokens: &[u32],
cache: &KvCache,
ctx: &mut InferenceContext,
return_all_positions: bool,
rope_inv_freq: Option<&[f64]>,
) -> Result<BuiltDecodeGraph> {
let dims = model.decode_dims();
let plan = model.decode_mask_plan();
let family = model.decode_family();
let entry = consts.entry();
let seq = tokens.len();
let batch = 1;
let cached_len = cache.cached_len;
if seq == 0 {
return Err(fuel_ir::Error::Msg(format!("{family}::{entry}: zero tokens",)).bt());
}
if cache.n_layers() != dims.n_layers {
return Err(fuel_ir::Error::Msg(format!(
"{family}::{entry}: cache n_layers {} != model n_layers {}",
cache.n_layers(),
dims.n_layers,
))
.bt());
}
if plan.n_layers() != dims.n_layers {
return Err(fuel_ir::Error::Msg(format!(
"{family}::{entry}: mask plan covers {} layer(s) but the model has {}",
plan.n_layers(),
dims.n_layers,
))
.bt());
}
let max_seq_len = cache.max_seq_len.ok_or_else(|| {
fuel_ir::Error::Msg(format!(
"{family}::{entry}: cache was constructed via with_dims (no \
pre-allocated buffers); call KvCache::with_capacity(...) for the \
WriteSlice path",
))
.bt()
})?;
if cached_len + seq > max_seq_len {
return Err(fuel_ir::Error::Msg(format!(
"{family}::{entry}: cached_len ({cached_len}) + seq ({seq}) > \
max_seq_len ({max_seq_len})",
))
.bt());
}
let cache_dtype = cache.dtype.unwrap_or(DType::F32);
if consts == DataConsts::Baked
&& (cache.n_kv_heads != dims.n_kv_heads || cache.head_dim != dims.head_dim)
{
return Err(fuel_ir::Error::Msg(format!(
"{family}::{entry}: cache shape (n_kv_heads={}, head_dim={}) \
disagrees with model config (n_kv_heads={}, head_dim={})",
cache.n_kv_heads, cache.head_dim, dims.n_kv_heads, dims.head_dim,
))
.bt());
}
let host = compute_decode_token_host(model, cached_len, tokens, max_seq_len, rope_inv_freq);
let embed = Tensor::from_f32(
model.decode_token_embedding(),
Shape::from_dims(&[dims.vocab, dims.hidden]),
&Device::cpu(),
)?;
let token_ids = match consts {
DataConsts::Baked => {
embed.const_u32_like(host.token_ids.clone(), Shape::from_dims(&[seq]))?
}
DataConsts::Rebindable => {
embed.const_placeholder_like(Shape::from_dims(&[seq]), DType::U32)
}
};
let token_ids_node = token_ids.inner.id();
let mut h = embed
.index_select(0, &token_ids)?
.reshape(Shape::from_dims(&[batch, seq, dims.hidden]))?;
if let Some(scale) = dims.embed_scale {
h = h.mul_scalar(scale);
}
h = h.to_dtype(cache_dtype)?;
let rope_plan = model.decode_rope_plan();
if rope_plan.n_layers() != dims.n_layers {
return Err(fuel_ir::Error::Msg(format!(
"{family}::{entry}: RoPE plan covers {} layer(s) but the model has {}",
rope_plan.n_layers(),
dims.n_layers,
))
.bt());
}
if rope_inv_freq.is_some() && rope_plan.n_variants() > 1 {
return Err(fuel_ir::Error::Msg(format!(
"{family}::{entry}: rope_inv_freq override is single-base only, but this \
model declares {} RoPE variants; per-variant scaled frequencies are not \
expressible yet",
rope_plan.n_variants(),
))
.bt());
}
let n_rope = rope_plan.n_variants();
let rope_shape = if n_rope == 1 {
Shape::from_dims(&[seq, dims.rope_width])
} else {
Shape::from_dims(&[n_rope, seq, dims.rope_width])
};
let (rope_cos, rope_sin) = match consts {
DataConsts::Baked => (
h.const_f32_like(host.rope_cos.clone(), rope_shape.clone())?,
h.const_f32_like(host.rope_sin.clone(), rope_shape)?,
),
DataConsts::Rebindable => (
h.const_placeholder_like(rope_shape.clone(), DType::F32),
h.const_placeholder_like(rope_shape, DType::F32),
),
};
let rope_cos_node = rope_cos.inner.id();
let rope_sin_node = rope_sin.inner.id();
let rope_2d = Shape::from_dims(&[seq, dims.rope_width]);
let (rope_cos_v, rope_sin_v): (Vec<Tensor>, Vec<Tensor>) = if n_rope == 1 {
(vec![rope_cos], vec![rope_sin])
} else {
let mut cs = Vec::with_capacity(n_rope);
let mut ss = Vec::with_capacity(n_rope);
for v in 0..n_rope {
cs.push(rope_cos.slice(0, v, 1)?.reshape(rope_2d.clone())?);
ss.push(rope_sin.slice(0, v, 1)?.reshape(rope_2d.clone())?);
}
(cs, ss)
};
let n_variants = plan.n_variants();
let mask_shape = Shape::from_dims(&[n_variants, 1, seq, max_seq_len]);
let mask = match consts {
DataConsts::Baked => h.const_like_dtype(&host.mask, mask_shape, cache_dtype)?,
DataConsts::Rebindable => h.const_placeholder_like(mask_shape, cache_dtype),
};
let mask_node = mask.inner.id();
let masks: Vec<Tensor> = if n_variants == 1 {
vec![mask]
} else {
(0..n_variants)
.map(|v| mask.slice(0, v, 1))
.collect::<std::result::Result<Vec<_>, _>>()?
};
let use_device_offset =
consts == DataConsts::Rebindable && (ctx.device().is_cpu() || ctx.device().is_cuda());
let offset_tensor = if use_device_offset {
Some(h.const_placeholder_like(Shape::from_dims(&[]), DType::I64))
} else {
None
};
let offset_node = offset_tensor.as_ref().map(|t| t.inner.id());
let cached_len_sym = fuel_ir::SymId(0);
let attended_len_sym = fuel_ir::SymId(1);
let cache_shape = Shape::from_dims(&[batch, dims.n_kv_heads, max_seq_len, dims.head_dim]);
let mut kv_nodes: Vec<(fuel_graph::NodeId, fuel_graph::NodeId)> =
Vec::with_capacity(dims.n_layers);
for li in 0..dims.n_layers {
let k_arc = cache.slot_storage(li, KvSlot::K).ok_or_else(|| {
fuel_ir::Error::Msg(format!(
"{family}::{entry}: cache layer {li} has no K slot \
(with_capacity should have populated all layers)",
))
.bt()
})?;
let v_arc = cache.slot_storage(li, KvSlot::V).ok_or_else(|| {
fuel_ir::Error::Msg(format!("{family}::{entry}: cache layer {li} has no V slot",)).bt()
})?;
let k_cache_node = h.const_placeholder_like(cache_shape.clone(), cache_dtype);
let v_cache_node = h.const_placeholder_like(cache_shape.clone(), cache_dtype);
let k_id = k_cache_node.inner.id();
let v_id = v_cache_node.inner.id();
ctx.insert(k_id, k_arc);
ctx.insert(v_id, v_arc);
kv_nodes.push((k_id, v_id));
h = model.decode_apply_layer(
li,
&DecodeLayerInputs {
x: &h,
k_cache: &k_cache_node,
v_cache: &v_cache_node,
cached_len_sym,
attended_len_sym,
offset: offset_tensor.as_ref(),
rope_cos: &rope_cos_v[rope_plan.variant_for_layer(li)],
rope_sin: &rope_sin_v[rope_plan.variant_for_layer(li)],
mask: &masks[plan.variant_for_layer(li)],
attn_window: plan.window_for_layer(li),
},
)?;
}
let logits = model.decode_final_norm_and_head(&h)?;
let logits_root = if return_all_positions {
logits.reshape(Shape::from_dims(&[seq * dims.vocab]))?
} else {
let last_pos = seq - 1;
logits
.slice(1, last_pos, 1)?
.reshape(Shape::from_dims(&[dims.vocab]))?
};
let logits_root = logits_root.to_dtype(DType::F32)?;
let rebindable = match consts {
DataConsts::Baked => None,
DataConsts::Rebindable => Some(RebindableNodes {
token_ids_node,
rope_cos_node,
rope_sin_node,
mask_node,
offset_node,
first_token: upload_decode_token_data(
ctx.device(),
&host,
cache_dtype,
use_device_offset.then_some(cached_len),
)?,
}),
};
Ok(BuiltDecodeGraph {
logits_root,
kv_nodes,
rebindable,
cached_len_sym,
attended_len_sym,
})
}
pub fn upload_decode_token_data(
device: &Device,
host: &DecodeTokenHost,
cache_dtype: DType,
device_offset: Option<usize>,
) -> Result<DecodeTokenData> {
let upload = crate::pipelined_bridge::upload_host_buffer_to_device;
let mask = match cache_dtype {
DType::F32 => fuel_ir::HostBuffer::F32(host.mask.clone()),
DType::BF16 => {
fuel_ir::HostBuffer::BF16(host.mask.iter().map(|&v| half::bf16::from_f32(v)).collect())
}
other => {
return Err(fuel_ir::Error::Msg(format!(
"decode token data: unsupported cache dtype {other:?} (expected F32 or BF16)",
))
.bt());
}
};
Ok(DecodeTokenData {
token_ids: upload(device, fuel_ir::HostBuffer::U32(host.token_ids.clone()))?,
rope_cos: upload(device, fuel_ir::HostBuffer::F32(host.rope_cos.clone()))?,
rope_sin: upload(device, fuel_ir::HostBuffer::F32(host.rope_sin.clone()))?,
mask: upload(device, mask)?,
offset: device_offset
.map(|o| upload(device, fuel_ir::HostBuffer::I64(vec![o as i64])))
.transpose()?,
})
}
pub fn forward_with_kv_context<M: DecodeBackbone + ?Sized>(
model: &M,
tokens: &[u32],
cache: &mut KvCache,
ctx: &mut InferenceContext,
return_all_positions: bool,
rope_inv_freq: Option<&[f64]>,
) -> Result<Vec<f32>> {
let seq = tokens.len();
let built = build_decode_graph(
model,
DataConsts::Baked,
tokens,
&*cache,
ctx,
return_all_positions,
rope_inv_freq,
)?;
let cached_len = cache.cached_len;
let _ = crate::planner::Planner::warm(
built.logits_root.inner.graph(),
&[built.logits_root.inner.id()],
ctx.device(),
);
let mut sym_env = fuel_ir::SymEnv::new();
sym_env.bind(built.cached_len_sym, cached_len)?;
sym_env.bind(built.attended_len_sym, cached_len + seq)?;
let logits_vec = ctx.realize_one_as_with_env::<f32>(
built.logits_root.inner.graph(),
built.logits_root.inner.id(),
&sym_env,
)?;
for (k, v) in &built.kv_nodes {
ctx.remove(*k);
ctx.remove(*v);
}
advance_cache(cache, seq, model.decode_n_layers());
Ok(logits_vec)
}
pub fn build_and_realize_first_decode_token<M: DecodeBackbone + ?Sized>(
model: &M,
tokens: &[u32],
cache: &mut KvCache,
ctx: &mut InferenceContext,
session: &mut Option<DecodeSession>,
rope_inv_freq: Option<&[f64]>,
) -> Result<Vec<f32>> {
let seq = tokens.len();
let cached_len = cache.cached_len;
let cache_dtype = cache.dtype.unwrap_or(DType::F32);
let dims = model.decode_dims();
let built = build_decode_graph(
model,
DataConsts::Rebindable,
tokens,
&*cache,
ctx,
false,
rope_inv_freq,
)?;
let max_seq_len = cache.max_seq_len.expect("checked in build_decode_graph");
let nodes = built
.rebindable
.expect("Rebindable mode yields rebindable nodes");
let logits_node = built.logits_root.inner.id();
let graph = built.logits_root.inner.graph().clone();
ctx.insert(
nodes.token_ids_node,
Arc::clone(&nodes.first_token.token_ids),
);
ctx.insert(nodes.rope_cos_node, Arc::clone(&nodes.first_token.rope_cos));
ctx.insert(nodes.rope_sin_node, Arc::clone(&nodes.first_token.rope_sin));
ctx.insert(nodes.mask_node, Arc::clone(&nodes.first_token.mask));
if let (Some(off_node), Some(off_arc)) = (nodes.offset_node, nodes.first_token.offset.as_ref())
{
ctx.insert(off_node, Arc::clone(off_arc));
}
let mut sym_env = fuel_ir::SymEnv::new();
sym_env.bind(built.cached_len_sym, cached_len)?;
sym_env.bind(built.attended_len_sym, cached_len + seq)?;
let (effective_target, optimized, base_cache, logits_vec) =
ctx.prebuild_optimized_capturing_as_with_env::<f32>(&graph, logits_node, &sym_env)?;
ctx.remove(nodes.token_ids_node);
ctx.remove(nodes.rope_cos_node);
ctx.remove(nodes.rope_sin_node);
ctx.remove(nodes.mask_node);
if let Some(off_node) = nodes.offset_node {
ctx.remove(off_node);
}
for (k, v) in &built.kv_nodes {
ctx.remove(*k);
ctx.remove(*v);
}
*session = Some(DecodeSession::new(
graph,
optimized,
effective_target,
logits_node,
nodes.token_ids_node,
nodes.rope_cos_node,
nodes.rope_sin_node,
nodes.mask_node,
built.kv_nodes,
nodes.offset_node,
built.cached_len_sym,
built.attended_len_sym,
base_cache,
seq,
max_seq_len,
dims.n_layers,
cache_dtype,
model.decode_shape_key(),
cache,
));
advance_cache(cache, seq, dims.n_layers);
Ok(logits_vec)
}
pub fn drop_decode_session(session: &mut Option<DecodeSession>, ctx: &mut InferenceContext) {
if let Some(s) = session.take() {
ctx.remove(s.token_ids_node());
ctx.remove(s.rope_cos_node());
ctx.remove(s.rope_sin_node());
ctx.remove(s.mask_node());
for (k, v) in s.kv_nodes() {
ctx.remove(*k);
ctx.remove(*v);
}
}
}
pub fn forward_with_kv_context_persistent<M: DecodeBackbone + ?Sized>(
model: &M,
tokens: &[u32],
cache: &mut KvCache,
ctx: &mut InferenceContext,
session: &mut Option<DecodeSession>,
rope_inv_freq: Option<&[f64]>,
) -> Result<Vec<f32>> {
let seq = tokens.len();
let max_seq_len = cache.max_seq_len;
let cache_dtype = cache.dtype.unwrap_or(DType::F32);
let n_layers = model.decode_dims().n_layers;
if seq != 1 {
drop_decode_session(session, ctx);
return forward_with_kv_context(model, tokens, cache, ctx, false, rope_inv_freq);
}
crate::lazy::refresh_decode_session(
session,
ctx,
seq,
max_seq_len,
cache_dtype,
n_layers,
model.decode_shape_key(),
cache,
|| {},
drop_decode_session,
);
match session.as_ref() {
None => {
build_and_realize_first_decode_token(model, tokens, cache, ctx, session, rope_inv_freq)
}
Some(_) => {
match rebind_and_realize_prebuilt(model, tokens, cache, &*ctx, &*session, rope_inv_freq)
{
Ok(logits) => Ok(logits),
Err(crate::Error::TopologyChanged { .. }) => {
drop_decode_session(session, ctx);
forward_with_kv_context(model, tokens, cache, ctx, false, rope_inv_freq)
}
Err(e) => Err(e),
}
}
}
}
fn advance_cache(cache: &mut KvCache, seq: usize, n_layers: usize) {
cache.cached_len += seq;
for li in 0..n_layers {
cache.bump_version(li, KvSlot::K);
cache.bump_version(li, KvSlot::V);
}
}
#[cfg(test)]
mod mask_plan_tests {
use super::*;
#[test]
fn mask_variants_match_the_dense_builder_when_uniform() {
for (cached_len, seq, max_seq_len, n_layers) in
[(0, 1, 8, 2), (3, 1, 8, 4), (0, 5, 8, 1), (2, 3, 16, 3)]
{
let plan = MaskPlan::dense(n_layers);
assert_eq!(
plan.n_variants(),
1,
"the dense plan must be single-variant"
);
assert_eq!(
build_decode_mask_variants(&plan, cached_len, seq, max_seq_len),
crate::lazy::build_decode_causal_mask(cached_len, seq, max_seq_len),
"uniform plan must be byte-identical to the dense builder \
(cached_len={cached_len}, seq={seq}, max_seq_len={max_seq_len})",
);
}
}
#[test]
fn variants_stack_on_the_leading_axis_in_plan_order() {
let plan = MaskPlan::new(vec![None, Some(2)], vec![0, 1, 1]).expect("valid plan");
let (cached_len, seq, max_seq_len) = (3, 1, 8);
let stacked = build_decode_mask_variants(&plan, cached_len, seq, max_seq_len);
assert_eq!(stacked.len(), 2 * seq * max_seq_len);
assert_eq!(
&stacked[..seq * max_seq_len],
&crate::lazy::build_decode_causal_mask(cached_len, seq, max_seq_len)[..],
"variant 0 is the dense mask and must come first",
);
}
#[test]
fn both_degenerate_splits_collapse_to_a_single_variant() {
let all_dense = MaskPlan::split_window(4, 0, 8);
assert_eq!(all_dense.n_variants(), 1, "split 0 must be the dense plan");
assert_eq!(all_dense, MaskPlan::dense(4), "and identical to it");
for split in [4, 9] {
let all_windowed = MaskPlan::split_window(4, split, 8);
assert_eq!(
all_windowed.n_variants(),
1,
"split {split} covers every layer — one windowed variant, no slice",
);
for li in 0..4 {
assert_eq!(all_windowed.variant_for_layer(li), 0);
}
let (cached_len, seq, max_seq_len, window) = (10, 1, 12, 8);
assert!(
cached_len > window,
"non-vacuity: the window must bite here"
);
assert_ne!(
build_decode_mask_variants(
&MaskPlan::split_window(4, split, window),
cached_len,
seq,
max_seq_len,
),
crate::lazy::build_decode_causal_mask(cached_len, seq, max_seq_len),
"collapsing to one variant must not collapse to the DENSE one",
);
}
assert_eq!(MaskPlan::split_window(4, 2, 8).n_variants(), 2);
}
#[test]
fn a_layer_selecting_an_undefined_variant_is_refused_at_construction() {
let err = MaskPlan::new(vec![None], vec![0, 1]).expect_err("must refuse");
let msg = err.to_string();
assert!(
msg.contains("layer 1") && msg.contains("variant 1"),
"the refusal must name the offending layer and variant, got: {msg}",
);
assert!(
MaskPlan::new(vec![], vec![]).is_err(),
"a plan needs a variant"
);
}
}