use cortiq_core::CmfModel;
use std::cell::Cell;
use std::sync::atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, OnceLock};
thread_local! {
static CUR_LAYER: Cell<i64> = const { Cell::new(-1) };
static CPU_ONLY: Cell<bool> = const { Cell::new(false) };
static PROBE_COLD: Cell<bool> = const { Cell::new(false) };
}
pub fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
struct Restore(bool);
impl Drop for Restore {
fn drop(&mut self) {
CPU_ONLY.with(|c| c.set(self.0));
}
}
let previous = CPU_ONLY.with(|c| c.replace(true));
let _restore = Restore(previous);
f()
}
pub(crate) fn probe_note_cold() {
PROBE_COLD.with(|c| c.set(true));
}
pub fn set_layer(l: i64) {
CUR_LAYER.with(|c| c.set(l));
}
pub fn cur_layer() -> i64 {
CUR_LAYER.with(|c| c.get())
}
fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
R.get_or_init(|| {
let s = std::env::var("CMF_GPU_LAYERS").ok()?;
let mut v = Vec::new();
for part in s.split(',') {
let part = part.trim();
match part.split_once('-') {
Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
None => {
let x: i64 = part.parse().ok()?;
v.push((x, x));
}
}
}
Some(v)
})
}
fn layer_allowed() -> bool {
match layer_ranges() {
None => true,
Some(ranges) => {
let cur = CUR_LAYER.with(|c| c.get());
cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
}
}
}
pub fn enabled_here() -> bool {
!CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
}
#[derive(Clone, Copy)]
pub enum OpClass {
Ffn = 0,
Matvec = 1,
Matmat = 2,
Batch = 3,
}
pub enum ProbeArm {
Gpu,
CpuTimed,
Cpu,
}
const PROBE_SAMPLES: u32 = 6;
struct Probe {
state: AtomicU8,
flip: AtomicU32,
gpu_ns: AtomicU64,
gpu_n: AtomicU32,
cpu_ns: AtomicU64,
cpu_n: AtomicU32,
}
impl Probe {
const fn new() -> Self {
Self {
state: AtomicU8::new(0),
flip: AtomicU32::new(0),
gpu_ns: AtomicU64::new(0),
gpu_n: AtomicU32::new(0),
cpu_ns: AtomicU64::new(0),
cpu_n: AtomicU32::new(0),
}
}
}
static PROBES: [Probe; 4] = [Probe::new(), Probe::new(), Probe::new(), Probe::new()];
fn probe_on() -> bool {
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| {
std::env::var("CMF_GPU_PROBE")
.map(|v| v != "0" && v != "off")
.unwrap_or(true)
})
}
pub fn q1_force() -> bool {
#[cfg(target_os = "macos")]
{
backend() == Backend::Metal
}
#[cfg(not(target_os = "macos"))]
{
false
}
}
pub fn probe_arm(c: OpClass) -> ProbeArm {
if !probe_on() {
return ProbeArm::Gpu;
}
let p = &PROBES[c as usize];
match p.state.load(Ordering::Relaxed) {
1 => ProbeArm::Gpu,
2 => ProbeArm::Cpu,
_ => {
PROBE_COLD.with(|f| f.set(false));
if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
ProbeArm::Gpu
} else {
ProbeArm::CpuTimed
}
}
}
}
pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
let p = &PROBES[c as usize];
if p.state.load(Ordering::Relaxed) != 0 {
return;
}
if gpu && PROBE_COLD.with(|f| f.replace(false)) {
return; }
let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
if gpu {
p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
p.gpu_n.fetch_add(1, Ordering::Relaxed);
} else {
p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
p.cpu_n.fetch_add(1, Ordering::Relaxed);
}
let (gn, cn) = (
p.gpu_n.load(Ordering::Relaxed),
p.cpu_n.load(Ordering::Relaxed),
);
if gn >= 2 && cn >= 2 {
let g = p.gpu_ns.load(Ordering::Relaxed) as f64 / gn as f64;
let cp = p.cpu_ns.load(Ordering::Relaxed) as f64 / cn as f64;
if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 3.0 && cp < g * 3.0 {
return;
}
let winner = if g <= cp { 1 } else { 2 };
if p.state
.compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
tracing::info!(
"gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
["ffn", "matvec", "matmat", "qkv-batch"][c as usize],
g / 1e6,
cp / 1e6,
if winner == 1 { "gpu" } else { "cpu" },
);
}
}
}
pub fn probe_deciding(c: OpClass) -> bool {
probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
}
#[allow(unused_variables)]
pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
let resident = match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
Backend::None => false,
};
if !resident && may_upload {
PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
}
resident
}
#[cfg(test)]
pub(crate) fn probe_reset() {
for p in &PROBES {
p.state.store(0, Ordering::Relaxed);
p.flip.store(0, Ordering::Relaxed);
p.gpu_ns.store(0, Ordering::Relaxed);
p.gpu_n.store(0, Ordering::Relaxed);
p.cpu_ns.store(0, Ordering::Relaxed);
p.cpu_n.store(0, Ordering::Relaxed);
}
}
#[cfg(test)]
mod probe_tests {
use super::*;
use std::time::Duration;
#[test]
fn probe_alternates_discards_cold_and_decides() {
probe_reset();
assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
probe_note_cold();
probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
for _ in 0..PROBE_SAMPLES {
probe_record(OpClass::Ffn, true, Duration::from_millis(1));
probe_record(OpClass::Ffn, false, Duration::from_millis(4));
}
assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
for _ in 0..PROBE_SAMPLES {
probe_record(OpClass::Matmat, true, Duration::from_millis(4));
probe_record(OpClass::Matmat, false, Duration::from_millis(1));
}
assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
CPU_ONLY.with(|c| assert!(!c.get()));
cpu_scope(|| {
cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
CPU_ONLY.with(|c| assert!(c.get()));
});
let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
CPU_ONLY.with(|c| assert!(!c.get()));
probe_reset();
}
}
pub const GPU_MIN_ROWS: usize = 65_536;
pub fn min_rows() -> usize {
if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
.ok()
.and_then(|v| v.parse().ok())
{
return v;
}
if discrete() { 4096 } else { GPU_MIN_ROWS }
}
pub fn discrete() -> bool {
match backend() {
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
#[cfg(target_os = "macos")]
Backend::Metal => false, Backend::None => false,
}
}
pub struct MoeJob<'a> {
pub gate: (usize, usize, usize, &'a [f32]),
pub up: (usize, usize, usize, &'a [f32]),
pub down: (usize, usize, usize, &'a [f32]),
pub xs_gate: Vec<f32>,
pub xs_up: Vec<f32>,
pub down_col: &'a [f32],
pub w: f32,
pub q1: bool,
}
pub struct BatchJob<'a> {
pub idx: usize,
pub rows: usize,
pub cols: usize,
pub row_scale: &'a [f32],
pub xs: Vec<f32>,
pub q1: bool,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Backend {
None,
#[cfg(target_os = "macos")]
Metal,
#[cfg(feature = "gpu")]
Wgpu,
}
fn backend() -> Backend {
#[cfg(feature = "gpu")]
if crate::gpu_wgpu::selected() {
return if crate::gpu_wgpu::enabled() {
Backend::Wgpu
} else {
Backend::None
};
}
#[cfg(target_os = "macos")]
if crate::gpu_metal::enabled() {
return Backend::Metal;
}
Backend::None
}
pub fn enabled() -> bool {
backend() != Backend::None
}
#[allow(clippy::too_many_arguments, unused_variables)]
pub fn q8_matvec_range(
model: &Arc<CmfModel>,
idx: usize,
row0: usize,
row_scale: &[f32],
xs: &[f32],
rows: usize,
cols: usize,
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => {
crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
}
#[cfg(feature = "gpu")]
Backend::Wgpu => {
crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
}
Backend::None => false,
}
}
#[allow(clippy::too_many_arguments, unused_variables)]
pub fn q8_matmat(
model: &Arc<CmfModel>,
idx: usize,
row_scale: &[f32],
pre: &[f32],
b: usize,
rows: usize,
cols: usize,
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => {
crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
}
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
Backend::None => false,
}
}
#[allow(unused_variables)]
pub fn q1_matvec(
model: &Arc<CmfModel>,
idx: usize,
xs: &[f32],
rows: usize,
cols: usize,
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
Backend::None => false,
}
}
#[allow(clippy::too_many_arguments)]
pub fn attn_dropin(
model: &Arc<CmfModel>,
kv_id: u64,
layer: usize,
normed: &[f32],
wq_idx: usize,
wk_idx: usize,
wv_idx: usize,
wo_idx: usize,
q_norm: Option<&[f32]>,
k_norm: Option<&[f32]>,
invf: &[f32],
nh: usize,
nkv: usize,
hd: usize,
rd: usize,
hidden: usize,
pos: usize,
cap: usize,
gemma: bool,
eps: f32,
cpu_k: &[Vec<f32>],
cpu_v: &[Vec<f32>],
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
),
_ => false,
}
}
pub struct GraphW<'a> {
pub idx: usize,
pub kind: u8,
pub row_scale: &'a [f32],
pub data: &'a [f32],
}
pub enum GraphAttn<'a> {
Full {
wq: GraphW<'a>,
wk: GraphW<'a>,
wv: GraphW<'a>,
wo: GraphW<'a>,
q_norm: Option<&'a [f32]>,
k_norm: Option<&'a [f32]>,
bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
output_gate: bool,
cpu_k: &'a [Vec<f32>],
cpu_v: &'a [Vec<f32>],
},
Gdn {
qkv: GraphW<'a>,
z: GraphW<'a>,
a: GraphW<'a>,
b: GraphW<'a>,
out: GraphW<'a>,
conv1d: &'a [f32],
a_log: &'a [f32],
dt_bias: &'a [f32],
norm: &'a [f32],
nv: usize,
nk: usize,
dk: usize,
dv: usize,
kk: usize,
},
}
pub struct GraphLayer<'a> {
pub input_norm: &'a [f32],
pub attn: GraphAttn<'a>,
pub post_norm: &'a [f32],
pub gate: GraphW<'a>,
pub up: GraphW<'a>,
pub down: GraphW<'a>,
}
#[allow(clippy::too_many_arguments)]
pub fn forward_token_graph(
model: &Arc<CmfModel>,
kv_id: u64,
layers: &[GraphLayer],
invf: &[f32],
h: &mut [f32],
nh: usize,
nkv: usize,
hd: usize,
rd: usize,
hidden: usize,
inter: usize,
position: usize,
cap: usize,
gemma: bool,
eps: f32,
lm_head: Option<(&GraphW, usize)>,
final_norm: &[f32],
logits: &mut Vec<f32>,
) -> bool {
match backend() {
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, position, cap, gemma,
eps, lm_head, final_norm, logits,
),
_ => {
let _ = (lm_head, final_norm, logits);
false
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn forward_batch_graph(
model: &Arc<CmfModel>,
kv_id: u64,
layers: &[GraphLayer],
invf: &[f32],
h: &mut [f32],
nh: usize,
nkv: usize,
hd: usize,
rd: usize,
hidden: usize,
inter: usize,
positions: &[usize],
cap: usize,
gemma: bool,
eps: f32,
k: usize,
) -> bool {
match backend() {
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
eps, k,
),
_ => false,
}
}
pub fn graph_kv_reset(_kv_id: u64) {
#[cfg(feature = "gpu")]
if backend() == Backend::Wgpu {
crate::gpu_wgpu::kv_mirror_reset(_kv_id);
}
}
pub fn q1t_matvec(
model: &Arc<CmfModel>,
idx: usize,
xs: &[f32],
rows: usize,
cols: usize,
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => {
if metal_q1t_enabled() {
crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
} else {
false
}
}
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
Backend::None => false,
}
}
#[allow(unused_variables)]
pub fn q4b_matvec(
model: &Arc<CmfModel>,
idx: usize,
xs: &[f32],
rows: usize,
cols: usize,
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => false,
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
Backend::None => false,
}
}
pub fn q1t_matmat(
model: &Arc<CmfModel>,
idx: usize,
xs: &[f32],
b: usize,
rows: usize,
cols: usize,
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
Backend::None => false,
}
}
#[cfg(target_os = "macos")]
pub(crate) fn metal_q1t_enabled() -> bool {
std::env::var("CMF_METAL_Q1T")
.map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
.unwrap_or(true)
}
pub fn q1_matmat(
model: &Arc<CmfModel>,
idx: usize,
xs: &[f32],
b: usize,
rows: usize,
cols: usize,
out: &mut [f32],
) -> bool {
match backend() {
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
_ => false,
}
}
#[cfg(target_os = "macos")]
pub use crate::gpu_metal::{
AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
kv_mirror_read_last, kv_mirror_take_imp,
};
#[cfg(target_os = "macos")]
pub fn gdn_block(
model: &Arc<CmfModel>,
layers: &[GdnGpuLayer],
states: &mut [&mut [f32]],
cfg: &GdnGpuCfg,
h: &mut [f32],
) -> bool {
match backend() {
Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
_ => false,
}
}
#[allow(unused_variables)]
pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
Backend::None => false,
}
}
#[allow(unused_variables)]
pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
match backend() {
#[cfg(target_os = "macos")]
Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
#[cfg(feature = "gpu")]
Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
Backend::None => false,
}
}