use std::fmt::Write as _;
use super::{
Arithmetic, Attention, Evaluation, LayerDelta, LayerState, Qwen35, Transforms, add,
checked_product, finite, row_into,
};
use crate::kernels::{
MatvecBackend, RowParallelism,
prefill::{ProjectionTile, ROW_TASK_SCRATCH_BYTES},
};
use crate::model::Tensor;
use crate::model::lora::{RankOneAdapter, Scratch as AdapterScratch};
use crate::{MAX_PREFILL_BLOCK_TOKENS, Result, invalid};
#[path = "prefill_gdn.rs"]
mod gdn;
const fn reborrow_executor<'a, 'b: 'a>(
executor: &'a mut Option<&'b mut dyn crate::kernels::Pq2ProjectionExecutor>,
) -> Option<&'a mut (dyn crate::kernels::Pq2ProjectionExecutor + 'a)> {
match executor {
Some(exec) => Some(*exec),
None => None,
}
}
#[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
pub struct TiledPrefillStatistics {
pub completed_tiles: u64,
pub completed_tokens: u64,
pub packed_projections: u64,
pub decoded_weight_blocks: u64,
pub chunkwise_gdn_heads: u64,
}
pub(super) struct TiledPrefill {
tokens: usize,
scratch_bytes: usize,
hidden: Vec<Vec<f32>>,
normalized: Vec<Vec<f32>>,
up: Vec<Vec<f32>>,
gate: Vec<Vec<f32>>,
transformed: Vec<Vec<f32>>,
mixed: Vec<Vec<f32>>,
recurrent_gate: Vec<Vec<f32>>,
beta: Vec<Vec<f32>>,
alpha: Vec<Vec<f32>>,
attended: Vec<Vec<f32>>,
attention: Vec<Vec<f32>>,
chunkwise_gdn: bool,
projector: ProjectionTile,
statistics: TiledPrefillStatistics,
}
impl TiledPrefill {
#[cfg_attr(
not(feature = "parallel-rows"),
allow(
clippy::missing_const_for_fn,
reason = "The delegated setter drops the replaced pool owner in parallel builds; retain one signature across feature sets."
)
)]
pub(super) fn set_row_parallelism(&mut self, parallelism: RowParallelism) {
self.projector.set_row_parallelism(parallelism);
}
fn new(
tokens: usize,
width: usize,
scratch_bytes: usize,
backend: MatvecBackend,
parallelism: RowParallelism,
) -> Result<Self> {
let create = || -> Result<Vec<Vec<f32>>> {
let mut rows = Vec::new();
rows.try_reserve_exact(tokens)?;
for _ in 0..tokens {
let mut row = Vec::new();
row.try_reserve_exact(width)?;
rows.push(row);
}
Ok(rows)
};
let mut projector = ProjectionTile::with_backend(backend);
projector.set_row_parallelism(parallelism);
Ok(Self {
tokens,
scratch_bytes,
hidden: create()?,
normalized: create()?,
up: create()?,
gate: create()?,
transformed: create()?,
mixed: create()?,
recurrent_gate: create()?,
beta: create()?,
alpha: create()?,
attended: create()?,
attention: create()?,
chunkwise_gdn: false,
projector,
statistics: TiledPrefillStatistics::default(),
})
}
}
impl Qwen35 {
pub(super) fn append_tiled_prefill_identity(&self, identity: &mut String) -> Result<()> {
if let Some(tile) = &self.tiled_prefill {
identity.try_reserve_exact(64)?;
write!(identity, ":cpu-tiled-ffn-v1:{}", tile.tokens)?;
if tile.chunkwise_gdn {
identity.push_str(":gdn-wy-f32-v1");
}
}
Ok(())
}
pub fn tiled_prefill_scratch_bytes(&self, tokens: usize) -> Result<usize> {
if !matches!(tokens, 8 | 16 | 32) {
return Err(invalid("CPU prefill tiles must contain 8, 16 or 32 tokens"));
}
let width = self
.geometry
.embedding
.max(self.geometry.feed_forward)
.max(self.geometry.convolution_width())
.max(self.geometry.value_width());
checked_product(&[tokens, width, 80])?
.checked_add(checked_product(&[tokens, 2048])?)
.and_then(|bytes| bytes.checked_add(64 * 1024))
.and_then(|bytes| bytes.checked_add(ROW_TASK_SCRATCH_BYTES))
.ok_or_else(|| invalid("CPU prefill scratch reservation overflow"))
}
pub fn with_tiled_prefill(mut self, tokens: usize, scratch_bytes: usize) -> Result<Self> {
let required = self.tiled_prefill_scratch_bytes(tokens)?;
if self.position != 0 || self.tiled_prefill.is_some() || scratch_bytes < required {
return Err(invalid(
"CPU tiled prefill requires an empty graph and sufficient scratch",
));
}
let tile = TiledPrefill::new(
tokens,
self.geometry
.embedding
.max(self.geometry.feed_forward)
.max(self.geometry.convolution_width())
.max(self.geometry.value_width()),
required,
self.requested_backend(),
self.workspace.projection.matvec.clone_row_parallelism(),
)?;
let suffix = format!(":cpu-tiled-ffn-v1:{tokens}");
self.identity.try_reserve_exact(suffix.len())?;
self.identity.push_str(&suffix);
self.provenance = std::sync::Arc::new(());
self.tiled_prefill = Some(tile);
Ok(self)
}
pub fn with_chunkwise_gdn_prefill(mut self) -> Result<Self> {
let Some(tile) = self.tiled_prefill.as_mut() else {
return Err(invalid("chunkwise GDN requires explicit tiled prefill"));
};
if self.position != 0 || tile.chunkwise_gdn {
return Err(invalid("chunkwise GDN requires an empty, unselected graph"));
}
let suffix = ":gdn-wy-f32-v1";
self.identity.try_reserve_exact(suffix.len())?;
self.identity.push_str(suffix);
self.provenance = std::sync::Arc::new(());
tile.chunkwise_gdn = true;
Ok(self)
}
#[must_use]
pub fn tiled_prefill_reserved_bytes(&self) -> usize {
self.tiled_prefill
.as_ref()
.map_or(0, |tile| tile.scratch_bytes)
}
#[must_use]
pub fn tiled_prefill_statistics(&self) -> TiledPrefillStatistics {
self.tiled_prefill
.as_ref()
.map_or_else(TiledPrefillStatistics::default, |tile| {
let mut statistics = tile.statistics;
statistics.packed_projections = tile.projector.packed_projections;
statistics.decoded_weight_blocks = tile.projector.decoded_blocks;
statistics
})
}
pub(super) fn ingest_prefill_block(
&mut self,
tokens: &[u32],
check: &dyn Fn() -> Result<()>,
) -> Result<()> {
const fn coerce_executor<'a>(
executor: &'a mut Box<dyn crate::kernels::Pq2ProjectionExecutor + Send>,
) -> &'a mut (dyn crate::kernels::Pq2ProjectionExecutor + 'a) {
&mut **executor
}
let mut executor = self.projection_executor.take();
let result = self.ingest_prefill_block_with_executor(
tokens,
check,
executor.as_mut().map(coerce_executor),
);
self.projection_executor = executor;
result
}
pub(super) fn ingest_prefill_block_with_executor(
&mut self,
tokens: &[u32],
check: &dyn Fn() -> Result<()>,
mut executor: Option<&mut dyn crate::kernels::Pq2ProjectionExecutor>,
) -> Result<()> {
if tokens.is_empty()
|| tokens.len() > MAX_PREFILL_BLOCK_TOKENS
|| tokens
.iter()
.any(|&token| usize::try_from(token).map_or(true, |value| value >= self.vocabulary))
{
return Err(invalid("invalid native prefill block bounds or tokens"));
}
if self.tiled_prefill.is_some()
&& self
.position
.checked_add(tokens.len())
.is_none_or(|end| end > self.context_limit)
{
return Err(invalid("CPU prefill block exceeds remaining context"));
}
let Some(mut tile) = self.tiled_prefill.take() else {
for &token in tokens {
check()?;
self.evaluate_token_with_executor(token, None, reborrow_executor(&mut executor))?;
check()?;
}
return Ok(());
};
let result = (|| {
for tokens in tokens.chunks(tile.tokens) {
check()?;
self.ingest_cpu_tile(tokens, check, &mut tile, reborrow_executor(&mut executor))?;
tile.statistics.completed_tiles = tile.statistics.completed_tiles.saturating_add(1);
tile.statistics.completed_tokens = tile
.statistics
.completed_tokens
.saturating_add(u64::try_from(tokens.len())?);
check()?;
}
Ok(())
})();
self.tiled_prefill = Some(tile);
result
}
#[allow(
clippy::too_many_lines,
reason = "Keep causal attention commits, FFN tile consumption and cancellation visibly ordered."
)]
fn ingest_cpu_tile(
&mut self,
tokens: &[u32],
check: &dyn Fn() -> Result<()>,
tile: &mut TiledPrefill,
mut executor: Option<&mut dyn crate::kernels::Pq2ProjectionExecutor>,
) -> Result<()> {
let count = tokens.len();
let base = self.position;
for (token, hidden) in tokens.iter().zip(&mut tile.hidden) {
check()?;
row_into(&self.embedding, usize::try_from(*token)?, hidden)?;
if self.transforms.inverse_embedding {
self.transforms
.rotate_with_parallelism(hidden, tile.projector.row_parallelism())?;
self.transforms.sign(hidden);
}
if let Some(adapter) = &self.adapter {
adapter.embedding(
usize::try_from(*token)?,
self.adapter_scale,
hidden,
&mut self.workspace.adapter,
)?;
}
}
self.position = base + count;
for ((layer, state), delta) in self
.layers
.iter()
.zip(&mut self.state)
.zip(&mut self.workspace.deltas)
{
for (hidden, normalized) in tile.hidden[..count]
.iter()
.zip(&mut tile.normalized[..count])
{
self.arithmetic.normalize(
hidden,
&layer.norm,
self.geometry.epsilon,
normalized,
)?;
}
let chunked = if let (true, Attention::Recurrent(weights)) =
(tile.chunkwise_gdn, &layer.attention)
{
gdn::recurrent_tile(
weights,
state,
delta,
&self.geometry,
self.arithmetic,
&self.transforms,
self.adapter.as_ref(),
self.adapter_scale,
&mut self.workspace.adapter,
&mut self.workspace.projection.matvec,
count,
check,
tile,
reborrow_executor(&mut executor),
)?;
true
} else {
false
};
for index in 0..count {
check()?;
let hidden = &mut tile.hidden[index];
let normalized = &mut tile.normalized[index];
if chunked {
add(hidden, &tile.attention[index]);
self.arithmetic.normalize(
hidden,
&layer.post_norm,
self.geometry.epsilon,
normalized,
)?;
check()?;
continue;
}
let mut evaluation = Evaluation {
geometry: &self.geometry,
arithmetic: self.arithmetic,
transforms: &self.transforms,
position: base + index,
frequencies_ready: false,
projection: &mut self.workspace.projection,
adapter: self.adapter.as_ref(),
adapter_scale: self.adapter_scale,
adapter_scratch: &mut self.workspace.adapter,
executor: reborrow_executor(&mut executor),
};
match (&layer.attention, &mut *state) {
(Attention::Full(weights), LayerState::Full { keys, values }) => {
evaluation.full_attention(
weights,
normalized,
keys,
values,
&mut self.workspace.attention_scratch,
delta,
&mut self.workspace.attention,
)?;
}
(
Attention::Recurrent(weights),
LayerState::Recurrent {
convolution,
matrix,
},
) => {
evaluation.recurrent_attention(
weights,
normalized,
convolution,
matrix,
&mut self.workspace.attention_scratch,
delta,
&mut self.workspace.attention,
)?;
}
_ => return Err(invalid("CPU tiled prefill layer/cache mismatch")),
}
match (&mut *state, &mut *delta) {
(LayerState::Full { keys, values }, LayerDelta::Full { key, value }) => {
keys.reserve_append()?;
values.reserve_append()?;
keys.append(key);
values.append(value);
}
(
LayerState::Recurrent {
convolution,
matrix,
},
LayerDelta::Recurrent {
convolution: next,
matrix: next_matrix,
},
) => {
std::mem::swap(convolution, next);
std::mem::swap(matrix, next_matrix);
}
_ => return Err(invalid("CPU tiled prefill delta mismatch")),
}
add(hidden, &self.workspace.attention);
self.arithmetic.normalize(
hidden,
&layer.post_norm,
self.geometry.epsilon,
normalized,
)?;
check()?;
}
check()?;
let _ = project_tile_pair(
&self.transforms,
&self.geometry,
self.arithmetic,
self.adapter.as_ref(),
self.adapter_scale,
&mut self.workspace.adapter,
&mut self.workspace.projection.matvec,
[&layer.up, &layer.gate],
&tile.normalized[..count],
&mut tile.up[..count],
&mut tile.gate[..count],
&mut tile.transformed[..count],
&mut tile.projector,
reborrow_executor(&mut executor),
check,
)?;
for (up, gate) in tile.up[..count].iter_mut().zip(&tile.gate[..count]) {
self.arithmetic.swiglu_in_place_with_parallelism(
gate,
up,
tile.projector.row_parallelism(),
)?;
}
check()?;
project_tile(
&self.transforms,
&self.geometry,
self.arithmetic,
self.adapter.as_ref(),
self.adapter_scale,
&mut self.workspace.adapter,
&mut self.workspace.projection.matvec,
None,
&layer.down,
&tile.up[..count],
&mut tile.normalized[..count],
&mut tile.transformed[..count],
&mut tile.projector,
reborrow_executor(&mut executor),
check,
)?;
for (hidden, output) in tile.hidden[..count]
.iter_mut()
.zip(&tile.normalized[..count])
{
add(hidden, output);
finite(hidden, "CPU tiled decoder layer output")?;
}
check()?;
}
Ok(())
}
}
fn transform_tile_inputs<'a>(
transforms: &Transforms,
geometry: &super::Geometry,
tensor: &Tensor,
reuse_basis: bool,
inputs: &'a [Vec<f32>],
transformed: &'a mut [Vec<f32>],
parallelism: &RowParallelism,
) -> Result<&'a [Vec<f32>]> {
let basis = transforms.basis(tensor);
if basis.0 {
if !reuse_basis {
if inputs.len() > 1 && parallelism.workers() > 1 {
parallelism.update_chunks(transformed, 1, |base, chunk| {
for (offset, target) in chunk.iter_mut().enumerate() {
transforms.transform_into(
tensor,
&inputs[base + offset],
geometry,
target,
)?;
}
Ok(())
})?;
} else {
for (input, transformed) in inputs.iter().zip(transformed.iter_mut()) {
transforms.transform_into_with_parallelism(
tensor,
input,
geometry,
parallelism,
transformed,
)?;
}
}
}
Ok(&*transformed)
} else {
Ok(inputs)
}
}
#[allow(
clippy::too_many_arguments,
reason = "Keep exact transforms, original-basis adapter and bounded tile storage explicit."
)]
fn project_tile_pair<'a>(
transforms: &Transforms,
geometry: &super::Geometry,
arithmetic: Arithmetic,
adapter: Option<&RankOneAdapter>,
adapter_scale: f32,
adapter_scratch: &mut AdapterScratch,
projection_scratch: &mut crate::kernels::MatvecScratch,
tensors: [&'a Tensor; 2],
inputs: &[Vec<f32>],
outputs_a: &mut [Vec<f32>],
outputs_b: &mut [Vec<f32>],
transformed: &mut [Vec<f32>],
projector: &mut ProjectionTile,
mut executor: Option<&mut dyn crate::kernels::Pq2ProjectionExecutor>,
check: &dyn Fn() -> Result<()>,
) -> Result<Option<&'a Tensor>> {
if let Some(exec) = reborrow_executor(&mut executor)
&& tensors[0].kind == 142
&& tensors[1].kind == 142
&& transforms.basis(tensors[0]) == transforms.basis(tensors[1])
&& exec.supports_f32_group(&tensors)
{
let activation = transform_tile_inputs(
transforms,
geometry,
tensors[0],
false,
inputs,
transformed,
projector.row_parallelism(),
)?;
check()?;
exec.project_f32_pair_tokens(
tensors,
activation,
matches!(arithmetic, Arithmetic::PinnedSwift),
outputs_a,
outputs_b,
)?;
let rows_a = tensors[0]
.dimensions
.get(1..)
.map_or(0, |dims| dims.iter().product::<usize>());
let rows_b = tensors[1]
.dimensions
.get(1..)
.map_or(0, |dims| dims.iter().product::<usize>());
for (out_a, out_b) in outputs_a.iter().zip(outputs_b.iter()) {
if out_a.len() != rows_a || out_b.len() != rows_b {
return Err(invalid("projection executor returned incomplete output"));
}
finite(out_a, "projection executor output")?;
finite(out_b, "projection executor output")?;
}
if let Some(adapter) = adapter {
for (input, output) in inputs.iter().zip(outputs_a) {
adapter.linear(
&tensors[0].name,
input,
adapter_scale,
output,
adapter_scratch,
)?;
}
for (input, output) in inputs.iter().zip(outputs_b) {
adapter.linear(
&tensors[1].name,
input,
adapter_scale,
output,
adapter_scratch,
)?;
}
}
return Ok(None);
}
project_tile(
transforms,
geometry,
arithmetic,
adapter,
adapter_scale,
adapter_scratch,
projection_scratch,
None,
tensors[0],
inputs,
outputs_a,
transformed,
projector,
reborrow_executor(&mut executor),
check,
)?;
check()?;
project_tile(
transforms,
geometry,
arithmetic,
adapter,
adapter_scale,
adapter_scratch,
projection_scratch,
Some(tensors[0]),
tensors[1],
inputs,
outputs_b,
transformed,
projector,
reborrow_executor(&mut executor),
check,
)?;
Ok(Some(tensors[1]))
}
#[allow(
clippy::too_many_arguments,
reason = "Keep exact transforms, original-basis adapter and bounded tile storage explicit."
)]
fn project_tile(
transforms: &Transforms,
geometry: &super::Geometry,
arithmetic: Arithmetic,
adapter: Option<&RankOneAdapter>,
adapter_scale: f32,
adapter_scratch: &mut AdapterScratch,
projection_scratch: &mut crate::kernels::MatvecScratch,
previous: Option<&Tensor>,
tensor: &Tensor,
inputs: &[Vec<f32>],
outputs: &mut [Vec<f32>],
transformed: &mut [Vec<f32>],
projector: &mut ProjectionTile,
mut executor: Option<&mut dyn crate::kernels::Pq2ProjectionExecutor>,
check: &dyn Fn() -> Result<()>,
) -> Result<()> {
let basis = transforms.basis(tensor);
let reuse_basis = previous.is_some_and(|prev| transforms.basis(prev) == basis);
let activation = transform_tile_inputs(
transforms,
geometry,
tensor,
reuse_basis,
inputs,
transformed,
projector.row_parallelism(),
)?;
if let Some(exec) = reborrow_executor(&mut executor) {
let cols = tensor.dimensions.first().copied().unwrap_or(0);
let rows = tensor
.dimensions
.get(1..)
.map_or(0, |dims| dims.iter().product::<usize>());
if tensor.kind == 142 && cols.is_multiple_of(128) && exec.supports_f32(tensor) {
check()?;
exec.project_f32_tokens(
tensor,
activation,
matches!(arithmetic, Arithmetic::PinnedSwift),
outputs,
)?;
for out in outputs.iter() {
if out.len() != rows {
return Err(invalid("projection executor returned incomplete output"));
}
finite(out, "projection executor output")?;
}
} else if (tensor.kind == 142 && cols.is_multiple_of(256) && exec.supports(tensor))
|| ((tensor.kind == 143 || (tensor.kind == 142 && !cols.is_multiple_of(256)))
&& exec.supports_q8(tensor))
{
for (input, output) in activation.iter().zip(outputs.iter_mut()) {
check()?;
projection_scratch
.prepare(tensor, input)?
.matvec_with_executor(tensor, output, exec)?;
}
} else {
projector.project_with_previous(
previous.filter(|_| reuse_basis),
tensor,
activation,
outputs,
matches!(arithmetic, Arithmetic::PinnedSwift),
check,
)?;
}
} else {
projector.project_with_previous(
previous.filter(|_| reuse_basis),
tensor,
activation,
outputs,
matches!(arithmetic, Arithmetic::PinnedSwift),
check,
)?;
}
if let Some(adapter) = adapter {
for (input, output) in inputs.iter().zip(outputs) {
adapter.linear(&tensor.name, input, adapter_scale, output, adapter_scratch)?;
}
}
Ok(())
}