MiniMind 模型架构创新技术详解

MiniMind 模型架构创新技术详解

github.com/AlvinScrp/m...

一、核心架构差异

1. RMSNorm(Root Mean Square Layer Normalization)

代码位置

model/model.py:16-50

实现原理
python 复制代码
def _norm(self, x):
    """执行 RMS 归一化计算
    计算公式: x / sqrt(mean(x^2) + eps)
    """
    return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)

def forward(self, x):
    return self.weight * self._norm(x.float()).type_as(x)
与LayerNorm的对比
特性 LayerNorm RMSNorm
计算公式 (x - mean) / sqrt(var + eps) x / sqrt(mean(x^2) + eps)
减去均值 ✅ 是 ❌ 否
计算量 较高 较低
内存占用 较高 较低
稳定性 非常好 良好
GPU友好度 一般 极好
选取原因
  • 计算效率:RMSNorm 消除了减去均值的步骤,减少了计算量
  • 内存效率:不需要存储均值和方差,内存占用更少
  • 现代LLM标准:LLaMA、Falcon等最新大模型都采用RMSNorm
  • GPU优化:更易被GPU的底层优化内核加速
技术对比系列
  • L1 Normalization:固定均值和方差(过时)
  • BatchNorm:跨batch维度归一化(不适用序列任务)
  • LayerNorm:跨特征维度归一化(标准做法)
  • GroupNorm:分组归一化(介于两者之间)
  • RMSNorm ⭐:简化的LayerNorm(当前最优实践)
核心优势
erlang 复制代码
计算复杂度降低 30-40%
↓
训练和推理速度更快
↓
显存占用更少
↓
可训练更大模型或更长序列

2. RoPE(Rotary Position Embeddings)

代码位置

model/model.py:53-74model/model.py:77-113

实现原理

预计算阶段 (model.py:53-74):

python 复制代码
def precompute_pos_cis(dim: int, end: int = int(32 * 1024), theta: float = 1e6):
    """预计算旋转位置编码所需的复数值"""
    # 计算不同频率的逆频率项
    freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
    # 生成位置索引
    t = torch.arange(end, device=freqs.device)
    # 计算外积得到每个位置对应的每个频率
    freqs = torch.outer(t, freqs).float()
    # 使用欧拉公式 e^(i*θ) = cos(θ) + i*sin(θ) 生成复数
    pos_cis = torch.polar(torch.ones_like(freqs), freqs)  # complex64
    return pos_cis

应用阶段 (model.py:77-113):

python 复制代码
def apply_rotary_emb(xq, xk, pos_cis):
    # 将Q和K转换为复数形式
    xq_ = torch.view_as_complex(xq.float().reshape(*xq.shape[:-1], -1, 2))
    xk_ = torch.view_as_complex(xk.float().reshape(*xk.shape[:-1], -1, 2))

    # 在复数域中应用旋转(乘以pos_cis)
    xq_out = torch.view_as_real(xq_ * pos_cis).flatten(3)
    xk_out = torch.view_as_real(xk_ * pos_cis).flatten(3)
    return xq_out.type_as(xq), xk_out.type_as(xk)
与传统位置编码的对比
特性 绝对位置编码 相对位置编码 RoPE
实现方式 直接加到嵌入 相对位置矩阵 复数旋转
外推性能 ❌ 极差 一般 优秀
长文本适应 ❌ 无法处理 中等 可达32K+
计算复杂度 O(1) O(n²) O(n)
显存占用 最少 中等 极少
理论基础 相对位置偏置 复数几何
选取原因

数学优雅性

  • 利用复数旋转的几何性质编码位置信息
  • 相邻位置之间的相对距离由旋转角度决定

外推能力

  • 可以处理训练长度之外的序列(外推长度可达10-100倍)
  • 这是LLaMA能处理4K→32K序列的关键

硬件友好

  • 无需额外的位置矩阵存储
  • 计算可完全融合到Q、K的投影中
技术演进线路
scss 复制代码
绝对位置编码 (Sin/Cos embedding)
    ↓ [问题:外推能力差]
相对位置编码 (Shaw et al., 2018)
    ↓ [问题:计算复杂度高]
ALiBi (Press et al., 2022)
    ↓ [问题:需要修改注意力计算]
RoPE (Su et al., 2021) ⭐
    ↓ [最优方案]
YaRN / NTK-Aware (2023)
核心优势
复制代码
相对位置不变性
↓
频率分解的自然性
↓
优秀的外推性能
↓
可训练更长序列

3. GQA(Grouped-Query Attention)

代码位置

model/model.py:142-280

实现原理

头部配置 (model.py:163-201):

python 复制代码
self.n_kv_heads = args.n_kv_heads if args.n_kv_heads is not None else args.n_heads
assert args.n_heads % self.n_kv_heads == 0  # Q头数必须是KV头数的倍数
self.n_rep = self.n_local_heads // self.n_local_kv_heads  # 每个KV头对应的Q头数

重复KV头 (model.py:116-139):

python 复制代码
def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
    """将KV头重复n_rep次以匹配Q头数量"""
    bs, slen, n_kv_heads, head_dim = x.shape
    if n_rep == 1:
        return x
    return (
        x[:, :, :, None, :]
        .expand(bs, slen, n_kv_heads, n_rep, head_dim)
        .reshape(bs, slen, n_kv_heads * n_rep, head_dim)
    )

注意力计算 (model.py:244-272):

python 复制代码
# 重复KV头以匹配Q头数量
xq, xk, xv = (
    xq.transpose(1, 2),
    repeat_kv(xk, self.n_rep).transpose(1, 2),  # ← 关键:KV被重复
    repeat_kv(xv, self.n_rep).transpose(1, 2)
)
与标准MHA的对比
特性 MHA MQA GQA
Q头数 n n n
KV头数 n 1 n/g (可调)
KV投影参数 最多 最少 中等
推理显存 基准 减少 95% 减少 50-80%
推理速度 基准
精度损失 0% 0-2% <0.5%
配置示例
ini 复制代码
标准 MHA:Q=32头, KV=32头
    ↓
MQA:Q=32头, KV=1头 (激进,精度可能下降)
    ↓
GQA (n_kv_heads=4):Q=32头, KV=4头 (均衡) ⭐
    ↓
GQA (n_kv_heads=8):Q=32头, KV=8头 (接近MHA)
选取原因

