mixture of experts deep dive

混合专家模型深入剖析:从 GShard 到 DeepSeek V4

MoE 核心原理 混合专家(Mixture of Experts, MoE)的核心思想:用路由器选择性地激活部分参数,实现参数总量大但计算量小。 给定输入 $x \in \mathbb{R}^d$,MoE 层的计算为: $$y = \sum_{i=1}^{N} g_i(x) \cdot E_i(x)$$ 其中 $g_i(x)$ 为门控函数(路由器),$E_i$ 为第 $i$ 个专家。稀疏激活的关键是 $g_i(x)$ 只对 Top-k 个专家非零: $$g_i(x) = \begin{cases} \text{softmax}(W_g x)_i & \text{if } i \in \text{Top-k}(W_g x) \ 0 & \text{otherwise} \end{cases}$$ class MoELayer(nn.Module): def __init__(self, d_model, num_experts=8, top_k=2): super().__init__() self.gate = nn.Linear(d_model, num_experts, bias=False) self.experts = nn.ModuleList([ FeedForward(d_model) for _ in range(num_experts) ]) self.top_k = top_k self.num_experts = num_experts def forward(self, x): # x: (B, L, d) logits = self.gate(x) # (B, L, num_experts) topk_logits, topk_idx = logits.topk(self.top_k, dim=-1) topk_weights = F.softmax(topk_logits, dim=-1) output = torch.zeros_like(x) for i in range(self.top_k): expert_idx = topk_idx[..., i] # (B, L) weight = topk_weights[..., i:i+1] # (B, L, 1) for j in range(self.num_experts): mask = (expert_idx == j) if mask.any(): expert_input = x[mask] expert_output = self.experts[j](expert_input) output[mask] += weight[mask] * expert_output return output 路由算法演进 1. Top-k 路由(标准方案) 最常用的路由策略。每个 token 选择得分最高的 k 个专家。问题:容易出现"赢者通吃"——少数专家被过度使用。 ...

2026-06-25 · 3 min · 523 words · 硅基 AGI 探索者
activation function evolution

激活函数演进:从 ReLU 到 SwiGLU

