大模型 | QWen3 结构解析

一、简介

25/4/29 发布的Qwen3系列模型,共 8 个模型,其中六个Dense 模型分别为,Qwen3-32B、Qwen3-14B、Qwen3-8B、Qwen3-4B、Qwen3-1.7B 和 Qwen3-0.6B。另外两个MoE 模型分别为,Qwen3-235B-A22B,拥有 2350 多亿总参数和 220 多亿激活参数的大模型,以及 Qwen3-30B-A3B,拥有约 300 亿总参数和 30 亿激活参数的小型 MoE 模型。

1.1 Qwen3-2507

在社区的反馈以及进一步研究的启发下,仅指令(Instruct-only)和仅思考(Thinking-only)模型回归啦!成果就是通义千问 3-2507(Qwen3-2507),three sizes, 235B-A22B, 30B-A3B, and 4B。

Qwen3-Instruct-2507 具备以下特点:

  • 泛化能力显著提升,涵盖 指令遵循、逻辑推理、文本理解、数学、科学、编码以及工具使用。
  • 长尾知识覆盖在多种语言上大幅增强。
  • 在主观和开放式任务中与用户偏好的契合度明显提高,能够生成更有用的回复和更高质量的文本。
  • 在 25.6 万长上下文理解方面能力增强,可扩展至 100 万。

Qwen3-Thinking-2507 具备以下特点:

  • 推理任务性能显著提升,涵盖逻辑推理、数学、科学、编码以及通常需要人类专业知识的学术基准测试------在开源 thinking 模型中取得了领先的成果。
  • 泛化能力显著增强,如指令遵循、工具使用、文本生成以及与人类偏好的一致性。
  • 256K 长上下文理解能力得到强化,可扩展至 1M。

1.2 Qwen3-2504( Qwen3)

  • 全尺寸稠密与混合专家模型:0.6B, 1.7B, 4B, 8B, 14B, 32B and 30B-A3B, 235B-A22B
  • 支持在思考模式 (用于复杂逻辑推理、数学和编码)和 非思考模式 (用于高效通用对话)之间无缝切换,确保在各种场景下的最佳性能。
  • 显著增强的推理能力,在数学、代码生成和常识逻辑推理方面超越了之前的 QwQ(在思考模式下)和 Qwen2.5 指令模型(在非思考模式下)。
  • 卓越的人类偏好对齐,在创意写作、角色扮演、多轮对话和指令跟随方面表现出色,提供更自然、更吸引人和更具沉浸感的对话体验。
  • 擅长智能体能力,可以在思考和非思考模式下精确集成外部工具,在复杂的基于代理的任务中在开源模型中表现领先。
  • 支持 100 多种语言和方言,具有强大的多语言理解、推理、指令跟随和生成能力。

二、Qwen 模型结构解析

本节以 qwen3_moe 代码为例,解析一下结构。

代码路径:github.com/huggingface...

配置文件:github.com/huggingface...

2.1 总体网络结构

Qwen3 主要由四个部分组成:

  • embed_tokens:嵌入层。这是模型处理输入的第一步。它的核心功能是将输入的离散文本符号(通常是经过 Tokenizer 处理后的 Token ID)转换为连续的、稠密的向量表示(称为嵌入向量或 Embeddings)。
  • Decoder layers:多个堆叠的解码器。这是模型的核心计算引擎,负责理解输入序列的上下文、提取特征并进行深度信息处理。模型的能力(如理解、推理、生成)主要源于这些层。