推理性能

  • KV缓存大小减少,提高显存利用率
  • 在长序列生成时显著加速

训练稳定性

  • 相比MQA,精度损失极小(<0.5%)
  • 保留了足够的KV多样性

工业标准

  • Llama 2使用GQA
  • OpenAI的最新模型采用此方案
技术演进
scss 复制代码
Multi-Head Attention (Vaswani et al., 2017)
    ↓ [问题:推理时KV缓存太大]
Multi-Query Attention (Shazeer, 2019)
    ↓ [问题:精度下降明显]
Grouped-Query Attention (Ainslie et al., 2023) ⭐
    ↓ [最优平衡点]
核心优势
erlang 复制代码
KV缓存减少 50-80%
↓
推理显存压力减轻
↓
可提高吞吐量或处理更长序列
↓
精度基本无损

4. Flash Attention

代码位置

model/model.py:254-261

实现原理

检测与启用 (model.py:194-195):

python 复制代码
self.flash = hasattr(torch.nn.functional, 'scaled_dot_product_attention') and args.flash_attn

调用方式 (model.py:254-261):

python 复制代码
if self.flash and seq_len != 1:
    dropout_p = self.dropout if self.training else 0.0
    output = F.scaled_dot_product_attention(
        xq, xk, xv,
        attn_mask=None,  # Flash Attention内部处理因果掩码
        dropout_p=dropout_p,
        is_causal=True  # 指示使用因果掩码
    )
else:  # 降级到标准注意力
    scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
    scores += self.mask[:, :, :seq_len, :seq_len]
    scores = F.softmax(scores.float(), dim=-1).type_as(xq)
    scores = self.attn_dropout(scores)
    output = scores @ xv
与标准Attention的对比
特性 标准Attention Flash Attention
计算流程 Q@K→softmax→@V 优化的融合核
中间张量 需要存储N²分数矩阵 按块流式处理
IO复杂度 O(N²) O(N)
计算速度 基准 2-4倍快
显存占用 基准 减少 90%
精度 FP32 FP16(自适应)
工作原理(高层)

标准方法的问题:

css 复制代码
[Q @ K^T] → 生成N×N的分数矩阵(显存瓶颈)
    ↓
[softmax] → 每个元素依赖全局信息
    ↓
[@ V] → 需要重新读取全N×N矩阵

Flash Attention解决方案:

markdown 复制代码
将注意力计算分块进行:
  1. 将Q、K、V分块加载到快速显存(SRAM)
  2. 对每块计算注意力
  3. 融合softmax和dropout
  4. 直接写入结果,无需存储中间分数
选取原因

性能瓶颈突破

  • 标准注意力的IO成为主要瓶颈(不是计算)
  • Flash Attention通过减少IO实现2-4倍加速

显存节省

  • 关键在长序列场景下
  • 可训练更长上下文

库支持

  • PyTorch 2.0+集成支持
  • 无需额外依赖
核心优势
复制代码
IO感知的算法设计
↓
充分利用GPU硬件特性
↓
推理和训练都快 2-4 倍
↓
长序列成为可能

5. KV Cache 优化

代码位置

model/model.py:236-242

实现原理

缓存保存 (model.py:236-242):

python 复制代码
# KV缓存处理:如果有历史KV,则与当前KV拼接
if past_key_value is not None:
    xk = torch.cat([past_key_value[0], xk], dim=1)  # 拼接历史K和当前K
    xv = torch.cat([past_key_value[1], xv], dim=1)  # 拼接历史V和当前V

# 如果需要缓存,则保存当前KV用于下一步
past_kv = (xk, xv) if use_cache else None

生成时的使用 (model.py:731-737):

python 复制代码
# 首次推理或不使用缓存时,处理整个序列
if first_seq or not use_cache:
    out, first_seq = self(input_ids, past_key_values=past_kvs, use_cache=use_cache, **args), False
# 后续推理且使用缓存时,只处理最新的token
else:
    out = self(input_ids[:, -1:], past_key_values=past_kvs, use_cache=use_cache,
               start_pos=input_ids.shape[1] - 1, **args)
性能影响
场景 无KV Cache 有KV Cache
生成速度 基准 (100%) ~10倍快
显存占用 随长度平方增长 线性增长
长序列生成 不可行 ✅ 可行
首token延迟 略高(需要全序列计算)
后续token延迟 极低
原理分析

无缓存时的计算流程:

perl 复制代码
生成token 100:
  Q, K, V = self.wq(seq_0-100), self.wk(seq_0-100), self.wv(seq_0-100)
  attn = softmax(Q @ K^T) @ V
  重新计算了seq_0-99的K、V(浪费!)

生成token 101:
  Q, K, V = self.wq(seq_0-101), self.wk(seq_0-101), self.wv(seq_0-101)
  attn = softmax(Q @ K^T) @ V
  又重新计算了seq_0-100的K、V(二次浪费!)

有缓存时:

ini 复制代码
生成token 100:
  Q100, K100, V100 = self.wq(token_100), self.wk(token_100), self.wv(token_100)
  cached_K = [K_0-99, K100]  ← 保存
  cached_V = [V_0-99, V100]  ← 保存
  attn = softmax(Q100 @ cached_K^T) @ cached_V

生成token 101:
  Q101, K101, V101 = self.wq(token_101), self.wk(token_101), self.wv(token_101)
  cached_K = [K_0-99, K100, K101]  ← 追加(只计算新token)
  cached_V = [V_0-99, V100, V101]  ← 追加
  attn = softmax(Q101 @ cached_K^T) @ cached_V
选取原因

自回归生成的必要条件

  • 不使用KV Cache时,生成100个token需要计算~5000次矩阵乘法
  • 使用KV Cache只需~200次(理论加速50倍)

实际场景

  • 所有LLM推理框架都使用KV Cache
  • 没有KV Cache无法进行实用的文本生成
核心优势
复制代码
避免重复计算历史token的K、V
↓
生成速度提升 10-50 倍
↓
可进行实时交互式应用
↓
大幅降低推理成本

技术总结矩阵

技术 改进目标 相对复杂度 收益幅度 现代地位
RMSNorm 计算效率 ⭐ 低 30-40% 🔥 标准
RoPE 外推能力 ⭐⭐ 中 10-100倍 🔥 标准
GQA 推理效率 ⭐⭐ 中 2-4倍 🔥 标准
Flash Attention 计算速度 ⭐⭐⭐ 复杂 2-4倍 🔥 推荐
KV Cache 生成速度 ⭐ 低 10-50倍 🔥 必须

