Split Rust code into domain modules
This commit is contained in:
1286
src/engine/metal.rs
1286
src/engine/metal.rs
File diff suppressed because it is too large
Load Diff
367
src/engine/metal/checkpoint.rs
Normal file
367
src/engine/metal/checkpoint.rs
Normal file
@@ -0,0 +1,367 @@
|
||||
use super::*;
|
||||
|
||||
impl Executor {
|
||||
pub(in crate::engine) fn save_checkpoint(
|
||||
&mut self,
|
||||
path: &Path,
|
||||
tag: [u8; 32],
|
||||
progress: &mut impl FnMut(u64),
|
||||
) -> Result<(), String> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|error| error.to_string())?;
|
||||
}
|
||||
let temporary = path.with_extension("tmp");
|
||||
let mut file = File::create(&temporary).map_err(|error| error.to_string())?;
|
||||
let mut reported = 0_u64;
|
||||
let mut pending = 0_u64;
|
||||
self.write_checkpoint(&mut file, tag, &mut |bytes| {
|
||||
reported += bytes;
|
||||
pending += bytes;
|
||||
if pending >= CHECKPOINT_IO_CHUNK as u64 {
|
||||
progress(pending);
|
||||
pending = 0;
|
||||
}
|
||||
})?;
|
||||
progress(pending);
|
||||
let total = file.metadata().map_err(|error| error.to_string())?.len();
|
||||
progress(total.saturating_sub(reported));
|
||||
file.sync_all().map_err(|error| error.to_string())?;
|
||||
fs::rename(&temporary, path).map_err(|error| error.to_string())?;
|
||||
self.checkpoint_tag = tag;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::engine) fn load_checkpoint(
|
||||
&mut self,
|
||||
path: &Path,
|
||||
progress: &mut impl FnMut(u64),
|
||||
) -> Result<bool, String> {
|
||||
let mut file = match File::open(path) {
|
||||
Ok(file) => file,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false),
|
||||
Err(error) => return Err(error.to_string()),
|
||||
};
|
||||
let mut reported = 0_u64;
|
||||
let mut pending = 0_u64;
|
||||
self.read_checkpoint(&mut file, &mut |bytes| {
|
||||
reported += bytes;
|
||||
pending += bytes;
|
||||
if pending >= CHECKPOINT_IO_CHUNK as u64 {
|
||||
progress(pending);
|
||||
pending = 0;
|
||||
}
|
||||
})?;
|
||||
progress(pending);
|
||||
let total = file.metadata().map_err(|error| error.to_string())?.len();
|
||||
progress(total.saturating_sub(reported));
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn write_checkpoint(
|
||||
&self,
|
||||
file: &mut File,
|
||||
tag: [u8; 32],
|
||||
progress: &mut impl FnMut(u64),
|
||||
) -> Result<(), String> {
|
||||
let shape = self.model.shape;
|
||||
file.write_all(CHECKPOINT_MAGIC)
|
||||
.map_err(|error| error.to_string())?;
|
||||
for value in [
|
||||
CHECKPOINT_VERSION,
|
||||
self.session.context,
|
||||
self.session.raw_cap,
|
||||
shape.layers,
|
||||
shape.head_dim as u32,
|
||||
shape.indexer_head_dim as u32,
|
||||
shape.vocab as u32,
|
||||
u32::from(self.quality),
|
||||
] {
|
||||
write_u32(file, value)?;
|
||||
}
|
||||
write_u64(file, self.model.main.len())?;
|
||||
write_u64(file, self.model_modified.0)?;
|
||||
write_u32(file, self.model_modified.1)?;
|
||||
for weight in [self.weights.token_embedding, self.weights.output] {
|
||||
write_u64(file, weight.offset)?;
|
||||
write_u64(file, weight.bytes)?;
|
||||
write_u32(file, weight.kind)?;
|
||||
}
|
||||
file.write_all(&tag).map_err(|error| error.to_string())?;
|
||||
let token_count = u32::try_from(self.tokens.len())
|
||||
.map_err(|_| "KV checkpoint has too many tokens".to_owned())?;
|
||||
let raw_live = token_count.min(self.session.raw_cap);
|
||||
write_u32(file, token_count)?;
|
||||
write_u32(file, raw_live)?;
|
||||
for &token in &self.tokens {
|
||||
write_u32(file, token as u32)?;
|
||||
}
|
||||
for &logit in &self.logits {
|
||||
write_u32(file, logit.to_bits())?;
|
||||
}
|
||||
for layer in &self.session.layers {
|
||||
write_u32(
|
||||
file,
|
||||
layer.compression.as_ref().map_or(0, |state| state.rows),
|
||||
)?;
|
||||
}
|
||||
for layer in &self.session.layers {
|
||||
write_u32(file, layer.indexer.as_ref().map_or(0, |state| state.rows))?;
|
||||
}
|
||||
|
||||
let mut chunk = vec![0; CHECKPOINT_IO_CHUNK];
|
||||
let raw_first = token_count - raw_live;
|
||||
for layer in &self.session.layers {
|
||||
for position in raw_first..token_count {
|
||||
let physical = position % self.session.raw_cap;
|
||||
write_buffer(
|
||||
file,
|
||||
&layer.raw_cache,
|
||||
u64::from(physical) * shape.head_dim * 4,
|
||||
shape.head_dim * 4,
|
||||
&mut chunk,
|
||||
progress,
|
||||
)?;
|
||||
}
|
||||
if let Some(state) = &layer.compression {
|
||||
write_buffer(
|
||||
file,
|
||||
&state.cache,
|
||||
0,
|
||||
u64::from(state.rows) * shape.head_dim * 2,
|
||||
&mut chunk,
|
||||
progress,
|
||||
)?;
|
||||
let bytes = compressor_state_bytes(state.ratio, shape.head_dim);
|
||||
write_buffer(file, &state.state_kv, 0, bytes, &mut chunk, progress)?;
|
||||
write_buffer(file, &state.state_score, 0, bytes, &mut chunk, progress)?;
|
||||
}
|
||||
if let Some(state) = &layer.indexer {
|
||||
write_buffer(
|
||||
file,
|
||||
&state.cache,
|
||||
0,
|
||||
u64::from(state.rows) * shape.indexer_head_dim * 4,
|
||||
&mut chunk,
|
||||
progress,
|
||||
)?;
|
||||
let bytes = compressor_state_bytes(state.ratio, shape.indexer_head_dim);
|
||||
write_buffer(file, &state.state_kv, 0, bytes, &mut chunk, progress)?;
|
||||
write_buffer(file, &state.state_score, 0, bytes, &mut chunk, progress)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_checkpoint(
|
||||
&mut self,
|
||||
file: &mut File,
|
||||
progress: &mut impl FnMut(u64),
|
||||
) -> Result<(), String> {
|
||||
let mut magic = [0; 8];
|
||||
file.read_exact(&mut magic)
|
||||
.map_err(|error| error.to_string())?;
|
||||
if &magic != CHECKPOINT_MAGIC {
|
||||
return Err("KV checkpoint has an invalid signature".into());
|
||||
}
|
||||
let shape = self.model.shape;
|
||||
let header = [
|
||||
CHECKPOINT_VERSION,
|
||||
self.session.context,
|
||||
self.session.raw_cap,
|
||||
shape.layers,
|
||||
shape.head_dim as u32,
|
||||
shape.indexer_head_dim as u32,
|
||||
shape.vocab as u32,
|
||||
u32::from(self.quality),
|
||||
];
|
||||
for expected in header {
|
||||
if read_u32(file)? != expected {
|
||||
return Err("KV checkpoint does not match the current executor".into());
|
||||
}
|
||||
}
|
||||
if read_u64(file)? != self.model.main.len() {
|
||||
return Err("KV checkpoint was written for a different model".into());
|
||||
}
|
||||
if read_u64(file)? != self.model_modified.0 || read_u32(file)? != self.model_modified.1 {
|
||||
return Err("KV checkpoint model file has changed".into());
|
||||
}
|
||||
for weight in [self.weights.token_embedding, self.weights.output] {
|
||||
if read_u64(file)? != weight.offset
|
||||
|| read_u64(file)? != weight.bytes
|
||||
|| read_u32(file)? != weight.kind
|
||||
{
|
||||
return Err("KV checkpoint was written for a different model layout".into());
|
||||
}
|
||||
}
|
||||
let mut checkpoint_tag = [0; 32];
|
||||
file.read_exact(&mut checkpoint_tag)
|
||||
.map_err(|error| error.to_string())?;
|
||||
let token_count = read_u32(file)?;
|
||||
let raw_live = read_u32(file)?;
|
||||
if token_count > self.session.context || raw_live != token_count.min(self.session.raw_cap) {
|
||||
return Err("KV checkpoint token count is invalid".into());
|
||||
}
|
||||
let mut tokens = Vec::with_capacity(token_count as usize);
|
||||
for _ in 0..token_count {
|
||||
let token = read_u32(file)?;
|
||||
if u64::from(token) >= shape.vocab {
|
||||
return Err("KV checkpoint contains an invalid token".into());
|
||||
}
|
||||
tokens.push(token as i32);
|
||||
}
|
||||
let mut logits = Vec::with_capacity(shape.vocab as usize);
|
||||
for _ in 0..shape.vocab {
|
||||
logits.push(f32::from_bits(read_u32(file)?));
|
||||
}
|
||||
let mut compressed_rows = Vec::with_capacity(shape.layers as usize);
|
||||
let mut indexer_rows = Vec::with_capacity(shape.layers as usize);
|
||||
for layer in 0..shape.layers {
|
||||
let rows = read_u32(file)?;
|
||||
let ratio = compression_ratio(layer);
|
||||
if rows != token_count.checked_div(ratio).unwrap_or(0) {
|
||||
return Err("KV checkpoint compressed row count is invalid".into());
|
||||
}
|
||||
compressed_rows.push(rows);
|
||||
}
|
||||
for layer in 0..shape.layers {
|
||||
let rows = read_u32(file)?;
|
||||
let expected = if compression_ratio(layer) == 4 {
|
||||
token_count / 4
|
||||
} else {
|
||||
0
|
||||
};
|
||||
if rows != expected {
|
||||
return Err("KV checkpoint indexer row count is invalid".into());
|
||||
}
|
||||
indexer_rows.push(rows);
|
||||
}
|
||||
|
||||
self.reset()?;
|
||||
let mut chunk = vec![0; CHECKPOINT_IO_CHUNK];
|
||||
let raw_first = token_count - raw_live;
|
||||
for (index, layer) in self.session.layers.iter_mut().enumerate() {
|
||||
for position in raw_first..token_count {
|
||||
let physical = position % self.session.raw_cap;
|
||||
read_buffer(
|
||||
file,
|
||||
&layer.raw_cache,
|
||||
u64::from(physical) * shape.head_dim * 4,
|
||||
shape.head_dim * 4,
|
||||
&mut chunk,
|
||||
progress,
|
||||
)?;
|
||||
}
|
||||
if let Some(state) = &mut layer.compression {
|
||||
state.rows = compressed_rows[index];
|
||||
read_buffer(
|
||||
file,
|
||||
&state.cache,
|
||||
0,
|
||||
u64::from(state.rows) * shape.head_dim * 2,
|
||||
&mut chunk,
|
||||
progress,
|
||||
)?;
|
||||
let bytes = compressor_state_bytes(state.ratio, shape.head_dim);
|
||||
read_buffer(file, &state.state_kv, 0, bytes, &mut chunk, progress)?;
|
||||
read_buffer(file, &state.state_score, 0, bytes, &mut chunk, progress)?;
|
||||
}
|
||||
if let Some(state) = &mut layer.indexer {
|
||||
state.rows = indexer_rows[index];
|
||||
read_buffer(
|
||||
file,
|
||||
&state.cache,
|
||||
0,
|
||||
u64::from(state.rows) * shape.indexer_head_dim * 4,
|
||||
&mut chunk,
|
||||
progress,
|
||||
)?;
|
||||
let bytes = compressor_state_bytes(state.ratio, shape.indexer_head_dim);
|
||||
read_buffer(file, &state.state_kv, 0, bytes, &mut chunk, progress)?;
|
||||
read_buffer(file, &state.state_score, 0, bytes, &mut chunk, progress)?;
|
||||
}
|
||||
}
|
||||
let mut trailing = [0];
|
||||
if file
|
||||
.read(&mut trailing)
|
||||
.map_err(|error| error.to_string())?
|
||||
!= 0
|
||||
{
|
||||
self.reset()?;
|
||||
return Err("KV checkpoint has trailing data".into());
|
||||
}
|
||||
self.session.position = token_count;
|
||||
self.tokens = tokens;
|
||||
self.logits = logits;
|
||||
self.checkpoint_tag = checkpoint_tag;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn compressor_state_bytes(ratio: u32, head_dim: u64) -> u64 {
|
||||
let coefficient = if ratio == 4 { 2 } else { 1 };
|
||||
coefficient * head_dim * coefficient * u64::from(ratio) * 4
|
||||
}
|
||||
|
||||
fn write_buffer(
|
||||
file: &mut File,
|
||||
buffer: &Buffer,
|
||||
mut offset: u64,
|
||||
mut bytes: u64,
|
||||
chunk: &mut [u8],
|
||||
progress: &mut impl FnMut(u64),
|
||||
) -> Result<(), String> {
|
||||
while bytes != 0 {
|
||||
let length = bytes.min(chunk.len() as u64) as usize;
|
||||
buffer.read(offset, &mut chunk[..length])?;
|
||||
file.write_all(&chunk[..length])
|
||||
.map_err(|error| error.to_string())?;
|
||||
progress(length as u64);
|
||||
offset += length as u64;
|
||||
bytes -= length as u64;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_buffer(
|
||||
file: &mut File,
|
||||
buffer: &Buffer,
|
||||
mut offset: u64,
|
||||
mut bytes: u64,
|
||||
chunk: &mut [u8],
|
||||
progress: &mut impl FnMut(u64),
|
||||
) -> Result<(), String> {
|
||||
while bytes != 0 {
|
||||
let length = bytes.min(chunk.len() as u64) as usize;
|
||||
file.read_exact(&mut chunk[..length])
|
||||
.map_err(|error| error.to_string())?;
|
||||
buffer.write(offset, &chunk[..length])?;
|
||||
progress(length as u64);
|
||||
offset += length as u64;
|
||||
bytes -= length as u64;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_u32(file: &mut File, value: u32) -> Result<(), String> {
|
||||
file.write_all(&value.to_le_bytes())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
fn write_u64(file: &mut File, value: u64) -> Result<(), String> {
|
||||
file.write_all(&value.to_le_bytes())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
fn read_u32(file: &mut File) -> Result<u32, String> {
|
||||
let mut bytes = [0; 4];
|
||||
file.read_exact(&mut bytes)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(u32::from_le_bytes(bytes))
|
||||
}
|
||||
|
||||
fn read_u64(file: &mut File) -> Result<u64, String> {
|
||||
let mut bytes = [0; 8];
|
||||
file.read_exact(&mut bytes)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(u64::from_le_bytes(bytes))
|
||||
}
|
||||
926
src/engine/metal/gpu.rs
Normal file
926
src/engine/metal/gpu.rs
Normal file
@@ -0,0 +1,926 @@
|
||||
use super::*;
|
||||
|
||||
#[repr(C)]
|
||||
pub(super) struct GpuTensor {
|
||||
_private: [u8; 0],
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
pub(super) fn ds4_gpu_init() -> i32;
|
||||
pub(super) fn ds4_gpu_cleanup();
|
||||
pub(super) fn ds4_gpu_set_model_map_range(
|
||||
model_map: *const c_void,
|
||||
model_size: u64,
|
||||
map_offset: u64,
|
||||
map_size: u64,
|
||||
max_tensor_bytes: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_set_quality(quality: bool);
|
||||
pub(super) fn ds4_gpu_tensor_alloc(bytes: u64) -> *mut GpuTensor;
|
||||
pub(super) fn ds4_gpu_tensor_view(
|
||||
base: *const GpuTensor,
|
||||
offset: u64,
|
||||
bytes: u64,
|
||||
) -> *mut GpuTensor;
|
||||
pub(super) fn ds4_gpu_tensor_free(tensor: *mut GpuTensor);
|
||||
pub(super) fn ds4_gpu_tensor_fill_f32(tensor: *mut GpuTensor, value: f32, count: u64) -> i32;
|
||||
pub(super) fn ds4_gpu_tensor_read(
|
||||
tensor: *const GpuTensor,
|
||||
offset: u64,
|
||||
data: *mut c_void,
|
||||
bytes: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_tensor_write(
|
||||
tensor: *mut GpuTensor,
|
||||
offset: u64,
|
||||
data: *const c_void,
|
||||
bytes: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_tensor_copy(
|
||||
dst: *mut GpuTensor,
|
||||
dst_offset: u64,
|
||||
src: *const GpuTensor,
|
||||
src_offset: u64,
|
||||
bytes: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_tensor_copy_f32_to_f16(
|
||||
dst: *mut GpuTensor,
|
||||
dst_offset: u64,
|
||||
src: *const GpuTensor,
|
||||
src_offset: u64,
|
||||
count: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_begin_commands() -> i32;
|
||||
pub(super) fn ds4_gpu_end_commands() -> i32;
|
||||
|
||||
pub(super) fn ds4_gpu_embed_tokens_hc_tensor(
|
||||
out: *mut GpuTensor,
|
||||
tokens: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
vocab: u32,
|
||||
rows: u32,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
|
||||
pub(super) fn ds4_gpu_embed_token_hc_tensor(
|
||||
out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
vocab: u32,
|
||||
token: u32,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_rms_norm_plain_tensor(
|
||||
out: *mut GpuTensor,
|
||||
x: *const GpuTensor,
|
||||
n: u32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_rms_norm_weight_tensor(
|
||||
out: *mut GpuTensor,
|
||||
x: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
n: u32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_hc_rms_scale_project_f16_tensor(
|
||||
out: *mut GpuTensor,
|
||||
scale: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
input: u32,
|
||||
output: u32,
|
||||
x: *const GpuTensor,
|
||||
rows: u32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_matmul_f16_tensor(
|
||||
out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
input: u64,
|
||||
output: u64,
|
||||
x: *const GpuTensor,
|
||||
rows: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_matmul_q8_0_tensor(
|
||||
out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
input: u64,
|
||||
output: u64,
|
||||
x: *const GpuTensor,
|
||||
rows: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_matmul_q8_0_pair_tensor(
|
||||
out_a: *mut GpuTensor,
|
||||
out_b: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight_a: u64,
|
||||
weight_b: u64,
|
||||
input: u64,
|
||||
output_a: u64,
|
||||
output_b: u64,
|
||||
x: *const GpuTensor,
|
||||
rows: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_matmul_f16_pair_tensor(
|
||||
out_a: *mut GpuTensor,
|
||||
out_b: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight_a: u64,
|
||||
weight_b: u64,
|
||||
input: u64,
|
||||
output: u64,
|
||||
x: *const GpuTensor,
|
||||
rows: u64,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_matmul_f16_pair_compressor_store_tensor(
|
||||
out_kv: *mut GpuTensor,
|
||||
out_score: *mut GpuTensor,
|
||||
state_kv: *mut GpuTensor,
|
||||
state_score: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight_kv: u64,
|
||||
weight_score: u64,
|
||||
ape: u64,
|
||||
ape_type: u32,
|
||||
input: u64,
|
||||
width: u32,
|
||||
x: *const GpuTensor,
|
||||
ratio: u32,
|
||||
pos: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_hc_split_weighted_sum_norm_tensor(
|
||||
out: *mut GpuTensor,
|
||||
norm: *mut GpuTensor,
|
||||
split: *mut GpuTensor,
|
||||
mix: *const GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
scale: u64,
|
||||
base: u64,
|
||||
norm_weight: u64,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
iterations: u32,
|
||||
eps: f32,
|
||||
norm_eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_dsv4_qkv_rms_norm_rows_tensor(
|
||||
q_out: *mut GpuTensor,
|
||||
q: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
q_weight: u64,
|
||||
q_n: u32,
|
||||
kv_out: *mut GpuTensor,
|
||||
kv: *const GpuTensor,
|
||||
kv_weight: u64,
|
||||
kv_n: u32,
|
||||
rows: u32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attn_q_b_f16_head_rms_rope_tail_tensor(
|
||||
out: *mut GpuTensor,
|
||||
half: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
input: u64,
|
||||
output: u64,
|
||||
x: *const GpuTensor,
|
||||
rows: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
rot: u32,
|
||||
pos: u32,
|
||||
original_context: u32,
|
||||
inverse: bool,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext_factor: f32,
|
||||
attn_factor: f32,
|
||||
beta_fast: f32,
|
||||
beta_slow: f32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_head_rms_norm_tensor(
|
||||
x: *mut GpuTensor,
|
||||
rows: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_head_rms_norm_rope_tail_tensor(
|
||||
x: *mut GpuTensor,
|
||||
rows: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
rot: u32,
|
||||
pos: u32,
|
||||
original_context: u32,
|
||||
inverse: bool,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext_factor: f32,
|
||||
attn_factor: f32,
|
||||
beta_fast: f32,
|
||||
beta_slow: f32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_rope_tail_tensor(
|
||||
x: *mut GpuTensor,
|
||||
rows: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
rot: u32,
|
||||
pos: u32,
|
||||
original_context: u32,
|
||||
inverse: bool,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext_factor: f32,
|
||||
attn_factor: f32,
|
||||
beta_fast: f32,
|
||||
beta_slow: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_decode_heads_tensor(
|
||||
heads_out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
sinks: u64,
|
||||
q: *const GpuTensor,
|
||||
raw_kv: *const GpuTensor,
|
||||
n_raw: u32,
|
||||
raw_cap: u32,
|
||||
raw_start: u32,
|
||||
comp_kv: *const GpuTensor,
|
||||
comp_f16: u32,
|
||||
n_comp: u32,
|
||||
comp_mask: *const GpuTensor,
|
||||
use_mask: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_decode_raw_batch_heads_tensor(
|
||||
heads: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
sinks: u64,
|
||||
q: *const GpuTensor,
|
||||
raw_kv: *const GpuTensor,
|
||||
tokens: u32,
|
||||
pos: u32,
|
||||
n_raw: u32,
|
||||
raw_cap: u32,
|
||||
raw_start: u32,
|
||||
window: u32,
|
||||
heads_count: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_decode_mixed_batch_heads_tensor(
|
||||
heads: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
sinks: u64,
|
||||
q: *const GpuTensor,
|
||||
raw_kv: *const GpuTensor,
|
||||
compressed: *const GpuTensor,
|
||||
compressed_f16: u32,
|
||||
compressed_mask: *const GpuTensor,
|
||||
use_mask: u32,
|
||||
tokens: u32,
|
||||
pos: u32,
|
||||
n_raw: u32,
|
||||
raw_cap: u32,
|
||||
raw_start: u32,
|
||||
n_comp: u32,
|
||||
window: u32,
|
||||
ratio: u32,
|
||||
heads_count: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
pub(super) 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;
|
||||
pub(super) 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;
|
||||
pub(super) fn ds4_gpu_indexer_scores_decode_batch_tensor(
|
||||
scores: *mut GpuTensor,
|
||||
q: *const GpuTensor,
|
||||
weights: *const GpuTensor,
|
||||
index_comp: *const GpuTensor,
|
||||
n_comp: u32,
|
||||
tokens: u32,
|
||||
pos: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
ratio: u32,
|
||||
scale: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_indexer_topk_tensor(
|
||||
selected: *mut GpuTensor,
|
||||
scores: *const GpuTensor,
|
||||
n_comp: u32,
|
||||
tokens: u32,
|
||||
top_k: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_dsv4_indexer_qat_tensor(
|
||||
x: *mut GpuTensor,
|
||||
rows: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_compressor_update_tensor(
|
||||
kv: *const GpuTensor,
|
||||
score: *const GpuTensor,
|
||||
state_kv: *mut GpuTensor,
|
||||
state_score: *mut GpuTensor,
|
||||
cache: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
ape: u64,
|
||||
ape_type: u32,
|
||||
norm: u64,
|
||||
norm_type: u32,
|
||||
head_dim: u32,
|
||||
ratio: u32,
|
||||
pos: u32,
|
||||
row: u32,
|
||||
rot: u32,
|
||||
original_context: u32,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext_factor: f32,
|
||||
attn_factor: f32,
|
||||
beta_fast: f32,
|
||||
beta_slow: f32,
|
||||
rms_eps: f32,
|
||||
state_already_stored: bool,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_compressor_prefill_state_ratio4_tensor(
|
||||
state_kv: *mut GpuTensor,
|
||||
state_score: *mut GpuTensor,
|
||||
kv_tail: *const GpuTensor,
|
||||
score_tail: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
ape: u64,
|
||||
ape_type: u32,
|
||||
head_dim: u32,
|
||||
pos: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_dsv4_fp8_kv_quantize_tensor(
|
||||
x: *mut GpuTensor,
|
||||
rows: u32,
|
||||
head_dim: u32,
|
||||
rot: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_kv_fp8_store_raw_tensor(
|
||||
kv: *mut GpuTensor,
|
||||
raw_cache: *mut GpuTensor,
|
||||
raw_cap: u32,
|
||||
row: u32,
|
||||
head_dim: u32,
|
||||
rot: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_store_raw_kv_batch_tensor(
|
||||
raw_cache: *mut GpuTensor,
|
||||
kv: *const GpuTensor,
|
||||
raw_cap: u32,
|
||||
pos: u32,
|
||||
rows: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_compressor_prefill_tensor(
|
||||
cache: *mut GpuTensor,
|
||||
state_kv: *mut GpuTensor,
|
||||
state_score: *mut GpuTensor,
|
||||
kv: *const GpuTensor,
|
||||
score: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
ape: u64,
|
||||
ape_type: u32,
|
||||
norm: u64,
|
||||
norm_type: u32,
|
||||
head_dim: u32,
|
||||
ratio: u32,
|
||||
pos: u32,
|
||||
rows: u32,
|
||||
rot: u32,
|
||||
original_context: u32,
|
||||
quantize_fp8: bool,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext_factor: f32,
|
||||
attn_factor: f32,
|
||||
beta_fast: f32,
|
||||
beta_slow: f32,
|
||||
rms_eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_compressor_prefill_ratio4_replay_tensor(
|
||||
cache: *mut GpuTensor,
|
||||
state_kv: *mut GpuTensor,
|
||||
state_score: *mut GpuTensor,
|
||||
kv: *const GpuTensor,
|
||||
score: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
ape: u64,
|
||||
ape_type: u32,
|
||||
norm: u64,
|
||||
norm_type: u32,
|
||||
head_dim: u32,
|
||||
pos: u32,
|
||||
rows: u32,
|
||||
rot: u32,
|
||||
original_context: u32,
|
||||
quantize_fp8: bool,
|
||||
freq_base: f32,
|
||||
freq_scale: f32,
|
||||
ext_factor: f32,
|
||||
attn_factor: f32,
|
||||
beta_fast: f32,
|
||||
beta_slow: f32,
|
||||
rms_eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_prefill_raw_heads_tensor(
|
||||
heads: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
sinks: u64,
|
||||
q: *const GpuTensor,
|
||||
raw: *const GpuTensor,
|
||||
rows: u32,
|
||||
window: u32,
|
||||
heads_count: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_prefill_static_mixed_heads_tensor(
|
||||
heads: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
sinks: u64,
|
||||
q: *const GpuTensor,
|
||||
raw: *const GpuTensor,
|
||||
compressed: *const GpuTensor,
|
||||
compressed_f16: u32,
|
||||
rows: u32,
|
||||
compressed_rows: u32,
|
||||
window: u32,
|
||||
ratio: u32,
|
||||
heads_count: u32,
|
||||
head_dim: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_indexer_scores_prefill_tensor(
|
||||
scores: *mut GpuTensor,
|
||||
q: *const GpuTensor,
|
||||
weights: *const GpuTensor,
|
||||
compressed: *const GpuTensor,
|
||||
compressed_rows: u32,
|
||||
rows: u32,
|
||||
heads: u32,
|
||||
head_dim: u32,
|
||||
ratio: u32,
|
||||
scale: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_output_q8_batch_f16_tensor(
|
||||
out_half: *mut GpuTensor,
|
||||
low: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight_a: u64,
|
||||
weight_b: u64,
|
||||
group_dim: u64,
|
||||
rank: u64,
|
||||
groups: u32,
|
||||
output: u64,
|
||||
heads: *const GpuTensor,
|
||||
rows: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_output_q8_batch_tensor(
|
||||
out: *mut GpuTensor,
|
||||
low: *mut GpuTensor,
|
||||
group_scratch: *mut GpuTensor,
|
||||
low_scratch: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight_a: u64,
|
||||
weight_b: u64,
|
||||
group_dim: u64,
|
||||
rank: u64,
|
||||
groups: u32,
|
||||
output: u64,
|
||||
heads: *const GpuTensor,
|
||||
rows: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_router_select_batch_tensor(
|
||||
selected: *mut GpuTensor,
|
||||
weights: *mut GpuTensor,
|
||||
probs: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
bias: u64,
|
||||
hash: u64,
|
||||
hash_rows: u32,
|
||||
expert_groups: u32,
|
||||
groups_used: u32,
|
||||
has_bias: bool,
|
||||
hash_mode: bool,
|
||||
logits: *const GpuTensor,
|
||||
tokens: *const GpuTensor,
|
||||
experts: u32,
|
||||
experts_used: u32,
|
||||
scale: f32,
|
||||
rows: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_routed_moe_batch_tensor(
|
||||
out: *mut GpuTensor,
|
||||
gate: *mut GpuTensor,
|
||||
up: *mut GpuTensor,
|
||||
mid: *mut GpuTensor,
|
||||
experts_out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
gate_weight: u64,
|
||||
up_weight: u64,
|
||||
down_weight: u64,
|
||||
gate_type: u32,
|
||||
down_type: u32,
|
||||
gate_expert_bytes: u64,
|
||||
gate_row_bytes: u64,
|
||||
down_expert_bytes: u64,
|
||||
down_row_bytes: u64,
|
||||
input: u32,
|
||||
middle: u32,
|
||||
output: u32,
|
||||
selected: *const GpuTensor,
|
||||
weights: *const GpuTensor,
|
||||
total_experts: u32,
|
||||
used_experts: u32,
|
||||
clamp: f32,
|
||||
x: *const GpuTensor,
|
||||
layer: u32,
|
||||
rows: u32,
|
||||
mid_f16: *mut bool,
|
||||
force_resident: bool,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_swiglu_tensor(
|
||||
out: *mut GpuTensor,
|
||||
gate: *const GpuTensor,
|
||||
up: *const GpuTensor,
|
||||
count: u32,
|
||||
clamp: f32,
|
||||
scale: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_hc_expand_split_half_tensor(
|
||||
out: *mut GpuTensor,
|
||||
block_half: *const GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
split: *const GpuTensor,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_hc_expand_split_tensor(
|
||||
out: *mut GpuTensor,
|
||||
block: *const GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
split: *const GpuTensor,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_hc_expand_add_split_tensor(
|
||||
out: *mut GpuTensor,
|
||||
block: *const GpuTensor,
|
||||
add: *const GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
split: *const GpuTensor,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_attention_output_low_q8_tensor(
|
||||
low: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
group_dim: u64,
|
||||
rank: u64,
|
||||
groups: u32,
|
||||
heads: *const GpuTensor,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_matmul_q8_0_hc_expand_tensor(
|
||||
out_hc: *mut GpuTensor,
|
||||
block_out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
input: u64,
|
||||
output: u64,
|
||||
x: *const GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
split: *const GpuTensor,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_router_select_tensor(
|
||||
selected: *mut GpuTensor,
|
||||
weights: *mut GpuTensor,
|
||||
probs: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
bias: u64,
|
||||
hash: u64,
|
||||
hash_rows: u32,
|
||||
token: u32,
|
||||
experts: u32,
|
||||
used: u32,
|
||||
scale: f32,
|
||||
expert_groups: u32,
|
||||
groups_used: u32,
|
||||
has_bias: bool,
|
||||
hash_mode: bool,
|
||||
logits: *const GpuTensor,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_routed_moe_one_tensor(
|
||||
out: *mut GpuTensor,
|
||||
gate: *mut GpuTensor,
|
||||
up: *mut GpuTensor,
|
||||
mid: *mut GpuTensor,
|
||||
experts_out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
gate_weight: u64,
|
||||
up_weight: u64,
|
||||
down_weight: u64,
|
||||
gate_type: u32,
|
||||
down_type: u32,
|
||||
gate_expert_bytes: u64,
|
||||
gate_row_bytes: u64,
|
||||
down_expert_bytes: u64,
|
||||
down_row_bytes: u64,
|
||||
input: u32,
|
||||
middle: u32,
|
||||
output: u32,
|
||||
selected: *const GpuTensor,
|
||||
weights: *const GpuTensor,
|
||||
total_experts: u32,
|
||||
used_experts: u32,
|
||||
clamp: f32,
|
||||
x: *const GpuTensor,
|
||||
add: *const GpuTensor,
|
||||
layer: u32,
|
||||
force_resident: bool,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_shared_gate_up_swiglu_q8_0_tensor(
|
||||
gate: *mut GpuTensor,
|
||||
up: *mut GpuTensor,
|
||||
mid: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
gate_weight: u64,
|
||||
up_weight: u64,
|
||||
input: u64,
|
||||
output: u64,
|
||||
x: *const GpuTensor,
|
||||
clamp: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_shared_down_hc_expand_q8_0_tensor(
|
||||
out_hc: *mut GpuTensor,
|
||||
shared_out: *mut GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
weight: u64,
|
||||
input: u64,
|
||||
output: u64,
|
||||
middle: *const GpuTensor,
|
||||
routed: *const GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
split: *const GpuTensor,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_output_hc_weights_tensor(
|
||||
out: *mut GpuTensor,
|
||||
pre: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
scale: u64,
|
||||
base: u64,
|
||||
hc: u32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_hc_weighted_sum_norm_tensor(
|
||||
out: *mut GpuTensor,
|
||||
norm: *mut GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
weights: *const GpuTensor,
|
||||
map: *const c_void,
|
||||
size: u64,
|
||||
norm_weight: u64,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
eps: f32,
|
||||
) -> i32;
|
||||
pub(super) fn ds4_gpu_hc_weighted_sum_tensor(
|
||||
out: *mut GpuTensor,
|
||||
residual: *const GpuTensor,
|
||||
weights: *const GpuTensor,
|
||||
embd: u32,
|
||||
hc: u32,
|
||||
) -> i32;
|
||||
}
|
||||
|
||||
pub(super) struct Context;
|
||||
|
||||
impl Context {
|
||||
pub(super) fn open(model: &Model, quality: bool) -> Result<Self, String> {
|
||||
check(unsafe { ds4_gpu_init() }, "Metal initialization")?;
|
||||
let data_offset = model.main.data_offset();
|
||||
if let Err(error) = check(
|
||||
unsafe {
|
||||
ds4_gpu_set_model_map_range(
|
||||
model.main.map_ptr().cast(),
|
||||
model.main.len(),
|
||||
data_offset,
|
||||
model.main.len() - data_offset,
|
||||
model.main.max_tensor_bytes(),
|
||||
)
|
||||
},
|
||||
"model mapping",
|
||||
) {
|
||||
unsafe { ds4_gpu_cleanup() };
|
||||
return Err(error);
|
||||
}
|
||||
unsafe { ds4_gpu_set_quality(quality) };
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ds4_gpu_cleanup() };
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Commands(bool);
|
||||
|
||||
impl Commands {
|
||||
pub(super) fn begin() -> Result<Self, String> {
|
||||
check(
|
||||
unsafe { ds4_gpu_begin_commands() },
|
||||
"beginning Metal commands",
|
||||
)?;
|
||||
Ok(Self(true))
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> Result<(), String> {
|
||||
self.0 = false;
|
||||
check(
|
||||
unsafe { ds4_gpu_end_commands() },
|
||||
"executing Metal commands",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Commands {
|
||||
fn drop(&mut self) {
|
||||
if self.0 {
|
||||
unsafe { ds4_gpu_end_commands() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct Buffer(NonNull<GpuTensor>);
|
||||
|
||||
impl Buffer {
|
||||
pub(super) fn floats(count: u64) -> Result<Self, String> {
|
||||
Self::bytes(count * 4)
|
||||
}
|
||||
|
||||
pub(super) fn bytes(bytes: u64) -> Result<Self, String> {
|
||||
NonNull::new(unsafe { ds4_gpu_tensor_alloc(bytes) })
|
||||
.map(Self)
|
||||
.ok_or_else(|| format!("Metal could not allocate {bytes} bytes"))
|
||||
}
|
||||
|
||||
pub(super) fn view(&self, offset: u64, bytes: u64) -> Result<Self, String> {
|
||||
NonNull::new(unsafe { ds4_gpu_tensor_view(self.raw(), offset, bytes) })
|
||||
.map(Self)
|
||||
.ok_or_else(|| "Metal could not create a tensor view".to_owned())
|
||||
}
|
||||
|
||||
pub(super) fn read_f32(&self, values: &mut [f32]) -> Result<(), String> {
|
||||
check(
|
||||
unsafe {
|
||||
ds4_gpu_tensor_read(
|
||||
self.raw(),
|
||||
0,
|
||||
values.as_mut_ptr().cast(),
|
||||
std::mem::size_of_val(values) as u64,
|
||||
)
|
||||
},
|
||||
"reading Metal output",
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn read(&self, offset: u64, values: &mut [u8]) -> Result<(), String> {
|
||||
check(
|
||||
unsafe {
|
||||
ds4_gpu_tensor_read(
|
||||
self.raw(),
|
||||
offset,
|
||||
values.as_mut_ptr().cast(),
|
||||
values.len() as u64,
|
||||
)
|
||||
},
|
||||
"reading a Metal buffer",
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn write(&self, offset: u64, values: &[u8]) -> Result<(), String> {
|
||||
check(
|
||||
unsafe {
|
||||
ds4_gpu_tensor_write(
|
||||
self.raw(),
|
||||
offset,
|
||||
values.as_ptr().cast(),
|
||||
values.len() as u64,
|
||||
)
|
||||
},
|
||||
"restoring a Metal buffer",
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn write_i32(&self, values: &[i32]) -> Result<(), String> {
|
||||
check(
|
||||
unsafe {
|
||||
ds4_gpu_tensor_write(
|
||||
self.raw(),
|
||||
0,
|
||||
values.as_ptr().cast(),
|
||||
std::mem::size_of_val(values) as u64,
|
||||
)
|
||||
},
|
||||
"uploading tokens",
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn fill(&self, value: f32, count: u64) -> Result<(), String> {
|
||||
call(
|
||||
unsafe { ds4_gpu_tensor_fill_f32(self.raw(), value, count) },
|
||||
"initializing a Metal buffer",
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn raw(&self) -> *mut GpuTensor {
|
||||
self.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Buffer {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ds4_gpu_tensor_free(self.raw()) };
|
||||
}
|
||||
}
|
||||
994
src/engine/validation.rs
Normal file
994
src/engine/validation.rs
Normal file
@@ -0,0 +1,994 @@
|
||||
use super::*;
|
||||
|
||||
pub(crate) fn validate_model_artifact(
|
||||
path: &Path,
|
||||
expected: ModelChoice,
|
||||
support: bool,
|
||||
) -> Result<(), String> {
|
||||
if support {
|
||||
let model = Gguf::open(path)?;
|
||||
validate_dspark(&model, &FLASH)
|
||||
} else {
|
||||
let model = Model::open_main(path, expected)?;
|
||||
let summary = model.summary();
|
||||
if summary.tensor_count == 0
|
||||
|| model
|
||||
.render_prompt("", "", ReasoningMode::Direct)
|
||||
.is_empty()
|
||||
{
|
||||
return Err("model intake produced an empty tensor directory or prompt".into());
|
||||
}
|
||||
let _ = model.tokenize("");
|
||||
let eos = model.eos_token();
|
||||
let _ = model.token_bytes(eos);
|
||||
if !model.is_stop_token(eos) {
|
||||
return Err("tokenizer EOS marker is not a stop token".into());
|
||||
}
|
||||
model.tensor_data("token_embd.weight")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape, String> {
|
||||
let family = if model.bytes("general.architecture").ok() == Some(b"glm-dsa") {
|
||||
ModelFamily::Glm
|
||||
} else {
|
||||
ModelFamily::DeepSeek
|
||||
};
|
||||
let shape = match family {
|
||||
ModelFamily::Glm => GLM,
|
||||
ModelFamily::DeepSeek => match model.u32("deepseek4.block_count")? {
|
||||
43 => FLASH,
|
||||
61 => PRO,
|
||||
layers => return Err(format!("unsupported DeepSeek layer count: {layers}")),
|
||||
},
|
||||
};
|
||||
if shape.model != expected {
|
||||
return Err(format!(
|
||||
"{} contains {}, but the selected model is {expected}",
|
||||
model.path().display(),
|
||||
shape.model
|
||||
));
|
||||
}
|
||||
validate_metadata(model, &shape)?;
|
||||
validate_tensors(model, &shape)?;
|
||||
Ok(shape)
|
||||
}
|
||||
|
||||
fn validate_metadata(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
let prefix = if shape.family == ModelFamily::Glm {
|
||||
"glm-dsa"
|
||||
} else {
|
||||
"deepseek4"
|
||||
};
|
||||
for (key, expected) in [
|
||||
("block_count", u64::from(shape.layers)),
|
||||
("embedding_length", shape.embd),
|
||||
("vocab_size", shape.vocab),
|
||||
("attention.head_count", shape.heads),
|
||||
("attention.head_count_kv", shape.head_kv),
|
||||
("attention.key_length", shape.head_dim),
|
||||
("attention.value_length", shape.value_dim),
|
||||
("rope.dimension_count", shape.rot),
|
||||
("attention.q_lora_rank", shape.lora_q),
|
||||
("expert_count", shape.experts),
|
||||
("expert_used_count", shape.experts_used),
|
||||
("expert_feed_forward_length", shape.ff_expert),
|
||||
("expert_shared_count", shape.expert_shared),
|
||||
("attention.indexer.head_count", shape.indexer_heads),
|
||||
("attention.indexer.key_length", shape.indexer_head_dim),
|
||||
("attention.indexer.top_k", shape.indexer_top_k),
|
||||
] {
|
||||
expect_u64(model, &format!("{prefix}.{key}"), expected)?;
|
||||
}
|
||||
expect_float(
|
||||
model,
|
||||
&format!("{prefix}.attention.layer_norm_rms_epsilon"),
|
||||
shape.rms_epsilon,
|
||||
)?;
|
||||
expect_float(
|
||||
model,
|
||||
&format!("{prefix}.expert_weights_scale"),
|
||||
shape.expert_weight_scale,
|
||||
)?;
|
||||
if !model.boolean(&format!("{prefix}.expert_weights_norm"))? {
|
||||
return Err(format!("{prefix}.expert_weights_norm must be true"));
|
||||
}
|
||||
expect_float(model, &format!("{prefix}.rope.freq_base"), shape.rope_base)?;
|
||||
|
||||
if shape.family == ModelFamily::Glm {
|
||||
for (key, expected) in [
|
||||
("context_length", shape.original_context),
|
||||
("feed_forward_length", shape.ff_dense),
|
||||
("attention.kv_lora_rank", shape.kv_lora),
|
||||
("attention.key_length_mla", shape.key_mla),
|
||||
("attention.value_length_mla", shape.value_mla),
|
||||
("expert_group_count", 1),
|
||||
("expert_group_used_count", 1),
|
||||
("expert_gating_func", 2),
|
||||
("leading_dense_block_count", u64::from(shape.leading_dense)),
|
||||
("nextn_predict_layers", u64::from(shape.nextn)),
|
||||
] {
|
||||
expect_u64(model, &format!("{prefix}.{key}"), expected)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (key, expected) in [
|
||||
("attention.output_group_count", shape.out_groups),
|
||||
("attention.output_lora_rank", shape.lora_o),
|
||||
("hash_layer_count", u64::from(shape.hash_layers)),
|
||||
("attention.sliding_window", shape.sliding_window),
|
||||
("hyper_connection.count", shape.hc),
|
||||
("hyper_connection.sinkhorn_iterations", shape.hc_sinkhorn),
|
||||
] {
|
||||
expect_u64(model, &format!("{prefix}.{key}"), expected)?;
|
||||
}
|
||||
for key in ["expert_group_count", "expert_group_used_count"] {
|
||||
if let Some(Value::U32(value)) = model.metadata.get(&format!("{prefix}.{key}"))
|
||||
&& *value != 0
|
||||
{
|
||||
return Err(format!("{prefix}.{key} must be zero"));
|
||||
}
|
||||
}
|
||||
for (key, expected) in [
|
||||
("hyper_connection.epsilon", shape.hc_epsilon),
|
||||
(
|
||||
"attention.compress_rope_freq_base",
|
||||
shape.compress_rope_base,
|
||||
),
|
||||
] {
|
||||
expect_float(model, &format!("{prefix}.{key}"), expected)?;
|
||||
}
|
||||
for (key, expected) in [
|
||||
("rope.scaling.factor", shape.rope_scale),
|
||||
("rope.scaling.yarn_beta_fast", shape.rope_beta_fast),
|
||||
("rope.scaling.yarn_beta_slow", shape.rope_beta_slow),
|
||||
] {
|
||||
if model.metadata.contains_key(&format!("{prefix}.{key}")) {
|
||||
expect_float(model, &format!("{prefix}.{key}"), expected)?;
|
||||
}
|
||||
}
|
||||
if model
|
||||
.metadata
|
||||
.contains_key("deepseek4.rope.scaling.original_context_length")
|
||||
{
|
||||
expect_u64(
|
||||
model,
|
||||
"deepseek4.rope.scaling.original_context_length",
|
||||
shape.original_context,
|
||||
)?;
|
||||
}
|
||||
let ratios = model.u32s("deepseek4.attention.compress_ratios")?;
|
||||
let clamps = model.f32s("deepseek4.swiglu_clamp_exp")?;
|
||||
if ratios.len() < shape.layers as usize || clamps.len() < shape.layers as usize {
|
||||
return Err("DeepSeek per-layer metadata is shorter than the layer count".into());
|
||||
}
|
||||
for layer in 0..shape.layers as usize {
|
||||
let expected = compression_ratio(shape, layer as u32);
|
||||
if ratios[layer] != expected {
|
||||
return Err(format!(
|
||||
"layer {layer} compression ratio is {}, expected {expected}",
|
||||
ratios[layer]
|
||||
));
|
||||
}
|
||||
if !float_eq(clamps[layer], shape.swiglu_clamp) {
|
||||
return Err(format!("layer {layer} has an invalid SwiGLU clamp"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
match shape.family {
|
||||
ModelFamily::DeepSeek => validate_deepseek_tensors(model, shape),
|
||||
ModelFamily::Glm => validate_glm_tensors(model, shape),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_deepseek_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
let hc_dim = shape.embd * shape.hc;
|
||||
let hc_mix = 2 * shape.hc + shape.hc * shape.hc;
|
||||
let q_dim = shape.heads * shape.head_dim;
|
||||
let output_low = shape.out_groups * shape.lora_o;
|
||||
expect(
|
||||
model,
|
||||
"token_embd.weight",
|
||||
&[F16],
|
||||
&[shape.embd, shape.vocab],
|
||||
)?;
|
||||
expect(model, "output_hc_base.weight", &[F32], &[shape.hc])?;
|
||||
expect(model, "output_hc_fn.weight", &[F16], &[hc_dim, shape.hc])?;
|
||||
expect(model, "output_hc_scale.weight", &[F32], &[1])?;
|
||||
expect(model, "output_norm.weight", &[F32], &[shape.embd])?;
|
||||
expect(model, "output.weight", DENSE, &[shape.embd, shape.vocab])?;
|
||||
|
||||
for layer in 0..shape.layers {
|
||||
let name = |suffix: &str| format!("blk.{layer}.{suffix}");
|
||||
expect(model, &name("hc_attn_fn.weight"), &[F16], &[hc_dim, hc_mix])?;
|
||||
expect(model, &name("hc_attn_scale.weight"), &[F32], &[3])?;
|
||||
expect(model, &name("hc_attn_base.weight"), &[F32], &[hc_mix])?;
|
||||
expect(model, &name("attn_norm.weight"), &[F32], &[shape.embd])?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_q_a.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.lora_q],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_q_a_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.lora_q],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_q_b.weight"),
|
||||
DENSE,
|
||||
&[shape.lora_q, q_dim],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_kv.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.head_dim],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_kv_a_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.head_dim],
|
||||
)?;
|
||||
expect(model, &name("attn_sinks.weight"), &[F32], &[shape.heads])?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_output_a.weight"),
|
||||
DENSE,
|
||||
&[
|
||||
shape.head_dim * (shape.heads / shape.out_groups),
|
||||
output_low,
|
||||
],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_output_b.weight"),
|
||||
DENSE,
|
||||
&[output_low, shape.embd],
|
||||
)?;
|
||||
|
||||
let ratio = compression_ratio(shape, layer);
|
||||
if ratio != 0 {
|
||||
let compression_width = if ratio == 4 { 2 } else { 1 } * shape.head_dim;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_compressor_ape.weight"),
|
||||
&[F16],
|
||||
&[compression_width, u64::from(ratio)],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_compressor_kv.weight"),
|
||||
&[F16],
|
||||
&[shape.embd, compression_width],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_compressor_gate.weight"),
|
||||
&[F16],
|
||||
&[shape.embd, compression_width],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_compressor_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.head_dim],
|
||||
)?;
|
||||
}
|
||||
if ratio == 4 {
|
||||
let index_q = shape.indexer_heads * shape.indexer_head_dim;
|
||||
let index_width = 2 * shape.indexer_head_dim;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer.attn_q_b.weight"),
|
||||
&[F16, Q8_0],
|
||||
&[shape.lora_q, index_q],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer.proj.weight"),
|
||||
&[F16],
|
||||
&[shape.embd, shape.indexer_heads],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer_compressor_ape.weight"),
|
||||
&[F16],
|
||||
&[index_width, 4],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer_compressor_kv.weight"),
|
||||
&[F16],
|
||||
&[shape.embd, index_width],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer_compressor_gate.weight"),
|
||||
&[F16],
|
||||
&[shape.embd, index_width],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer_compressor_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.indexer_head_dim],
|
||||
)?;
|
||||
}
|
||||
expect(model, &name("hc_ffn_fn.weight"), &[F16], &[hc_dim, hc_mix])?;
|
||||
expect(model, &name("hc_ffn_scale.weight"), &[F32], &[3])?;
|
||||
expect(model, &name("hc_ffn_base.weight"), &[F32], &[hc_mix])?;
|
||||
expect(model, &name("ffn_norm.weight"), &[F32], &[shape.embd])?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate_inp.weight"),
|
||||
&[F16],
|
||||
&[shape.embd, shape.experts],
|
||||
)?;
|
||||
expect_optional(model, &name("exp_probs_b.bias"), &[F32], &[shape.experts])?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate_exps.weight"),
|
||||
ROUTED,
|
||||
&[shape.embd, shape.ff_expert, shape.experts],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_up_exps.weight"),
|
||||
ROUTED,
|
||||
&[shape.embd, shape.ff_expert, shape.experts],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_down_exps.weight"),
|
||||
ROUTED,
|
||||
&[shape.ff_expert, shape.embd, shape.experts],
|
||||
)?;
|
||||
same_type(
|
||||
model,
|
||||
&name("ffn_gate_exps.weight"),
|
||||
&name("ffn_up_exps.weight"),
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate_shexp.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.ff_expert],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_up_shexp.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.ff_expert],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_down_shexp.weight"),
|
||||
DENSE,
|
||||
&[shape.ff_expert, shape.embd],
|
||||
)?;
|
||||
if layer < shape.hash_layers {
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate_tid2eid.weight"),
|
||||
&[I32],
|
||||
&[shape.experts_used, shape.vocab],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_glm_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
let q_dim = shape.heads * shape.key_mla;
|
||||
let q_nope = shape.key_mla - shape.rot;
|
||||
let index_q = shape.indexer_heads * shape.indexer_head_dim;
|
||||
expect(
|
||||
model,
|
||||
"token_embd.weight",
|
||||
DENSE,
|
||||
&[shape.embd, shape.vocab],
|
||||
)?;
|
||||
expect(model, "output_norm.weight", &[F32], &[shape.embd])?;
|
||||
expect(model, "output.weight", DENSE, &[shape.embd, shape.vocab])?;
|
||||
for layer in 0..shape.layers {
|
||||
let name = |suffix: &str| format!("blk.{layer}.{suffix}");
|
||||
expect(model, &name("attn_norm.weight"), &[F32], &[shape.embd])?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_q_a.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.lora_q],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_q_a_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.lora_q],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_q_b.weight"),
|
||||
DENSE,
|
||||
&[shape.lora_q, q_dim],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_kv_a_mqa.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.head_dim],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_kv_a_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.kv_lora],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_k_b.weight"),
|
||||
DENSE,
|
||||
&[q_nope, shape.kv_lora, shape.heads],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_v_b.weight"),
|
||||
DENSE,
|
||||
&[shape.kv_lora, shape.value_mla, shape.heads],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("attn_output.weight"),
|
||||
DENSE,
|
||||
&[shape.heads * shape.value_mla, shape.embd],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer.attn_k.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.indexer_head_dim],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer.attn_q_b.weight"),
|
||||
DENSE,
|
||||
&[shape.lora_q, index_q],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer.k_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.indexer_head_dim],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer.k_norm.bias"),
|
||||
&[F32],
|
||||
&[shape.indexer_head_dim],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("indexer.proj.weight"),
|
||||
&[F32],
|
||||
&[shape.embd, shape.indexer_heads],
|
||||
)?;
|
||||
expect(model, &name("ffn_norm.weight"), &[F32], &[shape.embd])?;
|
||||
if layer < shape.leading_dense {
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.ff_dense],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_up.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.ff_dense],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_down.weight"),
|
||||
DENSE,
|
||||
&[shape.ff_dense, shape.embd],
|
||||
)?;
|
||||
} else {
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate_inp.weight"),
|
||||
&[F32],
|
||||
&[shape.embd, shape.experts],
|
||||
)?;
|
||||
expect(model, &name("exp_probs_b.bias"), &[F32], &[shape.experts])?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate_exps.weight"),
|
||||
ROUTED,
|
||||
&[shape.embd, shape.ff_expert, shape.experts],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_up_exps.weight"),
|
||||
ROUTED,
|
||||
&[shape.embd, shape.ff_expert, shape.experts],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_down_exps.weight"),
|
||||
ROUTED,
|
||||
&[shape.ff_expert, shape.embd, shape.experts],
|
||||
)?;
|
||||
same_type(
|
||||
model,
|
||||
&name("ffn_gate_exps.weight"),
|
||||
&name("ffn_up_exps.weight"),
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_gate_shexp.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.ff_expert],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_up_shexp.weight"),
|
||||
DENSE,
|
||||
&[shape.embd, shape.ff_expert],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&name("ffn_down_shexp.weight"),
|
||||
DENSE,
|
||||
&[shape.ff_expert, shape.embd],
|
||||
)?;
|
||||
}
|
||||
if layer + shape.nextn >= shape.layers {
|
||||
expect(
|
||||
model,
|
||||
&name("nextn.eh_proj.weight"),
|
||||
DENSE,
|
||||
&[2 * shape.embd, shape.embd],
|
||||
)?;
|
||||
expect(model, &name("nextn.enorm.weight"), &[F32], &[shape.embd])?;
|
||||
expect(model, &name("nextn.hnorm.weight"), &[F32], &[shape.embd])?;
|
||||
expect(
|
||||
model,
|
||||
&name("nextn.shared_head_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.embd],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn validate_dspark(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||
if shape.model != ModelChoice::DeepSeekV4Flash {
|
||||
return Err("DSpark support is available only for DeepSeek V4 Flash".into());
|
||||
}
|
||||
let block_size = first_u32(
|
||||
model,
|
||||
&[
|
||||
"deepseek4.dspark.block_size",
|
||||
"deepseek4.dspark_block_size",
|
||||
"dspark.block_size",
|
||||
],
|
||||
)?;
|
||||
let markov_rank = first_u32(
|
||||
model,
|
||||
&[
|
||||
"deepseek4.dspark.markov_rank",
|
||||
"deepseek4.dspark_markov_rank",
|
||||
"dspark.markov_rank",
|
||||
],
|
||||
)?;
|
||||
let noise_token = first_u32(
|
||||
model,
|
||||
&[
|
||||
"deepseek4.dspark.noise_token_id",
|
||||
"deepseek4.dspark_noise_token_id",
|
||||
"dspark.noise_token_id",
|
||||
],
|
||||
)?;
|
||||
let targets = first_u32s(
|
||||
model,
|
||||
&[
|
||||
"deepseek4.dspark.target_layer_ids",
|
||||
"deepseek4.dspark_target_layer_ids",
|
||||
"dspark.target_layer_ids",
|
||||
],
|
||||
)?;
|
||||
if !(1..=16).contains(&block_size) || markov_rank == 0 || noise_token >= shape.vocab as u32 {
|
||||
return Err("invalid DSpark block, Markov, or noise-token metadata".into());
|
||||
}
|
||||
if targets.is_empty()
|
||||
|| targets.len() > 8
|
||||
|| targets.windows(2).any(|pair| pair[0] >= pair[1])
|
||||
|| targets.iter().any(|layer| *layer >= shape.layers)
|
||||
{
|
||||
return Err("invalid DSpark target-layer metadata".into());
|
||||
}
|
||||
let stages = model
|
||||
.tensors
|
||||
.keys()
|
||||
.filter_map(|name| {
|
||||
name.strip_prefix("mtp.")?
|
||||
.split('.')
|
||||
.next()?
|
||||
.parse::<u32>()
|
||||
.ok()
|
||||
})
|
||||
.max()
|
||||
.map_or(0, |stage| stage + 1);
|
||||
if !(1..=8).contains(&stages) {
|
||||
return Err(format!("invalid DSpark stage count: {stages}"));
|
||||
}
|
||||
for stage in 0..stages {
|
||||
validate_dspark_block(model, shape, stage)?;
|
||||
if stage == 0 {
|
||||
expect(
|
||||
model,
|
||||
&format!("mtp.{stage}.main_proj.weight"),
|
||||
DSPARK_DENSE,
|
||||
&[targets.len() as u64 * shape.embd, shape.embd],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&format!("mtp.{stage}.main_norm.weight"),
|
||||
&[F32],
|
||||
&[shape.embd],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
let prefix = format!("mtp.{}", stages - 1);
|
||||
expect(
|
||||
model,
|
||||
&format!("{prefix}.norm.weight"),
|
||||
&[F32],
|
||||
&[shape.embd],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&format!("{prefix}.hc_head_base.weight"),
|
||||
&[F32],
|
||||
&[shape.hc],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&format!("{prefix}.hc_head_fn.weight"),
|
||||
PLAIN,
|
||||
&[shape.embd * shape.hc, shape.hc],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&format!("{prefix}.hc_head_scale.weight"),
|
||||
&[F32],
|
||||
&[1],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&format!("{prefix}.markov_head.markov_w1.weight"),
|
||||
DSPARK_DENSE,
|
||||
&[u64::from(markov_rank), shape.vocab],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&format!("{prefix}.markov_head.markov_w2.weight"),
|
||||
DSPARK_DENSE,
|
||||
&[u64::from(markov_rank), shape.vocab],
|
||||
)?;
|
||||
expect(
|
||||
model,
|
||||
&format!("{prefix}.confidence_head.proj.weight"),
|
||||
DSPARK_DENSE,
|
||||
&[shape.embd + u64::from(markov_rank), 1],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_dspark_block(model: &Gguf, shape: &Shape, stage: u32) -> Result<(), String> {
|
||||
let hc_dim = shape.embd * shape.hc;
|
||||
let hc_mix = 2 * shape.hc + shape.hc * shape.hc;
|
||||
let q_dim = shape.heads * shape.head_dim;
|
||||
let output_low = shape.out_groups * shape.lora_o;
|
||||
let name = |suffix: &str| format!("mtp.{stage}.{suffix}");
|
||||
for (suffix, types, dims) in [
|
||||
("hc_attn_fn.weight", PLAIN, vec![hc_dim, hc_mix]),
|
||||
("hc_attn_scale.weight", &[F32][..], vec![3]),
|
||||
("hc_attn_base.weight", &[F32][..], vec![hc_mix]),
|
||||
("attn_norm.weight", &[F32][..], vec![shape.embd]),
|
||||
(
|
||||
"attn_q_a.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![shape.embd, shape.lora_q],
|
||||
),
|
||||
("attn_q_a_norm.weight", &[F32][..], vec![shape.lora_q]),
|
||||
("attn_q_b.weight", DSPARK_DENSE, vec![shape.lora_q, q_dim]),
|
||||
(
|
||||
"attn_kv.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![shape.embd, shape.head_dim],
|
||||
),
|
||||
("attn_kv_a_norm.weight", &[F32][..], vec![shape.head_dim]),
|
||||
("attn_sinks.weight", &[F32][..], vec![shape.heads]),
|
||||
(
|
||||
"attn_output_a.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![
|
||||
shape.head_dim * (shape.heads / shape.out_groups),
|
||||
output_low,
|
||||
],
|
||||
),
|
||||
(
|
||||
"attn_output_b.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![output_low, shape.embd],
|
||||
),
|
||||
("hc_ffn_fn.weight", PLAIN, vec![hc_dim, hc_mix]),
|
||||
("hc_ffn_scale.weight", &[F32][..], vec![3]),
|
||||
("hc_ffn_base.weight", &[F32][..], vec![hc_mix]),
|
||||
("ffn_norm.weight", &[F32][..], vec![shape.embd]),
|
||||
(
|
||||
"ffn_gate_inp.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![shape.embd, shape.experts],
|
||||
),
|
||||
("exp_probs_b.bias", &[F32][..], vec![shape.experts]),
|
||||
(
|
||||
"ffn_gate_exps.weight",
|
||||
ROUTED,
|
||||
vec![shape.embd, shape.ff_expert, shape.experts],
|
||||
),
|
||||
(
|
||||
"ffn_up_exps.weight",
|
||||
ROUTED,
|
||||
vec![shape.embd, shape.ff_expert, shape.experts],
|
||||
),
|
||||
(
|
||||
"ffn_down_exps.weight",
|
||||
ROUTED,
|
||||
vec![shape.ff_expert, shape.embd, shape.experts],
|
||||
),
|
||||
(
|
||||
"ffn_gate_shexp.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![shape.embd, shape.ff_expert],
|
||||
),
|
||||
(
|
||||
"ffn_up_shexp.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![shape.embd, shape.ff_expert],
|
||||
),
|
||||
(
|
||||
"ffn_down_shexp.weight",
|
||||
DSPARK_DENSE,
|
||||
vec![shape.ff_expert, shape.embd],
|
||||
),
|
||||
] {
|
||||
expect(model, &name(suffix), types, &dims)?;
|
||||
}
|
||||
same_type(
|
||||
model,
|
||||
&name("ffn_gate_exps.weight"),
|
||||
&name("ffn_up_exps.weight"),
|
||||
)
|
||||
}
|
||||
|
||||
fn expect(model: &Gguf, name: &str, types: &[u32], dims: &[u64]) -> Result<(), String> {
|
||||
validate_tensor(name, model.tensor(name)?, types, dims)
|
||||
}
|
||||
|
||||
fn expect_optional(model: &Gguf, name: &str, types: &[u32], dims: &[u64]) -> Result<(), String> {
|
||||
match model.tensors.get(name) {
|
||||
Some(tensor) => validate_tensor(name, tensor, types, dims),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_tensor(name: &str, tensor: &Tensor, types: &[u32], dims: &[u64]) -> Result<(), String> {
|
||||
if !types.contains(&tensor.kind) {
|
||||
return Err(format!(
|
||||
"tensor {name} has unsupported type {}",
|
||||
tensor.kind
|
||||
));
|
||||
}
|
||||
if tensor.dims != dims {
|
||||
return Err(format!(
|
||||
"tensor {name} has dimensions {:?}, expected {dims:?}",
|
||||
tensor.dims
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn same_type(model: &Gguf, first: &str, second: &str) -> Result<(), String> {
|
||||
if model.tensor(first)?.kind != model.tensor(second)?.kind {
|
||||
Err(format!(
|
||||
"tensors {first} and {second} use different quantizations"
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_u64(model: &Gguf, key: &str, expected: u64) -> Result<(), String> {
|
||||
let actual = model.u64(key)?;
|
||||
if actual == expected {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("{key} is {actual}, expected {expected}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_float(model: &Gguf, key: &str, expected: f32) -> Result<(), String> {
|
||||
let actual = model.f32(key)?;
|
||||
if float_eq(actual, expected) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("{key} is {actual}, expected {expected}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn float_eq(actual: f32, expected: f32) -> bool {
|
||||
actual.is_finite() && (actual - expected).abs() <= expected.abs().max(1.0) * 1.0e-6
|
||||
}
|
||||
|
||||
fn compression_ratio(shape: &Shape, layer: u32) -> u32 {
|
||||
match shape.model {
|
||||
ModelChoice::DeepSeekV4Flash if layer < 2 => 0,
|
||||
ModelChoice::DeepSeekV4Pro if layer < 2 => 128,
|
||||
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Pro if layer.is_multiple_of(2) => 4,
|
||||
ModelChoice::DeepSeekV4Flash | ModelChoice::DeepSeekV4Pro => 128,
|
||||
ModelChoice::Glm52 => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn first_u32(model: &Gguf, keys: &[&str]) -> Result<u32, String> {
|
||||
keys.iter()
|
||||
.find_map(|key| model.u32(key).ok())
|
||||
.ok_or_else(|| format!("required DSpark metadata is missing: {}", keys[0]))
|
||||
}
|
||||
|
||||
fn first_u32s<'a>(model: &'a Gguf, keys: &[&str]) -> Result<&'a [u32], String> {
|
||||
keys.iter()
|
||||
.find_map(|key| model.u32s(key).ok())
|
||||
.ok_or_else(|| format!("required DSpark metadata is missing: {}", keys[0]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn installed_ds4_fixture_opens_and_renders_a_prompt() {
|
||||
let path = Path::new(
|
||||
"../ds4/gguf/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2-imatrix.gguf",
|
||||
);
|
||||
if !path.exists() {
|
||||
return;
|
||||
}
|
||||
let model = Model::open_main(path, ModelChoice::DeepSeekV4Flash).unwrap();
|
||||
let summary = model.summary();
|
||||
assert_eq!(summary.model, ModelChoice::DeepSeekV4Flash);
|
||||
assert_eq!(summary.vocabulary_size, 129_280);
|
||||
assert_eq!(
|
||||
model.tokenize("Hello, world! 1234\nint café = 7;\n中文テスト"),
|
||||
[
|
||||
19_923, 14, 2_058, 3, 223, 6_895, 22, 201, 650, 57_664, 438, 223, 25, 510, 21_134,
|
||||
109_288,
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
model.render_prompt("", "Hello", ReasoningMode::Direct),
|
||||
[0, 128_803, 19_923, 128_804, 128_822]
|
||||
);
|
||||
assert_eq!(model.tokenizer.tokenize_rendered("|DSML|").len(), 1);
|
||||
assert!(model.is_stop_token_for_reasoning(128_822, ReasoningMode::Direct));
|
||||
assert!(!model.is_stop_token_for_reasoning(128_822, ReasoningMode::High));
|
||||
let think_start = *model
|
||||
.render_prompt("", "Hello", ReasoningMode::High)
|
||||
.last()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
model.render_conversation(
|
||||
"",
|
||||
&[
|
||||
ChatTurn {
|
||||
user: true,
|
||||
skip_previous_eos: false,
|
||||
reasoning: None,
|
||||
reasoning_complete: true,
|
||||
content: "Hello".into(),
|
||||
},
|
||||
ChatTurn {
|
||||
user: false,
|
||||
skip_previous_eos: false,
|
||||
reasoning: None,
|
||||
reasoning_complete: true,
|
||||
content: "Hello".into(),
|
||||
},
|
||||
ChatTurn {
|
||||
user: true,
|
||||
skip_previous_eos: false,
|
||||
reasoning: None,
|
||||
reasoning_complete: true,
|
||||
content: "Hello".into(),
|
||||
},
|
||||
],
|
||||
ReasoningMode::Direct,
|
||||
),
|
||||
[
|
||||
0, 128_803, 19_923, 128_804, 128_822, 19_923, 1, 128_803, 19_923, 128_804, 128_822,
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
model.render_continuation("Hello", ReasoningMode::Direct, false),
|
||||
[1, 128_803, 19_923, 128_804, 128_822]
|
||||
);
|
||||
assert_eq!(
|
||||
model.render_continuation("Hello", ReasoningMode::Direct, true),
|
||||
[128_803, 19_923, 128_804, 128_822]
|
||||
);
|
||||
assert_eq!(
|
||||
model.render_conversation(
|
||||
"",
|
||||
&[
|
||||
ChatTurn {
|
||||
user: true,
|
||||
skip_previous_eos: false,
|
||||
reasoning: None,
|
||||
reasoning_complete: true,
|
||||
content: "Hello".into(),
|
||||
},
|
||||
ChatTurn {
|
||||
user: false,
|
||||
skip_previous_eos: false,
|
||||
reasoning: Some("Hello".into()),
|
||||
reasoning_complete: true,
|
||||
content: "Hello".into(),
|
||||
},
|
||||
ChatTurn {
|
||||
user: true,
|
||||
skip_previous_eos: false,
|
||||
reasoning: None,
|
||||
reasoning_complete: true,
|
||||
content: "Hello".into(),
|
||||
},
|
||||
],
|
||||
ReasoningMode::Direct,
|
||||
),
|
||||
[
|
||||
0,
|
||||
128_803,
|
||||
19_923,
|
||||
128_804,
|
||||
think_start,
|
||||
19_923,
|
||||
128_822,
|
||||
19_923,
|
||||
1,
|
||||
128_803,
|
||||
19_923,
|
||||
128_804,
|
||||
128_822,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installed_dspark_fixture_passes_the_target_layout() {
|
||||
let path = Path::new("../ds4/gguf/DeepSeek-V4-Flash-DSpark-support.gguf");
|
||||
if path.exists() {
|
||||
validate_model_artifact(path, ModelChoice::DeepSeekV4Flash, true).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user