1. 为什么需要激活函数? 没有激活函数,多层线性变换 $\mathbf{W}_2(\mathbf{W}_1 \mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2$ 等价于单层线性变换。激活函数引入非线性,使网络可以拟合复杂函数。 Transformer 的 FFN 层结构: $$ \text{FFN}(x) = W_2 \cdot \sigma(W_1 x + b_1) + b_2 $$ 其中 $\sigma$ 就是激活函数。激活函数的选择直接影响模型的表达能力和训练效率。 2. ReLU 家族 2.1 ReLU $$ \text{ReLU}(x) = \max(0, x) = \begin{cases} x & x > 0 \ 0 & x \leq 0 \end{cases} $$ 优点: 计算极简、梯度不饱和(正区间梯度恒为 1)、被验证有效。 缺点: 神经元死亡(Dead Neuron)——如果输入持续为负,梯度永远为 0,该神经元永久停止学习。 2.2 Leaky ReLU / PReLU $$ \text{LeakyReLU}(x) = \begin{cases} x & x > 0 \ \alpha x & x \leq 0 \end{cases} $$ ...

2026-06-25 · 4 min · 685 words · 硅基 AGI 探索者
context window extension

上下文窗口扩展技术:从 4K 到 1M

1. 为什么长上下文重要? 4K 上下文只能容纳约 3000 字的文档。要处理整本书、代码仓库或长对话,需要 32K-1M 的上下文窗口。但直接训练长上下文模型面临三大挑战: 训练成本:Attention 复杂度 $O(n^2)$,32K 上下文的计算量是 4K 的 64 倍 位置编码外推:训练时未见过的位置编码效果不可控 显存占用:KV Cache 随序列长度线性增长 2. 位置编码外推 2.1 Position Interpolation (PI) 最简单的方法:将位置索引线性缩放到训练范围内。 $$ m’ = m \cdot \frac{L_{train}}{L_{target}} 4K 训练 → 8K 推理时,位置 $m$ 缩放为 $m \times 0.5$。 **问题:** 高频分量被等比缩放,局部注意力模式被破坏。短距离关系(如相邻 Token)的编码精度下降。 ### 2.2 NTK-aware Interpolation NTK(Neural Tangent Kernel)理论指出,高频分量需要保留,低频分量可以缩放。修改 RoPE 的 base: $$ b' = b \cdot s^{d/(d-2)} 其中 $s = L_{target} / L_{train}$ 是扩展倍数,$d$ 是 Head 维度。 ...

2026-06-25 · 4 min · 768 words · 硅基 AGI 探索者
speculative decoding deep

投机解码深度解析:让 LLM 推理快 3 倍

自回归推理的瓶颈 标准自回归解码每次只生成 1 个 token,每个 token 都需要完整前向传播: $$t_{\text{per_token}} = t_{\text{prefill}} + t_{\text{decode}}$$ 其中 decode 阶段是访存密集型的——计算量小但需要加载全部权重和 KV Cache,GPU 利用率通常 <10%。 投机解码的核心洞察:用小模型快速生成草稿,大模型批量验证,将多次串行 decode 变为一次并行验证。 投机解码原理 算法流程 1. Draft Model 快速生成 k 个候选 token: [t1, t2, ..., tk] 2. Target Model 一次前向传播计算这 k 个位置的 logits 3. 对每个候选 token,按拒绝采样决定接受/拒绝: - 若 p_target(ti) / p_draft(ti) >= 1: 接受 - 否则以概率 p_target(ti)/p_draft(ti) 接受,否则拒绝并从调整分布重采样 4. 接受的 token 加入序列,从拒绝点重新开始 拒绝采样数学 对于候选 token $x_i$,定义接受概率: $$\alpha(x_i) = \min\left(1, \frac{p_{\text{target}}(x_i)}{p_{\text{draft}}(x_i)}\right)$$ ...

2026-06-25 · 3 min · 608 words · 硅基 AGI 探索者
positional encoding guide

位置编码全解:绝对/相对/RoPE/ALiBi

1. 为什么 Transformer 需要位置编码? Self-Attention 本质上是一个集合操作:$\text{Attention}(Q, K, V)$ 对输入的顺序不敏感(permutation equivariant)。如果打乱输入顺序,输出只是相应打乱,不包含任何顺序信息。但语言有明确的顺序语义(“猫追狗” ≠ “狗追猫”),必须注入位置信息。 2. 绝对位置编码 2.1 Sinusoidal (原版 Transformer) Vaswani 等人提出用正弦/余弦函数生成位置编码: $$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$ $$ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$ 性质: 不同维度对应不同频率的正弦波,从 $2\pi$ 到 $10000 \times 2\pi$ 对于任意偏移 $k$,$PE_{pos+k}$ 可以表示为 $PE_{pos}$ 的线性变换:$\sin(pos + k) = \sin(pos)\cos(k) + \cos(pos)\sin(k)$ 理论上可以外推到比训练时更长的序列,但实际效果不佳 import torch import math def sinusoidal_pos_encoding(max_len, d_model): pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) return pe 2.2 Learned Positional Embedding BERT、GPT-2 使用可学习的位置 Embedding: self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size) # 前向传播 position_ids = torch.arange(seq_len).unsqueeze(0) embeddings = token_embeddings + self.position_embeddings(position_ids) 优点:灵活,模型可以学习最优的位置表示。缺点:硬编码了最大长度,无法外推。 ...

2026-06-25 · 3 min · 534 words · 硅基 AGI 探索者
rope rotation embedding

旋转位置编码 RoPE:从原理到长度外推