架构演进对比

原始Transformer (Vaswani et al., 2017)

scss 复制代码
Embedding + LayerNorm
    ↓
Positional Encoding (绝对位置)
    ↓
Multi-Head Attention (32头, 32KV)
    ↓
LayerNorm + FFN (4倍维度)
    ↓
无KV Cache推理
    ↓
外推能力:❌ 极差
推理速度:❌ 极慢

GPT-3风格 (Brown et al., 2020)

scss 复制代码
Embedding + LayerNorm
    ↓
Positional Encoding (绝对位置)
    ↓
Multi-Head Attention (96头, 96KV)
    ↓
LayerNorm + FFN (4倍维度)
    ↓
KV Cache推理
    ↓
外推能力:❌ 无法处理超训练长度
推理速度:中等(有Cache)

LLaMA风格 (Meta, 2023) - MiniMind采用

java 复制代码
Embedding + RMSNorm ⭐
    ↓
RoPE位置编码 ⭐
    ↓
Grouped-Query Attention (32Q, 8KV) ⭐
    ↓
RMSNorm + SwiGLU FFN
    ↓
Flash Attention + KV Cache ⭐
    ↓
外推能力:✅ 可处理 10-100倍长序列
推理速度:✅ 比GPT-3快 10-50倍
显存占用:✅ 减少 50-80%

MiniMind 前馈网络创新技术详解

二、前馈网络差异

1. SwiGLU 激活函数

代码位置

model/model.py:283-344

实现原理

类定义和初始化 (model.py:300-324):