Plain 复制代码
self.layers = nn.ModuleList(
            [Qwen3MoeDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
        )
  • norm:归一化层。处理完毕后,对最终的隐藏状态 (Hidden States) 进行最后一次归一化。
Plain 复制代码
self.norm = Qwen3MoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
  • rotary_emb:旋转位置编码。为模型提供关于序列中 Token 位置的信息。标准 Transformer 的自注意力机制本身是排列不变的(即打乱输入顺序可能得到相同结果),因此需要显式地注入位置信息。
Plain 复制代码
self.rotary_emb = Qwen3MoeRotaryEmbedding(config=config)

总体网络结构需要结合 Qwen3MoeModel 类来看,Qwen3MoeModel 是基于混合专家(MoE)架构的语言模型,继承自 Qwen3MoePreTrainedModel。具体代码注解如下:

Plain 复制代码
class Qwen3MoeModel(Qwen3MoePreTrainedModel):
    def __init__(self, config: Qwen3MoeConfig):
        super().__init__(config)
        self.padding_idx = config.pad_token_id
        #如 Transformer 解码器层)的特征向量维度,即每个 token 经过隐藏层处理后输出的向量长度
        self.vocab_size = config.vocab_size #151936,
        #hidden_size:2048
        self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
        #num_hidden_layers:24
        self.layers = nn.ModuleList(
            [Qwen3MoeDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
        )
        self.norm = Qwen3MoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.rotary_emb = Qwen3MoeRotaryEmbedding(config=config)
        self.gradient_checkpointing = False

        # Initialize weights and apply final processing
        self.post_init()

    @check_model_inputs
    @auto_docstring
    def forward(
        self,
        #可选的整数张量,通常是输入文本经过分词后的索引序列(如单词 / 子词的 ID),是模型最常见的输入形式。
        input_ids: Optional[torch.LongTensor] = None,
        #可选的张量,用于标记输入序列中哪些位置是有效内容(1)、哪些是填充(0),避免模型关注无效信息。
        attention_mask: Optional[torch.Tensor] = None,
        #可选的整数张量,标记每个 token 在序列中的位置,辅助模型理解语序(部分模型会自动生成)。
        position_ids: Optional[torch.LongTensor] = None,
        #可选的缓存对象,用于存储之前计算的键(key)和值(value),
        #在生成式任务(如文本续写)中加速推理,避免重复计算历史序列
        past_key_values: Optional[Cache] = None,
        #可选的浮点张量,直接输入预计算的嵌入向量(替代input_ids,适用于已处理过的特征输入)。
        inputs_embeds: Optional[torch.FloatTensor] = None,
        #指定是否采用缓存
        use_cache: Optional[bool] = None,
        #可选的整数张量,标记缓存中需要更新的位置,配合past_key_values使用。
        cache_position: Optional[torch.LongTensor] = None,
        **kwargs: Unpack[TransformersKwargs],
    ) -> MoeModelOutputWithPast:
        if (input_ids is None) ^ (inputs_embeds is not None):#异或计算,二者不可同时存在
            raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
        #使用KV cache,DynamicCache支持灵活的序列长度变化,自动扩展容量
        if use_cache and past_key_values is None:
            past_key_values = DynamicCache(config=self.config)
        #当inputs_embeds为None时(即用户未直接提供输入嵌入),
        #通过self.embed_tokens将input_ids(文本的整数编码)转换为对应的嵌入向量(inputs_embeds)。
        #self.embed_tokens通常是一个nn.Embedding层,负责将离散的 token 索引映射为连续的向量表示,
        #是语言模型中将文本转换为模型可处理的数值形式的核心步骤。
        if inputs_embeds is None:
            inputs_embeds = self.embed_tokens(input_ids)
        #确定当前输入的每个 token 在缓存中的位置,以便后续在生成文本或处理长序列时,
        #能正确关联历史缓存和当前输入,实现高效的上下文关联和缓存管理(比如 Transformer 中的 K/V 缓存)。
        if cache_position is None:
            past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
            cache_position = torch.arange(
                past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
            )
        
        if position_ids is None:
            position_ids = cache_position.unsqueeze(0)
        #选择不同的掩码函数
        mask_function = create_causal_mask if self.config.sliding_window is None else create_sliding_window_causal_mask
        causal_mask = mask_function(
            config=self.config,
            input_embeds=inputs_embeds,
            attention_mask=attention_mask,
            cache_position=cache_position,
            past_key_values=past_key_values,
            position_ids=position_ids,
        )

        hidden_states = inputs_embeds

        # create position embeddings to be shared across the decoder layers
        position_embeddings = self.rotary_emb(hidden_states, position_ids)

        for decoder_layer in self.layers[: self.config.num_hidden_layers]:
            hidden_states = decoder_layer(
                hidden_states,
                position_embeddings=position_embeddings,
                attention_mask=causal_mask,
                position_ids=position_ids,
                past_key_values=past_key_values,
                use_cache=use_cache,
                cache_position=cache_position,
                **kwargs,
            )

        hidden_states = self.norm(hidden_states)

        return MoeModelOutputWithPast(  # only diff with Mistral is the output type, we need MoE
            last_hidden_state=hidden_states,
            past_key_values=past_key_values,
        )

2.2 Qwen3MoeDecoderLayer 解析

Qwen3MoeDecoderLayer 的结构图如下所示:

下面逐个对上图中的 block 进行解释。

2.2.1 Qwen3MoeRMSNorm

  • 功能:实现 RMS 归一化,通过对输入特征的均方根进行缩放,稳定数值分布,类似 LayerNorm 但计算更轻量(不含均值中心化)。
  • 初始化:定义可学习的缩放权重 weight(维度与 hidden_size 一致)和数值稳定参数 eps(避免除零)。
  • 前向传播:
  • 装饰器:@use_kernel_forward_from_hub("RMSNorm") 表示从 hub 加载优化的 RMSNorm 内核实现(可能是高效的 C++/CUDA 算子),提升计算速度。
Plain 复制代码
@use_kernel_forward_from_hub("RMSNorm")
class Qwen3MoeRMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-6):
        """
        Qwen3MoeRMSNorm is equivalent to T5LayerNorm
        """
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.variance_epsilon = eps

    def forward(self, hidden_states):
        input_dtype = hidden_states.dtype
        hidden_states = hidden_states.to(torch.float32)
        variance = hidden_states.pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        return self.weight * hidden_states.to(input_dtype)

    def extra_repr(self):
        return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"

2.2.2 Qwen3MoeAttention

Qwen3 的注意力机制在 Qwen2 的基础上进行了微调,在 Q、K 的线性投影后面分别加入了一个归一化层,有助于提高稳定性。

2.2.3 Qwen3MoeSparseMoeBlock

我们从 Qwen3 的Transformer的实现来学习下优化方式。

将传统 Transformer 模型中的全连接 MLP 层替换为新型的稀疏专家模块(Qwen3MoeSparseMoeBlock)。该模块由以下两个关键组件构成:

  1. 包含 num_experts 个轻量级专家网络(Qwen3MoeMLP)的并行计算单元;
  2. 基于注意力机制的路由网络(gate)。在计算过程中,路由网络通过动态决策机制为每个输入 Token 生成路由决策,筛选出匹配度最高的 top_k 个专家节点。随后,系统将根据路由权重对选定专家的计算结果进行加权融合,最终生成该隐层的表征输出。

那么同样我们对比DeepSeekMOE,Qwen3MOE 有两个点的改变:1)没有 shared expert。2.优化了 MLP 架构,变为 Qwen3MoeSparseMoeBlock。

