首頁 > 科技週邊 > 人工智慧 > 如何修剪美洲駝3.2和類似的大語言模型

如何修剪美洲駝3.2和類似的大語言模型

王林
發布: 2025-02-25 18:26:08
原創
350 人瀏覽過

大模型的規模不斷擴大以提升性能,但對更高效、更小巧模型的需求也日益增長。然而,在不損失核心功能的前提下縮減模型規模是一項複雜的任務。

量化和剪枝等技術常用於減小模型大小,而知識蒸餾或遷移學習等方法則有助於保留或恢復縮減過程中損失的功能。

How to Prune LLaMA 3.2 and Similar Large Language Models其中,剪枝是縮減模型規模最有效的策略之一。與簡化數值表示的量化不同,剪枝涉及移除模型的特定部分,例如神經元或整個層。但這種有效性是有代價的:剪枝難以正確應用。您不僅需要確定要剪枝的模型部分,還需要仔細選擇要移除的元素,以最大限度地減少對模型能力的影響。

本文重點介紹結構化寬度剪枝(移除選定的神經元),並演示如何將其有效地應用於具有門控線性單元 (GLU) 結構的 MLP 層。通過遵循概述的步驟,您將了解剪枝如何顯著減小模型大小,同時保留其生成連貫輸出和在關鍵基準測試中表現良好的能力。

什麼是剪枝以及它如何影響模型?

如前所述,剪枝涉及移除被認為對模型最終輸出貢獻最小的部分。通過仔細選擇這些不太重要的組件,剪枝旨在創建一個更有效的模型,該模型具有更少的參數和更低的計算需求,而不會犧牲其核心能力。

剪枝的主要挑戰在於決定要移除模型的哪些部分。模型並非所有部分對性能的影響都相同;每個部分都有其獨特的作用。

為了說明這一點,讓我們檢查一下本文中使用的模型的結構:Llama 3.2–1B。