python 复制代码
class FeedForward(nn.Module):
    def __init__(self, config: LMConfig):
        super().__init__()
        # 如果未指定隐藏层维度,则自动计算
        if config.hidden_dim is None:
            hidden_dim = 4 * config.dim  # 初始为4倍维度
            hidden_dim = int(2 * hidden_dim / 3)  # ← 关键:降到约2.67倍
            # 对齐到multiple_of的倍数(通常为256)
            config.hidden_dim = config.multiple_of * ((hidden_dim + config.multiple_of - 1) // config.multiple_of)

        # 三个线性投影层
        self.w1 = nn.Linear(config.dim, config.hidden_dim, bias=False)  # 升维
        self.w2 = nn.Linear(config.hidden_dim, config.dim, bias=False)  # 降维
        self.w3 = nn.Linear(config.dim, config.hidden_dim, bias=False)  # 门控
        self.dropout = nn.Dropout(config.dropout)

    def forward(self, x):
        """SwiGLU实现:FFN(x) = dropout(W₂ · (SiLU(W₁·x) ⊙ W₃·x))"""
        return self.dropout(self.w2(F.silu(self.w1(x)) * self.w3(x)))
        #                                    ↑ 激活函数      ↑ 门控信号
数学公式对比
架构 公式 参数数量
传统FFN FFN(x) = W₂ · ReLU(W₁ · x) 2D·H
GELU FFN FFN(x) = W₂ · GELU(W₁ · x) 2D·H
SwiGLU FFN(x) = W₂ · (SiLU(W₁·x) ⊙ W₃·x) 3D·H

其中:

  • D = 模型维度 (通常 768-4096)
  • H = 隐藏维度 (通常 2.67D)
  • = 逐元素乘法(Hadamard积)
  • SiLU(x) = x · sigmoid(x) = Swish激活
SwiGLU的三个关键组件

1. SiLU激活函数(Swish)

python 复制代码
SiLU(x) = x · sigmoid(x) = x · 1/(1 + e^(-x))

对比其他激活函数:

scss 复制代码
ReLU:          max(0, x)           - 非光滑,梯度为0或1
GELU:          x · Φ(x)            - 光滑,类似softmax
Swish/SiLU:    x · sigmoid(x)      - 光滑自门控 ⭐

2. 门控机制(Gating)

python 复制代码
f(x) = SiLU(W₁·x) ⊙ W₃·x
       ↑ 激活路径    ↑ 门控路径

门控作用:

  • W₃·x 产生的值范围在 [0, 1] 之间(或接近)
  • 通过逐元素乘法来调节信息流
  • 类似于LSTM中的遗忘门

3. 参数高效性

r 复制代码
标准FFN:dim → 4*dim → dim
         参数数:2*D*4D = 8D²

SwiGLU:  dim → 2.67*dim → dim (两个分支)
         参数数:(1 + 1 + 1)*D*2.67D ≈ 8D²

↓ 虽然参数相同,但计算量相近,但效果显著更好
与其他激活函数的对比
特性 ReLU GELU Swish/SiLU SwiGLU
平滑性 ❌ 不平滑 ✅ 平滑 ✅ 平滑 ✅ 平滑
自门控 ❌ 无 ❌ 无 ✅ 有 ✅ 有
双路径 ❌ 单路 ❌ 单路 ❌ 单路 ✅ 双路
梯度流 优异
计算成本 最低 中等 中等 中等
模型质量 最高
现代采用 过时 广泛 常见 🔥标准
选取原因

1. 门控的必要性

markdown 复制代码
传统FFN的问题:
  - 激活函数处理所有信息
  - 无法根据上下文调节信息
  - 梯度流受限

SwiGLU的优势:
  - 双路径设计:激活路径 + 门控路径
  - 可动态调节信息流强度
  - 梯度从两条路径流向反向传播

2. 实验证据(来自原始论文)

使用相同参数量的情况下:

架构 BLEU评分 困惑度
ReLU FFN 25.4 5.20
GELU FFN 26.8 4.95
SwiGLU 27.6 4.71

SwiGLU相比GELU提升 ~3%,相比ReLU提升 ~8%

3. 参数高效性

r 复制代码
关键发现(Shazeer et al., 2022):

使用SwiGLU时,可以减少FFN的隐藏维度,
同时保持相同甚至更好的性能

原始:dim → 4*dim → dim
现在:dim → 2.67*dim → dim ⭐

效果:参数减少 33%,性能持平或更优

4. 现代LLM标准

复制代码
LLaMA / LLaMA-2:SwiGLU
PaLM / Gemini:SwiGLU
Falcon:SwiGLU
MistralAI:SwiGLU
激活函数演进线
scss 复制代码
时代 1 (2012-2017):ReLU 主宰
  max(0, x)
  ↓ [梯度消失问题]

时代 2 (2017-2020):GELU 兴起
  x·Φ(x)  [更平滑的梯度]
  ↓ [缺少自适应门控]

时代 3 (2020-2022):Swish 实验阶段
  x·sigmoid(x)  [自门控]
  ↓ [门控不够结构化]

时代 4 (2022+):SwiGLU 标准化 ⭐
  W₂(SiLU(W₁x) ⊙ W₃x)  [最优的表达力+效率]
  ↓

未来可能:MLP-Mixer, GLU Variants
核心优势总结
markdown 复制代码
双路径设计
    ↓
更好的梯度流
    ↓
更强的表达能力
    ↓
可减少参数同时保持性能
    ↓
成为现代LLM标准激活函数

2. MoE(Mixture of Experts)混合专家模型

代码位置

model/model.py:347-459

实现原理

MoE门控网络 (model.py:347-401):

python 复制代码
class MoEGate(nn.Module):
    """门控网络:决定每个token由哪些专家处理"""

    def __init__(self, config: LMConfig):
        super().__init__()
        self.top_k = config.num_experts_per_tok  # 每个token选择的专家数
        self.n_routed_experts = config.n_routed_experts  # 路由专家总数
        self.scoring_func = config.scoring_func  # 评分函数(softmax)
        self.alpha = config.aux_loss_alpha  # 辅助损失权重
        self.weight = nn.Parameter(torch.empty((self.n_routed_experts, self.gating_dim)))

    def forward(self, hidden_states):
        # 计算每个专家的得分
        logits = F.linear(hidden_states, self.weight, None)
        scores = logits.softmax(dim=-1)  # 转换为概率

        # 选择top-k个专家
        topk_weight, topk_idx = torch.topk(scores, k=self.top_k, dim=-1, sorted=False)

        # 归一化top-k权重
        if self.top_k > 1 and self.norm_topk_prob:
            denominator = topk_weight.sum(dim=-1, keepdim=True) + 1e-20
            topk_weight = topk_weight / denominator

        # 计算辅助损失(平衡专家负载)
        aux_loss = self._compute_aux_loss(...)

        return topk_idx, topk_weight, aux_loss

MoE前馈网络 (model.py:404-436):

python 复制代码
class MOEFeedForward(nn.Module):
    def __init__(self, config: LMConfig):
        super().__init__()
        # 创建多个专家(每个都是一个独立的FFN)
        self.experts = nn.ModuleList([
            FeedForward(config)
            for _ in range(config.n_routed_experts)
        ])
        self.gate = MoEGate(config)  # 门控网络

        # 可选的共享专家(所有token都经过)
        if config.n_shared_experts is not None:
            self.shared_experts = FeedForward(config)

    def forward(self, x):
        identity = x
        # 1. 使用门控网络选择专家
        topk_idx, topk_weight, aux_loss = self.gate(x)

        # 2. 分发token给选中的专家
        if self.training:
            # 训练时:循环分发(更简单的实现)
            y = torch.empty_like(x, dtype=torch.float16)
            for i, expert in enumerate(self.experts):
                y[flat_topk_idx == i] = expert(x[flat_topk_idx == i])
            y = (y.view(*topk_weight.shape, -1) * topk_weight.unsqueeze(-1)).sum(dim=1)
        else:
            # 推理时:优化的推理流程
            y = self.moe_infer(x, flat_topk_idx, topk_weight.view(-1, 1))

        # 3. 可选:添加共享专家的输出
        if self.config.n_shared_experts is not None:
            y = y + self.shared_experts(identity)

        # 保存辅助损失用于反向传播
        self.aux_loss = aux_loss
        return y

高效推理 (model.py:438-459):

python 复制代码
@torch.no_grad()
def moe_infer(self, x, flat_expert_indices, flat_expert_weights):
    """推理时的高效MoE计算"""
    expert_cache = torch.zeros_like(x)

    # 按专家索引排序token
    idxs = flat_expert_indices.argsort()

    # 计算每个专家处理的token数
    tokens_per_expert = flat_expert_indices.bincount().cpu().numpy().cumsum(0)
    token_idxs = idxs // self.config.num_experts_per_tok

    # 为每个专家处理对应的tokens
    for i, end_idx in enumerate(tokens_per_expert):
        start_idx = 0 if i == 0 else tokens_per_expert[i - 1]
        if start_idx == end_idx:
            continue
        expert = self.experts[i]
        exp_token_idx = token_idxs[start_idx:end_idx]
        expert_tokens = x[exp_token_idx]
        expert_out = expert(expert_tokens)
        expert_out.mul_(flat_expert_weights[idxs[start_idx:end_idx]])
        expert_cache.scatter_add_(0, exp_token_idx.view(-1, 1).repeat(1, x.shape[-1]), expert_out)

    return expert_cache
MoE工作流程详解

步骤 1:门控路由

yaml 复制代码
Input: token sequence [seq_len, dim]
            ↓
Gate Network: 计算每个token → 每个专家的概率
            ↓
Top-k Selection: 为每个token选择top-k个专家
            ↓
Output: (token_to_expert_mapping, expert_weights)

步骤 2:专家分发

python 复制代码
输入:Token序列 [B, S, D]
         ↓
Gate: [B*S, num_experts] → 每个token的专家权重
         ↓
TopK: 为每个token选择 top_k=2 个专家
         ↓
分发:
  Token 0 → Expert 3, Expert 7  (权重: 0.6, 0.4)
  Token 1 → Expert 1, Expert 5  (权重: 0.7, 0.3)
  Token 2 → Expert 7, Expert 9  (权重: 0.5, 0.5)
         ↓
汇聚:对每个token的多个专家输出加权求和

步骤 3:输出合并

ini 复制代码
Expert 1 output: [0.7 * token_1_output, ...]
Expert 3 output: [0.6 * token_0_output, ...]
Expert 5 output: [0.3 * token_1_output, ...]
Expert 7 output: [0.4 * token_0_output, 0.5 * token_2_output, ...]
Expert 9 output: [0.5 * token_2_output, ...]
         ↓ [合并到原始位置]
Final output: [token_0_out, token_1_out, token_2_out, ...]
MoE vs 标准FFN
特性 标准FFN MoE
参数数量 基准 (8D²) 稍高 (~10D²)
计算量 所有token都经过所有神经元 只经过top-k专家
激活参数 基准 减少 80-90%
模型容量 固定 动态可扩展
泛化能力 基准 更强
训练稳定性 良好 需要负载均衡
推理速度 基准 取决于top-k
详细对比分析

计算量对比(以Transformer块为例)

标准FFN:

css 复制代码
[B, S, D] → [B, S, 4D] → [B, S, D]

总计算量 = B*S*D * 4D + B*S*4D * D = 8*B*S*D²
(每个token都经过全部计算)

MoE (num_experts=8, top_k=2):

scss 复制代码
[B, S, D] → Gate → 选择top-2专家 (共8个专家)

每个专家大小 = D → D/2 → D  (为了保持总参数量)

单个token的计算 = D * (D/2) * 2 = B*S*D²
                  (只处理2个专家)

总计算量 ≈ 2*B*S*D²
(相比标准FFN减少 75%!)

实际性能数据 (来自GLaM论文)

配置 参数 计算 困惑度
标准 175B 175B 基准 10.5
MoE 64E 2K 175B 基准 9.8
MoE 1024E 2K 175B 基准 9.2

关键发现

  • 相同参数量,MoE显著超过标准模型
  • 可以用更少计算达到相同精度
  • 或用相同计算达到更高精度
MoE的四大挑战与解决方案

挑战 1:负载不均衡

ini 复制代码
问题:某些专家被频繁选中,某些很少被选中
      ↓ 浪费了参数和计算资源

解决方案:Auxiliary Loss(辅助损失)
  aux_loss = alpha * (专家使用概率 * 专家容量)
  ↓ 鼓励均匀分布

挑战 2:路由碰撞

markdown 复制代码
问题:某些token想去的专家都满了(容量限制)
      ↓ 需要丢弃或降级处理

解决方案:
  1. Expert Capacity:为每个专家设定容量阈值
  2. Drop Tokens:超过容量的token丢弃
  3. Shared Experts:所有token都经过的后备专家

挑战 3:训练不稳定

markdown 复制代码
问题:Gate网络突然改变路由决策
      ↓ 梯度不稳定

解决方案:
  1. Sticky Gate:添加随机噪声平滑路由
  2. Temperature Annealing:逐步降低温度
  3. Load Balancing:强制均衡专家负载

挑战 4:推理延迟

markdown 复制代码
问题:不规则的内存访问模式
      ↓ GPU利用率低

解决方案:Token Reordering(代码中的moe_infer)
  1. 按专家排序token
  2. 连续处理相同专家的token
  3. 提高GPU缓存命中率
选取原因

1. 稀疏性的力量

markdown 复制代码
在极大规模模型中:
  - 标准模型每个token都要经过每个参数 → 计算量巨大
  - MoE让每个token只经过部分参数 → 大幅减少计算

例子:Google GLaM
  - 1.2 万亿参数的MoE模型
  - 计算量仅相当于100B标准模型

2. 模型缩放的新方向

erlang 复制代码
参数缩放:基本饱和(10B→100B效果递减)

计算缩放:有效但不够快(计算4倍→性能仅提升~40%)

稀疏缩放(MoE):极有效(计算4倍→性能提升~60%)⭐
             可以用更少计算得到更强模型

3. 工业级应用的可行性

r 复制代码
有了MoE,才能在实际中训练/运行最强大的模型

Switch Transformer:1.6T参数,但激活参数仅相当于30B
Google's Gemini MoE:参数巨大,但高效可用
共享专家(Shared Experts)设计
python 复制代码
if config.n_shared_experts is not None:
    y = y + self.shared_experts(identity)

为什么需要共享专家?

特性 纯MoE 共享+MoE
容量 所有专家都路由 共享专家始终活跃
稳定性 需要强制均衡 自然更均衡
泛化 可能过专化 更一般化
计算 最少 略高
markdown 复制代码
共享专家的作用:
  - 所有token都经过共享专家 → 提供基础特征
  - 路由专家基于token的特殊性 → 提供特化特征
  - 合并:y = y_routed + y_shared ⭐

类比:
  共享专家 ≈ 通用骨架
  路由专家 ≈ 特化模块
核心优势总结
markdown 复制代码
模型参数巨大但计算精简
    ↓
稀疏激活的力量
    ↓
用更少计算达到更强性能
    ↓
可以训练超大规模模型
    ↓
适合实际工业应用

前馈网络架构演进

scss 复制代码
时代 1 (2017):标准FFN
  dim → 4*dim → dim
  激活:ReLU
  参数:8D²

         ↓ [质量瓶颈]

时代 2 (2018-2020):GELU激活函数
  dim → 4*dim → dim
  激活:GELU
  参数:8D²
  质量提升:~15%

         ↓ [容量瓶颈]

时代 3 (2020-2022):门控和SwiGLU
  dim → 2.67*dim → dim (两分支)
  激活:SiLU + 门控
  参数:8D²(但效果更好)
  质量提升:+3% vs GELU

         ↓ [缩放瓶颈]

时代 4 (2022+):MoE混合专家 ⭐
  dim → 多个专家 → dim
  激活:SwiGLU + 稀疏路由
  参数:10D² (但激活仅2/8)
  质量提升:+60% vs 标准FFN(相同计算)
  可扩展:到数万亿参数

与标准FFN的完整对比

标准FFN (原始Transformer)

python 复制代码
# 简单线性变换
class StandardFFN(nn.Module):
    def __init__(self, config):
        self.w1 = nn.Linear(dim, 4*dim)
        self.w2 = nn.Linear(4*dim, dim)

    def forward(self, x):
        return self.w2(F.relu(self.w1(x)))

效果:基准
计算:基准
参数:8D²

SwiGLU FFN (MiniMind采用)

python 复制代码
# 门控双路径设计
class FeedForward(nn.Module):
    def __init__(self, config):
        self.w1 = nn.Linear(dim, hidden_dim)
        self.w2 = nn.Linear(hidden_dim, dim)
        self.w3 = nn.Linear(dim, hidden_dim)

    def forward(self, x):
        return self.w2(F.silu(self.w1(x)) * self.w3(x))

效果:+3% vs GELU
计算:中等
参数:8D²(但更高效)

MoE FFN (可选)

python 复制代码
# 稀疏专家路由
class MOEFeedForward(nn.Module):
    def __init__(self, config):
        self.experts = [FeedForward(config) for _ in range(num_experts)]
        self.gate = MoEGate(config)
        self.shared_experts = FeedForward(config)

    def forward(self, x):
        idx, weight, aux_loss = self.gate(x)  # 路由
        y = self._route(x, idx, weight)       # 分发
        y = y + self.shared_experts(x)        # 共享
        return y

效果:+60% vs 标准FFN(相同计算)
计算:减少 75%(激活参数)
参数:10D²(但仅2/8活跃)
可扩展:最好

对标原始GPT论文的改进

原始GPT论文中的FFN:

vbnet 复制代码
"We employed a position-wise feed-forward network
which consists of two linear transformations with a
ReLU activation in between."

dim → 4*dim → dim with ReLU

MiniMind的改进:

markdown 复制代码
1. 激活函数升级:ReLU → GELU → SwiGLU
   质量提升:+30% (相同参数)

2. 智能门控:添加第三个投影 (W3)
   参数相同,表达力大幅提升

3. 可选稀疏化:标准FFN → MoE
   计算减少:75%(推理)
   性能:+60%(相同计算)

  • 优化技术 (参数共享, 生成采样策略)
  • 完整总结 (所有技术综合评分)

MiniMind 优化技术详解

三、优化技术

1. 参数共享(Weight Tying)

代码位置

model/model.py:562-578

实现原理

初始化阶段 (model.py:562-578):

python 复制代码
class MiniMindLM(PreTrainedModel):
    def __init__(self, params: LMConfig = None):
        # ... 其他初始化代码 ...

        # 词元嵌入层:将token ID映射为向量表示
        self.tok_embeddings = nn.Embedding(params.vocab_size, params.dim)

        # 输出层:将隐藏状态映射为词汇表大小的logits
        self.output = nn.Linear(params.dim, params.vocab_size, bias=False)

        # ⭐ 关键:参数共享
        # 让输出层的权重与嵌入层权重指向同一个参数张量
        self.tok_embeddings.weight = self.output.weight
数学原理
r 复制代码
标准方式:
  嵌入矩阵:W_emb [vocab_size, dim]
  输出矩阵:W_out [dim, vocab_size]
  总参数:vocab_size * dim * 2

参数共享:
  共享矩阵:W [vocab_size, dim]
  嵌入:token_id → W[token_id, :] ∈ ℝ^dim
  输出:h ∈ ℝ^dim → h @ W^T ∈ ℝ^vocab_size
  总参数:vocab_size * dim(减少50%!)
前向计算对比

标准方式

python 复制代码
# 嵌入阶段
h = embedding_matrix[input_ids]  # [B, S, dim]

# 输出阶段
logits = h @ output_matrix       # [B, S, vocab_size]

参数共享

python 复制代码
# 嵌入和输出共用同一个矩阵W [vocab_size, dim]

# 嵌入阶段
h = W[input_ids, :]              # [B, S, dim]

# 输出阶段(W是嵌入层权重的引用)
logits = h @ W.T                 # [B, S, vocab_size]
影响分析
方面 标准方式 参数共享
参数数量 vocab_size × dim × 2 vocab_size × dim ⭐
参数减少 基准 50%
显存占用 基准 减少 50%
计算量 基准 相同
精度 基准 相同
训练稳定性 基准 稍差(但可接受)
参数量估算示例

以Llama-7B为例:

ini 复制代码
Vocabulary size: 32,000
Hidden dimension: 4,096

标准方式:
  嵌入参数:32,000 × 4,096 = 131.1M
  输出参数:4,096 × 32,000 = 131.1M
  总计:262.2M 参数(占模型约3.8%)

参数共享:
  共享参数:32,000 × 4,096 = 131.1M
  总计:131.1M 参数(减少 50%)

节省显存:
  32-bit: 262.2M × 4B = 1.05GB
  半精度: 262.2M × 2B = 0.52GB
选取原因

1. 嵌入和输出的对称性

复制代码
语言模型的本质是:token编码 → 中间表示 → token解码

嵌入层:token_id → 向量
输出层:向量 → token_id

这两个操作在数学上应该互为转置关系!

语言模型假设

css 复制代码
一个token的嵌入向量应该与输出时的得分向量相近

即:
  token i 的嵌入向量 ≈ 模型预测token i时的权重向量

如果使用同一个矩阵,这个关系会更强!

2. 参数高效性

erlang 复制代码
模型的显存瓶颈:
  总参数数 = n_layers × (注意力参数 + FFN参数) + 嵌入+输出

对于大词汇表模型(如中文,词汇表>50K):
  嵌入+输出可占总参数的 3-10%
  参数共享可节省 1.5-5% 的总参数量

3. 现代LLM标准

复制代码
BERT:✅ 使用参数共享
GPT:✅ 使用参数共享
ALBERT:✅ 设计的核心特性
LLaMA:✅ 使用参数共享
T5:✅ 使用参数共享
潜在的缺点与应对

缺点 1:嵌入和输出学习目标不同

复制代码
嵌入需要编码:token的语义、语法信息
输出需要判别:不同token的区别

强制使用同一个矩阵可能引入冲突

应对:隐层变换

python 复制代码
# 使用一个额外的线性层解耦
self.output = nn.Linear(params.dim, params.vocab_size, bias=False)
# 但不再使用共享权重
# 而是使用投影层进行变换

缺点 2:词汇表增长时问题

markdown 复制代码
如果需要扩展词汇表(如添加新语言token):
  - 嵌入矩阵需要扩展:[vocab_size_old, dim] → [vocab_size_new, dim]
  - 输出矩阵也需要扩展
  - 如果参数共享,嵌入和输出同时扩展(没问题)
代码实现要点
python 复制代码
# 正确的参数共享方式
self.tok_embeddings = nn.Embedding(params.vocab_size, params.dim)
self.output = nn.Linear(params.dim, params.vocab_size, bias=False)

# 关键:使嵌入层权重和输出层权重指向同一个参数
self.tok_embeddings.weight = self.output.weight

# 为什么这样做有效?
# 1. 嵌入:token_id → self.tok_embeddings.weight[token_id]
# 2. 输出:hidden → hidden @ self.output.weight.T
#        = hidden @ self.tok_embeddings.weight.T
核心优势
erlang 复制代码
参数减少 50%(嵌入+输出)
    ↓
显存节省 1-3%(总模型)
    ↓
训练速度稍快
    ↓
对齐嵌入和输出的学习目标
    ↓
成本收益比极高

2. 生成采样策略

代码位置

model/model.py:710-773

实现原理

流式生成的完整流程 (model.py:710-773):

python 复制代码
def _stream(self, input_ids, eos_token_id, max_new_tokens,
            temperature, top_p, rp, use_cache, **args):
    """
    逐token生成的流式采样函数
    """
    start, first_seq, past_kvs = input_ids.shape[1], True, None

    while input_ids.shape[1] < max_new_tokens - 1:
        # 推理:获取logits
        if first_seq or not use_cache:
            out = self(input_ids, past_key_values=past_kvs, use_cache=use_cache)
        else:
            out = self(input_ids[:, -1:], past_key_values=past_kvs,
                      use_cache=use_cache, start_pos=input_ids.shape[1] - 1)

        logits, past_kvs = out.logits[:, -1, :], out.past_key_values

        # ⭐ 策略 1:重复惩罚(Repetition Penalty)
        logits[:, list(set(input_ids.tolist()[0]))] /= rp

        # ⭐ 策略 2:温度缩放(Temperature Scaling)
        logits /= (temperature + 1e-9)

        # ⭐ 策略 3:核采样(Top-p/Nucleus Sampling)
        if top_p is not None and top_p < 1.0:
            sorted_logits, sorted_indices = torch.sort(logits, descending=True, dim=-1)
            sorted_probs = F.softmax(sorted_logits, dim=-1)
            cumulative_probs = torch.cumsum(sorted_probs, dim=-1)

            # 找出超过threshold的位置
            sorted_indices_to_remove = cumulative_probs > top_p
            sorted_indices_to_remove[:, 1:] = sorted_indices_to_remove[:, :-1].clone()
            sorted_indices_to_remove[:, 0] = False

            indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices,
                                                                  sorted_indices_to_remove)
            logits[indices_to_remove] = -float('Inf')

        # ⭐ 策略 4:多项式采样(Multinomial Sampling)
        input_ids_next = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1)
        input_ids = torch.cat((input_ids, input_ids_next), dim=1)

        yield input_ids[:, start:]

        if input_ids_next.item() == eos_token_id:
            break