zhuanlan.zhihu.com/p/190246121...

代码解析:

Plain 复制代码
class Qwen3MoeSparseMoeBlock(nn.Module):
    """
    Qwen3混合专家模型中的稀疏MoE模块,通过路由器选择部分专家处理输入,实现高效计算
    """
    def __init__(self, config: Qwen3MoeConfig):
        super().__init__()
        # 1. 基础配置参数
        self.hidden_size = config.hidden_size  # 输入特征维度
        self.num_experts = config.num_experts  # 专家网络总数:128
        self.num_experts_per_tok = config.num_experts_per_tok  # 每个token激活的专家数:8

        # 2. 路由器(Router):决定每个token选择哪些专家
        # 输入:[batch_size, seq_len, hidden_size],输出:[batch_size, seq_len, num_experts](专家权重)
        self.gate = nn.Linear(self.hidden_size, self.num_experts, bias=False)

        # 3. 初始化专家网络(每个专家为独立的MLP)
        # 专家网络通常采用与稠密MLP相同的结构(如两次线性变换+激活函数)
        self.experts = nn.ModuleList([
            Qwen3MoeMLP(config)  # 复用Qwen3的MLP结构作为专家
            for _ in range(self.num_experts)
        ])

        # 4. 损失函数相关(可选,用于训练时平衡专家负载)
        self.router_aux_loss_coef = config.router_aux_loss_coef  # 路由器辅助损失系数

    def forward(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
        """
        前向传播:输入特征 → 路由器选专家 → 专家计算 → 融合输出
        Args:
            hidden_states: 输入特征,形状为 [batch_size, seq_len, hidden_size]
        Returns:
            output: 融合后的输出特征,形状同输入
            router_aux_loss: 路由器辅助损失(用于训练时优化专家负载均衡)
        """
        # ===== 步骤1:计算路由器输出(专家权重)=====
        # 输入通过线性层得到每个专家的原始分数(logits)
        # 形状:[batch_size, seq_len, num_experts]
        router_logits = self.gate(hidden_states)

        # ===== 步骤2:选择Top-K专家并计算权重 =====
        # 对每个token,选择分数最高的num_experts_per_tok个专家
        # top_k_weights: 选中专家的权重(经softmax归一化),形状 [batch_size, seq_len, num_experts_per_tok]
        # top_k_indices: 选中专家的索引,形状 [batch_size, seq_len, num_experts_per_tok]
        top_k_weights, top_k_indices = torch.topk(router_logits, self.num_experts_per_tok, dim=-1)
        top_k_weights = nn.functional.softmax(top_k_weights, dim=-1, dtype=torch.float32)

        # ===== 步骤3:计算路由器辅助损失(可选,训练用)=====
        # 目的是鼓励专家负载均衡,避免少数专家被频繁选中
        # 计算方式:对router_logits做softmax后取均值,再求负熵(简化实现)
        if self.training:
            # 先对专家分数做softmax,得到每个专家被选中的概率
            router_probs = nn.functional.softmax(router_logits, dim=-1, dtype=torch.float32)
            # 计算每个专家的平均负载(跨batch和seq_len)
            expert_load = torch.mean(router_probs, dim=(0, 1))  # 形状 [num_experts]
            # 辅助损失:鼓励负载均衡(熵越大,分布越均衡)
            router_aux_loss = torch.sum(expert_load * torch.log(expert_load + 1e-10))  # 负熵
            router_aux_loss *= self.router_aux_loss_coef  # 乘以系数
        else:
            router_aux_loss = torch.tensor(0.0, device=hidden_states.device)  # 推理时无损失

        # ===== 步骤4:准备输入,分发到选中的专家 =====
        # 调整输入形状为 [batch_size * seq_len, hidden_size],便于批量处理
        batch_size, seq_len, hidden_size = hidden_states.shape
        hidden_states = hidden_states.view(-1, hidden_size)  # 形状 [total_tokens, hidden_size],total_tokens = batch_size * seq_len

        # 调整选中专家索引形状:[total_tokens, num_experts_per_tok]
        top_k_indices = top_k_indices.view(-1, self.num_experts_per_tok)  # [total_tokens, k]
        # 调整权重形状:[total_tokens, num_experts_per_tok, 1](便于广播)
        top_k_weights = top_k_weights.view(-1, self.num_experts_per_tok, 1)  # [total_tokens, k, 1]

        # ===== 步骤5:专家计算与结果融合 =====
        # 初始化输出张量
        final_output = torch.zeros(
            (batch_size * seq_len, hidden_size),  # 与输入同形状
            dtype=hidden_states.dtype,
            device=hidden_states.device
        )

        # 遍历每个专家,处理所有选中该专家的token
        for expert_idx in range(self.num_experts):
            # 找到所有选中当前专家的token索引
            # 掩码:[total_tokens, k] → True表示该位置选中了当前专家
            expert_mask = (top_k_indices == expert_idx)  # [total_tokens, k]
            # 若没有token选中当前专家,跳过
            if not expert_mask.any():
                continue

            # 收集选中当前专家的token及其对应的权重
            # 1. 提取这些token的输入特征
            expert_input = hidden_states[expert_mask.any(dim=1)]  # [num_tokens_for_this_expert, hidden_size]
            # 2. 提取这些token对当前专家的权重(取第一个匹配的权重,因每个位置最多选k个专家)
            expert_weights = top_k_weights[expert_mask]  # [num_tokens_for_this_expert, 1]

            # 3. 当前专家处理输入
            expert_output = self.experts[expert_idx](expert_input)  # [num_tokens_for_this_expert, hidden_size]
            # 4. 加权:用该专家的权重乘以输出
            expert_output = expert_output * expert_weights  # [num_tokens_for_this_expert, hidden_size]

            # 5. 将结果累加至最终输出(对应位置)
            final_output[expert_mask.any(dim=1)] += expert_output

        # ===== 步骤6:恢复输出形状并返回 =====
        final_output = final_output.view(batch_size, seq_len, hidden_size)  # [batch_size, seq_len, hidden_size]
        return final_output, router_aux_loss

三、QWen3 部署实战

你可以在 Hugging Face Hub 的 Qwen3 collection 或 ModelScope 的 Qwen3 collection 中获取 Qwen3 模型。

Plain 复制代码
使用 huggingface-cli 命令行工具:
安装依赖:首先确保已安装huggingface_hub,可运行命令pip install -U huggingface_hub。
设置镜像地址:为提高下载速度,可设置国内镜像,如export HF_ENDPOINT=https://hf-mirror.com(Linux 系统)。
下载模型:使用huggingface-cli download命令下载,例如huggingface-cli download --resume-download gpt2 --local-dir gpt2,将gpt2模型下载到当前目录下的gpt2文件夹中。若要下载特定文件,可在模型名称后添加文件名,如huggingface-cli download gpt2 config.json。
Plain 复制代码
huggingface-cli download Qwen/Qwen3-4B-Thinking-2507 --local-dir Qwen3-4B-Thinking-2507

huggingface 中下载的文件:

Plain 复制代码
├── config.json
├── generation_config.json
├── LICENSE
├── merges.txt
├── model-00001-of-00003.safetensors
├── model-00002-of-00003.safetensors
├── model-00003-of-00003.safetensors
├── model.safetensors.index.json
├── README.md
├── tokenizer_config.json
├── tokenizer.json
└── vocab.json

文件说明

  • config.json
    • 模型的结构配置文件,比如隐藏层维度、层数、注意力头数、激活函数等。
    • AutoModelForCausalLM.from_pretrained() 会先读取它,决定用哪种架构初始化模型。
  • generation_config.json
    • 文本生成时的默认参数,例如 max_new_tokenstemperaturetop_pdo_sample 等。
    • 如果你用 model.generate(),没手动传参数,就会用这里的默认值。
  • merges.txt
    • BPE (Byte Pair Encoding) 分词的合并规则文件。
    • vocab.json 一起定义了 tokenizer 的词表。
  • vocab.json
    • tokenizer 的词表文件,存储了 token 到 ID 的映射。
    • 例如 "hello" -> 1234
  • tokenizer.json
    • Hugging Face 的标准 tokenizer 文件,包含 vocab 和 merges 的完整定义。
    • AutoTokenizer.from_pretrained() 会加载它。
  • tokenizer_config.json
    • Tokenizer 的额外参数,比如是否大小写敏感、padding/truncation 策略等。
  • model-00001-of-00003.safetensors, ​ model-00002-of-00003.safetensors, ​ model-00003-of-00003.safetensors
    • 模型的权重文件,分成了多个分片,每个几 GB。
    • safetensors 是一种比 pytorch_model.bin 更安全和高效的格式。
  • model.safetensors.index.json
    • 权重索引文件,指明每个参数在分片文件中的位置。
    • 加载模型时,transformers 会先读这个文件,再去对应分片里加载。

将 huggingface 中的文件全部下载到 "。/Qwen/Qwen3-4B-Thinking-2507"文件夹

在 docker 环境中安装(torch 2.3.0):

Plain 复制代码
pip3 install transformers==4.55.0
pip3 install accelerate

部署代码:

Plain 复制代码
from transformers import AutoModelForCausalLM, AutoTokenizer
#下载的权重文件的路径
model_name = "./Qwen/Qwen3-4B-Thinking-2507"

# load the tokenizer and the model
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",
    device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# prepare the model input
prompt = "你怎么认为普京"
messages = [
    {"role": "user", "content": prompt},
]
text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
    enable_thinking=True, # Switches between thinking and non-thinking modes. Default is True.
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)

# conduct text completion
g
相关推荐
Swift社区14 小时前
LeetCode 465 最优账单平衡
算法·leetcode·职场和发展
聆风吟º14 小时前
【数据结构手札】空间复杂度详解:概念 | 习题
java·数据结构·算法
weixin_4450547214 小时前
力扣热题51
c++·python·算法·leetcode
地平线开发者14 小时前
linux 常见稳定性问题分析方法
算法·自动驾驶
s砚山s15 小时前
代码随想录刷题——二叉树篇(九)
算法
地平线开发者15 小时前
大模型常见量化方法简介
算法·自动驾驶
smj2302_7968265217 小时前
解决leetcode第3801题合并有序列表的最小成本
数据结构·python·算法·leetcode
栗少18 小时前
英语自学手册:系统化进阶指南基于《英语自学手册》的方法论与行动路径
人工智能·算法
Xの哲學18 小时前
深入解析 Linux systemd: 现代初始化系统的设计与实现
linux·服务器·网络·算法·边缘计算
sinat_2554878118 小时前
InputStream/OutputStream小讲堂
java·数据结构·算法