<code>LlamaForCausalLM(
  (model): LlamaModel(
    (embed_tokens): Embedding(128256, 2048)
    (layers): ModuleList(
      (0-15): 16 x LlamaDecoderLayer(
        (self_attn): LlamaSdpaAttention(
          (q_proj): Linear(in_features=2048, out_features=2048, bias=False)
          (k_proj): Linear(in_features=2048, out_features=512, bias=False)
          (v_proj): Linear(in_features=2048, out_features=512, bias=False)
          (o_proj): Linear(in_features=2048, out_features=2048, bias=False)
          (rotary_emb): LlamaRotaryEmbedding()
        )
        (mlp): LlamaMLP(
          (gate_proj): Linear(in_features=2048, out_features=8192, bias=False)
          (up_proj): Linear(in_features=2048, out_features=8192, bias=False)
          (down_proj): Linear(in_features=8192, out_features=2048, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): LlamaRMSNorm((2048,), eps=1e-05)
        (post_attention_layernorm): LlamaRMSNorm((2048,), eps=1e-05)
      )
    )
    (norm): LlamaRMSNorm((2048,), eps=1e-05)
    (rotary_emb): LlamaRotaryEmbedding()
  )
  (lm_head): Linear(in_features=2048, out_features=128256, bias=False)
)</code>
登入後複製
登入後複製
登入後複製

檢查結構時,我們可以識別出三個可以作為剪枝目標的主要模塊:嵌入、自註意力機制和 MLP 層。為了決定哪些部分應該成為剪枝過程的重點,必須了解潛在的好處和可能的影響。

第一步是評估這些部分在模型中佔據的空間大小,以便了解潛在的縮減規模。

參數分佈分析

嵌入和輸出層 (embed_tokens, lm_head):

  • 128256 × 2048 ≈ 262M 個參數/層
  • 兩層共計 524M 個參數

自註意力機制 (self_attn):

  • 16 層,每層包含四個投影子層
  • 每層:2048 × (2048 512 512 2048) ≈ 10.5M 個參數
  • 總計:10.5 × 16 ≈ 168M 個參數

MLP 層 (mlp):

  • 16 層,具有 GLU 結構 (_gateproj, _upproj 和 _downproj)
  • 每層:2048 × 8192 2048 × 8192 8192 × 2048 ≈ 50M 個參數
  • 總計:50 × 16 ≈ 805M 個參數

我們可以看到,MLP 層佔據了模型大小的 50% 以上,因此它們是明確的剪枝候選對象。但是,在做出這個決定之前,務必了解每個部分對模型行為的貢獻。

影響分析

嵌入層負責將輸入轉換為模型可以有效處理的密集向量表示。剪枝嵌入層會導致模型喪失理解某些單詞的能力,或者至少降低創建正確捕捉輸入語義含義的向量的能力。例如,如果您想創建一個僅使用其輸入詞彙表中非常特定部分的高度特定模型(例如,用於財務或醫學分析的模型),則剪枝此層可能是一種選擇。

注意力機制允許模型在處理每個標記時關注輸入序列中最相關的部分。它計算輸入序列中每對標記之間的加權重要性分數,使模型能夠捕捉上下文並關注相關信息。剪枝此部分會降低模型執行需要廣泛理解輸入上下文的任務(例如文本摘要或翻譯)的能力。它還會影響生成的文本的連貫性。

MLP 層與註意力機制一起增強模型通過一系列數據擴展和收縮來理解複雜模式的能力。剪枝此部分會限制模型對未見數據或訓練期間未涵蓋的任務的響應。換句話說,它降低了模型的泛化能力及其提供對不熟悉輸入的連貫響應的能力。

一旦您決定要針對模型的哪個部分,下一步就是確定是執行寬度剪枝(移除單個神經元)還是深度剪枝(移除整個層)。

如您所見,剪枝模型是一個相當複雜的過程,涉及許多決策。您不僅必須評估生成的模型的能力,還必須評估其訓練能力。這些模型的設計目的是進行微調,通常用於特定任務,因此它們對於創建它們的特定任務比基礎模型更有效率。

門控線性單元的特性

門控線性單元 (GLU) 架構通常用於現代神經網絡,包括 LLaMA、Gemma、Mistral、Qwen 和類似的大型語言模型。 GLU 引入了一種逐元素門控機制,允許模型選擇性地過濾和控制信息流。此架構由成對的層組成,通常為:gate_proj、up_proj 和 down_proj(如上所示的模型結構中所示),它們協同工作以擴展和收縮數據。

這種機制使模型能夠處理更複雜的模式,同時保持效率。但是,這也意味著 GLU 結構中的層緊密耦合,剪枝這些層需要仔細考慮。

對一層(例如,移除神經元)的任何操作都必須在其相應的配對層中反映出來。例如,如果從 _gateproj 中移除一個神經元,則必須從 up_proj 中移除相同的神經元,並且必須相應地調整 _downproj 層的大小。最重要的是,在計算神經元的重要性以決定保留哪些神經元時,需要一起評估神經元對。

破壞這些層的平衡會導致性能下降,甚至模型完全失效,即使只移除少量神經元也是如此。

剪枝 Llama 3.2 模型

示例將使用 Llama 模型進行演示,但代碼也已在 Gemma 和 QWen 模型上成功測試。

您可以在我的 Github 代碼庫中的筆記本中訪問完整的代碼。

GitHub – peremartra/Large-Language-Model-Notebooks-Course: 關於大型語言……的實用課程

我對內存中的原始模型所做的第一步是執行一個小提示並保存結果。這使我可以輕鬆、直觀且快速地檢查通過剪枝過程生成的模型是否連貫,或者相反,是否失去了生成可理解文本的能力。

我可以向您保證,在我第一次嘗試中,由於沒有遵守模型的 GLU 結構,產生的文本毫無疑問地表明剪枝過程存在根本性缺陷。

原始提示是:“巴黎是……的首府”。讓我們看看原始模型的響應,並將其與我的第一次失敗的剪枝嘗試返回的響應進行比較。

基礎模型:

“巴黎是法國的首府,也是世界上游客最多的城市之一。它是一個藝術、文化、時尚和美食之都。這座城市擁有豐富的歷史,是許多著名地標的所在地,包括… …”

僅剪枝 20% 的不正確模型:

“巴黎是法國的首府。這是……的主要地區。這是……法國的城市……”

很明顯,第一次嘗試中有些東西不起作用。這看起來可能微不足道,但像這樣的經驗檢查可以為您節省大量時間。

實現細節

讓我們首先看看負責計算神經元重要性的函數,這最終將決定哪些神經元保留在模型中,哪些神經元被移除。

<code>LlamaForCausalLM(
  (model): LlamaModel(
    (embed_tokens): Embedding(128256, 2048)
    (layers): ModuleList(
      (0-15): 16 x LlamaDecoderLayer(
        (self_attn): LlamaSdpaAttention(
          (q_proj): Linear(in_features=2048, out_features=2048, bias=False)
          (k_proj): Linear(in_features=2048, out_features=512, bias=False)
          (v_proj): Linear(in_features=2048, out_features=512, bias=False)
          (o_proj): Linear(in_features=2048, out_features=2048, bias=False)
          (rotary_emb): LlamaRotaryEmbedding()
        )
        (mlp): LlamaMLP(
          (gate_proj): Linear(in_features=2048, out_features=8192, bias=False)
          (up_proj): Linear(in_features=2048, out_features=8192, bias=False)
          (down_proj): Linear(in_features=8192, out_features=2048, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): LlamaRMSNorm((2048,), eps=1e-05)
        (post_attention_layernorm): LlamaRMSNorm((2048,), eps=1e-05)
      )
    )
    (norm): LlamaRMSNorm((2048,), eps=1e-05)
    (rotary_emb): LlamaRotaryEmbedding()
  )
  (lm_head): Linear(in_features=2048, out_features=128256, bias=False)
)</code>
登入後複製
登入後複製
登入後複製

該函數接收 _gateproj 層和 _upproj 層的權重,正如我解釋的那樣,它們成對工作。因此,必須聯合計算神經元的重要性。

計算非常簡單:它計算每個神經元的權重的絕對值。正值和負值都被考慮在內,因為理論上,具有最極端值的神經元通過顯著改變通過它們的值來對模型的輸出產生更大的影響。

在這裡,我必須感謝 MariusZ Kurman 為將最小值納入計算所做的貢獻。雖然該方法在沒有它們的情況下也能正常工作,但包含它們可以改善結果。

每個層的重要性是分別計算的,但該函數返回組合值。

<code>LlamaForCausalLM(
  (model): LlamaModel(
    (embed_tokens): Embedding(128256, 2048)
    (layers): ModuleList(
      (0-15): 16 x LlamaDecoderLayer(
        (self_attn): LlamaSdpaAttention(
          (q_proj): Linear(in_features=2048, out_features=2048, bias=False)
          (k_proj): Linear(in_features=2048, out_features=512, bias=False)
          (v_proj): Linear(in_features=2048, out_features=512, bias=False)
          (o_proj): Linear(in_features=2048, out_features=2048, bias=False)
          (rotary_emb): LlamaRotaryEmbedding()
        )
        (mlp): LlamaMLP(
          (gate_proj): Linear(in_features=2048, out_features=8192, bias=False)
          (up_proj): Linear(in_features=2048, out_features=8192, bias=False)
          (down_proj): Linear(in_features=8192, out_features=2048, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): LlamaRMSNorm((2048,), eps=1e-05)
        (post_attention_layernorm): LlamaRMSNorm((2048,), eps=1e-05)
      )
    )
    (norm): LlamaRMSNorm((2048,), eps=1e-05)
    (rotary_emb): LlamaRotaryEmbedding()
  )
  (lm_head): Linear(in_features=2048, out_features=128256, bias=False)
)</code>
登入後複製
登入後複製
登入後複製

此函數創建新的、更小的層,同時保留最重要神經元。此過程包括:

  • 提取當前權重:
<code>def compute_neuron_pair_importance(gate_weight, up_weight):
    """
    计算神经元对重要性分数(最大绝对权重)
    参数:
    - gate_weight:来自 gate_proj 层的权重矩阵。
    - up_weight:来自 up_weight 层的权重矩阵。
    返回:
    - importance_scores:每个神经元对的重要性分数。
    """
    gate_max_abs = torch.max(gate_weight, dim=1).values + torch.abs(torch.min(gate_weight, dim=1).values)
    up_max_abs = torch.max(up_weight, dim=1).values + torch.abs(torch.min(up_weight, dim=1).values)
    importance_scores = gate_max_abs + up_max_abs
    return importance_scores</code>
登入後複製
  • 計算神經元對的重要性分數:
<code>def prune_neuron_pairs(mlp, prune_percent):
    """
    减少**gate_proj**、**up_proj**、**down_proj**层的维度,移除不太重要的神经元。
    参数:
    - mlp:要剪枝的层。
    - prune_percent:要剪枝的神经元的百分比。
    返回:
    - new_gate_proj, new_up_proj, new_down_proj:新的剪枝层。
    - k:新的中间大小。
    """
    # 从 MLP 层提取权重
    gate_weight = mlp.gate_proj.weight.data.float()
    up_weight = mlp.up_proj.weight.data.float()

    # 计算重要性分数
    importance_scores = compute_neuron_pair_importance(gate_weight, up_weight)
    original_intermediate_size = gate_weight.size(0)

    # 计算要保留的神经元
    num_neuron_pairs_to_prune = min(int(prune_percent * original_intermediate_size),
                                   original_intermediate_size - 1)
    k = original_intermediate_size - num_neuron_pairs_to_prune

    # 验证检查
    if k < 1:
        raise ValueError("k must be greater than 0")

    # 选择要保留的神经元
    _, indices_to_keep = torch.topk(importance_scores, k, largest=True, sorted=True)
    indices_to_keep = indices_to_keep.sort().values

    # 创建并填充新层
    new_gate_proj = nn.Linear(mlp.gate_proj.in_features, k, bias=False).to(device)
    new_up_proj = nn.Linear(mlp.up_proj.in_features, k, bias=False).to(device)
    new_down_proj = nn.Linear(k, mlp.down_proj.out_features, bias=False).to(device)

    # 将选定的权重复制到新层。
    new_gate_proj.weight.data = mlp.gate_proj.weight.data[indices_to_keep, :]
    new_up_proj.weight.data = mlp.up_proj.weight.data[indices_to_keep, :]
    new_down_proj.weight.data = mlp.down_proj.weight.data[:, indices_to_keep]

    return new_gate_proj, new_up_proj, new_down_proj, k</code>
登入後複製

獲得一個張量,其中包含為每個神經元計算的重要性分數。這些分數反映了每個神經元對最終輸出的貢獻,指示應該保留哪些神經元。

  • 確定要保留的神經元數量:
<code># 从 MLP 层提取权重
    gate_weight = mlp.gate_proj.weight.data.float()
    up_weight = mlp.up_proj.weight.data.float()</code>
登入後複製

使用作為參數提供的剪枝百分比和層的原始大小來計算要保留的神經元的總數。

  • 選擇最重要神經元:
<code># 计算重要性分数
    importance_scores = compute_neuron_pair_importance(gate_weight, up_weight)
    original_intermediate_size = gate_weight.size(0)</code>
登入後複製

Torch 用於檢索具有最高重要性分數的神經元,同時還將它們從最重要到最不重要的順序排列。由於 torch 按降序返回數據,因此使用 sort 方法將其重新排列為升序,這就是我們需要的。

  • 創建新的、更小的層:
<code># 计算要保留的神经元
    num_neuron_pairs_to_prune = min(int(prune_percent * original_intermediate_size),
                                   original_intermediate_size - 1)
    k = original_intermediate_size - num_neuron_pairs_to_prune</code>
登入後複製

創建三個新層,其維度根據所選索引進行調整。在 _new_gateproj 和 _new_upproj 中,保留輸入維度,而輸出維度則減小。相反,在 _new_downproj 中,調整輸入維度,而輸出維度保持不變。

  • 將選定的權重複製到新層:
<code># 选择要保留的神经元
    _, indices_to_keep = torch.topk(importance_scores, k, largest=True, sorted=True)
    indices_to_keep = indices_to_keep.sort().values</code>
登入後複製

相關的權重從原始層轉移到新層,確保只保留與所選神經元對應的權重。

現在,讓我們看看負責迭代所有層並構建修改後的模型的函數。

<code># 创建并填充新层
    new_gate_proj = nn.Linear(mlp.gate_proj.in_features, k, bias=False).to(device)
    new_up_proj = nn.Linear(mlp.up_proj.in_features, k, bias=False).to(device)
    new_down_proj = nn.Linear(k, mlp.down_proj.out_features, bias=False).to(device)</code>
登入後複製

此函數迭代模型的每一層,應用剪枝過程並更新模型的配置以反映新的架構。

如果不更新配置文件,則保存後無法使用該模型,無論是在 Hugging Face 上還是本地。許多庫(例如 Hugging Face 的 Transformers)都依賴於 model.config 來解釋模型的架構。如果配置與實際結構不匹配,則通過這些庫執行的微調或推理操作可能會失敗。

結果分析

使用此代碼,我創建了幾個模型,這些模型可在 Hugging Face Hub 上獲得。

這些包括:

  • 從 Llama-3.2–1b 派生的三個模型,MLP 層中的神經元分別剪枝了 20%、40% 和 60%。
  • 基於 Gemma-2–2B 的一個模型,剪枝了 40%。

您可以下載這些模型,除了使用它們之外,還可以研究它們的架構以及與它們所基於的原始模型相比發生了哪些變化。

讓我們分析將 Llama3.2–1b 模型應用 20% 剪枝後的架構變化。

<code># 将选定的权重复制到新层。
 new_gate_proj.weight.data = mlp.gate_proj.weight.data[indices_to_keep, :]
 new_up_proj.weight.data = mlp.up_proj.weight.data[indices_to_keep, :]
 new_down_proj.weight.data = mlp.down_proj.weight.data[:, indices_to_keep]</code>
登入後複製

除了 MLP 塊中中間層的大小之外,模型的結構保持不變。如您所見,_gateproj 和_upproj 層已從8192 個特徵減少到6554 個,而_downproj 層也發生了同樣的變化,但在其輸入特徵中。

此更改與代碼的功能完全一致:修改這些層,同時保留對模型性能至關重要的神經元。如果我們移除 8192 的 20%,我們將得到 6553.6,這證實已剪枝了正確比例的神經元。

經驗提示測試

現在,讓我們看看剪枝後的模型在測試提示中的表現如何:

巴黎是法國的首府。它也是世界上最美麗的城市之一。巴黎有如此多值得一看和體驗的東西,一天之內不可能全部涵蓋。但是,有一些事情……

響應與原始模型的響應並不完全相同,但它保持了連貫性。這表明該模型保留了其大部分能力,更重要的是,它可以通過知識蒸餾微調來恢復任何損失。

EleutherAI / lm-evaluation

除了這種經驗檢查之外,我還使用一些最常見的基準測試評估了該模型。讓我們分析不同程度的剪枝如何影響模型的性能。

How to Prune LLaMA 3.2 and Similar Large Language Models如我們所見,剪枝的影響有些不對稱。 BoolQ 測試評估的任務沒有經歷明顯的下降,對於在 MLP 層中損失了 40% 神經元的模型,僅下降了約 2%。

相比之下,對 Lambada 測試的影響非常顯著,準確率下降了 50% 以上。

這表明該模型保留了其大部分理解能力,但在需要更開放式生成的測試中卻難以應對。

BoolQ 只向模型呈現文本和需要用是/否回答的問題。這是一項專注於衡量模型理解輸入文本中關係的能力的測試。

另一方面,Lambada 要求模型猜測段落的最後一個單詞,這是一項複雜的任務,其中最後一個單詞測試了模型在復雜語言建模中的能力。

Hugging Face 開放 LLM 排行榜

在 Hugging Face 開放 LLM 排行榜上,剪枝到 20% 的模型的結果甚至更令人驚訝,因為它優於其基礎模型和廣泛使用的 TinyLlama-1.1B-v1.1。

在這個圖表中,我們可以看到兩個模型的結果。

How to Prune LLaMA 3.2 and Similar Large Language Models通過研究此圖表,我們可以得出以下結論:剪枝後的模型平均性能優於基礎模型 (4.86 對 4.03)。這表明剪枝過程有效地保留或增強了關鍵領域的性能,同時減少了冗餘。

通過研究結果,我們可以識別剪枝模型的優勢和劣勢。

優勢:

  • IFEval: 顯著改進 (19.94 對 14.78) 表明剪枝要么減少了過擬合,要么提高了模型有效提取信息的能力。
  • MUSR: 更好的性能(4.39 對2.56) 表明剪枝後的模型更好地處理需要對長上下文或敘事理解進行推理的任務,這可能是由於權重集中造成的。

劣勢:

  • BBH: 不確定性推理能力下降 (3.19 對 4.37) 可能表明剪枝降低了模型處理模棱兩可或多解釋場景的能力。
  • MMLU-PRO: 專業領域特定任務下降 (1.36 對 2.26) 可能歸因於刪除了在特定領域保留詳細信息的關鍵權重。

能源效率: 剪枝後的模型能源效率略高 (0.4 公斤對 0.42 公斤 CO₂),這與在保持競爭性能的同時降低計算開銷的目標一致。

需要對模型在不同排名中的性能進行更全面的研究,但這些結果表明我們擁有一個很有前景的模型,可以通過適當的知識蒸餾或微調得到顯著改進。最重要的是,這些結果與對 MLP 層執行的剪枝過程一致。

結論

模型的剪枝過程取得了成功。這種處理 GLU 層的方法使我們能夠在保留模型大部分能力的同時執行剪枝,從而大大減小其大小和資源消耗。

重要的是要注意,測試結果是在剪枝模型之前進行任何能力恢復過程(例如知識蒸餾微調)獲得的,這通常是對經過剪枝的模型進行的。

未來工作

有很多值得探索的剪枝技術。也許最直接的是深度剪枝,它涉及移除對模型性能貢獻最小的層。

另一個重要的研究領域是對這些剪枝後的模型進行知識蒸餾過程,並評估它們是否保留了學習新任務的能力。這可能會使它們的性能更接近基礎模型,尤其是在剪枝後的模型顯示出最大損失的基準測試中。

開發更輕量、更高效的模型仍然是一個極具吸引力的領域,特別是對於那些尋求在沒有廣泛基礎設施要求的情況下部署 LLM 功能的公司而言。這項工作為進一步研究如何使這些強大的模型更容易訪問和部署奠定了基礎。

本文是關於大型語言模型的完整課程的一部分,可在 Github 上獲得。要了解新文章的更新,請考慮關注代碼庫或加星標。 通過這種方式,您將在添加新內容時收到通知。

我是 Apress 出版社出版的《大型語言模型項目:應用和實施大型語言模型策略》一書的作者。

我定期撰寫關於生成式 AI、深度學習和 TensorFlow 的文章。 請考慮關注我在 Medium 上的賬號,以獲取有關新文章的更新。 當然,歡迎您在 LinkedIn 上與我聯繫。

以上是如何修剪美洲駝3.2和類似的大語言模型的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板