四大采样策略详解

策略 1:重复惩罚(Repetition Penalty) [model.py:742]

python 复制代码
logits[:, list(set(input_ids.tolist()[0]))] /= rp

目的:避免生成重复文本

工作原理

ini 复制代码
已生成的token:[我, 是, 一, 个, AI]

重复惩罚(rp = 1.2):
  对已经出现过的token,降低其logit值
  logit[我] = logit[我] / 1.2
  logit[是] = logit[是] / 1.2
  ...

效果:
  这些token被选中的概率下降
  → 新的token被选中的概率上升
  → 避免"我我我"或"是是是"

参数说明

  • rp = 1.0:无惩罚(允许重复)
  • rp = 1.2:轻度惩罚(推荐)
  • rp = 2.0:强度惩罚(可能失去必要的重复)
效果 使用场景
1.0 允许重复 列表、枚举
1.2 轻度限制 通用对话
1.5 中等限制 创意写作
2.0+ 严格禁止 多样性要求高

策略 2:温度缩放(Temperature Scaling) [model.py:744]

python 复制代码
logits /= (temperature + 1e-9)

目的:控制生成的随机性/确定性程度

工作原理

scss 复制代码
Softmax 概率计算:
  P(token_i) = exp(logits_i / temperature) / Σ exp(logits_j / temperature)

