pub fn hc_split_sinkhorn(
mixes: &[f32],
hc_scale: &[f32; 3],
hc_base: &[f32],
hc: usize,
iters: usize,
eps: f32,
pre: &mut [f32],
post: &mut [f32],
comb: &mut [f32],
) {
debug_assert_eq!(mixes.len(), (2 + hc) * hc);
debug_assert_eq!(comb.len(), hc * hc);
for j in 0..hc {
pre[j] = sigmoid(mixes[j] * hc_scale[0] + hc_base[j]) + eps;
post[j] = 2.0 * sigmoid(mixes[j + hc] * hc_scale[1] + hc_base[j + hc]);
}
for j in 0..hc {
for k in 0..hc {
let idx = j * hc + k + hc * 2;
comb[j * hc + k] = mixes[idx] * hc_scale[2] + hc_base[idx];
}
}
for j in 0..hc {
let row = &mut comb[j * hc..(j + 1) * hc];
let m = row.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
let mut sum = 0.0;
for v in row.iter_mut() {
*v = (*v - m).exp();
sum += *v;
}
for v in row.iter_mut() {
*v = *v / sum + eps;
}
}
normalize_cols(comb, hc, eps);
for _ in 0..iters.saturating_sub(1) {
normalize_rows(comb, hc, eps);
normalize_cols(comb, hc, eps);
}
}
fn normalize_rows(m: &mut [f32], n: usize, eps: f32) {
for j in 0..n {
let s: f32 = m[j * n..(j + 1) * n].iter().sum::<f32>() + eps;
for v in m[j * n..(j + 1) * n].iter_mut() {
*v /= s;
}
}
}
fn normalize_cols(m: &mut [f32], n: usize, eps: f32) {
for k in 0..n {
let mut s = eps;
for j in 0..n {
s += m[j * n + k];
}
for j in 0..n {
m[j * n + k] /= s;
}
}
}
#[inline]
fn sigmoid(x: f32) -> f32 {
1.0 / (1.0 + (-x).exp())
}
pub fn hc_mixes(
x_flat: &[f32],
hc_fn: &[f32],
mix_hc: usize,
eps: f32,
pool: Option<&crate::pool::Pool>,
out: &mut [f32],
) {
let n = x_flat.len();
debug_assert_eq!(hc_fn.len(), mix_hc * n);
debug_assert_eq!(out.len(), mix_hc);
let ms = x_flat.iter().map(|v| v * v).sum::<f32>() / n as f32;
let rsqrt = 1.0 / (ms + eps).sqrt();
match pool {
Some(p) if n >= 4096 => {
let addr = crate::pool::SendMut::new(out.as_mut_ptr());
p.run_rows(mix_hc, &|start, end| {
for i in start..end {
let row = &hc_fn[i * n..(i + 1) * n];
let v = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
unsafe { *addr.at(i) = v };
}
});
}
_ => {
for (i, o) in out.iter_mut().enumerate() {
let row = &hc_fn[i * n..(i + 1) * n];
*o = row.iter().zip(x_flat).map(|(a, b)| a * b).sum::<f32>() * rsqrt;
}
}
}
}
pub fn hc_fold(x: &[f32], pre: &[f32], hc: usize, dim: usize, out: &mut [f32]) {
debug_assert_eq!(x.len(), hc * dim);
out.fill(0.0);
for j in 0..hc {
let w = pre[j];
let src = &x[j * dim..(j + 1) * dim];
for (o, v) in out.iter_mut().zip(src) {
*o += w * v;
}
}
}
pub fn hc_expand(
block_out: &[f32],
residual: &[f32],
post: &[f32],
comb: &[f32],
hc: usize,
dim: usize,
out: &mut [f32],
) {
debug_assert_eq!(residual.len(), hc * dim);
debug_assert_eq!(out.len(), hc * dim);
for j in 0..hc {
let dst = &mut out[j * dim..(j + 1) * dim];
let p = post[j];
for (d, o) in dst.iter_mut().enumerate() {
*o = p * block_out[d];
}
for k in 0..hc {
let w = comb[k * hc + j];
let src = &residual[k * dim..(k + 1) * dim];
for (o, v) in dst.iter_mut().zip(src) {
*o += w * v;
}
}
}
}
pub fn hc_head_pre(mixes: &[f32], scale: f32, base: &[f32], hc: usize, eps: f32, pre: &mut [f32]) {
for j in 0..hc {
pre[j] = sigmoid(mixes[j] * scale + base[j]) + eps;
}
}
pub fn route(
scores_in: &[f32],
bias: Option<&[f32]>,
top_k: usize,
route_scale: f32,
forced: Option<&[usize]>,
mask: Option<&[bool]>,
indices: &mut Vec<usize>,
weights: &mut Vec<f32>,
) {
let n = scores_in.len();
let mut scores = Vec::with_capacity(n);
for &s in scores_in {
let sp = if s > 20.0 { s } else { (1.0 + s.exp()).ln() };
scores.push(sp.sqrt());
}
indices.clear();
weights.clear();
match forced {
Some(f) => indices.extend(f.iter().copied()),
None => {
let mut shifted: Vec<f32> = match bias {
Some(b) => scores.iter().zip(b).map(|(s, b)| s + b).collect(),
None => scores.clone(),
};
if let Some(m) = mask {
for (i, s) in shifted.iter_mut().enumerate() {
if !m.get(i).copied().unwrap_or(true) {
*s = f32::NEG_INFINITY;
}
}
}
for _ in 0..top_k.min(n) {
let mut best = 0usize;
let mut bv = f32::NEG_INFINITY;
for (i, &v) in shifted.iter().enumerate() {
if v > bv {
bv = v;
best = i;
}
}
if !bv.is_finite() {
break;
}
indices.push(best);
shifted[best] = f32::NEG_INFINITY;
}
}
}
for &i in indices.iter() {
weights.push(scores.get(i).copied().unwrap_or(0.0));
}
let sum: f32 = weights.iter().sum();
if sum > 0.0 {
for w in weights.iter_mut() {
*w = *w / sum * route_scale;
}
}
}
pub fn hash_route(tid2eid: &[f32], vocab: usize, top_k: usize, tid: u32) -> Vec<usize> {
let row = (tid as usize).min(vocab.saturating_sub(1)) * top_k;
(0..top_k)
.map(|k| tid2eid.get(row + k).copied().unwrap_or(0.0) as usize)
.collect()
}
pub fn rope_tail(v: &mut [f32], inv_freq: &[f32], pos: usize, rd: usize, inverse: bool) {
let n = v.len();
debug_assert!(
rd <= n && rd % 2 == 0,
"rope tail {rd} wider than the vector {n}"
);
let rd = rd.min(n) & !1;
let base = n - rd;
for i in 0..rd / 2 {
let theta = pos as f32 * inv_freq[i];
let (s, c) = (theta.sin(), theta.cos());
let s = if inverse { -s } else { s };
let a = v[base + 2 * i];
let b = v[base + 2 * i + 1];
v[base + 2 * i] = a * c - b * s;
v[base + 2 * i + 1] = a * s + b * c;
}
}
pub fn rms_inplace(v: &mut [f32], eps: f32) {
let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
let inv = 1.0 / (ms + eps).sqrt();
for x in v.iter_mut() {
*x *= inv;
}
}
pub fn sparse_attend(
q: &[f32],
kv: &[f32],
idxs: &[usize],
sink: f32,
scale: f32,
head_dim: usize,
out: &mut [f32],
) {
let mut m = sink;
let mut scores = Vec::with_capacity(idxs.len());
for &p in idxs {
if p == usize::MAX {
scores.push(f32::NEG_INFINITY);
continue;
}
let k = &kv[p * head_dim..(p + 1) * head_dim];
let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum::<f32>() * scale;
m = m.max(dot);
scores.push(dot);
}
let mut denom = (sink - m).exp();
out.fill(0.0);
for (&p, &s) in idxs.iter().zip(&scores) {
if p == usize::MAX {
continue;
}
let w = (s - m).exp();
denom += w;
let v = &kv[p * head_dim..(p + 1) * head_dim];
for (o, x) in out.iter_mut().zip(v) {
*o += w * x;
}
}
if std::env::var("CMF_ATTN_DEBUG").is_ok() {
eprintln!(
" [порт] позиций={} score={:?} sink={sink:.4} denom={denom:.4} |q|={:.3}",
idxs.iter().filter(|&&p| p != usize::MAX).count(),
scores
.iter()
.map(|x| (x * 10000.0).round() / 10000.0)
.collect::<Vec<_>>(),
q.iter().map(|x| x * x).sum::<f32>().sqrt()
);
}
let inv = 1.0 / denom;
for o in out.iter_mut() {
*o *= inv;
}
}
pub fn o_project(
attn: &[f32],
wo_a_row: &(dyn Fn(usize, &[f32], &mut [f32]) -> f32 + Sync),
scratch_len: usize,
wo_b: &dyn Fn(&[f32], &mut [f32]),
groups: usize,
lora: usize,
pool: Option<&crate::pool::Pool>,
out: &mut [f32],
) {
let per_group = attn.len() / groups;
let mut mid = vec![0.0f32; groups * lora];
let slice_of = |i: usize| {
let g = i / lora;
&attn[g * per_group..(g + 1) * per_group]
};
match pool {
Some(p) if mid.len() >= 256 => {
let addr = crate::pool::SendMut::new(mid.as_mut_ptr());
p.run_rows(mid.len(), &|start, end| {
let mut sc = vec![0.0f32; scratch_len];
for i in start..end {
let v = wo_a_row(i, slice_of(i), &mut sc);
unsafe { *addr.at(i) = v };
}
});
}
_ => {
let mut sc = vec![0.0f32; scratch_len];
for (i, m) in mid.iter_mut().enumerate() {
*m = wo_a_row(i, slice_of(i), &mut sc);
}
}
}
wo_b(&mid, out);
}
pub fn compress_window(
kv: &[f32],
score: &[f32],
ape: &[f32],
ratio: usize,
width: usize,
out: &mut [f32],
) {
debug_assert_eq!(kv.len(), ratio * width);
debug_assert_eq!(ape.len(), ratio * width);
let biased: Vec<f32> = score.iter().zip(ape).map(|(s, a)| s + a).collect();
pool_by_score(kv, &biased, ratio, width, out);
}
pub fn pool_by_score(kv: &[f32], score: &[f32], slots: usize, width: usize, out: &mut [f32]) {
debug_assert_eq!(kv.len(), slots * width);
debug_assert_eq!(score.len(), slots * width);
out.fill(0.0);
for d in 0..width {
let mut m = f32::NEG_INFINITY;
for t in 0..slots {
m = m.max(score[t * width + d]);
}
if !m.is_finite() {
continue;
}
let mut denom = 0.0;
for t in 0..slots {
denom += (score[t * width + d] - m).exp();
}
if denom <= 0.0 {
continue;
}
for t in 0..slots {
out[d] += ((score[t * width + d] - m).exp() / denom) * kv[t * width + d];
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn compress_window_overlap(
prev_kv: &[f32],
prev_score: &[f32],
cur_kv: &[f32],
cur_score: &[f32],
ratio: usize,
d: usize,
out: &mut [f32],
) {
let slots = 2 * ratio;
let mut kv = vec![0.0f32; slots * d];
let mut sc = vec![f32::NEG_INFINITY; slots * d];
let have_prev = prev_kv.len() == ratio * 2 * d;
for t in 0..ratio {
if have_prev {
kv[t * d..(t + 1) * d].copy_from_slice(&prev_kv[t * 2 * d..t * 2 * d + d]);
sc[t * d..(t + 1) * d].copy_from_slice(&prev_score[t * 2 * d..t * 2 * d + d]);
}
let src = t * 2 * d + d;
let dst = (ratio + t) * d;
kv[dst..dst + d].copy_from_slice(&cur_kv[src..src + d]);
sc[dst..dst + d].copy_from_slice(&cur_score[src..src + d]);
}
pool_by_score(&kv, &sc, slots, d, out);
}
#[allow(clippy::too_many_arguments)]
pub fn index_scores(
q_heads: &[f32],
kv: &[f32],
head_weights: &[f32],
n_heads: usize,
head_dim: usize,
n_pos: usize,
causal_limit: usize,
pool: Option<&crate::pool::Pool>,
out: &mut Vec<f32>,
) {
out.clear();
out.resize(n_pos, 0.0);
let score_at = |t: usize| -> f32 {
if t >= causal_limit {
return f32::NEG_INFINITY;
}
let k = &kv[t * head_dim..(t + 1) * head_dim];
let mut acc = 0.0;
for h in 0..n_heads {
let q = &q_heads[h * head_dim..(h + 1) * head_dim];
let dot: f32 = q.iter().zip(k).map(|(a, b)| a * b).sum();
acc += dot.max(0.0) * head_weights[h];
}
acc
};
match pool {
Some(p) if n_pos >= 64 => {
let addr = crate::pool::SendMut::new(out.as_mut_ptr());
p.run_rows(n_pos, &|start, end| {
for t in start..end {
unsafe { *addr.at(t) = score_at(t) };
}
});
}
_ => {
for (t, o) in out.iter_mut().enumerate() {
*o = score_at(t);
}
}
}
}
pub fn top_k_positions(scores: &[f32], k: usize, out: &mut Vec<usize>) {
out.clear();
if k >= scores.len() {
out.extend(
scores
.iter()
.enumerate()
.filter(|(_, v)| v.is_finite())
.map(|(i, _)| i),
);
return;
}
let mut taken = vec![false; scores.len()];
for _ in 0..k.min(scores.len()) {
let mut best = usize::MAX;
let mut bv = f32::NEG_INFINITY;
for (i, &v) in scores.iter().enumerate() {
if !taken[i] && v > bv && v.is_finite() {
bv = v;
best = i;
}
}
if best == usize::MAX {
break;
}
taken[best] = true;
out.push(best);
}
out.sort_unstable();
}
#[allow(clippy::too_many_arguments)]
pub fn expert_swiglu(
x: &[f32],
w1: &dyn Fn(&[f32], &mut [f32]),
w3: &dyn Fn(&[f32], &mut [f32]),
w2: &dyn Fn(&[f32], &mut [f32]),
inter: usize,
weight: f32,
limit: f32,
out: &mut [f32],
) {
let mut gate = vec![0.0f32; inter];
let mut up = vec![0.0f32; inter];
w1(x, &mut gate);
w3(x, &mut up);
if limit > 0.0 {
for u in up.iter_mut() {
*u = u.clamp(-limit, limit);
}
for g in gate.iter_mut() {
*g = g.min(limit);
}
}
for (g, u) in gate.iter_mut().zip(&up) {
let silu = *g / (1.0 + (-*g).exp());
*g = silu * u * weight;
}
w2(&gate, out);
}
#[derive(Debug, Clone, Copy)]
pub struct Dsv4Cfg {
pub dim: usize,
pub n_heads: usize,
pub head_dim: usize,
pub rope_head_dim: usize,
pub q_lora_rank: usize,
pub o_lora_rank: usize,
pub o_groups: usize,
pub hc_mult: usize,
pub hc_sinkhorn_iters: usize,
pub hc_eps: f32,
pub norm_eps: f32,
pub n_routed_experts: usize,
pub top_k: usize,
pub moe_inter: usize,
pub route_scale: f32,
pub swiglu_limit: f32,
pub window: usize,
pub index_topk: usize,
pub vocab: usize,
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
pub fn hc_block<F: FnMut(&[f32], &mut [f32])>(
state: &mut [f32],
hc_fn: &[f32],
hc_scale: &[f32; 3],
hc_base: &[f32],
norm_w: &[f32],
cfg: &Dsv4Cfg,
scratch: &mut HcScratch,
pool: Option<&crate::pool::Pool>,
mut block: F,
) {
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let mix_hc = (2 + hc) * hc;
hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut scratch.mixes);
hc_split_sinkhorn(
&scratch.mixes,
hc_scale,
hc_base,
hc,
cfg.hc_sinkhorn_iters,
cfg.hc_eps,
&mut scratch.pre,
&mut scratch.post,
&mut scratch.comb,
);
hc_fold(state, &scratch.pre, hc, dim, &mut scratch.folded);
let ms = scratch.folded.iter().map(|v| v * v).sum::<f32>() / dim as f32;
let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
for (v, w) in scratch.folded.iter_mut().zip(norm_w) {
*v = *v * inv * w;
}
block(&scratch.folded, &mut scratch.block_out);
scratch.residual.copy_from_slice(state);
hc_expand(
&scratch.block_out,
&scratch.residual,
&scratch.post,
&scratch.comb,
hc,
dim,
state,
);
}
pub struct HcScratch {
pub mixes: Vec<f32>,
pub pre: Vec<f32>,
pub post: Vec<f32>,
pub comb: Vec<f32>,
pub folded: Vec<f32>,
pub block_out: Vec<f32>,
pub residual: Vec<f32>,
}
impl HcScratch {
pub fn new(cfg: &Dsv4Cfg) -> Self {
let (hc, dim) = (cfg.hc_mult, cfg.dim);
Self {
mixes: vec![0.0; (2 + hc) * hc],
pre: vec![0.0; hc],
post: vec![0.0; hc],
comb: vec![0.0; hc * hc],
folded: vec![0.0; dim],
block_out: vec![0.0; dim],
residual: vec![0.0; hc * dim],
}
}
}
pub fn hc_head_fold(
state: &[f32],
hc_fn: &[f32],
hc_scale: f32,
hc_base: &[f32],
cfg: &Dsv4Cfg,
pool: Option<&crate::pool::Pool>,
out: &mut [f32],
) {
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let mut mixes = vec![0.0f32; hc];
hc_mixes(state, hc_fn, hc, cfg.norm_eps, pool, &mut mixes);
let mut pre = vec![0.0f32; hc];
hc_head_pre(&mixes, hc_scale, hc_base, hc, cfg.hc_eps, &mut pre);
hc_fold(state, &pre, hc, dim, out);
}
pub struct Dsv4Layer {
pub attn_norm: Vec<f32>,
pub ffn_norm: Vec<f32>,
pub wq_a: crate::qtensor::QTensor,
pub q_norm: Vec<f32>,
pub wq_b: crate::qtensor::QTensor,
pub wkv: crate::qtensor::QTensor,
pub kv_norm: Vec<f32>,
pub wo_a: crate::qtensor::QTensor,
pub wo_b: crate::qtensor::QTensor,
pub attn_sink: Vec<f32>,
pub compressor: Option<Dsv4Compressor>,
pub indexer: Option<Dsv4Indexer>,
pub hc_attn_fn: Vec<f32>,
pub hc_attn_base: Vec<f32>,
pub hc_attn_scale: [f32; 3],
pub hc_ffn_fn: Vec<f32>,
pub hc_ffn_base: Vec<f32>,
pub hc_ffn_scale: [f32; 3],
pub gate: crate::qtensor::QTensor,
pub gate_bias: Option<Vec<f32>>,
pub tid2eid: Option<Vec<f32>>,
pub experts: Vec<Dsv4Expert>,
pub shared: Dsv4Expert,
pub mask: Option<Vec<bool>>,
}
pub struct Dsv4Expert {
pub w1: crate::qtensor::QTensor,
pub w2: crate::qtensor::QTensor,
pub w3: crate::qtensor::QTensor,
}
pub struct Dsv4Compressor {
pub wkv: crate::qtensor::QTensor,
pub wgate: crate::qtensor::QTensor,
pub norm: Vec<f32>,
pub ape: Vec<f32>,
pub ratio: usize,
pub overlap: bool,
}
pub struct Dsv4Indexer {
pub wq_b: crate::qtensor::QTensor,
pub weights_proj: crate::qtensor::QTensor,
pub compressor: Dsv4Compressor,
}
pub struct Dsv4Globals {
pub inv_freq_compress: Vec<f32>,
pub inv_freq_window: Vec<f32>,
pub embed: crate::qtensor::QTensor,
pub norm: Vec<f32>,
pub head: crate::qtensor::QTensor,
pub hc_head_fn: Vec<f32>,
pub hc_head_base: Vec<f32>,
pub hc_head_scale: f32,
}
pub struct Dsv4State {
pub window: Vec<Vec<f32>>,
pub compressed: Vec<Vec<f32>>,
pub index_kv: Vec<Vec<f32>>,
pub pending_kv: Vec<Vec<f32>>,
pub pending_score: Vec<Vec<f32>>,
pub prev_kv: Vec<Vec<f32>>,
pub prev_score: Vec<Vec<f32>>,
pub pending_ix_kv: Vec<Vec<f32>>,
pub pending_ix_score: Vec<Vec<f32>>,
pub prev_ix_kv: Vec<Vec<f32>>,
pub prev_ix_score: Vec<Vec<f32>>,
pub pos: usize,
pub kv_id: u64,
pub dev_filled: Vec<usize>,
pub dev_n_comp: Vec<usize>,
pub dev_n_ix: Vec<usize>,
pub dev_owned: bool,
pub dev_set: Vec<bool>,
pub partial_set: Vec<bool>,
pub split_deep: bool,
}
impl Dsv4State {
pub fn new(layers: usize) -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT: AtomicU64 = AtomicU64::new(1);
Self {
kv_id: NEXT.fetch_add(1, Ordering::Relaxed),
dev_filled: vec![0; layers],
dev_n_comp: vec![0; layers],
dev_n_ix: vec![0; layers],
dev_owned: false,
dev_set: Vec::new(),
partial_set: Vec::new(),
split_deep: false,
window: vec![Vec::new(); layers],
compressed: vec![Vec::new(); layers],
index_kv: vec![Vec::new(); layers],
pending_kv: vec![Vec::new(); layers],
pending_score: vec![Vec::new(); layers],
prev_kv: vec![Vec::new(); layers],
prev_score: vec![Vec::new(); layers],
pending_ix_kv: vec![Vec::new(); layers],
pending_ix_score: vec![Vec::new(); layers],
prev_ix_kv: vec![Vec::new(); layers],
prev_ix_score: vec![Vec::new(); layers],
pos: 0,
}
}
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
fn compressor_step(
cp: &Dsv4Compressor,
hidden: &[f32],
pos: usize,
rd: usize,
norm_eps: f32,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
pending_kv: &mut Vec<f32>,
pending_score: &mut Vec<f32>,
prev_kv: &mut Vec<f32>,
prev_score: &mut Vec<f32>,
) -> Option<Vec<f32>> {
let width = cp.wkv.rows();
let ew = if cp.overlap { width / 2 } else { width };
let mut ckv = vec![0.0f32; width];
let mut cscore = vec![0.0f32; width];
crate::qtensor::QTensor::matvec_many(
[&cp.wkv, &cp.wgate],
hidden,
[&mut ckv, &mut cscore],
pool,
);
if cp.overlap {
let slot = pos % cp.ratio;
for (c, a) in cscore
.iter_mut()
.zip(&cp.ape[slot * width..(slot + 1) * width])
{
*c += a;
}
}
pending_kv.extend_from_slice(&ckv);
pending_score.extend_from_slice(&cscore);
if pending_kv.len() / width < cp.ratio {
return None;
}
let mut folded = vec![0.0f32; ew];
if cp.overlap {
compress_window_overlap(
prev_kv,
prev_score,
pending_kv,
pending_score,
cp.ratio,
ew,
&mut folded,
);
*prev_kv = std::mem::take(pending_kv);
*prev_score = std::mem::take(pending_score);
} else {
compress_window(
pending_kv,
pending_score,
&cp.ape,
cp.ratio,
width,
&mut folded,
);
}
rms_weighted(&mut folded, &cp.norm, norm_eps);
rope_tail(&mut folded, inv_freq, pos + 1 - cp.ratio, rd, false);
pending_kv.clear();
pending_score.clear();
Some(folded)
}
pub(crate) mod prof {
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
pub static ATTN_NS: AtomicU64 = AtomicU64::new(0);
pub static MOE_NS: AtomicU64 = AtomicU64::new(0);
pub static CALLS: AtomicU64 = AtomicU64::new(0);
pub static HC_NS: AtomicU64 = AtomicU64::new(0);
pub static HEAD_NS: AtomicU64 = AtomicU64::new(0);
pub static ALL_NS: AtomicU64 = AtomicU64::new(0);
pub static TOKENS: AtomicU64 = AtomicU64::new(0);
pub fn note_layer(li: usize) {
CALLS.fetch_add(1, Ordering::Relaxed);
if li == 0 {
if TOKENS.fetch_add(1, Ordering::Relaxed) == 1 && !ZEROED.swap(true, Ordering::Relaxed)
{
for a in [&ATTN_NS, &MOE_NS, &HC_NS, &HEAD_NS, &ALL_NS, &CALLS] {
a.store(0, Ordering::Relaxed);
}
TOKENS.store(1, Ordering::Relaxed);
#[cfg(feature = "gpu")]
for a in [
&crate::gpu_wgpu::MOE_ENC_NS,
&crate::gpu_wgpu::MOE_WAIT_NS,
&crate::gpu_wgpu::MOE_BUFS_NS,
&crate::gpu_wgpu::MOE_UP_NS,
&crate::gpu_wgpu::MOE_PASS_NS,
&crate::gpu_wgpu::ATT_ENC_NS,
&crate::gpu_wgpu::ATT_WAIT_NS,
&crate::gpu_wgpu::CHAIN_ENC_NS,
&crate::gpu_wgpu::CHAIN_WAIT_NS,
&crate::gpu_wgpu::CHAIN_LAYERS,
&crate::gpu_wgpu::CHAIN_RUNS,
&crate::gpu_wgpu::SUBMITS,
&crate::gpu_wgpu::PASSES,
] {
a.store(0, Ordering::Relaxed);
}
}
}
}
static REPORT: AtomicBool = AtomicBool::new(false);
static ZEROED: AtomicBool = AtomicBool::new(false);
pub fn on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_PROFILE").is_ok_and(|v| v != "0"))
}
pub fn report() {
if !on() || REPORT.swap(true, Ordering::Relaxed) {
return;
}
let calls = CALLS.load(Ordering::Relaxed).max(1);
let toks = TOKENS.load(Ordering::Relaxed).max(1);
let (a, m) = (
ATTN_NS.load(Ordering::Relaxed) as f64 / 1e6,
MOE_NS.load(Ordering::Relaxed) as f64 / 1e6,
);
let all = ALL_NS.load(Ordering::Relaxed) as f64 / 1e6;
let hc = (HC_NS.load(Ordering::Relaxed) as f64 / 1e6
- MOE_NS.load(Ordering::Relaxed) as f64 / 1e6)
.max(0.0);
let hd = HEAD_NS.load(Ordering::Relaxed) as f64 / 1e6;
eprintln!(
"[dsv4-профиль] {calls} вызовов слоя за {toks} токенов | \
на токен: внимание {:.0} мс, MoE {:.0} мс, гипер-связи+нормы {:.0} мс, \
голова {:.0} мс | на вызов: внимание {:.2}, MoE {:.2}, связи {:.2}",
a / toks as f64,
m / toks as f64,
hc / toks as f64,
hd / toks as f64,
a / calls as f64,
m / calls as f64,
hc / calls as f64,
);
eprintln!(
"[dsv4-профиль] весь проход {:.0} мс на токен; вне счётчиков {:.0} мс",
all / toks as f64,
(all - a - m - hd) / toks as f64,
);
#[cfg(feature = "gpu")]
{
let ae = crate::gpu_wgpu::ATT_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
let aw = crate::gpu_wgpu::ATT_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
if ae + aw > 0.0 {
eprintln!(
"[dsv4-профиль] кадр внимания на вызов: кодирование {:.2} мс, \
отправка и ожидание {:.2} мс",
ae / calls as f64,
aw / calls as f64,
);
}
let ub = crate::gpu_wgpu::UPLOAD_BYTES.load(Ordering::Relaxed);
let un = crate::gpu_wgpu::UPLOAD_NS.load(Ordering::Relaxed);
if ub > 0 && un > 0 {
eprintln!(
"[dsv4-профиль] ЗАЛИВКА весов: {:.1} ГБ за {:.1} с ({:.0} МБ/с)",
ub as f64 / 1e9,
un as f64 / 1e9,
ub as f64 / (un as f64 / 1e9) / 1e6,
);
}
let sub = crate::gpu_wgpu::SUBMITS.load(Ordering::Relaxed);
if sub > 0 {
eprintln!(
"[dsv4-профиль] ОТПРАВОК на карту: {:.1} на токен, ПРОХОДОВ {:.0} \
({:.1} на слой)",
sub as f64 / toks as f64,
crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / toks as f64,
crate::gpu_wgpu::PASSES.load(Ordering::Relaxed) as f64 / calls as f64,
);
}
let cl = crate::gpu_wgpu::CHAIN_LAYERS.load(Ordering::Relaxed);
if cl > 0 {
let toks2 = toks.max(1) as f64;
eprintln!(
"[dsv4-профиль] ЦЕПОЧКА на токен: кодирование {:.2} мс, \
ожидание {:.2} мс ({} слоёв, {} отправок)",
crate::gpu_wgpu::CHAIN_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
crate::gpu_wgpu::CHAIN_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6 / toks2,
cl / toks.max(1),
crate::gpu_wgpu::CHAIN_RUNS.load(Ordering::Relaxed) / toks.max(1),
);
}
let e = crate::gpu_wgpu::MOE_ENC_NS.load(Ordering::Relaxed) as f64 / 1e6;
let wt = crate::gpu_wgpu::MOE_WAIT_NS.load(Ordering::Relaxed) as f64 / 1e6;
if e + wt > 0.0 {
let ns = |a: &std::sync::atomic::AtomicU64| {
a.load(Ordering::Relaxed) as f64 / 1e6 / calls as f64
};
eprintln!(
"[dsv4-профиль] кадр MoE на вызов: кодирование {:.2} мс, \
отправка и ожидание {:.2} мс",
e / calls as f64,
wt / calls as f64,
);
let an = crate::gpu_wgpu::ATT_GPU_N.load(Ordering::Relaxed);
if an > 0 {
let g = |i: usize| {
crate::gpu_wgpu::ATT_GPU_NS[i].load(Ordering::Relaxed) as f64
/ 1e6 / an as f64
};
eprintln!(
"[dsv4-профиль] ВНИМАНИЕ НА КАРТЕ на вызов: одиночное {:.3} мс, \
оценки {:.3} мс, применение {:.3} мс",
g(0), g(1), g(2),
);
}
let gn = crate::gpu_wgpu::MOE_GPU_N.load(Ordering::Relaxed);
let gns = crate::gpu_wgpu::MOE_GPU_NS[0].load(Ordering::Relaxed);
if gn > 0 && gns > 0 {
eprintln!(
"[dsv4-профиль] MoE НА КАРТЕ: {:.3} мс на вызов ({gn} замеров)",
gns as f64 / 1e6 / gn as f64,
);
} else if gn > 0 {
eprintln!(
"[dsv4-профиль] MoE НА КАРТЕ: метки вернули НОЛЬ на {gn} замерах — \
запрос времени не сработал, число не использовать"
);
}
eprintln!(
"[dsv4-профиль] из кодирования: буферы экспертов {:.2} мс, \
загрузки {:.2} мс, проходы {:.2} мс",
ns(&crate::gpu_wgpu::MOE_BUFS_NS),
ns(&crate::gpu_wgpu::MOE_UP_NS),
ns(&crate::gpu_wgpu::MOE_PASS_NS),
);
}
}
}
}
pub fn profile_report() {
prof::report();
}
fn gpu_attn_enabled() -> bool {
#[cfg(feature = "gpu")]
{
use std::sync::OnceLock;
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| {
let want = std::env::var("CMF_DSV4_GPU_ATTN")
.map(|v| v != "0")
.unwrap_or(true);
let have = want && crate::gpu::backend_available();
if want && !have && std::env::var("CMF_DSV4_GPU_ATTN").is_ok() {
tracing::warn!(
"CMF_DSV4_GPU_ATTN задан, но устройства нет — блок внимания остаётся на CPU. Проверьте CMF_GPU=wgpu и Vulkan-ICD."
);
}
if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
eprintln!("кадр dsv4: запрошен={want} доступен={have}");
}
have
})
}
#[cfg(not(feature = "gpu"))]
{
false
}
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn attn_frame(
l: &Dsv4Layer,
cfg: &Dsv4Cfg,
st: &Dsv4State,
li: usize,
hidden: &[f32],
qn: &[f32],
idxs: &[usize],
inv_freq: &[f32],
pos: usize,
win_len: usize,
scale: f32,
hc: Option<&crate::gpu_wgpu::Dsv4HcTail>,
out: &mut [f32],
) -> bool {
let hd = cfg.head_dim;
let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
l.wq_a.model_idx(),
l.wq_b.model_idx(),
l.wo_a.model_idx(),
l.wo_b.model_idx(),
) else {
return false;
};
let Some(model) = l.wq_b.model_arc() else {
return false;
};
let n_comp = st.compressed[li].len() / hd;
let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
let kv_id = st.kv_id;
if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap) {
return false;
}
if n_comp > 0
&& !crate::gpu_wgpu::dsv4_cache_write(
kv_id,
li,
cfg.window * hd,
&st.compressed[li],
cap,
)
{
return false;
}
let idx32: Vec<u32> = idxs
.iter()
.map(|&p| {
if p < win_len {
p as u32
} else {
(cfg.window + (p - win_len)) as u32
}
})
.collect();
let w = crate::gpu_wgpu::Dsv4AttnW {
wq_a,
wq_b,
wo_a,
wo_b,
q_norm: &l.q_norm,
sink: &l.attn_sink,
};
let g = crate::gpu_wgpu::Dsv4AttnGeom {
dim: cfg.dim,
nh: cfg.n_heads,
hd,
rd: cfg.rope_head_dim,
q_lora: cfg.q_lora_rank,
o_lora: cfg.o_lora_rank,
o_groups: cfg.o_groups,
eps: cfg.norm_eps,
scale,
};
crate::gpu_wgpu::dsv4_attn_frame(
&model, &w, g, hidden, Some(qn), kv_id, li, &idx32, inv_freq, pos, hc, out,
)
}
#[derive(Default)]
pub struct AttnPrep {
pub qr: Vec<f32>,
pub idxs: Vec<usize>,
pub win_len: usize,
}
#[allow(clippy::too_many_arguments)]
pub fn attention_step(
hidden: &[f32],
l: &Dsv4Layer,
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
li: usize,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
prep_out: Option<&mut AttnPrep>,
out: &mut [f32],
) {
let _t0 = prof::on().then(std::time::Instant::now);
let _guard = scopeguard_attn(_t0);
let (hd, rd) = (cfg.head_dim, cfg.rope_head_dim);
let pos = st.pos;
if std::env::var("CMF_FREQ_DEBUG").is_ok() && li == 0 && pos == 0 {
eprintln!(
" [порт] rd={rd} частот={} inv_freq[0..4]={:?}",
inv_freq.len(),
&inv_freq[..4.min(inv_freq.len())]
);
}
let mut qr = vec![0.0f32; cfg.q_lora_rank];
let mut kv = vec![0.0f32; hd];
crate::qtensor::QTensor::matvec_many([&l.wq_a, &l.wkv], hidden, [&mut qr, &mut kv], pool);
rms_weighted(&mut qr, &l.q_norm, cfg.norm_eps);
let split_config = st.partial_set.iter().any(|&p| p) && st.split_deep;
let past_chain = st.dev_owned
&& (li >= st.dev_set.len() || !st.dev_set.get(li).copied().unwrap_or(false));
if std::env::var("CMF_DSV4_GATE_DBG").is_ok() {
eprintln!(
"[gate] li={li} pos={} split={split_config} past={past_chain} dev_owned={} set_len={} part_len={}",
st.pos, st.dev_owned, st.dev_set.len(), st.partial_set.len()
);
}
let on_gpu = gpu_attn_enabled() && !(split_config && past_chain);
rms_weighted(&mut kv, &l.kv_norm, cfg.norm_eps);
rope_tail(&mut kv, inv_freq, pos, rd, false);
if let Some(cp) = &l.compressor {
let mut pk = std::mem::take(&mut st.pending_kv[li]);
let mut ps = std::mem::take(&mut st.pending_score[li]);
let mut qk = std::mem::take(&mut st.prev_kv[li]);
let mut qs = std::mem::take(&mut st.prev_score[li]);
let entry = compressor_step(
cp,
hidden,
pos,
rd,
cfg.norm_eps,
inv_freq,
pool,
&mut pk,
&mut ps,
&mut qk,
&mut qs,
);
st.pending_kv[li] = pk;
st.pending_score[li] = ps;
st.prev_kv[li] = qk;
st.prev_score[li] = qs;
if let Some(e) = entry {
st.compressed[li].extend_from_slice(&e);
}
}
if let Some(ix) = &l.indexer {
let mut pk = std::mem::take(&mut st.pending_ix_kv[li]);
let mut ps = std::mem::take(&mut st.pending_ix_score[li]);
let mut qk = std::mem::take(&mut st.prev_ix_kv[li]);
let mut qs = std::mem::take(&mut st.prev_ix_score[li]);
let entry = compressor_step(
&ix.compressor,
hidden,
pos,
rd,
cfg.norm_eps,
inv_freq,
pool,
&mut pk,
&mut ps,
&mut qk,
&mut qs,
);
st.pending_ix_kv[li] = pk;
st.pending_ix_score[li] = ps;
st.prev_ix_kv[li] = qk;
st.prev_ix_score[li] = qs;
if let Some(e) = entry {
st.index_kv[li].extend_from_slice(&e);
}
}
st.window[li].extend_from_slice(&kv);
let cap = cfg.window * hd;
if st.window[li].len() > cap {
let drop = st.window[li].len() - cap;
st.window[li].drain(..drop);
}
let win_len = st.window[li].len() / hd;
let n_pos = win_len + st.compressed[li].len() / hd;
let mut idxs: Vec<usize> = (0..win_len).collect();
if !st.compressed[li].is_empty() && !no_compressed() {
let n_comp = st.compressed[li].len() / hd;
match &l.indexer {
Some(ix) => {
let ih = ix.weights_proj.rows();
let idim = ix.wq_b.rows() / ih.max(1);
let mut qi = vec![0.0f32; ix.wq_b.rows()];
ix.wq_b.matvec(&qr, &mut qi, pool);
for h in 0..ih {
rope_tail(&mut qi[h * idim..(h + 1) * idim], inv_freq, pos, rd, false);
}
let mut hw = vec![0.0f32; ih];
ix.weights_proj.matvec(hidden, &mut hw, pool);
let sc_factor = (idim as f32).powf(-0.5) * (ih as f32).powf(-0.5);
for w in hw.iter_mut() {
*w *= sc_factor;
}
let n_ix = st.index_kv[li].len() / idim.max(1);
let mut sc = Vec::new();
index_scores(
&qi,
&st.index_kv[li],
&hw,
ih,
idim,
n_ix.min(n_comp),
n_ix.min(n_comp),
pool,
&mut sc,
);
let mut picked = Vec::new();
top_k_positions(&sc, cfg.index_topk, &mut picked);
idxs.extend(picked.into_iter().map(|p| win_len + p));
}
None => idxs.extend((0..n_comp).map(|p| win_len + p)),
}
}
debug_assert!(idxs.iter().all(|&p| p < n_pos));
if let Some(p) = prep_out {
p.qr = qr;
p.idxs = idxs;
p.win_len = win_len;
return;
}
let scale = (hd as f32).powf(-0.5);
#[cfg(feature = "gpu")]
if on_gpu
&& {
if std::env::var("CMF_DSV4_XCHK").is_ok() {
if let Some(card) = crate::gpu_wgpu::dsv4_dbg_read_tag(45, 0, hidden.len()) {
let md = hidden
.iter()
.zip(card.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0f32, f32::max);
eprintln!("[xchk] li={li} pos={pos} x2 maxdiff={md:.3e}");
}
}
true
}
&& attn_frame(
l, cfg, st, li, hidden, &qr, &idxs, inv_freq, pos, win_len, scale, None, out,
)
{
return;
}
let mut q = vec![0.0f32; cfg.n_heads * hd];
l.wq_b.matvec(&qr, &mut q, pool);
for h in 0..cfg.n_heads {
let head = &mut q[h * hd..(h + 1) * hd];
rms_inplace(head, cfg.norm_eps);
rope_tail(head, inv_freq, pos, rd, false);
}
let mut cache: Vec<f32> = st.window[li].clone();
cache.extend_from_slice(&st.compressed[li]);
let mut attn = vec![0.0f32; cfg.n_heads * hd];
for h in 0..cfg.n_heads {
let qh = &q[h * hd..(h + 1) * hd];
let oh = &mut attn[h * hd..(h + 1) * hd];
sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
rope_tail(oh, inv_freq, pos, rd, true);
}
o_project(
&attn,
&|r, x, sc| l.wo_a.row_dot(r, x, sc),
l.wo_a.cols(),
&|mid, dst| l.wo_b.matvec(mid, dst, pool),
cfg.o_groups,
cfg.o_lora_rank,
pool,
out,
);
}
pub fn rms_weighted(v: &mut [f32], w: &[f32], eps: f32) {
let ms = v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32;
let inv = 1.0 / (ms + eps).sqrt();
for (x, g) in v.iter_mut().zip(w) {
*x = *x * inv * g;
}
}
thread_local! {
static ROUTE_COUNTS: std::cell::RefCell<Vec<Vec<u64>>> =
const { std::cell::RefCell::new(Vec::new()) };
}
fn route_stats_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_MOE_STATS").is_ok())
}
fn record_route(li: usize, n_layers_hint: usize, n_experts: usize, idx: &[usize]) {
ROUTE_COUNTS.with(|c| {
let mut c = c.borrow_mut();
if c.len() <= li.max(n_layers_hint) {
c.resize(li.max(n_layers_hint) + 1, Vec::new());
}
let row = &mut c[li];
if row.len() < n_experts {
row.resize(n_experts, 0);
}
for &e in idx {
if e < row.len() {
row[e] += 1;
}
}
});
}
pub fn take_route_counts() -> Vec<Vec<u64>> {
ROUTE_COUNTS.with(|c| std::mem::take(&mut *c.borrow_mut()))
}
struct Charge(Option<std::time::Instant>, &'static std::sync::atomic::AtomicU64);
impl Drop for Charge {
fn drop(&mut self) {
if let Some(t) = self.0 {
self.1.fetch_add(
t.elapsed().as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
}
}
fn scopeguard_attn(t: Option<std::time::Instant>) -> Charge {
Charge(t, &prof::ATTN_NS)
}
fn scopeguard_moe(t: Option<std::time::Instant>, li: usize) -> Charge {
if t.is_some() {
prof::note_layer(li);
}
Charge(t, &prof::MOE_NS)
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn dsv4_layer_loop(
state: &mut [f32],
layers: &[Dsv4Layer],
g: &Dsv4Globals,
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
token_id: u32,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
scratch: &mut HcScratch,
) -> bool {
let dim = cfg.dim;
let freqs_of = |l: &Dsv4Layer| -> &[f32] {
let f = if l.compressor.is_some() {
&g.inv_freq_compress
} else {
&g.inv_freq_window
};
if f.is_empty() { inv_freq } else { f.as_slice() }
};
{
static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
if let (Some(idx), Some(model)) = (g.head.model_idx(), g.head.model_arc()) {
let ok = crate::gpu_wgpu::dsv4_weight_ready(&model, idx);
tracing::info!("dsv4: голова на карте: {}", if ok { "да" } else { "нет" });
}
}
}
let mut on_dev = vec![false; layers.len()];
let mut partial_dev = vec![false; layers.len()];
for (li, l) in layers.iter().enumerate() {
if l.wq_a.model_idx().is_none()
|| l.wq_b.model_idx().is_none()
|| l.wo_a.model_idx().is_none()
|| l.wo_b.model_idx().is_none()
{
return false;
}
let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
return false;
};
let gu_q2 = l
.experts
.first()
.is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
let mut want = vec![
l.wq_a.model_idx(),
l.wq_b.model_idx(),
l.wo_a.model_idx(),
l.wo_b.model_idx(),
];
if chain_enabled() {
want.push(l.wkv.model_idx());
if let Some(cp) = &l.compressor {
want.push(cp.wkv.model_idx());
want.push(cp.wgate.model_idx());
}
if let Some(ix) = &l.indexer {
want.push(ix.wq_b.model_idx());
want.push(ix.weights_proj.model_idx());
want.push(ix.compressor.wkv.model_idx());
want.push(ix.compressor.wgate.model_idx());
}
}
let attn_ok = want
.into_iter()
.flatten()
.all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
let pk = pack_for(l, cfg, li);
if let Some(pk) = pk {
let dn_q2 = l
.experts
.first()
.is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
let experts_ok = crate::gpu_wgpu::dsv4_experts_ready(
&model,
&pk.tensors,
cfg.moe_inter,
dim,
gu_q2,
dn_q2,
);
on_dev[li] = attn_ok && experts_ok && pk.globals.len() == cfg.n_routed_experts;
partial_dev[li] = attn_ok && experts_ok && pk.globals.len() < cfg.n_routed_experts;
}
}
let active_dev: Vec<bool> = on_dev
.iter()
.zip(&partial_dev)
.map(|(&full, &partial)| full || partial)
.collect();
if !active_dev.iter().any(|&x| x) {
return false;
}
if st.partial_set.len() != partial_dev.len() || st.partial_set != partial_dev {
st.partial_set = partial_dev.clone();
st.split_deep = active_dev
.iter()
.zip(&partial_dev)
.filter(|(a, p)| !**a || **p)
.count()
> 1;
}
{
static SAID: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if !SAID.swap(true, std::sync::atomic::Ordering::Relaxed) {
let host: Vec<usize> = active_dev
.iter()
.enumerate()
.filter(|&(_, d)| !*d)
.map(|(i, _)| i)
.collect();
let partial: Vec<(usize, usize)> = partial_dev
.iter()
.enumerate()
.filter(|&(_, d)| *d)
.filter_map(|(li, _)| pack_for(&layers[li], cfg, li).map(|p| (li, p.globals.len())))
.collect();
if host.is_empty() && partial.is_empty() {
tracing::info!("dsv4: все {} слоёв на карте", on_dev.len());
} else {
tracing::info!(
"dsv4: {} из {} слоёв используют карту; частичные {:?}; на хосте {:?}",
active_dev.len() - host.len(),
on_dev.len(),
partial,
host,
);
}
}
}
let (mut folded, post0, comb0) = hc_fold_norm(
state,
&layers[0].hc_attn_fn,
&layers[0].hc_attn_scale,
&layers[0].hc_attn_base,
&layers[0].attn_norm,
cfg,
pool,
);
if !crate::gpu_wgpu::dsv4_state_write(state)
|| !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
{
return false;
}
if st.dev_owned && st.dev_set != active_dev {
let left: Vec<usize> = (0..active_dev.len().min(st.dev_set.len()))
.filter(|&i| st.dev_set[i] && !active_dev[i])
.collect();
if !left.is_empty() {
tracing::warn!("слои {left:?} ушли с карты — кеши на разных сторонах");
return false;
}
}
let chain = chain_enabled();
let layer_cap = {
static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*N.get_or_init(|| {
std::env::var("CMF_DSV4_LAYERS_PROBE")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(usize::MAX)
})
};
let mut run: Vec<usize> = Vec::new();
let mut sink_out = vec![0.0f32; dim];
let mut state_on_host = true;
for (li, l) in layers.iter().enumerate() {
if li >= layer_cap {
break;
}
if prof::on() {
prof::note_layer(li);
}
if chain && on_dev[li] {
run.push(li);
if run.len() >= chain_max() || dspark_wants(li) {
let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
let captured = *run.last().unwrap();
if !dsv4_chain_run(
layers, &run, cfg, g, st, token_id, &mut folded,
Some(state), 1, &[], need_qn, pool,
) {
return false;
}
state_on_host = true;
dspark_note(captured, state, cfg);
run.clear();
}
continue;
}
if chain && !run.is_empty() {
if !dsv4_chain_run(
layers,
&run,
cfg,
g,
st,
token_id,
&mut folded,
Some(state),
1,
&[],
run[0] == 0 || !on_dev[run[0] - 1],
pool,
) {
return false;
}
state_on_host = true;
dspark_note(*run.last().unwrap(), state, cfg);
}
run.clear();
if partial_dev[li] && partial_walk_on() {
let Some(home) = dsv4_partial_layer(
state,
&mut folded,
layers,
l,
cfg,
st,
token_id,
li,
freqs_of(l),
pool,
) else {
return false;
};
state_on_host = home;
if home {
dspark_note(li, state, cfg);
}
continue;
}
if !on_dev[li] {
if !state_on_host && !crate::gpu_wgpu::dsv4_state_read(state) {
return false;
}
state_on_host = true;
let freqs = freqs_of(l);
hc_block(
state,
&l.hc_attn_fn,
&l.hc_attn_scale,
&l.hc_attn_base,
&l.attn_norm,
cfg,
scratch,
pool,
|f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
);
hc_block(
state,
&l.hc_ffn_fn,
&l.hc_ffn_scale,
&l.hc_ffn_base,
&l.ffn_norm,
cfg,
scratch,
pool,
|f, o| {
if host_cpu_moe() {
crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
} else {
moe_step(f, l, cfg, token_id, li, pool, o)
}
},
);
if layers.get(li + 1).is_some() && on_dev.get(li + 1).copied().unwrap_or(false) {
let n = &layers[li + 1];
let (f, p2, c2) = hc_fold_norm(
state,
&n.hc_attn_fn,
&n.hc_attn_scale,
&n.hc_attn_base,
&n.attn_norm,
cfg,
pool,
);
folded = f;
if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2) {
return false;
}
if !crate::gpu_wgpu::dsv4_state_write(state) {
return false;
}
}
dspark_note(li, state, cfg);
continue;
}
let mut prep = AttnPrep::default();
attention_step(
&folded,
l,
cfg,
st,
li,
freqs_of(l),
pool,
Some(&mut prep),
&mut sink_out,
);
let hd = cfg.head_dim;
let n_comp = st.compressed[li].len() / hd;
let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
let kv_id = st.kv_id;
if !crate::gpu_wgpu::dsv4_cache_write(kv_id, li, 0, &st.window[li], cap)
|| (n_comp > 0
&& !crate::gpu_wgpu::dsv4_cache_write(
kv_id,
li,
cfg.window * hd,
&st.compressed[li],
cap,
))
{
return false;
}
let idx32: Vec<u32> = prep
.idxs
.iter()
.map(|&p| {
if p < prep.win_len {
p as u32
} else {
(cfg.window + (p - prep.win_len)) as u32
}
})
.collect();
let Some(pk) = pack_for(l, cfg, li) else {
return false;
};
let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b)) = (
l.wq_a.model_idx(),
l.wq_b.model_idx(),
l.wo_a.model_idx(),
l.wo_b.model_idx(),
) else {
return false;
};
let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
return false;
};
let forced: Option<Vec<usize>> = l.tid2eid.as_ref().and_then(|tbl| {
let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
.into_iter()
.map(|gi| pk.to_slot[gi])
.collect();
if v.iter().any(|&x| x == usize::MAX) {
None
} else {
Some(v)
}
});
if l.tid2eid.is_some() && forced.is_none() {
return false;
}
let nxt = layers.get(li + 1);
let w = crate::gpu_wgpu::Dsv4LayerW {
attn: crate::gpu_wgpu::Dsv4AttnW {
wq_a,
wq_b,
wo_a,
wo_b,
q_norm: &l.q_norm,
sink: &l.attn_sink,
},
moe: crate::gpu_wgpu::Dsv4MoeW {
router: &[],
experts: &pk.tensors,
logits: &[],
bias: pk.bias.as_deref(),
forced: forced.as_deref(),
remap: None,
},
hc_ffn_fn: &l.hc_ffn_fn,
hc_ffn_scale: &l.hc_ffn_scale,
hc_ffn_base: &l.hc_ffn_base,
hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
ffn_norm: &l.ffn_norm,
next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
router: &pk.router,
};
let geom = crate::gpu_wgpu::Dsv4LayerGeom {
attn: crate::gpu_wgpu::Dsv4AttnGeom {
dim,
nh: cfg.n_heads,
hd,
rd: cfg.rope_head_dim,
q_lora: cfg.q_lora_rank,
o_lora: cfg.o_lora_rank,
o_groups: cfg.o_groups,
eps: cfg.norm_eps,
scale: (hd as f32).powf(-0.5),
},
moe: crate::gpu_wgpu::Dsv4MoeGeom {
hidden: dim,
inter: cfg.moe_inter,
top_k: cfg.top_k,
route_scale: cfg.route_scale,
swiglu_limit: cfg.swiglu_limit,
gu_q2: l.experts.first().is_some_and(|e| {
e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
}),
},
hc: cfg.hc_mult,
hc_eps: cfg.hc_eps,
sinkhorn_iters: cfg.hc_sinkhorn_iters,
};
let mut next = vec![0.0f32; dim];
if !crate::gpu_wgpu::dsv4_layer_frame(
&model,
&w,
geom,
kv_id,
li,
Some(&prep.qr),
&idx32,
freqs_of(l),
st.pos,
&mut next,
) {
return false;
}
state_on_host = false;
folded = next;
dspark_note(li, state, cfg);
}
let mut state_home = false;
if chain {
if !run.is_empty() {
let need_qn = run[0] == 0 || !on_dev[run[0] - 1];
let last_on_dev = *on_dev.last().unwrap_or(&false);
let carry = last_on_dev && run.last() == Some(&(layers.len() - 1));
let ok = if carry {
let r = dsv4_chain_run(
layers, &run, cfg, g, st, token_id, &mut folded,
Some(state), 1, &[], need_qn, pool,
);
state_home = r;
state_on_host = r;
if r {
dspark_note(*run.last().unwrap(), state, cfg);
}
r
} else {
let r = dsv4_chain_run(
layers, &run, cfg, g, st, token_id, &mut folded, None, 1, &[], need_qn, pool,
);
if r {
state_on_host = false;
}
r
};
if !ok {
return false;
}
}
if st.dev_set.is_empty() {
st.dev_set = active_dev.clone();
st.partial_set = partial_dev.clone();
let mut idxs = Vec::new();
for (li, l) in layers.iter().enumerate() {
if !active_dev.get(li).copied().unwrap_or(false) {
continue;
}
for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b, &l.gate] {
idxs.extend(t.model_idx());
}
if let Some(pk) = pack_for(l, cfg, li) {
for &(a, b, c) in &pk.tensors {
idxs.extend([a, b, c]);
}
}
}
for (li, l) in layers.iter().enumerate() {
if active_dev.get(li).copied().unwrap_or(false) {
continue;
}
let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
tracing::info!(
"слой {li} на хосте: упаковано {packed} экспертов из {}",
cfg.n_routed_experts
);
}
let pinned = layers
.iter()
.find_map(|l| l.experts.first().and_then(|e| e.w1.model_arc()))
.map_or(0, |m| crate::gpu_wgpu::pin_weights(&m, &idxs));
tracing::info!(
"закреплено на карте: {pinned} тензоров {} слоёв",
on_dev.iter().filter(|&&x| x).count()
);
}
}
if state_home || state_on_host {
return true;
}
crate::gpu_wgpu::dsv4_state_read(state)
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn dsv4_partial_layer(
state: &mut [f32],
folded: &mut Vec<f32>,
layers: &[Dsv4Layer],
l: &Dsv4Layer,
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
token_id: u32,
li: usize,
freqs: &[f32],
pool: Option<&crate::pool::Pool>,
) -> Option<bool> {
let dim = cfg.dim;
let mut prep = AttnPrep::default();
let mut sink = vec![0.0f32; dim];
attention_step(
folded,
l,
cfg,
st,
li,
freqs,
pool,
Some(&mut prep),
&mut sink,
);
let hd = cfg.head_dim;
let n_comp = st.compressed[li].len() / hd;
let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
|| (n_comp > 0
&& !crate::gpu_wgpu::dsv4_cache_write(
st.kv_id,
li,
cfg.window * hd,
&st.compressed[li],
cap,
))
{
return None;
}
let a_tail = crate::gpu_wgpu::Dsv4HcTail {
fn_: &l.hc_ffn_fn,
scale: &l.hc_ffn_scale,
base: &l.hc_ffn_base,
norm: &l.ffn_norm,
hc: cfg.hc_mult,
sinkhorn_iters: cfg.hc_sinkhorn_iters,
hc_eps: cfg.hc_eps,
eps: cfg.norm_eps,
};
let scale = (cfg.head_dim as f32).powf(-0.5);
if !attn_frame(
l,
cfg,
st,
li,
folded,
&prep.qr,
&prep.idxs,
freqs,
st.pos,
prep.win_len,
scale,
Some(&a_tail),
&mut [],
) {
return None;
}
let nxt = layers.get(li + 1);
let forced = l
.tid2eid
.as_ref()
.map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
let mut next = vec![0.0f32; dim];
let (cold_sum, cold_count) = moe_frame(
&[],
l,
cfg,
li,
&[],
forced.as_deref(),
pool,
Some(&a_tail),
None,
&mut next,
)?;
let state_ok = if cold_count == 0 {
crate::gpu_wgpu::dsv4_state_read(state)
} else {
crate::gpu_wgpu::dsv4_state_add_cold(&cold_sum, cfg.hc_mult, state)
};
if !state_ok {
return None;
}
if let Some(n) = nxt {
let (f, post, comb) = hc_fold_norm(
state,
&n.hc_attn_fn,
&n.hc_attn_scale,
&n.hc_attn_base,
&n.attn_norm,
cfg,
pool,
);
*folded = f;
if !crate::gpu_wgpu::dsv4_hc_write(&post, &comb)
|| !crate::gpu_wgpu::dsv4_state_write(state)
{
return None;
}
}
Some(true)
}
#[cfg(feature = "gpu")]
fn chain_max() -> usize {
static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*N.get_or_init(|| {
std::env::var("CMF_DSV4_CHAIN_MAX")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(usize::MAX)
})
}
#[cfg(feature = "gpu")]
fn partial_walk_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_PARTIAL_WALK").is_ok_and(|v| v != "0"))
}
fn host_cpu_moe() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_HOST_CPU_MOE").is_ok_and(|v| v != "0"))
}
fn chain_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_CHAIN").map(|v| v != "0").unwrap_or(true))
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn dsv4_chain_run(
layers: &[Dsv4Layer],
run: &[usize],
cfg: &Dsv4Cfg,
g: &Dsv4Globals,
st: &mut Dsv4State,
token_id: u32,
folded: &mut Vec<f32>,
state_out: Option<&mut [f32]>,
batch: usize,
batch_ids: &[u32],
need_qn: bool,
pool: Option<&crate::pool::Pool>,
) -> bool {
if run.is_empty() {
return true;
}
let (dim, hd) = (cfg.dim, cfg.head_dim);
let first = run[0];
let Some(model) = layers[first].experts.first().and_then(|e| e.w1.model_arc()) else {
return false;
};
if batch <= 1 && need_qn {
let mut qn0 = vec![0.0f32; cfg.q_lora_rank];
layers[first].wq_a.matvec(folded, &mut qn0, pool);
rms_weighted(&mut qn0, &layers[first].q_norm, cfg.norm_eps);
if !crate::gpu_wgpu::dsv4_chain_seed(folded, &qn0) {
return false;
}
} else if batch <= 1 && !crate::gpu_wgpu::dsv4_chain_seed_fold(folded) {
return false;
}
let mut packs = Vec::with_capacity(run.len());
let mut forceds: Vec<Option<Vec<usize>>> = Vec::with_capacity(run.len());
for &li in run {
let Some(pk) = pack_for(&layers[li], cfg, li) else {
return false;
};
let forced: Option<Vec<usize>> = layers[li].tid2eid.as_ref().and_then(|tbl| {
let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, token_id)
.into_iter()
.map(|gi| pk.to_slot[gi])
.collect();
if v.iter().any(|&x| x == usize::MAX) { None } else { Some(v) }
});
if layers[li].tid2eid.is_some() && forced.is_none() {
return false;
}
forceds.push(forced);
packs.push(pk);
}
let mut items = Vec::with_capacity(run.len());
let mut freqs = Vec::with_capacity(run.len());
for (i, &li) in run.iter().enumerate() {
let l = &layers[li];
let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
l.wq_a.model_idx(),
l.wq_b.model_idx(),
l.wo_a.model_idx(),
l.wo_b.model_idx(),
l.wkv.model_idx(),
) else {
return false;
};
let comp = match &l.compressor {
None => None,
Some(cp) => {
let (Some(a), Some(b)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
return false;
};
Some((
crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: b, norm: &cp.norm, ape: &cp.ape },
crate::gpu_wgpu::Dsv4CompGeom {
width: cp.wkv.rows(),
hidden: dim,
ratio: cp.ratio,
overlap: cp.overlap,
rope_dim: cfg.rope_head_dim,
eps: cfg.norm_eps,
},
))
}
};
let ix = match &l.indexer {
None => None,
Some(ixr) => {
let cp = &ixr.compressor;
let (Some(a), Some(b), Some(qb), Some(wp)) = (
cp.wkv.model_idx(),
cp.wgate.model_idx(),
ixr.wq_b.model_idx(),
ixr.weights_proj.model_idx(),
) else {
return false;
};
let ih = ixr.weights_proj.rows();
Some((
crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: b, norm: &cp.norm, ape: &cp.ape },
crate::gpu_wgpu::Dsv4CompGeom {
width: cp.wkv.rows(),
hidden: dim,
ratio: cp.ratio,
overlap: cp.overlap,
rope_dim: cfg.rope_head_dim,
eps: cfg.norm_eps,
},
crate::gpu_wgpu::Dsv4IxW { wq_b: qb, weights_proj: wp },
crate::gpu_wgpu::Dsv4IxGeom {
ih,
idim: ixr.wq_b.rows() / ih.max(1),
q_lora: cfg.q_lora_rank,
hidden: dim,
rope_dim: cfg.rope_head_dim,
eps: cfg.norm_eps,
top_k: cfg.index_topk,
window: cfg.window,
},
))
}
};
let ew_c0 = l.compressor.as_ref().map_or(0, |cp| {
if cp.overlap { cp.wkv.rows() / 2 } else { cp.wkv.rows() }
});
let comp_extra = l.compressor.as_ref().map_or(0, |cp| {
batch.max(1).div_ceil(cp.ratio.max(1))
});
let need = cfg.window * hd
+ (st.dev_n_comp[li] + comp_extra + 1) * ew_c0.max(1)
+ (batch.max(1) + 1) * hd;
if !crate::gpu_wgpu::dsv4_cache_ensure(st.kv_id, li, need.next_power_of_two()) {
return false;
}
let ew_c = comp.as_ref().map_or(0, |(_, cg)| {
if cg.overlap { cg.width / 2 } else { cg.width }
});
let ew_i = ix.as_ref().map_or(0, |(_, cg, _, _)| {
if cg.overlap { cg.width / 2 } else { cg.width }
});
let prep = crate::gpu_wgpu::Dsv4Prep {
wkv,
kv_norm: &l.kv_norm,
comp,
ix,
filled: st.dev_filled[li],
window: cfg.window,
n_comp: st.dev_n_comp[li],
n_ix: st.dev_n_ix[li],
comp_dst_off: cfg.window * hd + st.dev_n_comp[li] * ew_c,
ix_dst_off: st.dev_n_ix[li] * ew_i,
idx_cap: cfg.window
+ if l.indexer.is_some() {
cfg.index_topk
} else {
st.dev_n_comp[li] + comp_extra + 1
},
};
let nxt = layers.get(li + 1);
let w = crate::gpu_wgpu::Dsv4LayerW {
attn: crate::gpu_wgpu::Dsv4AttnW {
wq_a, wq_b, wo_a, wo_b,
q_norm: &l.q_norm,
sink: &l.attn_sink,
},
moe: crate::gpu_wgpu::Dsv4MoeW {
router: &packs[i].router,
experts: &packs[i].tensors,
logits: &[],
bias: packs[i].bias.as_deref(),
forced: forceds[i].as_deref(),
remap: None,
},
hc_ffn_fn: &l.hc_ffn_fn,
hc_ffn_scale: &l.hc_ffn_scale,
hc_ffn_base: &l.hc_ffn_base,
hc_next_fn: nxt.map(|n| n.hc_attn_fn.as_slice()),
hc_next_scale: nxt.map_or(&l.hc_attn_scale, |n| &n.hc_attn_scale),
hc_next_base: nxt.map_or(&l.hc_attn_base, |n| n.hc_attn_base.as_slice()),
ffn_norm: &l.ffn_norm,
next_norm: nxt.map_or(&l.attn_norm, |n| n.attn_norm.as_slice()),
next_q_norm: nxt.map_or(&l.q_norm, |n| n.q_norm.as_slice()),
next_wq_a: nxt.and_then(|n| n.wq_a.model_idx()),
router: &packs[i].router,
};
let geom = crate::gpu_wgpu::Dsv4LayerGeom {
attn: crate::gpu_wgpu::Dsv4AttnGeom {
dim,
nh: cfg.n_heads,
hd,
rd: cfg.rope_head_dim,
q_lora: cfg.q_lora_rank,
o_lora: cfg.o_lora_rank,
o_groups: cfg.o_groups,
eps: cfg.norm_eps,
scale: (hd as f32).powf(-0.5),
},
moe: crate::gpu_wgpu::Dsv4MoeGeom {
hidden: dim,
inter: cfg.moe_inter,
top_k: cfg.top_k,
route_scale: cfg.route_scale,
swiglu_limit: cfg.swiglu_limit,
gu_q2: l.experts.first().is_some_and(|e| {
e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
}),
},
hc: cfg.hc_mult,
hc_eps: cfg.hc_eps,
sinkhorn_iters: cfg.hc_sinkhorn_iters,
};
freqs.push(if l.compressor.is_some() {
g.inv_freq_compress.as_slice()
} else {
g.inv_freq_window.as_slice()
});
items.push((w, geom, prep));
}
let mut out = vec![0.0f32; dim * batch.max(1)];
if batch > 1 {
let mut forced_pt: Vec<Vec<Option<Vec<usize>>>> = Vec::with_capacity(batch);
for t in 0..batch {
let id = batch_ids.get(t).copied().unwrap_or(token_id);
let mut row = Vec::with_capacity(run.len());
for (i, &li) in run.iter().enumerate() {
row.push(layers[li].tid2eid.as_ref().and_then(|tbl| {
let v: Vec<usize> = hash_route(tbl, cfg.vocab, cfg.top_k, id)
.into_iter()
.map(|gi| packs[i].to_slot[gi])
.collect();
if v.iter().any(|&x| x == usize::MAX) { None } else { Some(v) }
}));
if layers[li].tid2eid.is_some() && row[i].is_none() {
return false;
}
}
forced_pt.push(row);
}
if !crate::gpu_wgpu::dsv4_chain_batch(
&model, &items, st.kv_id, first, &freqs, st.pos, batch,
Some(&forced_pt), &mut out, state_out,
) {
return false;
}
*folded = out[(batch - 1) * dim..batch * dim].to_vec();
} else {
if !crate::gpu_wgpu::dsv4_layer_chain(
&model, &items, st.kv_id, first, &freqs, st.pos, &mut out, state_out,
) {
return false;
}
*folded = out;
}
for (i, &li) in run.iter().enumerate() {
for t in 0..batch.max(1) {
let pos = st.pos + t;
st.dev_filled[li] = (st.dev_filled[li] + 1).min(cfg.window);
if let Some((_, cg, ..)) = items[i].2.ix.as_ref() {
if (pos + 1) % cg.ratio == 0 {
st.dev_n_ix[li] += 1;
}
}
if let Some((_, cg)) = items[i].2.comp.as_ref() {
if (pos + 1) % cg.ratio == 0 {
st.dev_n_comp[li] += 1;
}
}
}
}
st.dev_owned = true;
true
}
#[cfg(feature = "gpu")]
fn hc_on_device() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| {
std::env::var("CMF_DSV4_HC_DEV").is_ok_and(|v| v != "0")
&& crate::gpu::backend_available()
})
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn dsv4_two_frame_loop(
state: &mut [f32],
layers: &[Dsv4Layer],
g: &Dsv4Globals,
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
token_id: u32,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
scratch: &mut HcScratch,
) -> bool {
let dim = cfg.dim;
let freqs_of = |l: &Dsv4Layer| -> &[f32] {
let f = if l.compressor.is_some() {
&g.inv_freq_compress
} else {
&g.inv_freq_window
};
if f.is_empty() { inv_freq } else { f.as_slice() }
};
let (mut folded, post0, comb0) = hc_fold_norm(
state,
&layers[0].hc_attn_fn,
&layers[0].hc_attn_scale,
&layers[0].hc_attn_base,
&layers[0].attn_norm,
cfg,
pool,
);
if !crate::gpu_wgpu::dsv4_state_write(state)
|| !crate::gpu_wgpu::dsv4_hc_write(&post0, &comb0)
{
return false;
}
let mut on_dev = vec![false; layers.len()];
for (li, l) in layers.iter().enumerate() {
let Some(pk) = pack_for(l, cfg, li) else {
return false;
};
let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
return false;
};
let gu_q2 = l
.experts
.first()
.is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
let attn_ok = [
l.wq_a.model_idx(),
l.wq_b.model_idx(),
l.wo_a.model_idx(),
l.wo_b.model_idx(),
]
.into_iter()
.flatten()
.all(|i| crate::gpu_wgpu::dsv4_weight_ready(&model, i));
on_dev[li] = attn_ok
&& pk.globals.len() == cfg.n_routed_experts
&& crate::gpu_wgpu::dsv4_experts_ready(
&model,
&pk.tensors,
cfg.moe_inter,
dim,
gu_q2,
l.experts.first().is_some_and(|e| {
e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
}),
);
}
if !on_dev.iter().any(|&x| x) {
return false;
}
let mut sink = vec![0.0f32; dim];
for (li, l) in layers.iter().enumerate() {
if !on_dev[li] {
if !crate::gpu_wgpu::dsv4_state_read(state) {
return false;
}
let freqs = freqs_of(l);
hc_block(
state,
&l.hc_attn_fn,
&l.hc_attn_scale,
&l.hc_attn_base,
&l.attn_norm,
cfg,
scratch,
pool,
|f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
);
hc_block(
state,
&l.hc_ffn_fn,
&l.hc_ffn_scale,
&l.hc_ffn_base,
&l.ffn_norm,
cfg,
scratch,
pool,
|f, o| moe_step(f, l, cfg, token_id, li, pool, o),
);
let nref = layers.get(li + 1).unwrap_or(l);
let (f, p2, c2) = hc_fold_norm(
state,
&nref.hc_attn_fn,
&nref.hc_attn_scale,
&nref.hc_attn_base,
&nref.attn_norm,
cfg,
pool,
);
folded = f;
if !crate::gpu_wgpu::dsv4_hc_write(&p2, &c2)
|| !crate::gpu_wgpu::dsv4_state_write(state)
{
return false;
}
continue;
}
let mut prep = AttnPrep::default();
attention_step(
&folded, l, cfg, st, li, freqs_of(l), pool, Some(&mut prep), &mut sink,
);
let hd = cfg.head_dim;
let n_comp = st.compressed[li].len() / hd;
let cap = (cfg.window + n_comp.next_power_of_two().max(64)) * hd;
if !crate::gpu_wgpu::dsv4_cache_write(st.kv_id, li, 0, &st.window[li], cap)
|| (n_comp > 0
&& !crate::gpu_wgpu::dsv4_cache_write(
st.kv_id, li, cfg.window * hd, &st.compressed[li], cap,
))
{
return false;
}
let idx32: Vec<u32> = prep
.idxs
.iter()
.map(|&p| {
if p < prep.win_len {
p as u32
} else {
(cfg.window + (p - prep.win_len)) as u32
}
})
.collect();
let nxt = layers.get(li + 1);
let a_tail = crate::gpu_wgpu::Dsv4HcTail {
fn_: &l.hc_ffn_fn,
scale: &l.hc_ffn_scale,
base: &l.hc_ffn_base,
norm: &l.ffn_norm,
hc: cfg.hc_mult,
sinkhorn_iters: cfg.hc_sinkhorn_iters,
hc_eps: cfg.hc_eps,
eps: cfg.norm_eps,
};
let scale = (cfg.head_dim as f32).powf(-0.5);
if !attn_frame(
l, cfg, st, li, &folded, &prep.qr, &prep.idxs, freqs_of(l), st.pos,
prep.win_len, scale, Some(&a_tail), &mut [],
) {
return false;
}
let m_tail = nxt.map(|n| crate::gpu_wgpu::Dsv4HcTail {
fn_: &n.hc_attn_fn,
scale: &n.hc_attn_scale,
base: &n.hc_attn_base,
norm: &n.attn_norm,
hc: cfg.hc_mult,
sinkhorn_iters: cfg.hc_sinkhorn_iters,
hc_eps: cfg.hc_eps,
eps: cfg.norm_eps,
});
let mut next = vec![0.0f32; dim];
let pair = m_tail
.as_ref()
.zip(nxt)
.map(|(t, n)| (t, n.attn_norm.as_slice()));
let forced = l
.tid2eid
.as_ref()
.map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
if moe_frame(&[], l, cfg, li, &[], forced.as_deref(), pool, Some(&a_tail), pair, &mut next)
.is_none()
{
return false;
}
folded = next;
}
let _ = scratch;
crate::gpu_wgpu::dsv4_state_read(state)
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn hc_fold_norm(
state: &[f32],
hc_fn: &[f32],
hc_scale: &[f32; 3],
hc_base: &[f32],
norm_w: &[f32],
cfg: &Dsv4Cfg,
pool: Option<&crate::pool::Pool>,
) -> (Vec<f32>, Vec<f32>, Vec<f32>) {
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let mix_hc = (2 + hc) * hc;
let mut mixes = vec![0.0f32; mix_hc];
hc_mixes(state, hc_fn, mix_hc, cfg.norm_eps, pool, &mut mixes);
let mut pre = vec![0.0f32; hc];
let mut post = vec![0.0f32; hc];
let mut comb = vec![0.0f32; hc * hc];
hc_split_sinkhorn(
&mixes,
hc_scale,
hc_base,
hc,
cfg.hc_sinkhorn_iters,
cfg.hc_eps,
&mut pre,
&mut post,
&mut comb,
);
let mut folded = vec![0.0f32; dim];
hc_fold(state, &pre, hc, dim, &mut folded);
rms_weighted(&mut folded, norm_w, cfg.norm_eps);
(folded, post, comb)
}
#[cfg(feature = "gpu")]
fn gpu_layer_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| {
std::env::var("CMF_DSV4_GPU_LAYER").map(|v| v != "0").unwrap_or(true)
&& crate::gpu::backend_available()
})
}
#[cfg(feature = "gpu")]
struct Pack {
router: Vec<f32>,
to_slot: Vec<usize>,
remap: Vec<u32>,
globals: Vec<usize>,
tensors: Vec<(usize, usize, usize)>,
bias: Option<Vec<f32>>,
}
#[cfg(feature = "gpu")]
fn pack_freq_order(li: usize, n: usize) -> Option<Vec<usize>> {
use std::collections::HashMap;
use std::sync::OnceLock;
static FREQ: OnceLock<Option<HashMap<(usize, usize), u64>>> = OnceLock::new();
let map = FREQ
.get_or_init(|| {
let path = std::env::var("CMF_DSV4_PACK_FREQ").ok()?;
let text = match std::fs::read_to_string(&path) {
Ok(t) => t,
Err(e) => {
eprintln!("CMF_DSV4_PACK_FREQ={path} не читается ({e}) — порядок по id");
return None;
}
};
let mut m = HashMap::new();
for line in text.lines() {
let mut it = line.split('\t');
if let (Some(l), Some(e), Some(c)) = (it.next(), it.next(), it.next()) {
if let (Ok(l), Ok(e), Ok(c)) =
(l.trim().parse(), e.trim().parse(), c.trim().parse::<u64>())
{
*m.entry((l, e)).or_insert(0) += c;
}
}
}
Some(m)
})
.as_ref()?;
if !(0..n).any(|e| map.contains_key(&(li, e))) {
return None;
}
let mut idx: Vec<usize> = (0..n).collect();
idx.sort_by_key(|&e| (std::cmp::Reverse(map.get(&(li, e)).copied().unwrap_or(0)), e));
Some(idx)
}
#[cfg(feature = "gpu")]
fn pack_for(l: &Dsv4Layer, cfg: &Dsv4Cfg, li: usize) -> Option<std::sync::Arc<Pack>> {
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
static CACHE: OnceLock<Mutex<HashMap<(u64, usize, usize), Option<Arc<Pack>>>>> =
OnceLock::new();
let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let model_uid = l
.experts
.first()
.and_then(|e| e.w1.model_arc())
.map_or(0, |m| m.uid());
let first_expert = l
.experts
.first()
.and_then(|e| e.w1.model_idx())
.unwrap_or(usize::MAX);
let key = (model_uid, li, first_expert);
if let Some(v) = cache.lock().unwrap().get(&key) {
return v.clone();
}
if let Ok(v) = std::env::var("CMF_DSV4_PACK_MAX_LI") {
if v.parse::<usize>().is_ok_and(|max| li > max) {
cache.lock().unwrap().insert(key, None);
return None;
}
}
let build = || -> Option<Arc<Pack>> {
let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
let mut globals = Vec::new();
let mut tensors = Vec::new();
let idx3 = |e: &Dsv4Expert| -> Option<(usize, usize, usize)> {
Some((e.w1.model_idx()?, e.w3.model_idx()?, e.w2.model_idx()?))
};
let gu_q2 = l
.experts
.first()
.is_some_and(|e| e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
if let Some(n) = std::env::var("CMF_DSV4_PACK_MAX")
.ok()
.and_then(|v| v.parse::<usize>().ok())
{
let mut to_slot = vec![usize::MAX; cfg.n_routed_experts];
let mut globals = Vec::new();
let mut tensors = Vec::new();
for (gi, e) in l.experts.iter().enumerate().take(n) {
to_slot[gi] = globals.len();
globals.push(gi);
tensors.push(idx3(e)?);
}
tensors.push(idx3(&l.shared)?);
let (rows, cols) = (l.gate.rows(), l.gate.cols());
let mut router = vec![0.0f32; rows * cols];
for r in 0..rows {
l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
}
let remap: Vec<u32> = to_slot
.iter()
.map(|&sl| if sl == usize::MAX { u32::MAX } else { sl as u32 })
.collect();
return Some(Arc::new(Pack {
bias: l.gate_bias.as_deref().map(|b| {
globals.iter().map(|&g| b[g]).collect()
}),
router,
to_slot,
remap,
globals,
tensors,
}));
}
let dn_q2_fit = l
.experts
.first()
.is_some_and(|e| e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP));
let room = crate::gpu_wgpu::dsv4_experts_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2_fit)
.saturating_sub(1);
let order = pack_freq_order(li, l.experts.len())
.unwrap_or_else(|| (0..l.experts.len()).collect());
for gi in order {
let e = &l.experts[gi];
if l.mask.as_deref().is_some_and(|m| !m.get(gi).copied().unwrap_or(true)) {
continue;
}
if globals.len() >= room {
break;
}
to_slot[gi] = globals.len();
globals.push(gi);
match idx3(e) {
Some(t) => tensors.push(t),
None => {
if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
eprintln!("слой {li}: эксперт {gi} без индексов в каталоге");
}
return None;
}
}
}
if globals.is_empty() {
if room == 0 {
static SAID_ZERO: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
if !SAID_ZERO.swap(true, std::sync::atomic::Ordering::Relaxed) {
tracing::warn!(
"начиная со слоя {li}, в бюджете VRAM не осталось места даже под одного \
эксперта — остальные веса остаются mmap-backed и читаются по требованию"
);
}
} else {
tracing::warn!("слой {li}: маска не оставила ни одного эксперта");
}
return None;
}
tensors.push(idx3(&l.shared)?); let (rows, cols) = (l.gate.rows(), l.gate.cols());
let mut router = vec![0.0f32; rows * cols];
for r in 0..rows {
l.gate.row_f32(r, &mut router[r * cols..(r + 1) * cols]);
}
let remap: Vec<u32> = to_slot
.iter()
.map(|&sl| if sl == usize::MAX { u32::MAX } else { sl as u32 })
.collect();
Some(Arc::new(Pack {
bias: l.gate_bias.as_deref().map(|b| {
globals.iter().map(|&g| b[g]).collect()
}),
router,
to_slot,
remap,
globals,
tensors,
}))
};
let v = build();
cache.lock().unwrap().insert(key, v.clone());
v
}
#[cfg(feature = "gpu")]
fn moe_frame(
hidden: &[f32],
l: &Dsv4Layer,
cfg: &Dsv4Cfg,
li: usize,
logits: &[f32],
forced: Option<&[usize]>,
pool: Option<&crate::pool::Pool>,
hc_cur: Option<&crate::gpu_wgpu::Dsv4HcTail>,
hc_next: Option<(&crate::gpu_wgpu::Dsv4HcTail, &[f32])>,
out: &mut [f32],
) -> Option<(Vec<f32>, usize)> {
macro_rules! no {
($($t:tt)*) => {{
if std::env::var("CMF_DSV4_FRAME_DEBUG").is_ok() {
eprintln!("кадр MoE отклонён: {}", format_args!($($t)*));
}
return None;
}};
}
let Some(pk) = pack_for(l, cfg, li) else {
no!("слой {li}: упаковка экспертов не построена");
};
let Some(model) = l.experts.first().and_then(|e| e.w1.model_arc()) else {
no!("слой {li}: эксперты не отображены из файла");
};
let subset = pk.globals.len() < cfg.n_routed_experts;
let fpack: Option<Vec<usize>> = match forced {
Some(f) if subset => Some(f.to_vec()),
Some(f) => {
let v: Vec<usize> = f.iter().map(|&g| pk.to_slot[g]).collect();
if v.iter().any(|&s| s == usize::MAX) {
no!("слой {li}: хеш-слой называет эксперта вне упаковки");
}
Some(v)
}
None => None,
};
let lg: Vec<f32> = if logits.is_empty() || subset {
logits.to_vec()
} else {
pk.globals.iter().map(|&g| logits[g]).collect()
};
let bias: Option<Vec<f32>> = l.gate_bias.as_deref().map(|b| {
if subset {
b.to_vec()
} else {
pk.globals.iter().map(|&g| b[g]).collect()
}
});
let w = crate::gpu_wgpu::Dsv4MoeW {
router: &pk.router,
experts: &pk.tensors,
logits: &lg,
bias: bias.as_deref(),
forced: fpack.as_deref(),
remap: if subset { Some(&pk.remap) } else { None },
};
let g = crate::gpu_wgpu::Dsv4MoeGeom {
hidden: cfg.dim,
inter: cfg.moe_inter,
top_k: cfg.top_k,
route_scale: cfg.route_scale,
swiglu_limit: cfg.swiglu_limit,
gu_q2: l.experts.first().is_some_and(|e| {
e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
}),
};
let mut cold = Vec::new();
let mut cold_x = Vec::new();
if !crate::gpu_wgpu::dsv4_moe_frame(
&model,
&w,
g,
hidden,
&mut cold,
&mut cold_x,
hc_cur,
hc_next,
out,
) {
return None;
}
if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
let csum: f32 = cold.iter().map(|c| c.1).sum();
eprintln!(
"[холодные] слой {li}: вернулось {} из {} | сумма холодных {csum:.4} | \
route_scale {:.4} | {:?}",
cold.len(),
cfg.top_k,
cfg.route_scale,
&cold[..cold.len().min(3)]
);
}
let mut acc = vec![0.0f32; cfg.dim];
let mut cold_sum = vec![0.0f32; cfg.dim];
let cold_input = if hidden.is_empty() { cold_x.as_slice() } else { hidden };
for &(gi, wt) in &cold {
let Some(exp) = l.experts.get(gi) else { continue };
crate::gpu::cpu_scope(|| run_expert(cold_input, exp, cfg, wt, pool, &mut acc));
for ((o, sum), a) in out.iter_mut().zip(&mut cold_sum).zip(&acc) {
*o += a;
*sum += a;
}
}
Some((cold_sum, cold.len()))
}
#[cfg(feature = "gpu")]
fn last_grew(now: u64) -> u64 {
use std::sync::atomic::{AtomicU64, Ordering};
static SEEN: AtomicU64 = AtomicU64::new(0);
let was = SEEN.load(Ordering::Relaxed);
if was != now {
SEEN.store(now, Ordering::Relaxed);
compressed_map().lock().unwrap().clear();
return u64::MAX; }
now
}
#[cfg(feature = "gpu")]
fn compressed_map() -> &'static std::sync::Mutex<std::collections::HashMap<(u64, usize), usize>> {
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
static W: OnceLock<Mutex<HashMap<(u64, usize), usize>>> = OnceLock::new();
W.get_or_init(|| Mutex::new(HashMap::new()))
}
#[cfg(feature = "gpu")]
fn compressed_written(kv_id: u64, li: usize) -> usize {
compressed_map()
.lock()
.unwrap()
.get(&(kv_id, li))
.copied()
.unwrap_or(0)
}
#[cfg(feature = "gpu")]
fn note_compressed(kv_id: u64, li: usize, n: usize) {
compressed_map().lock().unwrap().insert((kv_id, li), n);
}
#[cfg(feature = "gpu")]
fn gpu_moe2_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| {
std::env::var("CMF_DSV4_GPU_MOE2").map(|v| v != "0").unwrap_or(true)
&& crate::gpu::backend_available()
})
}
pub fn moe_step(
hidden: &[f32],
l: &Dsv4Layer,
cfg: &Dsv4Cfg,
token_id: u32,
li: usize,
pool: Option<&crate::pool::Pool>,
out: &mut [f32],
) {
let _t0 = prof::on().then(std::time::Instant::now);
let _guard = scopeguard_moe(_t0, li);
let mut logits = vec![0.0f32; cfg.n_routed_experts];
l.gate.matvec(hidden, &mut logits, pool);
let (mut idx, mut w) = (Vec::new(), Vec::new());
route(
&logits,
l.gate_bias.as_deref(),
cfg.top_k,
cfg.route_scale,
l.tid2eid
.as_ref()
.map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id))
.as_deref(),
l.mask.as_deref(),
&mut idx,
&mut w,
);
if route_stats_on() {
record_route(li, 0, cfg.n_routed_experts, &idx);
}
#[cfg(feature = "gpu")]
if gpu_moe2_enabled() && crate::gpu::enabled_here() {
let forced = l
.tid2eid
.as_ref()
.map(|tbl| hash_route(tbl, cfg.vocab, cfg.top_k, token_id));
if moe_frame(hidden, l, cfg, li, &logits, forced.as_deref(), pool, None, None, out)
.is_some()
{
if std::env::var("CMF_DSV4_MOE_CHECK").is_ok() {
let mut want = vec![0.0f32; out.len()];
let mut acc = vec![0.0f32; cfg.dim];
for (e, &ei) in idx.iter().enumerate() {
let Some(exp) = l.experts.get(ei) else { continue };
run_expert(hidden, exp, cfg, w.get(e).copied().unwrap_or(0.0), pool, &mut acc);
for (o, a) in want.iter_mut().zip(&acc) {
*o += a;
}
}
run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
for (o, a) in want.iter_mut().zip(&acc) {
*o += a;
}
let num: f32 = want.iter().zip(out.iter()).map(|(a, b)| (a - b) * (a - b)).sum();
let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
let rel = (num / den).sqrt();
if rel > 1e-3 {
let packed = pack_for(l, cfg, li).map_or(0, |p| p.globals.len());
eprintln!(
"[кадр MoE] слой {li}: расхождение {rel:.3e} | выбрано {} | \
упаковано {packed} из {} | хеш={} | смещение={}",
idx.len(),
cfg.n_routed_experts,
l.tid2eid.is_some(),
l.gate_bias.is_some()
);
}
}
return;
}
}
PICK_TALLY.with(|t| {
if let Some(v) = t.borrow_mut().as_mut() {
v.push((li, idx.to_vec()));
}
});
if dump_path().is_some() {
PICKED.with(|p| {
let mut p = p.borrow_mut();
if p.len() <= li {
p.resize(li + 1, Vec::new());
}
p[li] = idx.clone();
});
}
fn gpu_moe_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_GPU_MOE").is_ok_and(|v| v != "0"))
}
if gpu_moe_on() && crate::gpu::enabled_here() {
let mut jobs = Vec::with_capacity(idx.len() + 1);
let mut model_ref = None;
let mut ok = true;
for (e, &ei) in idx.iter().enumerate() {
let Some(exp) = l.experts.get(ei) else { continue };
ok &= crate::pipeline::moe_push_job_parts(
&exp.w1,
&exp.w3,
&exp.w2,
hidden,
w.get(e).copied().unwrap_or(0.0),
cfg.swiglu_limit,
&mut jobs,
&mut model_ref,
)
.is_some();
}
ok &= crate::pipeline::moe_push_job_parts(
&l.shared.w1,
&l.shared.w3,
&l.shared.w2,
hidden,
1.0,
cfg.swiglu_limit,
&mut jobs,
&mut model_ref,
)
.is_some();
if ok {
if let Some(m) = model_ref.as_ref() {
if crate::gpu::moe_block(m, &jobs, out) {
if std::env::var("CMF_DSV4_GPU_CHECK").is_ok() {
let mut want = vec![0.0f32; out.len()];
let mut acc = vec![0.0f32; cfg.dim];
for (e, &ei) in idx.iter().enumerate() {
let Some(exp) = l.experts.get(ei) else { continue };
run_expert(
hidden, exp, cfg,
w.get(e).copied().unwrap_or(0.0), pool, &mut acc,
);
for (o, a) in want.iter_mut().zip(&acc) {
*o += a;
}
}
run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
for (o, a) in want.iter_mut().zip(&acc) {
*o += a;
}
let num: f32 = want
.iter()
.zip(out.iter())
.map(|(a, b)| (a - b) * (a - b))
.sum();
let den: f32 = want.iter().map(|a| a * a).sum::<f32>().max(1e-20);
eprintln!(
"[dsv4-gpu] слой {li}: расхождение {:.3e} | |CPU|={:.5} |GPU|={:.5} | экспертов {}",
(num / den).sqrt(),
den.sqrt(),
out.iter().map(|x| x * x).sum::<f32>().sqrt(),
jobs.len()
);
}
return;
}
}
}
}
out.fill(0.0);
let mut acc = vec![0.0f32; cfg.dim];
for (e, &ei) in idx.iter().enumerate() {
let Some(exp) = l.experts.get(ei) else {
continue;
};
run_expert(
hidden,
exp,
cfg,
w.get(e).copied().unwrap_or(0.0),
pool,
&mut acc,
);
for (o, a) in out.iter_mut().zip(&acc) {
*o += a;
}
}
run_expert(hidden, &l.shared, cfg, 1.0, pool, &mut acc);
for (o, a) in out.iter_mut().zip(&acc) {
*o += a;
}
}
fn run_expert(
x: &[f32],
e: &Dsv4Expert,
cfg: &Dsv4Cfg,
weight: f32,
pool: Option<&crate::pool::Pool>,
out: &mut [f32],
) {
expert_swiglu(
x,
&|src, dst| e.w1.matvec(src, dst, pool),
&|src, dst| e.w3.matvec(src, dst, pool),
&|src, dst| e.w2.matvec(src, dst, pool),
cfg.moe_inter,
weight,
cfg.swiglu_limit,
out,
);
}
fn moe_step_block(
xs: &[f32],
b: usize,
l: &Dsv4Layer,
cfg: &Dsv4Cfg,
token_ids: &[u32],
tally_layer: usize,
pool: Option<&crate::pool::Pool>,
out: &mut [f32],
) {
let (dim, inter) = (cfg.dim, cfg.moe_inter);
debug_assert_eq!(xs.len(), b * dim);
debug_assert_eq!(out.len(), b * dim);
out.fill(0.0);
let mut logits = vec![0.0f32; b * cfg.n_routed_experts];
l.gate.matmat(xs, b, &mut logits, pool);
let mut picks: Vec<Vec<usize>> = Vec::with_capacity(b);
let mut weights: Vec<Vec<f32>> = Vec::with_capacity(b);
for bi in 0..b {
let mut idx = Vec::new();
let mut wt = Vec::new();
let forced = l.tid2eid.as_ref().map(|tbl| {
hash_route(
tbl,
cfg.vocab,
cfg.top_k,
token_ids.get(bi).copied().unwrap_or(0),
)
});
route(
&logits[bi * cfg.n_routed_experts..(bi + 1) * cfg.n_routed_experts],
l.gate_bias.as_deref(),
cfg.top_k,
cfg.route_scale,
forced.as_deref(),
l.mask.as_deref(),
&mut idx,
&mut wt,
);
PICK_TALLY.with(|t| {
if let Some(v) = t.borrow_mut().as_mut() {
v.push((tally_layer, idx.clone()));
}
});
picks.push(idx);
weights.push(wt);
}
let mut routed = vec![0.0f32; b * cfg.top_k * dim];
for ei in 0..l.experts.len() {
let mut jobs = Vec::new();
for bi in 0..b {
for (slot, &picked) in picks[bi].iter().enumerate() {
if picked == ei {
jobs.push((bi, slot, weights[bi][slot]));
}
}
}
if jobs.is_empty() {
continue;
}
let e = &l.experts[ei];
let n = jobs.len();
let mut xj = vec![0.0f32; n * dim];
for (j, &(bi, _, _)) in jobs.iter().enumerate() {
xj[j * dim..(j + 1) * dim]
.copy_from_slice(&xs[bi * dim..(bi + 1) * dim]);
}
let mut gate = vec![0.0f32; n * inter];
let mut up = vec![0.0f32; n * inter];
e.w1.matmat(&xj, n, &mut gate, pool);
e.w3.matmat(&xj, n, &mut up, pool);
for (j, &(_, _, wt)) in jobs.iter().enumerate() {
let (gj, uj) = (
&mut gate[j * inter..(j + 1) * inter],
&mut up[j * inter..(j + 1) * inter],
);
if cfg.swiglu_limit > 0.0 {
for u in uj.iter_mut() {
*u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
}
for g in gj.iter_mut() {
*g = g.min(cfg.swiglu_limit);
}
}
for (g, &u) in gj.iter_mut().zip(uj.iter()) {
*g = (*g / (1.0 + (-*g).exp())) * u * wt;
}
}
let mut down = vec![0.0f32; n * dim];
e.w2.matmat(&gate, n, &mut down, pool);
for (j, &(bi, slot, _)) in jobs.iter().enumerate() {
routed[(bi * cfg.top_k + slot) * dim..(bi * cfg.top_k + slot + 1) * dim]
.copy_from_slice(&down[j * dim..(j + 1) * dim]);
}
}
let mut sg = vec![0.0f32; b * inter];
let mut su = vec![0.0f32; b * inter];
l.shared.w1.matmat(xs, b, &mut sg, pool);
l.shared.w3.matmat(xs, b, &mut su, pool);
for bi in 0..b {
let (gj, uj) = (
&mut sg[bi * inter..(bi + 1) * inter],
&mut su[bi * inter..(bi + 1) * inter],
);
if cfg.swiglu_limit > 0.0 {
for u in uj.iter_mut() {
*u = u.clamp(-cfg.swiglu_limit, cfg.swiglu_limit);
}
for g in gj.iter_mut() {
*g = g.min(cfg.swiglu_limit);
}
}
for (g, &u) in gj.iter_mut().zip(uj.iter()) {
*g = (*g / (1.0 + (-*g).exp())) * u;
}
}
let mut shared = vec![0.0f32; b * dim];
l.shared.w2.matmat(&sg, b, &mut shared, pool);
for bi in 0..b {
let dst = &mut out[bi * dim..(bi + 1) * dim];
for slot in 0..picks[bi].len() {
let src = &routed[(bi * cfg.top_k + slot) * dim
..(bi * cfg.top_k + slot + 1) * dim];
for (o, &v) in dst.iter_mut().zip(src) {
*o += v;
}
}
for (o, &v) in dst.iter_mut().zip(&shared[bi * dim..(bi + 1) * dim]) {
*o += v;
}
}
}
fn o_project_block(
attn: &[f32],
b: usize,
wo_a: &crate::qtensor::QTensor,
wo_b: &crate::qtensor::QTensor,
groups: usize,
lora: usize,
pool: Option<&crate::pool::Pool>,
out: &mut [f32],
) {
let attn_len = attn.len() / b;
let per_group = attn_len / groups;
let rows = groups * lora;
let mut mid = vec![0.0f32; b * rows];
let mid_addr = crate::pool::SendMut::new(mid.as_mut_ptr());
let run = |start: usize, end: usize| {
let mut wr = vec![0.0f32; wo_a.cols()];
for r in start..end {
wo_a.row_f32(r, &mut wr);
let group = r / lora;
for bi in 0..b {
let x = &attn[bi * attn_len + group * per_group
..bi * attn_len + (group + 1) * per_group];
let v = wr.iter().zip(x).map(|(w, x)| w * x).sum();
unsafe { *mid_addr.at(bi * rows + r) = v };
}
}
};
match pool {
Some(p) if rows >= 256 => p.run_rows(rows, &run),
_ => run(0, rows),
}
wo_b.matmat(&mid, b, out, pool);
}
fn no_compressed() -> bool {
static OFF: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*OFF.get_or_init(|| std::env::var("CMF_DSV4_NO_COMPRESSED").is_ok_and(|v| v != "0"))
}
fn trace_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_TRACE").is_ok_and(|v| v != "0"))
}
fn rms_of(v: &[f32]) -> f32 {
if v.is_empty() {
return 0.0;
}
(v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt()
}
thread_local! {
static BODY: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
static PICKED: std::cell::RefCell<Vec<Vec<usize>>> =
const { std::cell::RefCell::new(Vec::new()) };
static PICK_TALLY: std::cell::RefCell<Option<Vec<(usize, Vec<usize>)>>> =
const { std::cell::RefCell::new(None) };
}
pub fn pick_tally_arm() {
PICK_TALLY.with(|t| *t.borrow_mut() = Some(Vec::new()));
}
pub fn pick_tally_take() -> Vec<(usize, Vec<usize>)> {
PICK_TALLY.with(|t| t.borrow_mut().take().unwrap_or_default())
}
pub fn tally_unique(picks: &[(usize, Vec<usize>)]) -> (usize, usize) {
let mut seen = std::collections::HashSet::new();
let mut total = 0;
for (li, v) in picks {
total += v.len();
for &e in v {
seen.insert((*li, e));
}
}
(seen.len(), total)
}
fn dump_path() -> Option<&'static str> {
static P: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
P.get_or_init(|| std::env::var("CMF_DSV4_DUMP").ok())
.as_deref()
}
fn dump_line(json: &str) {
if let Some(p) = dump_path() {
use std::io::Write as _;
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(p)
{
let _ = writeln!(f, "{json}");
}
}
}
fn vec_json(v: &[f32]) -> String {
let mut s = String::with_capacity(v.len() * 9);
s.push('[');
for (i, x) in v.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&format!("{x:.6e}"));
}
s.push(']');
s
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments)]
fn batch_prefill() -> usize {
static N: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*N.get_or_init(|| {
std::env::var("CMF_DSV4_BATCH")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.filter(|&n| (1..=32).contains(&n))
.unwrap_or(1)
})
}
#[allow(clippy::too_many_arguments)]
fn forward_chunk_batched(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
ids: &[u32],
pos0: usize,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
logits: &mut Vec<f32>,
want_logits: bool,
) -> bool {
#[cfg(not(feature = "gpu"))]
{
let _ = (
g, layers, cfg, st, ids, pos0, inv_freq, pool, logits, want_logits,
);
false
}
#[cfg(feature = "gpu")]
{
let b = ids.len();
let gpu_end = st
.dev_set
.iter()
.enumerate()
.position(|(li, &on)| {
!on
|| pack_for(&layers[li], cfg, li)
.is_none_or(|p| p.globals.len() < cfg.n_routed_experts)
})
.unwrap_or(st.dev_set.len());
let why = if b < 2 {
"токенов меньше двух"
} else if !chain_enabled() {
"цепочка выключена"
} else if !st.dev_owned {
"карта ещё не владеет состоянием"
} else if st.dev_set.len() != layers.len() {
"набор слоёв ещё не зафиксирован"
} else if gpu_end == 0
|| st.dev_set[gpu_end.min(st.dev_set.len())..]
.iter()
.enumerate()
.any(|(i, &on)| {
on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false)
})
{
"слои на карте не образуют префикс"
} else {
""
};
if !why.is_empty() {
static SAID: std::sync::Once = std::sync::Once::new();
SAID.call_once(|| tracing::warn!("dsv4: пакет отказал — {why}"));
return false;
}
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let mut emb = vec![0.0f32; dim];
for (t, &id) in ids.iter().enumerate() {
let mut state = vec![0.0f32; hc * dim];
g.embed.row_f32(id as usize, &mut emb);
for j in 0..hc {
state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
}
let (folded, post0, comb0) = hc_fold_norm(
&state,
&layers[0].hc_attn_fn,
&layers[0].hc_attn_scale,
&layers[0].hc_attn_base,
&layers[0].attn_norm,
cfg,
pool,
);
let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
layers[0].wq_a.matvec(&folded, &mut qn0, pool);
rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
|| !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
|| !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
|| !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
{
return false;
}
}
let run: Vec<usize> = (0..gpu_end).collect();
let mut folded = Vec::new();
let mut states = vec![0.0f32; b * hc * dim];
st.pos = pos0;
if !dsv4_chain_run(
layers,
&run,
cfg,
g,
st,
*ids.last().unwrap(),
&mut folded,
Some(&mut states),
b,
ids,
true,
pool,
) {
return false;
}
let mut scratch = HcScratch::new(cfg);
for t in 0..b {
st.pos = pos0 + t;
let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
for (li, l) in layers.iter().enumerate().skip(gpu_end) {
let freqs = if l.compressor.is_some() {
&g.inv_freq_compress
} else {
&g.inv_freq_window
};
let freqs = if freqs.is_empty() {
inv_freq
} else {
freqs.as_slice()
};
hc_block(
state,
&l.hc_attn_fn,
&l.hc_attn_scale,
&l.hc_attn_base,
&l.attn_norm,
cfg,
&mut scratch,
pool,
|f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
);
hc_block(
state,
&l.hc_ffn_fn,
&l.hc_ffn_scale,
&l.hc_ffn_base,
&l.ffn_norm,
cfg,
&mut scratch,
pool,
|f, o| {
if host_cpu_moe() {
crate::gpu::cpu_scope(|| moe_step(f, l, cfg, ids[t], li, pool, o))
} else {
moe_step(f, l, cfg, ids[t], li, pool, o)
}
},
);
dspark_note(li, state, cfg);
}
}
st.pos = pos0 + b;
{
static SAID: std::sync::Once = std::sync::Once::new();
SAID.call_once(|| tracing::warn!("dsv4: префилл пакетами по {b}"));
}
if want_logits {
let last = &states[(b - 1) * hc * dim..b * hc * dim];
let mut h = vec![0.0f32; dim];
hc_head_fold(
last,
&g.hc_head_fn,
g.hc_head_scale,
&g.hc_head_base,
cfg,
pool,
&mut h,
);
rms_weighted(&mut h, &g.norm, cfg.norm_eps);
logits.resize(cfg.vocab, 0.0);
g.head.matvec(&h, logits, pool);
} else {
logits.clear();
}
true
}
}
#[cfg(feature = "gpu")]
pub struct Dsv4SpecTxn {
pos0: usize,
batch: usize,
gpu_end: usize,
dev_filled: Vec<usize>,
dev_n_comp: Vec<usize>,
dev_n_ix: Vec<usize>,
host: Vec<(usize, HostLayerSnap)>,
host_steps: Vec<(usize, Vec<HostLayerSnap>)>,
pub states: Vec<f32>,
shadow: Option<crate::gpu_wgpu::Dsv4SpecShadow>,
}
#[cfg(feature = "gpu")]
struct HostLayerSnap {
window: Vec<f32>,
compressed: Vec<f32>,
index_kv: Vec<f32>,
pending_kv: Vec<f32>,
pending_score: Vec<f32>,
prev_kv: Vec<f32>,
prev_score: Vec<f32>,
pending_ix_kv: Vec<f32>,
pending_ix_score: Vec<f32>,
prev_ix_kv: Vec<f32>,
prev_ix_score: Vec<f32>,
}
#[cfg(feature = "gpu")]
fn host_snap(st: &Dsv4State, li: usize) -> HostLayerSnap {
HostLayerSnap {
window: st.window[li].clone(),
compressed: st.compressed[li].clone(),
index_kv: st.index_kv[li].clone(),
pending_kv: st.pending_kv[li].clone(),
pending_score: st.pending_score[li].clone(),
prev_kv: st.prev_kv[li].clone(),
prev_score: st.prev_score[li].clone(),
pending_ix_kv: st.pending_ix_kv[li].clone(),
pending_ix_score: st.pending_ix_score[li].clone(),
prev_ix_kv: st.prev_ix_kv[li].clone(),
prev_ix_score: st.prev_ix_score[li].clone(),
}
}
#[cfg(feature = "gpu")]
fn host_restore(st: &mut Dsv4State, li: usize, s: &HostLayerSnap) {
st.window[li] = s.window.clone();
st.compressed[li] = s.compressed.clone();
st.index_kv[li] = s.index_kv.clone();
st.pending_kv[li] = s.pending_kv.clone();
st.pending_score[li] = s.pending_score.clone();
st.prev_kv[li] = s.prev_kv.clone();
st.prev_score[li] = s.prev_score.clone();
st.pending_ix_kv[li] = s.pending_ix_kv.clone();
st.pending_ix_score[li] = s.pending_ix_score.clone();
st.prev_ix_kv[li] = s.prev_ix_kv.clone();
st.prev_ix_score[li] = s.prev_ix_score.clone();
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn host_tail_walk(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
gpu_end: usize,
state: &mut [f32],
token_id: u32,
pos: usize,
inv_freq: &[f32],
scratch: &mut HcScratch,
pool: Option<&crate::pool::Pool>,
) {
st.pos = pos;
for (li, l) in layers.iter().enumerate().skip(gpu_end) {
let freqs = if l.compressor.is_some() {
&g.inv_freq_compress
} else {
&g.inv_freq_window
};
let freqs = if freqs.is_empty() { inv_freq } else { freqs.as_slice() };
hc_block(
state,
&l.hc_attn_fn,
&l.hc_attn_scale,
&l.hc_attn_base,
&l.attn_norm,
cfg,
scratch,
pool,
|f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
);
hc_block(
state,
&l.hc_ffn_fn,
&l.hc_ffn_scale,
&l.hc_ffn_base,
&l.ffn_norm,
cfg,
scratch,
pool,
|f, o| {
if host_cpu_moe() {
crate::gpu::cpu_scope(|| moe_step(f, l, cfg, token_id, li, pool, o))
} else {
moe_step(f, l, cfg, token_id, li, pool, o)
}
},
);
dspark_note(li, state, cfg);
}
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
fn host_tail_walk_batch(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
gpu_end: usize,
states: &mut [f32],
ids: &[u32],
pos0: usize,
b: usize,
inv_freq: &[f32],
scratch: &mut HcScratch,
pool: Option<&crate::pool::Pool>,
mut steps: Option<&mut Vec<(usize, Vec<HostLayerSnap>)>>,
) {
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let mix_hc = (2 + hc) * hc;
let mut folds = vec![0.0f32; b * dim];
let mut mo = vec![0.0f32; b * dim];
let mut posts = vec![0.0f32; b * hc];
let mut combs = vec![0.0f32; b * hc * hc];
let mut resid = vec![0.0f32; b * hc * dim];
let spec_time = {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
};
for (li, l) in layers.iter().enumerate().skip(gpu_end) {
let t_attn = std::time::Instant::now();
let freqs = if l.compressor.is_some() {
&g.inv_freq_compress
} else {
&g.inv_freq_window
};
let freqs = if freqs.is_empty() { inv_freq } else { freqs.as_slice() };
for t in 0..b {
st.pos = pos0 + t;
let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
hc_block(
state,
&l.hc_attn_fn,
&l.hc_attn_scale,
&l.hc_attn_base,
&l.attn_norm,
cfg,
scratch,
pool,
|f, o| attention_step(f, l, cfg, st, li, freqs, pool, None, o),
);
if let Some(steps) = steps.as_mut() {
match steps.iter_mut().find(|(l, _)| *l == li) {
Some((_, v)) => v.push(host_snap(st, li)),
None => steps.push((li, vec![host_snap(st, li)])),
}
}
}
let t_glue = std::time::Instant::now();
for t in 0..b {
let state = &states[t * hc * dim..(t + 1) * hc * dim];
hc_mixes(state, &l.hc_ffn_fn, mix_hc, cfg.norm_eps, pool, &mut scratch.mixes);
hc_split_sinkhorn(
&scratch.mixes,
&l.hc_ffn_scale,
&l.hc_ffn_base,
hc,
cfg.hc_sinkhorn_iters,
cfg.hc_eps,
&mut scratch.pre,
&mut posts[t * hc..(t + 1) * hc],
&mut combs[t * hc * hc..(t + 1) * hc * hc],
);
let fold = &mut folds[t * dim..(t + 1) * dim];
hc_fold(state, &scratch.pre, hc, dim, fold);
let ms = fold.iter().map(|v| v * v).sum::<f32>() / dim as f32;
let inv = 1.0 / (ms + cfg.norm_eps).sqrt();
for (v, w) in fold.iter_mut().zip(&l.ffn_norm) {
*v = *v * inv * w;
}
resid[t * hc * dim..(t + 1) * hc * dim]
.copy_from_slice(&states[t * hc * dim..(t + 1) * hc * dim]);
}
let t_moe = std::time::Instant::now();
let tail_pack = {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| {
std::env::var("CMF_DSV4_TAIL_PACK").map(|v| v != "0").unwrap_or(true)
})
};
let mut packed_done = false;
if tail_pack && pack_for(l, cfg, li).is_some() {
packed_done = true;
for t in 0..b {
let f = &folds[t * dim..(t + 1) * dim];
let forced = l.tid2eid.as_ref().map(|tbl| {
hash_route(tbl, cfg.vocab, cfg.top_k, ids.get(t).copied().unwrap_or(0))
});
let o = &mut mo[t * dim..(t + 1) * dim];
match moe_frame(f, l, cfg, li, &[], forced.as_deref(), pool, None, None, o) {
Some((cold_sum, n)) => {
if n > 0 {
for (od, cd) in o.iter_mut().zip(cold_sum.iter()) {
*od += cd;
}
}
}
None => {
packed_done = false;
break;
}
}
}
}
if !packed_done {
if host_cpu_moe() {
crate::gpu::cpu_scope(|| {
moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo)
});
} else {
moe_step_block(&folds, b, l, cfg, ids, li, pool, &mut mo);
}
}
let t_exp = std::time::Instant::now();
for t in 0..b {
let state = &mut states[t * hc * dim..(t + 1) * hc * dim];
hc_expand(
&mo[t * dim..(t + 1) * dim],
&resid[t * hc * dim..(t + 1) * hc * dim],
&posts[t * hc..(t + 1) * hc],
&combs[t * hc * hc..(t + 1) * hc * hc],
hc,
dim,
state,
);
dspark_note(li, state, cfg);
}
if spec_time {
eprintln!(
"хвост слоя {li}: attn {:.1} мс, клей {:.1}, moe {:.1}, expand {:.1}",
(t_glue - t_attn).as_secs_f64() * 1e3,
(t_moe - t_glue).as_secs_f64() * 1e3,
(t_exp - t_moe).as_secs_f64() * 1e3,
t_exp.elapsed().as_secs_f64() * 1e3,
);
}
}
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
pub fn dsv4_verify_chunk(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
ids: &[u32],
pos0: usize,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
cap_targets: &[usize],
argmax_out: &mut Vec<u32>,
logits_out: &mut Vec<f32>,
walked_out: &mut Vec<f32>,
) -> Option<Dsv4SpecTxn> {
let b = ids.len();
let gpu_end = st
.dev_set
.iter()
.enumerate()
.position(|(li, &on)| {
!on || pack_for(&layers[li], cfg, li)
.is_none_or(|p| p.globals.len() < cfg.n_routed_experts)
})
.unwrap_or(st.dev_set.len());
let full_beyond = st.dev_set[gpu_end.min(st.dev_set.len())..]
.iter()
.enumerate()
.any(|(i, &on)| {
on && !st.partial_set.get(gpu_end + i).copied().unwrap_or(false)
});
if b < 2
|| !chain_enabled()
|| !st.dev_owned
|| st.dev_set.len() != layers.len()
|| gpu_end == 0
|| full_beyond
{
return None;
}
let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
let metas: Vec<(usize, usize, usize, usize)> = (0..gpu_end)
.map(|li| (li, hd, cfg.window, st.dev_filled[li]))
.collect();
let shadow = crate::gpu_wgpu::dsv4_spec_shadow(st.kv_id, &metas, b)?;
let mut txn = Dsv4SpecTxn {
pos0,
batch: b,
gpu_end,
dev_filled: st.dev_filled.clone(),
dev_n_comp: st.dev_n_comp.clone(),
dev_n_ix: st.dev_n_ix.clone(),
host: (gpu_end..layers.len())
.map(|li| (li, host_snap(st, li)))
.collect(),
states: Vec::new(),
host_steps: Vec::new(),
shadow: Some(shadow),
};
let dev_caps: Vec<usize> = cap_targets.iter().copied().filter(|&t| t < gpu_end).collect();
crate::gpu_wgpu::dsv4_spec_retain_arm(gpu_end, &dev_caps);
let mut emb = vec![0.0f32; dim];
for (t, &id) in ids.iter().enumerate() {
let mut state = vec![0.0f32; hc * dim];
g.embed.row_f32(id as usize, &mut emb);
for j in 0..hc {
state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
}
let (folded, post0, comb0) = hc_fold_norm(
&state,
&layers[0].hc_attn_fn,
&layers[0].hc_attn_scale,
&layers[0].hc_attn_base,
&layers[0].attn_norm,
cfg,
pool,
);
let mut qn0 = vec![0.0f32; layers[0].wq_a.rows()];
layers[0].wq_a.matvec(&folded, &mut qn0, pool);
rms_weighted(&mut qn0, &layers[0].q_norm, cfg.norm_eps);
if !crate::gpu_wgpu::dsv4_state_write_t(&state, t)
|| !crate::gpu_wgpu::dsv4_hc_write_t(&post0, &comb0, t)
|| !crate::gpu_wgpu::dsv4_chain_seed_t(&folded, &qn0, t)
|| !crate::gpu_wgpu::dsv4_chain_seed_bt(t, b, &state, &post0, &comb0, &folded, &qn0)
{
crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
return None;
}
}
let spec_time = {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSV4_SPEC_TIME").is_ok_and(|v| v != "0"))
};
let t0 = std::time::Instant::now();
let run: Vec<usize> = (0..gpu_end).collect();
let mut folded = Vec::new();
let mut states = vec![0.0f32; b * hc * dim];
st.pos = pos0;
let ok = dsv4_chain_run(
layers,
&run,
cfg,
g,
st,
*ids.last().unwrap(),
&mut folded,
Some(&mut states),
b,
ids,
true,
pool,
);
crate::gpu_wgpu::dsv4_spec_retain_arm(0, &[]);
if !ok {
if let Some(sh) = txn.shadow.take() {
let _ = crate::gpu_wgpu::dsv4_spec_restore(&sh);
}
st.dev_filled = txn.dev_filled;
st.dev_n_comp = txn.dev_n_comp;
st.dev_n_ix = txn.dev_n_ix;
st.pos = pos0;
return None;
}
txn.states = states.clone();
let t_chain = t0.elapsed();
if std::env::var("CMF_DSV4_FOLD_DBG").is_ok() {
for li in 0..gpu_end {
let Some(ixr) = &layers[li].indexer else { continue };
let ratio = ixr.compressor.ratio;
for t in 0..b {
if (pos0 + t + 1) % ratio == 0 {
let ew = {
let w = ixr.compressor.wkv.rows();
if ixr.compressor.overlap { w / 2 } else { w }
};
let idx_new = txn.dev_n_ix[li] + (0..=t)
.filter(|k| (pos0 + k + 1) % ratio == 0)
.count()
- 1;
if let Some(v) =
crate::gpu_wgpu::dsv4_dbg_read_ix(st.kv_id, li, idx_new * ew, ew.min(8))
{
let sum: f32 = v.iter().sum();
eprintln!(
"[fold] li={li} pos={} entry={idx_new} head={:?} sum={sum:.6}",
pos0 + t,
&v[..4.min(v.len())]
);
}
}
}
}
}
let mut scratch = HcScratch::new(cfg);
argmax_out.clear();
logits_out.clear();
logits_out.resize(b * cfg.vocab, 0.0);
let mut head_in = vec![0.0f32; b * dim];
let mut host_steps: Vec<(usize, Vec<HostLayerSnap>)> = Vec::new();
host_tail_walk_batch(
g, layers, cfg, st, gpu_end, &mut states, ids, pos0, b, inv_freq, &mut scratch, pool,
Some(&mut host_steps),
);
txn.host_steps = host_steps;
for t in 0..b {
let state = &states[t * hc * dim..(t + 1) * hc * dim];
let h = &mut head_in[t * dim..(t + 1) * dim];
hc_head_fold(state, &g.hc_head_fn, g.hc_head_scale, &g.hc_head_base, cfg, pool, h);
rms_weighted(h, &g.norm, cfg.norm_eps);
}
let head_gpu = g.head.model_idx().is_some_and(|hi| {
let model = layers[0].experts.first().and_then(|e| e.w1.model_arc());
model.is_some_and(|m| {
crate::gpu_wgpu::q4tp_matvec_batch_for_test(
&m, hi, &head_in, b, cfg.vocab, dim, logits_out,
)
})
});
for t in 0..b {
if !head_gpu {
let h = &head_in[t * dim..(t + 1) * dim];
g.head.matvec(h, &mut logits_out[t * cfg.vocab..(t + 1) * cfg.vocab], pool);
}
let row = &logits_out[t * cfg.vocab..(t + 1) * cfg.vocab];
let mut best = 0usize;
for v in 1..cfg.vocab {
if row[v] > row[best] {
best = v;
}
}
argmax_out.push(best as u32);
}
walked_out.clear();
walked_out.extend_from_slice(&states);
st.pos = pos0 + b;
if spec_time {
eprintln!(
"verify: тень+сид+цепочка {:.1} мс, хвост+голова {:.1} мс",
t_chain.as_secs_f64() * 1e3,
(t0.elapsed() - t_chain).as_secs_f64() * 1e3,
);
}
Some(txn)
}
#[cfg(feature = "gpu")]
pub fn dsv4_spec_finish(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
mut txn: Dsv4SpecTxn,
accepted: usize,
ids: &[u32],
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
) -> bool {
macro_rules! sfail {
($($t:tt)*) => {{
if std::env::var("CMF_DSV4_SPEC_DEBUG").is_ok() {
eprintln!("spec_finish: {}", format_args!($($t)*));
}
return false;
}};
}
let b = txn.batch;
let k = accepted.min(b);
let (hc, dim, hd) = (cfg.hc_mult, cfg.dim, cfg.head_dim);
let win_metas: Vec<(usize, usize, usize, usize)> = (0..txn.gpu_end)
.map(|li| (li, txn.dev_filled[li], cfg.window, hd))
.collect();
if !crate::gpu_wgpu::dsv4_spec_commit_windows(st.kv_id, &win_metas, b, k) {
sfail!("коммит окон");
}
if k == b {
return true;
}
let Some(sh) = txn.shadow.take() else { sfail!("нет тени") };
if !crate::gpu_wgpu::dsv4_spec_restore(&sh) {
sfail!("restore");
}
let Some(model) = layers[0].experts.first().and_then(|e| e.w1.model_arc()) else {
sfail!("нет модели");
};
let mut plan: Vec<(usize, crate::gpu_wgpu::Dsv4Prep)> = Vec::new();
let mut freqs_own: Vec<&[f32]> = Vec::new();
for li in 0..txn.gpu_end {
let l = &layers[li];
let Some(wkv) = l.wkv.model_idx() else { sfail!("wkv слоя {li}") };
let comp = match &l.compressor {
None => None,
Some(cp) => {
let (Some(a), Some(bx)) = (cp.wkv.model_idx(), cp.wgate.model_idx()) else {
sfail!("компрессор слоя {li}");
};
Some((
crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: bx, norm: &cp.norm, ape: &cp.ape },
crate::gpu_wgpu::Dsv4CompGeom {
width: cp.wkv.rows(),
hidden: dim,
ratio: cp.ratio,
overlap: cp.overlap,
rope_dim: cfg.rope_head_dim,
eps: cfg.norm_eps,
},
))
}
};
let ix = match &l.indexer {
None => None,
Some(ixr) => {
let cp = &ixr.compressor;
let (Some(a), Some(bx), Some(qb), Some(wp)) = (
cp.wkv.model_idx(),
cp.wgate.model_idx(),
ixr.wq_b.model_idx(),
ixr.weights_proj.model_idx(),
) else {
sfail!("индексер слоя {li}");
};
let ih = ixr.weights_proj.rows();
Some((
crate::gpu_wgpu::Dsv4CompW { wkv: a, wgate: bx, norm: &cp.norm, ape: &cp.ape },
crate::gpu_wgpu::Dsv4CompGeom {
width: cp.wkv.rows(),
hidden: dim,
ratio: cp.ratio,
overlap: cp.overlap,
rope_dim: cfg.rope_head_dim,
eps: cfg.norm_eps,
},
crate::gpu_wgpu::Dsv4IxW { wq_b: qb, weights_proj: wp },
crate::gpu_wgpu::Dsv4IxGeom {
ih,
idim: ixr.wq_b.rows() / ih.max(1),
q_lora: cfg.q_lora_rank,
hidden: dim,
rope_dim: cfg.rope_head_dim,
eps: cfg.norm_eps,
top_k: cfg.index_topk,
window: cfg.window,
},
))
}
};
let ew_c = comp.as_ref().map_or(0, |(_, cg)| {
if cg.overlap { cg.width / 2 } else { cg.width }
});
let ew_i = ix.as_ref().map_or(0, |(_, cg, _, _)| {
if cg.overlap { cg.width / 2 } else { cg.width }
});
let prep = crate::gpu_wgpu::Dsv4Prep {
wkv,
kv_norm: &l.kv_norm,
comp,
ix,
filled: txn.dev_filled[li],
window: cfg.window,
n_comp: txn.dev_n_comp[li],
n_ix: txn.dev_n_ix[li],
comp_dst_off: cfg.window * hd + txn.dev_n_comp[li] * ew_c,
ix_dst_off: txn.dev_n_ix[li] * ew_i,
idx_cap: cfg.window + if l.indexer.is_some() { cfg.index_topk } else { 0 },
};
let fr = if l.compressor.is_some() {
g.inv_freq_compress.as_slice()
} else {
g.inv_freq_window.as_slice()
};
freqs_own.push(if fr.is_empty() { inv_freq } else { fr });
plan.push((li, prep));
}
if !crate::gpu_wgpu::dsv4_spec_replay(
&model,
&plan,
st.kv_id,
txn.pos0,
b,
k,
&freqs_own,
hd,
dim,
cfg.rope_head_dim,
cfg.norm_eps,
true,
) {
sfail!("replay k={k}");
}
let advanced = |ratio: usize| -> usize {
if ratio == 0 {
return 0;
}
(0..k).filter(|t| (txn.pos0 + t + 1) % ratio == 0).count()
};
for li in 0..txn.gpu_end {
let l = &layers[li];
st.dev_filled[li] = (txn.dev_filled[li] + k).min(cfg.window);
let ac = l.compressor.as_ref().map_or(0, |cp| advanced(cp.ratio));
let ai = l.indexer.as_ref().map_or(0, |ix| advanced(ix.compressor.ratio));
st.dev_n_comp[li] = txn.dev_n_comp[li] + ac;
st.dev_n_ix[li] = txn.dev_n_ix[li] + ai;
note_compressed(st.kv_id, li, st.dev_n_comp[li]);
}
if k >= 1 && txn.host_steps.iter().all(|(_, v)| v.len() >= k) && !txn.host_steps.is_empty()
{
for (li, v) in &txn.host_steps {
host_restore(st, *li, &v[k - 1]);
}
} else {
for (li, snap) in &txn.host {
host_restore(st, *li, snap);
}
let mut scratch = HcScratch::new(cfg);
let mut states = txn.states.clone();
host_tail_walk_batch(
g, layers, cfg, st, txn.gpu_end, &mut states[..k * hc * dim], ids, txn.pos0, k,
inv_freq, &mut scratch, pool, None,
);
}
st.pos = txn.pos0 + k;
true
}
pub fn forward_chunk(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
ids: &[u32],
pos0: usize,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
logits: &mut Vec<f32>,
want_logits: bool,
) {
let bs = batch_prefill();
if bs > 1 {
let mut i = 0;
if !st.dev_owned && !ids.is_empty() {
st.pos = pos0;
forward_token_inner(g, layers, cfg, st, ids[0], inv_freq, pool, logits, ids.len() == 1);
i = 1;
}
while i < ids.len() {
let end = (i + bs).min(ids.len());
st.pos = pos0 + i;
if !forward_chunk_batched(
g,
layers,
cfg,
st,
&ids[i..end],
pos0 + i,
inv_freq,
pool,
logits,
want_logits && end == ids.len(),
) {
break;
}
i = end;
}
if i == ids.len() {
return;
}
for (k, &id) in ids.iter().enumerate().skip(i) {
st.pos = pos0 + k;
let last = want_logits && k + 1 == ids.len();
forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
}
return;
}
for (i, &id) in ids.iter().enumerate() {
st.pos = pos0 + i;
let last = want_logits && i + 1 == ids.len();
forward_token_inner(g, layers, cfg, st, id, inv_freq, pool, logits, last);
}
}
pub fn forward_token(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
token_id: u32,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
logits: &mut Vec<f32>,
) {
forward_token_inner(g, layers, cfg, st, token_id, inv_freq, pool, logits, true);
}
#[allow(clippy::too_many_arguments)]
fn forward_token_inner(
g: &Dsv4Globals,
layers: &[Dsv4Layer],
cfg: &Dsv4Cfg,
st: &mut Dsv4State,
token_id: u32,
inv_freq: &[f32],
pool: Option<&crate::pool::Pool>,
logits: &mut Vec<f32>,
want_logits: bool,
) {
let _t_all = prof::on().then(std::time::Instant::now);
let _all_guard = Charge(_t_all, &prof::ALL_NS);
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let mut emb = vec![0.0f32; dim];
g.embed.row_f32(token_id as usize, &mut emb);
let mut state = vec![0.0f32; hc * dim];
for j in 0..hc {
state[j * dim..(j + 1) * dim].copy_from_slice(&emb);
}
let mut scratch = HcScratch::new(cfg);
let mut dump: Vec<String> = Vec::new();
if dump_path().is_some() {
dump.push(format!("\"embed\":{}", vec_json(&emb)));
PICKED.with(|p| p.borrow_mut().clear());
BODY.with(|b| b.borrow_mut().clear());
dump.push(",\"layers\":[".into());
}
if trace_on() {
eprintln!(
"[dsv4] tok={token_id} pos={} embed rms={:.5}",
st.pos,
rms_of(&emb)
);
}
#[cfg(feature = "gpu")]
let layer_frames = gpu_layer_enabled()
&& dsv4_layer_loop(
&mut state, layers, g, cfg, st, token_id, inv_freq, pool, &mut scratch,
);
#[cfg(not(feature = "gpu"))]
let layer_frames = false;
#[cfg(feature = "gpu")]
let hc_dev = hc_on_device()
&& !layer_frames
&& gpu_attn_enabled()
&& gpu_moe2_enabled()
&& dump_path().is_none();
#[cfg(not(feature = "gpu"))]
let hc_dev = false;
#[cfg(feature = "gpu")]
let two_frame_done = hc_dev
&& dsv4_two_frame_loop(
&mut state, layers, g, cfg, st, token_id, inv_freq, pool, &mut scratch,
);
#[cfg(not(feature = "gpu"))]
let two_frame_done = false;
if !two_frame_done {
for (li, l) in layers.iter().enumerate() {
if layer_frames {
break;
}
hc_block(
&mut state,
&l.hc_attn_fn,
&l.hc_attn_scale,
&l.hc_attn_base,
&l.attn_norm,
cfg,
&mut scratch,
pool,
|folded, out| {
if dump_path().is_some() {
BODY.with(|b| b.borrow_mut().push(vec_json(folded)));
}
let freqs = if l.compressor.is_some() {
&g.inv_freq_compress
} else {
&g.inv_freq_window
};
let freqs = if freqs.is_empty() {
inv_freq
} else {
freqs.as_slice()
};
attention_step(folded, l, cfg, st, li, freqs, pool, None, out);
if dump_path().is_some() {
BODY.with(|b| b.borrow_mut().push(vec_json(out)));
}
},
);
if dump_path().is_some() {
dump.push(format!(
"{}{}",
if li == 0 { "" } else { "," },
vec_json(&state)
));
}
let _t_hc2 = prof::on().then(std::time::Instant::now);
hc_block(
&mut state,
&l.hc_ffn_fn,
&l.hc_ffn_scale,
&l.hc_ffn_base,
&l.ffn_norm,
cfg,
&mut scratch,
pool,
|folded, out| moe_step(folded, l, cfg, token_id, li, pool, out),
);
if let Some(t) = _t_hc2 {
prof::HC_NS.fetch_add(
t.elapsed().as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
if dump_path().is_some() {
dump.push(format!(",{}", vec_json(&state)));
}
if trace_on() && (st.pos % 64 == 0 || st.pos == 199) {
eprintln!(
"[dsv4] кеши слоя {li}: окно={} сжатых={} индекс={} (ratio={:?})",
st.window[li].len() / cfg.head_dim.max(1),
st.compressed[li].len() / cfg.head_dim.max(1),
st.index_kv[li].len().max(1) / 128,
l.compressor.as_ref().map(|c| (c.ratio, c.overlap)),
);
}
if trace_on() {
let bad = state.iter().filter(|v| !v.is_finite()).count();
eprintln!(
"[dsv4] layer {li:>2}: rms={:.5}{}",
rms_of(&state),
if bad > 0 {
format!(" NON-FINITE x{bad}")
} else {
String::new()
}
);
}
dspark_note(li, &state, cfg);
}
}
st.pos += 1;
let mut h = vec![0.0f32; dim];
hc_head_fold(
&state,
&g.hc_head_fn,
g.hc_head_scale,
&g.hc_head_base,
cfg,
pool,
&mut h,
);
if !want_logits {
logits.clear();
return;
}
let _t_head = prof::on().then(std::time::Instant::now);
rms_weighted(&mut h, &g.norm, cfg.norm_eps);
logits.clear();
logits.resize(g.head.rows(), 0.0);
g.head.matvec(&h, logits, pool);
if let Some(t) = _t_head {
prof::HEAD_NS.fetch_add(
t.elapsed().as_nanos() as u64,
std::sync::atomic::Ordering::Relaxed,
);
}
if dump_path().is_some() {
dump.push("]".into());
let picked = PICKED.with(|p| {
p.borrow()
.iter()
.map(|v| {
format!(
"[{}]",
v.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(",")
)
})
.collect::<Vec<_>>()
.join(",")
});
dump.push(format!(",\"experts\":[{picked}]"));
let body = BODY.with(|b| b.borrow().join(","));
dump.push(format!(",\"attn_io\":[{body}]"));
dump_line(&format!(
"{{\"tok\":{token_id},\"pos\":{},{},\"head\":{},\"logits\":{}}}",
st.pos - 1,
dump.join(""),
vec_json(&h),
vec_json(logits)
));
}
if trace_on() {
let (mut top, mut best) = (0usize, f32::NEG_INFINITY);
for (i, &v) in logits.iter().enumerate() {
if v > best {
best = v;
top = i;
}
}
let lo = logits.iter().cloned().fold(f32::MAX, f32::min);
eprintln!(
"[dsv4] head: rms={:.5} logits[{}..{:.3}] argmax={top}",
rms_of(&h),
format_args!("{lo:.3}"),
best
);
}
}
pub fn load(
model: &std::sync::Arc<cortiq_core::CmfModel>,
cfg: &Dsv4Cfg,
n_layers: usize,
) -> Result<(Dsv4Globals, Vec<Dsv4Layer>), String> {
let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
crate::qtensor::QTensor::from_model(model, name)
};
let f = |name: &str| -> Result<Vec<f32>, String> {
crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
};
let rope_of = |base: f32, yarn: bool| -> Vec<f32> {
if yarn {
crate::attention::yarn_inv_freq(cfg.rope_head_dim, base, 16.0, 65536, 32.0, 1.0)
} else {
crate::attention::rope_inv_freq(cfg.rope_head_dim, base)
}
};
let globals = Dsv4Globals {
inv_freq_compress: rope_of(160_000.0, true),
inv_freq_window: rope_of(10_000.0, false),
embed: q("model.embed_tokens.weight")?,
norm: f("model.norm.weight")?,
head: q("lm_head.weight")?,
hc_head_fn: f("model.hc_head_fn")?,
hc_head_base: f("model.hc_head_base")?,
hc_head_scale: *f("model.hc_head_scale")?
.first()
.ok_or("dsv4: empty hc_head_scale")?,
};
let mut layers = Vec::with_capacity(n_layers);
for li in 0..n_layers {
layers.push(load_layer(
model,
cfg,
&format!("model.layers.{li}"),
Scheme::Main,
)?);
}
Ok((globals, layers))
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Scheme {
Main,
Mtp,
}
impl Scheme {
fn attn(self) -> &'static str {
match self {
Scheme::Main => "self_attn",
Scheme::Mtp => "attn",
}
}
fn attn_norm(self) -> &'static str {
match self {
Scheme::Main => "input_layernorm.weight",
Scheme::Mtp => "attn_norm.weight",
}
}
fn ffn_norm(self) -> &'static str {
match self {
Scheme::Main => "post_attention_layernorm.weight",
Scheme::Mtp => "ffn_norm.weight",
}
}
fn mlp(self) -> &'static str {
match self {
Scheme::Main => "mlp",
Scheme::Mtp => "ffn",
}
}
fn gate_bias(self) -> &'static str {
match self {
Scheme::Main => "expert_bias",
Scheme::Mtp => "gate.bias",
}
}
fn shared(self) -> &'static str {
match self {
Scheme::Main => "shared_expert",
Scheme::Mtp => "shared_experts",
}
}
fn w(self, i: u8) -> &'static str {
match (self, i) {
(Scheme::Main, 1) => "gate_proj.weight",
(Scheme::Main, 2) => "down_proj.weight",
(Scheme::Main, _) => "up_proj.weight",
(Scheme::Mtp, 1) => "w1.weight",
(Scheme::Mtp, 2) => "w2.weight",
(Scheme::Mtp, _) => "w3.weight",
}
}
}
pub fn load_layer(
model: &std::sync::Arc<cortiq_core::CmfModel>,
cfg: &Dsv4Cfg,
p: &str,
s: Scheme,
) -> Result<Dsv4Layer, String> {
let q = |name: &str| -> Result<crate::qtensor::QTensor, String> {
crate::qtensor::QTensor::from_model(model, name)
};
let f = |name: &str| -> Result<Vec<f32>, String> {
crate::loader::load_f32(model, name, &crate::loader::Overlay::None)
};
let opt_f = |name: &str| -> Option<Vec<f32>> { f(name).ok() };
let at = s.attn();
let ml = s.mlp();
{
let scale3 = |name: &str| -> Result<[f32; 3], String> {
let v = f(name)?;
if v.len() < 3 {
return Err(format!("{name}: expected 3 scales, got {}", v.len()));
}
Ok([v[0], v[1], v[2]])
};
let compressor = match q(&format!("{p}.{at}.compressor.wkv.weight")) {
Ok(wkv) => {
let ape = f(&format!("{p}.{at}.compressor.ape"))?;
let width = wkv.rows();
let ratio = (ape.len() / width.max(1)).max(1);
Some(Dsv4Compressor {
wkv,
wgate: q(&format!("{p}.{at}.compressor.wgate.weight"))?,
norm: f(&format!("{p}.{at}.compressor.norm.weight"))?,
ape,
ratio,
overlap: ratio == 4,
})
}
Err(_) => None,
};
let indexer = match q(&format!("{p}.{at}.indexer.wq_b.weight")) {
Ok(wq_b) => {
let ape = f(&format!("{p}.{at}.indexer.compressor.ape"))?;
let cwkv = q(&format!("{p}.{at}.indexer.compressor.wkv.weight"))?;
let width = cwkv.rows();
let ratio = (ape.len() / width.max(1)).max(1);
Some(Dsv4Indexer {
wq_b,
weights_proj: q(&format!("{p}.{at}.indexer.weights_proj.weight"))?,
compressor: Dsv4Compressor {
wkv: cwkv,
wgate: q(&format!("{p}.{at}.indexer.compressor.wgate.weight"))?,
norm: f(&format!("{p}.{at}.indexer.compressor.norm.weight"))?,
ape,
ratio,
overlap: ratio == 4,
},
})
}
Err(_) => None,
};
let mut experts = Vec::with_capacity(cfg.n_routed_experts);
for e in 0..cfg.n_routed_experts {
let ep = format!("{p}.{ml}.experts.{e}");
experts.push(Dsv4Expert {
w1: q(&format!("{ep}.{w}", w = s.w(1)))?,
w2: q(&format!("{ep}.{w}", w = s.w(2)))?,
w3: q(&format!("{ep}.{w}", w = s.w(3)))?,
});
}
Ok(Dsv4Layer {
attn_norm: f(&format!("{p}.{an}", an = s.attn_norm()))?,
ffn_norm: f(&format!("{p}.{fnm}", fnm = s.ffn_norm()))?,
wq_a: q(&format!("{p}.{at}.wq_a.weight"))?,
q_norm: f(&format!("{p}.{at}.q_norm.weight"))?,
wq_b: q(&format!("{p}.{at}.wq_b.weight"))?,
wkv: q(&format!("{p}.{at}.wkv.weight"))?,
kv_norm: f(&format!("{p}.{at}.kv_norm.weight"))?,
wo_a: q(&format!("{p}.{at}.wo_a.weight"))?,
wo_b: q(&format!("{p}.{at}.wo_b.weight"))?,
attn_sink: f(&format!("{p}.{at}.attn_sink"))?,
compressor,
indexer,
hc_attn_fn: f(&format!("{p}.hc_attn_fn"))?,
hc_attn_base: f(&format!("{p}.hc_attn_base"))?,
hc_attn_scale: scale3(&format!("{p}.hc_attn_scale"))?,
hc_ffn_fn: f(&format!("{p}.hc_ffn_fn"))?,
hc_ffn_base: f(&format!("{p}.hc_ffn_base"))?,
hc_ffn_scale: scale3(&format!("{p}.hc_ffn_scale"))?,
gate: q(&format!("{p}.{ml}.gate.weight"))?,
gate_bias: opt_f(&format!("{p}.{ml}.{b}", b = s.gate_bias())),
tid2eid: opt_f(&format!("{p}.{ml}.tid2eid")),
experts,
mask: if model.tensor(&format!("{p}.{ml}.tid2eid")).is_some() {
None
} else {
crate::loader::moe_task_mask(&format!("{p}."), cfg.n_routed_experts)
},
shared: Dsv4Expert {
w1: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(1)))?,
w2: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(2)))?,
w3: q(&format!("{p}.{ml}.{sh}.{w}", sh = s.shared(), w = s.w(3)))?,
},
})
}
}
pub struct Dsv4Mtp {
pub layer: Dsv4Layer,
pub main_proj: Option<crate::qtensor::QTensor>,
pub main_norm: Option<Vec<f32>>,
pub norm: Option<Vec<f32>>,
pub hc_head_fn: Option<Vec<f32>>,
pub hc_head_base: Option<Vec<f32>>,
pub hc_head_scale: Option<f32>,
pub confidence: Option<crate::qtensor::QTensor>,
pub markov_w1: Option<crate::qtensor::QTensor>,
pub markov_w2: Option<crate::qtensor::QTensor>,
}
pub fn load_mtp(
model: &std::sync::Arc<cortiq_core::CmfModel>,
cfg: &Dsv4Cfg,
max_depth: usize,
) -> Vec<Dsv4Mtp> {
let f = |name: &str| -> Option<Vec<f32>> {
crate::loader::load_f32(model, name, &crate::loader::Overlay::None).ok()
};
let mut out = Vec::new();
for d in 0..max_depth {
let p = format!("model.mtp.{d}");
if model.tensor(&format!("{p}.attn.wq_a.weight")).is_none() {
break;
}
let layer = match load_layer(model, cfg, &p, Scheme::Mtp) {
Ok(l) => l,
Err(e) => {
eprintln!("MTP {d}: пропущен, {e}");
break;
}
};
out.push(Dsv4Mtp {
layer,
main_proj: crate::qtensor::QTensor::from_model(model, &format!("{p}.main_proj.weight"))
.ok(),
main_norm: f(&format!("{p}.main_norm.weight")),
norm: f(&format!("{p}.norm.weight")),
hc_head_fn: f(&format!("{p}.hc_head_fn")),
hc_head_base: f(&format!("{p}.hc_head_base")),
hc_head_scale: f(&format!("{p}.hc_head_scale")).and_then(|v| v.first().copied()),
confidence: crate::qtensor::QTensor::from_model(
model,
&format!("{p}.confidence_head.proj.weight"),
)
.ok(),
markov_w1: crate::qtensor::QTensor::from_model(
model,
&format!("{p}.markov_head.markov_w1.weight"),
)
.ok(),
markov_w2: crate::qtensor::QTensor::from_model(
model,
&format!("{p}.markov_head.markov_w2.weight"),
)
.ok(),
});
}
dspark_apply_mask(&mut out);
if !out.is_empty() {
let mp = out
.iter()
.find_map(|m| m.main_proj.as_ref())
.map(|t| format!("[{}, {}]", t.rows(), t.cols()))
.unwrap_or_else(|| "нет".into());
eprintln!(
"MTP: {} стади(я/и/й), main_proj {mp}, экспертов {}, \
голова уверенности {}, марков {}",
out.len(),
out[0].layer.experts.len(),
if out.iter().any(|m| m.confidence.is_some()) { "есть" } else { "нет" },
if out.iter().any(|m| m.markov_w1.is_some()) { "есть" } else { "нет" },
);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn toy() -> (Dsv4Globals, Vec<Dsv4Layer>, Dsv4Cfg) {
use crate::qtensor::QTensor;
let cfg = Dsv4Cfg {
dim: 32,
n_heads: 4,
head_dim: 8,
rope_head_dim: 4,
q_lora_rank: 16,
o_lora_rank: 16,
o_groups: 2,
hc_mult: 4,
hc_sinkhorn_iters: 20,
hc_eps: 1e-6,
norm_eps: 1e-6,
n_routed_experts: 8,
top_k: 2,
moe_inter: 16,
route_scale: 1.0,
swiglu_limit: 10.0,
window: 6,
index_topk: 8,
vocab: 24,
};
let w = |n: usize, seed: usize| -> Vec<f32> {
(0..n)
.map(|i| (((i * 7 + seed * 13) % 101) as f32 / 101.0 - 0.5) * 0.3)
.collect()
};
let t = |rows: usize, cols: usize, seed: usize| {
QTensor::from_f32(w(rows * cols, seed), rows, cols)
};
let ones = |n: usize| vec![1.0f32; n];
let (dim, hc) = (cfg.dim, cfg.hc_mult);
let q_width = cfg.n_heads * cfg.head_dim;
let kv_width = cfg.head_dim;
let o_per_group = q_width / cfg.o_groups;
let mut layers = Vec::new();
for li in 0..2 {
let experts: Vec<Dsv4Expert> = (0..cfg.n_routed_experts)
.map(|e| Dsv4Expert {
w1: t(cfg.moe_inter, dim, 40 + e + li * 8),
w2: t(dim, cfg.moe_inter, 60 + e + li * 8),
w3: t(cfg.moe_inter, dim, 80 + e + li * 8),
})
.collect();
layers.push(Dsv4Layer {
attn_norm: ones(dim),
ffn_norm: ones(dim),
wq_a: t(cfg.q_lora_rank, dim, 1 + li),
q_norm: ones(cfg.q_lora_rank),
wq_b: t(q_width, cfg.q_lora_rank, 3 + li),
wkv: t(kv_width, dim, 5 + li),
kv_norm: ones(kv_width),
wo_a: t(cfg.o_groups * cfg.o_lora_rank, o_per_group, 7 + li),
wo_b: t(dim, cfg.o_groups * cfg.o_lora_rank, 9 + li),
attn_sink: vec![0.1; cfg.n_heads],
compressor: if li == 1 {
Some(Dsv4Compressor {
wkv: t(2 * kv_width, dim, 11),
wgate: t(2 * kv_width, dim, 13),
norm: ones(kv_width),
ape: vec![0.01; 4 * 2 * kv_width],
ratio: 4,
overlap: true,
})
} else {
None
},
indexer: if li == 1 {
Some(Dsv4Indexer {
wq_b: t(2 * 16, cfg.q_lora_rank, 41),
weights_proj: t(2, dim, 43),
compressor: Dsv4Compressor {
wkv: t(2 * 16, dim, 45),
wgate: t(2 * 16, dim, 47),
norm: ones(16),
ape: vec![0.01; 4 * 2 * 16],
ratio: 4,
overlap: true,
},
})
} else {
None
},
hc_attn_fn: w((2 + hc) * hc * hc * dim, 15 + li),
hc_attn_base: w((2 + hc) * hc, 17 + li),
hc_attn_scale: [1.0, 1.0, 1.0],
hc_ffn_fn: w((2 + hc) * hc * hc * dim, 19 + li),
hc_ffn_base: w((2 + hc) * hc, 21 + li),
hc_ffn_scale: [1.0, 1.0, 1.0],
gate: t(cfg.n_routed_experts, dim, 23 + li),
gate_bias: if li == 1 {
Some(vec![0.0; cfg.n_routed_experts])
} else {
None
},
tid2eid: if li == 0 {
Some(
(0..cfg.vocab * cfg.top_k)
.map(|i| (i % cfg.n_routed_experts) as f32)
.collect(),
)
} else {
None
},
experts,
mask: None,
shared: Dsv4Expert {
w1: t(cfg.moe_inter, dim, 25 + li),
w2: t(dim, cfg.moe_inter, 27 + li),
w3: t(cfg.moe_inter, dim, 29 + li),
},
});
}
let inv = |base: f32| -> Vec<f32> {
(0..cfg.rope_head_dim / 2)
.map(|i| 1.0 / base.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
.collect()
};
let g = Dsv4Globals {
inv_freq_compress: inv(160000.0),
inv_freq_window: inv(10000.0),
embed: t(cfg.vocab, dim, 31),
norm: ones(dim),
head: t(cfg.vocab, dim, 33),
hc_head_fn: w(hc * hc * dim, 35),
hc_head_base: w(hc, 37),
hc_head_scale: 1.0,
};
(g, layers, cfg)
}
#[test]
fn forward_token_decodes_a_sequence_without_falling_over() {
let (g, layers, cfg) = toy();
let mut st = Dsv4State::new(layers.len());
let inv_freq: Vec<f32> = (0..cfg.rope_head_dim / 2)
.map(|i| 1.0 / 10000f32.powf(2.0 * i as f32 / cfg.rope_head_dim as f32))
.collect();
let mut logits = Vec::new();
let mut first: Option<Vec<f32>> = None;
for (step, tok) in [3u32, 7, 1, 9, 4, 2, 8, 5, 6, 0].into_iter().enumerate() {
forward_token(
&g,
&layers,
&cfg,
&mut st,
tok,
&inv_freq,
None,
&mut logits,
);
assert_eq!(logits.len(), cfg.vocab, "step {step}: logit count");
assert!(
logits.iter().all(|v| v.is_finite()),
"step {step}: non-finite logit — {logits:?}"
);
let spread = logits.iter().cloned().fold(f32::MIN, f32::max)
- logits.iter().cloned().fold(f32::MAX, f32::min);
assert!(spread > 1e-6, "step {step}: logits are flat ({spread})");
if step == 0 {
first = Some(logits.clone());
}
assert_eq!(st.pos, step + 1, "position bookkeeping");
}
assert!(!st.window[0].is_empty(), "sliding window never filled");
for (li, w) in st.window.iter().enumerate() {
assert!(
w.len() / cfg.head_dim <= cfg.window,
"layer {li}: window holds {} positions, cap is {}",
w.len() / cfg.head_dim,
cfg.window
);
}
assert!(
!st.compressed[1].is_empty(),
"compressor layer produced no compressed KV in 10 tokens"
);
assert_eq!(
st.compressed[1].len() / cfg.head_dim,
2,
"expected two folds in ten tokens at ratio 4"
);
assert!(
!st.prev_kv[1].is_empty(),
"the overlapping compressor never kept a previous window"
);
for (li, l) in layers.iter().enumerate() {
if l.indexer.is_some() {
assert!(
!st.index_kv[li].is_empty(),
"layer {li} has an indexer but its cache stayed empty"
);
}
}
let mut fresh = Dsv4State::new(layers.len());
let mut relogits = Vec::new();
forward_token(
&g,
&layers,
&cfg,
&mut fresh,
3,
&inv_freq,
None,
&mut relogits,
);
assert_eq!(
relogits,
first.unwrap(),
"the same token from a fresh state must reproduce exactly"
);
}
#[test]
fn swiglu_limit_clamps_up_both_ways_and_gate_only_from_above() {
let inter = 4;
let gate_src = [-50.0f32, 50.0, 1.0, -1.0];
let up_src = [50.0f32, -50.0, 1.0, -1.0];
let limit = 10.0f32;
let mut got = vec![0.0f32; inter];
expert_swiglu(
&[0.0],
&|_, d| d.copy_from_slice(&gate_src),
&|_, d| d.copy_from_slice(&up_src),
&|src, d| d.copy_from_slice(src),
inter,
1.0,
limit,
&mut got,
);
let silu = |g: f32| g / (1.0 + (-g).exp());
let want = [
silu(-50.0) * limit,
silu(limit) * -limit,
silu(1.0) * 1.0,
silu(-1.0) * -1.0,
];
for (i, w) in want.iter().enumerate() {
assert!(
(got[i] - w).abs() < 1e-5,
"lane {i}: got {} want {w}",
got[i]
);
}
let mut raw = vec![0.0f32; inter];
expert_swiglu(
&[0.0],
&|_, d| d.copy_from_slice(&gate_src),
&|_, d| d.copy_from_slice(&up_src),
&|src, d| d.copy_from_slice(src),
inter,
1.0,
0.0,
&mut raw,
);
assert!(
(raw[1] - silu(50.0) * -50.0).abs() < 1e-3,
"limit 0 must not clamp"
);
}
#[test]
fn grouped_projection_is_identical_with_and_without_a_pool() {
let (groups, lora, per_group, dim) = (4usize, 128usize, 64usize, 32usize);
let attn: Vec<f32> = (0..groups * per_group)
.map(|i| ((i * 13) as f32 * 0.021).sin())
.collect();
let wo_a: Vec<f32> = (0..groups * lora * per_group)
.map(|i| ((i * 7) as f32 * 0.011).cos())
.collect();
let wo_b: Vec<f32> = (0..dim * groups * lora)
.map(|i| ((i * 5) as f32 * 0.009).sin())
.collect();
let row = |r: usize, x: &[f32], _sc: &mut [f32]| -> f32 {
wo_a[r * per_group..(r + 1) * per_group]
.iter()
.zip(x)
.map(|(a, b)| a * b)
.sum()
};
let project = |mid: &[f32], dst: &mut [f32]| {
for (d, o) in dst.iter_mut().enumerate() {
*o = wo_b[d * mid.len()..(d + 1) * mid.len()]
.iter()
.zip(mid)
.map(|(a, b)| a * b)
.sum();
}
};
let mut serial = vec![0.0f32; dim];
o_project(
&attn,
&row,
per_group,
&project,
groups,
lora,
None,
&mut serial,
);
let pool = crate::pool::Pool::new(4);
let mut pooled = vec![0.0f32; dim];
o_project(
&attn,
&row,
per_group,
&project,
groups,
lora,
Some(&pool),
&mut pooled,
);
assert_eq!(serial, pooled, "the pooled projection diverged");
assert!(
serial.iter().any(|v| v.abs() > 1e-6),
"test data is degenerate"
);
}
#[test]
fn block_grouped_projection_matches_position_walk() {
let (_g, layers, cfg) = toy();
let l = &layers[1];
let b = 5;
let attn_len = cfg.n_heads * cfg.head_dim;
let attn: Vec<f32> = (0..b * attn_len)
.map(|i| ((i * 17) as f32 * 0.013).sin())
.collect();
let mut walked = vec![0.0f32; b * cfg.dim];
for bi in 0..b {
o_project(
&attn[bi * attn_len..(bi + 1) * attn_len],
&|r, x, sc| l.wo_a.row_dot(r, x, sc),
l.wo_a.cols(),
&|mid, dst| l.wo_b.matvec(mid, dst, None),
cfg.o_groups,
cfg.o_lora_rank,
None,
&mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
);
}
let mut batched = vec![0.0f32; b * cfg.dim];
o_project_block(
&attn,
b,
&l.wo_a,
&l.wo_b,
cfg.o_groups,
cfg.o_lora_rank,
None,
&mut batched,
);
assert_eq!(batched, walked);
}
#[test]
fn block_moe_matches_position_walk_in_route_order() {
let (_g, layers, cfg) = toy();
let l = &layers[1];
let b = 5;
let xs: Vec<f32> = (0..b * cfg.dim)
.map(|i| ((i * 11) as f32 * 0.019).cos())
.collect();
let ids = [1u32, 2, 3, 4, 5];
let mut walked = vec![0.0f32; b * cfg.dim];
for bi in 0..b {
moe_step(
&xs[bi * cfg.dim..(bi + 1) * cfg.dim],
l,
&cfg,
ids[bi],
1,
None,
&mut walked[bi * cfg.dim..(bi + 1) * cfg.dim],
);
}
let mut batched = vec![0.0f32; b * cfg.dim];
moe_step_block(&xs, b, l, &cfg, &ids, 1, None, &mut batched);
assert_eq!(batched, walked);
}
#[test]
fn overlapping_compressor_folds_both_windows() {
let (ratio, d) = (2usize, 3usize);
let cur_kv: Vec<f32> = vec![
1.0, 1.0, 1.0, 10.0, 20.0, 30.0, 2.0, 2.0, 2.0, 40.0, 50.0, 60.0, ];
let cur_sc: Vec<f32> = vec![
0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0,
];
let prev_kv: Vec<f32> = vec![
7.0, 8.0, 9.0, 0.0, 0.0, 0.0, 5.0, 6.0, 7.0, 0.0, 0.0, 0.0,
];
let prev_sc = vec![0.0f32; ratio * 2 * d];
let mut out = vec![0.0f32; d];
compress_window_overlap(&prev_kv, &prev_sc, &cur_kv, &cur_sc, ratio, d, &mut out);
assert!((out[0] - 40.0).abs() < 1e-3, "dim0 = {}", out[0]);
assert!((out[1] - 50.0).abs() < 1e-3, "dim1 = {}", out[1]);
assert!((out[2] - 30.0).abs() < 1e-3, "dim2 = {}", out[2]);
let mut first = vec![0.0f32; d];
compress_window_overlap(&[], &[], &cur_kv, &cur_sc, ratio, d, &mut first);
assert!(
first.iter().all(|v| v.is_finite()),
"first window: {first:?}"
);
assert!((first[0] - 40.0).abs() < 1e-3, "first dim0 = {}", first[0]);
let mut both = vec![0.0f32; d];
let strong_prev = vec![100.0f32; ratio * 2 * d];
compress_window_overlap(
&prev_kv,
&strong_prev,
&cur_kv,
&cur_sc,
ratio,
d,
&mut both,
);
assert!(
(both[0] - 40.0).abs() > 1.0,
"a scored previous window must move the fold, got {}",
both[0]
);
}
#[test]
fn sinkhorn_matches_the_reference_numbers() {
let hc = 4;
let mixes: Vec<f32> = (0..24).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
let base: Vec<f32> = (0..24).map(|i| (i as f32 * 0.11).cos()).collect();
let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
hc_split_sinkhorn(
&mixes,
&[1.0, 1.0, 1.0],
&base,
hc,
20,
1e-6,
&mut pre,
&mut post,
&mut comb,
);
let want_pre = [0.7310596, 0.8888268, 0.9525191, 0.97424865];
let want_post = [1.9600224, 1.9534285, 1.9201256, 1.8160983];
let want_comb = [
0.5996052,
0.28253591,
0.09218107,
0.025676856,
0.17564717,
0.22228767,
0.27174541,
0.33031881,
0.029528176,
0.12206022,
0.32619134,
0.5222193,
0.19521846,
0.37311527,
0.30988118,
0.12178412,
];
for (i, w) in want_pre.iter().enumerate() {
assert!((pre[i] - w).abs() < 1e-5, "pre[{i}]: {} vs {w}", pre[i]);
}
for (i, w) in want_post.iter().enumerate() {
assert!((post[i] - w).abs() < 1e-5, "post[{i}]: {} vs {w}", post[i]);
}
for (i, w) in want_comb.iter().enumerate() {
assert!((comb[i] - w).abs() < 1e-4, "comb[{i}]: {} vs {w}", comb[i]);
}
}
#[test]
fn sinkhorn_leaves_the_mixing_matrix_doubly_stochastic() {
let hc = 4;
let mix_hc = (2 + hc) * hc;
let mixes: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.37).sin() * 3.0).collect();
let base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.11).cos()).collect();
let (mut pre, mut post, mut comb) = (vec![0.0; hc], vec![0.0; hc], vec![0.0; hc * hc]);
hc_split_sinkhorn(
&mixes,
&[1.0, 1.0, 1.0],
&base,
hc,
20,
1e-6,
&mut pre,
&mut post,
&mut comb,
);
for j in 0..hc {
let r: f32 = comb[j * hc..(j + 1) * hc].iter().sum();
assert!((r - 1.0).abs() < 2e-3, "row {j} sums to {r}");
let c: f32 = (0..hc).map(|k| comb[k * hc + j]).sum();
assert!((c - 1.0).abs() < 2e-3, "col {j} sums to {c}");
}
assert!(pre.iter().all(|&v| v > 0.0 && v < 1.001));
assert!(post.iter().all(|&v| v >= 0.0 && v <= 2.0));
}
#[test]
fn expand_of_identical_copies_is_a_fixed_point() {
let (hc, dim) = (4usize, 3usize);
let residual: Vec<f32> = std::iter::repeat([1.5f32, -2.0, 0.25])
.take(hc)
.flatten()
.collect();
let comb = {
vec![0.25f32; hc * hc]
};
let post = vec![0.0f32; hc];
let mut out = vec![0.0f32; hc * dim];
hc_expand(&[0.0; 3], &residual, &post, &comb, hc, dim, &mut out);
for (o, r) in out.iter().zip(&residual) {
assert!((o - r).abs() < 1e-6, "{o} vs {r}");
}
}
#[test]
fn selection_bias_steers_the_choice_but_not_the_weights() {
let scores = [3.0f32, 0.1, 2.0, 0.05];
let bias = [0.0f32, 10.0, 0.0, 0.0];
let (mut idx, mut w) = (Vec::new(), Vec::new());
route(&scores, Some(&bias), 2, 1.5, None, None, &mut idx, &mut w);
assert_eq!(idx[0], 1, "the biased expert must win selection");
assert_eq!(idx[1], 0);
assert!(w[0] < w[1], "biased expert kept its own (small) weight");
let sum: f32 = w.iter().sum();
assert!((sum - 1.5).abs() < 1e-5, "weights renormalize then scale");
}
#[test]
fn attention_sink_drains_weight_without_contributing_output() {
let hd = 2;
let q = [1.0f32, 0.0];
let kv = [1.0f32, 0.0, 0.0, 1.0];
let mut out = vec![0.0f32; hd];
sparse_attend(&q, &kv, &[0, 1], f32::NEG_INFINITY, 1.0, hd, &mut out);
let plain = out.clone();
assert!(plain[0] > plain[1], "the aligned key must dominate");
sparse_attend(&q, &kv, &[0, 1], 20.0, 1.0, hd, &mut out);
assert!(
out[0] < plain[0] * 0.01 && out[1] < plain[1] * 0.01,
"a large sink must drain nearly all the mass: {out:?}"
);
}
#[test]
fn masked_positions_leave_the_denominator_alone() {
let hd = 2;
let q = [1.0f32, 0.0];
let kv = [1.0f32, 0.0, 0.0, 1.0];
let (mut a, mut b) = (vec![0.0f32; hd], vec![0.0f32; hd]);
sparse_attend(&q, &kv, &[0], f32::NEG_INFINITY, 1.0, hd, &mut a);
sparse_attend(
&q,
&kv,
&[0, usize::MAX],
f32::NEG_INFINITY,
1.0,
hd,
&mut b,
);
for (x, y) in a.iter().zip(&b) {
assert!((x - y).abs() < 1e-6, "{x} vs {y}");
}
}
#[test]
fn rope_tail_inverts_itself() {
let inv_freq = [1.0f32, 0.5];
let orig = [9.0f32, 8.0, 1.0, 2.0, 3.0, 4.0];
let mut v = orig;
rope_tail(&mut v, &inv_freq, 7, 4, false);
assert!(v[..2] == orig[..2], "the non-rope head must not move");
assert!(v[2..] != orig[2..], "the tail must actually rotate");
rope_tail(&mut v, &inv_freq, 7, 4, true);
for (a, b) in v.iter().zip(&orig) {
assert!((a - b).abs() < 1e-5, "{a} vs {b}");
}
}
#[test]
fn compressor_pools_the_window_per_dimension() {
let (ratio, width) = (2usize, 2usize);
let kv = [1.0f32, 10.0, 3.0, 20.0];
let score = [0.0f32, 0.0, 0.0, 50.0];
let ape = vec![0.0f32; ratio * width];
let mut out = vec![0.0f32; width];
compress_window(&kv, &score, &ape, ratio, width, &mut out);
assert!(
(out[0] - 2.0).abs() < 1e-5,
"equal scores average: {}",
out[0]
);
assert!(
(out[1] - 20.0).abs() < 1e-3,
"a dominant score wins: {}",
out[1]
);
}
#[test]
fn index_scores_relu_before_weighting() {
let (nh, hd) = (2usize, 2usize);
let q = [1.0f32, 0.0, -1.0, 0.0];
let kv = [1.0f32, 0.0, 0.0, 1.0];
let w = [1.0f32, 1.0];
let mut sc = Vec::new();
index_scores(&q, &kv, &w, nh, hd, 2, 2, None, &mut sc);
assert!(sc[0] > 0.9, "abstention, not veto: {:?}", sc);
}
#[test]
fn index_scores_mask_the_future() {
let (nh, hd) = (1usize, 2usize);
let q = [1.0f32, 0.0];
let kv = [1.0f32, 0.0, 1.0, 0.0, 1.0, 0.0];
let w = [1.0f32];
let mut sc = Vec::new();
index_scores(&q, &kv, &w, nh, hd, 3, 2, None, &mut sc);
assert!(sc[0].is_finite() && sc[1].is_finite());
assert!(sc[2] == f32::NEG_INFINITY, "position 2 is in the future");
let mut idx = Vec::new();
top_k_positions(&sc, 3, &mut idx);
assert_eq!(idx, vec![0, 1], "a masked slot never wins a slot");
}
#[test]
fn top_k_is_deterministic_on_ties() {
let sc = [1.0f32, 1.0, 1.0, 0.0];
let mut idx = Vec::new();
top_k_positions(&sc, 2, &mut idx);
assert_eq!(idx, vec![0, 1], "ties resolve to the lower index");
}
#[test]
fn hc_block_preserves_the_copy_structure_and_applies_the_block() {
let cfg = Dsv4Cfg {
dim: 4,
n_heads: 1,
head_dim: 4,
rope_head_dim: 2,
q_lora_rank: 4,
o_lora_rank: 2,
o_groups: 1,
hc_mult: 4,
hc_sinkhorn_iters: 20,
hc_eps: 1e-6,
norm_eps: 1e-6,
n_routed_experts: 2,
top_k: 1,
moe_inter: 4,
route_scale: 1.0,
swiglu_limit: 10.0,
window: 128,
index_topk: 4,
vocab: 8,
};
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let mix_hc = (2 + hc) * hc;
let hc_fn: Vec<f32> = (0..mix_hc * hc * dim)
.map(|i| ((i % 13) as f32 - 6.0) * 0.05)
.collect();
let hc_base: Vec<f32> = (0..mix_hc).map(|i| (i as f32 * 0.2).sin()).collect();
let norm_w = vec![1.0f32; dim];
let mut state: Vec<f32> = (0..hc * dim).map(|i| (i as f32 * 0.3).cos()).collect();
let before = state.clone();
let mut scratch = HcScratch::new(&cfg);
hc_block(
&mut state,
&hc_fn,
&[1.0, 1.0, 1.0],
&hc_base,
&norm_w,
&cfg,
&mut scratch,
None,
|_folded, out: &mut [f32]| out.iter_mut().for_each(|o| *o = 1.0),
);
assert_eq!(state.len(), before.len(), "copy structure must survive");
assert!(state.iter().all(|v| v.is_finite()), "{state:?}");
assert!(
state.iter().zip(&before).any(|(a, b)| (a - b).abs() > 1e-4),
"the block's output has to reach the state"
);
}
#[test]
fn hash_route_reads_the_table_row() {
let table = [7.0f32, 9.0, 1.0, 2.0, 5.0, 6.0];
assert_eq!(hash_route(&table, 3, 2, 0), vec![7, 9]);
assert_eq!(hash_route(&table, 3, 2, 2), vec![5, 6]);
assert_eq!(hash_route(&table, 3, 2, 99), vec![5, 6]);
}
#[test]
fn a_task_mask_restricts_selection_and_renormalizes() {
let scores = [0.1f32, 4.0, 1.0, 9.0];
let (mut idx, mut w) = (Vec::new(), Vec::new());
route(&scores, None, 2, 1.0, None, None, &mut idx, &mut w);
assert_eq!(idx, vec![3, 1], "unmasked: the two best win");
let sum: f32 = w.iter().sum();
assert!((sum - 1.0).abs() < 1e-5, "weights must sum to route_scale");
let mask = [true, false, true, true];
let (mut i2, mut w2) = (Vec::new(), Vec::new());
route(&scores, None, 2, 1.0, None, Some(&mask), &mut i2, &mut w2);
assert_eq!(i2, vec![3, 2], "masked expert must not be selected");
let sum2: f32 = w2.iter().sum();
assert!((sum2 - 1.0).abs() < 1e-5, "masked weights must renormalize");
let tight = [false, false, false, true];
let (mut i3, mut w3) = (Vec::new(), Vec::new());
route(&scores, None, 2, 1.0, None, Some(&tight), &mut i3, &mut w3);
assert_eq!(i3, vec![3]);
assert_eq!(w3.len(), 1);
}
#[test]
fn hash_layers_weight_the_experts_the_table_names() {
let scores = [0.1f32, 0.4, 0.2, 5.0];
let table = vec![0.0f32, 1.0];
let idx_forced = hash_route(&table, 1, 2, 0);
assert_eq!(idx_forced, vec![0, 1]);
let (mut idx, mut w) = (Vec::new(), Vec::new());
route(
&scores,
None,
2,
1.0,
Some(&idx_forced),
None,
&mut idx,
&mut w,
);
assert_eq!(idx, vec![0, 1], "the table must decide the experts");
let sp = |x: f32| (1.0 + x.exp()).ln().sqrt();
let (s0, s1) = (sp(scores[0]), sp(scores[1]));
let tot = s0 + s1;
assert!(
(w[0] - s0 / tot).abs() < 1e-6,
"w[0]={} want {}",
w[0],
s0 / tot
);
assert!(
(w[1] - s1 / tot).abs() < 1e-6,
"w[1]={} want {}",
w[1],
s1 / tot
);
let (mut idx2, mut w2) = (Vec::new(), Vec::new());
route(&scores, None, 2, 1.0, None, None, &mut idx2, &mut w2);
assert_eq!(idx2[0], 3, "without a table the highest score still wins");
}
}
pub const DSPARK_NOISE_TOKEN: u32 = 128799;
pub fn dspark_block() -> usize {
5
}
pub fn dspark_verify_k() -> usize {
static K: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*K.get_or_init(|| {
std::env::var("CMF_DSPARK_VERIFY_K")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.filter(|&n| (1..=DSPARK_BLOCK_MAX).contains(&n))
.unwrap_or(DSPARK_BLOCK_MAX)
})
}
pub const DSPARK_BLOCK_MAX: usize = 5;
pub struct DsparkState {
pub win: Vec<Vec<f32>>,
pub filled: Vec<usize>,
pub main_hidden: Vec<f32>,
pub have_hidden: bool,
}
impl DsparkState {
pub fn new(stages: usize, cfg: &Dsv4Cfg, targets: usize) -> Self {
Self {
win: vec![Vec::new(); stages],
filled: vec![0; stages],
main_hidden: vec![0.0; cfg.dim * targets],
have_hidden: false,
}
}
}
pub fn dspark_targets(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, n_layers: usize) -> Vec<usize> {
let Some(mp) = mtp.iter().find_map(|m| m.main_proj.as_ref()) else {
return Vec::new();
};
let n = (mp.cols() / cfg.dim.max(1)).clamp(1, n_layers);
(n_layers - n..n_layers).collect()
}
thread_local! {
static DSPARK_CAP: std::cell::RefCell<(Vec<usize>, Vec<f32>, usize)> =
const { std::cell::RefCell::new((Vec::new(), Vec::new(), 0)) };
}
pub fn dspark_arm(targets: &[usize], dim: usize) {
DSPARK_CAP.with(|c| {
let mut c = c.borrow_mut();
c.0 = targets.to_vec();
c.1 = vec![0.0; dim * targets.len()];
c.2 = 0;
});
}
fn dspark_wants(li: usize) -> bool {
DSPARK_CAP.with(|c| c.borrow().0.contains(&li))
}
pub fn dspark_note(li: usize, state: &[f32], cfg: &Dsv4Cfg) {
DSPARK_CAP.with(|c| {
let mut c = c.borrow_mut();
if c.0.is_empty() {
return;
}
if let Some(slot) = c.0.iter().position(|&t| t == li) {
let (_, buf, seen) = &mut *c;
dspark_capture(state, cfg, slot, buf);
*seen = if slot == 0 { 1 } else { *seen + 1 };
if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
eprintln!("[cap] note li={li} slot={slot} seen={}", *seen);
}
}
});
}
pub fn dspark_peek_slot(slot: usize, dim: usize, out: &mut [f32]) -> bool {
DSPARK_CAP.with(|c| {
let c = c.borrow();
let lo = slot * dim;
if c.1.len() < lo + dim {
return false;
}
out[..dim].copy_from_slice(&c.1[lo..lo + dim]);
true
})
}
pub fn dspark_take(out: &mut Vec<f32>) -> bool {
DSPARK_CAP.with(|c| {
let mut c = c.borrow_mut();
if c.0.is_empty() || c.2 != c.0.len() {
if std::env::var("CMF_DSPARK_CAP_DBG").is_ok() {
eprintln!("[cap] take FAIL armed={:?} seen={}", c.0, c.2);
}
return false;
}
out.clear();
out.extend_from_slice(&c.1);
c.2 = 0;
true
})
}
pub fn dspark_capture(state: &[f32], cfg: &Dsv4Cfg, slot: usize, out: &mut [f32]) {
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let dst = &mut out[slot * dim..(slot + 1) * dim];
let inv = 1.0 / hc as f32;
for d in 0..dim {
let mut s = 0.0;
for j in 0..hc {
s += state[j * dim + d];
}
dst[d] = s * inv;
}
}
pub fn dspark_freq_note(picks: &[(usize, Vec<usize>)]) {
static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
std::sync::Mutex::new(None);
let Ok(path) = std::env::var("CMF_DSPARK_PICK_DUMP") else {
return;
};
let mut g = FREQ.lock().unwrap();
let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
for (stage, idx) in picks {
for &e in idx {
*map.entry((*stage, e)).or_insert(0) += 1;
}
}
*blocks += 1;
if *blocks % 32 == 0 {
let mut lines: Vec<_> = map.iter().collect();
lines.sort();
let body: String = lines
.iter()
.map(|((s, e), n)| format!("{s}\t{e}\t{n}\n"))
.collect();
let _ = std::fs::write(&path, body);
}
}
pub fn trunk_freq_note(picks: &[(usize, Vec<usize>)]) {
static FREQ: std::sync::Mutex<Option<(std::collections::HashMap<(usize, usize), u64>, u64)>> =
std::sync::Mutex::new(None);
let Ok(path) = std::env::var("CMF_DSV4_TRUNK_PICK_DUMP") else {
return;
};
let mut g = FREQ.lock().unwrap();
let (map, blocks) = g.get_or_insert_with(|| (std::collections::HashMap::new(), 0));
for (li, idx) in picks {
for &e in idx {
*map.entry((*li, e)).or_insert(0) += 1;
}
}
*blocks += 1;
if *blocks % 32 == 0 {
let mut lines: Vec<_> = map.iter().collect();
lines.sort();
let body: String = lines
.iter()
.map(|((l, e), n)| format!("{l}\t{e}\t{n}\n"))
.collect();
let _ = std::fs::write(&path, body);
}
}
fn dspark_apply_mask(out: &mut [Dsv4Mtp]) {
let Ok(path) = std::env::var("CMF_DSPARK_MASK") else {
return;
};
let Ok(text) = std::fs::read_to_string(&path) else {
eprintln!("DSpark: CMF_DSPARK_MASK={path} не читается — маска не применена");
return;
};
for (d, line) in text.lines().enumerate() {
let Some(m) = out.get_mut(d) else { break };
let n = m.layer.experts.len();
let mut mask = vec![false; n];
let mut kept = 0usize;
for tok in line.split(',') {
if let Ok(e) = tok.trim().parse::<usize>() {
if e < n && !mask[e] {
mask[e] = true;
kept += 1;
}
}
}
if kept == 0 {
continue;
}
eprintln!("DSpark: стадия {d} ограничена {kept}/{n} экспертами");
m.layer.mask = Some(mask);
}
}
#[cfg(feature = "gpu")]
pub struct DsparkPack {
pub stages: Vec<DsparkStagePack>,
pub gu_q2: bool,
pub dn_q2: bool,
pub routers: Vec<Vec<f32>>,
pub biases: Vec<Option<Vec<f32>>>,
}
#[cfg(feature = "gpu")]
pub struct DsparkStagePack {
pub mask: Vec<bool>,
pub to_slot: Vec<usize>,
pub mask_u32: Vec<u32>,
pub map_u32: Vec<u32>,
pub tensors: Vec<(usize, usize, usize)>,
pub n_resident: usize,
}
pub static DSPARK_Q2TP_ENCODE: std::sync::OnceLock<fn(&[f32], usize, usize) -> Vec<u8>> =
std::sync::OnceLock::new();
#[cfg(feature = "gpu")]
pub fn dspark_gpu_on() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSPARK_GPU").map(|v| v != "0").unwrap_or(true))
}
#[cfg(feature = "gpu")]
pub fn dspark_pack_get(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<&'static DsparkPack> {
static P: std::sync::OnceLock<Option<Box<DsparkPack>>> = std::sync::OnceLock::new();
P.get_or_init(|| dspark_pack_build(mtp, cfg).map(Box::new)).as_deref()
}
#[cfg(feature = "gpu")]
pub fn dspark_reserve_note(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg, layers: &[Dsv4Layer]) {
if mtp.is_empty()
|| std::env::var("CMF_DSV4_SPEC").is_ok_and(|v| v == "0")
|| !dspark_gpu_on()
{
return;
}
let dt = |q2: bool| {
if q2 {
cortiq_core::TensorDtype::Q2TiledP
} else {
cortiq_core::TensorDtype::Q4TiledP
}
};
let gu_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
});
let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
});
let gu = cortiq_core::quant::expected_nbytes(dt(gu_q2), &[cfg.moe_inter, cfg.dim])
.unwrap_or(0);
let dn = cortiq_core::quant::expected_nbytes(dt(dn_q2), &[cfg.dim, cfg.moe_inter])
.unwrap_or(0);
let per = (2 * gu + dn) as u64;
let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(40);
let bytes = per * (n_res * mtp.len() + mtp.len() + 1) as u64;
let trunk: u64 = layers
.iter()
.map(|l| {
let Some(e) = l.experts.first() else { return 0 };
let gu = cortiq_core::quant::expected_nbytes(
dt(e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
&[cfg.moe_inter, cfg.dim],
)
.unwrap_or(0);
let dn = cortiq_core::quant::expected_nbytes(
dt(e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)),
&[cfg.dim, cfg.moe_inter],
)
.unwrap_or(0);
((2 * gu + dn) * (l.experts.len() + 1)) as u64
})
.sum();
if let Some(budget) = crate::gpu_wgpu::dsv4_vram_budget() {
if budget < trunk / 10 * 9 + bytes {
return;
}
}
crate::gpu_wgpu::DRAFT_RESERVE.store(bytes, std::sync::atomic::Ordering::Relaxed);
}
#[cfg(not(feature = "gpu"))]
pub fn dspark_reserve_note(_mtp: &[Dsv4Mtp], _cfg: &Dsv4Cfg, _layers: &[Dsv4Layer]) {}
#[cfg(feature = "gpu")]
pub fn dspark_pack_build(mtp: &[Dsv4Mtp], cfg: &Dsv4Cfg) -> Option<DsparkPack> {
if mtp.is_empty() {
return None;
}
let n_res: usize = std::env::var("CMF_DSPARK_RESIDENT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or_else(|| {
let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
});
let gu_q2 = native_q2 || DSPARK_Q2TP_ENCODE.get().is_some();
let dn_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
});
let room = crate::gpu_wgpu::dsv4_draft_fit(cfg.moe_inter, cfg.dim, gu_q2, dn_q2);
(room.saturating_sub(mtp.len() + 1) / mtp.len().max(1)).clamp(8, 64)
});
let mut freq: Vec<Vec<(u64, usize)>> = vec![Vec::new(); mtp.len()];
let pack_path = std::env::var("CMF_DSPARK_PACK").ok().or_else(|| {
let m = mtp[0].layer.experts.first()?.w1.model_arc()?;
let mut s = m.path.as_os_str().to_os_string();
s.push(".dspark.tsv");
let p = std::path::PathBuf::from(s);
p.exists().then(|| p.to_string_lossy().into_owned())
});
if let Some(path) = pack_path {
if let Ok(text) = std::fs::read_to_string(&path) {
for line in text.lines() {
let mut it = line.split_whitespace();
if let (Some(s), Some(e), Some(n)) = (it.next(), it.next(), it.next()) {
if let (Ok(s), Ok(e), Ok(n)) =
(s.parse::<usize>(), e.parse::<usize>(), n.parse::<u64>())
{
if s < freq.len() {
freq[s].push((n, e));
}
}
}
}
}
}
let mut stages = Vec::with_capacity(mtp.len());
let mut routers = Vec::with_capacity(mtp.len());
let mut biases = Vec::with_capacity(mtp.len());
for (si, m) in mtp.iter().enumerate() {
let l = &m.layer;
let n = l.experts.len();
let mut order: Vec<usize> = {
let mut f = freq[si].clone();
f.sort_by(|a, b| b.0.cmp(&a.0));
let mut seen = vec![false; n];
let mut o: Vec<usize> = f
.into_iter()
.map(|(_, e)| e)
.filter(|&e| {
if e < n && !seen[e] {
seen[e] = true;
true
} else {
false
}
})
.collect();
o.extend((0..n).filter(|&e| !seen[e]));
o
};
order.truncate(n_res.min(n));
let mut mask = vec![false; n];
let mut to_slot = vec![usize::MAX; n];
let mut tensors = Vec::with_capacity(order.len() + 1);
for (slot, &e) in order.iter().enumerate() {
let ex = &l.experts[e];
let (Some(w1), Some(w3), Some(w2)) =
(ex.w1.model_idx(), ex.w3.model_idx(), ex.w2.model_idx())
else {
return None;
};
mask[e] = true;
to_slot[e] = slot;
tensors.push((w1, w3, w2));
}
let (Some(s1), Some(s3), Some(s2)) = (
l.shared.w1.model_idx(),
l.shared.w3.model_idx(),
l.shared.w2.model_idx(),
) else {
return None;
};
tensors.push((s1, s3, s2));
let mut router = vec![0.0f32; n * cfg.dim];
for (r, row) in (0..n).zip(router.chunks_mut(cfg.dim)) {
l.gate.row_f32(r, row);
}
routers.push(router);
biases.push(l.gate_bias.clone());
let mask_u32: Vec<u32> = mask.iter().map(|&m| m as u32).collect();
let map_u32: Vec<u32> = to_slot
.iter()
.map(|&x| if x == usize::MAX { u32::MAX } else { x as u32 })
.collect();
stages.push(DsparkStagePack {
mask,
to_slot,
mask_u32,
map_u32,
tensors,
n_resident: order.len(),
});
}
let model = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc())?;
let mut skeleton = Vec::new();
for m in mtp {
let l = &m.layer;
for t in [&l.wq_a, &l.wq_b, &l.wkv, &l.wo_a, &l.wo_b] {
skeleton.push(t.model_idx()?);
}
}
if let Some(mp) = mtp[0].main_proj.as_ref() {
skeleton.push(mp.model_idx()?);
}
for &idx in &skeleton {
if !crate::gpu_wgpu::dsv4_weight_ready(&model, idx) {
eprintln!("DSpark: скелет драфта не влез в VRAM — GPU-черновик выключен");
return None;
}
}
let native_q2 = mtp[0].layer.experts.first().is_some_and(|e| {
e.w1.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
});
let gu_q2 = native_q2 || crate::dsv4::DSPARK_Q2TP_ENCODE.get().is_some();
let dn_native = mtp[0].layer.experts.first().is_some_and(|e| {
e.w2.model_dtype() == Some(cortiq_core::TensorDtype::Q2TiledP)
});
for (si, sp) in stages.iter().enumerate() {
let ok = if native_q2 {
crate::gpu_wgpu::dsv4_experts_ready(
&model, &sp.tensors, cfg.moe_inter, cfg.dim, true, dn_native,
)
} else if gu_q2 {
crate::gpu_wgpu::moe_expert_bufs_requant_gu(
&model,
&sp.tensors,
cfg.moe_inter,
cfg.dim,
)
.is_some()
} else {
crate::gpu_wgpu::dsv4_experts_ready(
&model, &sp.tensors, cfg.moe_inter, cfg.dim, false, false,
)
};
if !ok {
eprintln!(
"DSpark: эксперты стадии {si} ({} + shared) не влезли в VRAM — GPU-черновик выключен",
sp.n_resident
);
return None;
}
}
let _ = crate::gpu_wgpu::pin_weights(&model, &skeleton);
eprintln!(
"DSpark: пак драфта на карте — {} стадии по {} экспертов + shared",
stages.len(),
stages.iter().map(|s| s.n_resident.to_string()).collect::<Vec<_>>().join("/")
);
Some(DsparkPack {
stages,
gu_q2,
dn_q2: dn_native,
routers,
biases,
})
}
pub fn dspark_ring_append(
g: &Dsv4Globals,
mtp: &[Dsv4Mtp],
cfg: &Dsv4Cfg,
ds: &mut DsparkState,
pos: usize,
pool: Option<&crate::pool::Pool>,
) {
let (dim, hd, rd) = (cfg.dim, cfg.head_dim, cfg.rope_head_dim);
let inv_freq = &g.inv_freq_window;
let Some(stage0) = mtp.first() else { return };
let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
return;
};
let mut main_x = vec![0.0f32; dim];
mp.matvec(&ds.main_hidden, &mut main_x, pool);
rms_weighted(&mut main_x, mn, cfg.norm_eps);
for (si, m) in mtp.iter().enumerate() {
let kvw = m.layer.wkv.rows();
if ds.win[si].len() < cfg.window * kvw {
ds.win[si].resize(cfg.window * kvw, 0.0);
}
let mut kv = vec![0.0f32; kvw];
m.layer.wkv.matvec(&main_x, &mut kv, pool);
rms_weighted(&mut kv, &m.layer.kv_norm, cfg.norm_eps);
rope_tail(&mut kv[kvw - hd..], inv_freq, pos, rd, false);
let slot = pos % cfg.window;
ds.win[si][slot * kvw..(slot + 1) * kvw].copy_from_slice(&kv);
ds.filled[si] = (pos + 1).min(cfg.window);
}
}
#[cfg(feature = "gpu")]
#[allow(clippy::too_many_arguments)]
pub fn dspark_draft_gpu(
g: &Dsv4Globals,
mtp: &[Dsv4Mtp],
cfg: &Dsv4Cfg,
ds: &mut DsparkState,
pack: &DsparkPack,
kv_id: u64,
last_token: u32,
pos: usize,
pool: Option<&crate::pool::Pool>,
out_conf: &mut Vec<f32>,
) -> Vec<u32> {
let (hc, dim) = (cfg.hc_mult, cfg.dim);
let block = dspark_block();
let Some(model) = mtp[0].layer.experts.first().and_then(|e| e.w1.model_arc()) else {
return Vec::new();
};
let (Some(mp), Some(mn)) = (mtp[0].main_proj.as_ref(), mtp[0].main_norm.as_ref()) else {
return Vec::new();
};
let Some(mp_idx) = mp.model_idx() else {
return Vec::new();
};
let mut stages = Vec::with_capacity(mtp.len());
for (si, m) in mtp.iter().enumerate() {
let l = &m.layer;
let (Some(wq_a), Some(wq_b), Some(wo_a), Some(wo_b), Some(wkv)) = (
l.wq_a.model_idx(),
l.wq_b.model_idx(),
l.wo_a.model_idx(),
l.wo_b.model_idx(),
l.wkv.model_idx(),
) else {
return Vec::new();
};
let sp = &pack.stages[si];
stages.push(crate::gpu_wgpu::DsparkStageW {
wq_a,
wq_b,
wo_a,
wo_b,
wkv,
q_norm: &l.q_norm,
kv_norm: &l.kv_norm,
attn_norm: &l.attn_norm,
ffn_norm: &l.ffn_norm,
sink: &l.attn_sink,
hc_attn_fn: &l.hc_attn_fn,
hc_attn_scale: &l.hc_attn_scale,
hc_attn_base: &l.hc_attn_base,
hc_ffn_fn: &l.hc_ffn_fn,
hc_ffn_scale: &l.hc_ffn_scale,
hc_ffn_base: &l.hc_ffn_base,
router: &pack.routers[si],
bias: pack.biases[si].as_deref(),
experts: &sp.tensors,
mask_u32: &sp.mask_u32,
map_u32: &sp.map_u32,
});
}
let geom = crate::gpu_wgpu::DsparkGeom {
dim,
hc,
nh: cfg.n_heads,
hd: cfg.head_dim,
rd: cfg.rope_head_dim,
q_lora: cfg.q_lora_rank,
o_lora: cfg.o_lora_rank,
o_groups: cfg.o_groups,
inter: cfg.moe_inter,
n_experts: cfg.n_routed_experts,
top_k: cfg.top_k,
window: cfg.window,
eps: cfg.norm_eps,
hc_eps: cfg.hc_eps,
sinkhorn_iters: cfg.hc_sinkhorn_iters,
route_scale: cfg.route_scale,
swiglu_limit: cfg.swiglu_limit,
scale: (cfg.head_dim as f32).powf(-0.5),
gu_q2: pack.gu_q2,
dn_q2: pack.dn_q2,
};
let ids: Vec<u32> = (0..block)
.map(|i| if i == 0 { last_token } else { DSPARK_NOISE_TOKEN })
.collect();
let mut states0 = vec![0.0f32; block * hc * dim];
let mut emb = vec![0.0f32; dim];
for (i, &id) in ids.iter().enumerate() {
g.embed.row_f32(id as usize, &mut emb);
for j in 0..hc {
states0[(i * hc + j) * dim..(i * hc + j + 1) * dim].copy_from_slice(&emb);
}
}
let dspark_time = {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSPARK_TIME").is_ok_and(|v| v != "0"))
};
let t0 = std::time::Instant::now();
let filled = (pos + 1).min(cfg.window);
let mut states = vec![0.0f32; block * hc * dim];
if !crate::gpu_wgpu::dspark_graph(
&model,
&stages,
geom,
kv_id,
mp_idx,
mn,
&ds.main_hidden,
&states0,
pos,
filled,
&g.inv_freq_window,
block,
&mut states,
) {
return Vec::new();
}
for si in 0..mtp.len() {
ds.filled[si] = filled;
}
let t_graph = t0.elapsed();
let last = &mtp[mtp.len() - 1];
let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
last.hc_head_fn.as_ref(),
last.hc_head_base.as_ref(),
last.hc_head_scale,
last.norm.as_ref(),
) else {
return Vec::new();
};
let mut head_in = vec![0.0f32; block * dim];
let mut pre_norms = vec![vec![0.0f32; dim]; block];
for i in 0..block {
hc_head_fold(
&states[i * hc * dim..(i + 1) * hc * dim],
hfn,
hscale,
hbase,
cfg,
pool,
&mut head_in[i * dim..(i + 1) * dim],
);
pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
rms_weighted(&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps);
}
let t_fold = t0.elapsed();
let mut logits = vec![0.0f32; block * cfg.vocab];
let head_gpu = g.head.model_idx().is_some_and(|hi| {
crate::gpu_wgpu::q4tp_matvec_batch_for_test(
&model,
hi,
&head_in,
block,
cfg.vocab,
dim,
&mut logits,
)
});
if !head_gpu {
g.head.matmat(&head_in, block, &mut logits, pool);
}
let t_head = t0.elapsed();
let mut proposals = Vec::with_capacity(block);
out_conf.clear();
let mut prev = last_token;
let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
let mut bias = vec![0.0f32; cfg.vocab];
for i in 0..block {
let row = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
w1.row_f32(prev as usize, &mut mk_embed);
w2.matvec(&mk_embed, &mut bias, pool);
for (a, b) in row.iter_mut().zip(&bias) {
*a += *b;
}
}
let mut best = 0usize;
for v in 1..row.len() {
if row[v] > row[best] {
best = v;
}
}
if let Some(cf) = last.confidence.as_ref() {
let mut cat = pre_norms[i].clone();
cat.extend_from_slice(&mk_embed);
let mut sc = [0.0f32; 1];
if cat.len() == cf.cols() {
cf.matvec(&cat, &mut sc, pool);
}
out_conf.push(sc[0]);
}
proposals.push(best as u32);
prev = best as u32;
}
if dspark_time {
eprintln!(
"DSpark GPU: граф {:.1} мс, фолды {:.1}, голова {:.1}, марков+argmax {:.1}",
t_graph.as_secs_f64() * 1e3,
(t_fold - t_graph).as_secs_f64() * 1e3,
(t_head - t_fold).as_secs_f64() * 1e3,
(t0.elapsed() - t_head).as_secs_f64() * 1e3,
);
}
proposals
}
#[allow(clippy::too_many_arguments)]
pub fn dspark_draft(
g: &Dsv4Globals,
mtp: &[Dsv4Mtp],
cfg: &Dsv4Cfg,
ds: &mut DsparkState,
last_token: u32,
pos: usize,
pool: Option<&crate::pool::Pool>,
out_conf: &mut Vec<f32>,
) -> Vec<u32> {
let (hc, dim, hd, rd) = (cfg.hc_mult, cfg.dim, cfg.head_dim, cfg.rope_head_dim);
let block = dspark_block();
let inv_freq = &g.inv_freq_window;
let Some(stage0) = mtp.first() else {
return Vec::new();
};
let (Some(mp), Some(mn)) = (stage0.main_proj.as_ref(), stage0.main_norm.as_ref()) else {
return Vec::new();
};
dspark_ring_append(g, mtp, cfg, ds, pos, pool);
let ids: Vec<u32> = (0..block)
.map(|i| if i == 0 { last_token } else { DSPARK_NOISE_TOKEN })
.collect();
let mut states = vec![vec![0.0f32; hc * dim]; block];
let mut emb = vec![0.0f32; dim];
for (i, &id) in ids.iter().enumerate() {
g.embed.row_f32(id as usize, &mut emb);
for j in 0..hc {
states[i][j * dim..(j + 1) * dim].copy_from_slice(&emb);
}
}
let mut scratch = HcScratch::new(cfg);
for (si, m) in mtp.iter().enumerate() {
let l = &m.layer;
let kvw = l.wkv.rows();
let mut post = vec![vec![0.0f32; hc]; block];
let mut comb = vec![vec![0.0f32; hc * hc]; block];
let mut resid = vec![vec![0.0f32; hc * dim]; block];
let mut folded = vec![vec![0.0f32; dim]; block];
let mix_hc = (2 + hc) * hc;
for i in 0..block {
hc_mixes(
&states[i],
&l.hc_attn_fn,
mix_hc,
cfg.norm_eps,
pool,
&mut scratch.mixes,
);
hc_split_sinkhorn(
&scratch.mixes,
&l.hc_attn_scale,
&l.hc_attn_base,
hc,
cfg.hc_sinkhorn_iters,
cfg.hc_eps,
&mut scratch.pre,
&mut post[i],
&mut comb[i],
);
hc_fold(&states[i], &scratch.pre, hc, dim, &mut folded[i]);
rms_weighted(&mut folded[i], &l.attn_norm, cfg.norm_eps);
resid[i].copy_from_slice(&states[i]);
}
let folded_all: Vec<f32> = folded.iter().flatten().copied().collect();
let mut blk_kv = vec![0.0f32; block * kvw];
l.wkv.matmat(&folded_all, block, &mut blk_kv, pool);
for i in 0..block {
let dst = &mut blk_kv[i * kvw..(i + 1) * kvw];
rms_weighted(dst, &l.kv_norm, cfg.norm_eps);
rope_tail(&mut dst[kvw - hd..], inv_freq, pos + 1 + i, rd, false);
}
let win_len = ds.filled[si];
let mut cache = Vec::with_capacity((win_len + block) * hd);
for p in 0..win_len {
let e = &ds.win[si][p * kvw..(p + 1) * kvw];
cache.extend_from_slice(&e[kvw - hd..]);
}
for i in 0..block {
let e = &blk_kv[i * kvw..(i + 1) * kvw];
cache.extend_from_slice(&e[kvw - hd..]);
}
let idxs: Vec<usize> = (0..win_len + block).collect();
let scale = (hd as f32).powf(-0.5);
let qrank = l.wq_a.rows();
let qdim = cfg.n_heads * hd;
let mut qr = vec![0.0f32; block * qrank];
l.wq_a.matmat(&folded_all, block, &mut qr, pool);
for i in 0..block {
rms_weighted(
&mut qr[i * qrank..(i + 1) * qrank],
&l.q_norm,
cfg.norm_eps,
);
}
let mut q = vec![0.0f32; block * qdim];
l.wq_b.matmat(&qr, block, &mut q, pool);
let mut attn = vec![0.0f32; block * qdim];
for i in 0..block {
let qi = &mut q[i * qdim..(i + 1) * qdim];
let ai = &mut attn[i * qdim..(i + 1) * qdim];
let qpos = pos + 1 + i;
for h in 0..cfg.n_heads {
let head = &mut qi[h * hd..(h + 1) * hd];
rms_inplace(head, cfg.norm_eps);
rope_tail(head, inv_freq, qpos, rd, false);
}
for h in 0..cfg.n_heads {
let qh = &qi[h * hd..(h + 1) * hd];
let oh = &mut ai[h * hd..(h + 1) * hd];
sparse_attend(qh, &cache, &idxs, l.attn_sink[h], scale, hd, oh);
rope_tail(oh, inv_freq, qpos, rd, true);
}
}
let mut blk_out = vec![0.0f32; block * dim];
o_project_block(
&attn, block, &l.wo_a, &l.wo_b, cfg.o_groups, cfg.o_lora_rank, pool,
&mut blk_out,
);
for i in 0..block {
let mut next = vec![0.0f32; hc * dim];
hc_expand(
&blk_out[i * dim..(i + 1) * dim],
&resid[i], &post[i], &comb[i], hc, dim, &mut next,
);
states[i] = next;
}
let mut ffn_fold = vec![0.0f32; block * dim];
let mut ffn_post = vec![vec![0.0f32; hc]; block];
let mut ffn_comb = vec![vec![0.0f32; hc * hc]; block];
let mut ffn_resid = vec![vec![0.0f32; hc * dim]; block];
for i in 0..block {
hc_mixes(
&states[i],
&l.hc_ffn_fn,
mix_hc,
cfg.norm_eps,
pool,
&mut scratch.mixes,
);
hc_split_sinkhorn(
&scratch.mixes,
&l.hc_ffn_scale,
&l.hc_ffn_base,
hc,
cfg.hc_sinkhorn_iters,
cfg.hc_eps,
&mut scratch.pre,
&mut ffn_post[i],
&mut ffn_comb[i],
);
hc_fold(
&states[i], &scratch.pre, hc, dim,
&mut ffn_fold[i * dim..(i + 1) * dim],
);
rms_weighted(
&mut ffn_fold[i * dim..(i + 1) * dim],
&l.ffn_norm,
cfg.norm_eps,
);
ffn_resid[i].copy_from_slice(&states[i]);
}
let mut moe_out = vec![0.0f32; block * dim];
moe_step_block(
&ffn_fold, block, l, cfg, &ids, si, pool, &mut moe_out,
);
for i in 0..block {
let mut next = vec![0.0f32; hc * dim];
hc_expand(
&moe_out[i * dim..(i + 1) * dim],
&ffn_resid[i], &ffn_post[i], &ffn_comb[i], hc, dim, &mut next,
);
states[i] = next;
}
}
let last = &mtp[mtp.len() - 1];
let (Some(hfn), Some(hbase), Some(hscale), Some(hnorm)) = (
last.hc_head_fn.as_ref(),
last.hc_head_base.as_ref(),
last.hc_head_scale,
last.norm.as_ref(),
) else {
return Vec::new();
};
let mut proposals = Vec::with_capacity(block);
out_conf.clear();
let mut prev = last_token;
let mut head_in = vec![0.0f32; block * dim];
let mut pre_norms = vec![vec![0.0f32; dim]; block];
for i in 0..block {
hc_head_fold(
&states[i], hfn, hscale, hbase, cfg, pool,
&mut head_in[i * dim..(i + 1) * dim],
);
pre_norms[i].copy_from_slice(&head_in[i * dim..(i + 1) * dim]);
rms_weighted(
&mut head_in[i * dim..(i + 1) * dim], hnorm, cfg.norm_eps,
);
}
let mut logits = vec![0.0f32; block * cfg.vocab];
g.head.matmat(&head_in, block, &mut logits, pool);
let mut mk_embed = vec![0.0f32; last.markov_w1.as_ref().map_or(0, |t| t.cols())];
for i in 0..block {
let logits_i = &mut logits[i * cfg.vocab..(i + 1) * cfg.vocab];
let no_markov = {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::env::var("CMF_DSPARK_NO_MARKOV").is_ok_and(|v| v != "0"))
};
if no_markov {
if let Some(w1) = last.markov_w1.as_ref() {
w1.row_f32(prev as usize, &mut mk_embed);
}
} else if let (Some(w1), Some(w2)) = (last.markov_w1.as_ref(), last.markov_w2.as_ref()) {
w1.row_f32(prev as usize, &mut mk_embed);
let mut bias = vec![0.0f32; cfg.vocab];
w2.matvec(&mk_embed, &mut bias, pool);
for (a, b) in logits_i.iter_mut().zip(&bias) {
*a += *b;
}
}
let mut best = 0usize;
for v in 1..logits_i.len() {
if logits_i[v] > logits_i[best] {
best = v;
}
}
if let Some(cf) = last.confidence.as_ref() {
let mut cat = pre_norms[i].clone();
cat.extend_from_slice(&mk_embed);
let mut s = [0.0f32; 1];
if cat.len() == cf.cols() {
cf.matvec(&cat, &mut s, pool);
}
out_conf.push(s[0]);
}
proposals.push(best as u32);
prev = best as u32;
}
proposals
}