Appearance
Part 3: 代码走读
1. 公共 API 与权重结构
追踪 1:权重结构层次
三级结构
ds4_weights 全局模型权重
├── token_embd 词嵌入矩阵 [DS4_N_EMBD, DS4_N_VOCAB]
├── output_hc_base/fn/scale 超连接输出头(3 个张量)
├── output_norm 输出层 RMSNorm 权重
├── output 最终输出投影
└── layer[43] 43 层,每层一个 ds4_layer_weights
ds4_layer_weights 单层权重(行 1987-2023)
├── HC 注意力控制
│ ├── hc_attn_fn/scale/base 超连接参数(3 个张量)
├── 注意力机制
│ ├── attn_norm 注意力 RMSNorm
│ ├── attn_q_a / attn_q_a_norm / attn_q_b 两级低秩 Q 投影
│ ├── attn_kv / attn_kv_a_norm KV 投影 + Norm
│ ├── attn_sinks Sink 注意力参数
│ ├── attn_output_a / attn_output_b 两级低秩输出投影
│ ├── attn_compressor_* KV 压缩器(条件绑定)
│ └── indexer_* 索引器(仅 ratio==4 层)
├── HC FFN 控制
│ └── hc_ffn_fn/scale/base FFN 超连接参数
└── FFN / MoE
├── ffn_norm FFN RMSNorm
├── ffn_gate_tid2eid Hash 路由表(仅 Hash 层)
├── ffn_gate_inp 路由器权重
├── ffn_exp_probs_b 专家概率偏置(可选)
├── ffn_gate/up/down_exps 256 个专家权重
└── ffn_gate/up/down_shexp 共享专家权重
ds4_mtp_weights 多 token 预测头(行 2035-2045)
├── e_proj / h_proj 嵌入/隐藏投影
├── enorm / hnorm / norm 归一化层
├── hc_head_base/fn/scale 超连接头
└── block 一整套 ds4_layer_weights追踪 2:权重绑定过程
调用链
ds4_engine_open() [行 16950]
└── weights_bind(&e->weights, &e->model) [行 2578]
│
├── memset(w, 0, sizeof(*w)) 清零结构体
│
├── 绑定顶层张量(按 GGUF 名称查找)
│ required_tensor(m, "token_embd.weight") → w->token_embd
│ required_tensor(m, "output_hc_base.weight") → w->output_hc_base
│ required_tensor(m, "output_norm.weight") → w->output_norm
│ required_tensor(m, "output.weight") → w->output
│
├── for il = 0..42: 逐层绑定
│ compress_ratio = ds4_layer_compress_ratio(il)
│
│ 绑定通用注意力张量:
│ required_tensorf("blk.%d.attn_norm.weight", il)
│ required_tensorf("blk.%d.attn_q_a.weight", il)
│ ...
│
│ if (compress_ratio != 0): 压缩层额外绑定
│ required_tensorf("blk.%d.attn_compressor_kv.weight", il)
│
│ if (compress_ratio == 4): 索引器层额外绑定
│ required_tensorf("blk.%d.indexer_proj.weight", il)
│
│ ffn_exp_probs_b = tensor_by_namef(...) ← 可选!
│
│ if (il < DS4_N_HASH_LAYER): Hash 路由层绑定
│ required_tensorf("blk.%d.ffn_gate_tid2eid.weight", il)
│
└── weights_validate_layout(w) [行 2285] 验证所有张量维度条件绑定的内存布局
Layer 0-28 Layer 29-36 Layer 37-42
compress_ratio: 0 4 8
┌──────────┐ ┌──────────┐ ┌──────────┐
attn_norm │ ✓ │ │ ✓ │ │ ✓ │
attn_q_a/b │ ✓ │ │ ✓ │ │ ✓ │
compressor_kv │ │ │ ✓ │ │ ✓ │
indexer_proj │ │ │ ✓ │ │ │
ffn_gate_tid2eid │ ✓ │ │ │ │ │
└──────────┘ └──────────┘ └──────────┘追踪 3:Engine / Session 生命周期
Engine 初始化
c
// 行 16950: 引擎打开
int ds4_engine_open(ds4_engine **out, const ds4_engine_options *opt) {
ds4_engine *e = xcalloc(1, sizeof(*e));
e->fd = -1; // 文件描述哨兵
e->backend = opt->backend; // CPU / Metal / CUDA
e->mtp_draft_tokens = CLAMP(opt->mtp_draft_tokens, 1, 16);
e->mtp_margin = opt->mtp_margin < 0 ? 3.0f : opt->mtp_margin;
// 核心加载流程
model_open(&e->model, opt->model_path, ...); // mmap GGUF
vocab_load(&e->vocab, &e->model); // 分词器
config_validate_model(&e->model); // 参数校验
weights_bind(&e->weights, &e->model); // 指针绑定
// GPU 后端初始化
if (backend == DS4_BACKEND_METAL) {
ds4_gpu_init();
ds4_gpu_set_model_map(e->model.map, e->model.size);
}
}Session 创建与释放
ds4_session_create(&sess, engine, ctx_size) [行 17094]
│
├── CPU 路径:
│ kv_cache_init(&s->cache, ctx_size) 分配 KV 缓存
│ cpu_decode_scratch_init(&s->scratch) 分配解码暂存区
│ s->logits = xmalloc(DS4_N_VOCAB * sizeof(float)) 129280 × 4 字节
│
└── GPU 路径:
metal_graph_alloc_raw_cap(...) 分配 Metal 计算图
s->logits = ds4_gpu_tensor_alloc(...)
ds4_session_free(s) [行 17142]
├── kv_cache_free / scratch_free (CPU)
├── ds4_gpu_graph_free (GPU)
├── free(s->checkpoint.data)
├── free/ds4_gpu_tensor_free(s->logits)
└── free(s)追踪 4:Session 同步与前缀复用
同步策略
c
// 行 17201: 将 session 状态推进到指定 prompt
int ds4_session_sync(ds4_session *s, const ds4_tokens *prompt, ...) {场景 1: 完全匹配(checkpoint 是 prompt 的前缀)
checkpoint: [A, B, C] 已处理 3 个 token
prompt: [A, B, C, D, E, F] 需要 6 个 token
→ 增量计算 D, E, F
→ 如果 suffix 长度 <= 8: 逐 token decode
→ 如果 suffix 长度 > 8: 批量 prefill
场景 2: 无匹配
checkpoint: [A, B, C]
prompt: [X, Y, Z]
→ 丢弃 checkpoint
→ 从头 prefill [X, Y, Z]公共前缀查找
c
// 行 17397: O(n) 线性比较
int ds4_session_common_prefix(ds4_session *s, const ds4_tokens *prompt) {
if (!s->checkpoint.data) return 0; // 无缓存,直接返回 0
int n = MIN(s->checkpoint.len, prompt->len);
for (int i = 0; i < n; i++) {
if (s->checkpoint.data[i] != prompt->data[i]) return i; // 不匹配
}
return n; // 全部匹配
}改写验证
c
// 行 17360: 从公共前缀处改写
ds4_session_rewrite_result ds4_session_rewrite_from_common(
ds4_session *s, const ds4_tokens *prompt, int common, ...) {
// 验证前 common 个 token 确实一致
for (int i = 0; i < common; i++) { ... }
// 如果 common == checkpoint.len → 可以直接 ds4_session_sync
// 否则检查是否需要重建 KV cache
}追踪 5:公共采样 API
一行代理模式
c
// 行 17405: 贪心采样
int ds4_session_argmax(ds4_session *s) {
return sample_argmax(s->logits, DS4_N_VOCAB);
}
// 行 17424: 带参采样
int ds4_session_sample(ds4_session *s,
float temperature, int top_k, float top_p, float min_p, uint64_t *rng) {
return sample_top_p_min_p(s->logits, DS4_N_VOCAB,
temperature, top_k, top_p, min_p, rng);
}API 调用层次
用户代码
│
├── ds4_session_argmax(s)
│ └── sample_argmax(s->logits, 129280) [行 14874]
│ └── 线性扫描最大值
│
└── ds4_session_sample(s, temp, k, p, min_p, rng)
└── sample_top_p_min_p(...) [行 15023]
├── temperature <= 0 → sample_argmax 退化为贪心
├── top_k <= 0 → sample_full_vocab 全词表采样
│ ├── 快速路径 (top_p >= 1.0): 无排序
│ └── 排序路径: qsort + 截断
└── top_k > 0 → 栈上插入排序 + min-p + top-p 过滤ds4.h 的设计模式:对外只暴露 ds4_session_argmax 和 ds4_session_sample 两个简单接口,内部自动路由到最优实现。
2. 量化与矩阵运算
追踪 1:量化块结构体
编译期大小验证
c
// 行 157: 利用数组大小为负的 typedef 触发编译错误
#define DS4_STATIC_ASSERT(name, cond) typedef char name[(cond) ? 1 : -1]
// ^^^^ ^^^^
// 条件真 条件假 → 负大小 → 编译失败四种量化块
c
// 行 132: Q2_K — 2-bit 量化,84 字节/块(256 个权重)
typedef struct {
uint8_t scales[QK_K / 16]; // 16 个缩放因子 = 16 字节
uint8_t qs[QK_K / 4]; // 256×2bit/8=64 字节 = 64 字节
uint16_t d; // 块缩放 = 2 字节
uint16_t dmin; // 最小值偏移 = 2 字节
} block_q2_K; // = 84 字节 ✓
// 行 139: Q4_K — 4-bit 量化,144 字节/块
typedef struct {
uint16_t d; // = 2 字节
uint16_t dmin; // = 2 字节
uint8_t scales[12]; // = 12 字节
uint8_t qs[QK_K / 2]; // 256×4bit/8=128 字节
} block_q4_K; // = 144 字节 ✓
// 行 146: Q8_K — 8-bit 量化,292 字节/块
typedef struct {
float d; // = 4 字节
int8_t qs[QK_K]; // 256 字节
int16_t bsums[QK_K / 16]; // 32 字节(用于矩阵乘分块累加)
} block_q8_K; // = 292 字节 ✓
// 行 152: IQ2_XXS — 超低比特,66 字节/块
typedef struct {
uint16_t d; // = 2 字节
uint16_t qs[QK_K / 8]; // 256/8=32 个索引 × 2 字节 = 64 字节
} block_iq2_xxs; // = 66 字节 ✓每个 Q2_K 字节的权重存储
qs[0] = 0b11_10_01_00 ← 4 个 2-bit 权重打包在 1 字节中
^^ ^^ ^^ ^^
v3 v2 v1 v0 值 0-3,需要 scales 和 d/dmin 解码为实际浮点值追踪 2:F16 转换与 FP8 量化
IEEE 754 半精度转单精度
c
// 行 1514: f16 → f32 纯软件实现(ARM NEON 有硬件加速)
static inline float f16_to_f32(uint16_t h) {
// ARM 路径: vcvt_f32_f16 硬件指令,1 周期
// 标量路径: 手动位操作
const uint32_t sign = (h >> 15) & 1; // 最高位
const uint32_t exp = (h >> 10) & 0x1f; // 5 位指数
const uint32_t mant = h & 0x3ff; // 10 位尾数
// 非正规数处理: exp==0 && mant!=0 → while 循环正规化
}F16 (16 bit): F32 (32 bit):
S EEEEE MMMMMMMMMM S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM
1 5bit 10bit 1 8bit 23bit
指数偏移: F16=15, F32=127
尾数位差: 23 - 10 = 13(需要左移 13 位补齐)FP8 KV 缓存原地量化
c
// 行 1635: E4M3FN 动态量化(FP8 格式)
static void dsv4_fp8_kv_quantize_row_inplace_cpu(
float *x, uint32_t head_dim, uint32_t n_rot) {
const uint32_t n_nope = head_dim - n_rot; // 非 RoPE 部分
// 每 64 个元素一块
for (uint32_t off = 0; off < n_nope; off += 64) {
float amax = 找到块内最大绝对值;
amax = fmaxf(amax, 1.0e-4f); // 防止除零
// 动态缩放: ldexp 形式的 E4M3FN
float scale = ldexpf(1.0f, ceilf(log2f(amax / 448.0f)));
// ^^^^
// E4M3FN 最大可表示值 = 448
for (int i = 0; i < block_size; i++) {
float v = x[off + i] / scale;
v = clamp(v, -448.0f, 448.0f);
x[off + i] = dsv4_e4m3fn_dequant_cpu(v) * scale; // 量化后立即反量化
}
}
}原始 float (32bit) → 量化 FP8 (8bit) → 反量化 float (32bit)
3.14159 0b_0_1000_011 3.125
↑ ↑ ↑
S E4 M3
精度损失不可避免,但节省 4× KV 缓存内存追踪 3:IQ2_XXS 查找表点积
预计算查找表
c
// 行 307: 初始化 256×128×8 的三维查找表
static void iq2xxs_signed_grid_init(void) {
// 第一步: 构建 128 种符号模式
for (int i = 0; i < 128; i++) {
// ksigns_iq2xs[i] → 8 个 ±1 符号
// kmask_iq2xs[i] → 8 位掩码
}
// 第二步: 组合 256 个网格点 × 128 种符号
for (int grid_idx = 0; grid_idx < 256; grid_idx++) {
for (int sign_idx = 0; sign_idx < 128; sign_idx++) {
// iq2xxs_signed_grid[grid_idx][sign_idx][8]
// = iq2xxs_grid[grid_idx] × 符号模式[sign_idx]
}
}
}点积计算(3 路硬件加速)
c
// 行 327: 16 元素对点积
static inline int32_t dot_iq2_pair_16(
const int8_t *grid0, // 第一个网格查表结果(8 个值)
const int8_t *grid1, // 第二个网格查表结果(8 个值)
const int8_t *q8) { // Q8 激活值(16 个 int8)
// ARM NEON + dotprod:
int32x4_t s0 = vdotq_s32(vdupq_n_s32(0), v0, q8_0); // 硬件点积
int32x4_t s1 = vdotq_s32(vdupq_n_s32(0), v1, q8_1);
// ARM NEON 无 dotprod:
int16x8_t p0 = vmull_s8(v0, q8_0); // 8 位乘法
acc = vpaddlq_s16(p0); // 成对加法
// 标量回退:
int32_t sum = 0;
for (int i = 0; i < 8; i++)
sum += grid0[i] * q8[i] + grid1[i] * q8[8 + i];
}IQ2_XXS 反量化流水线:
索引 qs[i] (8-bit) → 查表 iq2xxs_grid[qs[i]] → 8 个 2-bit 值
↓
× 符号模式 → 8 个 int8 值
↓
· Q8 激活 → 点积累加追踪 4:Q8_0 激活量化与 matvec
Q8_0 量化(块大小 32)
c
// 行 3123: 激活值 float → int8 量化
static void quantize_q8_0_activation(
const float *x, int8_t *xq, float *scale, uint64_t n) {
const uint64_t blocks = (n + 31) / 32; // 块大小 32(不是 256)
for (uint64_t b = 0; b < blocks; b++) {
// 找块内最大绝对值
float amax = 0.0f;
for (int i = 0; i < 32; i++)
amax = fmaxf(amax, fabsf(x[b * 32 + i]));
float d = amax / 127.0f; // 缩放因子
float id = (d != 0.0f) ? 1.0f / d : 0.0f;
scale[b] = d; // 存储缩放因子
for (int i = 0; i < 32; i++) {
xq[b * 32 + i] = (int8_t)clamp(lrintf(x[b*32+i] * id), -128, 127);
}
}
}Decode 热路径 matvec
c
// 行 3494: 单 token Q8_0 矩阵向量乘
static void matvec_q8_0(float *out, const ds4_model *m,
const ds4_tensor *w, const float *x) {
matvec_q8_0_rows(out, m, w, x, 0, w->dim[1]); // 委托给行范围版本
}
// 行 3510: Decode 热路径(零分配)
static void matvec_q8_0_decode_scratch(
float *out, const ds4_model *m, const ds4_tensor *w,
const float *x, ds4_cpu_decode_scratch *scratch) {
cpu_decode_quantize_q8_0(scratch, x, in_dim); // 量化到预分配暂存区
matvec_q8_0_prequant(out, m, w, scratch); // 用预量化结果做 matvec
}Decode 每层的 matvec 调用:
float x[4096] → quantize_q8_0 → int8 xq[4096] + float scale[128]
│
▼ matvec_q8_0_prequant
│
float out[dim] ← Q8_0 权重矩阵 × Q8_0 激活 ← 权重已在模型文件中预量化追踪 5:Q2_K / IQ2_XXS 专家 matvec
IQ2_XXS 专家对投影
c
// 行 3775: 同时计算一个专家的 gate 和 up 投影
static void matvec_iq2_xxs_expert_pair_prequant(
float *out0, float *out1, // gate 输出和 up 输出
const ds4_model *m,
const ds4_tensor *w0, *w1, // gate 和 up 权重
const block_q8_K *xq, // 共享的 Q8_K 激活
uint32_t expert) { // 专家编号
// 验证两个张量同为 IQ2_XXS 类型
// 计算专家偏移: base + expert * expert_bytes
// 填充 pair_ctx,调用并行内核
}Q2_K 专家 Down 投影
c
// 行 3909: 单专家 Q2_K 下投影(诊断/追踪用)
static void matvec_q2_k_expert(
float *out, const ds4_model *m,
const ds4_tensor *w, const float *x,
uint32_t expert) {
// 注: 这个版本内部做 Q8_K 量化(xmalloc),不在热路径
block_q8_K *xq = xmalloc(...); // 临时分配
ds4_quantize_row_q8_K(x, xq, in_dim); // 量化激活
ds4_parallel_for(out_dim, matvec_q2_k_worker, &ctx);
free(xq); // 释放临时缓冲
}MoE matvec 数据流
输入激活 float x[in_dim]
│
▼ quantize Q8_K(一次)
│
xq[QK_K 块...] ← 所有 6 个专家共享
│
├── 专家 0: matvec_iq2_xxs_expert_pair(w_gate[0], w_up[0], xq)
│ → gate[0][mid_dim], up[0][mid_dim]
│
├── 专家 1: matvec_iq2_xxs_expert_pair(w_gate[1], w_up[1], xq)
│ → gate[1][mid_dim], up[1][mid_dim]
│
├── ... (共 6 个专家)
│
▼ SwiGLU: mid[i] = silu(gate[i]) * up[i]
│
▼ quantize Q8_K(per-expert)
│
├── 专家 0: matvec_q2_k(w_down[0], midq[0]) → out += weight[0]
├── 专家 1: matvec_q2_k(w_down[1], midq[1]) → out += weight[1]
└── ...
│
▼ 加权累加
│
float out[out_dim] = Σ weight[i] * expert_down[i]3. 注意力机制
追踪 1:RMS 归一化
三种 RMSNorm 变体
c
// `rms_norm_no_weight()` 中(约 2700 行): 无权重(用于 HC 控制向量)
static void rms_norm_no_weight(float *out, const float *x,
uint64_t n, float eps) {
double ss = 0.0; // double 精度累加
for (uint64_t i = 0; i < n; i++) ss += (double)x[i] * x[i];
float scale = 1.0f / sqrtf((float)(ss / n) + eps);
for (uint64_t i = 0; i < n; i++) out[i] = x[i] * scale;
}
// `rms_norm_weight()` 中(约 2709 行): 带可学习权重(用于层归一化)
static void rms_norm_weight(float *out, const float *x,
const float *weight, uint64_t n, float eps) {
// ... 同上计算 scale ...
for (uint64_t i = 0; i < n; i++)
out[i] = x[i] * scale * weight[i]; // 逐通道乘以学习到的 γ
}
// `head_rms_norm_inplace()` 中(约 2718 行): 单头原地归一化(用于 Q/KV 投影后)
static void head_rms_norm_inplace(float *x, uint32_t n_head,
uint32_t head_dim, float eps) {
for (uint32_t h = 0; h < n_head; h++) {
float *xh = x + (uint64_t)h * head_dim;
rms_norm_no_weight(xh, xh, head_dim, eps); // 每头独立归一化
}
}RMSNorm 公式: out[i] = x[i] / sqrt(mean(x²) + ε) × weight[i]
LayerNorm vs RMSNorm:
LayerNorm: (x - μ) / sqrt(σ² + ε) × γ + β ← 减均值、有偏移
RMSNorm: x / sqrt(mean(x²) + ε) × γ ← 不减均值、无偏移
RMSNorm 省略了均值计算和偏移参数,计算更简单,推理更快追踪 2:HC 头压缩(MLA)
Sinkhorn 分解
c
// `hc_split_sinkhorn_one()` 中(约 4186 行): 将混合向量分解为 pre/post/comb 三个输出
static void hc_split_sinkhorn_one(
float *out, // 输出: [2*n_hc + n_hc*n_hc] 元素
const float *mix, // 输入: 混合系数
const float *scale, // [pre_scale, post_scale, comb_scale]
const float *base, // 基础偏置
int n_hc, int iters, float eps) {
// Sigmoid 生成 pre 权重: 1/(1 + exp(-z)),z = mix[i]*pre_scale + base[i]
// 双重归一化的 comb 矩阵(Sinkhorn 迭代)
}HC Pre / Post 操作
c
// `hc_pre_from_state_one()` 中(约 4317 行): 从隐藏状态计算 HC 控制向量
static void hc_pre_from_state_one(
const ds4_model *model,
const ds4_tensor *fn, // 投影权重
const ds4_tensor *scale_tensor, // 缩放参数
const ds4_tensor *base_tensor, // 基础偏置
const float *residual_hc, // HC 残差状态
float *out, float *post, float *comb) {
// 分配暂存区,调用 hc_pre_from_state_one_scratch
// 输出: pre 权重、post 门控、comb 组合矩阵
}
// `hc_post_one()` 中(约 4366 行): 将子层输出与 HC 残差合并
static void hc_post_one(
float *out_hc, const float *block_out,
const float *residual_hc, const float *post,
const float *comb, uint32_t n_embd, uint32_t n_hc) {
for (uint32_t dst = 0; dst < n_hc; dst++) {
for (uint32_t d = 0; d < n_embd; d++) {
out_hc[dst * n_embd + d] = block_out[d] * post[dst];
for (uint32_t src = 0; src < n_hc; src++) {
out_hc[dst*n_embd + d] += comb[dst + src*n_hc]
* residual_hc[src*n_embd + d];
}
}
}
}HC(Hyper-Connection)数据流:
residual_hc[n_hc × n_embd]
│
▼ hc_pre_from_state_one
│
├── pre[n_hc] ← 每个子层输入的门控
├── post[n_hc] ← 每个子层输出的门控
└── comb[n_hc×n_hc] ← 子层间的路由矩阵
│
▼ 子层计算(注意力或 FFN)
│
block_out[n_embd]
│
▼ hc_post_one
│
out_hc[dst] = block_out × post[dst]
+ Σ comb[dst,src] × residual_hc[src]追踪 3:Q / KV 投影
两级低秩 Q 投影
c
// `layer_q_projection_normed_one()` 中(约 4596 行): Q = norm(proj_b(norm(proj_a(x))))
static void layer_q_projection_normed_one(
const ds4_model *model, const ds4_layer_weights *layer,
const float *norm, float *q) {
// 第一级: 4096 → 1024(低秩压缩)
float qr[1024]; // 中间维度
matvec_q8_0(qr, model, layer->attn_q_a, norm);
// 中间归一化
rms_norm_weight(qr, qr, layer->attn_q_a_norm, 1024, DS4_RMS_EPS);
// 第二级: 1024 → 64×512 = 32768(展开到所有头)
matvec_q8_0(q, model, layer->attn_q_b, qr);
// 头级归一化
head_rms_norm_inplace(q, DS4_N_HEAD, DS4_N_HEAD_DIM, DS4_RMS_EPS);
}KV 投影(单头)
c
// `layer_kv_projection_normed_one()` 中(约 4633 行): KV = norm(proj(x)),输出维度 = DS4_N_HEAD_DIM = 512
static void layer_kv_projection_normed_one(
const ds4_model *model, const ds4_layer_weights *layer,
const float *normed, float *kv) {
float raw[DS4_N_HEAD_DIM]; // 512
matvec_q8_0(raw, model, layer->attn_kv, normed);
rms_norm_weight(kv, raw, layer->attn_kv_a_norm,
DS4_N_HEAD_DIM, DS4_RMS_EPS);
}DeepSeek V4 的 GQA (Grouped Query Attention):
Q: 64 头 × 512 维 = 32768 维
KV: 1 头 × 512 维 = 512 维
Q 投影: 4096 → 1024 → 32768 (两级低秩)
KV 投影: 4096 → 512 (单级)
所有 64 个 Q 头共享 1 组 KV → GQA 64:1
KV 体积减小 64×,是 MLA 的核心优化追踪 4:单 token 注意力计算
Sink-Aware 注意力
c
// `layer_attention_rows_one()` 中(约 4897 行): 带 Sink 的缩放点积注意力
static void layer_attention_rows_one(
float *out_heads, const ds4_model *model,
const ds4_layer_weights *layer,
const float *q, const float *kv_rows, uint32_t n_kv) {
for (uint32_t h = 0; h < DS4_N_HEAD; h++) {
const float *qh = q + (uint64_t)h * DS4_N_HEAD_DIM; // 当前头的 Q
float *oh = out_heads + (uint64_t)h * DS4_N_HEAD_DIM; // 当前头输出
// 加载 Sink(可学习的注意力汇聚点)
const float sink_logit = tensor_data(layer->attn_sinks, ...)[h];
float scores[512]; // 栈缓冲(n_kv <= 512 时)
// 或堆分配(n_kv > 512 时)
float max_score = sink_logit; // Sink 参与最大值计算
// 计算注意力分数
for (uint32_t r = 0; r < n_kv; r++) {
scores[r] = dot_f32(qh, kv_rows + r * DS4_N_HEAD_DIM,
DS4_N_HEAD_DIM) / sqrtf(DS4_N_HEAD_DIM);
if (scores[r] > max_score) max_score = scores[r];
}
// Softmax
float sum = expf(sink_logit - max_score); // Sink 贡献分母但不贡献分子
for (uint32_t r = 0; r < n_kv; r++) {
scores[r] = expf(scores[r] - max_score);
sum += scores[r];
}
float inv_sum = 1.0f / sum;
// 加权求值(Sink 不贡献值向量)
memset(oh, 0, DS4_N_HEAD_DIM * sizeof(float));
for (uint32_t r = 0; r < n_kv; r++) {
axpy_f32(scores[r] * inv_sum,
kv_rows + r * DS4_N_HEAD_DIM, oh, DS4_N_HEAD_DIM);
}
}
}Sink-Aware Softmax:
标准: softmax(scores) = exp(score) / Σ exp(score)
Sink: 分母 = exp(sink) + Σ exp(score)
分子 = 仅 Σ score[r] × value[r]
(sink 不对应任何 value 向量)
效果: sink 作为"虚拟 token"吸收多余的注意力权重,
防止首个真实 token 被过度关注(StreamingLLM 的核心思想)追踪 5:KV 缓存管理与混合注意力
KV 缓存写入
c
// `kv_cache_push_raw()` 中(约 6307 行): 原始 SWA(滑动窗口注意力)缓存
static void kv_cache_push_raw(ds4_layer_cache *cache, const float *kv) {
if (cache->n_raw < cache->raw_cap) {
// 未满: 直接追加,F16 精度舍入
float *dst = cache->raw + (uint64_t)cache->n_raw * DS4_N_HEAD_DIM;
for (int i = 0; i < DS4_N_HEAD_DIM; i++)
dst[i] = f16_to_f32(f32_to_f16(kv[i])); // float → f16 → float
} else {
// 已满: 滑动窗口,最老的行被丢弃
memmove(cache->raw, cache->raw + DS4_N_HEAD_DIM,
(cache->n_raw - 1) * DS4_N_HEAD_DIM * sizeof(float));
// 新行写到末尾
}
cache->n_raw++;
}
// `kv_cache_push_comp()` 中(约 6322 行): 压缩缓存(追加式,无滑动窗口)
static void kv_cache_push_comp(float *rows, uint32_t *n_rows,
uint32_t cap_rows, uint32_t row_dim,
const float *kv) {
// 超过容量直接报错(压缩缓存不支持丢弃)
rows[(*n_rows)++] = kv[...]; // 同样 F16 舍入
}混合注意力
c
// `layer_attention_mixed_one()` 中(约 6613 行): 同时在 raw 和 comp 缓存上做注意力
static void layer_attention_mixed_one(
float *out_heads, const ds4_model *model,
const ds4_layer_weights *layer,
const float *q,
const float *raw_kv, uint32_t n_raw,
const float *comp_kv, uint32_t n_comp,
const bool *comp_allowed) { // 哪些 comp 行可参与
const uint32_t n_total = n_raw + n_comp;
// 先算 raw 行的分数
for (uint32_t r = 0; r < n_raw; r++) { ... }
// 再算 comp 行的分数(跳过 comp_allowed[r]==false 的行)
for (uint32_t r = 0; r < n_comp; r++) {
if (!comp_allowed[r]) continue; // ratio-4 层的间歇性压缩
...
}
}混合注意力内存布局:
┌─────────────────────── Raw SWA 缓存 ──────────────────────┐
│ [tok_0] [tok_1] ... [tok_W-1] ← 滑动窗口,最多 W 行 │
└──────────────────────────────────────────────────────────┘
+
┌─────────────────────── Compressed 缓存 ──────────────────┐
│ [comp_0] [comp_1] ... [comp_N] ← 累积压缩,最多 N 行 │
└──────────────────────────────────────────────────────────┘
总注意力行数 = n_raw + n_comp
Raw 保留最近 W 个 token 的精确 KV
Comp 存储更早的 token 的压缩表示
两者在同一个 softmax 中统一计算4. MoE 混合专家
追踪 1:SiLU / Softplus / SwiGLU 激活
三种激活函数
c
// 行 5012: SiLU (Sigmoid Linear Unit) = x × σ(x)
static float silu(float x) {
return x * sigmoid_stable(x);
}
// 行 5016: 数值稳定的 Softplus = log(1 + exp(x))
static float softplus_stable(float x) {
if (x > 20.0f) return x; // 大正数: log(1+exp(x)) ≈ x
if (x < -20.0f) return expf(x); // 大负数: log(1+exp(x)) ≈ exp(x)
return log1pf(expf(x)); // 中间区间: 精确计算
}
// 行 5022: SwiGLU = SiLU(gate) × up
static void swiglu(float *out, const float *gate,
const float *up, uint64_t n) {
for (uint64_t i = 0; i < n; i++) {
out[i] = silu(gate[i]) * up[i];
}
}SiLU 图形:
| /
3 | /
2 | /
1 | /
0 ----+--------
-1 | \ ← x < 0 时有轻微负输出("自门控"特性)
|
+---------------→ x
SwiGLU 数据流:
gate[i] → SiLU → × up[i] → out[i]
↑
门控信号 信息流追踪 2:共享 FFN(密集前馈)
单 token 共享专家
c
// 行 5029: 共享专家的完整前馈
static void layer_shared_ffn_one(
float *out, const ds4_model *model,
const ds4_layer_weights *layer, const float *x) {
// gate 和 up 投影同时计算
float *gate = xmalloc(DS4_N_FF_EXP * sizeof(float));
float *up = xmalloc(DS4_N_FF_EXP * sizeof(float));
float *mid = xmalloc(DS4_N_FF_EXP * sizeof(float));
// 激活量化 + pair matvec(gate 和 up 共享量化激活)
quantize_q8_0_activation(x, xq, scale, in_dim);
matvec_q8_0_pair_prequant(gate, up, model,
layer->ffn_gate_shexp,
layer->ffn_up_shexp, xq, scale);
// SwiGLU 激活
swiglu(mid, gate, up, DS4_N_FF_EXP);
// Down 投影
matvec_q8_0(out, model, layer->ffn_down_shexp, mid);
free(gate); free(up); free(mid);
}Decode 热路径(零分配)
c
// 行 5063: 使用预分配暂存区的版本
static void layer_shared_ffn_one_decode_scratch(
float *out, const ds4_model *model,
const ds4_layer_weights *layer,
const float *x, ds4_cpu_decode_scratch *scratch) {
// 所有中间缓冲区从 scratch 中分配,无 malloc
}批量并行 SwiGLU
c
// 行 5094: 批量 worker
static void swiglu_batch_worker(void *vctx, uint64_t t0, uint64_t t1) {
for (uint64_t t = t0; t < t1; t++)
swiglu(mid + t * DS4_N_FF_EXP,
gate + t * DS4_N_FF_EXP,
up + t * DS4_N_FF_EXP, DS4_N_FF_EXP);
}追踪 3:Hash 路由与 Top-K 选择
Hash 查表路由
c
// 行 5148: 直接查表获取被选专家
static void layer_hash_selected_experts(
int selected[DS4_N_EXPERT_USED], // 输出: 6 个专家 ID
const ds4_model *model,
const ds4_layer_weights *layer,
int token) { // 当前 token ID
// ffn_gate_tid2eid 是预计算的 [DS4_N_EXPERT_USED, n_vocab] 查找表
// selected[i] = table[i * n_vocab + token]
// 无需实时计算路由,O(1) 查表
}路由概率计算
c
// 行 5169: 基于路由器的概率
static void layer_router_probs_one(
float probs[DS4_N_EXPERT], // 256 个专家的概率
const ds4_model *model,
const ds4_layer_weights *layer,
const float *x) {
float logits[DS4_N_EXPERT];
matvec_q8_0(logits, model, layer->ffn_gate_inp, x);
for (int i = 0; i < DS4_N_EXPERT; i++)
probs[i] = sqrtf(softplus_stable(logits[i]));
// ^^^^^^^ ^^^^^^^^^^^^
// 开方使分布更尖锐 softplus 保证非负
}插入排序 Top-K
c
// 行 5211: O(n×k) 的插入排序 top-k
static void topk_desc(const float *score, int n, int k, int *idx) {
for (int i = 0; i < k; i++) idx[i] = -1; // 初始化
for (int i = 0; i < n; i++) { // 扫描 n=256 个
for (int j = 0; j < k; j++) { // 与当前 top-k 比较
if (idx[j] < 0 || score[i] > score[idx[j]]) {
for (int m = k - 1; m > j; m--) // 后移
idx[m] = idx[m - 1];
idx[j] = i; // 插入
break;
}
}
}
}Top-K 选择过程 (n=256, k=6):
score[0] = 0.3 → idx = [0, -1, -1, -1, -1, -1]
score[1] = 0.8 → idx = [1, 0, -1, -1, -1, -1]
score[2] = 0.1 → idx 不变(太小)
score[3] = 0.5 → idx = [1, 3, 0, -1, -1, -1]
...
score[255] = 0.9 → idx = [255, 1, 3, 7, 42, 0]
最终选中 6 个专家: [255, 1, 3, 7, 42, 0]追踪 4:路由 MoE 完整流水线
单 token MoE
c
// 行 5278: 路由 MoE 的核心
static void layer_routed_moe_one(
float *out, const ds4_model *model,
const ds4_layer_weights *layer,
const float *x, uint32_t il, int token,
float clamp, bool trace) {
// 1. 路由选择
if (layer->ffn_gate_tid2eid) {
layer_hash_selected_experts(selected, model, layer, token); // Hash 查表
layer_hash_router_weights_one(weight, model, layer, x); // 计算权重
} else {
layer_topk_selected_experts(selected, weight, model, layer, x); // Top-K 选择
}
// 2. 快速路径: 所有专家的 gate+up 一起计算
matvec_iq2_xxs_experts_mid_prequant(
gate_all, up_all, model, layer,
selected, weight, xq, clamp);
// 3. SwiGLU 激活(逐专家)
for (int e = 0; e < DS4_N_EXPERT_USED; e++)
swiglu(mid + e * DS4_N_FF_EXP,
gate + e * DS4_N_FF_EXP,
up + e * DS4_N_FF_EXP, DS4_N_FF_EXP);
// 4. 量化 mid 并做 down 投影
ds4_quantize_row_q8_K(mid, midq, ...);
matvec_q2_k_experts_accum_prequant(
out, model, layer, selected, weight, midq);
}MoE 数据流
输入 x[in_dim]
│
├─→ 路由选择 → selected[6], weight[6]
│
├─→ Q8_K 量化 x → xq
│
├── 专家 0: gate[0],up[0] = IQ2_XXS_matvec(w_gate[0],w_up[0],xq)
├── 专家 1: gate[1],up[1] = IQ2_XXS_matvec(w_gate[1],w_up[1],xq)
├── ...
├── 专家 5: gate[5],up[5] = IQ2_XXS_matvec(w_gate[5],w_up[5],xq)
│
├── mid[0] = SwiGLU(gate[0], up[0])
├── mid[1] = SwiGLU(gate[1], up[1])
├── ...
│
├── Q8_K 量化 mid → midq
│
├── out += weight[0] × Q2_K_matvec(w_down[0], midq[0])
├── out += weight[1] × Q2_K_matvec(w_down[1], midq[1])
├── ...
└── out += weight[5] × Q2_K_matvec(w_down[5], midq[5])追踪 5:FFN 层完整前向
单 token FFN(shared + routed)
c
// 行 5576: 完整的 FFN 层
static void layer_ffn_one(
float *out_hc, const ds4_model *model,
const ds4_layer_weights *layer,
const float *inp_hc, uint32_t il, int token,
const float *steering_dirs, float steering_scale,
bool trace) {
// 1. HC 前置(提取 pre/post/comb 控制向量)
hc_pre_from_state_one(model, fn, scale, base, inp_hc,
pre, post, comb);
// 2. 注意力层 RMSNorm 后的状态
rms_norm_weight(norm, inp_hc, layer->ffn_norm, DS4_N_EMBD, DS4_RMS_EPS);
// 3. 路由 MoE
layer_routed_moe_one(moe, model, layer, norm, il, token, clamp, trace);
// 4. 共享专家
layer_shared_ffn_one(shared, model, layer, norm);
// 5. 合并
for (int i = 0; i < DS4_N_EMBD; i++)
ffn_out[i] = moe[i] + shared[i];
// 6. 可选 steering 投影
if (steering_dirs) { ... }
// 7. HC 后置(将输出写回 HC 状态)
hc_post_one(out_hc, ffn_out, inp_hc, post, comb, n_embd, n_hc);
}FFN 层全貌
inp_hc[n_hc × n_embd] ← HC 残差状态
│
├─→ HC pre → pre, post, comb ← 控制向量
│
├─→ RMSNorm → norm ← 归一化
│
├──→ 路由 MoE(norm) → moe ← 6/256 个专家
│
├──→ 共享 FFN(norm) → shared ← 1 个共享专家
│
├─→ moe + shared → ffn_out ← 合并
│
├─→ [steering projection] ← 可选方向控制
│
└─→ HC post → out_hc ← 写回 HC 状态
DeepSeek V4 FFN 设计: 每层 256 个路由专家 + 1 个共享专家
每个 token 只激活 6 个路由专家
稀疏度 = 1 - 6/256 ≈ 97.7%