Appearance
Part 4: 代码走读
1. RoPE 与推理循环
追踪 1:YaRN 频率缩放
频率修正斜坡函数
c
// 行 4675: 线性衰减斜坡,用于平滑过渡维度区间
static float rope_yarn_ramp(float low, float high, int i0) {
const float y = ((float)(i0 / 2) - low) / fmaxf(0.001f, high - low);
return 1.0f - fminf(1.0f, fmaxf(0.0f, y));
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 当 i0 < 2*low: 返回 1.0(完全应用原始频率)
// 当 i0 > 2*high: 返回 0.0(完全应用缩放频率)
// 中间: 线性过渡
}修正维度范围计算
c
// 行 4680: 计算需要频率修正的维度边界
static float rope_yarn_corr_dim(int n_dims, uint64_t n_ctx_orig,
float n_rot, float base) {
return (float)n_dims * logf((float)n_ctx_orig / (n_rot * 2.0f * (float)M_PI))
/ (2.0f * logf(base));
}
// 行 4684: 计算 [低频修正起点, 低频修正终点]
static void rope_yarn_corr_dims(int n_dims, uint64_t n_ctx_orig,
float freq_base, float beta_fast,
float beta_slow, float dims[2]) {
// dims[0] = rope_yarn_corr_dim(..., beta_fast) ← 快速衰减开始
// dims[1] = rope_yarn_corr_dim(..., beta_slow) ← 慢速衰减结束
}维度索引 vs 频率修正:
维度: 0 2 4 6 ... dims[0] ... dims[1] ... n_rot
频率: 高 ←──────────────────────────────────────→ 低
修正: 无修正 ramp: 1→0 全修正
↑过渡区↑追踪 2:RoPE 尾部旋转应用
逐层频率配置
c
// 行 4745: 压缩层使用不同的 RoPE 频率基数
static float layer_rope_freq_base(uint32_t il) {
return ds4_layer_compress_ratio(il) != 0 && DS4_COMPRESS_ROPE_FREQ_BASE > 0.0f
? DS4_COMPRESS_ROPE_FREQ_BASE // 压缩层: 独立频率
: DS4_ROPE_FREQ_BASE; // 普通层: 全局频率
}
// 行 4751: 压缩层的频率缩放
static float layer_rope_freq_scale(uint32_t il) {
if (ds4_layer_compress_ratio(il) == 0 || DS4_ROPE_SCALE_FACTOR <= 0.0f)
return 1.0f;
return 1.0f / DS4_ROPE_SCALE_FACTOR;
}旋转应用(带 YaRN 扩展)
c
// 行 4758: 对 Q/KV 的尾部维度应用 RoPE
static void rope_tail_layer_inplace(
float *x, uint32_t n_head, uint32_t head_dim,
uint32_t n_rot, uint32_t pos, uint32_t il, bool inverse) {
const bool compressed = ds4_layer_compress_ratio(il) != 0;
const float freq_base = layer_rope_freq_base(il);
const float freq_scale = layer_rope_freq_scale(il);
const float ext_factor = compressed && DS4_ROPE_SCALE_FACTOR > 1.0f
? 1.0f : 0.0f;
// 对每个头的尾部长度应用旋转
for (uint32_t h = 0; h < n_head; h++) {
float *tail = x + (uint64_t)h * head_dim; // 尾部维度
float theta_scale = powf(freq_base, -2.0f / (float)n_rot);
for (uint32_t i = 0; i < n_rot; i += 2) {
float theta = pos * powf(theta_scale, i / 2); // θ = pos × base^(-2i/d)
// YaRN 修正: 根据 ext_factor 和 ramp 混合原始/缩放频率
const float c = cosf(theta) * mscale;
const float s = sin_sign * sinf(theta) * mscale;
// 2D 旋转
float x0 = tail[i + 0], x1 = tail[i + 1];
tail[i + 0] = x0 * c - x1 * s;
tail[i + 1] = x0 * s + x1 * c;
}
}
}RoPE 旋转矩阵:
[x0'] [cos θ -sin θ] [x0]
[x1'] = [sin θ cos θ] [x1]
θ = pos × base^(-2i/d)
pos=0: θ=0, 旋转 0° → 不旋转(位置 0 无位置信息)
pos=1: θ=θ₁, 旋转 θ₁ → 低维度旋转角度大(高频)
pos=N: θ=θₙ, 旋转 θₙ → 高维度旋转角度小(低频)
每个 token 的位置信息编码在旋转角度中,
不同维度的旋转频率不同 → 自然而然地捕获相对位置关系追踪 3:Prefill 层主序前向
批量层主序
c
// 行 7674: 所有 prompt token 依次通过每一层
static void prefill_layer_major_cpu(
float *logits, const ds4_model *model,
const ds4_weights *weights,
ds4_kv_cache *cache, const token_vec *prompt, ...) {
const uint32_t n_tok = prompt->len;
// 分配双缓冲
float *cur = xmalloc(n_tok * hc_dim * sizeof(float));
float *next = xmalloc(n_tok * hc_dim * sizeof(float));
float *attn = xmalloc(n_tok * hc_dim * sizeof(float));
// Token 嵌入
for (uint32_t t = 0; t < n_tok; t++)
embed_token_f16(cur + t * hc_dim, model, weights, prompt->data[t]);
// 层主序: 所有 token 通过层 0,再通过层 1,...
for (uint32_t il = 0; il < DS4_N_LAYER; il++) {
// 注意力子层(token 间并行)
for (uint32_t t = 0; t < n_tok; t++) {
layer_attention_prefill_one(attn + t*hc_dim, model,
layer, cache, cur, il, t, ...);
}
// FFN 子层(批量 128 token 一组)
const uint32_t ffn_batch = 128;
for (uint32_t b = 0; b < n_tok; b += ffn_batch) {
uint32_t end = MIN(b + ffn_batch, n_tok);
layer_ffn_batch(next + b*hc_dim, model, layer,
attn + b*hc_dim, prompt->data + b, end - b, ...);
}
// 交换缓冲区
float *tmp = cur; cur = next; next = tmp;
}
// 输出 logits
for (uint32_t t = 0; t < n_tok; t++)
output_logits_one(logits, model, weights, cur + t * hc_dim);
}Prefill 层主序 vs Token 主序:
层主序(ds4 使用):
Layer 0: [tok0, tok1, ..., tokN] → 全部处理
Layer 1: [tok0, tok1, ..., tokN] → 全部处理
...
Layer 42: [tok0, tok1, ..., tokN]
Token 主序:
tok0: [Layer 0, Layer 1, ..., Layer 42] → 一个 token 走完全程
tok1: [Layer 0, Layer 1, ..., Layer 42]
...
层主序优势:
- 每层只需加载一次权重(所有 token 共享)
- FFN 可以批量处理(MoE 按专家分组)
- 内存访问模式更友好(权重驻留在缓存中)追踪 4:Decode 单 token 生成
完整层前向
c
// 行 7438: 单 token 的完整 Transformer 层
static void layer_forward_raw_swa_one(
float *out_hc, const ds4_model *model,
const ds4_layer_weights *layer,
ds4_layer_cache *cache,
const float *inp_hc, uint32_t il,
uint32_t pos, int token,
const float *steering_dirs,
float steering_attn_scale, float steering_ffn_scale,
ds4_cpu_decode_scratch *scratch) {
// 注意力子层:
// HC pre → RMSNorm → Q/KV 投影 → RoPE → KV 缓存写入
// → 注意力计算 → 输出投影 → HC post
// FFN 子层:
// HC pre → RMSNorm → routed MoE + shared FFN → HC post
// → 可选 steering
}Decode 循环
c
// 行 7649: 单 token decode 入口
static void forward_token_raw_swa_cpu(
float *logits, const ds4_model *model,
const ds4_weights *weights,
ds4_kv_cache *cache, int token, uint32_t pos) {
// 初始化暂存区(根据上下文长度估算大小)
// 逐层调用 layer_forward_raw_swa_one_decode_scratch
// 最终输出 logits
}Decode 循环:
for each token to generate:
│
▼ forward_token_raw_swa_cpu(logits, model, cache, token, pos)
│
├── Layer 0: attention(1 token, n_kv rows) + FFN(1 token)
├── Layer 1: attention(1 token, n_kv rows) + FFN(1 token)
├── ...
└── Layer 42: attention(1 token, n_kv rows) + FFN(1 token)
│
▼ output_logits_one → logits[129280]
│
▼ sample_top_p_min_p → token_id
│
pos++
继续...追踪 5:输出 Logits 与首 token
首 token 特殊处理
c
// 行 7836: 首个 token 的层前向(无 KV 缓存历史)
static void layer_forward_self_one(
float *out_hc, const ds4_model *model,
const ds4_layer_weights *layer,
const float *inp_hc, uint32_t il,
uint32_t pos, int token) {
// Pipeline: HC pre → RMSNorm → Q/KV → RoPE → FP8 量化
// → F16 舍入 → 自注意力(只有自身)→ 逆 RoPE → 输出投影
// → HC post → FFN
//
// 特殊: 无 KV 缓存写入,无压缩,无 steering
// 这是诊断模式,用于理解单 token 的行为
}
// 行 7891: 从 BOS token 开始的首 token 生成
static void forward_first_token_cpu(
float *out_hc, const ds4_model *model,
const ds4_weights *weights, int token) {
// 嵌入 token
float *cur = xmalloc(hc_dim * sizeof(float));
float *next = xmalloc(hc_dim * sizeof(float));
embed_token_f16(cur, model, weights, token);
hc_from_plain_embedding(cur, ...); // 转换为 HC 状态
// 逐层前向
for (uint32_t il = 0; il < DS4_N_LAYER; il++) {
layer_forward_self_one(next, model, layer, cur, il, 0, token);
float *tmp = cur; cur = next; next = tmp; // 双缓冲交换
}
memcpy(out_hc, cur, hc_dim * sizeof(float));
free(cur); free(next);
}完整推理流程:
用户输入: "Hello world"
│
▼ 分词 → [BOS, Hello, ▁world]
│
▼ Prefill (层主序)
│ 所有 3 个 token 依次通过 43 层
│ 每层: 注意力 + FFN + KV 缓存写入
│
▼ 输出 logits → 采样 → token_4 = "!"
│
▼ Decode 循环
│ token_4 → 43 层 → logits → 采样 → token_5 = " How"
│ token_5 → 43 层 → logits → 采样 → token_6 = " are"
│ ...
│ token_N → EOS → 停止
│
▼ 解码 → "Hello world! How are you?"2. 线程池
追踪 1:线程池结构体设计
完整字段解析
c
// 行 612: 工作函数签名
typedef void (*ds4_parallel_fn)(void *ctx, uint64_t row0, uint64_t row1);
// 行 614: 最大线程数
#define DS4_MAX_THREADS 32
// 行 616-630: 线程池结构体
typedef struct {
pthread_t threads[DS4_MAX_THREADS]; // 线程句柄数组
pthread_mutex_t mutex; // 保护以下共享字段的锁
pthread_cond_t work_cond; // 工作线程等待条件
pthread_cond_t done_cond; // 主线程等待条件
uint32_t n_threads; // 总线程数(含主线程)
uint32_t n_workers; // 工作线程数(= n_threads - 1)
uint32_t generation; // 任务代号(避免虚假唤醒)
uint32_t done; // 已完成的工作线程数
bool initialized; // 是否已初始化
bool shutdown; // 是否正在关闭
ds4_parallel_fn fn; // 当前工作函数
void *ctx; // 工作函数上下文
uint64_t n_rows; // 总行数
} ds4_thread_pool;
// 行 632-634: 全局变量
static ds4_thread_pool g_pool; // 单例线程池
static __thread int g_parallel_depth; // 线程局部递归深度
static uint32_t g_requested_threads; // 用户请求的线程数内存布局
g_pool (全局单例)
┌──────────────────────────────────────┐
│ threads[0..31] (pthread_t × 32) │ ← 线程句柄
│ mutex (pthread_mutex_t) │ ← 互斥锁
│ work_cond (pthread_cond_t) │ ← 唤醒工人
│ done_cond (pthread_cond_t) │ ← 唤醒主线程
│ n_threads = 9 │ ← 例如 8 核 + 主线程
│ n_workers = 8 │
│ generation = 42 │ ← 当前任务编号
│ done = 0 │ ← 已完成计数
│ fn = matvec_q8_0_rows_worker │ ← 当前任务函数
│ ctx = &matvec_ctx │ ← 任务上下文指针
│ n_rows = 32768 │ ← 总行数
└──────────────────────────────────────┘
线程 0 (主线程) 线程 1..8 (工作线程)
g_parallel_depth=0 g_parallel_depth=1
(独立副本) (独立副本)追踪 2:Worker 主循环与 barrier 同步
代号机制
c
// 行 636: 工作线程入口
static void *ds4_worker_main(void *arg) {
const int tid = (int)(uintptr_t)arg; // 线程编号
uint32_t seen_generation = 0; // 已处理的代号
for (;;) {
// 等待新任务
pthread_mutex_lock(&g_pool.mutex);
while (seen_generation == g_pool.generation && !g_pool.shutdown) {
pthread_cond_wait(&g_pool.work_cond, &g_pool.mutex);
// ^^^^^^^^^^^^ ^^^^^^^^^^^^
// 等待被唤醒 释放锁并等待
}
if (g_pool.shutdown) {
pthread_mutex_unlock(&g_pool.mutex);
return NULL; // 关闭退出
}
// 取任务参数(在锁内,安全)
const ds4_parallel_fn fn = g_pool.fn;
void *ctx = g_pool.ctx;
const uint64_t n_rows = g_pool.n_rows;
seen_generation = g_pool.generation;
pthread_mutex_unlock(&g_pool.mutex);
// 计算行范围
const uint64_t rows_per_thread = (n_rows + g_pool.n_threads - 1)
/ g_pool.n_threads;
const uint64_t row0 = tid * rows_per_thread;
const uint64_t row1 = MIN(row0 + rows_per_thread, n_rows);
// 执行工作(标记递归深度)
g_parallel_depth++;
fn(ctx, row0, row1); // ← 实际工作
g_parallel_depth--;
// 完成通知
__sync_fetch_and_add(&g_pool.done, 1); // 原子递增
if (g_pool.done == g_pool.n_workers) {
pthread_mutex_lock(&g_pool.mutex);
pthread_cond_signal(&g_pool.done_cond); // 唤醒主线程
pthread_mutex_unlock(&g_pool.mutex);
}
}
}时序图(generation = 42, n_workers = 3):
主线程 Worker 0 Worker 1 Worker 2
│ │ │ │
├─ set fn, ctx ──────┤ │ │
├─ generation++ ─────┤ │ │
├─ broadcast ───────→│ ← cond_wait │ ← cond_wait │ ← cond_wait
│ │ │ │
├─ fn(ctx, 0, r/3) ├─ fn(ctx,r/3,2r/3)├─ fn(ctx,2r/3,r) │
│ 主线程执行切片0 │ 切片1 │ 切片2 │
│ │ │ │
│ ├─ done=1 ──────────┤ │
│ │ ├─ done=2 ──────────┤
│ │ │ ├─ done=3
│ ←─────────────────────────────────────── signal done_cond ─┤
│ │ │ │
▼ ▼ ▼ ▼追踪 3:线程池初始化与关闭
初始化
c
// 行 678: 初始化线程池
static void ds4_threads_init(void) {
if (g_pool.initialized) return;
// 默认: min(CPU 核心数, 12)
long nprocs = sysconf(_SC_NPROCESSORS_ONLN);
uint32_t n = (uint32_t)nprocs;
if (n > 12) n = 12; // 上限 12 线程
// 环境变量覆盖
const char *env = getenv("DS4_THREADS");
if (env) n = atoi(env);
if (n < 1) n = 1;
if (n > DS4_MAX_THREADS) n = DS4_MAX_THREADS;
g_pool.n_threads = n;
g_pool.n_workers = n - 1; // 主线程也算一个
// 创建 n-1 个工作线程(主线程是 worker 0)
for (uint32_t i = 1; i < n; i++) {
pthread_create(&g_pool.threads[i], NULL,
ds4_worker_main, (void *)(uintptr_t)i);
}
g_pool.initialized = true;
}关闭
c
// 行 715: 优雅关闭
static void ds4_threads_shutdown(void) {
if (!g_pool.initialized) return;
pthread_mutex_lock(&g_pool.mutex);
g_pool.shutdown = true; // 设置关闭标志
g_pool.generation++; // 代号递增触发唤醒
pthread_cond_broadcast(&g_pool.work_cond); // 广播唤醒所有工人
pthread_mutex_unlock(&g_pool.mutex);
// 等待所有工作线程退出
for (uint32_t i = 1; i < g_pool.n_threads; i++) {
pthread_join(g_pool.threads[i], NULL);
}
g_pool.initialized = false;
}追踪 4:parallel_for 分发机制
行范围分配策略
c
// 行 736: 带最小行数阈值的并行分发
static void ds4_parallel_for_min_rows(
uint64_t n_rows, ds4_parallel_fn fn, void *ctx,
uint64_t min_parallel_rows) {
// 退化为串行的情况:
if (g_parallel_depth > 0) goto serial; // 嵌套并行 → 串行
if (g_pool.n_threads <= 1) goto serial; // 单线程
if (n_rows < min_parallel_rows) goto serial; // 行数太少
// 并行分发
ds4_threads_init(); // 懒初始化
pthread_mutex_lock(&g_pool.mutex);
g_pool.fn = fn;
g_pool.ctx = ctx;
g_pool.n_rows = n_rows;
g_pool.done = 0;
g_pool.generation++; // 新代号
pthread_cond_broadcast(&g_pool.work_cond); // 唤醒工人
pthread_mutex_unlock(&g_pool.mutex);
// 主线程执行切片 0
{
const uint64_t rpt = (n_rows + g_pool.n_threads - 1) / g_pool.n_threads;
g_parallel_depth++;
fn(ctx, 0, MIN(rpt, n_rows)); // 主线程的行范围
g_parallel_depth--;
}
// 等待所有工人完成
pthread_mutex_lock(&g_pool.mutex);
while (g_pool.done < g_pool.n_workers) {
pthread_cond_wait(&g_pool.done_cond, &g_pool.mutex);
}
pthread_mutex_unlock(&g_pool.mutex);
return;
serial:
fn(ctx, 0, n_rows); // 直接串行执行
}
// 行 770: 默认最小行数 512 的便捷封装
static void ds4_parallel_for(uint64_t n_rows,
ds4_parallel_fn fn, void *ctx) {
ds4_parallel_for_min_rows(n_rows, fn, ctx, 512);
}行分配示例 (n_rows=32768, n_threads=9):
rows_per_thread = ceil(32768 / 9) = 3641
Thread 0 (主): [ 0, 3641)
Thread 1: [ 3641, 7282)
Thread 2: [ 7282, 10923)
...
Thread 8: [29128, 32768)
每个线程处理约 3641 行,负载基本均衡
最后一线程可能略少(32768 - 29128 = 3640)追踪 5:TLS 与并发安全
递归并行防护
c
// 行 633: 线程局部存储
static __thread int g_parallel_depth;为什么需要防止递归并行?
场景: matvec_q8_0_rows 被 parallel_for 调用
内部某个函数又调用 parallel_for
ds4_parallel_for → fn(ctx, 0, rows)
└── some_matvec_worker(...)
└── ds4_parallel_for(n, sub_fn, sub_ctx)
└── 如果不加防护 → 死锁!
所有工作线程都在执行第一个任务
没有线程空闲来执行第二个任务
防护机制:
g_parallel_depth++ → 值变为 1
ds4_parallel_for_min_rows 检测到 depth > 0
→ 退化为串行执行 fn(ctx, 0, n_rows)
g_parallel_depth-- → 恢复为 0原子操作
c
// Worker 完成通知使用原子操作(无锁)
__sync_fetch_and_add(&g_pool.done, 1); // 原子递增
if (g_pool.done == g_pool.n_workers) { // 最后一个完成
pthread_cond_signal(&g_pool.done_cond);
}done 的递增不需要 mutex 保护,因为 __sync_fetch_and_add 是编译器内置的原子操作。只有当最后一个 worker 完成时才需要锁来 signal 条件变量。