temperature = 0.1(低温):
  exp(logits_i / 0.1) 放大了logits的差异
  → 分布尖锐,概率高的token更容易被选中
  → 生成结果确定、连贯

temperature = 1.0(常温):
  标准 softmax
  → 平衡的随机性

temperature = 2.0(高温):
  exp(logits_i / 2.0) 缩小了logits的差异
  → 分布平坦,所有token概率接近
  → 生成结果随意、多样

可视化

ini 复制代码
Logits: [2.0, 1.0, 0.5]

Temperature = 0.1 (确定模式):
  P = softmax([20, 10, 5]) ≈ [0.999, 0.001, 0.0]
  ↓ 高概率token几乎必中

Temperature = 1.0 (平衡模式):
  P = softmax([2, 1, 0.5]) ≈ [0.659, 0.242, 0.099]
  ↓ 保留一定的随机性

Temperature = 2.0 (随机模式):
  P = softmax([1, 0.5, 0.25]) ≈ [0.409, 0.326, 0.265]
  ↓ 所有token概率接近,高度随意
温度 行为 使用场景
0.1-0.5 确定、保守 代码、数据、事实问答
0.7-0.8 平衡 通用对话
1.0-1.5 创意、多样 故事创作、诗歌
2.0+ 极度随意 数据增强、探索