位置编码演进 绝对位置编码 原始 Transformer 使用可学习的绝对位置编码:$E = E_{\text{token}} + E_{\text{pos}}$,其中 $E_{\text{pos}}$ 是可学习参数。 问题:无法外推到训练时未见过的长度。 相对位置编码 T5/ALBERT 使用相对位置偏置:$\text{Attn}(Q_i, K_j) = \frac{Q_i K_j^T}{\sqrt{d}} + b_{i-j}$ 问题:需要额外偏置项,且对窗口外的位置需要截断。 旋转位置编码(RoPE) RoPE(Su et al., 2021)的核心思想:通过旋转矩阵将绝对位置编码转化为相对位置编码——对位置 $m$ 的 Query 和位置 $n$ 的 Key,注意力分数只依赖 $m - n$。 RoPE 数学原理 二维情形 对于二维向量 $q = (q_0, q_1)$,在位置 $m$ 处旋转角度 $m\theta$: $$q_m = R_m q = \begin{pmatrix} \cos m\theta & -\sin m\theta \ \sin m\theta & \cos m\theta \end{pmatrix} \begin{pmatrix} q_0 \ q_1 \end{pmatrix}$$ ...

2026-06-25 · 4 min · 843 words · 硅基 AGI 探索者
kv cache guide

KV Cache 原理与优化:LLM 推理加速核心

1. KV Cache 基本原理 1.1 自回归解码的重复计算问题 LLM 生成文本时逐 token 自回归:每生成一个新 token,需要计算它与之前所有 token 的注意力。 无 Cache 时:生成第 $t$ 个 token,需要重新计算前 $t-1$ 个 token 的 K 和 V。生成 $n$ 个 token 的总计算量为 $O(n^2 \cdot d)$。 有 Cache 时:之前 token 的 K、V 缓存复用,每步只需计算新 token 的 Q、K、V,总计算量降为 $O(n \cdot d)$。 1.2 工作流程 Step 1: 输入 [t0, t1, t2] → 计算并缓存 K0,K1,K2 V0,V1,V2 → 生成 t3 Step 2: 输入 [t3] → 计算 K3,V3 → Cache: [K0..K3, V0..V3] → 生成 t4 Step 3: 输入 [t4] → 计算 K4,V4 → Cache: [K0..K4, V0..V4] → 生成 t5 ... 每步只需对新 token 做 $Q_{new} \cdot K_{cache}^T$,复杂度 $O(n \cdot d)$ 而非 $O(n^2 \cdot d)$。 ...

2026-06-24 · 5 min · 984 words · 硅基 AGI 探索者
transformer architecture deep

Transformer 架构深度解析:从 Attention 到 GPT

1. Transformer 的诞生与核心思想 2017 年 Google 发表《Attention Is All You Need》,Transformer 彻底取代了 RNN/LSTM 的序列建模范式。其核心创新:完全基于注意力机制,消除循环结构,实现高度并行化训练。 Transformer 的三大设计支柱: 组件 作用 解决的问题 Self-Attention 捕捉序列内任意位置间的依赖 RNN 长距离梯度消失 Positional Encoding 注入位置信息 注意力本身是排列不变的 Residual + LayerNorm 稳定深层网络训练 深层网络梯度退化 2. 自注意力机制(Self-Attention) 2.1 公式推导 给定输入序列 $X \in \mathbb{R}^{n \times d}$,通过三个线性投影得到 Query、Key、Value: $$Q = XW_Q, \quad K = XW_K, \quad V = XW_V$$ 注意力计算公式: $$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$ 其中 $\sqrt{d_k}$ 是缩放因子,防止内积值过大导致 softmax 梯度饱和。 为什么除以 $\sqrt{d_k}$? 当 $d_k$ 较大时,$QK^T$ 的方差为 $d_k$(假设 $Q, K$ 各分量独立标准正态),softmax 输入过大时梯度趋近于零。缩放后方差恢复为 1。 ...

2026-06-24 · 4 min · 724 words · 硅基 AGI 探索者
tokenization guide

分词原理与工程实践:BPE/SentencePiece/WordPiece

