Add GLM 5.3 Flash support
This commit is contained in:
1 parent
9a33c61ea6
commit
46d6a976a5
31 files changed
+7806
-447
No files matched your search
@@ -4,6 +4,7 @@
|
|||||||
- Keep changes focused, handle errors explicitly, and add the smallest useful test for non-trivial behavior.
|
- Keep changes focused, handle errors explicitly, and add the smallest useful test for non-trivial behavior.
|
||||||
- Preserve `rustfmt` output and keep Clippy warning-free.
|
- Preserve `rustfmt` output and keep Clippy warning-free.
|
||||||
- Treat DS4 as the behavioral oracle. Model execution, token processing, context accounting, and KV-cache behavior must remain identical to DS4. Differences are bugs unless they are unavoidable Rust/platform requirements and the user explicitly approves them before implementation; when uncertain, preserve DS4 behavior.
|
- Treat DS4 as the behavioral oracle. Model execution, token processing, context accounting, and KV-cache behavior must remain identical to DS4. Differences are bugs unless they are unavoidable Rust/platform requirements and the user explicitly approves them before implementation; when uncertain, preserve DS4 behavior.
|
||||||
|
- Keep all application, model-loading, inference-graph, session, and orchestration code in Rust. Never copy, vendor, compile, or call DS4's C implementation; use it only as a behavioral and performance reference. Native code is limited to the Objective-C Metal bridge and `.metal` kernels required to execute GPU work.
|
||||||
- This is not a GitHub project. Use direct `git` commands for version control and the `tea` CLI for forge operations; do not use GitHub tools or workflows.
|
- This is not a GitHub project. Use direct `git` commands for version control and the `tea` CLI for forge operations; do not use GitHub tools or workflows.
|
||||||
- Issues are managed with the command "tea" run from the project directory.
|
- Issues are managed with the command "tea" run from the project directory.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,69 @@
|
|||||||
# Apple Metal parity for issue #78
|
# Apple Metal parity for issue #78
|
||||||
|
|
||||||
|
## GLM 5.3 Flash parity target
|
||||||
|
|
||||||
|
- Previous DS4 oracle: `8db89fe083ae4d17c9a2428ccd29803d3ae8f577`
|
||||||
|
- Current DS4 oracle: `ec7642cdd9ec81d01ad4b1fd8f8a3d1511533748`
|
||||||
|
- Main artifact: `GLM-5.3-Flash-Q2.gguf`, SHA-256
|
||||||
|
`e81fd6241c6e55a64e1e14e47a3eab61a173fa8d7e4b5c1d1848827119705b32`
|
||||||
|
- Vision artifact: `GLM-5.3-Flash-Vision-Encoder.gguf`, SHA-256
|
||||||
|
`ae23e14c6979e889051b2e4a39351abcdafb161e18e606fae4d8c40095a4bf3a`
|
||||||
|
- Machine: MacBook Pro `Mac17,7`, Apple M5 Max, 128 GiB unified memory,
|
||||||
|
resident Q2 weights
|
||||||
|
|
||||||
|
The applicable commits after the previous oracle are:
|
||||||
|
|
||||||
|
| DS4 commit(s) | Scope carried into DS4Server |
|
||||||
|
| --- | --- |
|
||||||
|
| `147109a` | GLM 5.3 shape, GGUF binding, KDA/DSA attention, hyperconnections, routed MoE, BF16 tensors, output head, integrated MTP, and Metal kernels |
|
||||||
|
| `4771329`, `9f95d9f` | Vision encoder, image preprocessing, prompt overlays, and compact-prefill correctness |
|
||||||
|
| `b0c31af` | Pooled indexer cache, bounded attention memory, and batched prefill |
|
||||||
|
| `747eafc`, `1df8ff0` | Fixed-image encoder oracle, replay/logit checks, and multimodal quality gates |
|
||||||
|
| `8adb221` | Directional attention and FFN steering at the DS4 residual points |
|
||||||
|
| `639d4eb` | Continued-prefill cache and position behavior |
|
||||||
|
| `6cf658a` | BF16 decode projection schedule and Metal kernel specialization |
|
||||||
|
| `01f3316`, `81c8c39`, `5da3206` | Current Metal compact-cache ABI, Q2/full-checkpoint binding, and GLM streaming compatibility |
|
||||||
|
|
||||||
|
CUDA- and ROCm-only commits in the range are excluded. The Rust release gates
|
||||||
|
compare three-run medians against this exact DS4 build, require identical
|
||||||
|
greedy token hashes, and fail only when Rust is more than 2% slower than DS4;
|
||||||
|
there is no upper bound when Rust is faster. Text measures an
|
||||||
|
8192-token frontier plus steady decode, both with and without integrated MTP.
|
||||||
|
Vision compares the fixed encoder embedding (cosine at least 0.995, mean
|
||||||
|
absolute error at most 0.001, maximum error at most 0.06), encoder latency,
|
||||||
|
multimodal prefill, steady decode, image-conditioned logits, and replay.
|
||||||
|
|
||||||
|
### GLM 5.3 Flash acceptance results
|
||||||
|
|
||||||
|
The text fixture is `ds4.c`, rendered as one no-system chat turn (`-sys ''` in
|
||||||
|
DS4), truncated to exactly 8192 tokens, with a 32768-token allocation and 64
|
||||||
|
greedy output tokens. Same-process three-run medians are used because both DS4
|
||||||
|
and DS4Server exhibit a load-order/thermal artifact with this 96 GB model.
|
||||||
|
|
||||||
|
| Capability | Metric | DS4 | DS4Server | Result |
|
||||||
|
| --- | --- | ---: | ---: | --- |
|
||||||
|
| Text | Prefill tok/s | 288.91 | 295.21 | Rust 2.18% faster |
|
||||||
|
| Text | Decode tok/s | 22.58 | 22.78 | Rust 0.89% faster |
|
||||||
|
| Integrated MTP | Decode tok/s | 26.55 | 26.80 | Rust 0.93% faster |
|
||||||
|
| Vision encoder | Fixed-image seconds | 2.6255 | 2.4354 | Rust 7.24% faster |
|
||||||
|
| Vision prompt | Prefill tok/s | 185.81 | 201.51 | Rust 8.45% faster |
|
||||||
|
| Vision prompt | Decode tok/s | 26.40 | 27.21 | Rust 3.09% faster |
|
||||||
|
|
||||||
|
Text and MTP produced the same token SHA-256,
|
||||||
|
`0b61e613708d8367fc87f5a32fbfd4ba098b40ee920645b1e38381e867131108`.
|
||||||
|
The 1094-token fixed-image prompt produced the same vision token SHA-256,
|
||||||
|
`424fdadc10c0a2bd95b405a3985151e84aa844a209d2a2ca72cd01c39dfa53db`.
|
||||||
|
The Rust vision embedding compared with the DS4 oracle at cosine
|
||||||
|
`0.999958726`, mean absolute error `0.000036702`, and maximum error
|
||||||
|
`0.006153620`.
|
||||||
|
|
||||||
|
The checkpoint advertises a 1048576-token maximum. On this 128 GiB machine,
|
||||||
|
32768 is the default manageable context and 50000 is the extended option: the
|
||||||
|
50000-token run used 3.51 GiB of live runtime tensors in addition to 90.61 GiB
|
||||||
|
of mapped model wrappers and retained the exact output hash. Larger allocations
|
||||||
|
leave too little operating-system and application headroom for a reliable
|
||||||
|
default.
|
||||||
|
|
||||||
## Frozen comparison
|
## Frozen comparison
|
||||||
|
|
||||||
- DS4 oracle: `8db89fe083ae4d17c9a2428ccd29803d3ae8f577`
|
- DS4 oracle: `8db89fe083ae4d17c9a2428ccd29803d3ae8f577`
|
||||||
|
|||||||
+227
-47
@@ -195,6 +195,17 @@ struct ds4_metal_args_glm_store_indexer_k {
|
|||||||
float pad1;
|
float pad1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ds4_metal_args_glm53_indexer_pool_update {
|
||||||
|
uint32_t pos0;
|
||||||
|
uint32_t n_tokens;
|
||||||
|
uint32_t cache_cap;
|
||||||
|
uint32_t head_dim;
|
||||||
|
uint32_t pool_size;
|
||||||
|
uint32_t cache_f16;
|
||||||
|
float eps;
|
||||||
|
uint32_t pad0;
|
||||||
|
};
|
||||||
|
|
||||||
struct ds4_metal_args_glm_attention_full {
|
struct ds4_metal_args_glm_attention_full {
|
||||||
uint32_t pos0;
|
uint32_t pos0;
|
||||||
uint32_t n_tokens;
|
uint32_t n_tokens;
|
||||||
@@ -221,6 +232,15 @@ struct ds4_metal_args_glm_fill_selected_range_batch {
|
|||||||
uint32_t pad_row;
|
uint32_t pad_row;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ds4_metal_args_glm53_expand_pool_selection {
|
||||||
|
uint32_t n_tokens;
|
||||||
|
uint32_t pos0;
|
||||||
|
uint32_t selected_pools;
|
||||||
|
uint32_t index_topk;
|
||||||
|
uint32_t pool_size;
|
||||||
|
uint32_t output_width;
|
||||||
|
};
|
||||||
|
|
||||||
struct ds4_metal_args_glm_indexer_rope_tail {
|
struct ds4_metal_args_glm_indexer_rope_tail {
|
||||||
uint32_t n_tokens;
|
uint32_t n_tokens;
|
||||||
uint32_t n_head;
|
uint32_t n_head;
|
||||||
@@ -252,6 +272,8 @@ struct ds4_metal_args_glm_indexer_scores_batch {
|
|||||||
uint32_t head_dim;
|
uint32_t head_dim;
|
||||||
uint32_t pos0;
|
uint32_t pos0;
|
||||||
uint32_t cache_f16;
|
uint32_t cache_f16;
|
||||||
|
uint32_t row_group_size;
|
||||||
|
uint32_t pad0;
|
||||||
uint64_t q_token_stride;
|
uint64_t q_token_stride;
|
||||||
uint64_t q_head_stride;
|
uint64_t q_head_stride;
|
||||||
uint64_t weights_token_stride;
|
uint64_t weights_token_stride;
|
||||||
@@ -259,6 +281,13 @@ struct ds4_metal_args_glm_indexer_scores_batch {
|
|||||||
float scale;
|
float scale;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline uint glm_indexer_batch_visible_rows(
|
||||||
|
constant ds4_metal_args_glm_indexer_scores_batch &args,
|
||||||
|
uint token) {
|
||||||
|
const uint group = max(args.row_group_size, 1u);
|
||||||
|
return min((args.pos0 + token + 1u) / group, args.n_rows);
|
||||||
|
}
|
||||||
|
|
||||||
struct ds4_metal_args_glm_qk_lowrank {
|
struct ds4_metal_args_glm_qk_lowrank {
|
||||||
uint32_t n_head;
|
uint32_t n_head;
|
||||||
uint32_t kv_lora_dim;
|
uint32_t kv_lora_dim;
|
||||||
@@ -933,6 +962,117 @@ kernel void kernel_glm_store_indexer_k(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline float glm53_pool_bf16_to_f32(ushort value) {
|
||||||
|
return as_type<float>((uint)value << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_indexer_pool_update(
|
||||||
|
constant ds4_metal_args_glm53_indexer_pool_update &args,
|
||||||
|
device const char *raw_k,
|
||||||
|
device const char *gate,
|
||||||
|
device const float *norm_weight,
|
||||||
|
device const float *norm_bias,
|
||||||
|
device const ushort *ape,
|
||||||
|
device char *pool_cache,
|
||||||
|
device float *tail_k,
|
||||||
|
device float *tail_gate,
|
||||||
|
threadgroup float *shared [[threadgroup(0)]],
|
||||||
|
uint tid [[thread_index_in_threadgroup]],
|
||||||
|
uint3 tgpig [[threadgroup_position_in_grid]]) {
|
||||||
|
if (args.head_dim == 0u || args.pool_size == 0u ||
|
||||||
|
tid >= args.head_dim || args.n_tokens == 0u) return;
|
||||||
|
|
||||||
|
const uint pool = args.pos0 / args.pool_size + tgpig.x;
|
||||||
|
const uint pool_start = pool * args.pool_size;
|
||||||
|
const uint input_end = args.pos0 + args.n_tokens;
|
||||||
|
if (pool_start >= input_end || pool_start + args.pool_size <= args.pos0) return;
|
||||||
|
|
||||||
|
threadgroup float *rows = shared;
|
||||||
|
threadgroup float *mean = rows + args.pool_size * args.head_dim;
|
||||||
|
threadgroup float *inv = mean + args.pool_size;
|
||||||
|
const bool complete = pool_start + args.pool_size <= input_end;
|
||||||
|
|
||||||
|
for (uint r = 0; r < args.pool_size; r++) {
|
||||||
|
const uint pos = pool_start + r;
|
||||||
|
float k_value = 0.0f;
|
||||||
|
float gate_value = 0.0f;
|
||||||
|
if (pos >= args.pos0 && pos < input_end) {
|
||||||
|
const uint src_row = pos - args.pos0;
|
||||||
|
k_value = ((device const float *)raw_k)[
|
||||||
|
(uint64_t)src_row * args.head_dim + tid];
|
||||||
|
gate_value = ((device const float *)gate)[
|
||||||
|
(uint64_t)src_row * args.head_dim + tid];
|
||||||
|
if (!complete) {
|
||||||
|
tail_k[(uint64_t)r * args.head_dim + tid] = k_value;
|
||||||
|
tail_gate[(uint64_t)r * args.head_dim + tid] = gate_value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
k_value = tail_k[(uint64_t)r * args.head_dim + tid];
|
||||||
|
gate_value = tail_gate[(uint64_t)r * args.head_dim + tid];
|
||||||
|
}
|
||||||
|
rows[(uint64_t)r * args.head_dim + tid] = k_value;
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
|
||||||
|
if (!complete || pool >= (args.cache_cap + args.pool_size - 1u) / args.pool_size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tid < args.pool_size) {
|
||||||
|
const uint r = tid;
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (uint d = 0; d < args.head_dim; d++) {
|
||||||
|
sum += rows[(uint64_t)r * args.head_dim + d];
|
||||||
|
}
|
||||||
|
const float m = sum / (float)args.head_dim;
|
||||||
|
float ss = 0.0f;
|
||||||
|
for (uint d = 0; d < args.head_dim; d++) {
|
||||||
|
const float delta = rows[(uint64_t)r * args.head_dim + d] - m;
|
||||||
|
ss += delta * delta;
|
||||||
|
}
|
||||||
|
mean[r] = m;
|
||||||
|
inv[r] = rsqrt(ss / (float)args.head_dim + args.eps);
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
|
||||||
|
float max_logit = -INFINITY;
|
||||||
|
float logits[4];
|
||||||
|
for (uint r = 0; r < args.pool_size; r++) {
|
||||||
|
const uint pos = pool_start + r;
|
||||||
|
float gate_value;
|
||||||
|
if (pos >= args.pos0) {
|
||||||
|
const uint src_row = pos - args.pos0;
|
||||||
|
gate_value = ((device const float *)gate)[
|
||||||
|
(uint64_t)src_row * args.head_dim + tid];
|
||||||
|
} else {
|
||||||
|
gate_value = tail_gate[(uint64_t)r * args.head_dim + tid];
|
||||||
|
}
|
||||||
|
logits[r] = gate_value +
|
||||||
|
glm53_pool_bf16_to_f32(ape[(uint64_t)r * args.head_dim + tid]);
|
||||||
|
max_logit = max(max_logit, logits[r]);
|
||||||
|
}
|
||||||
|
|
||||||
|
float denom = 0.0f;
|
||||||
|
for (uint r = 0; r < args.pool_size; r++) {
|
||||||
|
logits[r] = exp(logits[r] - max_logit);
|
||||||
|
denom += logits[r];
|
||||||
|
}
|
||||||
|
float pooled = 0.0f;
|
||||||
|
for (uint r = 0; r < args.pool_size; r++) {
|
||||||
|
const float normalized =
|
||||||
|
(rows[(uint64_t)r * args.head_dim + tid] - mean[r]) * inv[r] *
|
||||||
|
norm_weight[tid] + norm_bias[tid];
|
||||||
|
pooled += (logits[r] / denom) * normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t dst_index = (uint64_t)pool * args.head_dim + tid;
|
||||||
|
if (args.cache_f16 != 0u) {
|
||||||
|
((device half *)pool_cache)[dst_index] = (half)pooled;
|
||||||
|
} else {
|
||||||
|
((device float *)pool_cache)[dst_index] = pooled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void glm_dense_cache_store_f32_or_f16(
|
static inline void glm_dense_cache_store_f32_or_f16(
|
||||||
device char *base,
|
device char *base,
|
||||||
uint64_t index,
|
uint64_t index,
|
||||||
@@ -1011,7 +1151,8 @@ kernel void kernel_glm_build_kv_cache(
|
|||||||
corr_dims);
|
corr_dims);
|
||||||
}
|
}
|
||||||
const float theta_base = (float)pos;
|
const float theta_base = (float)pos;
|
||||||
const float inv_ndims = -1.0f / (float)args.qk_rope;
|
const float inv_ndims = args.qk_rope != 0u ?
|
||||||
|
-1.0f / (float)args.qk_rope : 0.0f;
|
||||||
for (uint r = tid * 2u; r < args.qk_rope; r += nth * 2u) {
|
for (uint r = tid * 2u; r < args.qk_rope; r += nth * 2u) {
|
||||||
#ifdef DS4_METAL_ROPE_EXP2_LOG2
|
#ifdef DS4_METAL_ROPE_EXP2_LOG2
|
||||||
const float theta = theta_base * exp2(inv_ndims * (float)r * log2(args.freq_base));
|
const float theta = theta_base * exp2(inv_ndims * (float)r * log2(args.freq_base));
|
||||||
@@ -1096,7 +1237,8 @@ kernel void kernel_glm_build_kv_cache_decode_group4(
|
|||||||
corr_dims);
|
corr_dims);
|
||||||
}
|
}
|
||||||
const float theta_base = (float)pos;
|
const float theta_base = (float)pos;
|
||||||
const float inv_ndims = -1.0f / (float)args.qk_rope;
|
const float inv_ndims = args.qk_rope != 0u ?
|
||||||
|
-1.0f / (float)args.qk_rope : 0.0f;
|
||||||
for (uint r = tid * 2u; r < args.qk_rope; r += 512u) {
|
for (uint r = tid * 2u; r < args.qk_rope; r += 512u) {
|
||||||
#ifdef DS4_METAL_ROPE_EXP2_LOG2
|
#ifdef DS4_METAL_ROPE_EXP2_LOG2
|
||||||
const float theta = theta_base * exp2(inv_ndims * (float)r * log2(args.freq_base));
|
const float theta = theta_base * exp2(inv_ndims * (float)r * log2(args.freq_base));
|
||||||
@@ -1179,7 +1321,8 @@ kernel void kernel_glm_build_kv_cache_flash(
|
|||||||
corr_dims);
|
corr_dims);
|
||||||
}
|
}
|
||||||
const float theta_base = (float)pos;
|
const float theta_base = (float)pos;
|
||||||
const float inv_ndims = -1.0f / (float)args.qk_rope;
|
const float inv_ndims = args.qk_rope != 0u ?
|
||||||
|
-1.0f / (float)args.qk_rope : 0.0f;
|
||||||
for (uint r = tid * 2u; r < args.qk_rope; r += nth * 2u) {
|
for (uint r = tid * 2u; r < args.qk_rope; r += nth * 2u) {
|
||||||
#ifdef DS4_METAL_ROPE_EXP2_LOG2
|
#ifdef DS4_METAL_ROPE_EXP2_LOG2
|
||||||
const float theta = theta_base * exp2(inv_ndims * (float)r * log2(args.freq_base));
|
const float theta = theta_base * exp2(inv_ndims * (float)r * log2(args.freq_base));
|
||||||
@@ -1400,6 +1543,35 @@ kernel void kernel_glm_fill_selected_range_batch(
|
|||||||
selected[gid] = slot < visible ? slot : args.pad_row;
|
selected[gid] = slot < visible ? slot : args.pad_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_expand_pool_selection(
|
||||||
|
constant ds4_metal_args_glm53_expand_pool_selection &args,
|
||||||
|
device const uint32_t *pool_selected,
|
||||||
|
device uint32_t *raw_selected,
|
||||||
|
uint gid [[thread_position_in_grid]]) {
|
||||||
|
const uint total = args.n_tokens * args.output_width;
|
||||||
|
if (gid >= total || args.output_width == 0u || args.pool_size == 0u) return;
|
||||||
|
|
||||||
|
const uint token = gid / args.output_width;
|
||||||
|
const uint slot = gid - token * args.output_width;
|
||||||
|
uint value = 0xffffffffu;
|
||||||
|
if (slot < args.index_topk) {
|
||||||
|
const uint pool_slot = slot / args.pool_size;
|
||||||
|
if (pool_slot < args.selected_pools) {
|
||||||
|
const uint pool = pool_selected[
|
||||||
|
(uint64_t)token * args.selected_pools + pool_slot];
|
||||||
|
value = pool * args.pool_size + slot % args.pool_size;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const uint tail_slot = slot - args.index_topk;
|
||||||
|
const uint visible = args.pos0 + token + 1u;
|
||||||
|
const uint tail_count = visible % args.pool_size;
|
||||||
|
if (tail_slot < tail_count) {
|
||||||
|
value = visible - tail_count + tail_slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raw_selected[gid] = value;
|
||||||
|
}
|
||||||
|
|
||||||
kernel void kernel_glm_indexer_rope_tail_f32(
|
kernel void kernel_glm_indexer_rope_tail_f32(
|
||||||
constant ds4_metal_args_glm_indexer_rope_tail & args,
|
constant ds4_metal_args_glm_indexer_rope_tail & args,
|
||||||
device char *x,
|
device char *x,
|
||||||
@@ -1849,7 +2021,7 @@ kernel void kernel_glm_indexer_scores_batch(
|
|||||||
|
|
||||||
device float *dst = (device float *)(scores +
|
device float *dst = (device float *)(scores +
|
||||||
(uint64_t)token * args.score_token_stride) + row;
|
(uint64_t)token * args.score_token_stride) + row;
|
||||||
const uint visible = min(args.pos0 + token + 1u, args.n_rows);
|
const uint visible = glm_indexer_batch_visible_rows(args, token);
|
||||||
if (row >= visible) {
|
if (row >= visible) {
|
||||||
if (tid == 0) *dst = -INFINITY;
|
if (tid == 0) *dst = -INFINITY;
|
||||||
return;
|
return;
|
||||||
@@ -1910,7 +2082,7 @@ kernel void kernel_glm_indexer_scores_tiled_f32(
|
|||||||
|
|
||||||
const uint last_token = min(token_base + TM, args.n_tokens);
|
const uint last_token = min(token_base + TM, args.n_tokens);
|
||||||
const uint max_visible = last_token > token_base ?
|
const uint max_visible = last_token > token_base ?
|
||||||
min(args.pos0 + last_token, args.n_rows) : 0u;
|
glm_indexer_batch_visible_rows(args, last_token - 1u) : 0u;
|
||||||
|
|
||||||
if (row_base >= max_visible) {
|
if (row_base >= max_visible) {
|
||||||
for (uint i = tid; i < TM*TN; i += 128) {
|
for (uint i = tid; i < TM*TN; i += 128) {
|
||||||
@@ -2005,13 +2177,13 @@ kernel void kernel_glm_indexer_scores_tiled_f32(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token0 < args.n_tokens && row0 < args.n_rows) {
|
if (token0 < args.n_tokens && row0 < args.n_rows) {
|
||||||
const uint visible = min(args.pos0 + token0 + 1u, args.n_rows);
|
const uint visible = glm_indexer_batch_visible_rows(args, token0);
|
||||||
device float *dst = (device float *)(scores +
|
device float *dst = (device float *)(scores +
|
||||||
(uint64_t)token0 * args.score_token_stride) + row0;
|
(uint64_t)token0 * args.score_token_stride) + row0;
|
||||||
*dst = row0 < visible ? acc0 : -INFINITY;
|
*dst = row0 < visible ? acc0 : -INFINITY;
|
||||||
}
|
}
|
||||||
if (token1 < args.n_tokens && row1 < args.n_rows) {
|
if (token1 < args.n_tokens && row1 < args.n_rows) {
|
||||||
const uint visible = min(args.pos0 + token1 + 1u, args.n_rows);
|
const uint visible = glm_indexer_batch_visible_rows(args, token1);
|
||||||
device float *dst = (device float *)(scores +
|
device float *dst = (device float *)(scores +
|
||||||
(uint64_t)token1 * args.score_token_stride) + row1;
|
(uint64_t)token1 * args.score_token_stride) + row1;
|
||||||
*dst = row1 < visible ? acc1 : -INFINITY;
|
*dst = row1 < visible ? acc1 : -INFINITY;
|
||||||
@@ -2043,7 +2215,7 @@ kernel void kernel_glm_indexer_scores_tiled(
|
|||||||
|
|
||||||
const uint last_token = min(token_base + TM, args.n_tokens);
|
const uint last_token = min(token_base + TM, args.n_tokens);
|
||||||
const uint max_visible = last_token > token_base ?
|
const uint max_visible = last_token > token_base ?
|
||||||
min(args.pos0 + last_token, args.n_rows) : 0u;
|
glm_indexer_batch_visible_rows(args, last_token - 1u) : 0u;
|
||||||
|
|
||||||
if (row_base >= max_visible) {
|
if (row_base >= max_visible) {
|
||||||
for (uint i = tid; i < TM*TN; i += 128) {
|
for (uint i = tid; i < TM*TN; i += 128) {
|
||||||
@@ -2138,13 +2310,13 @@ kernel void kernel_glm_indexer_scores_tiled(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token0 < args.n_tokens && row0 < args.n_rows) {
|
if (token0 < args.n_tokens && row0 < args.n_rows) {
|
||||||
const uint visible = min(args.pos0 + token0 + 1u, args.n_rows);
|
const uint visible = glm_indexer_batch_visible_rows(args, token0);
|
||||||
device float *dst = (device float *)(scores +
|
device float *dst = (device float *)(scores +
|
||||||
(uint64_t)token0 * args.score_token_stride) + row0;
|
(uint64_t)token0 * args.score_token_stride) + row0;
|
||||||
*dst = row0 < visible ? acc0 : -INFINITY;
|
*dst = row0 < visible ? acc0 : -INFINITY;
|
||||||
}
|
}
|
||||||
if (token1 < args.n_tokens && row1 < args.n_rows) {
|
if (token1 < args.n_tokens && row1 < args.n_rows) {
|
||||||
const uint visible = min(args.pos0 + token1 + 1u, args.n_rows);
|
const uint visible = glm_indexer_batch_visible_rows(args, token1);
|
||||||
device float *dst = (device float *)(scores +
|
device float *dst = (device float *)(scores +
|
||||||
(uint64_t)token1 * args.score_token_stride) + row1;
|
(uint64_t)token1 * args.score_token_stride) + row1;
|
||||||
*dst = row1 < visible ? acc1 : -INFINITY;
|
*dst = row1 < visible ? acc1 : -INFINITY;
|
||||||
@@ -2241,7 +2413,6 @@ kernel void kernel_glm_qk_lowrank_q8_0_glm52_sg(
|
|||||||
ushort3 ntg_u [[threads_per_threadgroup]],
|
ushort3 ntg_u [[threads_per_threadgroup]],
|
||||||
ushort tiisg [[thread_index_in_simdgroup]],
|
ushort tiisg [[thread_index_in_simdgroup]],
|
||||||
ushort sgitg [[simdgroup_index_in_threadgroup]]) {
|
ushort sgitg [[simdgroup_index_in_threadgroup]]) {
|
||||||
constexpr uint n_head = 64u;
|
|
||||||
constexpr uint kv_lora_dim = 512u;
|
constexpr uint kv_lora_dim = 512u;
|
||||||
constexpr uint qk_nope = 192u;
|
constexpr uint qk_nope = 192u;
|
||||||
constexpr uint qk_dim = 256u;
|
constexpr uint qk_dim = 256u;
|
||||||
@@ -2249,8 +2420,8 @@ kernel void kernel_glm_qk_lowrank_q8_0_glm52_sg(
|
|||||||
|
|
||||||
const uint head = tgpig.x;
|
const uint head = tgpig.x;
|
||||||
const uint wt = args.weight_type;
|
const uint wt = args.weight_type;
|
||||||
if (head >= n_head ||
|
if (head >= args.n_head ||
|
||||||
args.n_head != n_head ||
|
(args.n_head != 32u && args.n_head != 64u) ||
|
||||||
args.kv_lora_dim != kv_lora_dim ||
|
args.kv_lora_dim != kv_lora_dim ||
|
||||||
args.qk_nope != qk_nope ||
|
args.qk_nope != qk_nope ||
|
||||||
args.qk_dim != qk_dim ||
|
args.qk_dim != qk_dim ||
|
||||||
@@ -3624,7 +3795,7 @@ kernel void kernel_glm_attention_indexed_batch_lora_group8_vec_impl(
|
|||||||
args.n_selected == 0u ||
|
args.n_selected == 0u ||
|
||||||
args.cache_f16 == 0u ||
|
args.cache_f16 == 0u ||
|
||||||
args.kv_lora_dim != 512u ||
|
args.kv_lora_dim != 512u ||
|
||||||
args.qk_rope != 64u) {
|
(args.qk_rope != 0u && args.qk_rope != 64u)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3668,7 +3839,7 @@ kernel void kernel_glm_attention_indexed_batch_lora_group8_vec_impl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
float corr_dims[2] = {0.0f, 0.0f};
|
float corr_dims[2] = {0.0f, 0.0f};
|
||||||
if (args.ext_factor != 0.0f) {
|
if (args.qk_rope != 0u && args.ext_factor != 0.0f) {
|
||||||
glm_rope_yarn_corr_dims((int)args.qk_rope,
|
glm_rope_yarn_corr_dims((int)args.qk_rope,
|
||||||
(int)args.n_ctx_orig,
|
(int)args.n_ctx_orig,
|
||||||
args.freq_base,
|
args.freq_base,
|
||||||
@@ -3822,9 +3993,8 @@ kernel void kernel_glm_attention_indexed_batch_lora_group8_vec_causal_impl(
|
|||||||
const uint head = tgpig.x * group_heads + head_in_group + args.head_base;
|
const uint head = tgpig.x * group_heads + head_in_group + args.head_base;
|
||||||
if (token >= args.n_tokens ||
|
if (token >= args.n_tokens ||
|
||||||
args.n_selected == 0u ||
|
args.n_selected == 0u ||
|
||||||
args.cache_f16 == 0u ||
|
|
||||||
args.kv_lora_dim != 512u ||
|
args.kv_lora_dim != 512u ||
|
||||||
args.qk_rope != 64u) {
|
(args.qk_rope != 0u && args.qk_rope != 64u)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3869,7 +4039,7 @@ kernel void kernel_glm_attention_indexed_batch_lora_group8_vec_causal_impl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
float corr_dims[2] = {0.0f, 0.0f};
|
float corr_dims[2] = {0.0f, 0.0f};
|
||||||
if (args.ext_factor != 0.0f) {
|
if (args.qk_rope != 0u && args.ext_factor != 0.0f) {
|
||||||
glm_rope_yarn_corr_dims((int)args.qk_rope,
|
glm_rope_yarn_corr_dims((int)args.qk_rope,
|
||||||
(int)args.n_ctx_orig,
|
(int)args.n_ctx_orig,
|
||||||
args.freq_base,
|
args.freq_base,
|
||||||
@@ -3891,10 +4061,17 @@ kernel void kernel_glm_attention_indexed_batch_lora_group8_vec_causal_impl(
|
|||||||
const uint rr = off / kv_vecs;
|
const uint rr = off / kv_vecs;
|
||||||
const uint vv = off - rr * kv_vecs;
|
const uint vv = off - rr * kv_vecs;
|
||||||
const uint row = base + rr;
|
const uint row = base + rr;
|
||||||
device const half4 *src =
|
if (args.cache_f16 != 0u) {
|
||||||
(device const half4 *)((device const half *)kv_lora_cache +
|
device const half4 *src =
|
||||||
(uint64_t)row * args.kv_lora_dim);
|
(device const half4 *)((device const half *)kv_lora_cache +
|
||||||
kv_shared[off] = src[vv];
|
(uint64_t)row * args.kv_lora_dim);
|
||||||
|
kv_shared[off] = src[vv];
|
||||||
|
} else {
|
||||||
|
device const float4 *src =
|
||||||
|
(device const float4 *)((device const float *)kv_lora_cache +
|
||||||
|
(uint64_t)row * args.kv_lora_dim);
|
||||||
|
kv_shared[off] = (half4)src[vv];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (uint off = tid; off < rows * rope_vecs; off += 256u) {
|
for (uint off = tid; off < rows * rope_vecs; off += 256u) {
|
||||||
const uint rr = off / rope_vecs;
|
const uint rr = off / rope_vecs;
|
||||||
@@ -3903,29 +4080,31 @@ kernel void kernel_glm_attention_indexed_batch_lora_group8_vec_causal_impl(
|
|||||||
const uint row = base + rr;
|
const uint row = base + rr;
|
||||||
const uint64_t rope_base = (uint64_t)row * args.qk_rope;
|
const uint64_t rope_base = (uint64_t)row * args.qk_rope;
|
||||||
const float2 y0 =
|
const float2 y0 =
|
||||||
glm_cache_load_rotated_rope_pair_f16_only(k_rope_cache,
|
glm_cache_load_rotated_rope_pair(k_rope_cache,
|
||||||
rope_base,
|
rope_base,
|
||||||
r,
|
r,
|
||||||
row,
|
row,
|
||||||
args.qk_rope,
|
args.qk_rope,
|
||||||
args.freq_base,
|
args.cache_f16,
|
||||||
args.freq_scale,
|
args.freq_base,
|
||||||
args.ext_factor,
|
args.freq_scale,
|
||||||
args.attn_factor,
|
args.ext_factor,
|
||||||
corr_dims[0],
|
args.attn_factor,
|
||||||
corr_dims[1]);
|
corr_dims[0],
|
||||||
|
corr_dims[1]);
|
||||||
const float2 y1 =
|
const float2 y1 =
|
||||||
glm_cache_load_rotated_rope_pair_f16_only(k_rope_cache,
|
glm_cache_load_rotated_rope_pair(k_rope_cache,
|
||||||
rope_base,
|
rope_base,
|
||||||
r + 2u,
|
r + 2u,
|
||||||
row,
|
row,
|
||||||
args.qk_rope,
|
args.qk_rope,
|
||||||
args.freq_base,
|
args.cache_f16,
|
||||||
args.freq_scale,
|
args.freq_base,
|
||||||
args.ext_factor,
|
args.freq_scale,
|
||||||
args.attn_factor,
|
args.ext_factor,
|
||||||
corr_dims[0],
|
args.attn_factor,
|
||||||
corr_dims[1]);
|
corr_dims[0],
|
||||||
|
corr_dims[1]);
|
||||||
rope_shared[off] = float4(y0.x, y0.y, y1.x, y1.y);
|
rope_shared[off] = float4(y0.x, y0.y, y1.x, y1.y);
|
||||||
}
|
}
|
||||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
@@ -4586,14 +4765,15 @@ kernel void kernel_glm_router_select_one(
|
|||||||
threadgroup float *scratch [[threadgroup(0)]],
|
threadgroup float *scratch [[threadgroup(0)]],
|
||||||
uint token [[threadgroup_position_in_grid]],
|
uint token [[threadgroup_position_in_grid]],
|
||||||
uint tid [[thread_position_in_threadgroup]]) {
|
uint tid [[thread_position_in_threadgroup]]) {
|
||||||
|
const uint sort_width = args.n_expert > 256u ? 512u : 256u;
|
||||||
threadgroup float *sel_scores = scratch;
|
threadgroup float *sel_scores = scratch;
|
||||||
threadgroup int32_t *idx = (threadgroup int32_t *)(scratch + 256);
|
threadgroup int32_t *idx = (threadgroup int32_t *)(scratch + sort_width);
|
||||||
device const float *token_logits = logits + (uint64_t)token * args.n_expert;
|
device const float *token_logits = logits + (uint64_t)token * args.n_expert;
|
||||||
device int32_t *token_selected = selected + (uint64_t)token * args.n_expert_used;
|
device int32_t *token_selected = selected + (uint64_t)token * args.n_expert_used;
|
||||||
device float *token_weights = weights + (uint64_t)token * args.n_expert_used;
|
device float *token_weights = weights + (uint64_t)token * args.n_expert_used;
|
||||||
device float *token_probs = probs + (uint64_t)token * args.n_expert;
|
device float *token_probs = probs + (uint64_t)token * args.n_expert;
|
||||||
|
|
||||||
const uint n_expert = min(args.n_expert, 256u);
|
const uint n_expert = min(args.n_expert, 512u);
|
||||||
const bool active = tid < n_expert;
|
const bool active = tid < n_expert;
|
||||||
const float p = active ? ds4_glm_router_sigmoid(token_logits[tid]) : 0.0f;
|
const float p = active ? ds4_glm_router_sigmoid(token_logits[tid]) : 0.0f;
|
||||||
if (active) token_probs[tid] = p;
|
if (active) token_probs[tid] = p;
|
||||||
@@ -4601,7 +4781,7 @@ kernel void kernel_glm_router_select_one(
|
|||||||
idx[tid] = (int32_t)tid;
|
idx[tid] = (int32_t)tid;
|
||||||
threadgroup_barrier(mem_flags::mem_threadgroup);
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
|
||||||
for (uint k = 2; k <= 256; k <<= 1) {
|
for (uint k = 2; k <= sort_width; k <<= 1) {
|
||||||
for (uint j = k >> 1; j > 0; j >>= 1) {
|
for (uint j = k >> 1; j > 0; j >>= 1) {
|
||||||
const uint other = tid ^ j;
|
const uint other = tid ^ j;
|
||||||
if (other > tid) {
|
if (other > tid) {
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// BF16 model-weight kernels used by GLM-5.3 Flash.
|
||||||
|
|
||||||
|
static inline float glm53_bf16_to_f32(ushort value) {
|
||||||
|
return as_type<float>((uint)value << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct glm53_bf16_matmul_args {
|
||||||
|
uint in_dim;
|
||||||
|
uint out_dim;
|
||||||
|
uint n_rows;
|
||||||
|
};
|
||||||
|
|
||||||
|
kernel void kernel_glm53_embedding_bf16(
|
||||||
|
constant glm53_bf16_matmul_args &args,
|
||||||
|
device const ushort *weights,
|
||||||
|
device const int *tokens,
|
||||||
|
device float *out,
|
||||||
|
uint2 gid [[thread_position_in_grid]]) {
|
||||||
|
const uint d = gid.x;
|
||||||
|
const uint row = gid.y;
|
||||||
|
if (d >= args.in_dim || row >= args.n_rows) return;
|
||||||
|
const int token = tokens[row];
|
||||||
|
out[(ulong)row * args.in_dim + d] =
|
||||||
|
token >= 0 && (uint)token < args.out_dim
|
||||||
|
? glm53_bf16_to_f32(weights[(ulong)(uint)token * args.in_dim + d])
|
||||||
|
: 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void glm53_mul_mv_bf16_f32_row(
|
||||||
|
constant glm53_bf16_matmul_args &args,
|
||||||
|
device const ushort *weights,
|
||||||
|
device const float *x,
|
||||||
|
device float *out,
|
||||||
|
uint2 tgpig,
|
||||||
|
ushort lane,
|
||||||
|
ushort sg,
|
||||||
|
ushort nsg) {
|
||||||
|
const uint out_row = tgpig.x * (uint)nsg + sg;
|
||||||
|
const uint token = tgpig.y;
|
||||||
|
if (out_row >= args.out_dim || token >= args.n_rows) return;
|
||||||
|
|
||||||
|
device const ushort *w = weights + (ulong)out_row * args.in_dim;
|
||||||
|
device const float *xr = x + (ulong)token * args.in_dim;
|
||||||
|
float sum = 0.0f;
|
||||||
|
uint k = lane;
|
||||||
|
for (; k + 224u < args.in_dim; k += 256u) {
|
||||||
|
const ushort w0 = w[k];
|
||||||
|
const ushort w1 = w[k + 32u];
|
||||||
|
const ushort w2 = w[k + 64u];
|
||||||
|
const ushort w3 = w[k + 96u];
|
||||||
|
const ushort w4 = w[k + 128u];
|
||||||
|
const ushort w5 = w[k + 160u];
|
||||||
|
const ushort w6 = w[k + 192u];
|
||||||
|
const ushort w7 = w[k + 224u];
|
||||||
|
const float x0 = xr[k];
|
||||||
|
const float x1 = xr[k + 32u];
|
||||||
|
const float x2 = xr[k + 64u];
|
||||||
|
const float x3 = xr[k + 96u];
|
||||||
|
const float x4 = xr[k + 128u];
|
||||||
|
const float x5 = xr[k + 160u];
|
||||||
|
const float x6 = xr[k + 192u];
|
||||||
|
const float x7 = xr[k + 224u];
|
||||||
|
sum = fma(glm53_bf16_to_f32(w0), x0, sum);
|
||||||
|
sum = fma(glm53_bf16_to_f32(w1), x1, sum);
|
||||||
|
sum = fma(glm53_bf16_to_f32(w2), x2, sum);
|
||||||
|
sum = fma(glm53_bf16_to_f32(w3), x3, sum);
|
||||||
|
sum = fma(glm53_bf16_to_f32(w4), x4, sum);
|
||||||
|
sum = fma(glm53_bf16_to_f32(w5), x5, sum);
|
||||||
|
sum = fma(glm53_bf16_to_f32(w6), x6, sum);
|
||||||
|
sum = fma(glm53_bf16_to_f32(w7), x7, sum);
|
||||||
|
}
|
||||||
|
for (; k < args.in_dim; k += 32u) {
|
||||||
|
sum = fma(glm53_bf16_to_f32(w[k]), xr[k], sum);
|
||||||
|
}
|
||||||
|
sum = simd_sum(sum);
|
||||||
|
if (lane == 0u) out[(ulong)token * args.out_dim + out_row] = sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* One simdgroup owns one output row. Eight independent loads expose enough
|
||||||
|
* memory-level parallelism for decode without changing the reduction tree. */
|
||||||
|
kernel void kernel_glm53_mul_mv_bf16_f32(
|
||||||
|
constant glm53_bf16_matmul_args &args,
|
||||||
|
device const ushort *weights,
|
||||||
|
device const float *x,
|
||||||
|
device float *out,
|
||||||
|
uint2 tgpig [[threadgroup_position_in_grid]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]],
|
||||||
|
ushort nsg [[simdgroups_per_threadgroup]]) {
|
||||||
|
glm53_mul_mv_bf16_f32_row(args, weights, x, out,
|
||||||
|
tgpig, lane, sg, nsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_mul_mv_bf16_f32_qkv(
|
||||||
|
constant glm53_bf16_matmul_args &args,
|
||||||
|
device const ushort *weights_q,
|
||||||
|
device const ushort *weights_k,
|
||||||
|
device const ushort *weights_v,
|
||||||
|
device const float *x,
|
||||||
|
device float *out_q,
|
||||||
|
device float *out_k,
|
||||||
|
device float *out_v,
|
||||||
|
uint3 tgpig [[threadgroup_position_in_grid]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]],
|
||||||
|
ushort nsg [[simdgroups_per_threadgroup]]) {
|
||||||
|
device const ushort *weights = tgpig.z == 0u ? weights_q :
|
||||||
|
(tgpig.z == 1u ? weights_k : weights_v);
|
||||||
|
device float *out = tgpig.z == 0u ? out_q :
|
||||||
|
(tgpig.z == 1u ? out_k : out_v);
|
||||||
|
glm53_mul_mv_bf16_f32_row(args, weights, x, out,
|
||||||
|
tgpig.xy, lane, sg, nsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct glm53_bf16_block16 {
|
||||||
|
ushort v[16];
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename type4x4>
|
||||||
|
void glm53_dequantize_bf16(
|
||||||
|
device const glm53_bf16_block16 *src,
|
||||||
|
short il,
|
||||||
|
thread type4x4 ®) {
|
||||||
|
(void)il;
|
||||||
|
float4x4 values;
|
||||||
|
for (short i = 0; i < 16; i++) {
|
||||||
|
values[i / 4][i % 4] = glm53_bf16_to_f32(src->v[i]);
|
||||||
|
}
|
||||||
|
reg = (type4x4)values;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef decltype(kernel_mul_mm<
|
||||||
|
half, half4x4, simdgroup_half8x8,
|
||||||
|
half, half2x4, simdgroup_half8x8,
|
||||||
|
glm53_bf16_block16, 1, glm53_dequantize_bf16,
|
||||||
|
float, float4x4, float, float2x4>) glm53_mul_mm_bf16_t;
|
||||||
|
|
||||||
|
template [[host_name("kernel_glm53_mul_mm_bf16_f32")]]
|
||||||
|
kernel glm53_mul_mm_bf16_t kernel_mul_mm<
|
||||||
|
half, half4x4, simdgroup_half8x8,
|
||||||
|
half, half2x4, simdgroup_half8x8,
|
||||||
|
glm53_bf16_block16, 1, glm53_dequantize_bf16,
|
||||||
|
half, half4x4, float, float2x4>;
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
// Kimi Delta Attention kernels, adapted from the kimi-k3 branch.
|
||||||
|
|
||||||
|
struct glm53_kda_args {
|
||||||
|
uint n_heads;
|
||||||
|
uint n_rows;
|
||||||
|
float lower_bound;
|
||||||
|
float norm_eps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One threadgroup owns one (sequence, head). Four simdgroups update four
|
||||||
|
* value rows concurrently; every lane owns four adjacent key columns.
|
||||||
|
*/
|
||||||
|
kernel void kernel_glm53_kda_decode(
|
||||||
|
constant glm53_kda_args &args,
|
||||||
|
device const float *q_in,
|
||||||
|
device const float *k_in,
|
||||||
|
device const float *v_in,
|
||||||
|
device const float *raw_gate,
|
||||||
|
device const float *raw_beta,
|
||||||
|
device const float *output_gate,
|
||||||
|
device const float *q_conv,
|
||||||
|
device const float *k_conv,
|
||||||
|
device const float *v_conv,
|
||||||
|
device const float *a_log,
|
||||||
|
device const float *dt_bias,
|
||||||
|
device const float *output_norm,
|
||||||
|
device float *conv_state,
|
||||||
|
device float *state,
|
||||||
|
device float *out,
|
||||||
|
threadgroup float *scratch [[threadgroup(0)]],
|
||||||
|
uint2 tgpig [[threadgroup_position_in_grid]],
|
||||||
|
ushort tid [[thread_index_in_threadgroup]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]]) {
|
||||||
|
constexpr uint D = 128u;
|
||||||
|
constexpr uint HISTORY = 3u;
|
||||||
|
const uint row = tgpig.x;
|
||||||
|
const uint head = tgpig.y;
|
||||||
|
if (row >= args.n_rows || head >= args.n_heads) return;
|
||||||
|
|
||||||
|
threadgroup float *sq = scratch;
|
||||||
|
threadgroup float *sk = sq + D;
|
||||||
|
threadgroup float *sd = sk + D;
|
||||||
|
threadgroup float *sv = sd + D;
|
||||||
|
threadgroup float *so = sv + D;
|
||||||
|
threadgroup float *reduce_q = so + D;
|
||||||
|
threadgroup float *reduce_k = reduce_q + 4u;
|
||||||
|
threadgroup float *reduce_o = reduce_k + 4u;
|
||||||
|
threadgroup float *beta_shared = reduce_o + 4u;
|
||||||
|
|
||||||
|
const uint projection = args.n_heads * D;
|
||||||
|
const uint channel = head * D + tid;
|
||||||
|
const ulong input_base = (ulong)row * projection + head * D;
|
||||||
|
const ulong conv_row_stride = 3ul * HISTORY * projection;
|
||||||
|
|
||||||
|
if (tid < D) {
|
||||||
|
float q_acc = 0.0f;
|
||||||
|
float k_acc = 0.0f;
|
||||||
|
float v_acc = 0.0f;
|
||||||
|
device float *q_state = conv_state +
|
||||||
|
(ulong)row * conv_row_stride;
|
||||||
|
device float *k_state = q_state + HISTORY * projection;
|
||||||
|
device float *v_state = k_state + HISTORY * projection;
|
||||||
|
for (uint w = 0; w < HISTORY; w++) {
|
||||||
|
q_acc = fma(q_state[(ulong)w * projection + channel],
|
||||||
|
q_conv[(ulong)channel * 4u + w], q_acc);
|
||||||
|
k_acc = fma(k_state[(ulong)w * projection + channel],
|
||||||
|
k_conv[(ulong)channel * 4u + w], k_acc);
|
||||||
|
v_acc = fma(v_state[(ulong)w * projection + channel],
|
||||||
|
v_conv[(ulong)channel * 4u + w], v_acc);
|
||||||
|
}
|
||||||
|
const float q_new = q_in[input_base + tid];
|
||||||
|
const float k_new = k_in[input_base + tid];
|
||||||
|
const float v_new = v_in[input_base + tid];
|
||||||
|
q_acc = fma(q_new, q_conv[(ulong)channel * 4u + 3u], q_acc);
|
||||||
|
k_acc = fma(k_new, k_conv[(ulong)channel * 4u + 3u], k_acc);
|
||||||
|
v_acc = fma(v_new, v_conv[(ulong)channel * 4u + 3u], v_acc);
|
||||||
|
|
||||||
|
q_state[channel] = q_state[projection + channel];
|
||||||
|
q_state[projection + channel] = q_state[2ul * projection + channel];
|
||||||
|
q_state[2ul * projection + channel] = q_new;
|
||||||
|
k_state[channel] = k_state[projection + channel];
|
||||||
|
k_state[projection + channel] = k_state[2ul * projection + channel];
|
||||||
|
k_state[2ul * projection + channel] = k_new;
|
||||||
|
v_state[channel] = v_state[projection + channel];
|
||||||
|
v_state[projection + channel] = v_state[2ul * projection + channel];
|
||||||
|
v_state[2ul * projection + channel] = v_new;
|
||||||
|
|
||||||
|
sq[tid] = q_acc / (1.0f + exp(-q_acc));
|
||||||
|
sk[tid] = k_acc / (1.0f + exp(-k_acc));
|
||||||
|
sv[tid] = v_acc / (1.0f + exp(-v_acc));
|
||||||
|
const float gate = raw_gate[input_base + tid] + dt_bias[channel];
|
||||||
|
sd[tid] = exp(args.lower_bound *
|
||||||
|
(1.0f / (1.0f + exp(-exp(a_log[head]) * gate))));
|
||||||
|
}
|
||||||
|
if (tid == 0u) {
|
||||||
|
beta_shared[0] =
|
||||||
|
1.0f / (1.0f + exp(-raw_beta[(ulong)row * args.n_heads + head]));
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup |
|
||||||
|
mem_flags::mem_device);
|
||||||
|
|
||||||
|
float q_sumsq = sq[tid] * sq[tid];
|
||||||
|
float k_sumsq = sk[tid] * sk[tid];
|
||||||
|
q_sumsq = simd_sum(q_sumsq);
|
||||||
|
k_sumsq = simd_sum(k_sumsq);
|
||||||
|
if (lane == 0u) {
|
||||||
|
reduce_q[sg] = q_sumsq;
|
||||||
|
reduce_k[sg] = k_sumsq;
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
float q_total = lane < 4u ? reduce_q[lane] : 0.0f;
|
||||||
|
float k_total = lane < 4u ? reduce_k[lane] : 0.0f;
|
||||||
|
q_total = simd_sum(q_total);
|
||||||
|
k_total = simd_sum(k_total);
|
||||||
|
const float q_scale = rsqrt(q_total + 1.0e-6f) * 0x1.6a09e6p-4f;
|
||||||
|
const float k_scale = rsqrt(k_total + 1.0e-6f);
|
||||||
|
if (tid < D) {
|
||||||
|
sq[tid] *= q_scale;
|
||||||
|
sk[tid] *= k_scale;
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
|
||||||
|
const uint k0 = lane * 4u;
|
||||||
|
const float4 q4 = *((threadgroup float4 *)(sq + k0));
|
||||||
|
const float4 k4 = *((threadgroup float4 *)(sk + k0));
|
||||||
|
const float4 decay4 = *((threadgroup float4 *)(sd + k0));
|
||||||
|
const ulong state_head =
|
||||||
|
((ulong)row * args.n_heads + head) * D * D;
|
||||||
|
|
||||||
|
for (uint value = sg; value < D; value += 4u) {
|
||||||
|
device float4 *hptr =
|
||||||
|
(device float4 *)(state + state_head + (ulong)value * D + k0);
|
||||||
|
float4 h = *hptr * decay4;
|
||||||
|
float hk = dot(h, k4);
|
||||||
|
hk = simd_sum(hk);
|
||||||
|
const float delta_v = (sv[value] - hk) * beta_shared[0];
|
||||||
|
h = fma(k4, float4(delta_v), h);
|
||||||
|
*hptr = h;
|
||||||
|
float hq = simd_sum(dot(h, q4));
|
||||||
|
if (lane == 0u) so[value] = hq;
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup |
|
||||||
|
mem_flags::mem_device);
|
||||||
|
|
||||||
|
float o_sumsq = so[tid] * so[tid];
|
||||||
|
o_sumsq = simd_sum(o_sumsq);
|
||||||
|
if (lane == 0u) reduce_o[sg] = o_sumsq;
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
float o_total = lane < 4u ? reduce_o[lane] : 0.0f;
|
||||||
|
o_total = simd_sum(o_total);
|
||||||
|
const float o_scale = rsqrt(o_total / (float)D + args.norm_eps);
|
||||||
|
if (tid < D) {
|
||||||
|
const ulong index = input_base + tid;
|
||||||
|
const float gate =
|
||||||
|
1.0f / (1.0f + exp(-output_gate[index]));
|
||||||
|
out[index] = so[tid] * o_scale * output_norm[tid] * gate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_kda_prefill_prepare(
|
||||||
|
constant glm53_kda_args &args,
|
||||||
|
device float *q,
|
||||||
|
device float *k,
|
||||||
|
device float *v,
|
||||||
|
device float *raw_gate,
|
||||||
|
device const float *q_conv,
|
||||||
|
device const float *k_conv,
|
||||||
|
device const float *v_conv,
|
||||||
|
device const float *a_log,
|
||||||
|
device const float *dt_bias,
|
||||||
|
device float *conv_state,
|
||||||
|
threadgroup float *scratch [[threadgroup(0)]],
|
||||||
|
uint head [[threadgroup_position_in_grid]],
|
||||||
|
ushort tid [[thread_index_in_threadgroup]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]]) {
|
||||||
|
constexpr uint D = 128u;
|
||||||
|
constexpr uint HISTORY = 3u;
|
||||||
|
if (head >= args.n_heads) return;
|
||||||
|
threadgroup float *sq = scratch;
|
||||||
|
threadgroup float *sk = sq + D;
|
||||||
|
threadgroup float *reduce_q = sk + D;
|
||||||
|
threadgroup float *reduce_k = reduce_q + 4u;
|
||||||
|
const uint projection = args.n_heads * D;
|
||||||
|
const uint channel = head * D + tid;
|
||||||
|
device float *q_state = conv_state;
|
||||||
|
device float *k_state = q_state + HISTORY * projection;
|
||||||
|
device float *v_state = k_state + HISTORY * projection;
|
||||||
|
|
||||||
|
for (uint token = 0; token < args.n_rows; token++) {
|
||||||
|
const ulong index = (ulong)token * projection + channel;
|
||||||
|
float q_acc = 0.0f;
|
||||||
|
float k_acc = 0.0f;
|
||||||
|
float v_acc = 0.0f;
|
||||||
|
for (uint w = 0; w < HISTORY; w++) {
|
||||||
|
q_acc = fma(q_state[(ulong)w * projection + channel],
|
||||||
|
q_conv[(ulong)channel * 4u + w], q_acc);
|
||||||
|
k_acc = fma(k_state[(ulong)w * projection + channel],
|
||||||
|
k_conv[(ulong)channel * 4u + w], k_acc);
|
||||||
|
v_acc = fma(v_state[(ulong)w * projection + channel],
|
||||||
|
v_conv[(ulong)channel * 4u + w], v_acc);
|
||||||
|
}
|
||||||
|
const float q_new = q[index];
|
||||||
|
const float k_new = k[index];
|
||||||
|
const float v_new = v[index];
|
||||||
|
q_acc = fma(q_new, q_conv[(ulong)channel * 4u + 3u], q_acc);
|
||||||
|
k_acc = fma(k_new, k_conv[(ulong)channel * 4u + 3u], k_acc);
|
||||||
|
v_acc = fma(v_new, v_conv[(ulong)channel * 4u + 3u], v_acc);
|
||||||
|
q_state[channel] = q_state[projection + channel];
|
||||||
|
q_state[projection + channel] = q_state[2ul * projection + channel];
|
||||||
|
q_state[2ul * projection + channel] = q_new;
|
||||||
|
k_state[channel] = k_state[projection + channel];
|
||||||
|
k_state[projection + channel] = k_state[2ul * projection + channel];
|
||||||
|
k_state[2ul * projection + channel] = k_new;
|
||||||
|
v_state[channel] = v_state[projection + channel];
|
||||||
|
v_state[projection + channel] = v_state[2ul * projection + channel];
|
||||||
|
v_state[2ul * projection + channel] = v_new;
|
||||||
|
|
||||||
|
sq[tid] = q_acc / (1.0f + exp(-q_acc));
|
||||||
|
sk[tid] = k_acc / (1.0f + exp(-k_acc));
|
||||||
|
v[index] = v_acc / (1.0f + exp(-v_acc));
|
||||||
|
const float gate = raw_gate[index] + dt_bias[channel];
|
||||||
|
raw_gate[index] = exp(args.lower_bound *
|
||||||
|
(1.0f / (1.0f + exp(-exp(a_log[head]) * gate))));
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup |
|
||||||
|
mem_flags::mem_device);
|
||||||
|
|
||||||
|
float q_sumsq = simd_sum(sq[tid] * sq[tid]);
|
||||||
|
float k_sumsq = simd_sum(sk[tid] * sk[tid]);
|
||||||
|
if (lane == 0u) {
|
||||||
|
reduce_q[sg] = q_sumsq;
|
||||||
|
reduce_k[sg] = k_sumsq;
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
float q_total = lane < 4u ? reduce_q[lane] : 0.0f;
|
||||||
|
float k_total = lane < 4u ? reduce_k[lane] : 0.0f;
|
||||||
|
q_total = simd_sum(q_total);
|
||||||
|
k_total = simd_sum(k_total);
|
||||||
|
q[index] = sq[tid] * rsqrt(q_total + 1.0e-6f) *
|
||||||
|
0x1.6a09e6p-4f;
|
||||||
|
k[index] = sk[tid] * rsqrt(k_total + 1.0e-6f);
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup |
|
||||||
|
mem_flags::mem_device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_kda_prefill_recurrence(
|
||||||
|
constant glm53_kda_args &args,
|
||||||
|
device const float *q,
|
||||||
|
device const float *k,
|
||||||
|
device const float *v,
|
||||||
|
device const float *decay,
|
||||||
|
device const float *raw_beta,
|
||||||
|
device float *state,
|
||||||
|
device float *out,
|
||||||
|
uint2 tgpig [[threadgroup_position_in_grid]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]]) {
|
||||||
|
constexpr uint D = 128u;
|
||||||
|
const uint head = tgpig.x;
|
||||||
|
const uint value = tgpig.y * 4u + sg;
|
||||||
|
if (head >= args.n_heads || value >= D) return;
|
||||||
|
const uint projection = args.n_heads * D;
|
||||||
|
const uint k0 = lane * 4u;
|
||||||
|
device float4 *state_ptr = (device float4 *)(
|
||||||
|
state + ((ulong)head * D + value) * D + k0);
|
||||||
|
float4 h = *state_ptr;
|
||||||
|
|
||||||
|
for (uint token = 0; token < args.n_rows; token++) {
|
||||||
|
const ulong base = (ulong)token * projection + head * D;
|
||||||
|
const float4 q4 = *((device const float4 *)(q + base + k0));
|
||||||
|
const float4 k4 = *((device const float4 *)(k + base + k0));
|
||||||
|
const float4 decay4 =
|
||||||
|
*((device const float4 *)(decay + base + k0));
|
||||||
|
h *= decay4;
|
||||||
|
const float hk = simd_sum(dot(h, k4));
|
||||||
|
const float beta = 1.0f /
|
||||||
|
(1.0f + exp(-raw_beta[(ulong)token * args.n_heads + head]));
|
||||||
|
const float delta_v = (v[base + value] - hk) * beta;
|
||||||
|
h = fma(k4, float4(delta_v), h);
|
||||||
|
const float result = simd_sum(dot(h, q4));
|
||||||
|
if (lane == 0u) out[base + value] = result;
|
||||||
|
}
|
||||||
|
*state_ptr = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_kda_prefill_output(
|
||||||
|
constant glm53_kda_args &args,
|
||||||
|
device float *out,
|
||||||
|
device const float *output_gate,
|
||||||
|
device const float *output_norm,
|
||||||
|
threadgroup float *partial [[threadgroup(0)]],
|
||||||
|
uint2 tgpig [[threadgroup_position_in_grid]],
|
||||||
|
ushort tid [[thread_index_in_threadgroup]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]]) {
|
||||||
|
constexpr uint D = 128u;
|
||||||
|
const uint token = tgpig.x;
|
||||||
|
const uint head = tgpig.y;
|
||||||
|
if (token >= args.n_rows || head >= args.n_heads) return;
|
||||||
|
const uint projection = args.n_heads * D;
|
||||||
|
const ulong base = (ulong)token * projection + head * D;
|
||||||
|
const float raw = out[base + tid];
|
||||||
|
float sumsq = simd_sum(raw * raw);
|
||||||
|
if (lane == 0u) partial[sg] = sumsq;
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
float total = lane < 4u ? partial[lane] : 0.0f;
|
||||||
|
total = simd_sum(total);
|
||||||
|
const float scale = rsqrt(total / (float)D + args.norm_eps);
|
||||||
|
out[base + tid] = raw * scale * output_norm[tid] /
|
||||||
|
(1.0f + exp(-output_gate[base + tid]));
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
// GLM-5.3 Flash vision operations not covered by the shared BF16 matmuls.
|
||||||
|
|
||||||
|
struct glm53_vision_rows_args {
|
||||||
|
uint width;
|
||||||
|
uint rows;
|
||||||
|
float eps;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct glm53_vision_qkv_args {
|
||||||
|
uint rows;
|
||||||
|
uint grid_h;
|
||||||
|
uint grid_w;
|
||||||
|
float eps;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct glm53_vision_attention_args {
|
||||||
|
uint rows;
|
||||||
|
float scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct glm53_vision_scatter_args {
|
||||||
|
uint dst_row;
|
||||||
|
uint image_row;
|
||||||
|
uint rows;
|
||||||
|
uint total_rows;
|
||||||
|
uint width;
|
||||||
|
uint hc;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline float glm53_vision_erf(float x) {
|
||||||
|
const float sign = x < 0.0f ? -1.0f : 1.0f;
|
||||||
|
const float a = abs(x);
|
||||||
|
const float t = 1.0f / (1.0f + 0.3275911f * a);
|
||||||
|
const float p = (((((1.061405429f * t - 1.453152027f) * t) +
|
||||||
|
1.421413741f) * t - 0.284496736f) * t +
|
||||||
|
0.254829592f) * t;
|
||||||
|
return sign * (1.0f - p * exp(-a * a));
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_add_bias(
|
||||||
|
constant glm53_vision_rows_args &args,
|
||||||
|
device float *x,
|
||||||
|
device const ushort *bias,
|
||||||
|
uint2 gid [[thread_position_in_grid]]) {
|
||||||
|
if (gid.x >= args.width || gid.y >= args.rows) return;
|
||||||
|
x[(ulong)gid.y * args.width + gid.x] += glm53_bf16_to_f32(bias[gid.x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_rms_bf16(
|
||||||
|
constant glm53_vision_rows_args &args,
|
||||||
|
device const float *x,
|
||||||
|
device const ushort *weight,
|
||||||
|
device float *out,
|
||||||
|
threadgroup float *partial,
|
||||||
|
uint row [[threadgroup_position_in_grid]],
|
||||||
|
uint tid [[thread_index_in_threadgroup]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]],
|
||||||
|
ushort nsg [[simdgroups_per_threadgroup]]) {
|
||||||
|
if (row >= args.rows) return;
|
||||||
|
device const float *xr = x + (ulong)row * args.width;
|
||||||
|
device float *yr = out + (ulong)row * args.width;
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (uint d = tid; d < args.width; d += 256u) sum = fma(xr[d], xr[d], sum);
|
||||||
|
sum = simd_sum(sum);
|
||||||
|
if (lane == 0u) partial[sg] = sum;
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
if (sg == 0u) {
|
||||||
|
float v = lane < nsg ? partial[lane] : 0.0f;
|
||||||
|
v = simd_sum(v);
|
||||||
|
if (lane == 0u) partial[0] = rsqrt(v / (float)args.width + args.eps);
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
const float inv = partial[0];
|
||||||
|
for (uint d = tid; d < args.width; d += 256u) {
|
||||||
|
yr[d] = xr[d] * inv * glm53_bf16_to_f32(weight[d]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_qkv_rope(
|
||||||
|
constant glm53_vision_qkv_args &args,
|
||||||
|
device const float *qkv,
|
||||||
|
device const ushort *bias,
|
||||||
|
device const ushort *q_weight,
|
||||||
|
device const ushort *k_weight,
|
||||||
|
device float *q,
|
||||||
|
device float *k,
|
||||||
|
device float *v,
|
||||||
|
uint2 group [[threadgroup_position_in_grid]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]]) {
|
||||||
|
const uint row = group.x;
|
||||||
|
const uint head = group.y;
|
||||||
|
if (row >= args.rows || head >= 16u) return;
|
||||||
|
const ulong qkv_base = (ulong)row * 3072u + (ulong)head * 64u;
|
||||||
|
const ulong out_base = (ulong)row * 1024u + (ulong)head * 64u;
|
||||||
|
float q0 = qkv[qkv_base + lane] + glm53_bf16_to_f32(bias[(ulong)head * 64u + lane]);
|
||||||
|
float q1 = qkv[qkv_base + lane + 32u] +
|
||||||
|
glm53_bf16_to_f32(bias[(ulong)head * 64u + lane + 32u]);
|
||||||
|
float k0 = qkv[qkv_base + 1024u + lane] +
|
||||||
|
glm53_bf16_to_f32(bias[1024u + (ulong)head * 64u + lane]);
|
||||||
|
float k1 = qkv[qkv_base + 1024u + lane + 32u] +
|
||||||
|
glm53_bf16_to_f32(bias[1024u + (ulong)head * 64u + lane + 32u]);
|
||||||
|
const float qsum = simd_sum(fma(q0, q0, q1 * q1));
|
||||||
|
const float ksum = simd_sum(fma(k0, k0, k1 * k1));
|
||||||
|
const float qinv = rsqrt(qsum / 64.0f + args.eps);
|
||||||
|
const float kinv = rsqrt(ksum / 64.0f + args.eps);
|
||||||
|
q0 *= qinv * glm53_bf16_to_f32(q_weight[lane]);
|
||||||
|
q1 *= qinv * glm53_bf16_to_f32(q_weight[lane + 32u]);
|
||||||
|
k0 *= kinv * glm53_bf16_to_f32(k_weight[lane]);
|
||||||
|
k1 *= kinv * glm53_bf16_to_f32(k_weight[lane + 32u]);
|
||||||
|
|
||||||
|
const uint merge_w = args.grid_w / 2u;
|
||||||
|
const uint group_index = row / 4u;
|
||||||
|
const uint within = row & 3u;
|
||||||
|
const uint py = (group_index / merge_w) * 2u + within / 2u;
|
||||||
|
const uint px = (group_index % merge_w) * 2u + within % 2u;
|
||||||
|
const uint freq_index = lane & 15u;
|
||||||
|
const uint pos = lane < 16u ? py : px;
|
||||||
|
const float inv_freq = powr(10000.0f, -(float)freq_index / 16.0f);
|
||||||
|
const float angle = (float)pos * inv_freq;
|
||||||
|
const float cs = cos(angle);
|
||||||
|
const float sn = sin(angle);
|
||||||
|
q[out_base + lane] = q0 * cs - q1 * sn;
|
||||||
|
q[out_base + lane + 32u] = q1 * cs + q0 * sn;
|
||||||
|
k[out_base + lane] = k0 * cs - k1 * sn;
|
||||||
|
k[out_base + lane + 32u] = k1 * cs + k0 * sn;
|
||||||
|
v[out_base + lane] = qkv[qkv_base + 2048u + lane] +
|
||||||
|
glm53_bf16_to_f32(bias[2048u + (ulong)head * 64u + lane]);
|
||||||
|
v[out_base + lane + 32u] = qkv[qkv_base + 2048u + lane + 32u] +
|
||||||
|
glm53_bf16_to_f32(bias[2048u + (ulong)head * 64u + lane + 32u]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A simdgroup owns one query/head and keeps its 64 output values in registers.
|
||||||
|
* This is quadratic in compute, as the model graph requires, but linear in
|
||||||
|
* memory and never materializes the attention matrix. */
|
||||||
|
kernel void kernel_glm53_vision_attention(
|
||||||
|
constant glm53_vision_attention_args &args,
|
||||||
|
device const float *q,
|
||||||
|
device const float *k,
|
||||||
|
device const float *v,
|
||||||
|
device float *out,
|
||||||
|
uint2 group [[threadgroup_position_in_grid]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]]) {
|
||||||
|
const uint row = group.x;
|
||||||
|
const uint head = group.y;
|
||||||
|
if (row >= args.rows || head >= 16u) return;
|
||||||
|
const ulong base = (ulong)row * 1024u + (ulong)head * 64u;
|
||||||
|
const float q0 = q[base + lane];
|
||||||
|
const float q1 = q[base + lane + 32u];
|
||||||
|
float acc0 = 0.0f, acc1 = 0.0f;
|
||||||
|
float max_score = -INFINITY;
|
||||||
|
float denom = 0.0f;
|
||||||
|
for (uint key_row = 0; key_row < args.rows; key_row++) {
|
||||||
|
const ulong kb = (ulong)key_row * 1024u + (ulong)head * 64u;
|
||||||
|
float score = simd_sum(q0 * k[kb + lane] + q1 * k[kb + lane + 32u]);
|
||||||
|
score *= args.scale;
|
||||||
|
const float next_max = max(max_score, score);
|
||||||
|
const float old_scale = max_score == -INFINITY ? 0.0f : exp(max_score - next_max);
|
||||||
|
const float new_scale = exp(score - next_max);
|
||||||
|
denom = denom * old_scale + new_scale;
|
||||||
|
acc0 = acc0 * old_scale + new_scale * v[kb + lane];
|
||||||
|
acc1 = acc1 * old_scale + new_scale * v[kb + lane + 32u];
|
||||||
|
max_score = next_max;
|
||||||
|
}
|
||||||
|
out[base + lane] = acc0 / denom;
|
||||||
|
out[base + lane + 32u] = acc1 / denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_bias_residual(
|
||||||
|
constant glm53_vision_rows_args &args,
|
||||||
|
device float *x,
|
||||||
|
device const ushort *bias,
|
||||||
|
device const float *residual,
|
||||||
|
uint2 gid [[thread_position_in_grid]]) {
|
||||||
|
if (gid.x >= args.width || gid.y >= args.rows) return;
|
||||||
|
const ulong off = (ulong)gid.y * args.width + gid.x;
|
||||||
|
x[off] += glm53_bf16_to_f32(bias[gid.x]) + residual[off];
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_swiglu_bias(
|
||||||
|
constant glm53_vision_rows_args &args,
|
||||||
|
device const float *gate,
|
||||||
|
device const ushort *gate_bias,
|
||||||
|
device const float *up,
|
||||||
|
device const ushort *up_bias,
|
||||||
|
device float *out,
|
||||||
|
uint2 gid [[thread_position_in_grid]]) {
|
||||||
|
if (gid.x >= args.width || gid.y >= args.rows) return;
|
||||||
|
const ulong off = (ulong)gid.y * args.width + gid.x;
|
||||||
|
const float g = min(gate[off] + glm53_bf16_to_f32(gate_bias[gid.x]), 10.0f);
|
||||||
|
const float u = clamp(up[off] + glm53_bf16_to_f32(up_bias[gid.x]), -10.0f, 10.0f);
|
||||||
|
out[off] = (g / (1.0f + exp(-g))) * u;
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_downsample_reorder(
|
||||||
|
device const float *x,
|
||||||
|
device float *out,
|
||||||
|
uint2 gid [[thread_position_in_grid]]) {
|
||||||
|
const uint d = gid.x;
|
||||||
|
const uint row = gid.y;
|
||||||
|
if (d >= 4096u) return;
|
||||||
|
const uint channel = d / 4u;
|
||||||
|
const uint within = d & 3u;
|
||||||
|
out[(ulong)row * 4096u + d] = x[((ulong)row * 4u + within) * 1024u + channel];
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_layernorm_gelu(
|
||||||
|
constant glm53_vision_rows_args &args,
|
||||||
|
device const float *x,
|
||||||
|
device const ushort *weight,
|
||||||
|
device const ushort *bias,
|
||||||
|
device float *out,
|
||||||
|
threadgroup float *partial,
|
||||||
|
uint row [[threadgroup_position_in_grid]],
|
||||||
|
uint tid [[thread_index_in_threadgroup]],
|
||||||
|
ushort lane [[thread_index_in_simdgroup]],
|
||||||
|
ushort sg [[simdgroup_index_in_threadgroup]],
|
||||||
|
ushort nsg [[simdgroups_per_threadgroup]]) {
|
||||||
|
if (row >= args.rows) return;
|
||||||
|
device const float *xr = x + (ulong)row * args.width;
|
||||||
|
device float *yr = out + (ulong)row * args.width;
|
||||||
|
float sum = 0.0f;
|
||||||
|
for (uint d = tid; d < args.width; d += 256u) sum += xr[d];
|
||||||
|
sum = simd_sum(sum);
|
||||||
|
if (lane == 0u) partial[sg] = sum;
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
if (sg == 0u) {
|
||||||
|
float v = lane < nsg ? partial[lane] : 0.0f;
|
||||||
|
v = simd_sum(v);
|
||||||
|
if (lane == 0u) partial[0] = v / (float)args.width;
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
const float mean = partial[0];
|
||||||
|
float var = 0.0f;
|
||||||
|
for (uint d = tid; d < args.width; d += 256u) {
|
||||||
|
const float centered = xr[d] - mean;
|
||||||
|
var = fma(centered, centered, var);
|
||||||
|
}
|
||||||
|
var = simd_sum(var);
|
||||||
|
if (lane == 0u) partial[sg] = var;
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
if (sg == 0u) {
|
||||||
|
float v = lane < nsg ? partial[lane] : 0.0f;
|
||||||
|
v = simd_sum(v);
|
||||||
|
if (lane == 0u) partial[0] = rsqrt(v / (float)args.width + args.eps);
|
||||||
|
}
|
||||||
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
||||||
|
const float inv = partial[0];
|
||||||
|
const float inv_sqrt2 = 0.7071067811865475f;
|
||||||
|
for (uint d = tid; d < args.width; d += 256u) {
|
||||||
|
float v = (xr[d] - mean) * inv * glm53_bf16_to_f32(weight[d]) +
|
||||||
|
glm53_bf16_to_f32(bias[d]);
|
||||||
|
yr[d] = 0.5f * v * (1.0f + glm53_vision_erf(v * inv_sqrt2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void kernel_glm53_vision_scatter_hc(
|
||||||
|
constant glm53_vision_scatter_args &args,
|
||||||
|
device float *hc,
|
||||||
|
device const float *image,
|
||||||
|
uint2 gid [[thread_position_in_grid]]) {
|
||||||
|
const uint d = gid.x;
|
||||||
|
const uint linear_row = gid.y;
|
||||||
|
if (d >= args.width || linear_row >= args.rows * args.hc) return;
|
||||||
|
const uint image_delta = linear_row / args.hc;
|
||||||
|
const uint hc_index = linear_row % args.hc;
|
||||||
|
const ulong dst = ((ulong)(args.dst_row + image_delta) * args.hc + hc_index) *
|
||||||
|
args.width + d;
|
||||||
|
const ulong src = (ulong)(args.image_row + image_delta) * args.width + d;
|
||||||
|
if (args.dst_row + image_delta < args.total_rows) hc[dst] = image[src];
|
||||||
|
}
|
||||||
+64
-25
@@ -423,6 +423,7 @@ struct ds4_metal_glm_routed_moe_args {
|
|||||||
uint32_t n_tokens;
|
uint32_t n_tokens;
|
||||||
uint32_t mid_token_stride;
|
uint32_t mid_token_stride;
|
||||||
uint32_t down_type;
|
uint32_t down_type;
|
||||||
|
float swiglu_clamp;
|
||||||
/* Expert ownership under tensor parallelism: tp_world 0/1 = full
|
/* Expert ownership under tensor parallelism: tp_world 0/1 = full
|
||||||
* compute; otherwise each rank owns a contiguous expert range. */
|
* compute; otherwise each rank owns a contiguous expert range. */
|
||||||
int32_t tp_rank;
|
int32_t tp_rank;
|
||||||
@@ -436,6 +437,14 @@ struct ds4_metal_glm_routed_moe_args {
|
|||||||
uint64_t down_row_bytes;
|
uint64_t down_row_bytes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline float ds4_glm_swiglu(float gate, float up, float limit) {
|
||||||
|
if (limit > 1.0e-6f) {
|
||||||
|
gate = min(gate, limit);
|
||||||
|
up = clamp(up, -limit, limit);
|
||||||
|
}
|
||||||
|
return (gate / (1.0f + exp(-gate))) * up;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline bool ds4_tp_owns_expert(int expert, int n_total,
|
static inline bool ds4_tp_owns_expert(int expert, int n_total,
|
||||||
int tp_rank, int tp_world) {
|
int tp_rank, int tp_world) {
|
||||||
@@ -712,18 +721,20 @@ kernel void kernel_glm_q4_K_pair_swiglu_f32(
|
|||||||
const uint64_t mid_off = (uint64_t)token * args.mid_token_stride +
|
const uint64_t mid_off = (uint64_t)token * args.mid_token_stride +
|
||||||
(uint64_t)slot * args.mid_dim + row;
|
(uint64_t)slot * args.mid_dim + row;
|
||||||
const int expert = selected[selected_off];
|
const int expert = selected[selected_off];
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) {
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) {
|
||||||
if (tid == 0u) mid[mid_off] = 0.0f;
|
if (tid == 0u) mid[mid_off] = 0.0f;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const int local_expert = expert - args.tp_expert_base;
|
||||||
|
|
||||||
device const block_q4_K *gate_row =
|
device const block_q4_K *gate_row =
|
||||||
(device const block_q4_K *)(gate +
|
(device const block_q4_K *)(gate +
|
||||||
(uint64_t)(uint)expert * args.gate_expert_bytes +
|
(uint64_t)(uint)local_expert * args.gate_expert_bytes +
|
||||||
(uint64_t)row * args.gate_row_bytes);
|
(uint64_t)row * args.gate_row_bytes);
|
||||||
device const block_q4_K *up_row =
|
device const block_q4_K *up_row =
|
||||||
(device const block_q4_K *)(up +
|
(device const block_q4_K *)(up +
|
||||||
(uint64_t)(uint)expert * args.up_expert_bytes +
|
(uint64_t)(uint)local_expert * args.up_expert_bytes +
|
||||||
(uint64_t)row * args.up_row_bytes);
|
(uint64_t)row * args.up_row_bytes);
|
||||||
|
|
||||||
float acc_gate = 0.0f;
|
float acc_gate = 0.0f;
|
||||||
@@ -747,10 +758,8 @@ kernel void kernel_glm_q4_K_pair_swiglu_f32(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tid == 0u) {
|
if (tid == 0u) {
|
||||||
const float g = scratch[0];
|
mid[mid_off] = ds4_glm_swiglu(scratch[0], scratch[ntg],
|
||||||
const float u = scratch[ntg];
|
args.swiglu_clamp) * weights[selected_off];
|
||||||
const float sw = g / (1.0f + exp(-g));
|
|
||||||
mid[mid_off] = sw * u * weights[selected_off];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -887,8 +896,8 @@ static inline void glm_q2_K_pair_swiglu_simd_f32_impl(
|
|||||||
const float g = simd_sum(sumg[row]);
|
const float g = simd_sum(sumg[row]);
|
||||||
const float u = simd_sum(sumu[row]);
|
const float u = simd_sum(sumu[row]);
|
||||||
if (tiisg == 0u) {
|
if (tiisg == 0u) {
|
||||||
const float sw = g / (1.0f + exp(-g));
|
mid[mid_base + row0 + (uint)row] =
|
||||||
mid[mid_base + row0 + (uint)row] = sw * u * weights[selected_off];
|
ds4_glm_swiglu(g, u, args.swiglu_clamp) * weights[selected_off];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -944,7 +953,9 @@ kernel void kernel_glm_q2_K_addr_pair_swiglu2_f32(
|
|||||||
(uint64_t)slot * args.mid_dim;
|
(uint64_t)slot * args.mid_dim;
|
||||||
if (row0 >= args.mid_dim) return;
|
if (row0 >= args.mid_dim) return;
|
||||||
|
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) {
|
if (expert < 0 || (uint)expert >= args.n_total_expert ||
|
||||||
|
!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) {
|
||||||
if (tiisg == 0u) {
|
if (tiisg == 0u) {
|
||||||
for (short row = 0;
|
for (short row = 0;
|
||||||
row < N_R0_GLM_Q2_PAIR2_K && row0 + (uint)row < args.mid_dim;
|
row < N_R0_GLM_Q2_PAIR2_K && row0 + (uint)row < args.mid_dim;
|
||||||
@@ -1005,7 +1016,9 @@ kernel void kernel_glm_q2_K_addr_pair_swiglu2_f32_masked(
|
|||||||
(uint64_t)slot * args.mid_dim;
|
(uint64_t)slot * args.mid_dim;
|
||||||
if (row0 >= args.mid_dim) return;
|
if (row0 >= args.mid_dim) return;
|
||||||
|
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) {
|
if (expert < 0 || (uint)expert >= args.n_total_expert ||
|
||||||
|
!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) {
|
||||||
if (tiisg == 0u) {
|
if (tiisg == 0u) {
|
||||||
for (short row = 0;
|
for (short row = 0;
|
||||||
row < N_R0_GLM_Q2_PAIR2_K && row0 + (uint)row < args.mid_dim;
|
row < N_R0_GLM_Q2_PAIR2_K && row0 + (uint)row < args.mid_dim;
|
||||||
@@ -1191,8 +1204,8 @@ static inline void glm_q4_K_pair_swiglu_simd_f32_impl(
|
|||||||
const float g = simd_sum(sumg[row]);
|
const float g = simd_sum(sumg[row]);
|
||||||
const float u = simd_sum(sumu[row]);
|
const float u = simd_sum(sumu[row]);
|
||||||
if (tiisg == 0u) {
|
if (tiisg == 0u) {
|
||||||
const float sw = g / (1.0f + exp(-g));
|
mid[mid_base + row0 + (uint)row] =
|
||||||
mid[mid_base + row0 + (uint)row] = sw * u * weights[selected_off];
|
ds4_glm_swiglu(g, u, args.swiglu_clamp) * weights[selected_off];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1216,9 +1229,12 @@ kernel void kernel_glm_q4_K_pair_swiglu2_f32(
|
|||||||
if (slot >= args.n_expert_used || token >= args.n_tokens) return;
|
if (slot >= args.n_expert_used || token >= args.n_tokens) return;
|
||||||
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
||||||
const int expert = selected[selected_off];
|
const int expert = selected[selected_off];
|
||||||
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) return;
|
||||||
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_GLM_Q4_PAIR2_K>(
|
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_GLM_Q4_PAIR2_K>(
|
||||||
args, gate, up, x, weights, mid, scratch,
|
args, gate, up, x, weights, mid, scratch,
|
||||||
tgpig, slot, token, selected_off, expert, tiisg, sgitg);
|
tgpig, slot, token, selected_off,
|
||||||
|
expert - args.tp_expert_base, tiisg, sgitg);
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void kernel_glm_q4_K_addr_pair_swiglu_f32(
|
kernel void kernel_glm_q4_K_addr_pair_swiglu_f32(
|
||||||
@@ -1245,7 +1261,9 @@ kernel void kernel_glm_q4_K_addr_pair_swiglu_f32(
|
|||||||
(uint64_t)slot * args.mid_dim;
|
(uint64_t)slot * args.mid_dim;
|
||||||
if (row0 >= args.mid_dim) return;
|
if (row0 >= args.mid_dim) return;
|
||||||
|
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) {
|
if (expert < 0 || (uint)expert >= args.n_total_expert ||
|
||||||
|
!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) {
|
||||||
if (tiisg == 0u) {
|
if (tiisg == 0u) {
|
||||||
for (short row = 0;
|
for (short row = 0;
|
||||||
row < N_R0_Q4_K && row0 + (uint)row < args.mid_dim;
|
row < N_R0_Q4_K && row0 + (uint)row < args.mid_dim;
|
||||||
@@ -1306,7 +1324,9 @@ kernel void kernel_glm_q4_K_addr_pair_swiglu_f32_masked(
|
|||||||
(uint64_t)slot * args.mid_dim;
|
(uint64_t)slot * args.mid_dim;
|
||||||
if (row0 >= args.mid_dim) return;
|
if (row0 >= args.mid_dim) return;
|
||||||
|
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) {
|
if (expert < 0 || (uint)expert >= args.n_total_expert ||
|
||||||
|
!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) {
|
||||||
if (tiisg == 0u) {
|
if (tiisg == 0u) {
|
||||||
for (short row = 0;
|
for (short row = 0;
|
||||||
row < N_R0_Q4_K && row0 + (uint)row < args.mid_dim;
|
row < N_R0_Q4_K && row0 + (uint)row < args.mid_dim;
|
||||||
@@ -1358,9 +1378,12 @@ kernel void kernel_glm_q4_K_pair_swiglu4_f32(
|
|||||||
if (slot >= args.n_expert_used || token >= args.n_tokens) return;
|
if (slot >= args.n_expert_used || token >= args.n_tokens) return;
|
||||||
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
||||||
const int expert = selected[selected_off];
|
const int expert = selected[selected_off];
|
||||||
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) return;
|
||||||
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_GLM_Q4_PAIR_K>(
|
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_GLM_Q4_PAIR_K>(
|
||||||
args, gate, up, x, weights, mid, scratch,
|
args, gate, up, x, weights, mid, scratch,
|
||||||
tgpig, slot, token, selected_off, expert, tiisg, sgitg);
|
tgpig, slot, token, selected_off,
|
||||||
|
expert - args.tp_expert_base, tiisg, sgitg);
|
||||||
}
|
}
|
||||||
|
|
||||||
kernel void kernel_glm_q4_K_pair_swiglu2_mapped_f32(
|
kernel void kernel_glm_q4_K_pair_swiglu2_mapped_f32(
|
||||||
@@ -1378,6 +1401,8 @@ kernel void kernel_glm_q4_K_pair_swiglu2_mapped_f32(
|
|||||||
ushort sgitg [[simdgroup_index_in_threadgroup]]) {
|
ushort sgitg [[simdgroup_index_in_threadgroup]]) {
|
||||||
const uint expert = tgpig.z;
|
const uint expert = tgpig.z;
|
||||||
if (expert >= args.n_total_expert) return;
|
if (expert >= args.n_total_expert) return;
|
||||||
|
if (!ds4_tp_owns_expert((int)expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) return;
|
||||||
const uint count = htpe[expert];
|
const uint count = htpe[expert];
|
||||||
const uint map_base = tgpig.y * 32u;
|
const uint map_base = tgpig.y * 32u;
|
||||||
for (uint i = 0; i < 32u; i++) {
|
for (uint i = 0; i < 32u; i++) {
|
||||||
@@ -1391,7 +1416,8 @@ kernel void kernel_glm_q4_K_pair_swiglu2_mapped_f32(
|
|||||||
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
||||||
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_Q4_K>(
|
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_Q4_K>(
|
||||||
args, gate, up, x, weights, mid, scratch,
|
args, gate, up, x, weights, mid, scratch,
|
||||||
tgpig, slot, token, selected_off, (int)expert, tiisg, sgitg);
|
tgpig, slot, token, selected_off,
|
||||||
|
(int)expert - args.tp_expert_base, tiisg, sgitg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1411,6 +1437,8 @@ kernel void kernel_glm_q4_K_pair_swiglu2_mapped_row_f32(
|
|||||||
const uint expert = tgpig.z;
|
const uint expert = tgpig.z;
|
||||||
const uint map_row = tgpig.y;
|
const uint map_row = tgpig.y;
|
||||||
if (expert >= args.n_total_expert || map_row >= htpe[expert]) return;
|
if (expert >= args.n_total_expert || map_row >= htpe[expert]) return;
|
||||||
|
if (!ds4_tp_owns_expert((int)expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) return;
|
||||||
const int id = hids[(uint64_t)expert * args.n_tokens + map_row];
|
const int id = hids[(uint64_t)expert * args.n_tokens + map_row];
|
||||||
if (id < 0) return;
|
if (id < 0) return;
|
||||||
const uint token = (uint)id / args.n_expert_used;
|
const uint token = (uint)id / args.n_expert_used;
|
||||||
@@ -1419,7 +1447,8 @@ kernel void kernel_glm_q4_K_pair_swiglu2_mapped_row_f32(
|
|||||||
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
const uint64_t selected_off = (uint64_t)token * args.n_expert_used + slot;
|
||||||
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_Q4_K>(
|
glm_q4_K_pair_swiglu_simd_f32_impl<N_R0_Q4_K>(
|
||||||
args, gate, up, x, weights, mid, scratch,
|
args, gate, up, x, weights, mid, scratch,
|
||||||
tgpig, slot, token, selected_off, (int)expert, tiisg, sgitg);
|
tgpig, slot, token, selected_off,
|
||||||
|
(int)expert - args.tp_expert_base, tiisg, sgitg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void glm_q5_K_pair_swiglu_f32_impl(
|
static inline void glm_q5_K_pair_swiglu_f32_impl(
|
||||||
@@ -1591,8 +1620,8 @@ static inline void glm_q5_K_pair_swiglu_f32_impl(
|
|||||||
const float g = simd_sum(sumg[row]);
|
const float g = simd_sum(sumg[row]);
|
||||||
const float u = simd_sum(sumu[row]);
|
const float u = simd_sum(sumu[row]);
|
||||||
if (tiisg == 0u) {
|
if (tiisg == 0u) {
|
||||||
const float sw = g / (1.0f + exp(-g));
|
mid[mid_base + row0 + (uint)row] =
|
||||||
mid[mid_base + row0 + (uint)row] = sw * u * weights[selected_off];
|
ds4_glm_swiglu(g, u, args.swiglu_clamp) * weights[selected_off];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1913,6 +1942,8 @@ kernel void kernel_glm_q2_K_addr_down_f32(
|
|||||||
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
||||||
const int expert = selected[selected_base + slot];
|
const int expert = selected[selected_base + slot];
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
||||||
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) continue;
|
||||||
const uint64_t down_addr = down_addrs[(uint)expert];
|
const uint64_t down_addr = down_addrs[(uint)expert];
|
||||||
if (down_addr == 0) continue;
|
if (down_addr == 0) continue;
|
||||||
device const block_q2_K *x =
|
device const block_q2_K *x =
|
||||||
@@ -1996,10 +2027,12 @@ kernel void kernel_glm_q4_K_down_f32(
|
|||||||
const uint64_t mid_base = (uint64_t)token * args.mid_token_stride;
|
const uint64_t mid_base = (uint64_t)token * args.mid_token_stride;
|
||||||
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
||||||
const int expert = selected[selected_base + slot];
|
const int expert = selected[selected_base + slot];
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) continue;
|
||||||
device const block_q4_K *down_row =
|
device const block_q4_K *down_row =
|
||||||
(device const block_q4_K *)(down +
|
(device const block_q4_K *)(down +
|
||||||
(uint64_t)(uint)expert * args.down_expert_bytes +
|
(uint64_t)(uint)(expert - args.tp_expert_base) *
|
||||||
|
args.down_expert_bytes +
|
||||||
(uint64_t)row * args.down_row_bytes);
|
(uint64_t)row * args.down_row_bytes);
|
||||||
device const float *slot_mid = mid + mid_base + (uint64_t)slot * args.mid_dim;
|
device const float *slot_mid = mid + mid_base + (uint64_t)slot * args.mid_dim;
|
||||||
for (uint k = tid; k < args.mid_dim; k += ntg) {
|
for (uint k = tid; k < args.mid_dim; k += ntg) {
|
||||||
@@ -2038,6 +2071,8 @@ kernel void kernel_glm_q4_K_addr_down_f32(
|
|||||||
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
||||||
const int expert = selected[selected_base + slot];
|
const int expert = selected[selected_base + slot];
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
||||||
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) continue;
|
||||||
const uint64_t down_addr = down_addrs[(uint)expert];
|
const uint64_t down_addr = down_addrs[(uint)expert];
|
||||||
if (down_addr == 0) continue;
|
if (down_addr == 0) continue;
|
||||||
device const block_q4_K *down_row =
|
device const block_q4_K *down_row =
|
||||||
@@ -2093,11 +2128,13 @@ kernel void kernel_glm_q4_K_down_simd_f32(
|
|||||||
const uint64_t mid_base = (uint64_t)token * args.mid_token_stride;
|
const uint64_t mid_base = (uint64_t)token * args.mid_token_stride;
|
||||||
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
||||||
const int expert = selected[selected_base + slot];
|
const int expert = selected[selected_base + slot];
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) continue;
|
||||||
|
|
||||||
device const block_q4_K *x =
|
device const block_q4_K *x =
|
||||||
(device const block_q4_K *)(down +
|
(device const block_q4_K *)(down +
|
||||||
(uint64_t)(uint)expert * args.down_expert_bytes +
|
(uint64_t)(uint)(expert - args.tp_expert_base) *
|
||||||
|
args.down_expert_bytes +
|
||||||
(uint64_t)row0 * args.down_row_bytes);
|
(uint64_t)row0 * args.down_row_bytes);
|
||||||
device const float *y = mid + mid_base + (uint64_t)slot * args.mid_dim;
|
device const float *y = mid + mid_base + (uint64_t)slot * args.mid_dim;
|
||||||
device const float *y4 = y + ix * QK_K + 64 * iq + 8 * ir;
|
device const float *y4 = y + ix * QK_K + 64 * iq + 8 * ir;
|
||||||
@@ -2197,6 +2234,8 @@ kernel void kernel_glm_q4_K_addr_down_simd_f32(
|
|||||||
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
for (uint slot = 0; slot < args.n_expert_used; slot++) {
|
||||||
const int expert = selected[selected_base + slot];
|
const int expert = selected[selected_base + slot];
|
||||||
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
if (expert < 0 || (uint)expert >= args.n_total_expert) continue;
|
||||||
|
if (!ds4_tp_owns_expert(expert, args.n_total_expert,
|
||||||
|
args.tp_rank, args.tp_world)) continue;
|
||||||
const uint64_t down_addr = down_addrs[(uint)expert];
|
const uint64_t down_addr = down_addrs[(uint)expert];
|
||||||
if (down_addr == 0) continue;
|
if (down_addr == 0) continue;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
# Vendored DS4 Metal boundary
|
# Vendored DS4 Metal boundary
|
||||||
|
|
||||||
These files track the DS4 Metal boundary audited at commit
|
The Metal bridge and GPU declarations track the DS4 boundary audited at commit
|
||||||
`8db89fe083ae4d17c9a2428ccd29803d3ae8f577` (2026-08-28):
|
`ec7642cdd9ec81d01ad4b1fd8f8a3d1511533748` (2026-08-31):
|
||||||
|
|
||||||
- `ds4_metal.m`
|
- `ds4_metal.m`
|
||||||
- `ds4.h`
|
|
||||||
- `ds4_gpu.h`
|
- `ds4_gpu.h`
|
||||||
- `ds4_ssd.h`
|
|
||||||
- `LICENSE`
|
|
||||||
|
|
||||||
The matching Metal kernels live in the repository-level `metal/` directory.
|
The matching Metal kernels, including `glm53_*.metal`, live in the
|
||||||
|
repository-level `metal/` directory. The other headers remain DS4Server-owned
|
||||||
|
interfaces and are not copies of DS4 application code.
|
||||||
DS4Server builds and bundles this local snapshot; it does not read a sibling
|
DS4Server builds and bundles this local snapshot; it does not read a sibling
|
||||||
DS4 checkout. Rust owns the model, graph, session, sampling, and lifecycle.
|
DS4 checkout. Rust owns the model, graph, session, sampling, and lifecycle.
|
||||||
Objective-C remains only at the platform Metal boundary.
|
Objective-C remains only at the platform Metal boundary.
|
||||||
@@ -118,6 +118,11 @@ int ds4_gpu_build_derived_artifacts(const void *model_map, uint64_t model_size,
|
|||||||
int ds4_gpu_model_range_replaced(const void *model_map, uint64_t offset,
|
int ds4_gpu_model_range_replaced(const void *model_map, uint64_t offset,
|
||||||
uint64_t bytes);
|
uint64_t bytes);
|
||||||
int ds4_gpu_set_model_map_range(const void *model_map, uint64_t model_size, uint64_t map_offset, uint64_t map_size, uint64_t max_tensor_bytes);
|
int ds4_gpu_set_model_map_range(const void *model_map, uint64_t model_size, uint64_t map_offset, uint64_t map_size, uint64_t max_tensor_bytes);
|
||||||
|
/* Add a secondary GGUF mapping without replacing the primary model mapping. */
|
||||||
|
int ds4_gpu_set_aux_model_map_range(const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t map_offset,
|
||||||
|
uint64_t map_size);
|
||||||
int ds4_gpu_set_model_map_spans(const void *model_map, uint64_t model_size, const uint64_t *offsets, const uint64_t *sizes, uint32_t count, uint64_t max_tensor_bytes);
|
int ds4_gpu_set_model_map_spans(const void *model_map, uint64_t model_size, const uint64_t *offsets, const uint64_t *sizes, uint32_t count, uint64_t max_tensor_bytes);
|
||||||
int ds4_gpu_cache_model_range(const void *model_map, uint64_t model_size, uint64_t offset, uint64_t bytes, const char *label);
|
int ds4_gpu_cache_model_range(const void *model_map, uint64_t model_size, uint64_t offset, uint64_t bytes, const char *label);
|
||||||
int ds4_gpu_cache_q8_f16_range(const void *model_map, uint64_t model_size, uint64_t offset, uint64_t bytes, uint64_t in_dim, uint64_t out_dim, const char *label);
|
int ds4_gpu_cache_q8_f16_range(const void *model_map, uint64_t model_size, uint64_t offset, uint64_t bytes, uint64_t in_dim, uint64_t out_dim, const char *label);
|
||||||
@@ -668,6 +673,17 @@ int ds4_gpu_matmul_q8_0_pair_tensor(
|
|||||||
const ds4_gpu_tensor *x,
|
const ds4_gpu_tensor *x,
|
||||||
uint64_t n_tok);
|
uint64_t n_tok);
|
||||||
|
|
||||||
|
int ds4_gpu_matmul_q4_K_pair_decode_tensor(
|
||||||
|
ds4_gpu_tensor *out0,
|
||||||
|
ds4_gpu_tensor *out1,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t weight0_offset,
|
||||||
|
uint64_t weight1_offset,
|
||||||
|
uint64_t in_dim,
|
||||||
|
uint64_t out_dim,
|
||||||
|
const ds4_gpu_tensor *x);
|
||||||
|
|
||||||
/* Multi-row decode projections that preserve the one-row reduction order. */
|
/* Multi-row decode projections that preserve the one-row reduction order. */
|
||||||
int ds4_gpu_matmul_q8_0_decode_rows_exact_tensor(
|
int ds4_gpu_matmul_q8_0_decode_rows_exact_tensor(
|
||||||
ds4_gpu_tensor *out,
|
ds4_gpu_tensor *out,
|
||||||
@@ -1276,6 +1292,37 @@ int ds4_gpu_glm_store_indexer_k_tensor(
|
|||||||
float beta_slow,
|
float beta_slow,
|
||||||
bool cache_f16);
|
bool cache_f16);
|
||||||
|
|
||||||
|
/* GLM-5.3 pools four normalized indexer keys with a learned, per-channel
|
||||||
|
* softmax. Partial pools are retained in tail_k/tail_gate across calls. */
|
||||||
|
int ds4_gpu_glm53_indexer_pool_update_tensor(
|
||||||
|
ds4_gpu_tensor *pool_cache,
|
||||||
|
ds4_gpu_tensor *tail_k,
|
||||||
|
ds4_gpu_tensor *tail_gate,
|
||||||
|
const ds4_gpu_tensor *raw_k,
|
||||||
|
const ds4_gpu_tensor *gate,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t norm_weight_offset,
|
||||||
|
uint64_t norm_bias_offset,
|
||||||
|
uint64_t ape_offset,
|
||||||
|
uint32_t pos0,
|
||||||
|
uint32_t n_tokens,
|
||||||
|
uint32_t cache_cap,
|
||||||
|
uint32_t head_dim,
|
||||||
|
uint32_t pool_size,
|
||||||
|
float eps,
|
||||||
|
bool cache_f16);
|
||||||
|
|
||||||
|
int ds4_gpu_glm53_expand_pool_selection_tensor(
|
||||||
|
ds4_gpu_tensor *raw_selected,
|
||||||
|
const ds4_gpu_tensor *pool_selected,
|
||||||
|
uint32_t n_tokens,
|
||||||
|
uint32_t pos0,
|
||||||
|
uint32_t selected_pools,
|
||||||
|
uint32_t index_topk,
|
||||||
|
uint32_t pool_size,
|
||||||
|
uint32_t output_width);
|
||||||
|
|
||||||
int ds4_gpu_glm_build_kv_cache_tensor(
|
int ds4_gpu_glm_build_kv_cache_tensor(
|
||||||
ds4_gpu_tensor *key_cache,
|
ds4_gpu_tensor *key_cache,
|
||||||
ds4_gpu_tensor *value_cache,
|
ds4_gpu_tensor *value_cache,
|
||||||
@@ -1388,6 +1435,20 @@ int ds4_gpu_glm_indexer_scores_batch_tensor(
|
|||||||
float scale,
|
float scale,
|
||||||
bool cache_f16);
|
bool cache_f16);
|
||||||
|
|
||||||
|
int ds4_gpu_glm53_indexer_scores_batch_tensor(
|
||||||
|
ds4_gpu_tensor *scores,
|
||||||
|
const ds4_gpu_tensor *q,
|
||||||
|
const ds4_gpu_tensor *weights,
|
||||||
|
const ds4_gpu_tensor *indexer_key_cache,
|
||||||
|
uint32_t n_rows,
|
||||||
|
uint32_t n_tokens,
|
||||||
|
uint32_t pos0,
|
||||||
|
uint32_t pool_size,
|
||||||
|
uint32_t n_head,
|
||||||
|
uint32_t head_dim,
|
||||||
|
float scale,
|
||||||
|
bool cache_f16);
|
||||||
|
|
||||||
int ds4_gpu_glm_qk_lowrank_q8_0_tensor(
|
int ds4_gpu_glm_qk_lowrank_q8_0_tensor(
|
||||||
ds4_gpu_tensor *qk_low,
|
ds4_gpu_tensor *qk_low,
|
||||||
const ds4_gpu_tensor *q,
|
const ds4_gpu_tensor *q,
|
||||||
@@ -1698,6 +1759,21 @@ int ds4_gpu_glm_attention_indexed_batch_lora_causal_tensor(
|
|||||||
float beta_fast,
|
float beta_fast,
|
||||||
float beta_slow);
|
float beta_slow);
|
||||||
|
|
||||||
|
/* Dense causal MLA over the shared compact latent cache. qk_low and lora_out
|
||||||
|
* are [token, head, kv_lora_dim]; the F16 cache is shared by all heads. */
|
||||||
|
int ds4_gpu_glm_attention_dense_compact_lora_causal_tensor(
|
||||||
|
ds4_gpu_tensor *lora_out,
|
||||||
|
const ds4_gpu_tensor *qk_low,
|
||||||
|
const ds4_gpu_tensor *kv_lora_cache,
|
||||||
|
uint32_t q_row0,
|
||||||
|
uint32_t n_q,
|
||||||
|
uint32_t n_kv,
|
||||||
|
uint32_t cache_cap,
|
||||||
|
bool cache_f16,
|
||||||
|
uint32_t n_head,
|
||||||
|
uint32_t kv_lora_dim,
|
||||||
|
uint32_t qk_dim);
|
||||||
|
|
||||||
int ds4_gpu_glm_attention_indexed_batch_lora_valid_tensor(
|
int ds4_gpu_glm_attention_indexed_batch_lora_valid_tensor(
|
||||||
ds4_gpu_tensor *lora_out,
|
ds4_gpu_tensor *lora_out,
|
||||||
const ds4_gpu_tensor *q,
|
const ds4_gpu_tensor *q,
|
||||||
@@ -2366,6 +2442,7 @@ int ds4_gpu_glm_routed_moe_one_tensor(
|
|||||||
const ds4_gpu_tensor *weights,
|
const ds4_gpu_tensor *weights,
|
||||||
uint32_t n_total_expert,
|
uint32_t n_total_expert,
|
||||||
uint32_t n_expert,
|
uint32_t n_expert,
|
||||||
|
float swiglu_clamp,
|
||||||
uint32_t layer_index,
|
uint32_t layer_index,
|
||||||
const ds4_gpu_tensor *x,
|
const ds4_gpu_tensor *x,
|
||||||
bool force_resident);
|
bool force_resident);
|
||||||
@@ -2394,6 +2471,7 @@ int ds4_gpu_glm_routed_moe_batch_tensor(
|
|||||||
const ds4_gpu_tensor *weights,
|
const ds4_gpu_tensor *weights,
|
||||||
uint32_t n_total_expert,
|
uint32_t n_total_expert,
|
||||||
uint32_t n_expert,
|
uint32_t n_expert,
|
||||||
|
float swiglu_clamp,
|
||||||
uint32_t layer_index,
|
uint32_t layer_index,
|
||||||
const ds4_gpu_tensor *x,
|
const ds4_gpu_tensor *x,
|
||||||
uint32_t n_tokens,
|
uint32_t n_tokens,
|
||||||
@@ -2424,6 +2502,7 @@ int ds4_gpu_glm_routed_moe_batch_direct_scalar_q4_tensor(
|
|||||||
const ds4_gpu_tensor *weights,
|
const ds4_gpu_tensor *weights,
|
||||||
uint32_t n_total_expert,
|
uint32_t n_total_expert,
|
||||||
uint32_t n_expert,
|
uint32_t n_expert,
|
||||||
|
float swiglu_clamp,
|
||||||
uint32_t layer_index,
|
uint32_t layer_index,
|
||||||
const ds4_gpu_tensor *x,
|
const ds4_gpu_tensor *x,
|
||||||
uint32_t n_tokens,
|
uint32_t n_tokens,
|
||||||
@@ -2431,6 +2510,10 @@ int ds4_gpu_glm_routed_moe_batch_direct_scalar_q4_tensor(
|
|||||||
|
|
||||||
int ds4_gpu_routed_moe_set_selected_override(const int32_t *selected, uint32_t n_selected);
|
int ds4_gpu_routed_moe_set_selected_override(const int32_t *selected, uint32_t n_selected);
|
||||||
void ds4_gpu_set_glm_mtp_verify_mode(bool enabled);
|
void ds4_gpu_set_glm_mtp_verify_mode(bool enabled);
|
||||||
|
#ifdef DS4_ROCM_BUILD
|
||||||
|
int ds4_gpu_dspark_gfx1151_fast_path(void);
|
||||||
|
void ds4_gpu_set_dspark_verify_mode(bool enabled);
|
||||||
|
#endif
|
||||||
|
|
||||||
int ds4_gpu_matmul_q8_0_kslice_hc_expand_add_tensor(
|
int ds4_gpu_matmul_q8_0_kslice_hc_expand_add_tensor(
|
||||||
ds4_gpu_tensor *out_hc,
|
ds4_gpu_tensor *out_hc,
|
||||||
@@ -2850,6 +2933,146 @@ int ds4_gpu_matmul_q8_0_hc_expand_tensor(
|
|||||||
uint32_t n_embd,
|
uint32_t n_embd,
|
||||||
uint32_t n_hc);
|
uint32_t n_hc);
|
||||||
|
|
||||||
|
int ds4_gpu_glm53_embedding_bf16(
|
||||||
|
ds4_gpu_tensor *out,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t weight_offset,
|
||||||
|
const ds4_gpu_tensor *token_ids,
|
||||||
|
uint32_t n_tokens,
|
||||||
|
uint32_t n_embd,
|
||||||
|
uint32_t n_vocab);
|
||||||
|
|
||||||
|
int ds4_gpu_glm53_matmul_bf16(
|
||||||
|
ds4_gpu_tensor *out,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t weight_offset,
|
||||||
|
uint32_t in_dim,
|
||||||
|
uint32_t out_dim,
|
||||||
|
const ds4_gpu_tensor *x,
|
||||||
|
uint32_t n_rows);
|
||||||
|
|
||||||
|
int ds4_gpu_glm53_matmul_bf16_qkv(
|
||||||
|
ds4_gpu_tensor *out_q,
|
||||||
|
ds4_gpu_tensor *out_k,
|
||||||
|
ds4_gpu_tensor *out_v,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t weight_q_offset,
|
||||||
|
uint64_t weight_k_offset,
|
||||||
|
uint64_t weight_v_offset,
|
||||||
|
uint32_t in_dim,
|
||||||
|
uint32_t out_dim,
|
||||||
|
const ds4_gpu_tensor *x);
|
||||||
|
|
||||||
|
#ifndef DS4_GLM53_VISION_TYPES_DEFINED
|
||||||
|
#define DS4_GLM53_VISION_TYPES_DEFINED
|
||||||
|
#define DS4_GLM53_VISION_LAYERS 24u
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t norm1;
|
||||||
|
uint64_t qkv_weight;
|
||||||
|
uint64_t qkv_bias;
|
||||||
|
uint64_t q_norm;
|
||||||
|
uint64_t k_norm;
|
||||||
|
uint64_t attn_proj_weight;
|
||||||
|
uint64_t attn_proj_bias;
|
||||||
|
uint64_t norm2;
|
||||||
|
uint64_t gate_weight;
|
||||||
|
uint64_t gate_bias;
|
||||||
|
uint64_t up_weight;
|
||||||
|
uint64_t up_bias;
|
||||||
|
uint64_t down_weight;
|
||||||
|
uint64_t down_bias;
|
||||||
|
} ds4_glm53_vision_layer_weights;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t patch_weight;
|
||||||
|
uint64_t patch_bias;
|
||||||
|
uint64_t post_norm;
|
||||||
|
uint64_t downsample_weight;
|
||||||
|
uint64_t downsample_bias;
|
||||||
|
uint64_t merger_proj;
|
||||||
|
uint64_t merger_norm;
|
||||||
|
uint64_t merger_norm_bias;
|
||||||
|
uint64_t merger_gate;
|
||||||
|
uint64_t merger_up;
|
||||||
|
uint64_t merger_down;
|
||||||
|
ds4_glm53_vision_layer_weights layer[DS4_GLM53_VISION_LAYERS];
|
||||||
|
} ds4_glm53_vision_weights;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Encode normalized, block-major image patches into 4096-wide language-model
|
||||||
|
* embeddings. GPU implementations keep every intermediate on device. */
|
||||||
|
int ds4_gpu_glm53_vision_encode(
|
||||||
|
float *out,
|
||||||
|
const float *patches,
|
||||||
|
uint32_t grid_h,
|
||||||
|
uint32_t grid_w,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
const ds4_glm53_vision_weights *weights);
|
||||||
|
|
||||||
|
/* Replace token rows with projected image embeddings and repeat each row into
|
||||||
|
* every GLM hyperconnection stream. Must be called in an active command batch. */
|
||||||
|
int ds4_gpu_glm53_scatter_image_hc(
|
||||||
|
ds4_gpu_tensor *hc,
|
||||||
|
const ds4_gpu_tensor *image,
|
||||||
|
uint32_t dst_row,
|
||||||
|
uint32_t image_row,
|
||||||
|
uint32_t rows,
|
||||||
|
uint32_t total_rows,
|
||||||
|
uint32_t n_embd,
|
||||||
|
uint32_t n_hc);
|
||||||
|
|
||||||
|
/* GLM-5.3 Kimi Delta Attention. Recurrent and convolution state stay FP32. */
|
||||||
|
int ds4_gpu_glm53_kda_decode(
|
||||||
|
ds4_gpu_tensor *out,
|
||||||
|
ds4_gpu_tensor *conv_state,
|
||||||
|
ds4_gpu_tensor *recurrent_state,
|
||||||
|
const ds4_gpu_tensor *q,
|
||||||
|
const ds4_gpu_tensor *k,
|
||||||
|
const ds4_gpu_tensor *v,
|
||||||
|
const ds4_gpu_tensor *raw_gate,
|
||||||
|
const ds4_gpu_tensor *raw_beta,
|
||||||
|
const ds4_gpu_tensor *output_gate,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t q_conv_offset,
|
||||||
|
uint64_t k_conv_offset,
|
||||||
|
uint64_t v_conv_offset,
|
||||||
|
uint64_t a_log_offset,
|
||||||
|
uint64_t dt_bias_offset,
|
||||||
|
uint64_t output_norm_offset,
|
||||||
|
uint32_t n_heads,
|
||||||
|
uint32_t n_rows,
|
||||||
|
float gate_lower_bound,
|
||||||
|
float norm_eps);
|
||||||
|
|
||||||
|
int ds4_gpu_glm53_kda_prefill(
|
||||||
|
ds4_gpu_tensor *out,
|
||||||
|
ds4_gpu_tensor *conv_state,
|
||||||
|
ds4_gpu_tensor *recurrent_state,
|
||||||
|
ds4_gpu_tensor *q,
|
||||||
|
ds4_gpu_tensor *k,
|
||||||
|
ds4_gpu_tensor *v,
|
||||||
|
ds4_gpu_tensor *raw_gate,
|
||||||
|
const ds4_gpu_tensor *raw_beta,
|
||||||
|
const ds4_gpu_tensor *output_gate,
|
||||||
|
const void *model_map,
|
||||||
|
uint64_t model_size,
|
||||||
|
uint64_t q_conv_offset,
|
||||||
|
uint64_t k_conv_offset,
|
||||||
|
uint64_t v_conv_offset,
|
||||||
|
uint64_t a_log_offset,
|
||||||
|
uint64_t dt_bias_offset,
|
||||||
|
uint64_t output_norm_offset,
|
||||||
|
uint32_t n_heads,
|
||||||
|
uint32_t n_tokens,
|
||||||
|
float gate_lower_bound,
|
||||||
|
float norm_eps);
|
||||||
|
|
||||||
/* Decode-island CUDA graph capture (CUDA backend; Metal/ROCm/CPU stub it
|
/* Decode-island CUDA graph capture (CUDA backend; Metal/ROCm/CPU stub it
|
||||||
* out and stay eager). Design ported from the Entrpi/ds4 batched-serving
|
* out and stay eager). Design ported from the Entrpi/ds4 batched-serving
|
||||||
* fork's per-layer decode graph capture. The key identifies a captured
|
* fork's per-layer decode graph capture. The key identifies a captured
|
||||||
|
|||||||
+1573
-91
File diff suppressed because it is too large.
Load diff
+2
-2
@@ -3612,7 +3612,7 @@ pub(crate) fn parse_tool_calls(
|
|||||||
model: ModelChoice,
|
model: ModelChoice,
|
||||||
text: &str,
|
text: &str,
|
||||||
) -> Result<(String, Vec<ToolCall>), String> {
|
) -> Result<(String, Vec<ToolCall>), String> {
|
||||||
let (content, calls) = if model == ModelChoice::Glm52 {
|
let (content, calls) = if model.is_glm() {
|
||||||
parse_glm_calls(text)?
|
parse_glm_calls(text)?
|
||||||
} else {
|
} else {
|
||||||
crate::dsml::parse_tool_calls(text)?
|
crate::dsml::parse_tool_calls(text)?
|
||||||
@@ -3643,7 +3643,7 @@ fn system_prompt_with_tools(
|
|||||||
ralph_child: bool,
|
ralph_child: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
let schemas = tool_schemas(dev_brain, ralph_child);
|
let schemas = tool_schemas(dev_brain, ralph_child);
|
||||||
let tools = if model == ModelChoice::Glm52 {
|
let tools = if model.is_glm() {
|
||||||
format!(
|
format!(
|
||||||
"You are a coding agent running in a local workspace. Use tools for local file and system work. Avoid printing large file contents or code blocks as answers; edit files with tools, then summarize briefly.\n\n# Tools\n\n<tools>\n{schemas}\n</tools>\n\nFor a function call, output exactly: <tool_call>function-name<arg_key>key</arg_key><arg_value>value</arg_value></tool_call>\nTool calls are not allowed inside <think></think>. Pass numbers and booleans as JSON primitives, not quoted strings. When a tool fails validation or execution, use its code, field, expected, and received feedback to correct the next call. Preserve the current system configuration unless the user explicitly asks otherwise."
|
"You are a coding agent running in a local workspace. Use tools for local file and system work. Avoid printing large file contents or code blocks as answers; edit files with tools, then summarize briefly.\n\n# Tools\n\n<tools>\n{schemas}\n</tools>\n\nFor a function call, output exactly: <tool_call>function-name<arg_key>key</arg_key><arg_value>value</arg_value></tool_call>\nTool calls are not allowed inside <think></think>. Pass numbers and booleans as JSON primitives, not quoted strings. When a tool fails validation or execution, use its code, field, expected, and received feedback to correct the next call. Preserve the current system configuration unless the user explicitly asks otherwise."
|
||||||
)
|
)
|
||||||
|
|||||||
+64
@@ -35,6 +35,7 @@ use crate::settings::{
|
|||||||
ReasoningMode, RuntimePreferences, SpeculativePreferences, SsdPreferences, SteeringPreferences,
|
ReasoningMode, RuntimePreferences, SpeculativePreferences, SsdPreferences, SteeringPreferences,
|
||||||
StreamingCacheBudget,
|
StreamingCacheBudget,
|
||||||
};
|
};
|
||||||
|
use base64::Engine as _;
|
||||||
use iced::widget::{markdown, scrollable, text_editor};
|
use iced::widget::{markdown, scrollable, text_editor};
|
||||||
use iced::{Size, Subscription, Task, keyboard, mouse, window};
|
use iced::{Size, Subscription, Task, keyboard, mouse, window};
|
||||||
use rfd::AsyncFileDialog;
|
use rfd::AsyncFileDialog;
|
||||||
@@ -115,6 +116,7 @@ pub(crate) struct App {
|
|||||||
project_name_input: String,
|
project_name_input: String,
|
||||||
model_download: ModelDownload,
|
model_download: ModelDownload,
|
||||||
pub(super) composer: text_editor::Content,
|
pub(super) composer: text_editor::Content,
|
||||||
|
pub(super) pending_vision_image: Option<PendingVisionImage>,
|
||||||
pub(super) queued_inputs: VecDeque<String>,
|
pub(super) queued_inputs: VecDeque<String>,
|
||||||
pub(super) conversation: Vec<ChatMessage>,
|
pub(super) conversation: Vec<ChatMessage>,
|
||||||
/// Follow appended chat content until the user scrolls away from the tail.
|
/// Follow appended chat content until the user scrolls away from the tail.
|
||||||
@@ -191,6 +193,7 @@ struct ChatSnapshot {
|
|||||||
selected_session: Option<i32>,
|
selected_session: Option<i32>,
|
||||||
permission_mode: PermissionMode,
|
permission_mode: PermissionMode,
|
||||||
composer: text_editor::Content,
|
composer: text_editor::Content,
|
||||||
|
pending_vision_image: Option<PendingVisionImage>,
|
||||||
queued_inputs: VecDeque<String>,
|
queued_inputs: VecDeque<String>,
|
||||||
conversation: Vec<ChatMessage>,
|
conversation: Vec<ChatMessage>,
|
||||||
chat_follow_tail: bool,
|
chat_follow_tail: bool,
|
||||||
@@ -231,6 +234,11 @@ struct ChatSnapshot {
|
|||||||
skip_compaction_once: bool,
|
skip_compaction_once: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) struct PendingVisionImage {
|
||||||
|
pub(super) name: String,
|
||||||
|
marker: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub(super) struct ProjectChoice {
|
pub(super) struct ProjectChoice {
|
||||||
id: i32,
|
id: i32,
|
||||||
@@ -480,6 +488,9 @@ pub(crate) enum Message {
|
|||||||
StopModelDownload,
|
StopModelDownload,
|
||||||
DownloadProgressTick,
|
DownloadProgressTick,
|
||||||
ComposerAction(text_editor::Action),
|
ComposerAction(text_editor::Action),
|
||||||
|
ChooseVisionImage,
|
||||||
|
VisionImagePicked(Result<Option<(String, Vec<u8>)>, String>),
|
||||||
|
RemoveVisionImage,
|
||||||
TranscriptAction(usize, text_editor::Action),
|
TranscriptAction(usize, text_editor::Action),
|
||||||
ToggleReasoning(usize),
|
ToggleReasoning(usize),
|
||||||
OpenLink(markdown::Uri),
|
OpenLink(markdown::Uri),
|
||||||
@@ -641,6 +652,7 @@ impl App {
|
|||||||
project_name_input: String::new(),
|
project_name_input: String::new(),
|
||||||
model_download: ModelDownload::Idle,
|
model_download: ModelDownload::Idle,
|
||||||
composer: text_editor::Content::new(),
|
composer: text_editor::Content::new(),
|
||||||
|
pending_vision_image: None,
|
||||||
queued_inputs: VecDeque::new(),
|
queued_inputs: VecDeque::new(),
|
||||||
conversation: Vec::new(),
|
conversation: Vec::new(),
|
||||||
chat_follow_tail: true,
|
chat_follow_tail: true,
|
||||||
@@ -805,6 +817,7 @@ impl App {
|
|||||||
project_name_input: String::new(),
|
project_name_input: String::new(),
|
||||||
model_download: ModelDownload::Idle,
|
model_download: ModelDownload::Idle,
|
||||||
composer: text_editor::Content::new(),
|
composer: text_editor::Content::new(),
|
||||||
|
pending_vision_image: None,
|
||||||
queued_inputs: VecDeque::new(),
|
queued_inputs: VecDeque::new(),
|
||||||
conversation: Vec::new(),
|
conversation: Vec::new(),
|
||||||
chat_follow_tail: true,
|
chat_follow_tail: true,
|
||||||
@@ -882,6 +895,7 @@ impl App {
|
|||||||
selected_session: self.selected_session.take(),
|
selected_session: self.selected_session.take(),
|
||||||
permission_mode: self.permission_mode,
|
permission_mode: self.permission_mode,
|
||||||
composer: std::mem::take(&mut self.composer),
|
composer: std::mem::take(&mut self.composer),
|
||||||
|
pending_vision_image: self.pending_vision_image.take(),
|
||||||
queued_inputs: std::mem::take(&mut self.queued_inputs),
|
queued_inputs: std::mem::take(&mut self.queued_inputs),
|
||||||
conversation: std::mem::take(&mut self.conversation),
|
conversation: std::mem::take(&mut self.conversation),
|
||||||
chat_follow_tail: std::mem::replace(&mut self.chat_follow_tail, true),
|
chat_follow_tail: std::mem::replace(&mut self.chat_follow_tail, true),
|
||||||
@@ -932,6 +946,7 @@ impl App {
|
|||||||
self.selected_session = snapshot.selected_session;
|
self.selected_session = snapshot.selected_session;
|
||||||
self.permission_mode = snapshot.permission_mode;
|
self.permission_mode = snapshot.permission_mode;
|
||||||
self.composer = snapshot.composer;
|
self.composer = snapshot.composer;
|
||||||
|
self.pending_vision_image = snapshot.pending_vision_image;
|
||||||
self.queued_inputs = snapshot.queued_inputs;
|
self.queued_inputs = snapshot.queued_inputs;
|
||||||
self.conversation = snapshot.conversation;
|
self.conversation = snapshot.conversation;
|
||||||
self.chat_follow_tail = snapshot.chat_follow_tail;
|
self.chat_follow_tail = snapshot.chat_follow_tail;
|
||||||
@@ -1290,6 +1305,55 @@ impl App {
|
|||||||
}
|
}
|
||||||
Message::MetricsTick => self.sample_metrics(),
|
Message::MetricsTick => self.sample_metrics(),
|
||||||
Message::ComposerAction(action) => self.composer.perform(action),
|
Message::ComposerAction(action) => self.composer.perform(action),
|
||||||
|
Message::ChooseVisionImage => {
|
||||||
|
if self.config.model != ModelChoice::Glm53Flash || self.generating {
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
return Task::perform(
|
||||||
|
async {
|
||||||
|
let file = AsyncFileDialog::new()
|
||||||
|
.set_title("Attach an image")
|
||||||
|
.add_filter("Image", &["png", "jpg", "jpeg"])
|
||||||
|
.pick_file()
|
||||||
|
.await;
|
||||||
|
let Some(file) = file else {
|
||||||
|
return Ok::<_, String>(None);
|
||||||
|
};
|
||||||
|
let name = file.file_name();
|
||||||
|
let bytes = file.read().await;
|
||||||
|
Ok::<_, String>(Some((name, bytes)))
|
||||||
|
},
|
||||||
|
Message::VisionImagePicked,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Message::VisionImagePicked(result) => match result {
|
||||||
|
Ok(Some((name, bytes))) => {
|
||||||
|
if bytes.is_empty() || bytes.len() > 64 * 1024 * 1024 {
|
||||||
|
self.error = Some("Image must be between 1 byte and 64 MiB.".into());
|
||||||
|
} else {
|
||||||
|
let mime = match image::guess_format(&bytes) {
|
||||||
|
Ok(image::ImageFormat::Png) => Some("image/png"),
|
||||||
|
Ok(image::ImageFormat::Jpeg) => Some("image/jpeg"),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(mime) = mime {
|
||||||
|
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
|
||||||
|
self.pending_vision_image = Some(PendingVisionImage {
|
||||||
|
name,
|
||||||
|
marker: crate::engine::vision_data_marker(&format!(
|
||||||
|
"data:{mime};base64,{encoded}"
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
self.error = None;
|
||||||
|
} else {
|
||||||
|
self.error = Some("Only PNG and JPEG images are supported.".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(error) => self.error = Some(error),
|
||||||
|
},
|
||||||
|
Message::RemoveVisionImage => self.pending_vision_image = None,
|
||||||
Message::TranscriptAction(index, action) => {
|
Message::TranscriptAction(index, action) => {
|
||||||
if !action.is_edit()
|
if !action.is_edit()
|
||||||
&& let Some(message) = self.conversation.get_mut(index)
|
&& let Some(message) = self.conversation.get_mut(index)
|
||||||
|
|||||||
+43
-6
@@ -517,7 +517,10 @@ impl App {
|
|||||||
if self.selected_project.is_none() {
|
if self.selected_project.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let prompt = self.composer.text().trim().to_owned();
|
let mut prompt = self.composer.text().trim().to_owned();
|
||||||
|
if prompt.is_empty() && self.pending_vision_image.is_some() {
|
||||||
|
prompt = "Describe this image.".into();
|
||||||
|
}
|
||||||
if prompt.is_empty() {
|
if prompt.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -659,13 +662,20 @@ impl App {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let assistant_reasoning = effective.turn.reasoning_mode != ReasoningMode::Direct;
|
let assistant_reasoning = effective.turn.reasoning_mode != ReasoningMode::Direct;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
let (visible_prompt, base_model_prompt) = vision_prompts(
|
||||||
|
&prompt,
|
||||||
|
self.pending_vision_image
|
||||||
|
.as_ref()
|
||||||
|
.map(|image| (image.name.as_str(), image.marker.as_str())),
|
||||||
|
);
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
let model_prompt = if self.config.a2ui_enabled {
|
let model_prompt = if self.config.a2ui_enabled {
|
||||||
format!(
|
format!(
|
||||||
"{prompt}\n\nA2UI client metadata:\n{}",
|
"{base_model_prompt}\n\nA2UI client metadata:\n{}",
|
||||||
self.a2ui.client_metadata()
|
self.a2ui.client_metadata()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
prompt.clone()
|
base_model_prompt
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let mut injected_system = self.workspace_instruction_messages(&[], opening_turn);
|
let mut injected_system = self.workspace_instruction_messages(&[], opening_turn);
|
||||||
@@ -750,8 +760,8 @@ impl App {
|
|||||||
};
|
};
|
||||||
let mut saved = match database.start_chat_turn(
|
let mut saved = match database.start_chat_turn(
|
||||||
session_id,
|
session_id,
|
||||||
&prompt,
|
&visible_prompt,
|
||||||
(model_prompt != prompt).then_some(model_prompt.as_str()),
|
(model_prompt != visible_prompt).then_some(model_prompt.as_str()),
|
||||||
&injected_system,
|
&injected_system,
|
||||||
assistant_reasoning,
|
assistant_reasoning,
|
||||||
) {
|
) {
|
||||||
@@ -811,6 +821,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
assistant.reasoning_open = assistant_reasoning;
|
assistant.reasoning_open = assistant_reasoning;
|
||||||
self.composer = text_editor::Content::new();
|
self.composer = text_editor::Content::new();
|
||||||
|
self.pending_vision_image = None;
|
||||||
self.conversation.push(user);
|
self.conversation.push(user);
|
||||||
self.conversation.push(assistant);
|
self.conversation.push(assistant);
|
||||||
self.generating = true;
|
self.generating = true;
|
||||||
@@ -2459,6 +2470,18 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vision_prompts(prompt: &str, image: Option<(&str, &str)>) -> (String, String) {
|
||||||
|
image.map_or_else(
|
||||||
|
|| (prompt.to_owned(), prompt.to_owned()),
|
||||||
|
|(name, marker)| {
|
||||||
|
(
|
||||||
|
format!("[Image: {name}]\n{prompt}"),
|
||||||
|
format!("{marker}\n{prompt}"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Reduces a model reply to a single sidebar-sized line, or `None` if nothing
|
/// Reduces a model reply to a single sidebar-sized line, or `None` if nothing
|
||||||
/// usable came back.
|
/// usable came back.
|
||||||
pub(super) fn session_title(reply: &str) -> Option<String> {
|
pub(super) fn session_title(reply: &str) -> Option<String> {
|
||||||
@@ -2485,7 +2508,7 @@ mod tests {
|
|||||||
ChatMessage, TOOL_PROTOCOL_CORRECTION, TurnSummary, chat_turn, compacted_context_start,
|
ChatMessage, TOOL_PROTOCOL_CORRECTION, TurnSummary, chat_turn, compacted_context_start,
|
||||||
correction_already_sent, extension_context_visibility, has_chat_after_last_compaction,
|
correction_already_sent, extension_context_visibility, has_chat_after_last_compaction,
|
||||||
has_misplaced_tool_call, is_empty_response, promote_legacy_turn_summaries, queued_prompt,
|
has_misplaced_tool_call, is_empty_response, promote_legacy_turn_summaries, queued_prompt,
|
||||||
sync_a2ui_message, title_context,
|
sync_a2ui_message, title_context, vision_prompts,
|
||||||
};
|
};
|
||||||
use crate::engine::ChatTurn;
|
use crate::engine::ChatTurn;
|
||||||
use crate::model::ModelChoice;
|
use crate::model::ModelChoice;
|
||||||
@@ -2553,6 +2576,20 @@ mod tests {
|
|||||||
assert_eq!(user.content, "visible question");
|
assert_eq!(user.content, "visible question");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attached_image_marker_is_model_only() {
|
||||||
|
let (visible, model) = vision_prompts(
|
||||||
|
"Describe this.",
|
||||||
|
Some(("diagram.png", "<|vision_start|>payload<|vision_end|>")),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(visible, "[Image: diagram.png]\nDescribe this.");
|
||||||
|
assert_eq!(
|
||||||
|
model,
|
||||||
|
"<|vision_start|>payload<|vision_end|>\nDescribe this."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn legacy_generation_rows_collapse_into_their_user_turn() {
|
fn legacy_generation_rows_collapse_into_their_user_turn() {
|
||||||
let mut user = assistant(None, "question");
|
let mut user = assistant(None, "question");
|
||||||
|
|||||||
@@ -593,6 +593,9 @@ impl App {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.config = config;
|
self.config = config;
|
||||||
|
if self.config.model != ModelChoice::Glm53Flash {
|
||||||
|
self.pending_vision_image = None;
|
||||||
|
}
|
||||||
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
|
self.context_limit = self.config.active_generation().context_tokens.max(0) as u32;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
@@ -653,9 +656,11 @@ impl App {
|
|||||||
self.preference_draft.default_reasoning_mode = mode;
|
self.preference_draft.default_reasoning_mode = mode;
|
||||||
self.preference_draft.load_generation(model, mode);
|
self.preference_draft.load_generation(model, mode);
|
||||||
self.preference_draft.load_acceleration(model);
|
self.preference_draft.load_acceleration(model);
|
||||||
if model == ModelChoice::Glm52 {
|
if model.is_glm() {
|
||||||
self.preference_draft.power_percent.clear();
|
self.preference_draft.power_percent.clear();
|
||||||
self.preference_draft.prefill_chunk.clear();
|
self.preference_draft.prefill_chunk.clear();
|
||||||
|
}
|
||||||
|
if model == ModelChoice::Glm52 {
|
||||||
self.preference_draft.directional_steering_file.clear();
|
self.preference_draft.directional_steering_file.clear();
|
||||||
self.preference_draft.directional_steering_ffn.clear();
|
self.preference_draft.directional_steering_ffn.clear();
|
||||||
self.preference_draft.directional_steering_attn.clear();
|
self.preference_draft.directional_steering_attn.clear();
|
||||||
@@ -851,7 +856,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
Message::PreferenceGlmMtpChanged(value) => {
|
Message::PreferenceGlmMtpChanged(value) => {
|
||||||
self.preference_draft.glm_mtp =
|
self.preference_draft.glm_mtp =
|
||||||
self.preference_draft.acceleration_model == ModelChoice::Glm52 && value;
|
self.preference_draft.acceleration_model.supports_glm_mtp() && value;
|
||||||
if !self.preference_draft.glm_mtp {
|
if !self.preference_draft.glm_mtp {
|
||||||
self.preference_draft.glm_mtp_timing = false;
|
self.preference_draft.glm_mtp_timing = false;
|
||||||
}
|
}
|
||||||
@@ -859,7 +864,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
Message::PreferenceGlmMtpTimingChanged(value) => {
|
Message::PreferenceGlmMtpTimingChanged(value) => {
|
||||||
self.preference_draft.glm_mtp_timing =
|
self.preference_draft.glm_mtp_timing =
|
||||||
self.preference_draft.acceleration_model == ModelChoice::Glm52 && value;
|
self.preference_draft.acceleration_model.supports_glm_mtp() && value;
|
||||||
if self.preference_draft.glm_mtp_timing {
|
if self.preference_draft.glm_mtp_timing {
|
||||||
self.preference_draft.glm_mtp = true;
|
self.preference_draft.glm_mtp = true;
|
||||||
}
|
}
|
||||||
@@ -907,7 +912,7 @@ impl App {
|
|||||||
self.preference_error = None;
|
self.preference_error = None;
|
||||||
}
|
}
|
||||||
Message::PreferenceSsdFullLayersChanged(value) => {
|
Message::PreferenceSsdFullLayersChanged(value) => {
|
||||||
if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
|
if self.preference_draft.acceleration_model.is_glm() {
|
||||||
self.preference_draft.ssd_full_layers = value;
|
self.preference_draft.ssd_full_layers = value;
|
||||||
}
|
}
|
||||||
self.preference_error = None;
|
self.preference_error = None;
|
||||||
|
|||||||
+39
-2
@@ -250,7 +250,8 @@ impl App {
|
|||||||
.padding(8)
|
.padding(8)
|
||||||
.style(stop_button_style)
|
.style(stop_button_style)
|
||||||
.on_press(Message::StopGeneration)
|
.on_press(Message::StopGeneration)
|
||||||
} else if self.composer.text().trim().is_empty() {
|
} else if self.composer.text().trim().is_empty() && self.pending_vision_image.is_none()
|
||||||
|
{
|
||||||
action_button(icon(ICON_SEND, 18)).padding(8)
|
action_button(icon(ICON_SEND, 18)).padding(8)
|
||||||
} else {
|
} else {
|
||||||
action_button(icon(ICON_SEND, 18))
|
action_button(icon(ICON_SEND, 18))
|
||||||
@@ -263,6 +264,20 @@ impl App {
|
|||||||
self.context_used.min(self.context_limit) as f32 / self.context_limit as f32
|
self.context_used.min(self.context_limit) as f32 / self.context_limit as f32
|
||||||
};
|
};
|
||||||
let mut composer_content = column![composer].spacing(6);
|
let mut composer_content = column![composer].spacing(6);
|
||||||
|
if let Some(image) = &self.pending_vision_image {
|
||||||
|
composer_content = composer_content.push(
|
||||||
|
row![
|
||||||
|
icon(ICON_PAPERCLIP, 14),
|
||||||
|
text(format!("Image · {}", image.name)).size(12),
|
||||||
|
button(text("Remove").size(11))
|
||||||
|
.padding(0)
|
||||||
|
.style(button::text)
|
||||||
|
.on_press(Message::RemoveVisionImage),
|
||||||
|
]
|
||||||
|
.spacing(7)
|
||||||
|
.align_y(Alignment::Center),
|
||||||
|
);
|
||||||
|
}
|
||||||
for queued in &self.queued_inputs {
|
for queued in &self.queued_inputs {
|
||||||
let mut queued = queued.replace('\n', " ");
|
let mut queued = queued.replace('\n', " ");
|
||||||
if queued.chars().count() > 120 {
|
if queued.chars().count() > 120 {
|
||||||
@@ -322,10 +337,32 @@ impl App {
|
|||||||
.padding([2, 6])
|
.padding([2, 6])
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
let vision_ready = self.config.model == ModelChoice::Glm53Flash
|
||||||
|
&& model::engine_artifacts(ModelChoice::Glm53Flash, false, &models_path())
|
||||||
|
.vision
|
||||||
|
.is_some();
|
||||||
|
let attach = if vision_ready && !self.generating && self.pending_vision_image.is_none()
|
||||||
|
{
|
||||||
|
action_button(icon(ICON_PAPERCLIP, 19))
|
||||||
|
.padding(4)
|
||||||
|
.on_press(Message::ChooseVisionImage)
|
||||||
|
} else {
|
||||||
|
action_button(icon(ICON_PAPERCLIP, 19)).padding(4)
|
||||||
|
};
|
||||||
composer_content =
|
composer_content =
|
||||||
composer_content.push(
|
composer_content.push(
|
||||||
row![
|
row![
|
||||||
icon(ICON_PAPERCLIP, 19),
|
tooltip(
|
||||||
|
attach,
|
||||||
|
container(text(if vision_ready {
|
||||||
|
"Attach PNG or JPEG"
|
||||||
|
} else {
|
||||||
|
"GLM 5.3 Flash vision sidecar is not ready"
|
||||||
|
}))
|
||||||
|
.padding(8)
|
||||||
|
.style(preference_group_style),
|
||||||
|
tooltip::Position::Top,
|
||||||
|
),
|
||||||
tooltip(
|
tooltip(
|
||||||
context_pie(context_fraction, 19),
|
context_pie(context_fraction, 19),
|
||||||
container(
|
container(
|
||||||
|
|||||||
+24
-12
@@ -14,12 +14,16 @@ impl App {
|
|||||||
.on_toggle_maybe(dspark_toggle),
|
.on_toggle_maybe(dspark_toggle),
|
||||||
"Speculative decoding with the managed DSpark draft artifact: a small model proposes tokens that the main model verifies in one pass. Usually a large speedup; the target model may also stream routed experts from SSD.",
|
"Speculative decoding with the managed DSpark draft artifact: a small model proposes tokens that the main model verifies in one pass. Usually a large speedup; the target model may also stream routed experts from SSD.",
|
||||||
);
|
);
|
||||||
let glm_mtp_toggle: Option<fn(bool) -> Message> =
|
let glm_mtp_toggle: Option<fn(bool) -> Message> = self
|
||||||
(self.preference_draft.acceleration_model == ModelChoice::Glm52)
|
.preference_draft
|
||||||
.then_some(Message::PreferenceGlmMtpChanged);
|
.acceleration_model
|
||||||
let glm_mtp_timing_toggle: Option<fn(bool) -> Message> =
|
.supports_glm_mtp()
|
||||||
(self.preference_draft.acceleration_model == ModelChoice::Glm52)
|
.then_some(Message::PreferenceGlmMtpChanged);
|
||||||
.then_some(Message::PreferenceGlmMtpTimingChanged);
|
let glm_mtp_timing_toggle: Option<fn(bool) -> Message> = self
|
||||||
|
.preference_draft
|
||||||
|
.acceleration_model
|
||||||
|
.supports_glm_mtp()
|
||||||
|
.then_some(Message::PreferenceGlmMtpTimingChanged);
|
||||||
let dspark_strict_toggle: Option<fn(bool) -> Message> = self
|
let dspark_strict_toggle: Option<fn(bool) -> Message> = self
|
||||||
.preference_draft
|
.preference_draft
|
||||||
.acceleration_model
|
.acceleration_model
|
||||||
@@ -71,14 +75,16 @@ impl App {
|
|||||||
"0.8 (DeepSeek V4 Flash default)",
|
"0.8 (DeepSeek V4 Flash default)",
|
||||||
&self.preference_draft.dspark_confidence_threshold,
|
&self.preference_draft.dspark_confidence_threshold,
|
||||||
);
|
);
|
||||||
if self.preference_draft.model != ModelChoice::Glm52 {
|
if !self.preference_draft.model.is_glm() {
|
||||||
power = power.on_input(Message::PreferencePowerChanged);
|
power = power.on_input(Message::PreferencePowerChanged);
|
||||||
prefill = prefill.on_input(Message::PreferencePrefillChunkChanged);
|
prefill = prefill.on_input(Message::PreferencePrefillChunkChanged);
|
||||||
|
}
|
||||||
|
if self.preference_draft.model != ModelChoice::Glm52 {
|
||||||
steering_file = steering_file.on_input(Message::PreferenceSteeringFileChanged);
|
steering_file = steering_file.on_input(Message::PreferenceSteeringFileChanged);
|
||||||
steering_ffn = steering_ffn.on_input(Message::PreferenceSteeringFfnChanged);
|
steering_ffn = steering_ffn.on_input(Message::PreferenceSteeringFfnChanged);
|
||||||
steering_attn = steering_attn.on_input(Message::PreferenceSteeringAttnChanged);
|
steering_attn = steering_attn.on_input(Message::PreferenceSteeringAttnChanged);
|
||||||
}
|
}
|
||||||
if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
|
if self.preference_draft.acceleration_model.is_glm() {
|
||||||
ssd_full_layers = ssd_full_layers.on_input(Message::PreferenceSsdFullLayersChanged);
|
ssd_full_layers = ssd_full_layers.on_input(Message::PreferenceSsdFullLayersChanged);
|
||||||
}
|
}
|
||||||
if self.preference_draft.acceleration_model.supports_dspark() {
|
if self.preference_draft.acceleration_model.supports_dspark() {
|
||||||
@@ -467,6 +473,12 @@ impl App {
|
|||||||
text_input("32768", &self.preference_draft.context_tokens)
|
text_input("32768", &self.preference_draft.context_tokens)
|
||||||
.on_input(Message::PreferenceContextChanged),
|
.on_input(Message::PreferenceContextChanged),
|
||||||
),
|
),
|
||||||
|
text(if self.preference_draft.generation_model == ModelChoice::Glm53Flash {
|
||||||
|
"GLM 5.3 Flash: 32768 is the recommended default on this 128 GB machine; 50000 is the validated extended-session target."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
})
|
||||||
|
.size(12),
|
||||||
preference_input_row(
|
preference_input_row(
|
||||||
"Maximum generated tokens",
|
"Maximum generated tokens",
|
||||||
"Hard stop for a single reply, counted from the first generated token. It bounds runaway answers and reasoning loops; it does not reserve memory.",
|
"Hard stop for a single reply, counted from the first generated token. It bounds runaway answers and reasoning loops; it does not reserve memory.",
|
||||||
@@ -550,8 +562,8 @@ impl App {
|
|||||||
.on_toggle(Message::PreferenceWarmWeightsChanged),
|
.on_toggle(Message::PreferenceWarmWeightsChanged),
|
||||||
"Reads every mapped weight page once at load, so the first reply is not interrupted by page faults from disk. Loading takes longer and memory pressure rises immediately.",
|
"Reads every mapped weight page once at load, so the first reply is not interrupted by page faults from disk. Loading takes longer and memory pressure rises immediately.",
|
||||||
),
|
),
|
||||||
text(if self.preference_draft.model == ModelChoice::Glm52 {
|
text(if self.preference_draft.model.is_glm() {
|
||||||
"GLM 5.2 uses full GPU power and selects prefill chunks automatically."
|
"GLM uses full GPU power and selects prefill chunks automatically."
|
||||||
} else {
|
} else {
|
||||||
"Blank numeric values preserve DS4's automatic engine behavior."
|
"Blank numeric values preserve DS4's automatic engine behavior."
|
||||||
})
|
})
|
||||||
@@ -595,7 +607,7 @@ impl App {
|
|||||||
toggle(self.preference_draft.glm_mtp)
|
toggle(self.preference_draft.glm_mtp)
|
||||||
.label("Enable integrated GLM MTP")
|
.label("Enable integrated GLM MTP")
|
||||||
.on_toggle_maybe(glm_mtp_toggle),
|
.on_toggle_maybe(glm_mtp_toggle),
|
||||||
"Uses the prediction head built into GLM 5.2 for speculative decoding, so no separate draft model is loaded. Available for GLM 5.2 only.",
|
"Uses the prediction head built into GLM for speculative decoding, so no separate draft model is loaded.",
|
||||||
),
|
),
|
||||||
hint(
|
hint(
|
||||||
toggle(self.preference_draft.glm_mtp_timing)
|
toggle(self.preference_draft.glm_mtp_timing)
|
||||||
@@ -623,7 +635,7 @@ impl App {
|
|||||||
),
|
),
|
||||||
text(if self.preference_draft.acceleration_model.supports_dspark() {
|
text(if self.preference_draft.acceleration_model.supports_dspark() {
|
||||||
"DeepSeek V4 Flash 0731 uses its managed DSpark support artifact."
|
"DeepSeek V4 Flash 0731 uses its managed DSpark support artifact."
|
||||||
} else if self.preference_draft.acceleration_model == ModelChoice::Glm52 {
|
} else if self.preference_draft.acceleration_model.supports_glm_mtp() {
|
||||||
"GLM MTP is integrated; DSpark is unavailable for this model."
|
"GLM MTP is integrated; DSpark is unavailable for this model."
|
||||||
} else {
|
} else {
|
||||||
"No speculative-decoding support is available for this model."
|
"No speculative-decoding support is available for this model."
|
||||||
|
|||||||
+202
-28
@@ -12,8 +12,10 @@ use crate::model::{ModelChoice, validate_engine_artifacts};
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::settings::TurnSettings;
|
use crate::settings::TurnSettings;
|
||||||
use crate::settings::{EngineSettings, ReasoningMode};
|
use crate::settings::{EngineSettings, ReasoningMode};
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use base64::Engine as _;
|
||||||
use gguf::{
|
use gguf::{
|
||||||
F16, F32, Gguf, I32, IQ2_XXS, MXFP4, Q2_K, Q4_0, Q4_K, Q5_K, Q6_K, Q8_0, Tensor, Value,
|
BF16, F16, F32, Gguf, I32, IQ2_XXS, MXFP4, Q2_K, Q4_0, Q4_K, Q5_K, Q6_K, Q8_0, Tensor, Value,
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use kvstore::{KvStore, StoreReason};
|
use kvstore::{KvStore, StoreReason};
|
||||||
@@ -32,6 +34,19 @@ use std::time::Instant;
|
|||||||
use tokenizer::Tokenizer;
|
use tokenizer::Tokenizer;
|
||||||
use validation::{SupportKind, validate_main, validate_support};
|
use validation::{SupportKind, validate_main, validate_support};
|
||||||
|
|
||||||
|
const VISION_DATA_START: &str = "<|ds4server_image_data|>";
|
||||||
|
const VISION_DATA_END: &str = "<|/ds4server_image_data|>";
|
||||||
|
const VISION_TOKEN_START: &str = "\u{fdd0}ds4-image:";
|
||||||
|
const VISION_TOKEN_END: &str = "\u{fdd1}";
|
||||||
|
const VISION_IMAGE_TOKEN: i32 = 154_854;
|
||||||
|
const VISION_START_TOKEN: i32 = 154_830;
|
||||||
|
const VISION_END_TOKEN: i32 = 154_831;
|
||||||
|
type VisionOverlays = Vec<(u32, metal::VisionEmbedding)>;
|
||||||
|
|
||||||
|
pub(crate) fn vision_data_marker(uri: &str) -> String {
|
||||||
|
format!("{VISION_DATA_START}{uri}{VISION_DATA_END}")
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
fn mmap(
|
fn mmap(
|
||||||
@@ -47,14 +62,14 @@ unsafe extern "C" {
|
|||||||
fn munmap(address: *mut std::ffi::c_void, length: usize) -> i32;
|
fn munmap(address: *mut std::ffi::c_void, length: usize) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use validation::validate_model_artifact;
|
pub(crate) use validation::{validate_model_artifact, validate_vision_artifact};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub(crate) use kvstore::sweep_unreachable as sweep_transient_cache;
|
pub(crate) use kvstore::sweep_unreachable as sweep_transient_cache;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub(crate) use metal::configure_sources as configure_metal_sources;
|
pub(crate) use metal::configure_sources as configure_metal_sources;
|
||||||
|
|
||||||
const DENSE: &[u32] = &[Q8_0, Q4_K, Q4_0];
|
const DENSE: &[u32] = &[BF16, Q8_0, Q4_K, Q4_0];
|
||||||
const ROUTED: &[u32] = &[Q8_0, IQ2_XXS, Q2_K, Q4_K, Q5_K, Q6_K, MXFP4];
|
const ROUTED: &[u32] = &[Q8_0, IQ2_XXS, Q2_K, Q4_K, Q5_K, Q6_K, MXFP4];
|
||||||
const PLAIN: &[u32] = &[F16, F32];
|
const PLAIN: &[u32] = &[F16, F32];
|
||||||
const DSPARK_DENSE: &[u32] = &[F16, F32, Q8_0];
|
const DSPARK_DENSE: &[u32] = &[F16, F32, Q8_0];
|
||||||
@@ -211,9 +226,53 @@ const GLM: Shape = Shape {
|
|||||||
original_context: 1_048_576,
|
original_context: 1_048_576,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GLM53_FLASH: Shape = Shape {
|
||||||
|
model: ModelChoice::Glm53Flash,
|
||||||
|
family: ModelFamily::Glm,
|
||||||
|
layers: 46,
|
||||||
|
embd: 4096,
|
||||||
|
vocab: 154_880,
|
||||||
|
heads: 64,
|
||||||
|
head_kv: 1,
|
||||||
|
head_dim: 512,
|
||||||
|
value_dim: 256,
|
||||||
|
rot: 0,
|
||||||
|
out_groups: 0,
|
||||||
|
lora_q: 1536,
|
||||||
|
lora_o: 0,
|
||||||
|
experts: 288,
|
||||||
|
experts_used: 8,
|
||||||
|
expert_shared: 1,
|
||||||
|
ff_expert: 2048,
|
||||||
|
ff_dense: 12_288,
|
||||||
|
hash_layers: 0,
|
||||||
|
sliding_window: 0,
|
||||||
|
indexer_heads: 32,
|
||||||
|
indexer_head_dim: 128,
|
||||||
|
indexer_top_k: 2048,
|
||||||
|
hc: 4,
|
||||||
|
hc_sinkhorn: 20,
|
||||||
|
nextn: 1,
|
||||||
|
leading_dense: 3,
|
||||||
|
kv_lora: 512,
|
||||||
|
key_mla: 256,
|
||||||
|
value_mla: 256,
|
||||||
|
rms_epsilon: 1.0e-5,
|
||||||
|
hc_epsilon: 1.0e-6,
|
||||||
|
expert_weight_scale: 2.5,
|
||||||
|
swiglu_clamp: 10.0,
|
||||||
|
rope_base: 0.0,
|
||||||
|
rope_scale: 0.0,
|
||||||
|
rope_beta_fast: 0.0,
|
||||||
|
rope_beta_slow: 0.0,
|
||||||
|
compress_rope_base: 0.0,
|
||||||
|
original_context: 1_048_576,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) struct Model {
|
pub(crate) struct Model {
|
||||||
main: Gguf,
|
main: Gguf,
|
||||||
support: Option<Gguf>,
|
support: Option<Gguf>,
|
||||||
|
vision: Option<Gguf>,
|
||||||
support_kind: Option<SupportKind>,
|
support_kind: Option<SupportKind>,
|
||||||
shape: Shape,
|
shape: Shape,
|
||||||
tokenizer: Tokenizer,
|
tokenizer: Tokenizer,
|
||||||
@@ -226,6 +285,7 @@ pub(crate) struct ModelSummary {
|
|||||||
pub(crate) tensor_count: usize,
|
pub(crate) tensor_count: usize,
|
||||||
pub(crate) vocabulary_size: usize,
|
pub(crate) vocabulary_size: usize,
|
||||||
pub(crate) support_loaded: bool,
|
pub(crate) support_loaded: bool,
|
||||||
|
pub(crate) vision_loaded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
@@ -249,6 +309,14 @@ impl Model {
|
|||||||
model.support = Some(support);
|
model.support = Some(support);
|
||||||
model.support_kind = Some(kind);
|
model.support_kind = Some(kind);
|
||||||
}
|
}
|
||||||
|
if let Some(path) = &settings.artifacts.vision {
|
||||||
|
validate_vision_artifact(path)?;
|
||||||
|
let vision = Gguf::open(path)?;
|
||||||
|
if settings.execution.warm_weights {
|
||||||
|
vision.warm()?;
|
||||||
|
}
|
||||||
|
model.vision = Some(vision);
|
||||||
|
}
|
||||||
Ok(model)
|
Ok(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +334,7 @@ impl Model {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
main,
|
main,
|
||||||
support: None,
|
support: None,
|
||||||
|
vision: None,
|
||||||
support_kind: None,
|
support_kind: None,
|
||||||
shape,
|
shape,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
@@ -275,14 +344,21 @@ impl Model {
|
|||||||
pub(crate) fn summary(&self) -> ModelSummary {
|
pub(crate) fn summary(&self) -> ModelSummary {
|
||||||
ModelSummary {
|
ModelSummary {
|
||||||
model: self.shape.model,
|
model: self.shape.model,
|
||||||
mapped_bytes: self.main.len() + self.support.as_ref().map_or(0, Gguf::len),
|
mapped_bytes: self.main.len()
|
||||||
|
+ self.support.as_ref().map_or(0, Gguf::len)
|
||||||
|
+ self.vision.as_ref().map_or(0, Gguf::len),
|
||||||
tensor_count: self.main.tensors.len()
|
tensor_count: self.main.tensors.len()
|
||||||
+ self
|
+ self
|
||||||
.support
|
.support
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(0, |support| support.tensors.len()),
|
.map_or(0, |support| support.tensors.len())
|
||||||
|
+ self
|
||||||
|
.vision
|
||||||
|
.as_ref()
|
||||||
|
.map_or(0, |vision| vision.tensors.len()),
|
||||||
vocabulary_size: self.tokenizer.vocab_size(),
|
vocabulary_size: self.tokenizer.vocab_size(),
|
||||||
support_loaded: self.support.is_some(),
|
support_loaded: self.support.is_some(),
|
||||||
|
vision_loaded: self.vision.is_some(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +369,9 @@ impl Model {
|
|||||||
if let Some(support) = &self.support {
|
if let Some(support) = &self.support {
|
||||||
hash.update(support.checkpoint_identity());
|
hash.update(support.checkpoint_identity());
|
||||||
}
|
}
|
||||||
|
if let Some(vision) = &self.vision {
|
||||||
|
hash.update(vision.checkpoint_identity());
|
||||||
|
}
|
||||||
hash.finalize().into()
|
hash.finalize().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,7 +811,7 @@ impl Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
phase("Updating system prompt cache…");
|
phase("Updating system prompt cache…");
|
||||||
let completed = self.prefill_suffix(&tokens, reused, cancelled, progress)?;
|
let completed = self.prefill_suffix(&tokens, reused, false, cancelled, progress)?;
|
||||||
if completed != tokens.len() - reused {
|
if completed != tokens.len() - reused {
|
||||||
return Err("generation cancelled while updating the system prompt cache".into());
|
return Err("generation cancelled while updating the system prompt cache".into());
|
||||||
}
|
}
|
||||||
@@ -1055,11 +1134,12 @@ impl Generator {
|
|||||||
&mut self,
|
&mut self,
|
||||||
tokens: &[i32],
|
tokens: &[i32],
|
||||||
reused: usize,
|
reused: usize,
|
||||||
|
has_vision: bool,
|
||||||
cancelled: &AtomicBool,
|
cancelled: &AtomicBool,
|
||||||
progress: &mut impl FnMut(u32, u32, Option<f32>),
|
progress: &mut impl FnMut(u32, u32, Option<f32>),
|
||||||
) -> Result<usize, String> {
|
) -> Result<usize, String> {
|
||||||
let suffix = &tokens[reused..];
|
let suffix = &tokens[reused..];
|
||||||
if (reused == 0 && tokens.len() > 1) || suffix.len() >= 4 {
|
if has_vision || (reused == 0 && tokens.len() > 1) || suffix.len() >= 4 {
|
||||||
let context = self.executor.context();
|
let context = self.executor.context();
|
||||||
self.executor.prefill(suffix, |used| {
|
self.executor.prefill(suffix, |used| {
|
||||||
progress(used, context, None);
|
progress(used, context, None);
|
||||||
@@ -1079,6 +1159,87 @@ impl Generator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_multimodal_conversation(
|
||||||
|
&mut self,
|
||||||
|
system: &str,
|
||||||
|
messages: &[ChatTurn],
|
||||||
|
reasoning: ReasoningMode,
|
||||||
|
) -> Result<(Vec<i32>, VisionOverlays), String> {
|
||||||
|
let mut rendered = messages.to_vec();
|
||||||
|
let mut embeddings = Vec::new();
|
||||||
|
let mut total_images = 0_usize;
|
||||||
|
let mut total_bytes = 0_usize;
|
||||||
|
for message in &mut rendered {
|
||||||
|
if !message.content.contains(VISION_DATA_START) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !message.user && !message.tool {
|
||||||
|
return Err("vision input is allowed only in user or tool messages".into());
|
||||||
|
}
|
||||||
|
let mut content = String::with_capacity(message.content.len());
|
||||||
|
let mut rest = message.content.as_str();
|
||||||
|
while let Some(start) = rest.find(VISION_DATA_START) {
|
||||||
|
content.push_str(&rest[..start]);
|
||||||
|
let encoded = &rest[start + VISION_DATA_START.len()..];
|
||||||
|
let end = encoded
|
||||||
|
.find(VISION_DATA_END)
|
||||||
|
.ok_or("unterminated image input")?;
|
||||||
|
let uri = &encoded[..end];
|
||||||
|
let payload = uri
|
||||||
|
.strip_prefix("data:image/png;base64,")
|
||||||
|
.or_else(|| uri.strip_prefix("data:image/jpeg;base64,"))
|
||||||
|
.ok_or("image input must be an inline PNG or JPEG data URI")?;
|
||||||
|
total_images += 1;
|
||||||
|
if total_images > 16 {
|
||||||
|
return Err("a request may contain at most 16 images".into());
|
||||||
|
}
|
||||||
|
let bytes = base64::engine::general_purpose::STANDARD
|
||||||
|
.decode(payload)
|
||||||
|
.map_err(|_| "image data URI contains invalid base64")?;
|
||||||
|
total_bytes = total_bytes
|
||||||
|
.checked_add(bytes.len())
|
||||||
|
.ok_or("image input size overflow")?;
|
||||||
|
if total_bytes > 64 * 1024 * 1024 {
|
||||||
|
return Err("image inputs exceed the 64 MiB request limit".into());
|
||||||
|
}
|
||||||
|
let embedding = self.executor.encode_vision(&bytes)?;
|
||||||
|
content.push_str(VISION_TOKEN_START);
|
||||||
|
content.push_str(&embedding.tokens.to_string());
|
||||||
|
content.push_str(VISION_TOKEN_END);
|
||||||
|
embeddings.push(embedding);
|
||||||
|
rest = &encoded[end + VISION_DATA_END.len()..];
|
||||||
|
}
|
||||||
|
content.push_str(rest);
|
||||||
|
message.content = content;
|
||||||
|
}
|
||||||
|
let tokens = self
|
||||||
|
.executor
|
||||||
|
.model()
|
||||||
|
.render_conversation(system, &rendered, reasoning);
|
||||||
|
let mut overlays = Vec::with_capacity(embeddings.len());
|
||||||
|
let mut cursor = 0_usize;
|
||||||
|
for embedding in embeddings {
|
||||||
|
let count = embedding.tokens as usize;
|
||||||
|
let relative = tokens[cursor..]
|
||||||
|
.windows(count + 2)
|
||||||
|
.position(|window| {
|
||||||
|
window[0] == VISION_START_TOKEN
|
||||||
|
&& window[count + 1] == VISION_END_TOKEN
|
||||||
|
&& window[1..count + 1]
|
||||||
|
.iter()
|
||||||
|
.all(|token| *token == VISION_IMAGE_TOKEN)
|
||||||
|
})
|
||||||
|
.ok_or("rendered prompt lost an image placeholder")?;
|
||||||
|
let start = cursor + relative + 1;
|
||||||
|
overlays.push((
|
||||||
|
u32::try_from(start).map_err(|_| "image prompt position overflow")?,
|
||||||
|
embedding,
|
||||||
|
));
|
||||||
|
cursor = start + count + 1;
|
||||||
|
}
|
||||||
|
Ok((tokens, overlays))
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_inner(
|
fn generate_inner(
|
||||||
&mut self,
|
&mut self,
|
||||||
messages: &[ChatTurn],
|
messages: &[ChatTurn],
|
||||||
@@ -1087,29 +1248,41 @@ impl Generator {
|
|||||||
emit: &mut impl FnMut(bool, String),
|
emit: &mut impl FnMut(bool, String),
|
||||||
progress: &mut impl FnMut(u32, u32, Option<f32>),
|
progress: &mut impl FnMut(u32, u32, Option<f32>),
|
||||||
) -> Result<(GenerationOutput, bool), String> {
|
) -> Result<(GenerationOutput, bool), String> {
|
||||||
let tokens = match messages.split_last() {
|
let has_vision = messages
|
||||||
Some((latest, history))
|
.iter()
|
||||||
if latest.user
|
.any(|message| message.content.contains(VISION_DATA_START));
|
||||||
&& self.executor.checkpoint_tag()
|
let (tokens, overlays) = if has_vision {
|
||||||
== conversation_tag(
|
self.render_multimodal_conversation(
|
||||||
&settings.system_prompt,
|
|
||||||
settings.reasoning_mode,
|
|
||||||
history,
|
|
||||||
) =>
|
|
||||||
{
|
|
||||||
let mut tokens = self.executor.tokens().to_vec();
|
|
||||||
tokens.extend(self.executor.model().render_continuation(
|
|
||||||
&latest.content,
|
|
||||||
settings.reasoning_mode,
|
|
||||||
latest.skip_previous_eos,
|
|
||||||
));
|
|
||||||
tokens
|
|
||||||
}
|
|
||||||
_ => self.executor.model().render_conversation(
|
|
||||||
&settings.system_prompt,
|
&settings.system_prompt,
|
||||||
messages,
|
messages,
|
||||||
settings.reasoning_mode,
|
settings.reasoning_mode,
|
||||||
),
|
)?
|
||||||
|
} else {
|
||||||
|
let tokens = match messages.split_last() {
|
||||||
|
Some((latest, history))
|
||||||
|
if latest.user
|
||||||
|
&& self.executor.checkpoint_tag()
|
||||||
|
== conversation_tag(
|
||||||
|
&settings.system_prompt,
|
||||||
|
settings.reasoning_mode,
|
||||||
|
history,
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
let mut tokens = self.executor.tokens().to_vec();
|
||||||
|
tokens.extend(self.executor.model().render_continuation(
|
||||||
|
&latest.content,
|
||||||
|
settings.reasoning_mode,
|
||||||
|
latest.skip_previous_eos,
|
||||||
|
));
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
_ => self.executor.model().render_conversation(
|
||||||
|
&settings.system_prompt,
|
||||||
|
messages,
|
||||||
|
settings.reasoning_mode,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
(tokens, Vec::new())
|
||||||
};
|
};
|
||||||
if tokens.is_empty() {
|
if tokens.is_empty() {
|
||||||
return Err("the rendered prompt is empty".into());
|
return Err("the rendered prompt is empty".into());
|
||||||
@@ -1122,6 +1295,7 @@ impl Generator {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
let reused = self.executor.align_prompt(&tokens)?;
|
let reused = self.executor.align_prompt(&tokens)?;
|
||||||
|
self.executor.set_vision_overlays(overlays)?;
|
||||||
self.metrics.kv_prefix_reused(reused);
|
self.metrics.kv_prefix_reused(reused);
|
||||||
progress(self.executor.position(), self.executor.context(), None);
|
progress(self.executor.position(), self.executor.context(), None);
|
||||||
let mut rng = Rng::new(settings.seed.unwrap_or(0x4453_3453_4552_5645));
|
let mut rng = Rng::new(settings.seed.unwrap_or(0x4453_3453_4552_5645));
|
||||||
@@ -1140,7 +1314,7 @@ impl Generator {
|
|||||||
let mut pending_utf8 = Vec::new();
|
let mut pending_utf8 = Vec::new();
|
||||||
let prompt_tokens = tokens.len();
|
let prompt_tokens = tokens.len();
|
||||||
let suffix = &tokens[reused..];
|
let suffix = &tokens[reused..];
|
||||||
let completed = self.prefill_suffix(&tokens, reused, cancelled, progress)?;
|
let completed = self.prefill_suffix(&tokens, reused, has_vision, cancelled, progress)?;
|
||||||
self.publish_execution_stats();
|
self.publish_execution_stats();
|
||||||
if completed != suffix.len() {
|
if completed != suffix.len() {
|
||||||
return Ok((
|
return Ok((
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub(super) const Q5_K: u32 = 13;
|
|||||||
pub(super) const Q6_K: u32 = 14;
|
pub(super) const Q6_K: u32 = 14;
|
||||||
pub(super) const IQ2_XXS: u32 = 16;
|
pub(super) const IQ2_XXS: u32 = 16;
|
||||||
pub(super) const I32: u32 = 26;
|
pub(super) const I32: u32 = 26;
|
||||||
|
pub(super) const BF16: u32 = 30;
|
||||||
pub(super) const MXFP4: u32 = 39;
|
pub(super) const MXFP4: u32 = 39;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -488,6 +489,7 @@ impl<'a> Cursor<'a> {
|
|||||||
| "deepseek4.dspark.target_layer_ids"
|
| "deepseek4.dspark.target_layer_ids"
|
||||||
| "deepseek4.dspark_target_layer_ids"
|
| "deepseek4.dspark_target_layer_ids"
|
||||||
| "dspark.target_layer_ids"
|
| "dspark.target_layer_ids"
|
||||||
|
| "glm5-next.layer_types"
|
||||||
);
|
);
|
||||||
if !keep {
|
if !keep {
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
|
|||||||
+32
-5
@@ -3,12 +3,14 @@ mod glm;
|
|||||||
mod gpu;
|
mod gpu;
|
||||||
mod hotlist;
|
mod hotlist;
|
||||||
mod profile;
|
mod profile;
|
||||||
|
mod vision;
|
||||||
|
pub(super) use vision::VisionEmbedding;
|
||||||
|
|
||||||
use glm::GlmExecutor;
|
use glm::GlmExecutor;
|
||||||
use gpu::*;
|
use gpu::*;
|
||||||
use profile::ExpertProfile;
|
use profile::ExpertProfile;
|
||||||
|
|
||||||
use super::gguf::{F16, F32, Gguf, IQ2_XXS, MXFP4, Q2_K, Q4_K, Q8_0, Tensor as GgufTensor};
|
use super::gguf::{BF16, F16, F32, Gguf, IQ2_XXS, MXFP4, Q2_K, Q4_K, Q8_0, Tensor as GgufTensor};
|
||||||
use super::validation::{DsparkConfig, SupportKind, dspark_config};
|
use super::validation::{DsparkConfig, SupportKind, dspark_config};
|
||||||
use super::{Model, ModelFamily, Rng, exact_delta_sample};
|
use super::{Model, ModelFamily, Rng, exact_delta_sample};
|
||||||
use crate::model::ModelChoice;
|
use crate::model::ModelChoice;
|
||||||
@@ -42,7 +44,7 @@ fn environment_present(name: &CStr) -> bool {
|
|||||||
!unsafe { getenv(name.as_ptr()) }.is_null()
|
!unsafe { getenv(name.as_ptr()) }.is_null()
|
||||||
}
|
}
|
||||||
|
|
||||||
const SOURCES: [(&str, &str); 19] = [
|
const SOURCES: [(&str, &str); 22] = [
|
||||||
("DS4_METAL_FLASH_ATTN_SOURCE", "flash_attn.metal"),
|
("DS4_METAL_FLASH_ATTN_SOURCE", "flash_attn.metal"),
|
||||||
("DS4_METAL_DENSE_SOURCE", "dense.metal"),
|
("DS4_METAL_DENSE_SOURCE", "dense.metal"),
|
||||||
("DS4_METAL_MOE_SOURCE", "moe.metal"),
|
("DS4_METAL_MOE_SOURCE", "moe.metal"),
|
||||||
@@ -62,6 +64,9 @@ const SOURCES: [(&str, &str); 19] = [
|
|||||||
("DS4_METAL_NORM_SOURCE", "norm.metal"),
|
("DS4_METAL_NORM_SOURCE", "norm.metal"),
|
||||||
("DS4_METAL_BIN_SOURCE", "bin.metal"),
|
("DS4_METAL_BIN_SOURCE", "bin.metal"),
|
||||||
("DS4_METAL_SET_ROWS_SOURCE", "set_rows.metal"),
|
("DS4_METAL_SET_ROWS_SOURCE", "set_rows.metal"),
|
||||||
|
("DS4_METAL_GLM53_BF16_SOURCE", "glm53_bf16.metal"),
|
||||||
|
("DS4_METAL_GLM53_VISION_SOURCE", "glm53_vision.metal"),
|
||||||
|
("DS4_METAL_GLM53_KDA_SOURCE", "glm53_kda.metal"),
|
||||||
];
|
];
|
||||||
|
|
||||||
// The Metal boundary uses this only to decide whether diagnostic logs get ANSI
|
// The Metal boundary uses this only to decide whether diagnostic logs get ANSI
|
||||||
@@ -1593,7 +1598,7 @@ impl Dspark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Steering {
|
pub(super) struct Steering {
|
||||||
directions: Buffer,
|
directions: Buffer,
|
||||||
attention_scale: f32,
|
attention_scale: f32,
|
||||||
ffn_scale: f32,
|
ffn_scale: f32,
|
||||||
@@ -1615,6 +1620,7 @@ impl Steering {
|
|||||||
let expected = model
|
let expected = model
|
||||||
.shape
|
.shape
|
||||||
.layers
|
.layers
|
||||||
|
.saturating_sub(model.shape.nextn)
|
||||||
.checked_mul(model.shape.embd as u32)
|
.checked_mul(model.shape.embd as u32)
|
||||||
.and_then(|values| values.checked_mul(4))
|
.and_then(|values| values.checked_mul(4))
|
||||||
.ok_or("directional steering size overflow")? as usize;
|
.ok_or("directional steering size overflow")? as usize;
|
||||||
@@ -2168,7 +2174,9 @@ impl SsdPlan {
|
|||||||
let hotlist = match model.shape.model {
|
let hotlist = match model.shape.model {
|
||||||
ModelChoice::DeepSeekV4Flash0731 => hotlist::FLASH,
|
ModelChoice::DeepSeekV4Flash0731 => hotlist::FLASH,
|
||||||
ModelChoice::DeepSeekV4Pro => hotlist::PRO,
|
ModelChoice::DeepSeekV4Pro => hotlist::PRO,
|
||||||
ModelChoice::Glm52 => unreachable!("GLM uses its dedicated executor"),
|
ModelChoice::Glm52 | ModelChoice::Glm53Flash => {
|
||||||
|
unreachable!("GLM uses its dedicated executor")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
for &(layer, expert) in hotlist {
|
for &(layer, expert) in hotlist {
|
||||||
if loaded == self.preload_experts {
|
if loaded == self.preload_experts {
|
||||||
@@ -4435,6 +4443,7 @@ impl Executor {
|
|||||||
quality,
|
quality,
|
||||||
ssd,
|
ssd,
|
||||||
speculative,
|
speculative,
|
||||||
|
steering,
|
||||||
expert_profile_path,
|
expert_profile_path,
|
||||||
)
|
)
|
||||||
.map(Box::new)
|
.map(Box::new)
|
||||||
@@ -4481,6 +4490,24 @@ impl Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn encode_vision(&self, encoded: &[u8]) -> Result<vision::VisionEmbedding, String> {
|
||||||
|
match self {
|
||||||
|
Self::Glm(executor) => executor.encode_vision(encoded),
|
||||||
|
Self::DeepSeek(_) => Err("vision input requires GLM 5.3 Flash".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn set_vision_overlays(
|
||||||
|
&mut self,
|
||||||
|
overlays: Vec<(u32, vision::VisionEmbedding)>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
match self {
|
||||||
|
Self::Glm(executor) => executor.set_vision_overlays(overlays),
|
||||||
|
Self::DeepSeek(_) if overlays.is_empty() => Ok(()),
|
||||||
|
Self::DeepSeek(_) => Err("vision input requires GLM 5.3 Flash".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn eval_speculative_greedy(
|
pub(super) fn eval_speculative_greedy(
|
||||||
&mut self,
|
&mut self,
|
||||||
token: i32,
|
token: i32,
|
||||||
@@ -7648,7 +7675,7 @@ fn compression_ratio(shape: super::Shape, layer: u32) -> u32 {
|
|||||||
}
|
}
|
||||||
crate::model::ModelChoice::DeepSeekV4Flash0731
|
crate::model::ModelChoice::DeepSeekV4Flash0731
|
||||||
| crate::model::ModelChoice::DeepSeekV4Pro => 128,
|
| crate::model::ModelChoice::DeepSeekV4Pro => 128,
|
||||||
crate::model::ModelChoice::Glm52 => 0,
|
crate::model::ModelChoice::Glm52 | crate::model::ModelChoice::Glm53Flash => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3530
-191
File diff suppressed because it is too large.
Load diff
@@ -5,6 +5,42 @@ pub(super) struct GpuTensor {
|
|||||||
_private: [u8; 0],
|
_private: [u8; 0],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub(super) struct Glm53VisionLayerWeights {
|
||||||
|
pub(super) norm1: u64,
|
||||||
|
pub(super) qkv_weight: u64,
|
||||||
|
pub(super) qkv_bias: u64,
|
||||||
|
pub(super) q_norm: u64,
|
||||||
|
pub(super) k_norm: u64,
|
||||||
|
pub(super) attn_proj_weight: u64,
|
||||||
|
pub(super) attn_proj_bias: u64,
|
||||||
|
pub(super) norm2: u64,
|
||||||
|
pub(super) gate_weight: u64,
|
||||||
|
pub(super) gate_bias: u64,
|
||||||
|
pub(super) up_weight: u64,
|
||||||
|
pub(super) up_bias: u64,
|
||||||
|
pub(super) down_weight: u64,
|
||||||
|
pub(super) down_bias: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub(super) struct Glm53VisionWeights {
|
||||||
|
pub(super) patch_weight: u64,
|
||||||
|
pub(super) patch_bias: u64,
|
||||||
|
pub(super) post_norm: u64,
|
||||||
|
pub(super) downsample_weight: u64,
|
||||||
|
pub(super) downsample_bias: u64,
|
||||||
|
pub(super) merger_proj: u64,
|
||||||
|
pub(super) merger_norm: u64,
|
||||||
|
pub(super) merger_norm_bias: u64,
|
||||||
|
pub(super) merger_gate: u64,
|
||||||
|
pub(super) merger_up: u64,
|
||||||
|
pub(super) merger_down: u64,
|
||||||
|
pub(super) layer: [Glm53VisionLayerWeights; 24],
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub(super) struct StreamExpertTable {
|
pub(super) struct StreamExpertTable {
|
||||||
pub(super) model_map: *const c_void,
|
pub(super) model_map: *const c_void,
|
||||||
@@ -93,6 +129,12 @@ unsafe extern "C" {
|
|||||||
count: u32,
|
count: u32,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_flush_commands() -> i32;
|
pub(super) fn ds4_gpu_flush_commands() -> i32;
|
||||||
|
pub(super) fn ds4_gpu_flush_encoder() -> i32;
|
||||||
|
pub(super) fn ds4_gpu_argmax_tensor(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
logits: *const GpuTensor,
|
||||||
|
vocab: u32,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_device_is_pre_m5_apple_silicon() -> i32;
|
pub(super) fn ds4_gpu_device_is_pre_m5_apple_silicon() -> i32;
|
||||||
pub(super) fn ds4_gpu_device_is_m5_apple_silicon() -> i32;
|
pub(super) fn ds4_gpu_device_is_m5_apple_silicon() -> i32;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -227,6 +269,65 @@ unsafe extern "C" {
|
|||||||
embd: u32,
|
embd: u32,
|
||||||
hc: u32,
|
hc: u32,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_repeat_hc_rows_tensor(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
x: *const GpuTensor,
|
||||||
|
rows: u32,
|
||||||
|
embd: u32,
|
||||||
|
hc: u32,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_embedding_bf16(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
weight: u64,
|
||||||
|
tokens: *const GpuTensor,
|
||||||
|
rows: u32,
|
||||||
|
embd: u32,
|
||||||
|
vocab: u32,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_vision_encode(
|
||||||
|
out: *mut f32,
|
||||||
|
patches: *const f32,
|
||||||
|
grid_h: u32,
|
||||||
|
grid_w: u32,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
weights: *const Glm53VisionWeights,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_scatter_image_hc(
|
||||||
|
hc: *mut GpuTensor,
|
||||||
|
image: *const GpuTensor,
|
||||||
|
dst_row: u32,
|
||||||
|
image_row: u32,
|
||||||
|
rows: u32,
|
||||||
|
total_rows: u32,
|
||||||
|
embd: u32,
|
||||||
|
hc_count: u32,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_matmul_bf16(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
weight: u64,
|
||||||
|
input: u32,
|
||||||
|
output: u32,
|
||||||
|
x: *const GpuTensor,
|
||||||
|
rows: u32,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_matmul_bf16_qkv(
|
||||||
|
q: *mut GpuTensor,
|
||||||
|
k: *mut GpuTensor,
|
||||||
|
v: *mut GpuTensor,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
q_weight: u64,
|
||||||
|
k_weight: u64,
|
||||||
|
v_weight: u64,
|
||||||
|
input: u32,
|
||||||
|
output: u32,
|
||||||
|
x: *const GpuTensor,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_attention_noncausal_raw_batch_heads_tensor(
|
pub(super) fn ds4_gpu_attention_noncausal_raw_batch_heads_tensor(
|
||||||
out: *mut GpuTensor,
|
out: *mut GpuTensor,
|
||||||
map: *const c_void,
|
map: *const c_void,
|
||||||
@@ -417,6 +518,35 @@ unsafe extern "C" {
|
|||||||
beta_slow: f32,
|
beta_slow: f32,
|
||||||
cache_f16: bool,
|
cache_f16: bool,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_indexer_pool_update_tensor(
|
||||||
|
cache: *mut GpuTensor,
|
||||||
|
tail_k: *mut GpuTensor,
|
||||||
|
tail_gate: *mut GpuTensor,
|
||||||
|
raw_k: *const GpuTensor,
|
||||||
|
gate: *const GpuTensor,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
norm_weight: u64,
|
||||||
|
norm_bias: u64,
|
||||||
|
ape: u64,
|
||||||
|
pos: u32,
|
||||||
|
rows: u32,
|
||||||
|
cache_cap: u32,
|
||||||
|
head_dim: u32,
|
||||||
|
pool_size: u32,
|
||||||
|
eps: f32,
|
||||||
|
cache_f16: bool,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_expand_pool_selection_tensor(
|
||||||
|
selected: *mut GpuTensor,
|
||||||
|
pools: *const GpuTensor,
|
||||||
|
rows: u32,
|
||||||
|
pos: u32,
|
||||||
|
selected_pools: u32,
|
||||||
|
top_k: u32,
|
||||||
|
pool_size: u32,
|
||||||
|
width: u32,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_glm_fill_selected_range_tensor(
|
pub(super) fn ds4_gpu_glm_fill_selected_range_tensor(
|
||||||
selected: *mut GpuTensor,
|
selected: *mut GpuTensor,
|
||||||
count: u32,
|
count: u32,
|
||||||
@@ -467,6 +597,66 @@ unsafe extern "C" {
|
|||||||
scale: f32,
|
scale: f32,
|
||||||
cache_f16: bool,
|
cache_f16: bool,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_indexer_scores_batch_tensor(
|
||||||
|
scores: *mut GpuTensor,
|
||||||
|
q: *const GpuTensor,
|
||||||
|
weights: *const GpuTensor,
|
||||||
|
cache: *const GpuTensor,
|
||||||
|
visible: u32,
|
||||||
|
rows: u32,
|
||||||
|
pos: u32,
|
||||||
|
pool_size: u32,
|
||||||
|
heads: u32,
|
||||||
|
head_dim: u32,
|
||||||
|
scale: f32,
|
||||||
|
cache_f16: bool,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_kda_decode(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
conv: *mut GpuTensor,
|
||||||
|
recurrent: *mut GpuTensor,
|
||||||
|
q: *const GpuTensor,
|
||||||
|
k: *const GpuTensor,
|
||||||
|
v: *const GpuTensor,
|
||||||
|
gate: *const GpuTensor,
|
||||||
|
beta: *const GpuTensor,
|
||||||
|
output_gate: *const GpuTensor,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
q_conv: u64,
|
||||||
|
k_conv: u64,
|
||||||
|
v_conv: u64,
|
||||||
|
a_log: u64,
|
||||||
|
dt_bias: u64,
|
||||||
|
output_norm: u64,
|
||||||
|
heads: u32,
|
||||||
|
rows: u32,
|
||||||
|
gate_lower_bound: f32,
|
||||||
|
eps: f32,
|
||||||
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm53_kda_prefill(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
conv: *mut GpuTensor,
|
||||||
|
recurrent: *mut GpuTensor,
|
||||||
|
q: *mut GpuTensor,
|
||||||
|
k: *mut GpuTensor,
|
||||||
|
v: *mut GpuTensor,
|
||||||
|
gate: *mut GpuTensor,
|
||||||
|
beta: *const GpuTensor,
|
||||||
|
output_gate: *const GpuTensor,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
q_conv: u64,
|
||||||
|
k_conv: u64,
|
||||||
|
v_conv: u64,
|
||||||
|
a_log: u64,
|
||||||
|
dt_bias: u64,
|
||||||
|
output_norm: u64,
|
||||||
|
heads: u32,
|
||||||
|
rows: u32,
|
||||||
|
gate_lower_bound: f32,
|
||||||
|
eps: f32,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_glm_qk_lowrank_typed_tensor(
|
pub(super) fn ds4_gpu_glm_qk_lowrank_typed_tensor(
|
||||||
out: *mut GpuTensor,
|
out: *mut GpuTensor,
|
||||||
q: *const GpuTensor,
|
q: *const GpuTensor,
|
||||||
@@ -554,6 +744,19 @@ unsafe extern "C" {
|
|||||||
beta_fast: f32,
|
beta_fast: f32,
|
||||||
beta_slow: f32,
|
beta_slow: f32,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_glm_attention_dense_compact_lora_causal_tensor(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
qk_low: *const GpuTensor,
|
||||||
|
kv_cache: *const GpuTensor,
|
||||||
|
q_row0: u32,
|
||||||
|
rows: u32,
|
||||||
|
selected: u32,
|
||||||
|
cache_cap: u32,
|
||||||
|
cache_f16: bool,
|
||||||
|
heads: u32,
|
||||||
|
kv_lora: u32,
|
||||||
|
q_nope: u32,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_glm_attention_indexed_batch_lora_valid_tensor(
|
pub(super) fn ds4_gpu_glm_attention_indexed_batch_lora_valid_tensor(
|
||||||
out: *mut GpuTensor,
|
out: *mut GpuTensor,
|
||||||
q: *const GpuTensor,
|
q: *const GpuTensor,
|
||||||
@@ -777,6 +980,20 @@ unsafe extern "C" {
|
|||||||
eps: f32,
|
eps: f32,
|
||||||
norm_eps: f32,
|
norm_eps: f32,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_hc_split_weighted_sum_tensor(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
split: *mut GpuTensor,
|
||||||
|
mix: *const GpuTensor,
|
||||||
|
residual: *const GpuTensor,
|
||||||
|
map: *const c_void,
|
||||||
|
size: u64,
|
||||||
|
scale: u64,
|
||||||
|
base: u64,
|
||||||
|
embd: u32,
|
||||||
|
hc: u32,
|
||||||
|
iterations: u32,
|
||||||
|
eps: f32,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_hc_rms_norm_mix_f16_available() -> i32;
|
pub(super) fn ds4_gpu_hc_rms_norm_mix_f16_available() -> i32;
|
||||||
pub(super) fn ds4_gpu_hc_rms_norm_mix_f16_tensor(
|
pub(super) fn ds4_gpu_hc_rms_norm_mix_f16_tensor(
|
||||||
out: *mut GpuTensor,
|
out: *mut GpuTensor,
|
||||||
@@ -1359,6 +1576,15 @@ unsafe extern "C" {
|
|||||||
embd: u32,
|
embd: u32,
|
||||||
hc: u32,
|
hc: u32,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_hc_expand_tensor(
|
||||||
|
out: *mut GpuTensor,
|
||||||
|
block: *const GpuTensor,
|
||||||
|
residual: *const GpuTensor,
|
||||||
|
post: *const GpuTensor,
|
||||||
|
combine: *const GpuTensor,
|
||||||
|
embd: u32,
|
||||||
|
hc: u32,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_hc_expand_add_split_tensor(
|
pub(super) fn ds4_gpu_hc_expand_add_split_tensor(
|
||||||
out: *mut GpuTensor,
|
out: *mut GpuTensor,
|
||||||
block: *const GpuTensor,
|
block: *const GpuTensor,
|
||||||
@@ -1463,6 +1689,44 @@ unsafe extern "C" {
|
|||||||
x: *const GpuTensor,
|
x: *const GpuTensor,
|
||||||
clamp: f32,
|
clamp: f32,
|
||||||
) -> i32;
|
) -> i32;
|
||||||
|
pub(super) fn ds4_gpu_shared_mid_swiglu_q8_0_tensor(
|
||||||
|
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_gate_up_swiglu_q8_0_model_view_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_gate_up_swiglu_q8_0_rows_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,
|
||||||
|
rows: u64,
|
||||||
|
clamp: f32,
|
||||||
|
) -> i32;
|
||||||
pub(super) fn ds4_gpu_router_shared_gate_up_q8_0_tensor(
|
pub(super) fn ds4_gpu_router_shared_gate_up_q8_0_tensor(
|
||||||
router_logits: *mut GpuTensor,
|
router_logits: *mut GpuTensor,
|
||||||
gate: *mut GpuTensor,
|
gate: *mut GpuTensor,
|
||||||
@@ -1598,6 +1862,21 @@ impl Context {
|
|||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(vision) = &model.vision {
|
||||||
|
let mapped = unsafe {
|
||||||
|
ds4_gpu_set_model_map_range(
|
||||||
|
vision.map_ptr().cast(),
|
||||||
|
vision.len(),
|
||||||
|
vision.data_offset(),
|
||||||
|
vision.len() - vision.data_offset(),
|
||||||
|
vision.max_tensor_bytes(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if let Err(error) = check(mapped, "vision-model mapping") {
|
||||||
|
unsafe { ds4_gpu_cleanup() };
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
unsafe { ds4_gpu_set_quality(quality) };
|
unsafe { ds4_gpu_set_quality(quality) };
|
||||||
let model_file = File::open(model.main.path()).map_err(|error| {
|
let model_file = File::open(model.main.path()).map_err(|error| {
|
||||||
unsafe { ds4_gpu_cleanup() };
|
unsafe { ds4_gpu_cleanup() };
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
use super::gpu::{Glm53VisionLayerWeights, Glm53VisionWeights, ds4_gpu_glm53_vision_encode};
|
||||||
|
use super::{Gguf, call};
|
||||||
|
use image::{DynamicImage, ImageDecoder, ImageReader};
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
const EMBEDDING: usize = 4096;
|
||||||
|
const PATCH: u32 = 14;
|
||||||
|
const MERGE: u32 = 2;
|
||||||
|
const MIN_TOKENS: u32 = 16;
|
||||||
|
const MAX_TOKENS: u32 = 8000;
|
||||||
|
|
||||||
|
pub(super) struct VisionEncoder {
|
||||||
|
weights: Glm53VisionWeights,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::engine) struct VisionEmbedding {
|
||||||
|
pub(in crate::engine) values: Vec<f32>,
|
||||||
|
pub(in crate::engine) tokens: u32,
|
||||||
|
pub(in crate::engine) width: u32,
|
||||||
|
pub(in crate::engine) height: u32,
|
||||||
|
pub(in crate::engine) content_width: u32,
|
||||||
|
pub(in crate::engine) content_height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisionEncoder {
|
||||||
|
pub(super) fn bind(model: &Gguf) -> Result<Self, String> {
|
||||||
|
let offset = |name: &str| model.tensor(name).map(|tensor| tensor.offset);
|
||||||
|
let mut weights = Glm53VisionWeights {
|
||||||
|
patch_weight: offset("model.visual.patch_embed.proj.weight")?,
|
||||||
|
patch_bias: offset("model.visual.patch_embed.proj.bias")?,
|
||||||
|
post_norm: offset("model.visual.post_layernorm.weight")?,
|
||||||
|
downsample_weight: offset("model.visual.downsample.weight")?,
|
||||||
|
downsample_bias: offset("model.visual.downsample.bias")?,
|
||||||
|
merger_proj: offset("model.visual.merger.proj.weight")?,
|
||||||
|
merger_norm: offset("model.visual.merger.post_projection_norm.weight")?,
|
||||||
|
merger_norm_bias: offset("model.visual.merger.post_projection_norm.bias")?,
|
||||||
|
merger_gate: offset("model.visual.merger.gate_proj.weight")?,
|
||||||
|
merger_up: offset("model.visual.merger.up_proj.weight")?,
|
||||||
|
merger_down: offset("model.visual.merger.down_proj.weight")?,
|
||||||
|
..Glm53VisionWeights::default()
|
||||||
|
};
|
||||||
|
for (layer, target) in weights.layer.iter_mut().enumerate() {
|
||||||
|
let name = |suffix: &str| format!("model.visual.blocks.{layer}.{suffix}");
|
||||||
|
*target = Glm53VisionLayerWeights {
|
||||||
|
norm1: offset(&name("norm1.weight"))?,
|
||||||
|
qkv_weight: offset(&name("attn.qkv.weight"))?,
|
||||||
|
qkv_bias: offset(&name("attn.qkv.bias"))?,
|
||||||
|
q_norm: offset(&name("attn.q_norm.weight"))?,
|
||||||
|
k_norm: offset(&name("attn.k_norm.weight"))?,
|
||||||
|
attn_proj_weight: offset(&name("attn.proj.weight"))?,
|
||||||
|
attn_proj_bias: offset(&name("attn.proj.bias"))?,
|
||||||
|
norm2: offset(&name("norm2.weight"))?,
|
||||||
|
gate_weight: offset(&name("mlp.gate_proj.weight"))?,
|
||||||
|
gate_bias: offset(&name("mlp.gate_proj.bias"))?,
|
||||||
|
up_weight: offset(&name("mlp.up_proj.weight"))?,
|
||||||
|
up_bias: offset(&name("mlp.up_proj.bias"))?,
|
||||||
|
down_weight: offset(&name("mlp.down_proj.weight"))?,
|
||||||
|
down_bias: offset(&name("mlp.down_proj.bias"))?,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(Self { weights })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn encode(&self, model: &Gguf, encoded: &[u8]) -> Result<VisionEmbedding, String> {
|
||||||
|
if encoded.is_empty() || encoded.len() > 64 * 1024 * 1024 {
|
||||||
|
return Err("image is empty or exceeds the 64 MiB encoded limit".into());
|
||||||
|
}
|
||||||
|
let reader = ImageReader::new(Cursor::new(encoded))
|
||||||
|
.with_guessed_format()
|
||||||
|
.map_err(|error| error.to_string())?;
|
||||||
|
let mut decoder = reader.into_decoder().map_err(|error| error.to_string())?;
|
||||||
|
let orientation = decoder.orientation().map_err(|error| error.to_string())?;
|
||||||
|
let mut image = DynamicImage::from_decoder(decoder).map_err(|error| error.to_string())?;
|
||||||
|
image.apply_orientation(orientation);
|
||||||
|
let rgb = image.into_rgb8();
|
||||||
|
let (width, height) = rgb.dimensions();
|
||||||
|
if width == 0
|
||||||
|
|| height == 0
|
||||||
|
|| width > 16_384
|
||||||
|
|| height > 16_384
|
||||||
|
|| u64::from(width) * u64::from(height) > 64 * 1024 * 1024
|
||||||
|
{
|
||||||
|
return Err("image dimensions exceed the GLM 5.3 vision limits".into());
|
||||||
|
}
|
||||||
|
let patches = preprocess(rgb.as_raw(), width, height)?;
|
||||||
|
let tokens = patches.grid_height * patches.grid_width / 4;
|
||||||
|
let mut values = vec![0.0_f32; tokens as usize * EMBEDDING];
|
||||||
|
call(
|
||||||
|
unsafe {
|
||||||
|
ds4_gpu_glm53_vision_encode(
|
||||||
|
values.as_mut_ptr(),
|
||||||
|
patches.values.as_ptr(),
|
||||||
|
patches.grid_height,
|
||||||
|
patches.grid_width,
|
||||||
|
model.map_ptr().cast(),
|
||||||
|
model.len(),
|
||||||
|
&self.weights,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"encoding a GLM 5.3 image",
|
||||||
|
)?;
|
||||||
|
Ok(VisionEmbedding {
|
||||||
|
values,
|
||||||
|
tokens,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
content_width: patches.content_width,
|
||||||
|
content_height: patches.content_height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Patches {
|
||||||
|
values: Vec<f32>,
|
||||||
|
content_width: u32,
|
||||||
|
content_height: u32,
|
||||||
|
grid_width: u32,
|
||||||
|
grid_height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preprocess(rgb: &[u8], width: u32, height: u32) -> Result<Patches, String> {
|
||||||
|
const MEAN: [f32; 3] = [0.48145466, 0.4578275, 0.40821073];
|
||||||
|
const STDDEV: [f32; 3] = [0.26862954, 0.261_302_6, 0.275_777_1];
|
||||||
|
let (target_height, target_width) = smart_resize(height, width)?;
|
||||||
|
let mut scale = (target_height as f64 / height as f64).min(target_width as f64 / width as f64);
|
||||||
|
if 2 * u64::from(height) * u64::from(width) >= 2 * 28 * 28 * MIN_TOKENS as u64 && scale > 1.0 {
|
||||||
|
scale = 1.0;
|
||||||
|
}
|
||||||
|
let content_height = ((height as f64 * scale).floor() as u32).clamp(1, target_height);
|
||||||
|
let content_width = ((width as f64 * scale).floor() as u32).clamp(1, target_width);
|
||||||
|
let mut canvas = vec![0.0_f32; target_height as usize * target_width as usize * 3];
|
||||||
|
if content_width == width && content_height == height {
|
||||||
|
for y in 0..height {
|
||||||
|
for x in 0..width {
|
||||||
|
let source = (y as usize * width as usize + x as usize) * 3;
|
||||||
|
let target = (y as usize * target_width as usize + x as usize) * 3;
|
||||||
|
for channel in 0..3 {
|
||||||
|
canvas[target + channel] = f32::from(rgb[source + channel]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resize_bicubic(
|
||||||
|
rgb,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
&mut canvas,
|
||||||
|
content_width,
|
||||||
|
content_height,
|
||||||
|
target_width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for y in 0..target_height {
|
||||||
|
for x in 0..target_width {
|
||||||
|
let pixel = (y as usize * target_width as usize + x as usize) * 3;
|
||||||
|
for channel in 0..3 {
|
||||||
|
let value = if x < content_width && y < content_height {
|
||||||
|
canvas[pixel + channel]
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
canvas[pixel + channel] = (value / 255.0 - MEAN[channel]) / STDDEV[channel];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let grid_height = target_height / PATCH;
|
||||||
|
let grid_width = target_width / PATCH;
|
||||||
|
let patch_values = grid_height as usize * grid_width as usize * 3 * 2 * 14 * 14;
|
||||||
|
let mut values = Vec::with_capacity(patch_values);
|
||||||
|
for block_y in 0..grid_height / MERGE {
|
||||||
|
for block_x in 0..grid_width / MERGE {
|
||||||
|
for merge_y in 0..MERGE {
|
||||||
|
for merge_x in 0..MERGE {
|
||||||
|
let patch_y = block_y * MERGE + merge_y;
|
||||||
|
let patch_x = block_x * MERGE + merge_x;
|
||||||
|
for channel in 0..3 {
|
||||||
|
for _ in 0..2 {
|
||||||
|
for y in 0..PATCH {
|
||||||
|
for x in 0..PATCH {
|
||||||
|
let pixel = ((patch_y * PATCH + y) as usize
|
||||||
|
* target_width as usize
|
||||||
|
+ (patch_x * PATCH + x) as usize)
|
||||||
|
* 3;
|
||||||
|
values.push(canvas[pixel + channel]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if values.len() != patch_values {
|
||||||
|
return Err("internal GLM 5.3 vision patch layout mismatch".into());
|
||||||
|
}
|
||||||
|
Ok(Patches {
|
||||||
|
values,
|
||||||
|
content_width,
|
||||||
|
content_height,
|
||||||
|
grid_width,
|
||||||
|
grid_height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn smart_resize(height: u32, width: u32) -> Result<(u32, u32), String> {
|
||||||
|
let factor = 28_u32;
|
||||||
|
let align = |value: u32| value.div_ceil(factor) * factor;
|
||||||
|
let pixels_per_token = 2_u64 * factor as u64 * factor as u64;
|
||||||
|
let min_pixels = MIN_TOKENS as u64 * pixels_per_token;
|
||||||
|
let max_pixels = MAX_TOKENS as u64 * pixels_per_token;
|
||||||
|
let mut aligned_height = align(height);
|
||||||
|
let mut aligned_width = align(width);
|
||||||
|
let mut budget = 2_u64 * aligned_height as u64 * aligned_width as u64;
|
||||||
|
if budget < min_pixels {
|
||||||
|
let scale = (min_pixels as f64 / (2.0 * height as f64 * width as f64)).sqrt();
|
||||||
|
aligned_height = align((height as f64 * scale).ceil() as u32);
|
||||||
|
aligned_width = align((width as f64 * scale).ceil() as u32);
|
||||||
|
budget = 2_u64 * aligned_height as u64 * aligned_width as u64;
|
||||||
|
}
|
||||||
|
if budget > max_pixels {
|
||||||
|
let (mut low, mut high) = (1_u32, height);
|
||||||
|
aligned_height = factor;
|
||||||
|
aligned_width = factor;
|
||||||
|
while low <= high {
|
||||||
|
let content_height = low + (high - low) / 2;
|
||||||
|
let content_width =
|
||||||
|
((width as f64 * content_height as f64 / height as f64).floor() as u32).max(1);
|
||||||
|
let candidate_height = align(content_height);
|
||||||
|
let candidate_width = align(content_width);
|
||||||
|
if 2_u64 * candidate_height as u64 * candidate_width as u64 <= max_pixels {
|
||||||
|
aligned_height = candidate_height;
|
||||||
|
aligned_width = candidate_width;
|
||||||
|
low = content_height + 1;
|
||||||
|
} else {
|
||||||
|
high = content_height - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((aligned_height, aligned_width))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn resize_bicubic(
|
||||||
|
source: &[u8],
|
||||||
|
source_width: u32,
|
||||||
|
source_height: u32,
|
||||||
|
target: &mut [f32],
|
||||||
|
target_width: u32,
|
||||||
|
target_height: u32,
|
||||||
|
target_stride: u32,
|
||||||
|
) {
|
||||||
|
let scale_x = source_width as f64 / target_width as f64;
|
||||||
|
let scale_y = source_height as f64 / target_height as f64;
|
||||||
|
let filter_x = if scale_x >= 1.0 { 1.0 / scale_x } else { 1.0 };
|
||||||
|
let filter_y = if scale_y >= 1.0 { 1.0 / scale_y } else { 1.0 };
|
||||||
|
let support_x = if scale_x >= 1.0 { 2.0 * scale_x } else { 2.0 };
|
||||||
|
let support_y = if scale_y >= 1.0 { 2.0 * scale_y } else { 2.0 };
|
||||||
|
for dy in 0..target_height {
|
||||||
|
let center_y = scale_y * (dy as f64 + 0.5);
|
||||||
|
let y0 = (center_y - support_y + 0.5).max(0.0) as u32;
|
||||||
|
let y1 = (center_y + support_y + 0.5).min(source_height as f64) as u32;
|
||||||
|
for dx in 0..target_width {
|
||||||
|
let center_x = scale_x * (dx as f64 + 0.5);
|
||||||
|
let x0 = (center_x - support_x + 0.5).max(0.0) as u32;
|
||||||
|
let x1 = (center_x + support_x + 0.5).min(source_width as f64) as u32;
|
||||||
|
let mut sum = [0.0; 3];
|
||||||
|
let mut weight_sum = 0.0;
|
||||||
|
for iy in y0..y1 {
|
||||||
|
let wy = cubic((iy as f64 + 0.5 - center_y) * filter_y);
|
||||||
|
for ix in x0..x1 {
|
||||||
|
let weight = wy * cubic((ix as f64 + 0.5 - center_x) * filter_x);
|
||||||
|
let pixel = (iy as usize * source_width as usize + ix as usize) * 3;
|
||||||
|
for channel in 0..3 {
|
||||||
|
sum[channel] += source[pixel + channel] as f64 * weight;
|
||||||
|
}
|
||||||
|
weight_sum += weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let pixel = (dy as usize * target_stride as usize + dx as usize) * 3;
|
||||||
|
for channel in 0..3 {
|
||||||
|
target[pixel + channel] =
|
||||||
|
(sum[channel] / weight_sum).round().clamp(0.0, 255.0) as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cubic(mut x: f64) -> f64 {
|
||||||
|
const A: f64 = -0.5;
|
||||||
|
x = x.abs();
|
||||||
|
if x < 1.0 {
|
||||||
|
((A + 2.0) * x - (A + 3.0)) * x * x + 1.0
|
||||||
|
} else if x < 2.0 {
|
||||||
|
((A * x - 5.0 * A) * x + 8.0 * A) * x - 4.0 * A
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::smart_resize;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glm53_resize_matches_reference_token_grids() {
|
||||||
|
assert_eq!(smart_resize(28, 28), Ok((112, 112)));
|
||||||
|
assert_eq!(smart_resize(1024, 1024), Ok((1036, 1036)));
|
||||||
|
assert_eq!(smart_resize(1080, 1920), Ok((1092, 1932)));
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-1
@@ -1,5 +1,8 @@
|
|||||||
use super::gguf::Gguf;
|
use super::gguf::Gguf;
|
||||||
use super::{ChatTurn, ModelFamily};
|
use super::{
|
||||||
|
ChatTurn, ModelFamily, VISION_END_TOKEN, VISION_IMAGE_TOKEN, VISION_START_TOKEN,
|
||||||
|
VISION_TOKEN_END, VISION_TOKEN_START,
|
||||||
|
};
|
||||||
use crate::settings::ReasoningMode;
|
use crate::settings::ReasoningMode;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -160,6 +163,22 @@ impl Tokenizer {
|
|||||||
let mut span = 0;
|
let mut span = 0;
|
||||||
let mut position = 0;
|
let mut position = 0;
|
||||||
while position < bytes.len() {
|
while position < bytes.len() {
|
||||||
|
if bytes[position..].starts_with(VISION_TOKEN_START.as_bytes())
|
||||||
|
&& let Some(relative_end) =
|
||||||
|
text[position + VISION_TOKEN_START.len()..].find(VISION_TOKEN_END)
|
||||||
|
{
|
||||||
|
let count_start = position + VISION_TOKEN_START.len();
|
||||||
|
let count_end = count_start + relative_end;
|
||||||
|
if let Ok(count) = text[count_start..count_end].parse::<usize>() {
|
||||||
|
self.tokenize_plain(&text[span..position], &mut output);
|
||||||
|
output.push(VISION_START_TOKEN);
|
||||||
|
output.extend(std::iter::repeat_n(VISION_IMAGE_TOKEN, count));
|
||||||
|
output.push(VISION_END_TOKEN);
|
||||||
|
position = count_end + VISION_TOKEN_END.len();
|
||||||
|
span = position;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
let special = self
|
let special = self
|
||||||
.rendered_specials
|
.rendered_specials
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
+186
-10
@@ -9,7 +9,7 @@ pub(crate) fn validate_model_artifact(
|
|||||||
let model = Gguf::open(path)?;
|
let model = Gguf::open(path)?;
|
||||||
let shape = match expected {
|
let shape = match expected {
|
||||||
ModelChoice::DeepSeekV4Flash0731 => FLASH_0731,
|
ModelChoice::DeepSeekV4Flash0731 => FLASH_0731,
|
||||||
ModelChoice::DeepSeekV4Pro | ModelChoice::Glm52 => {
|
ModelChoice::DeepSeekV4Pro | ModelChoice::Glm52 | ModelChoice::Glm53Flash => {
|
||||||
return Err(format!("{expected} does not use an external support GGUF"));
|
return Err(format!("{expected} does not use an external support GGUF"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -35,6 +35,79 @@ pub(crate) fn validate_model_artifact(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validate_vision_artifact(path: &Path) -> Result<(), String> {
|
||||||
|
let model = Gguf::open(path)?;
|
||||||
|
if model.bytes("general.architecture")? != b"glm5-next-vision" {
|
||||||
|
return Err("vision GGUF architecture is not glm5-next-vision".into());
|
||||||
|
}
|
||||||
|
if model.tensors.len() != 347 {
|
||||||
|
return Err(format!(
|
||||||
|
"vision GGUF has {} tensors, expected 347",
|
||||||
|
model.tensors.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for (key, expected) in [
|
||||||
|
("block_count", 24),
|
||||||
|
("embedding_length", 1024),
|
||||||
|
("feed_forward_length", 4096),
|
||||||
|
("attention.head_count", 16),
|
||||||
|
("projection_length", 4096),
|
||||||
|
("projection.feed_forward_length", 10_240),
|
||||||
|
("patch_size", 14),
|
||||||
|
("temporal_patch_size", 2),
|
||||||
|
("spatial_merge_size", 2),
|
||||||
|
("image_token_id", VISION_IMAGE_TOKEN as u64),
|
||||||
|
("image_start_token_id", VISION_START_TOKEN as u64),
|
||||||
|
("image_end_token_id", VISION_END_TOKEN as u64),
|
||||||
|
] {
|
||||||
|
expect_u64(&model, &format!("glm5-next-vision.{key}"), expected)?;
|
||||||
|
}
|
||||||
|
let bf16 = &[BF16];
|
||||||
|
for (name, dims) in [
|
||||||
|
(
|
||||||
|
"model.visual.patch_embed.proj.weight",
|
||||||
|
vec![14, 14, 2, 3, 1024],
|
||||||
|
),
|
||||||
|
("model.visual.patch_embed.proj.bias", vec![1024]),
|
||||||
|
("model.visual.post_layernorm.weight", vec![1024]),
|
||||||
|
("model.visual.downsample.weight", vec![2, 2, 1024, 4096]),
|
||||||
|
("model.visual.downsample.bias", vec![4096]),
|
||||||
|
("model.visual.merger.proj.weight", vec![4096, 4096]),
|
||||||
|
(
|
||||||
|
"model.visual.merger.post_projection_norm.weight",
|
||||||
|
vec![4096],
|
||||||
|
),
|
||||||
|
("model.visual.merger.post_projection_norm.bias", vec![4096]),
|
||||||
|
("model.visual.merger.gate_proj.weight", vec![4096, 10_240]),
|
||||||
|
("model.visual.merger.up_proj.weight", vec![4096, 10_240]),
|
||||||
|
("model.visual.merger.down_proj.weight", vec![10_240, 4096]),
|
||||||
|
] {
|
||||||
|
expect(&model, name, bf16, &dims)?;
|
||||||
|
}
|
||||||
|
for layer in 0..24 {
|
||||||
|
let name = |suffix: &str| format!("model.visual.blocks.{layer}.{suffix}");
|
||||||
|
for (suffix, dims) in [
|
||||||
|
("norm1.weight", vec![1024]),
|
||||||
|
("attn.qkv.weight", vec![1024, 3072]),
|
||||||
|
("attn.qkv.bias", vec![3072]),
|
||||||
|
("attn.q_norm.weight", vec![64]),
|
||||||
|
("attn.k_norm.weight", vec![64]),
|
||||||
|
("attn.proj.weight", vec![1024, 1024]),
|
||||||
|
("attn.proj.bias", vec![1024]),
|
||||||
|
("norm2.weight", vec![1024]),
|
||||||
|
("mlp.gate_proj.weight", vec![1024, 4096]),
|
||||||
|
("mlp.gate_proj.bias", vec![4096]),
|
||||||
|
("mlp.up_proj.weight", vec![1024, 4096]),
|
||||||
|
("mlp.up_proj.bias", vec![4096]),
|
||||||
|
("mlp.down_proj.weight", vec![4096, 1024]),
|
||||||
|
("mlp.down_proj.bias", vec![1024]),
|
||||||
|
] {
|
||||||
|
expect(&model, &name(suffix), bf16, &dims)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub(super) enum SupportKind {
|
pub(super) enum SupportKind {
|
||||||
DSpark,
|
DSpark,
|
||||||
@@ -116,14 +189,11 @@ pub(super) fn validate_support(model: &Gguf, shape: &Shape) -> Result<SupportKin
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape, String> {
|
pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape, String> {
|
||||||
let family = if model.bytes("general.architecture").ok() == Some(b"glm-dsa") {
|
let architecture = model.bytes("general.architecture")?;
|
||||||
ModelFamily::Glm
|
let shape = match architecture {
|
||||||
} else {
|
b"glm-dsa" => GLM,
|
||||||
ModelFamily::DeepSeek
|
b"glm5-next" => GLM53_FLASH,
|
||||||
};
|
_ => match (model.u32("deepseek4.block_count")?, expected) {
|
||||||
let shape = match family {
|
|
||||||
ModelFamily::Glm => GLM,
|
|
||||||
ModelFamily::DeepSeek => match (model.u32("deepseek4.block_count")?, expected) {
|
|
||||||
(43, ModelChoice::DeepSeekV4Flash0731) => FLASH_0731,
|
(43, ModelChoice::DeepSeekV4Flash0731) => FLASH_0731,
|
||||||
(43, _) => FLASH_0731,
|
(43, _) => FLASH_0731,
|
||||||
(61, _) => PRO,
|
(61, _) => PRO,
|
||||||
@@ -143,6 +213,9 @@ pub(super) fn validate_main(model: &Gguf, expected: ModelChoice) -> Result<Shape
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate_metadata(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
fn validate_metadata(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||||
|
if shape.model == ModelChoice::Glm53Flash {
|
||||||
|
return validate_glm53_metadata(model, shape);
|
||||||
|
}
|
||||||
let prefix = if shape.family == ModelFamily::Glm {
|
let prefix = if shape.family == ModelFamily::Glm {
|
||||||
"glm-dsa"
|
"glm-dsa"
|
||||||
} else {
|
} else {
|
||||||
@@ -266,13 +339,102 @@ fn validate_metadata(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_glm53_metadata(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||||
|
let prefix = "glm5-next";
|
||||||
|
for (key, expected) in [
|
||||||
|
("block_count", u64::from(shape.layers)),
|
||||||
|
("trunk_block_count", u64::from(shape.layers - shape.nextn)),
|
||||||
|
("nextn_predict_layers", u64::from(shape.nextn)),
|
||||||
|
("context_length", shape.original_context),
|
||||||
|
("embedding_length", shape.embd),
|
||||||
|
("vocab_size", shape.vocab),
|
||||||
|
("feed_forward_length", shape.ff_dense),
|
||||||
|
("expert_feed_forward_length", shape.ff_expert),
|
||||||
|
("expert_count", shape.experts),
|
||||||
|
("expert_used_count", shape.experts_used),
|
||||||
|
("expert_shared_count", shape.expert_shared),
|
||||||
|
("leading_dense_block_count", u64::from(shape.leading_dense)),
|
||||||
|
("attention.head_count", shape.heads),
|
||||||
|
("attention.key_length", shape.key_mla),
|
||||||
|
("attention.value_length", shape.value_mla),
|
||||||
|
("attention.q_lora_rank", shape.lora_q),
|
||||||
|
("attention.kv_lora_rank", shape.kv_lora),
|
||||||
|
("attention.rope_dimension_count", shape.rot),
|
||||||
|
("attention.indexer.head_count", shape.indexer_heads),
|
||||||
|
("attention.indexer.key_length", shape.indexer_head_dim),
|
||||||
|
("attention.indexer.top_k", shape.indexer_top_k),
|
||||||
|
("attention.indexer.pool_size", 4),
|
||||||
|
("linear_attention.head_count", 64),
|
||||||
|
("linear_attention.head_dimension", 128),
|
||||||
|
("linear_attention.conv_kernel", 4),
|
||||||
|
("hyper_connection.count", shape.hc),
|
||||||
|
("hyper_connection.sinkhorn_iterations", shape.hc_sinkhorn),
|
||||||
|
] {
|
||||||
|
expect_u64(model, &format!("{prefix}.{key}"), expected)?;
|
||||||
|
}
|
||||||
|
for (key, expected) in [
|
||||||
|
("expert_weights_scale", shape.expert_weight_scale),
|
||||||
|
("swiglu_limit", shape.swiglu_clamp),
|
||||||
|
("attention.layer_norm_rms_epsilon", shape.rms_epsilon),
|
||||||
|
("linear_attention.gate_lower_bound", -5.0),
|
||||||
|
("hyper_connection.epsilon", shape.hc_epsilon),
|
||||||
|
] {
|
||||||
|
expect_float(model, &format!("{prefix}.{key}"), expected)?;
|
||||||
|
}
|
||||||
|
if !model.boolean("glm5-next.expert_weights_norm")? {
|
||||||
|
return Err("glm5-next.expert_weights_norm must be true".into());
|
||||||
|
}
|
||||||
|
let layer_types = model.u32s("glm5-next.layer_types")?;
|
||||||
|
if layer_types.len() != shape.layers as usize {
|
||||||
|
return Err("glm5-next.layer_types must contain one entry per layer".into());
|
||||||
|
}
|
||||||
|
for (layer, &kind) in layer_types.iter().enumerate() {
|
||||||
|
let expected =
|
||||||
|
u32::from(layer + shape.nextn as usize >= shape.layers as usize || layer % 4 == 3);
|
||||||
|
if kind != expected {
|
||||||
|
return Err(format!(
|
||||||
|
"unexpected GLM 5.3 attention type at layer {layer}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
fn validate_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||||
match shape.family {
|
match shape.family {
|
||||||
ModelFamily::DeepSeek => validate_deepseek_tensors(model, shape),
|
ModelFamily::DeepSeek => validate_deepseek_tensors(model, shape),
|
||||||
|
ModelFamily::Glm if shape.model == ModelChoice::Glm53Flash => {
|
||||||
|
validate_glm53_tensors(model, shape)
|
||||||
|
}
|
||||||
ModelFamily::Glm => validate_glm_tensors(model, shape),
|
ModelFamily::Glm => validate_glm_tensors(model, shape),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_glm53_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||||
|
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])?;
|
||||||
|
expect(model, "blk.0.kda_q.weight", DENSE, &[shape.embd, 8192])?;
|
||||||
|
expect(
|
||||||
|
model,
|
||||||
|
"blk.3.attn_q_a.weight",
|
||||||
|
DENSE,
|
||||||
|
&[shape.embd, shape.lora_q],
|
||||||
|
)?;
|
||||||
|
expect(
|
||||||
|
model,
|
||||||
|
"blk.45.nextn.eh_proj.weight",
|
||||||
|
DENSE,
|
||||||
|
&[2 * shape.embd, shape.embd],
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_deepseek_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
fn validate_deepseek_tensors(model: &Gguf, shape: &Shape) -> Result<(), String> {
|
||||||
let hc_dim = shape.embd * shape.hc;
|
let hc_dim = shape.embd * shape.hc;
|
||||||
let hc_mix = 2 * shape.hc + shape.hc * shape.hc;
|
let hc_mix = 2 * shape.hc + shape.hc * shape.hc;
|
||||||
@@ -902,7 +1064,7 @@ fn compression_ratio(shape: &Shape, layer: u32) -> u32 {
|
|||||||
4
|
4
|
||||||
}
|
}
|
||||||
ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro => 128,
|
ModelChoice::DeepSeekV4Flash0731 | ModelChoice::DeepSeekV4Pro => 128,
|
||||||
ModelChoice::Glm52 => 0,
|
ModelChoice::Glm52 | ModelChoice::Glm53Flash => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1157,4 +1319,18 @@ mod tests {
|
|||||||
validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash0731, true).unwrap();
|
validate_model_artifact(&path, ModelChoice::DeepSeekV4Flash0731, true).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn configured_glm53_vision_fixture_passes_the_exact_layout() {
|
||||||
|
if let Some(path) = std::env::var_os("DS4SERVER_GLM53_VISION") {
|
||||||
|
validate_vision_artifact(Path::new(&path)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn configured_glm53_main_fixture_passes_the_exact_layout() {
|
||||||
|
if let Some(path) = std::env::var_os("DS4SERVER_GLM53_MODEL") {
|
||||||
|
validate_model_artifact(Path::new(&path), ModelChoice::Glm53Flash, false).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -847,6 +847,7 @@ fn model_code(model: ModelChoice) -> u8 {
|
|||||||
ModelChoice::DeepSeekV4Pro => 2,
|
ModelChoice::DeepSeekV4Pro => 2,
|
||||||
ModelChoice::Glm52 => 3,
|
ModelChoice::Glm52 => 3,
|
||||||
ModelChoice::DeepSeekV4Flash0731 => 4,
|
ModelChoice::DeepSeekV4Flash0731 => 4,
|
||||||
|
ModelChoice::Glm53Flash => 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,6 +857,7 @@ fn model_name(value: u8) -> &'static str {
|
|||||||
2 => "DeepSeek V4 Pro",
|
2 => "DeepSeek V4 Pro",
|
||||||
3 => "GLM 5.2",
|
3 => "GLM 5.2",
|
||||||
4 => "DeepSeek V4 Flash 0731",
|
4 => "DeepSeek V4 Flash 0731",
|
||||||
|
5 => "GLM 5.3 Flash Q2",
|
||||||
_ => "No model loaded",
|
_ => "No model loaded",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-3
@@ -9,20 +9,24 @@ use std::fmt;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub(crate) const MODEL_CHOICES: [ModelChoice; 3] = [
|
pub(crate) const MODEL_CHOICES: [ModelChoice; 4] = [
|
||||||
ModelChoice::DeepSeekV4Flash0731,
|
ModelChoice::DeepSeekV4Flash0731,
|
||||||
ModelChoice::DeepSeekV4Pro,
|
ModelChoice::DeepSeekV4Pro,
|
||||||
ModelChoice::Glm52,
|
ModelChoice::Glm52,
|
||||||
|
ModelChoice::Glm53Flash,
|
||||||
];
|
];
|
||||||
pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 4] = [
|
pub(crate) const MANAGED_ARTIFACTS: [ManagedArtifactId; 6] = [
|
||||||
ManagedArtifactId::DeepSeekV4Flash0731,
|
ManagedArtifactId::DeepSeekV4Flash0731,
|
||||||
ManagedArtifactId::DeepSeekV4Flash0731Dspark,
|
ManagedArtifactId::DeepSeekV4Flash0731Dspark,
|
||||||
ManagedArtifactId::DeepSeekV4Pro,
|
ManagedArtifactId::DeepSeekV4Pro,
|
||||||
ManagedArtifactId::Glm52,
|
ManagedArtifactId::Glm52,
|
||||||
|
ManagedArtifactId::Glm53Flash,
|
||||||
|
ManagedArtifactId::Glm53FlashVision,
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEEPSEEK_REPOSITORY: &str = "antirez/deepseek-v4-gguf";
|
const DEEPSEEK_REPOSITORY: &str = "antirez/deepseek-v4-gguf";
|
||||||
const GLM_REPOSITORY: &str = "antirez/glm-5.2-gguf";
|
const GLM_REPOSITORY: &str = "antirez/glm-5.2-gguf";
|
||||||
|
const GLM53_REPOSITORY: &str = "antirez/glm-5.3-flash-gguf";
|
||||||
|
|
||||||
const FLASH_0731: Artifact = Artifact {
|
const FLASH_0731: Artifact = Artifact {
|
||||||
label: "DeepSeek V4 Flash 0731 model",
|
label: "DeepSeek V4 Flash 0731 model",
|
||||||
@@ -31,6 +35,7 @@ const FLASH_0731: Artifact = Artifact {
|
|||||||
size: 86_720_111_488,
|
size: 86_720_111_488,
|
||||||
sha256: "ca22ae2f838e14077c22bc1c1417b71b45b5e5a3687bd96c2ac6e17fdb6261c0",
|
sha256: "ca22ae2f838e14077c22bc1c1417b71b45b5e5a3687bd96c2ac6e17fdb6261c0",
|
||||||
support: Some(false),
|
support: Some(false),
|
||||||
|
vision: false,
|
||||||
};
|
};
|
||||||
const FLASH_0731_DSPARK: Artifact = Artifact {
|
const FLASH_0731_DSPARK: Artifact = Artifact {
|
||||||
label: "DeepSeek V4 Flash 0731 DSpark support",
|
label: "DeepSeek V4 Flash 0731 DSpark support",
|
||||||
@@ -39,6 +44,7 @@ const FLASH_0731_DSPARK: Artifact = Artifact {
|
|||||||
size: 5_989_114_272,
|
size: 5_989_114_272,
|
||||||
sha256: "7e319924541db3f7a163ed7e11d7532a70d48228ab59d36cb81e1d4511885360",
|
sha256: "7e319924541db3f7a163ed7e11d7532a70d48228ab59d36cb81e1d4511885360",
|
||||||
support: Some(true),
|
support: Some(true),
|
||||||
|
vision: false,
|
||||||
};
|
};
|
||||||
const PRO: Artifact = Artifact {
|
const PRO: Artifact = Artifact {
|
||||||
label: "DeepSeek V4 Pro 0813 model",
|
label: "DeepSeek V4 Pro 0813 model",
|
||||||
@@ -47,6 +53,7 @@ const PRO: Artifact = Artifact {
|
|||||||
size: 464_627_334_560,
|
size: 464_627_334_560,
|
||||||
sha256: "c4d997ab9894b6c78b759f7869fe1726b6314b6515f6ff82607df3797c5eb193",
|
sha256: "c4d997ab9894b6c78b759f7869fe1726b6314b6515f6ff82607df3797c5eb193",
|
||||||
support: Some(false),
|
support: Some(false),
|
||||||
|
vision: false,
|
||||||
};
|
};
|
||||||
const GLM: Artifact = Artifact {
|
const GLM: Artifact = Artifact {
|
||||||
label: "GLM 5.2 model",
|
label: "GLM 5.2 model",
|
||||||
@@ -55,6 +62,25 @@ const GLM: Artifact = Artifact {
|
|||||||
size: 211_075_856_448,
|
size: 211_075_856_448,
|
||||||
sha256: "a49de64c5020432bdae23de36a423a9660a5621bc0db8d12b66bd8814b07fea0",
|
sha256: "a49de64c5020432bdae23de36a423a9660a5621bc0db8d12b66bd8814b07fea0",
|
||||||
support: Some(false),
|
support: Some(false),
|
||||||
|
vision: false,
|
||||||
|
};
|
||||||
|
const GLM53_FLASH: Artifact = Artifact {
|
||||||
|
label: "GLM 5.3 Flash Q2 model",
|
||||||
|
file_name: "GLM-5.3-Flash-Q2.gguf",
|
||||||
|
repository: GLM53_REPOSITORY,
|
||||||
|
size: 96_505_816_384,
|
||||||
|
sha256: "e81fd6241c6e55a64e1e14e47a3eab61a173fa8d7e4b5c1d1848827119705b32",
|
||||||
|
support: Some(false),
|
||||||
|
vision: false,
|
||||||
|
};
|
||||||
|
const GLM53_FLASH_VISION: Artifact = Artifact {
|
||||||
|
label: "GLM 5.3 Flash vision encoder",
|
||||||
|
file_name: "GLM-5.3-Flash-Vision-Encoder.gguf",
|
||||||
|
repository: GLM53_REPOSITORY,
|
||||||
|
size: 1_127_280_960,
|
||||||
|
sha256: "ae23e14c6979e889051b2e4a39351abcdafb161e18e606fae4d8c40095a4bf3a",
|
||||||
|
support: None,
|
||||||
|
vision: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||||
@@ -66,6 +92,8 @@ pub(crate) enum ModelChoice {
|
|||||||
DeepSeekV4Pro,
|
DeepSeekV4Pro,
|
||||||
#[serde(rename = "glm-5.2")]
|
#[serde(rename = "glm-5.2")]
|
||||||
Glm52,
|
Glm52,
|
||||||
|
#[serde(rename = "glm-5.3-flash")]
|
||||||
|
Glm53Flash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelChoice {
|
impl ModelChoice {
|
||||||
@@ -74,6 +102,7 @@ impl ModelChoice {
|
|||||||
Self::DeepSeekV4Flash0731 => "deepseek-v4-flash-0731",
|
Self::DeepSeekV4Flash0731 => "deepseek-v4-flash-0731",
|
||||||
Self::DeepSeekV4Pro => "deepseek-v4-pro",
|
Self::DeepSeekV4Pro => "deepseek-v4-pro",
|
||||||
Self::Glm52 => "glm-5.2",
|
Self::Glm52 => "glm-5.2",
|
||||||
|
Self::Glm53Flash => "glm-5.3-flash",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,26 +114,40 @@ impl ModelChoice {
|
|||||||
self == Self::DeepSeekV4Flash0731
|
self == Self::DeepSeekV4Flash0731
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_glm(self) -> bool {
|
||||||
|
matches!(self, Self::Glm52 | Self::Glm53Flash)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn supports_glm_mtp(self) -> bool {
|
||||||
|
self.is_glm()
|
||||||
|
}
|
||||||
|
|
||||||
fn main_artifact(self) -> &'static Artifact {
|
fn main_artifact(self) -> &'static Artifact {
|
||||||
match self {
|
match self {
|
||||||
Self::DeepSeekV4Flash0731 => &FLASH_0731,
|
Self::DeepSeekV4Flash0731 => &FLASH_0731,
|
||||||
Self::DeepSeekV4Pro => &PRO,
|
Self::DeepSeekV4Pro => &PRO,
|
||||||
Self::Glm52 => &GLM,
|
Self::Glm52 => &GLM,
|
||||||
|
Self::Glm53Flash => &GLM53_FLASH,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dspark_artifact(self) -> Option<&'static Artifact> {
|
fn dspark_artifact(self) -> Option<&'static Artifact> {
|
||||||
match self {
|
match self {
|
||||||
Self::DeepSeekV4Flash0731 => Some(&FLASH_0731_DSPARK),
|
Self::DeepSeekV4Flash0731 => Some(&FLASH_0731_DSPARK),
|
||||||
Self::DeepSeekV4Pro | Self::Glm52 => None,
|
Self::DeepSeekV4Pro | Self::Glm52 | Self::Glm53Flash => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vision_artifact(self) -> Option<&'static Artifact> {
|
||||||
|
(self == Self::Glm53Flash).then_some(&GLM53_FLASH_VISION)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn artifacts(self, dspark_enabled: bool) -> impl Iterator<Item = &'static Artifact> {
|
fn artifacts(self, dspark_enabled: bool) -> impl Iterator<Item = &'static Artifact> {
|
||||||
[
|
[
|
||||||
Some(self.main_artifact()),
|
Some(self.main_artifact()),
|
||||||
dspark_enabled.then(|| self.dspark_artifact()).flatten(),
|
dspark_enabled.then(|| self.dspark_artifact()).flatten(),
|
||||||
|
self.vision_artifact(),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -115,6 +158,7 @@ impl ModelChoice {
|
|||||||
pub(crate) struct EngineArtifacts {
|
pub(crate) struct EngineArtifacts {
|
||||||
pub(crate) model: PathBuf,
|
pub(crate) model: PathBuf,
|
||||||
pub(crate) support: Option<PathBuf>,
|
pub(crate) support: Option<PathBuf>,
|
||||||
|
pub(crate) vision: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn engine_artifacts(
|
pub(crate) fn engine_artifacts(
|
||||||
@@ -131,6 +175,11 @@ pub(crate) fn engine_artifacts(
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
vision: model.vision_artifact().and_then(|artifact| {
|
||||||
|
artifact
|
||||||
|
.is_installed(model, models_path)
|
||||||
|
.then(|| artifact.path(model, models_path))
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +207,14 @@ pub(crate) fn validate_engine_artifacts(
|
|||||||
"{} is not compatible with the selected {model} checkpoint",
|
"{} is not compatible with the selected {model} checkpoint",
|
||||||
path.display()
|
path.display()
|
||||||
)),
|
)),
|
||||||
|
}?;
|
||||||
|
if let Some(path) = artifacts.vision.as_deref() {
|
||||||
|
model
|
||||||
|
.vision_artifact()
|
||||||
|
.ok_or_else(|| format!("{} is not compatible with {model}", path.display()))?
|
||||||
|
.validate_installed_path(path)?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -167,6 +223,8 @@ pub(crate) enum ManagedArtifactId {
|
|||||||
DeepSeekV4Flash0731Dspark,
|
DeepSeekV4Flash0731Dspark,
|
||||||
DeepSeekV4Pro,
|
DeepSeekV4Pro,
|
||||||
Glm52,
|
Glm52,
|
||||||
|
Glm53Flash,
|
||||||
|
Glm53FlashVision,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManagedArtifactId {
|
impl ManagedArtifactId {
|
||||||
@@ -177,6 +235,7 @@ impl ManagedArtifactId {
|
|||||||
}
|
}
|
||||||
Self::DeepSeekV4Pro => ModelChoice::DeepSeekV4Pro,
|
Self::DeepSeekV4Pro => ModelChoice::DeepSeekV4Pro,
|
||||||
Self::Glm52 => ModelChoice::Glm52,
|
Self::Glm52 => ModelChoice::Glm52,
|
||||||
|
Self::Glm53Flash | Self::Glm53FlashVision => ModelChoice::Glm53Flash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +245,8 @@ impl ManagedArtifactId {
|
|||||||
Self::DeepSeekV4Flash0731Dspark => &FLASH_0731_DSPARK,
|
Self::DeepSeekV4Flash0731Dspark => &FLASH_0731_DSPARK,
|
||||||
Self::DeepSeekV4Pro => &PRO,
|
Self::DeepSeekV4Pro => &PRO,
|
||||||
Self::Glm52 => &GLM,
|
Self::Glm52 => &GLM,
|
||||||
|
Self::Glm53Flash => &GLM53_FLASH,
|
||||||
|
Self::Glm53FlashVision => &GLM53_FLASH_VISION,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,6 +341,7 @@ impl fmt::Display for ModelChoice {
|
|||||||
Self::DeepSeekV4Flash0731 => "DeepSeek V4 Flash 0731",
|
Self::DeepSeekV4Flash0731 => "DeepSeek V4 Flash 0731",
|
||||||
Self::DeepSeekV4Pro => "DeepSeek V4 Pro 0813",
|
Self::DeepSeekV4Pro => "DeepSeek V4 Pro 0813",
|
||||||
Self::Glm52 => "GLM 5.2",
|
Self::Glm52 => "GLM 5.2",
|
||||||
|
Self::Glm53Flash => "GLM 5.3 Flash Q2",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,6 +353,7 @@ struct Artifact {
|
|||||||
size: u64,
|
size: u64,
|
||||||
sha256: &'static str,
|
sha256: &'static str,
|
||||||
support: Option<bool>,
|
support: Option<bool>,
|
||||||
|
vision: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Artifact {
|
impl Artifact {
|
||||||
|
|||||||
+11
-3
@@ -205,7 +205,9 @@ fn verify(
|
|||||||
path.display()
|
path.display()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(support) = artifact.support {
|
if artifact.vision {
|
||||||
|
crate::engine::validate_vision_artifact(path)?;
|
||||||
|
} else if let Some(support) = artifact.support {
|
||||||
crate::engine::validate_model_artifact(path, model, support)?;
|
crate::engine::validate_model_artifact(path, model, support)?;
|
||||||
}
|
}
|
||||||
Ok(DownloadOutcome::Complete)
|
Ok(DownloadOutcome::Complete)
|
||||||
@@ -330,8 +332,8 @@ mod tests {
|
|||||||
Some(ModelChoice::DeepSeekV4Flash0731)
|
Some(ModelChoice::DeepSeekV4Flash0731)
|
||||||
);
|
);
|
||||||
assert!(ModelChoice::from_id("unknown").is_none());
|
assert!(ModelChoice::from_id("unknown").is_none());
|
||||||
assert_eq!(MODEL_CHOICES.len(), 3);
|
assert_eq!(MODEL_CHOICES.len(), 4);
|
||||||
assert_eq!(MANAGED_ARTIFACTS.len(), 4);
|
assert_eq!(MANAGED_ARTIFACTS.len(), 6);
|
||||||
assert_eq!(ModelChoice::Glm52.main_artifact().size, 211_075_856_448);
|
assert_eq!(ModelChoice::Glm52.main_artifact().size, 211_075_856_448);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ModelChoice::DeepSeekV4Flash0731.main_artifact().size,
|
ModelChoice::DeepSeekV4Flash0731.main_artifact().size,
|
||||||
@@ -339,6 +341,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert_eq!(ModelChoice::DeepSeekV4Flash0731.artifacts(true).count(), 2);
|
assert_eq!(ModelChoice::DeepSeekV4Flash0731.artifacts(true).count(), 2);
|
||||||
assert_eq!(ModelChoice::Glm52.artifacts(true).count(), 1);
|
assert_eq!(ModelChoice::Glm52.artifacts(true).count(), 1);
|
||||||
|
assert_eq!(ModelChoice::Glm53Flash.artifacts(true).count(), 2);
|
||||||
|
|
||||||
let id = SystemTime::now()
|
let id = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
@@ -366,6 +369,7 @@ mod tests {
|
|||||||
size: 0,
|
size: 0,
|
||||||
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||||
support: None,
|
support: None,
|
||||||
|
vision: false,
|
||||||
};
|
};
|
||||||
let partial = empty.partial_path(ModelChoice::DeepSeekV4Flash0731, &models_path);
|
let partial = empty.partial_path(ModelChoice::DeepSeekV4Flash0731, &models_path);
|
||||||
fs::create_dir_all(partial.parent().unwrap()).unwrap();
|
fs::create_dir_all(partial.parent().unwrap()).unwrap();
|
||||||
@@ -393,6 +397,7 @@ mod tests {
|
|||||||
&EngineArtifacts {
|
&EngineArtifacts {
|
||||||
model: installed.clone(),
|
model: installed.clone(),
|
||||||
support: None,
|
support: None,
|
||||||
|
vision: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
@@ -404,6 +409,7 @@ mod tests {
|
|||||||
&EngineArtifacts {
|
&EngineArtifacts {
|
||||||
model: installed,
|
model: installed,
|
||||||
support: None,
|
support: None,
|
||||||
|
vision: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
@@ -428,6 +434,7 @@ mod tests {
|
|||||||
size: 3,
|
size: 3,
|
||||||
sha256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
sha256: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||||
support: None,
|
support: None,
|
||||||
|
vision: false,
|
||||||
};
|
};
|
||||||
let verified_bytes = AtomicU64::new(999);
|
let verified_bytes = AtomicU64::new(999);
|
||||||
|
|
||||||
@@ -593,6 +600,7 @@ mod tests {
|
|||||||
size: 10,
|
size: 10,
|
||||||
sha256: "unused",
|
sha256: "unused",
|
||||||
support: None,
|
support: None,
|
||||||
|
vision: false,
|
||||||
};
|
};
|
||||||
let partial = artifact.partial_path(ModelChoice::DeepSeekV4Flash0731, &directory);
|
let partial = artifact.partial_path(ModelChoice::DeepSeekV4Flash0731, &directory);
|
||||||
fs::create_dir_all(partial.parent().unwrap()).unwrap();
|
fs::create_dir_all(partial.parent().unwrap()).unwrap();
|
||||||
|
|||||||
@@ -554,6 +554,12 @@ fn model_alias(id: &str) -> Option<ModelChoice> {
|
|||||||
| "zai/glm-5.2"
|
| "zai/glm-5.2"
|
||||||
| "zai/glm-5.2-chat"
|
| "zai/glm-5.2-chat"
|
||||||
| "zai/glm-5.2-reasoner" => Some(ModelChoice::Glm52),
|
| "zai/glm-5.2-reasoner" => Some(ModelChoice::Glm52),
|
||||||
|
"glm-5.3-flash"
|
||||||
|
| "glm-5.3-flash-chat"
|
||||||
|
| "glm-5.3-flash-reasoner"
|
||||||
|
| "zai/glm-5.3-flash"
|
||||||
|
| "zai/glm-5.3-flash-chat"
|
||||||
|
| "zai/glm-5.3-flash-reasoner" => Some(ModelChoice::Glm53Flash),
|
||||||
_ => ModelChoice::from_id(id),
|
_ => ModelChoice::from_id(id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -900,6 +906,13 @@ mod tests {
|
|||||||
content_text(&json!([{"type": "text", "text": "one"}, " two"])),
|
content_text(&json!([{"type": "text", "text": "one"}, " two"])),
|
||||||
"one two"
|
"one two"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
content_text(&json!([{
|
||||||
|
"type": "image_url",
|
||||||
|
"image_url": {"url": "data:image/png;base64,YQ=="}
|
||||||
|
}])),
|
||||||
|
"<|ds4server_image_data|>data:image/png;base64,YQ==<|/ds4server_image_data|>"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+41
-1
@@ -54,6 +54,23 @@ pub(super) fn anthropic_request(value: Value) -> Result<ChatRequest, (u16, Strin
|
|||||||
for block in blocks {
|
for block in blocks {
|
||||||
match block.get("type").and_then(Value::as_str).unwrap_or("text") {
|
match block.get("type").and_then(Value::as_str).unwrap_or("text") {
|
||||||
"text" => text.push_str(block.get("text").and_then(Value::as_str).unwrap_or("")),
|
"text" => text.push_str(block.get("text").and_then(Value::as_str).unwrap_or("")),
|
||||||
|
"image" => {
|
||||||
|
let source = block.get("source").and_then(Value::as_object);
|
||||||
|
let media = source
|
||||||
|
.and_then(|source| source.get("media_type"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or("");
|
||||||
|
let data = source
|
||||||
|
.and_then(|source| source.get("data"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or("");
|
||||||
|
if !matches!(media, "image/png" | "image/jpeg") || data.is_empty() {
|
||||||
|
return Err((400, "image input must be inline PNG or JPEG base64".into()));
|
||||||
|
}
|
||||||
|
text.push_str(&crate::engine::vision_data_marker(&format!(
|
||||||
|
"data:{media};base64,{data}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
"thinking" | "redacted_thinking" => reasoning.push_str(
|
"thinking" | "redacted_thinking" => reasoning.push_str(
|
||||||
block
|
block
|
||||||
.get("thinking")
|
.get("thinking")
|
||||||
@@ -382,6 +399,24 @@ fn responses_content_text(value: &Value) -> Result<String, (u16, String)> {
|
|||||||
text.push_str(value);
|
text.push_str(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Value::Object(part)
|
||||||
|
if part.get("type").and_then(Value::as_str) == Some("input_image") =>
|
||||||
|
{
|
||||||
|
let url = part
|
||||||
|
.get("image_url")
|
||||||
|
.or_else(|| part.get("url"))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or_else(|| (400, "invalid image input".into()))?;
|
||||||
|
if !url.starts_with("data:image/png;base64,")
|
||||||
|
&& !url.starts_with("data:image/jpeg;base64,")
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
400,
|
||||||
|
"image input must be an inline PNG or JPEG data URI".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
text.push_str(&crate::engine::vision_data_marker(url));
|
||||||
|
}
|
||||||
_ => return Err((400, "invalid JSON request".into())),
|
_ => return Err((400, "invalid JSON request".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -574,7 +609,12 @@ fn request_reasoning(
|
|||||||
|| (explicit_thinking.is_none()
|
|| (explicit_thinking.is_none()
|
||||||
&& matches!(
|
&& matches!(
|
||||||
model_id,
|
model_id,
|
||||||
"deepseek-chat" | "glm-5.2-chat" | "glm-5.2-no-think" | "glm-5.2-nothink"
|
"deepseek-chat"
|
||||||
|
| "glm-5.2-chat"
|
||||||
|
| "glm-5.2-no-think"
|
||||||
|
| "glm-5.2-nothink"
|
||||||
|
| "glm-5.3-flash-chat"
|
||||||
|
| "zai/glm-5.3-flash-chat"
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
reasoning = ReasoningMode::Direct;
|
reasoning = ReasoningMode::Direct;
|
||||||
|
|||||||
+37
-2
@@ -365,6 +365,7 @@ pub(super) fn render_messages(
|
|||||||
|
|
||||||
let mut turns = Vec::<ChatTurn>::new();
|
let mut turns = Vec::<ChatTurn>::new();
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
validate_inline_images(&message.content)?;
|
||||||
let content = content_text(&message.content);
|
let content = content_text(&message.content);
|
||||||
match message.role.as_str() {
|
match message.role.as_str() {
|
||||||
"system" | "developer" => {
|
"system" | "developer" => {
|
||||||
@@ -426,6 +427,30 @@ pub(super) fn render_messages(
|
|||||||
Ok((system, turns))
|
Ok((system, turns))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_inline_images(value: &Value) -> Result<(), (u16, String)> {
|
||||||
|
let Some(parts) = value.as_array() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
for part in parts {
|
||||||
|
let Some(image) = part.get("image_url") else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let url = image
|
||||||
|
.get("url")
|
||||||
|
.or(Some(image))
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or_else(|| (400, "invalid image input".into()))?;
|
||||||
|
if !url.starts_with("data:image/png;base64,") && !url.starts_with("data:image/jpeg;base64,")
|
||||||
|
{
|
||||||
|
return Err((
|
||||||
|
400,
|
||||||
|
"image input must be an inline PNG or JPEG data URI".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn validate_tool_results(
|
pub(super) fn validate_tool_results(
|
||||||
state: &State,
|
state: &State,
|
||||||
messages: &[ApiMessage],
|
messages: &[ApiMessage],
|
||||||
@@ -779,8 +804,18 @@ pub(super) fn content_text(value: &Value) -> String {
|
|||||||
Value::Array(parts) => parts
|
Value::Array(parts) => parts
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|part| match part {
|
.filter_map(|part| match part {
|
||||||
Value::String(text) => Some(text.as_str()),
|
Value::String(text) => Some(text.clone()),
|
||||||
Value::Object(object) => object.get("text").and_then(Value::as_str),
|
Value::Object(object) => object
|
||||||
|
.get("text")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.map(str::to_owned)
|
||||||
|
.or_else(|| {
|
||||||
|
let url = object
|
||||||
|
.get("image_url")
|
||||||
|
.and_then(|value| value.get("url").or(Some(value)))
|
||||||
|
.and_then(Value::as_str)?;
|
||||||
|
Some(crate::engine::vision_data_marker(url))
|
||||||
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|||||||
+8
-8
@@ -34,8 +34,8 @@ impl SpeculativePreferences {
|
|||||||
if self.glm_mtp_timing && !self.glm_mtp {
|
if self.glm_mtp_timing && !self.glm_mtp {
|
||||||
return Err("GLM MTP timing requires GLM MTP.".into());
|
return Err("GLM MTP timing requires GLM MTP.".into());
|
||||||
}
|
}
|
||||||
if model != ModelChoice::Glm52 && (self.glm_mtp || self.glm_mtp_timing) {
|
if !model.supports_glm_mtp() && (self.glm_mtp || self.glm_mtp_timing) {
|
||||||
return Err("GLM MTP is available only for GLM 5.2.".into());
|
return Err("GLM MTP is available only for GLM models.".into());
|
||||||
}
|
}
|
||||||
if self.dspark_enabled && !model.supports_dspark() {
|
if self.dspark_enabled && !model.supports_dspark() {
|
||||||
return Err("DSpark is not available for the selected model.".into());
|
return Err("DSpark is not available for the selected model.".into());
|
||||||
@@ -166,8 +166,8 @@ impl SsdPreferences {
|
|||||||
{
|
{
|
||||||
return Err("SSD full-layer count is too large.".into());
|
return Err("SSD full-layer count is too large.".into());
|
||||||
}
|
}
|
||||||
if self.full_layers.is_some_and(|layers| layers > 0) && model != ModelChoice::Glm52 {
|
if self.full_layers.is_some_and(|layers| layers > 0) && !model.is_glm() {
|
||||||
return Err("Fully resident SSD layers are available only for GLM 5.2.".into());
|
return Err("Fully resident SSD layers are available only for GLM models.".into());
|
||||||
}
|
}
|
||||||
if let Some(StreamingCacheBudget::Gib(gib)) = self.cache {
|
if let Some(StreamingCacheBudget::Gib(gib)) = self.cache {
|
||||||
validate_gib("SSD cache budget", gib)?;
|
validate_gib("SSD cache budget", gib)?;
|
||||||
@@ -443,12 +443,12 @@ impl ExecutionPreferences {
|
|||||||
{
|
{
|
||||||
return Err("Prefill chunk is too large.".into());
|
return Err("Prefill chunk is too large.".into());
|
||||||
}
|
}
|
||||||
if model == ModelChoice::Glm52 {
|
if model.is_glm() {
|
||||||
if self.power_percent.is_some_and(|power| power != 100) {
|
if self.power_percent.is_some_and(|power| power != 100) {
|
||||||
return Err("GLM 5.2 currently requires 100% GPU power.".into());
|
return Err("GLM currently requires 100% GPU power.".into());
|
||||||
}
|
}
|
||||||
if self.prefill_chunk.is_some() {
|
if self.prefill_chunk.is_some() {
|
||||||
return Err("GLM 5.2 selects its prefill chunk automatically.".into());
|
return Err("GLM selects its prefill chunk automatically.".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -569,7 +569,7 @@ impl GenerationPreferences {
|
|||||||
model: ModelChoice,
|
model: ModelChoice,
|
||||||
kv_cache: KvCacheSettings,
|
kv_cache: KvCacheSettings,
|
||||||
) -> TurnSettings {
|
) -> TurnSettings {
|
||||||
let glm = model == ModelChoice::Glm52;
|
let glm = model.is_glm();
|
||||||
TurnSettings {
|
TurnSettings {
|
||||||
kv_cache,
|
kv_cache,
|
||||||
context_tokens: self.context_tokens,
|
context_tokens: self.context_tokens,
|
||||||
|
|||||||
Reference in new issue
Block a user