策略 3:核采样(Top-p/Nucleus Sampling) [model.py:747-763]

python 复制代码
if top_p is not None and top_p < 1.0:
    sorted_logits, sorted_indices = torch.sort(logits, descending=True, dim=-1)
    sorted_probs = F.softmax(sorted_logits, dim=-1)
    cumulative_probs = torch.cumsum(sorted_probs, dim=-1)

    sorted_indices_to_remove = cumulative_probs > top_p
    sorted_indices_to_remove[:, 1:] = sorted_indices_to_remove[:, :-1].clone()
    sorted_indices_to_remove[:, 0] = False

    indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices,
                                                          sorted_indices_to_remove)
    logits[indices_to_remove] = -float('Inf')

目的:动态选择候选token集合,避免选择低概率token

工作原理(示例):

ini 复制代码
概率分布(已排序):
  P(token_A) = 0.50 → 累积: 0.50 ✅ 保留
  P(token_B) = 0.25 → 累积: 0.75 ✅ 保留
  P(token_C) = 0.15 → 累积: 0.90 ✅ 保留
  P(token_D) = 0.08 → 累积: 0.98 ❌ 移除(超过top_p=0.9)
  P(token_E) = 0.02 → 累积: 1.00 ❌ 移除

Top-p采样:从[A, B, C]中随机选择
          P(A|selected) = 0.50/0.90 = 0.556
          P(B|selected) = 0.25/0.90 = 0.278
          P(C|selected) = 0.15/0.90 = 0.167

与Top-k的对比

特性 Top-k采样 Top-p采样
原理 保留概率最高的k个token 保留累积概率达p的token
候选数 固定k 动态(取决于分布)
低置信度情况 可能包含低分token 自动过滤
高置信度情况 可能过度限制 自动放松
推荐值 k=50 p=0.9

工作场景对比

