Complete long-context chat support
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use super::gguf::{Gguf, Q8_0, Tensor as GgufTensor};
|
||||
use super::gguf::{F16, Gguf, Q8_0, Tensor as GgufTensor};
|
||||
use super::{Model, ModelFamily};
|
||||
use std::env;
|
||||
use std::ffi::c_void;
|
||||
@@ -78,6 +78,13 @@ unsafe extern "C" {
|
||||
data: *mut c_void,
|
||||
bytes: u64,
|
||||
) -> i32;
|
||||
fn ds4_gpu_tensor_copy(
|
||||
dst: *mut GpuTensor,
|
||||
dst_offset: u64,
|
||||
src: *const GpuTensor,
|
||||
src_offset: u64,
|
||||
bytes: u64,
|
||||
) -> i32;
|
||||
fn ds4_gpu_tensor_copy_f32_to_f16(
|
||||
dst: *mut GpuTensor,
|
||||
dst_offset: u64,
|
||||
@@ -264,6 +271,46 @@ unsafe extern "C" {
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
fn ds4_gpu_attention_indexed_mixed_batch_heads_tensor(
|
||||
heads: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
sinks: u64,
|
||||
q: *const GpuTensor,
|
||||
raw_kv: *const GpuTensor,
|
||||
comp_kv: *const GpuTensor,
|
||||
comp_f16: u32,
|
||||
topk: *const GpuTensor,
|
||||
tokens: u32,
|
||||
pos: u32,
|
||||
n_raw: u32,
|
||||
raw_cap: u32,
|
||||
raw_start: u32,
|
||||
n_comp: u32,
|
||||
top_k: u32,
|
||||
window: u32,
|
||||
ratio: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
fn ds4_gpu_indexer_score_one_tensor(
|
||||
scores: *mut GpuTensor,
|
||||
q: *const GpuTensor,
|
||||
weights: *const GpuTensor,
|
||||
index_comp: *const GpuTensor,
|
||||
n_comp: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
scale: f32,
|
||||
) -> i32;
|
||||
fn ds4_gpu_indexer_topk_tensor(
|
||||
selected: *mut GpuTensor,
|
||||
scores: *const GpuTensor,
|
||||
n_comp: u32,
|
||||
tokens: u32,
|
||||
top_k: u32,
|
||||
) -> i32;
|
||||
fn ds4_gpu_dsv4_indexer_qat_tensor(x: *mut GpuTensor, rows: u32, head_dim: u32) -> i32;
|
||||
fn ds4_gpu_compressor_update_tensor(
|
||||
kv: *const GpuTensor,
|
||||
score: *const GpuTensor,
|
||||
@@ -579,6 +626,7 @@ struct Layer {
|
||||
attn_output_a: Weight,
|
||||
attn_output_b: Weight,
|
||||
attn_compressor: Option<CompressorWeights>,
|
||||
indexer: Option<IndexerWeights>,
|
||||
hc_ffn_fn: Weight,
|
||||
hc_ffn_scale: Weight,
|
||||
hc_ffn_base: Weight,
|
||||
@@ -602,6 +650,13 @@ struct CompressorWeights {
|
||||
norm: Weight,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct IndexerWeights {
|
||||
q: Weight,
|
||||
proj: Weight,
|
||||
compressor: CompressorWeights,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
fn bind(model: &Gguf, index: u32) -> Result<Self, String> {
|
||||
let required = |suffix: &str| Weight::bind(model, &format!("blk.{index}.{suffix}"));
|
||||
@@ -616,6 +671,20 @@ impl Layer {
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let indexer = (compression_ratio(index) == 4)
|
||||
.then(|| {
|
||||
Ok::<_, String>(IndexerWeights {
|
||||
q: required("indexer.attn_q_b.weight")?,
|
||||
proj: required("indexer.proj.weight")?,
|
||||
compressor: CompressorWeights {
|
||||
ape: required("indexer_compressor_ape.weight")?,
|
||||
kv: required("indexer_compressor_kv.weight")?,
|
||||
gate: required("indexer_compressor_gate.weight")?,
|
||||
norm: required("indexer_compressor_norm.weight")?,
|
||||
},
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
let layer = Self {
|
||||
hc_attn_fn: required("hc_attn_fn.weight")?,
|
||||
hc_attn_scale: required("hc_attn_scale.weight")?,
|
||||
@@ -630,6 +699,7 @@ impl Layer {
|
||||
attn_output_a: required("attn_output_a.weight")?,
|
||||
attn_output_b: required("attn_output_b.weight")?,
|
||||
attn_compressor,
|
||||
indexer,
|
||||
hc_ffn_fn: required("hc_ffn_fn.weight")?,
|
||||
hc_ffn_scale: required("hc_ffn_scale.weight")?,
|
||||
hc_ffn_base: required("hc_ffn_base.weight")?,
|
||||
@@ -656,6 +726,11 @@ impl Layer {
|
||||
return Err(format!("Flash Metal path requires Q8_0 {name} weights"));
|
||||
}
|
||||
}
|
||||
if let Some(indexer) = layer.indexer
|
||||
&& (!matches!(indexer.q.kind, F16 | Q8_0) || indexer.proj.kind != F16)
|
||||
{
|
||||
return Err("Flash Metal path requires F16/Q8 indexer weights".into());
|
||||
}
|
||||
Ok(layer)
|
||||
}
|
||||
}
|
||||
@@ -711,6 +786,10 @@ struct Scratch {
|
||||
compressed_kv: Buffer,
|
||||
compressed_score: Buffer,
|
||||
compressed_stage: Buffer,
|
||||
indexer_q: Buffer,
|
||||
indexer_weights: Buffer,
|
||||
indexer_scores: Buffer,
|
||||
indexer_selected: Buffer,
|
||||
heads: Buffer,
|
||||
attention_low: Buffer,
|
||||
attention_out: Buffer,
|
||||
@@ -735,7 +814,7 @@ struct Scratch {
|
||||
}
|
||||
|
||||
impl Scratch {
|
||||
fn allocate(model: &Model) -> Result<Self, String> {
|
||||
fn allocate(model: &Model, context: u32) -> Result<Self, String> {
|
||||
let shape = model.shape;
|
||||
let hc_dim = shape.hc * shape.embd;
|
||||
let mix_hc = 2 * shape.hc + shape.hc * shape.hc;
|
||||
@@ -758,6 +837,10 @@ impl Scratch {
|
||||
compressed_kv: Buffer::floats(2 * shape.head_dim)?,
|
||||
compressed_score: Buffer::floats(2 * shape.head_dim)?,
|
||||
compressed_stage: Buffer::floats(shape.head_dim)?,
|
||||
indexer_q: Buffer::floats(shape.indexer_heads * shape.indexer_head_dim)?,
|
||||
indexer_weights: Buffer::floats(shape.indexer_heads)?,
|
||||
indexer_scores: Buffer::floats(u64::from(context / 4 + 2))?,
|
||||
indexer_selected: Buffer::bytes(shape.indexer_top_k * 4)?,
|
||||
heads: Buffer::floats(q_dim)?,
|
||||
attention_low: Buffer::floats(low_dim)?,
|
||||
attention_out: Buffer::floats(shape.embd)?,
|
||||
@@ -796,6 +879,7 @@ pub(super) struct Session {
|
||||
struct LayerState {
|
||||
raw_cache: Buffer,
|
||||
compression: Option<CompressionState>,
|
||||
indexer: Option<CompressionState>,
|
||||
}
|
||||
|
||||
struct CompressionState {
|
||||
@@ -828,9 +912,27 @@ impl LayerState {
|
||||
rows: 0,
|
||||
})
|
||||
};
|
||||
let indexer = if ratio == 4 {
|
||||
let width = 2 * shape.indexer_head_dim;
|
||||
let state_rows = 2 * u64::from(ratio);
|
||||
let state_kv = Buffer::floats(width * state_rows)?;
|
||||
let state_score = Buffer::floats(width * state_rows)?;
|
||||
state_kv.fill(0.0, width * state_rows)?;
|
||||
state_score.fill(f32::NEG_INFINITY, width * state_rows)?;
|
||||
Some(CompressionState {
|
||||
ratio,
|
||||
cache: Buffer::floats(u64::from(context / ratio + 2) * shape.indexer_head_dim)?,
|
||||
state_kv,
|
||||
state_score,
|
||||
rows: 0,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
raw_cache: Buffer::floats(raw_cap as u64 * shape.head_dim)?,
|
||||
compression,
|
||||
indexer,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -840,15 +942,12 @@ impl Session {
|
||||
if context == 0 {
|
||||
return Err("context must contain at least one token".into());
|
||||
}
|
||||
// ponytail: the ratio-4 indexer is needed above 2K; cap here until that
|
||||
// sparse selection path is ported.
|
||||
let context = context.min(2_048);
|
||||
let raw_cap = model.shape.sliding_window as u32;
|
||||
let raw_cap = (model.shape.sliding_window as u32).min(context);
|
||||
let layers = (0..model.shape.layers)
|
||||
.map(|index| LayerState::allocate(model, index, context, raw_cap))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(Self {
|
||||
scratch: Scratch::allocate(model)?,
|
||||
scratch: Scratch::allocate(model, context)?,
|
||||
layers,
|
||||
context,
|
||||
raw_cap,
|
||||
@@ -1202,35 +1301,169 @@ fn encode_layer(
|
||||
attn_factor,
|
||||
)?;
|
||||
}
|
||||
if let (Some(weights), Some(indexer)) = (w.indexer, state.indexer.as_mut()) {
|
||||
update_indexer_compression(
|
||||
s,
|
||||
indexer,
|
||||
weights.compressor,
|
||||
shape,
|
||||
map,
|
||||
size,
|
||||
pos,
|
||||
original,
|
||||
freq_base,
|
||||
freq_scale,
|
||||
ext,
|
||||
attn_factor,
|
||||
)?;
|
||||
}
|
||||
let (comp_cache, n_comp) = state
|
||||
.compression
|
||||
.as_ref()
|
||||
.map_or((std::ptr::null(), 0), |compression| {
|
||||
(compression.cache.raw().cast_const(), compression.rows)
|
||||
});
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_attention_decode_heads_tensor(
|
||||
s.heads.raw(),
|
||||
map,
|
||||
size,
|
||||
w.attn_sinks.offset,
|
||||
s.q.raw(),
|
||||
raw_cache,
|
||||
n_raw,
|
||||
raw_cap,
|
||||
raw_start,
|
||||
comp_cache,
|
||||
1,
|
||||
n_comp,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
shape.heads as u32,
|
||||
shape.head_dim as u32,
|
||||
)
|
||||
},
|
||||
"attention",
|
||||
)?;
|
||||
let indexed = if n_comp > 1_024 {
|
||||
match (w.indexer, state.indexer.as_ref()) {
|
||||
(Some(weights), Some(indexer)) if indexer.rows > shape.indexer_top_k as u32 => {
|
||||
matmul(
|
||||
&s.indexer_q,
|
||||
weights.q,
|
||||
shape.lora_q,
|
||||
shape.indexer_heads * shape.indexer_head_dim,
|
||||
&s.q_rank_norm,
|
||||
map,
|
||||
size,
|
||||
)?;
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_rope_tail_tensor(
|
||||
s.indexer_q.raw(),
|
||||
1,
|
||||
shape.indexer_heads as u32,
|
||||
shape.indexer_head_dim as u32,
|
||||
shape.rot as u32,
|
||||
pos,
|
||||
original,
|
||||
false,
|
||||
freq_base,
|
||||
freq_scale,
|
||||
ext,
|
||||
attn_factor,
|
||||
shape.rope_beta_fast,
|
||||
shape.rope_beta_slow,
|
||||
)
|
||||
},
|
||||
"indexer Q RoPE",
|
||||
)?;
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_dsv4_indexer_qat_tensor(
|
||||
s.indexer_q.raw(),
|
||||
shape.indexer_heads as u32,
|
||||
shape.indexer_head_dim as u32,
|
||||
)
|
||||
},
|
||||
"indexer Q quantization",
|
||||
)?;
|
||||
f16(
|
||||
&s.indexer_weights,
|
||||
weights.proj,
|
||||
shape.embd,
|
||||
shape.indexer_heads,
|
||||
&s.norm,
|
||||
map,
|
||||
size,
|
||||
)?;
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_indexer_score_one_tensor(
|
||||
s.indexer_scores.raw(),
|
||||
s.indexer_q.raw(),
|
||||
s.indexer_weights.raw(),
|
||||
indexer.cache.raw(),
|
||||
indexer.rows,
|
||||
shape.indexer_heads as u32,
|
||||
shape.indexer_head_dim as u32,
|
||||
((shape.indexer_heads * shape.indexer_head_dim) as f32)
|
||||
.sqrt()
|
||||
.recip(),
|
||||
)
|
||||
},
|
||||
"indexer scoring",
|
||||
)?;
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_indexer_topk_tensor(
|
||||
s.indexer_selected.raw(),
|
||||
s.indexer_scores.raw(),
|
||||
indexer.rows,
|
||||
1,
|
||||
shape.indexer_top_k as u32,
|
||||
)
|
||||
},
|
||||
"indexer top-k selection",
|
||||
)?;
|
||||
Some(indexer)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if indexed.is_some() {
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_attention_indexed_mixed_batch_heads_tensor(
|
||||
s.heads.raw(),
|
||||
map,
|
||||
size,
|
||||
w.attn_sinks.offset,
|
||||
s.q.raw(),
|
||||
raw_cache,
|
||||
comp_cache,
|
||||
1,
|
||||
s.indexer_selected.raw(),
|
||||
1,
|
||||
pos,
|
||||
n_raw,
|
||||
raw_cap,
|
||||
raw_start,
|
||||
n_comp,
|
||||
shape.indexer_top_k as u32,
|
||||
shape.sliding_window as u32,
|
||||
compression_ratio(layer),
|
||||
shape.heads as u32,
|
||||
shape.head_dim as u32,
|
||||
)
|
||||
},
|
||||
"indexed attention",
|
||||
)?;
|
||||
} else {
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_attention_decode_heads_tensor(
|
||||
s.heads.raw(),
|
||||
map,
|
||||
size,
|
||||
w.attn_sinks.offset,
|
||||
s.q.raw(),
|
||||
raw_cache,
|
||||
n_raw,
|
||||
raw_cap,
|
||||
raw_start,
|
||||
comp_cache,
|
||||
1,
|
||||
n_comp,
|
||||
std::ptr::null(),
|
||||
0,
|
||||
shape.heads as u32,
|
||||
shape.head_dim as u32,
|
||||
)
|
||||
},
|
||||
"attention",
|
||||
)?;
|
||||
}
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_rope_tail_tensor(
|
||||
@@ -1454,7 +1687,125 @@ fn update_compression(
|
||||
ext: f32,
|
||||
attn_factor: f32,
|
||||
) -> Result<(), String> {
|
||||
let width = if state.ratio == 4 { 2 } else { 1 } * shape.head_dim as u32;
|
||||
let emit = update_compressor_stage(
|
||||
s,
|
||||
state,
|
||||
weights,
|
||||
shape,
|
||||
map,
|
||||
size,
|
||||
pos,
|
||||
original,
|
||||
freq_base,
|
||||
freq_scale,
|
||||
ext,
|
||||
attn_factor,
|
||||
shape.head_dim as u32,
|
||||
)?;
|
||||
if emit {
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_dsv4_fp8_kv_quantize_tensor(
|
||||
s.compressed_stage.raw(),
|
||||
1,
|
||||
shape.head_dim as u32,
|
||||
shape.rot as u32,
|
||||
)
|
||||
},
|
||||
"compressed KV quantization",
|
||||
)?;
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_tensor_copy_f32_to_f16(
|
||||
state.cache.raw(),
|
||||
state.rows as u64 * shape.head_dim * 2,
|
||||
s.compressed_stage.raw(),
|
||||
0,
|
||||
shape.head_dim,
|
||||
)
|
||||
},
|
||||
"compressed KV cache write",
|
||||
)?;
|
||||
state.rows += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_indexer_compression(
|
||||
s: &Scratch,
|
||||
state: &mut CompressionState,
|
||||
weights: CompressorWeights,
|
||||
shape: super::Shape,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
pos: u32,
|
||||
original: u32,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext: f32,
|
||||
attn_factor: f32,
|
||||
) -> Result<(), String> {
|
||||
let emit = update_compressor_stage(
|
||||
s,
|
||||
state,
|
||||
weights,
|
||||
shape,
|
||||
map,
|
||||
size,
|
||||
pos,
|
||||
original,
|
||||
freq_base,
|
||||
freq_scale,
|
||||
ext,
|
||||
attn_factor,
|
||||
shape.indexer_head_dim as u32,
|
||||
)?;
|
||||
if emit {
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_dsv4_indexer_qat_tensor(
|
||||
s.compressed_stage.raw(),
|
||||
1,
|
||||
shape.indexer_head_dim as u32,
|
||||
)
|
||||
},
|
||||
"compressed index quantization",
|
||||
)?;
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_tensor_copy(
|
||||
state.cache.raw(),
|
||||
state.rows as u64 * shape.indexer_head_dim * 4,
|
||||
s.compressed_stage.raw(),
|
||||
0,
|
||||
shape.indexer_head_dim * 4,
|
||||
)
|
||||
},
|
||||
"compressed index cache write",
|
||||
)?;
|
||||
state.rows += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update_compressor_stage(
|
||||
s: &Scratch,
|
||||
state: &CompressionState,
|
||||
weights: CompressorWeights,
|
||||
shape: super::Shape,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
pos: u32,
|
||||
original: u32,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext: f32,
|
||||
attn_factor: f32,
|
||||
head_dim: u32,
|
||||
) -> Result<bool, String> {
|
||||
let width = if state.ratio == 4 { 2 } else { 1 } * head_dim;
|
||||
let fused = unsafe {
|
||||
ds4_gpu_matmul_f16_pair_compressor_store_tensor(
|
||||
s.compressed_kv.raw(),
|
||||
@@ -1496,7 +1847,6 @@ fn update_compression(
|
||||
"compressor projection",
|
||||
)?;
|
||||
}
|
||||
let emit = (pos + 1).is_multiple_of(state.ratio);
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_compressor_update_tensor(
|
||||
@@ -1511,7 +1861,7 @@ fn update_compression(
|
||||
weights.ape.kind,
|
||||
weights.norm.offset,
|
||||
weights.norm.kind,
|
||||
shape.head_dim as u32,
|
||||
head_dim,
|
||||
state.ratio,
|
||||
pos,
|
||||
0,
|
||||
@@ -1529,33 +1879,7 @@ fn update_compression(
|
||||
},
|
||||
"compressor update",
|
||||
)?;
|
||||
if emit {
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_dsv4_fp8_kv_quantize_tensor(
|
||||
s.compressed_stage.raw(),
|
||||
1,
|
||||
shape.head_dim as u32,
|
||||
shape.rot as u32,
|
||||
)
|
||||
},
|
||||
"compressed KV quantization",
|
||||
)?;
|
||||
call(
|
||||
unsafe {
|
||||
ds4_gpu_tensor_copy_f32_to_f16(
|
||||
state.cache.raw(),
|
||||
state.rows as u64 * shape.head_dim * 2,
|
||||
s.compressed_stage.raw(),
|
||||
0,
|
||||
shape.head_dim,
|
||||
)
|
||||
},
|
||||
"compressed KV cache write",
|
||||
)?;
|
||||
state.rows += 1;
|
||||
}
|
||||
Ok(())
|
||||
Ok((pos + 1).is_multiple_of(state.ratio))
|
||||
}
|
||||
|
||||
fn compression_ratio(layer: u32) -> u32 {
|
||||
@@ -1690,6 +2014,22 @@ fn f16(
|
||||
)
|
||||
}
|
||||
|
||||
fn matmul(
|
||||
out: &Buffer,
|
||||
weight: Weight,
|
||||
input: u64,
|
||||
output: u64,
|
||||
x: &Buffer,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
) -> Result<(), String> {
|
||||
match weight.kind {
|
||||
F16 => f16(out, weight, input, output, x, map, size),
|
||||
Q8_0 => q8(out, weight, input, output, x, map, size),
|
||||
kind => Err(format!("unsupported Metal projection type {kind}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn q8(
|
||||
out: &Buffer,
|
||||
weight: Weight,
|
||||
|
||||
Reference in New Issue
Block a user