1. 为什么需要分词 1.1 分词的核心矛盾 自然语言是连续字符序列,模型需要离散输入单元。分词面临三重矛盾: 策略 词表大小 OOV 问题 序列长度 语义完整性 字符级 ~100 无 极长 差 词级 100K+ 严重 短 好 子词级 32K-128K 少 中 较好 子词分词是最佳折中:高频词保持完整,低频词拆为有意义的子词单元。 1.2 分词影响一切 分词不仅是预处理步骤,它直接决定: 模型能力边界:词表决定模型能处理的语言和字符 推理成本:相同文本,不同 Tokenizer 产生的 token 数不同 多语言公平性:中文分词效率直接影响中文任务成本 2. BPE(Byte Pair Encoding) 2.1 算法原理 BPE 最初是数据压缩算法,被 Sennrich 等人(2016)引入 NLP。核心思想:从字符级开始,迭代合并最高频的相邻字符对。 训练算法: 1. 将所有词拆为字符序列,词尾加 </w> "low" → ['l', 'o', 'w', '</w>'] 2. 统计相邻字符对频率 3. 合并最高频字符对为新 token 4. 重复步骤 2-3 直到达到目标词表大小 2.2 代码实现 from collections import Counter, defaultdict def train_bpe(corpus, vocab_size): """BPE 训练算法""" # 初始化:每个词拆为字符 word_freqs = Counter(corpus.split()) splits = {word: list(word) + ['</w>'] for word in word_freqs} merges = [] vocab = set(char for word in word_freqs for char in list(word) + ['</w>']) while len(vocab) < vocab_size: # 统计相邻对频率 pair_freqs = defaultdict(int) for word, freq in word_freqs.items(): split = splits[word] for i in range(len(split) - 1): pair_freqs[(split[i], split[i+1])] += freq if not pair_freqs: break # 合并最高频对 best_pair = max(pair_freqs, key=pair_freqs.get) merges.append(best_pair) new_token = best_pair[0] + best_pair[1] vocab.add(new_token) # 更新所有词的分词 for word in word_freqs: split = splits[word] i = 0 while i < len(split) - 1: if (split[i], split[i+1]) == best_pair: split = split[:i] + [new_token] + split[i+2:] else: i += 1 splits[word] = split return vocab, merges def bpe_encode(text, merges): """BPE 编码""" tokens = list(text) + ['</w>'] for pair in merges: i = 0 while i < len(tokens) - 1: if (tokens[i], tokens[i+1]) == pair: tokens = tokens[:i] + [tokens[i]+tokens[i+1]] + tokens[i+2:] else: i += 1 return tokens 2.3 BPE 的特点 确定性:相同训练数据 → 相同词表 贪婪合并:高频对优先,可能不是全局最优 可逆性:token 拼接即可还原原文 增量性:可以从已有词表继续训练 3. WordPiece 3.1 与 BPE 的区别 WordPiece(Google, 2012)用于 BERT,与 BPE 类似但有关键区别: ...

2026-06-24 · 4 min · 667 words · 硅基 AGI 探索者
speculative decoding

推测解码加速原理:Draft Model 验证范式

1. 自回归解码的瓶颈 1.1 逐 token 生成的低效 LLM 自回归生成:每次前向传播只产生 1 个 token。对于 $N$ 个 token 的输出,需要 $N$ 次串行前向传播。 每次前向传播的计算量: $$\text{FLOPS}{\text{per step}} \approx 2 \times \text{Params}{\text{active}}$$ 但实际 GPU 利用率很低——单 token 推理是内存带宽受限的: $$\text{Compute intensity} = \frac{\text{FLOPS}}{\text{Bytes loaded}} = \frac{2N}{N \times \text{dtype_size}} = \frac{2}{\text{dtype_size}}$$ FP16 下计算强度仅为 1 FLOP/byte,远低于 GPU 的算力/带宽比(A100: 312 TFLOPS / 2TB/s = 156 FLOP/byte)。GPU 大部分时间在等待内存加载权重。 1.2 理想情况 如果能一次前向传播生成 $k$ 个 token,就能将权重复用 $k$ 次,计算强度提升 $k$ 倍。这就是推测解码的核心动机。 2. 推测解码原理 2.1 基本思想 推测解码(Speculative Decoding)使用两个模型: ...

2026-06-24 · 4 min · 833 words · 硅基 AGI 探索者
鲁ICP备2026018361号