css 复制代码
高置信情况(模型很确定答案):
  Top-k (k=50):强制选择50个token
               可能包含不相关的token

  Top-p (p=0.9):可能只需5个token就达到0.9
                自动缩小候选集
                更优!✅

低置信情况(模型不确定答案):
  Top-k (k=50):保留50个token
               可能包含很低分的

  Top-p (p=0.9):需要50个token才能达到0.9
                自动放松限制
                更优!✅

策略 4:多项式采样(Multinomial Sampling) [model.py:766]

python 复制代码
input_ids_next = torch.multinomial(F.softmax(logits, dim=-1), num_samples=1)

目的:按概率分布随机采样下一个token

与贪心解码的对比

scss 复制代码
贪心解码(Greedy Decoding):
  selected_token = argmax(logits)
  ↓ 总是选择概率最高的token
  ↓ 确定但可能陷入局部最优

多项式采样(Multinomial Sampling):
  P(token_i) ∝ exp(logits_i)
  selected_token ~ Multinomial(P)
  ↓ 按概率随机选择
  ↓ 保留一定随机性,发现更多可能性

概率示例

ini 复制代码
logits = [2.0, 1.5, 0.5]
P = softmax(logits) = [0.659, 0.242, 0.099]

多项式采样:
  - 65.9% 概率选token_0
  - 24.2% 概率选token_1
  - 9.9% 概率选token_2

而不是总是选token_0(贪心)

采样策略的完整流程
scss 复制代码
logits [vocab_size]
    ↓
[1] 重复惩罚
    对已出现的token:logits /= rp
    ↓ logits_penalized

[2] 温度缩放
    logits /= temperature
    ↓ logits_scaled

[3] 核采样(可选)
    mask out low-probability tokens
    ↓ logits_filtered

[4] 多项式采样
    P = softmax(logits_filtered)
    token ~ Multinomial(P)
    ↓
next_token
采样参数的推荐配置
场景 temperature top_p rp 特点
代码生成 0.1 0.95 1.0 确定、无重复限制
事实问答 0.7 0.90 1.2 准确、避免重复
对话交互 0.8 0.90 1.2 平衡
故事创作 1.2 0.85 1.1 创意、连贯
数据增强 1.5 0.80 1.0 多样、自由
极度创意 2.0 0.75 1.0 随意、无约束
选取原因

1. 解决模式坍塌(Mode Collapse)

markdown 复制代码
在生成任务中,如果只使用贪心解码:
  - 模型总是选择概率最高的token
  - 生成的文本虽然通顺,但极其单调
  - 重复某些短语:"我很高兴...我很高兴...我很高兴"

采样策略可以:
  - 保留多样性
  - 让"合理但不是最优"的token有机会被选中
  - 生成更自然的文本

2. 平衡质量和多样性

markdown 复制代码
贪心:高质量 ✅,低多样性 ❌
采样:中等质量,高多样性 ✅

采样策略的组合:
  - 温度:控制随机性程度
  - Top-p:动态选择候选集
  - 重复惩罚:避免冗余
  - 多项式采样:最终选择

四者结合 → 既保持质量,又保留多样性 ⭐

3. 实用性

markdown 复制代码
所有先进LLM都使用采样生成:
  - ChatGPT
  - Claude
  - Gemini
  - LLaMA

都是 temperature + top_p + 重复惩罚 的组合
核心优势
markdown 复制代码
避免生成单调重复文本
    ↓
保持多样性和创意性
    ↓
平衡质量和随机性
    ↓
适应不同应用场景
    ↓
提升用户体验

优化技术对比总结

技术 改进目标 实现复杂度 收益 现状
参数共享 模型大小 ⭐ 极简 参数减少50% 🔥 标准
重复惩罚 生成质量 ⭐ 极简 避免重复 🔥 必需
温度缩放 生成多样性 ⭐ 极简 可调随机性 🔥 必需
核采样 生成效率 ⭐⭐ 简单 动态候选集 🔥 推荐
多项式采样 生成方式 ⭐ 极简 保留多样性 🔥 标准

与标准GPT的改进对比

原始GPT论文

scss 复制代码
参数:嵌入矩阵 + 输出矩阵(分开)
生成:贪心解码 argmax(logits)
      → 单调、重复

MiniMind的改进

css 复制代码
参数:参数共享
      → 减少 50% 的嵌入+输出参数

生成:四阶段采样策略
      [重复惩罚] → [温度缩放] → [核采样] → [多项式采样]
      → 多样、流畅、自然

优化技术的实际效果

参数共享的显存节省

ini 复制代码
词汇表大小:32,000
模型维度:4,096

节省显存:32,000 × 4,096 × 4bytes = 0.52 GB(F32)
         32,000 × 4,096 × 2bytes = 0.26 GB(F16)

对于一个7B模型来说:节省 1-3% 显存

采样策略的生成质量对比

设置 文本流畅度 重复率 多样性 推荐度
贪心解码
Temperature=0.8 ⭐⭐
Top-p=0.9 ⭐⭐⭐
T=0.8 + p=0.9 ⭐⭐⭐⭐
T=0.8 + p=0.9 + rp=1.2 最优 最低 ⭐⭐⭐⭐⭐

相关推荐
骄傲的心别枯萎2 小时前
RV1126 NO.45:RV1126+OPENCV在视频中添加LOGO图像
人工智能·opencv·计算机视觉·音视频·rv1126
这儿有一堆花2 小时前
向工程神经网络对二进制加法的巧妙解决方案
人工智能·深度学习·神经网络
撬动未来的支点2 小时前
【AI】拆解神经网络“技术高墙”:一条基于“根本原理-补丁理论-AI部署”哲学的学习路径
人工智能·神经网络
dxnb222 小时前
【Datawhale25年11月组队学习:hello-agents+Task1学习笔记】
人工智能·学习
二进制星轨2 小时前
Transofrmer架构详解与PyTorch实现(附代码讲解)
人工智能·pytorch·python
领航猿1号3 小时前
DeepSeek-OCR 上下文光学压缩详解与本地部署及vLLM推理
人工智能·aigc·ocr
东方隐侠安全团队-千里3 小时前
第3节 RSA算法开启公钥加密时代
网络·人工智能·算法
骄傲的心别枯萎3 小时前
RV1126 NO.46:RV1126+OPENCV对视频流进行视频膨胀操作
人工智能·opencv·计算机视觉·音视频·rv1126