博客 / 詳情

返回

大模型 | QWen3 結構解析

一、簡介

25/4/29 發佈的<u> Qwen3 </u>系列模型,共 8 個模型,其中六個<u> Dense 模型</u>分別為,Qwen3-32B、Qwen3-14B、Qwen3-8B、Qwen3-4B、Qwen3-1.7B 和 Qwen3-0.6B。另外兩個<u> MoE 模型</u>分別為,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 代碼為例,解析一下結構。

代碼路徑:https://github.com/huggingface/transformers/blob/main/src/transformers/models/qwen3_moe/modeling_qwen3_moe.py

配置文件:https://github.com/huggingface/transformers/blob/main/src/transformers/models/qwen3_moe/configuration_qwen3_moe.py

2.1 總體網絡結構

Qwen3 主要由四個部分組成:

  • embed\_tokens:嵌入層。這是模型處理輸入的第一步。它的核心功能是將輸入的離散文本符號(通常是經過 Tokenizer 處理後的 Token ID)轉換為連續的、稠密的向量表示(稱為嵌入向量或 Embeddings)。
  • Decoder layers:多個堆疊的解碼器。這是模型的核心計算引擎,負責理解輸入序列的上下文、提取特徵並進行深度信息處理。模型的能力(如理解、推理、生成)主要源於這些層。
self.layers = nn.ModuleList(
            [Qwen3MoeDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
        )
  • norm:歸一化層。處理完畢後,對最終的隱藏狀態 (Hidden States) 進行最後一次歸一化。
self.norm = Qwen3MoeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
  • rotary\_emb:旋轉位置編碼。為模型提供關於序列中 Token 位置的信息。標準 Transformer 的自注意力機制本身是排列不變的(即打亂輸入順序可能得到相同結果),因此需要顯式地注入位置信息。
self.rotary_emb = Qwen3MoeRotaryEmbedding(config=config)

總體網絡結構需要結合 Qwen3MoeModel 類來看,Qwen3MoeModel 是基於混合專家(MoE)架構的語言模型,繼承自 Qwen3MoePreTrainedModel。具體代碼註解如下:

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 算子),提升計算速度。
@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 的<u> Transformer </u>的實現來學習下優化方式。

將傳統 Transformer 模型中的全連接 MLP 層替換為新型的稀疏專家模塊(Qwen3MoeSparseMoeBlock)。該模塊由以下兩個關鍵組件構成:

  1. 包含 num\_experts 個輕量級專家網絡(Qwen3MoeMLP)的並行計算單元;
  2. 基於注意力機制的<u>路由網絡</u>(gate)。在計算過程中,路由網絡通過動態決策機制為每個輸入 Token 生成路由決策,篩選出匹配度最高的 top\_k 個專家節點。隨後,系統將根據路由權重對選定專家的計算結果進行加權融合,最終生成該隱層的表徵輸出。

那麼同樣我們對比<u> DeepSeekMOE</u>,Qwen3MOE 有兩個點的改變:1)沒有 shared expert。2.優化了 MLP 架構,變為 Qwen3MoeSparseMoeBlock。

https://zhuanlan.zhihu.com/p/1902461213255925825

代碼解析:

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 的 <u>Qwen3 collection</u> 或 ModelScope 的 <u>Qwen3 collection</u> 中獲取 Qwen3 模型。

使用 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。
huggingface-cli download Qwen/Qwen3-4B-Thinking-2507 --local-dir Qwen3-4B-Thinking-2507

huggingface 中下載的文件:

├── 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):

pip3 install transformers==4.55.0
pip3 install accelerate

部署代碼:

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
generated_ids = model.generate(
    **model_inputs,
    max_new_tokens=32768
)
output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist() 

# parse thinking content
try:
    # rindex finding 151668 (</think>)
    index = len(output_ids) - output_ids[::-1].index(151668)
except ValueError:
    index = 0

thinking_content = tokenizer.decode(output_ids[:index], skip_special_tokens=True).strip("\n")
content = tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip("\n")

print("thinking content:", thinking_content)
print("content:", content)

四、參考鏈接

https://github.com/QwenLM/Qwen3

https://zhuanlan.zhihu.com/p/1901014191235633835

https://zhuanlan.zhihu.com/p/1902019286836449827

https://qwen.readthedocs.io/zh-cn/latest/

https://github.com/huggingface/transformers

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.