Skip to content

7.2 Qwen3 全规模实现

前一节介绍了从 GPT-2 到现代 LLM 架构的渐进式改造路径:RoPE 替换绝对位置编码、RMSNorm 替换 LayerNorm、SwiGLU 替换 GELU 前馈网络、移除所有线性层偏置项。这些改造并非纸上谈兵——Qwen3 系列正是这一现代化架构的量产级实现。Qwen3 覆盖从 0.6B 到 32B 的稠密模型、30B-A3B 的 MoE 模型,以及支持思维链 Thinking Tokens 的 Reasoning 变体,三种形态共享同一套核心组件,仅在前馈网络和推理模式上有所分化。

本节将给出 Qwen3 的完整架构代码,逐一剖析稠密版本、MoE 版本和 Reasoning 变体的实现细节,并通过对比表总结三者的异同。

Qwen3 Dense 模型架构示意

图 7-5:Qwen3 Dense 模型架构。采用 RMSNorm + SwiGLU + RoPE + GQA 的现代化配置,覆盖 0.6B 到 32B 的完整参数规模。

7.2.1 核心组件:四大改造的统一实现

Qwen3 的所有变体共享同一套核心组件:RMSNorm、RoPE、SwiGLU 前馈网络和分组查询注意力(GQA)。以下代码给出了完整的自包含实现。

RMSNorm。 Qwen3 在注意力层前后均使用 Pre-Norm 布局的 RMSNorm。实现中有一个与 Qwen3 官方权重兼容的细节:在计算方差前先将输入转换为 float32,归一化后再转回原始精度。这一步在使用 bfloat16 训练时至关重要——bfloat16 仅有 8 位尾数,直接在低精度下计算 pow(2).mean() 会导致显著的精度损失:

python
class RMSNorm(nn.Module):
    def __init__(self, emb_dim, eps=1e-6):
        super().__init__()
        self.eps = eps
        self.scale = nn.Parameter(torch.ones(emb_dim))

    def forward(self, x):
        input_dtype = x.dtype
        x = x.to(torch.float32)  # 关键:在 float32 下计算方差
        variance = x.pow(2).mean(dim=-1, keepdim=True)
        norm_x = x * torch.rsqrt(variance + self.eps)
        return (norm_x * self.scale).to(input_dtype)

与第六章介绍的通用 RMSNorm 相比,Qwen3 的实现有两点值得注意:(1)没有 shift 参数(即 bias=False),这与"移除所有偏置项"的设计原则一致;(2)scale 参数初始化为全 1 向量,保证初始化时 RMSNorm 接近恒等映射。

RoPE(旋转位置编码)。 Qwen3 使用的 RoPE 与 §6.4 中介绍的标准实现一致,但 theta_base 配置有所不同:稠密版本使用 θ=1,000,000,MoE 版本使用 θ=10,000,000。更大的 θ 值使频率衰减更缓慢,相邻位置的编码差异更小,从而让模型在超长上下文中保持更好的位置分辨率:

python
def compute_rope_params(head_dim, theta_base=10_000,
                        context_length=4096, dtype=torch.float32):
    assert head_dim % 2 == 0
    inv_freq = 1.0 / (theta_base ** (
        torch.arange(0, head_dim, 2, dtype=dtype)[: (head_dim // 2)].float()
        / head_dim
    ))
    positions = torch.arange(context_length, dtype=dtype)
    angles = positions[:, None] * inv_freq[None, :]
    angles = torch.cat([angles, angles], dim=1)
    return torch.cos(angles), torch.sin(angles)


def apply_rope(x, cos, sin):
    # x: (batch, num_heads, seq_len, head_dim)
    batch_size, num_heads, seq_len, head_dim = x.shape
    x1 = x[..., : head_dim // 2]
    x2 = x[..., head_dim // 2 :]
    cos = cos[:seq_len, :].unsqueeze(0).unsqueeze(0)
    sin = sin[:seq_len, :].unsqueeze(0).unsqueeze(0)
    rotated = torch.cat((-x2, x1), dim=-1)
    return ((x * cos) + (rotated * sin)).to(dtype=x.dtype)

分组查询注意力(GQA)与 QKNorm。 Qwen3 全系列使用 GQA,且所有规模均固定 head_dim=128n_kv_groups=8。一个关键的新增组件是 QKNorm——在将 Query 和 Key 送入 RoPE 之前,先各自通过一个 RMSNorm 层进行归一化。QKNorm 的作用是防止注意力 logits 在深层网络中爆炸:当模型层数达到 64 层(如 Qwen3-32B)时,未归一化的 Q/K 向量的范数可能随层数增长,导致注意力分数溢出 bfloat16 的表示范围。QKNorm 将每个头的 Q/K 向量归一化到单位方差,从根源上消除了这一问题:

python
class GroupedQueryAttention(nn.Module):
    def __init__(self, d_in, num_heads, num_kv_groups,
                 head_dim=None, qk_norm=False, dtype=None):
        super().__init__()
        assert num_heads % num_kv_groups == 0
        self.num_heads = num_heads
        self.num_kv_groups = num_kv_groups
        self.group_size = num_heads // num_kv_groups

        if head_dim is None:
            head_dim = d_in // num_heads
        self.head_dim = head_dim
        self.d_out = num_heads * head_dim

        self.W_query = nn.Linear(d_in, self.d_out, bias=False, dtype=dtype)
        self.W_key = nn.Linear(
            d_in, num_kv_groups * head_dim, bias=False, dtype=dtype
        )
        self.W_value = nn.Linear(
            d_in, num_kv_groups * head_dim, bias=False, dtype=dtype
        )
        self.out_proj = nn.Linear(self.d_out, d_in, bias=False, dtype=dtype)

        # QKNorm:对 Q 和 K 分别做 RMSNorm
        if qk_norm:
            self.q_norm = RMSNorm(head_dim, eps=1e-6)
            self.k_norm = RMSNorm(head_dim, eps=1e-6)
        else:
            self.q_norm = self.k_norm = None

    def forward(self, x, mask, cos, sin):
        b, num_tokens, _ = x.shape
        queries = self.W_query(x)
        keys = self.W_key(x)
        values = self.W_value(x)

        queries = queries.view(
            b, num_tokens, self.num_heads, self.head_dim
        ).transpose(1, 2)
        keys = keys.view(
            b, num_tokens, self.num_kv_groups, self.head_dim
        ).transpose(1, 2)
        values = values.view(
            b, num_tokens, self.num_kv_groups, self.head_dim
        ).transpose(1, 2)

        # QKNorm 在 RoPE 之前
        if self.q_norm:
            queries = self.q_norm(queries)
        if self.k_norm:
            keys = self.k_norm(keys)

        queries = apply_rope(queries, cos, sin)
        keys = apply_rope(keys, cos, sin)

        # 扩展 KV 头以匹配 Q 头数
        keys = keys.repeat_interleave(self.group_size, dim=1)
        values = values.repeat_interleave(self.group_size, dim=1)

        attn_scores = queries @ keys.transpose(2, 3)
        attn_scores = attn_scores.masked_fill(mask, -torch.inf)
        attn_weights = torch.softmax(
            attn_scores / self.head_dim**0.5, dim=-1
        )
        context = (attn_weights @ values).transpose(1, 2).reshape(
            b, num_tokens, self.d_out
        )
        return self.out_proj(context)

注意力计算中的一个实现细节:缩放因子 self.head_dim**0.5 使用的是固定的 head_dim=128,而非 emb_dim // num_heads。由于 Qwen3 显式指定了 head_dim,Q 投影的输出维度为 num_heads * head_dim,当 emb_dim 不能被 num_heads 整除时(如 0.6B 的 emb_dim=1024num_heads=16head_dim=128,Q 输出维度为 2048 而非 1024),这种设计避免了维度不匹配的问题。

7.2.2 稠密版本:SwiGLU 前馈网络

Qwen3 RoPE 与困惑度的关系

图 7-6:RoPE 基频对困惑度的影响。更大的 θbase 使旋转角度变化更缓慢,有助于模型在超长上下文中保持稳定的困惑度。

稠密版本使用标准的 SwiGLU 前馈网络。SwiGLU 由两条并行的线性投影构成:一条经过 SiLU 激活作为"门控",另一条保持线性,两者逐元素相乘后再通过一个下投影层:

python
class FeedForward(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.fc1 = nn.Linear(
            cfg["emb_dim"], cfg["hidden_dim"],
            dtype=cfg["dtype"], bias=False
        )  # gate_proj
        self.fc2 = nn.Linear(
            cfg["emb_dim"], cfg["hidden_dim"],
            dtype=cfg["dtype"], bias=False
        )  # up_proj
        self.fc3 = nn.Linear(
            cfg["hidden_dim"], cfg["emb_dim"],
            dtype=cfg["dtype"], bias=False
        )  # down_proj

    def forward(self, x):
        return self.fc3(nn.functional.silu(self.fc1(x)) * self.fc2(x))

这三个线性层在 Qwen3 的权重文件中分别对应 gate_proj(fc1)、up_proj(fc2)和 down_proj(fc3)。SwiGLU 的参数量为 3×dmodel×dhidden,由于多了一条并行投影,比标准 FFN 的 2×dmodel×dhidden 多出 50%。为保持总参数量可比,Qwen3 将 hidden_dim 设为 emb_dim 的约 3 倍(而非标准 FFN 常用的 4 倍)。

Transformer 块与完整模型。 稠密版本的 Transformer 块遵循 Pre-Norm + 残差连接的标准布局:

python
class TransformerBlock(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.att = GroupedQueryAttention(
            d_in=cfg["emb_dim"], num_heads=cfg["n_heads"],
            head_dim=cfg["head_dim"], num_kv_groups=cfg["n_kv_groups"],
            qk_norm=cfg["qk_norm"], dtype=cfg["dtype"]
        )
        self.ff = FeedForward(cfg)
        self.norm1 = RMSNorm(cfg["emb_dim"], eps=1e-6)
        self.norm2 = RMSNorm(cfg["emb_dim"], eps=1e-6)

    def forward(self, x, mask, cos, sin):
        shortcut = x
        x = self.att(self.norm1(x), mask, cos, sin)
        x = x + shortcut
        shortcut = x
        x = self.ff(self.norm2(x))
        x = x + shortcut
        return x


class Qwen3Model(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.tok_emb = nn.Embedding(
            cfg["vocab_size"], cfg["emb_dim"], dtype=cfg["dtype"]
        )
        self.trf_blocks = nn.ModuleList(
            [TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
        )
        self.final_norm = RMSNorm(cfg["emb_dim"])
        self.out_head = nn.Linear(
            cfg["emb_dim"], cfg["vocab_size"],
            bias=False, dtype=cfg["dtype"]
        )

        head_dim = cfg["head_dim"] or cfg["emb_dim"] // cfg["n_heads"]
        cos, sin = compute_rope_params(
            head_dim=head_dim,
            theta_base=cfg["rope_base"],
            context_length=cfg["context_length"]
        )
        self.register_buffer("cos", cos, persistent=False)
        self.register_buffer("sin", sin, persistent=False)
        self.cfg = cfg

    def forward(self, in_idx):
        x = self.tok_emb(in_idx)
        num_tokens = x.shape[1]
        mask = torch.triu(torch.ones(
            num_tokens, num_tokens,
            device=x.device, dtype=torch.bool
        ), diagonal=1)
        for block in self.trf_blocks:
            x = block(x, mask, self.cos, self.sin)
        x = self.final_norm(x)
        return self.out_head(x.to(self.cfg["dtype"]))

注意 Qwen3Model 的最后一步 x.to(self.cfg["dtype"])final_norm 内部会将输入转为 float32 计算,输出也是 float32,但 out_head 的权重是 bfloat16,为避免精度不匹配,需要显式转换。

稠密版本全系列配置。 以下列出 Qwen3 稠密模型从 0.6B 到 32B 的完整配置,所有模型共享 vocab_size=151,936head_dim=128n_kv_groups=8qk_norm=Truerope_base=1,000,000

规模emb_dimn_headsn_layershidden_dimcontext_length总参数量唯一参数量
0.6B1,02416283,07240,9607.52 亿5.96 亿
1.7B2,04816286,14440,960
4B2,56032369,72840,960
8B4,096323612,28840,960
14B5,120404017,40840,960
32B5,120646425,60040,960

从 0.6B 到 32B,模型的缩放路径遵循一个清晰的模式:emb_dim 从 1,024 增长到 5,120(5 倍),n_layers 从 28 增长到 64(2.3 倍),n_heads 从 16 增长到 64(4 倍)。其中 14B 和 32B 共享相同的 emb_dim=5,120,区别在于 32B 使用了更多的层(64 vs. 40)和更多的注意力头(64 vs. 40)。

0.6B 模型的"总参数量"(7.52 亿)与"唯一参数量"(5.96 亿)之间的差异来自 Weight Tyingtok_emb(嵌入层)和 out_head(输出投影层)共享同一组权重矩阵。共享的权重数量为 151,936×1,0241.56 亿,恰好等于两者之差。Weight Tying 在小模型中效果显著——节省 20% 参数量的同时几乎不损失性能;但在大模型中(如 32B),嵌入维度与词表大小的乘积相对总参数量占比很小,因此通常不再使用。

7.2.3 MoE 版本:稀疏前馈网络

MoE Transformer 块结构

图 7-7:MoE Transformer 块结构。在标准 Transformer 块的 FFN 位置替换为 MoE 层,路由器为每个 token 选择 Top-K 个专家并行计算。

Qwen3 的 MoE 版本(30B-A3B)在架构上与稠密版本仅有一处不同:将每个 Transformer 块中的 FeedForward 替换为 MoEFeedForward。注意力层、RMSNorm、RoPE 等组件完全相同。

MoE 前馈网络。 MoE 的核心思想是:维护多个并行的"专家"前馈网络,每次前向传播只激活其中一小部分。30B-A3B 模型共有 128 个专家,每个 token 仅激活 8 个(num_experts_per_tok=8),通过一个线性门控层(router)选择激活哪些专家:

python
class MoEFeedForward(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.num_experts_per_tok = cfg["num_experts_per_tok"]
        self.num_experts = cfg["num_experts"]
        self.emb_dim = cfg["emb_dim"]
        # Router:将输入映射到 num_experts 维的 logits
        self.gate = nn.Linear(
            cfg["emb_dim"], cfg["num_experts"],
            bias=False, dtype=cfg["dtype"]
        )
        # 每个专家是一个独立的 SwiGLU FFN
        self.fc1 = nn.ModuleList([
            nn.Linear(cfg["emb_dim"], cfg["moe_hidden_dim"],
                      bias=False, dtype=cfg["dtype"])
            for _ in range(cfg["num_experts"])
        ])
        self.fc2 = nn.ModuleList([
            nn.Linear(cfg["emb_dim"], cfg["moe_hidden_dim"],
                      bias=False, dtype=cfg["dtype"])
            for _ in range(cfg["num_experts"])
        ])
        self.fc3 = nn.ModuleList([
            nn.Linear(cfg["moe_hidden_dim"], cfg["emb_dim"],
                      bias=False, dtype=cfg["dtype"])
            for _ in range(cfg["num_experts"])
        ])

    def forward(self, x):
        # Router 计算:选择 top-k 专家
        scores = self.gate(x)  # (b, seq_len, num_experts)
        topk_scores, topk_indices = torch.topk(
            scores, self.num_experts_per_tok, dim=-1
        )
        topk_probs = torch.softmax(topk_scores, dim=-1)

        batch, seq_len, _ = x.shape
        x_flat = x.reshape(batch * seq_len, -1)
        out_flat = torch.zeros(
            batch * seq_len, self.emb_dim,
            device=x.device, dtype=x.dtype
        )

        topk_indices_flat = topk_indices.reshape(
            -1, self.num_experts_per_tok
        )
        topk_probs_flat = topk_probs.reshape(
            -1, self.num_experts_per_tok
        )

        # 按专家分组处理,避免逐 token 循环
        for expert_id in torch.unique(topk_indices_flat).tolist():
            mask = topk_indices_flat == expert_id
            token_mask = mask.any(dim=-1)
            selected_idx = token_mask.nonzero(as_tuple=False).squeeze(-1)
            if selected_idx.numel() == 0:
                continue

            expert_input = x_flat.index_select(0, selected_idx)
            # 每个专家内部仍是 SwiGLU
            hidden = (nn.functional.silu(self.fc1[expert_id](expert_input))
                      * self.fc2[expert_id](expert_input))
            expert_out = self.fc3[expert_id](hidden)

            # 加权累加:每个 token 的输出是其激活专家的加权和
            mask_sel = mask[selected_idx]
            slot_idx = mask_sel.int().argmax(dim=-1, keepdim=True)
            probs = torch.gather(
                topk_probs_flat.index_select(0, selected_idx),
                dim=-1, index=slot_idx
            ).squeeze(-1)
            out_flat.index_add_(
                0, selected_idx,
                expert_out * probs.unsqueeze(-1)
            )

        return out_flat.reshape(batch, seq_len, self.emb_dim)

Router 机制解读。 self.gate 是一个 emb_dim -> num_experts 的线性层,对每个 token 输出 128 维的 logits。torch.topk 选出得分最高的 8 个专家,再对这 8 个得分做 Softmax 得到归一化权重。最终输出是 8 个专家输出的加权和。这种"Top-K + Softmax"的路由策略是 MoE 的标准做法。

按专家分组处理。 上述实现采用"按专家遍历"而非"按 token 遍历"的策略:外层循环遍历被激活的唯一专家 ID,内层通过 index_select 收集该专家需要处理的所有 token,批量执行前向传播,再通过 index_add_ 将结果写回。这种策略在 GPU 上更高效,因为同一专家处理的 token 可以组成一个连续的 mini-batch,充分利用矩阵乘法的并行性。

MoE 版本的 TransformerBlock。 仅在初始化时根据配置选择前馈网络类型:

python
class TransformerBlock(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.att = GroupedQueryAttention(...)  # 与稠密版本相同
        if cfg["num_experts"] > 0:
            self.ff = MoEFeedForward(cfg)
        else:
            self.ff = FeedForward(cfg)
        self.norm1 = RMSNorm(cfg["emb_dim"], eps=1e-6)
        self.norm2 = RMSNorm(cfg["emb_dim"], eps=1e-6)

Qwen3Model 的代码无需任何修改——它只通过 TransformerBlock 间接使用前馈网络,而 TransformerBlock 已根据配置自动选择了正确的类型。

30B-A3B 的完整配置:

python
QWEN3_CONFIG_30B_A3B = {
    "vocab_size": 151_936,
    "context_length": 262_144,     # 256K 上下文
    "emb_dim": 2048,
    "n_heads": 32,
    "n_layers": 48,
    "head_dim": 128,
    "qk_norm": True,
    "n_kv_groups": 4,              # 注意:这里是 4,而非稠密版本的 8
    "rope_base": 10_000_000.0,     # 10M,比稠密版本大 10 倍
    "dtype": torch.bfloat16,
    "num_experts": 128,
    "num_experts_per_tok": 8,
    "moe_hidden_dim": 768,
}

几个关键数值值得对比:

对比维度稠密 8BMoE 30B-A3B
总参数量~80 亿~305 亿
每 token 激活参数量~80 亿~30 亿
单专家 hidden_dim12,288768
专家数量1128(激活 8)
n_kv_groups84
rope_base1,000,00010,000,000
context_length40,960262,144

MoE 版本的核心优势在于:总参数量达到 305 亿,赋予模型更大的知识容量,但每个 token 仅激活约 30 亿参数(8 个专家 + 注意力层 + 嵌入层),推理计算量与稠密 3B 模型相当。这就是"30B-A3B"命名的含义——30B 总参数,3B 激活参数。

每个专家的 moe_hidden_dim=768 远小于稠密模型的 hidden_dim(如 8B 的 12,288),这是因为每次前向传播有 8 个专家并行工作,总的"有效 hidden_dim"为 8×768=6,144,接近稠密 1.7B 模型的 hidden_dim

MoE 版本还将 n_kv_groups 从 8 减少到 4,意味着 KV 头数更少、KV 缓存更小——这对 256K 的超长上下文尤为重要。同时 rope_base 增大 10 倍至 107,以支持更长的上下文窗口。

7.2.4 Reasoning 变体:Thinking Tokens 支持

Qwen3-235B MoE 路由策略

图 7-8:Qwen3 MoE 的路由策略。128 个专家中每个 token 激活 8 个,路由器输出 128 维 logits,Top-K 选择后 Softmax 归一化得到专家权重。

Qwen3 的 Reasoning 变体在模型架构上与非 Reasoning 版本完全相同——没有任何额外的参数或层。区别完全在推理时的 token 模板和训练阶段的强化学习。

三种使用模式。 Qwen3 的非 Base 模型(即经过指令微调和强化学习的模型)支持三种使用模式:

  1. Reasoning 模式:模型在生成正式回答前,先输出一段包裹在 <think>...</think> 标签中的思维链推理过程。这些 Thinking Tokens 对用户可见,但不计入最终回答。
  2. Instruct 模式:通过在提示中预置空的 <think>\n\n</think> 标签,抑制模型的思维链输出,使其直接给出简洁回答。
  3. Base 模式:不使用聊天模板,直接续写输入文本。

这三种模式的切换完全由 Tokenizer 的聊天模板控制:

python
class Qwen3Tokenizer:
    def _wrap_chat(self, user_msg):
        s = f"<|im_start|>user\n{user_msg}<|im_end|>\n"
        if self.add_generation_prompt:
            s += "<|im_start|>assistant"
            if self.add_thinking:
                # Reasoning 模式:让模型自由开始思考
                s += "\n"
            else:
                # Instruct 模式:预置空 think 标签,抑制推理
                s += "\n<think>\n\n</think>\n\n"
        return s

Reasoning 模式的输入格式:

<|im_start|>user
Give me a short introduction to large language models.<|im_end|>
<|im_start|>assistant

模型看到这个输入后,会自主生成 <think> 标签并开始推理,最终关闭 </think> 后给出正式回答。

Instruct 模式的输入格式:

<|im_start|>user
Give me a short introduction to large language models.<|im_end|>
<|im_start|>assistant
<think>

</think>

通过预置空的 <think></think> 标签,模型被"告知"思考阶段已经结束,直接进入回答。这一技巧利用了模型在训练中学到的 </think> 后面应该是正式回答的模式。

Thinking Tokens 的本质。 从模型的视角看,<think></think> 只是词表中的两个特殊 token(ID 分别对应 tokenizer.json 中的条目)。模型在 Reasoning 训练阶段(通常使用 GRPO 等强化学习算法)学会了:

  • <think> 后输出详细的推理过程(问题分解、假设验证、自我纠错等)
  • 在推理完成后输出 </think> 标记结束
  • </think> 后输出简洁、准确的最终回答

这种设计的巧妙之处在于:不需要修改任何模型架构,仅通过特殊 token 和训练目标就实现了"思考-回答"的两阶段生成。

7.2.5 三种变体对比总结

下表从架构、配置和使用方式三个维度总结 Qwen3 三种变体的异同:

维度稠密版本(0.6B–32B)MoE 版本(30B-A3B)Reasoning 变体
注意力层GQA + QKNorm + RoPE相同相同
前馈网络SwiGLU FFNMoE SwiGLU(128 专家,激活 8)与底层模型相同
归一化RMSNorm (Pre-Norm)相同相同
偏置项全部移除相同相同
head_dim128128128
n_kv_groups84与底层模型相同
rope_base1,000,00010,000,000与底层模型相同
context_length40,960262,144与底层模型相同
总参数量0.6B–32B~30.5B与底层模型相同
激活参数量= 总参数量~3B与底层模型相同
Weight Tying小模型使用使用与底层模型相同
架构改动无(基线)仅替换 FFN 为 MoE FFN无任何架构改动
训练差异预训练 + SFT预训练 + SFT预训练 + SFT + RL(如 GRPO)
推理模式Base / InstructBase / Instruct / ReasoningBase / Instruct / Reasoning
特殊 token标准聊天模板标准聊天模板<think>/</think> Thinking Tokens

Qwen3 MoE 架构示意

图 7-9:Qwen3 MoE 架构。在 Dense 基础上将 FFN 替换为 MoE 层,每个 token 仅激活 Top-K 个专家,实现更大模型容量与高效推理的平衡。

本节小结

本节以完整的自包含代码给出了 Qwen3 全系列的架构实现:

  • 共享核心:所有变体共享 RMSNorm(float32 计算方差)+ RoPE(θ106107)+ SwiGLU + GQA with QKNorm 的组合。全部线性层移除偏置项,使用 bfloat16 精度,Pre-Norm 残差连接布局。
  • 稠密版本(0.6B–32B):标准 SwiGLU 前馈网络,hidden_dim 约为 emb_dim 的 3 倍。从 0.6B 到 32B 的缩放主要通过增加 emb_dim(1,024→5,120)、n_layers(28→64)和 n_heads(16→64)实现。小模型使用 Weight Tying 节省参数。
  • MoE 版本(30B-A3B):将 SwiGLU FFN 替换为 128 专家的 MoE FFN,每个 token 激活 8 个专家。总参数 305 亿但激活参数仅约 30 亿,推理计算量接近稠密 3B 模型。Router 使用 Top-K + Softmax 策略。n_kv_groups 减至 4、rope_base 增至 107,支持 256K 上下文。
  • Reasoning 变体:架构与底层模型完全相同,通过 <think>/</think> 特殊 token 和强化学习训练实现思维链推理。三种使用模式(Reasoning / Instruct / Base)完全由 Tokenizer 的聊天模板控制,无需切换模型权重。