大模型高效微調方法綜述:P-Tuning軟提示與lora低秩微調附案例代碼詳解

?Prompt TuningP-Tuning 都屬于“軟提示”(soft prompt)范式,但 P-Tuning 首次提出用小型 LSTM/MLP 對提示嵌入進行編碼生成,而 Prompt Tuning(又稱 Soft Prompt Tuning)則直接對一段可訓練的嵌入序列做梯度更新;LoRA(Low-Rank Adaptation)通過在 Transformer 層注入兩段低秩矩陣分解,僅訓練這部分額外參數實現參數效率微調;QLoRA 則在 4-bit 量化權重上應用 LoRA,幾乎與原始 16-bit LoRA 性能持平,卻將顯存占用降低近 3 倍。以下內容將依次覆蓋各方法的原理細節、訓練流程差異,以及在 BERT 分類任務中完整的訓練、保存、加載與推理偽代碼示例。

1 Prompt Tuning vs P-Tuning

1.1 方法定義與原理

  • Prompt Tuning:在模型輸入的 embedding 層前添加 L 個可訓練向量,稱為軟提示(soft prompts),并僅對這 L 參數進行梯度更新,凍結其余預訓練模型參數 。

  • P-Tuning:《GPT Understands, Too》中提出,除使用離散提示外,還通過一個小型 LSTM 或 MLP(prompt encoder)對初始提示嵌入做變換,生成最終的連續提示嵌入,再拼接到輸入前進行訓練,提示參數由反向傳播更新 。

1.2 訓練流程對比

階段

Prompt Tuning

P-Tuning

初始化

隨機或預訓練初始化 L 個提示向量

同上 + 初始化 LSTM/MLP 權重

前向傳遞

拼接提示向量 + 原始 embeddings → Encoder

先 LSTM/MLP 生成提示嵌入,再拼接 + 原始 embeddings → Encoder

反向更新

僅更新提示向量

更新提示向量與 prompt encoder 參數

數據存儲

保存提示向量矩陣

同上 + 保存 encoder 權重

?

1.3 保存與加載偽代碼

# 保存
torch.save(prompt_embeddings.state_dict(), "prompt_tuning_prompts.bin")# 加載
prompt_embeddings = nn.Parameter(torch.zeros(L, hidden_size))
prompt_embeddings.load_state_dict(torch.load("prompt_tuning_prompts.bin"))
# P-Tuning 保存
torch.save({"prompt_encoder": lstm.state_dict(),"prompt_vectors": prompt_embeddings_init
}, "p_tuning_prompt.bin")# 加載
ckpt = torch.load("p_tuning_prompt.bin")
lstm.load_state_dict(ckpt["prompt_encoder"])
prompt_embeddings_init = ckpt["prompt_vectors"]

2 LoRA vs QLoRA

2.1 LoRA(Low-Rank Adaptation)原理

LoRA 在每個 Transformer 層的線性映射 xW 的旁支引入低秩分解 ,并用????替代原始變換,其中??為縮放系數,可訓練參數量僅為 2dr,相比全量微調可減少約 10,000 倍參數 。

2.2 QLoRA 原理

QLoRA 首先將原模型權重量化到 4-bit(如 NF4),顯存占用大幅下降;然后在量化權重上按常規方式注入 LoRA 分支,并只訓練 LoRA 分支參數。該過程兼顧了量化與低秩適配的雙重優勢,實驗證明與 16-bit LoRA 性能相當,卻將顯存占用降至三分之一左右 。

2.3 訓練與保存偽代碼

# LoRA 注入示例(略)后,僅啟用 A,B 子模塊
for name,p in model.named_parameters():p.requires_grad = ('lora_A' in name or 'lora_B' in name)# 訓練循環
for batch in dataloader:outputs = model(**batch)loss = criterion(outputs.logits, batch.labels)loss.backward()optimizer.step()# 保存 LoRA 參數
torch.save(model.state_dict(), "bert_lora.pt")
# QLoRA 量化 + LoRA
from bitsandbytes import quantize_4bit
for n,p in model.named_parameters():p.data = quantize_4bit(p.data, dtype='nf4')
# 注入 LoRA 后同上訓練代碼# 保存模型
model.save_pretrained("bert_qlora")

3 BERT 分類任務:完整示例

本來應該是用chatglm,千問等大模型來來做演示的,但是此處只是為了講解,這些訓練的過程,所以使用大家熟悉的bert 模型來做。

下面以 PyTorch + Hugging Face Transformers 為原型,演示四種方法在 BERT 分類項目中的“訓練→保存→加載→推理”流程。

3.1 Prompt Tuning

注釋要點:

1.?? ?凍結預訓練模型:保證只有“軟提示”與分類頭參與訓練,極大降低顯存與計算開銷。

2.?? ?軟提示(Prompt):用少量可學習的向量充當“偽 token”,引導模型關注下游任務特征。

3.?? ?拼接邏輯:將 prompt 放在輸入序列最前面,BERT 的 Transformer 自注意力會自動將其納入計算。

4.?? ?保存與加載:只需保存 prompt 與分類頭,即可方便部署。

5.?? ?推理流程:與訓練相似,但不開啟梯度,快速得到預測結果。

關鍵說明

  1. attention_mask 擴展:在 prompt 前補 1,使得自注意力不會忽略 prompt 部分 。

  2. token_type_ids 擴展:prompt 通常歸為第 0 號句子,也可設置為其它值,務必與模型訓練時一致 。

  3. 使用 inputs_embeds:通過 model(inputs_embeds=…, attention_mask=…, token_type_ids=…) 保證 BERT 自帶的 絕對位置編碼句子編碼 會自動加到我們拼接后的輸入上,無需手動處理 。

  4. 取第 L 個位置:prompt 長度為 L,故第 L 個向量對應原始文本第 1 個 token 的“CLS 等價”表征,含 prompt 與輸入信息 。

  5. 保存與加載:只需保存 prompt 與分類頭,BERT 主干無需變動,極簡部署。

通過以上改動,代碼即完整支持了位置編碼與句子編碼,保證 soft prompt 能正確注入,而原有的自注意力機制與絕對位置編碼均被保留。

import torch
import torch.nn as nn
from transformers import BertModel
from transformers import AdamW# -----------------------------------------------------------------------------
# 1. 初始化
# -----------------------------------------------------------------------------# 1.1 加載預訓練 BERT 主干(不含任何 task-specific 頭)
model = BertModel.from_pretrained('bert-base-uncased')# 1.2 凍結所有 BERT 參數,只訓練后續新增模塊
for p in model.parameters():p.requires_grad = False# 1.3 軟提示長度 L,可根據顯存/性能自行調整
L = 20  # [turn0search0]# 1.4 創建一個可訓練的軟提示向量,形狀為 [L, H]
H = model.config.hidden_size
prompt = nn.Parameter(torch.randn(L, H))  # [turn0search4]# 1.5 定義分類頭,將隱藏維度 H 映射到類別數
num_labels = 2
classifier = nn.Linear(H, num_labels)# 1.6 優化器只更新 prompt 和 classifier
optim_params = [prompt, *classifier.parameters()]
optimizer = AdamW(optim_params, lr=1e-3)# 1.7 損失函數:交叉熵
loss_fn = nn.CrossEntropyLoss()# -----------------------------------------------------------------------------
# 2. 訓練循環
# -----------------------------------------------------------------------------# 假設 train_loader 每個 batch 包含:
#   batch['input_ids']     : [B, N]
#   batch['attention_mask']: [B, N]
#   batch['token_type_ids'] : [B, N] (可選,句子對任務需提供)
for batch in train_loader:input_ids      = batch['input_ids']                            # [B, N]orig_mask      = batch['attention_mask']                       # [B, N]orig_token_ids = batch.get('token_type_ids',torch.zeros_like(orig_mask))        # [B, N]# 2.1 擴展 attention_mask: 在最前面為 L 個 prompt 置 1#     prompt_mask: [B, L]prompt_mask = torch.ones(orig_mask.size(0), L,dtype=orig_mask.dtype,device=orig_mask.device)               # [turn1search0]new_mask = torch.cat([prompt_mask, orig_mask], dim=1)          # [B, L+N]# 2.2 擴展 token_type_ids: prompt 統一標為 0(或其他常數均可)prompt_type = torch.zeros_like(prompt_mask, dtype=orig_token_ids.dtype)new_token_ids = torch.cat([prompt_type, orig_token_ids], dim=1) # [B, L+N]# 2.3 從 embedding table 拿到原始 prompt 的 word embeddings#     init_emb: [L, H]init_emb = model.embeddings.word_embeddings(torch.arange(L, device=orig_mask.device))       # [turn0search4]# 2.4 用 LSTM/MLP 編碼(此處以 LSTM 為例,也可改為 nn.Linear 等)prompt_emb, _ = nn.LSTM(H, H, batch_first=True)(init_emb.unsqueeze(0))                       # [1, L, H]# 2.5 擴展至 batch 大小 → [B, L, H]pref = prompt_emb.expand(input_ids.size(0), -1, -1)# 2.6 原始 token embeddings: [B, N, H]emb = model.embeddings(input_ids)                              # [turn0search4]# 2.7 拼接 prompt 與原始 embeddings → [B, L+N, H]enc_inputs = torch.cat([pref, emb], dim=1)                     # [turn0search2]# 2.8 調用 BertModel,傳入 inputs_embeds、attention_mask、token_type_ids#     BertEmbeddings 層會在 inputs_embeds 上加上 position & token_type embeddingsoutputs = model(inputs_embeds=enc_inputs,attention_mask=new_mask,token_type_ids=new_token_ids)sequence_output = outputs.last_hidden_state                    # [B, L+N, H]# 2.9 取第 L 個位置(即 prompt 之后首個 token)做“CLS”等價表示pooled_rep = sequence_output[:, L, :]                          # [B, H]# 2.10 分類 & 計算損失logits = classifier(pooled_rep)                                # [B, num_labels]loss   = loss_fn(logits, batch['labels'])# 2.11 反向傳播 & 更新loss.backward()optimizer.step()optimizer.zero_grad()# -----------------------------------------------------------------------------
# 3. 保存
# -----------------------------------------------------------------------------# 3.1 保存 prompt 向量
torch.save(prompt.state_dict(), "pt_prompts.bin")# 3.2 保存分類頭
torch.save(classifier.state_dict(), "pt_cls.bin")# -----------------------------------------------------------------------------
# 4. 加載 & 推理
# -----------------------------------------------------------------------------# 4.1 重建 prompt 并加載
prompt = nn.Parameter(torch.empty(L, H))
prompt.load_state_dict(torch.load("pt_prompts.bin"))# 4.2 重建分類頭并加載
classifier = nn.Linear(H, num_labels)
classifier.load_state_dict(torch.load("pt_cls.bin"))# 4.3 推理模式
model.eval()
classifier.eval()with torch.no_grad():# 假設 test_ids, test_mask, test_token_type_ids 形狀分別 [B, N]emb = model.embeddings(test_ids)                             # [B, N, H]prompt_mask = torch.ones(test_mask.size(0), L,dtype=test_mask.dtype,device=test_mask.device)new_mask  = torch.cat([prompt_mask, test_mask], dim=1)       # [B, L+N]prompt_type = torch.zeros_like(prompt_mask, dtype=test_token_type_ids.dtype)new_types    = torch.cat([prompt_type, test_token_type_ids], dim=1)  # [B, L+N]prompt_emb, _ = lstm(init_emb.unsqueeze(0))                  # [1, L, H]pref = prompt_emb.expand(test_ids.size(0), -1, -1)           # [B, L, H]enc_inputs = torch.cat([pref, emb], dim=1)                   # [B, L+N, H]outputs = model(inputs_embeds=enc_inputs,attention_mask=new_mask,token_type_ids=new_types)seq_out = outputs.last_hidden_state                          # [B, L+N, H]rep = seq_out[:, L, :]                                       # [B, H]logits = classifier(rep)                                     # [B, num_labels]preds  = logits.argmax(dim=-1)                               # [B]

3.2 P-Tuning

關鍵說明

  1. prompt_ids → init_emb:借助 BERT 的 embedding table 獲取初始連續提示嵌入。

  2. LSTM 編碼:通過小型 LSTM(或通用 MLP)進一步轉換提示向量,增強其表達能力。

  3. 拼接邏輯:將提示嵌入放在序列最前端,BERT 的自注意力會自動將其納入上下文。

  4. CLS 等價:拼接后第 prompt_len 位置處的向量即為融合了提示與輸入的全局表示,用于分類。

  5. 推理流程:與訓練相同,但無梯度計算,速度更快。

  1. attention_mask 擴展:在 prompt 前補 1,使得自注意力不會忽略 prompt 部分 。

  2. token_type_ids 擴展:prompt 通常歸為第 0 號句子,也可設置為其它值,務必與模型訓練時一致 。

  3. 使用 inputs_embeds:通過 model(inputs_embeds=…, attention_mask=…, token_type_ids=…) 保證 BERT 自帶的 絕對位置編碼句子編碼 會自動加到我們拼接后的輸入上,無需手動處理 。

  4. 取第 L 個位置:prompt 長度為 L,故第 L 個向量對應原始文本第 1 個 token 的“CLS 等價”表征,含 prompt 與輸入信息 。

  5. 保存與加載:只需保存 prompt 與分類頭,BERT 主干無需變動,極簡部署。

通過以上改動,代碼即完整支持了位置編碼與句子編碼,保證 soft prompt 能正確注入,而原有的自注意力機制與絕對位置編碼均被保留。

import torch
import torch.nn as nn
from transformers import BertModel, AdamW# -----------------------------------------------------------------------------
# 1. 初始化階段
# -----------------------------------------------------------------------------
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 1.1 加載預訓練的 BERT 主干(不含 task-specific 頭),并移到 device
model = BertModel.from_pretrained('bert-base-uncased').to(device)# 1.2 凍結 BERT 主干所有參數,只訓練下面新增的部分
for p in model.parameters():p.requires_grad = False# 1.3 軟提示長度 L(可調)
L = 20# 1.4 偽 token IDs,用于從 embedding table 取初始 prompt 向量
prompt_ids = torch.arange(L, device=device)# 1.5 創建 LSTM 作為 prompt encoder
H = model.config.hidden_size
prompt_encoder = nn.LSTM(input_size=H,hidden_size=H,batch_first=True
).to(device)# 1.6 定義分類頭
num_labels = 2
classifier = nn.Linear(H, num_labels).to(device)# 1.7 優化器:僅優化 prompt encoder 和分類頭參數
optimizer = AdamW(list(prompt_encoder.parameters()) + list(classifier.parameters()),lr=1e-3
)# -----------------------------------------------------------------------------
# 2. 訓練循環
# -----------------------------------------------------------------------------# 假設 train_loader 每 batch 提供:
#   batch['input_ids']     : [B, N]
#   batch['attention_mask']: [B, N]
#   batch['token_type_ids'] : [B, N](可選)
#   batch['labels']         : [B]
for batch in train_loader:# 2.1 數據搬到 deviceinput_ids  = batch['input_ids'].to(device)          orig_mask  = batch['attention_mask'].to(device)     orig_types = batch.get('token_type_ids',torch.zeros_like(orig_mask)).to(device)labels     = batch['labels'].to(device)             B, N = input_ids.size()# 2.2 構造新的 attention_mask:prompt 部分全部設為 1prompt_mask = torch.ones(B, L, dtype=orig_mask.dtype, device=device)new_mask    = torch.cat([prompt_mask, orig_mask], dim=1)  # [B, L+N]# 2.3 構造新的 token_type_ids:prompt 部分設為 0prompt_type = torch.zeros_like(prompt_mask, dtype=orig_types.dtype, device=device)new_types   = torch.cat([prompt_type, orig_types], dim=1)  # [B, L+N]# 2.4 從 embedding table 提取初始 prompt 嵌入 [L, H]init_emb = model.embeddings.word_embeddings(prompt_ids)  # [L, H]# 2.5 通過 prompt encoder(LSTM)生成最終 prompt 嵌入 [1, L, H]prompt_emb, _ = prompt_encoder(init_emb.unsqueeze(0))    # [1, L, H]# 2.6 擴展至 batch 大小 → [B, L, H]pref = prompt_emb.expand(B, -1, -1)# 2.7 獲取原始文本 embeddings → [B, N, H]emb = model.embeddings(input_ids)                       # [B, N, H]# 2.8 拼接兩個部分 → [B, L+N, H]inputs_embeds = torch.cat([pref, emb], dim=1)           # [B, L+N, H]# 2.9 調用 BERT,傳入 inputs_embeds、attention_mask 和 token_type_idsoutputs = model(inputs_embeds=inputs_embeds,attention_mask=new_mask,token_type_ids=new_types)sequence_output = outputs.last_hidden_state              # [B, L+N, H]# 2.10 取第 L 個位置的向量作為 “CLS 等價” 表示 → [B, H]cls_equiv = sequence_output[:, L, :]# 2.11 分類 & 計算損失logits = classifier(cls_equiv)                          # [B, num_labels]loss   = nn.CrossEntropyLoss()(logits, labels)# 2.12 反向傳播 & 參數更新loss.backward()optimizer.step()optimizer.zero_grad()# -----------------------------------------------------------------------------
# 3. 保存訓練好的參數
# -----------------------------------------------------------------------------# 3.1 保存 prompt encoder 權重
torch.save(prompt_encoder.state_dict(), "ptuning_encoder.bin")# 3.2 保存分類頭權重
torch.save(classifier.state_dict(), "ptuning_cls.bin")# -----------------------------------------------------------------------------
# 4. 加載 & 推理
# -----------------------------------------------------------------------------# 4.1 重建 prompt encoder 并加載
prompt_encoder = nn.LSTM(input_size=H, hidden_size=H, batch_first=True).to(device)
prompt_encoder.load_state_dict(torch.load("ptuning_encoder.bin"))# 4.2 重建分類頭并加載
classifier = nn.Linear(H, num_labels).to(device)
classifier.load_state_dict(torch.load("ptuning_cls.bin"))# 4.3 設置為推理模式
model.eval()
prompt_encoder.eval()
classifier.eval()# 4.4 推理循環
for batch in test_loader:input_ids  = batch['input_ids'].to(device)orig_mask  = batch['attention_mask'].to(device)orig_types = batch.get('token_type_ids',torch.zeros_like(orig_mask)).to(device)B, N = input_ids.size()prompt_mask = torch.ones(B, L, dtype=orig_mask.dtype, device=device)new_mask    = torch.cat([prompt_mask, orig_mask], dim=1)prompt_type = torch.zeros_like(prompt_mask, dtype=orig_types.dtype, device=device)new_types   = torch.cat([prompt_type, orig_types], dim=1)init_emb     = model.embeddings.word_embeddings(prompt_ids)prompt_emb, _= prompt_encoder(init_emb.unsqueeze(0))pref         = prompt_emb.expand(B, -1, -1)emb          = model.embeddings(input_ids)inputs_embeds= torch.cat([pref, emb], dim=1)outputs      = model(inputs_embeds=inputs_embeds,attention_mask=new_mask,token_type_ids=new_types)seq_out      = outputs.last_hidden_statecls_equiv    = seq_out[:, L, :]logits       = classifier(cls_equiv)preds        = torch.argmax(logits, dim=-1)# 處理 preds (例如計算準確率或保存結果)# ...

以GPT2自回歸模型來講解 p_turning

要點說明

  • 使用 LSTM 作為 Prompt Encoder,可捕捉提示向量序列的時序依賴;

  • 前 L 個位置的 labels 設為 -100,保證 loss 只計算在真實文本部分;

  • GPT-2 自帶位置編碼與因果遮掩,無需手動處理;

  • 生成時也使用 inputs_embeds,并在輸出時剔除前 L 個“偽 token”。

  • teacher Forcing 是一種經典的自回歸序列模型訓練策略,最早由 Williams 和 Zipser 在 1989 年提出,用于加速和穩定循環神經網絡(RNN)的訓練 。其核心思路是在訓練階段,模型每一步的輸入不使用模型自身上一步的預測結果,而是直接采用真實標記(ground truth),以此減少誤差累積并加快收斂 。在生成(推理)階段,則依次將模型預測的標記拼回輸入,完成逐步自回歸生成。

import torch
import torch.nn as nn
from transformers import GPT2LMHeadModel, GPT2Tokenizer, AdamW
# 0. 環境與模型初始化
# 設備選取:若有可用 GPU 則用 GPU,否則用 CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  # 加載 GPT-2 自回歸語言模型與分詞器,并將模型移到 device
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')                   
model     = GPT2LMHeadModel.from_pretrained('gpt2').to(device)      # 凍結 GPT-2 主干所有參數,只保留微調 Prompt Encoder 的權重
for p in model.parameters():p.requires_grad = False                                          # 1. P-Tuning 參數與模塊定義
# 定義 Prompt 長度 L,可根據顯存與任務難度調節
L = 30                                                             # GPT-2 隱藏層維度 H,等同于 embedding 維度
H = model.config.n_embd# 從 GPT-2 的詞向量表中取出 L 個“偽 token”對應的初始 embedding
prompt_ids = torch.arange(L, device=device)                         
init_emb   = model.transformer.wte(prompt_ids)  # [L, H]          # 創建 LSTM Prompt Encoder:輸入 [1, L, H] → 輸出 [1, L, H]
prompt_encoder = nn.LSTM(input_size=H, hidden_size=H, num_layers=1, batch_first=True
).to(device)                                                        # 僅訓練 Prompt Encoder 的參數
optimizer = AdamW(prompt_encoder.parameters(), lr=5e-5)            # 2. 訓練循環(自回歸語言建模)
model.train()
prompt_encoder.train()for epoch in range(num_epochs):for batch in train_loader:# 假設 batch 中含有:# batch['input_ids']     : [B, N]# batch['attention_mask']: [B, N]# batch['labels']        : [B, N],用于計算下一個 token 的監督信號input_ids      = batch['input_ids'].to(device)           attention_mask = batch['attention_mask'].to(device)      labels         = batch['labels'].to(device)              B, N = input_ids.size()# 2.1 用 LSTM 編碼初始 prompt embedding → [1, L, H]prompt_emb, _ = prompt_encoder(init_emb.unsqueeze(0))     # 2.2 擴展到 batch 大小 → [B, L, H]prompt_emb = prompt_emb.expand(B, -1, -1)                 # 2.3 獲取原始輸入 token embedding → [B, N, H]token_emb = model.transformer.wte(input_ids)              # 2.4 拼接 prompt 與原始 embeddings → [B, L+N, H]inputs_embeds = torch.cat([prompt_emb, token_emb], dim=1) # 2.5 構造新的 attention_mask:在 prompt 部分填 1 → [B, L+N]prompt_mask = torch.ones(B, L, device=device)new_mask    = torch.cat([prompt_mask, attention_mask], dim=1)  # 2.6 構造新的 labels:prompt 部分設為 -100,跳過 loss 計算 → [B, L+N]prompt_labels = torch.full((B, L), -100, device=device, dtype=torch.long)new_labels    = torch.cat([prompt_labels, labels], dim=1)     # 2.7 前向計算,自回歸地并行預測所有位置下一個 tokenoutputs = model(inputs_embeds=inputs_embeds,attention_mask=new_mask,labels=new_labels)                                                         loss = outputs.loss                                        # 2.8 反向傳播 & 更新 Prompt Encoderloss.backward()optimizer.step()optimizer.zero_grad()print(f"Epoch {epoch+1} loss: {loss.item():.4f}")              
# 3. 保存 Prompt Encoder
torch.save(prompt_encoder.state_dict(), "gpt2_ptuning_lstm.bin")  
# 4. 加載 & 推理
# 4.1 重建并加載 Prompt Encoder
prompt_encoder = nn.LSTM(input_size=H, hidden_size=H, batch_first=True).to(device)
prompt_encoder.load_state_dict(torch.load("gpt2_ptuning_lstm.bin"))model.eval()
prompt_encoder.eval()# 4.2 準備初始上下文
context = "In a distant future"                                  
tokens  = tokenizer(context, return_tensors="pt").to(device)
input_ids      = tokens.input_ids                                
attention_mask = tokens.attention_mask                           
B, N = input_ids.size()# 4.3 生成 prompt_emb → [1, L, H] → 擴展為 [B, L, H]
prompt_emb, _ = prompt_encoder(init_emb.unsqueeze(0))
prompt_emb    = prompt_emb.expand(B, -1, -1)# 4.4 獲取原始 embeddings & 拼接
token_emb     = model.transformer.wte(input_ids)                  
inputs_embeds = torch.cat([prompt_emb, token_emb], dim=1)         # 4.5 構造新的 attention_mask
prompt_mask   = torch.ones(B, L, device=device)
new_mask      = torch.cat([prompt_mask, attention_mask], dim=1)   # 4.6 自回歸生成并剔除 prompt 部分
generated = model.generate(inputs_embeds=inputs_embeds,attention_mask=new_mask,max_length=L + N + 50
)                                                                 
generated = generated[:, L:]                                       # 4.7 解碼并輸出
print(tokenizer.decode(generated[0], skip_special_tokens=True))  

?


DeepSeek-R1 P-Tuning 示例代碼

以下代碼展示了對 DeepSeek-R1 模型進行 P-Tuning 的全流程:凍結主干 → 構造連續提示 → 并行化微調 → 保存加載 → 自回歸生成。根據具體硬件與任務需求,可將 model_name 替換為任意 Distill 版本以實現更輕量化部署。

比如

  1. DeepSeek-R1-Distill-Qwen-1.5B

  2. DeepSeek-R1-Distill-Llama-7B/8B

  3. DeepSeek-R1 / DeepSeek-R1-Zero

import torch
import torch.nn as nn
from transformers import AutoModelForCausalLM, AutoTokenizer, AdamW# 0. 設備與模型加載
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model_name = "deepseek-ai/DeepSeek-R1"
tokenizer  = AutoTokenizer.from_pretrained(model_name)
model      = AutoModelForCausalLM.from_pretrained(model_name).to(device)
for p in model.parameters():p.requires_grad = False# 1. Prompt Encoder 定義
L = 30
H = model.config.n_embd
prompt_ids     = torch.arange(L, device=device)
init_emb       = model.get_input_embeddings()(prompt_ids)  # [L, H]
prompt_encoder = nn.LSTM(input_size=H, hidden_size=H, batch_first=True).to(device)
optimizer      = AdamW(prompt_encoder.parameters(), lr=5e-5)# 2. 訓練循環
model.train()
prompt_encoder.train()
for batch in train_loader:input_ids      = batch["input_ids"].to(device)          # [B, N]attention_mask = batch["attention_mask"].to(device)     # [B, N]labels         = batch["labels"].to(device)             # [B, N]B, N = input_ids.size()# 2.1 生成 Prompt Embedding → [1, L, H] → 擴展至 [B, L, H]prompt_emb, _ = prompt_encoder(init_emb.unsqueeze(0))prompt_emb    = prompt_emb.expand(B, -1, -1)# 2.2 獲取輸入 embeddings & 拼接 → [B, L+N, H]token_emb     = model.get_input_embeddings()(input_ids)inputs_embeds = torch.cat([prompt_emb, token_emb], dim=1)# 2.3 構造新的 attention_mask & labelsprompt_mask   = torch.ones(B, L, device=device)new_mask      = torch.cat([prompt_mask, attention_mask], dim=1)prompt_labels = torch.full((B, L), -100, device=device, dtype=torch.long)new_labels    = torch.cat([prompt_labels, labels], dim=1)# 2.4 前向 + 反向outputs = model(inputs_embeds=inputs_embeds,attention_mask=new_mask,labels=new_labels)loss = outputs.lossloss.backward()optimizer.step()optimizer.zero_grad()# 3. 保存 Prompt Encoder
torch.save(prompt_encoder.state_dict(), "deepseek_ptuning_lstm.bin")# 4. 加載 & 推理
prompt_encoder.load_state_dict(torch.load("deepseek_ptuning_lstm.bin"))
model.eval()
prompt_encoder.eval()
context = "In a distant future"
tokens  = tokenizer(context, return_tensors="pt").to(device)
input_ids, attn = tokens.input_ids, tokens.attention_mask
B, N = input_ids.size()prompt_emb, _ = prompt_encoder(init_emb.unsqueeze(0))
prompt_emb    = prompt_emb.expand(B, -1, -1)
token_emb     = model.get_input_embeddings()(input_ids)
inputs_embeds = torch.cat([prompt_emb, token_emb], dim=1)
prompt_mask   = torch.ones(B, L, device=device)
new_mask      = torch.cat([prompt_mask, attn], dim=1)generated = model.generate(inputs_embeds=inputs_embeds,attention_mask=new_mask,max_length=L+N+50)[:, L:]
print(tokenizer.decode(generated[0], skip_special_tokens=True))

3.3 LoRA

?

Low-Rank Adaptation (LoRA) 是一種參數高效微調(PEFT)技術,通過將原始的大規模模型權重更新分解成兩個低秩矩陣 ?和 ,并替代原始權重增量,實現只訓練這兩段小矩陣而凍結其他參數,從而將可訓練參數量降低約 10,000 倍,顯存占用減少 3 倍,并保持與全量微調相當或更優的表現 。

  • 核心原理:在 Transformer 的線性層 W 旁路注入低秩增量

    ,

    其中 為縮放系數,通常設定為以平衡學習速率與參數尺度 。

  • 優點

    1. 極少可訓練參數:僅占原模型的零點幾至千分之一;

    2. 無推理延遲:與原模型結構兼容,無需在推理時合并額外層;

    3. 高效易用:Hugging Face PEFT、LoRAX 等開源工具提供開箱即用實現 。

LoRA 現已成為 2025 年主流大模型微調方法之一,廣泛應用于 BERTGPT-2/3LLaMADeepSeek-R1 等多種模型。

# 1. LoRA 注入方法略——替換所有 nn.Linear 為 LoRALinear
for p in model.parameters(): p.requires_grad=False
for m in model.modules():if isinstance(m, LoRALinear):for p in m.parameters(): p.requires_grad=True
classifier = nn.Linear(H, num_labels)
optimizer = AdamW(list(filter(lambda p:p.requires_grad, model.parameters())) + list(classifier.parameters()), lr=1e-4)# 2. 訓練/保存/加載 同常規 fine-tuning

?

1. BERT 上的 LoRA 微調示例

import torch
from transformers import BertForSequenceClassification, BertTokenizerFast, Trainer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType# 1.1 設備與模型/分詞器加載
device    = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_id  = "bert-base-uncased"
tokenizer = BertTokenizerFast.from_pretrained(model_id)
model     = BertForSequenceClassification.from_pretrained(model_id, num_labels=2).to(device)  # 二分類  [oai_citation:0?Hugging Face](https://huggingface.co/docs/transformers/en/peft?utm_source=chatgpt.com)# 1.2 凍結主模型參數,只訓練 LoRA 矩陣
for param in model.base_model.parameters():param.requires_grad = False  # 凍結 Bert 主體# 1.3 配置 LoRA
lora_config = LoraConfig(task_type="SEQ_CLS",   # 序列分類任務inference_mode=False,  # 微調模式r=8,                   # LoRA 低秩矩陣秩lora_alpha=16,         # 縮放系數lora_dropout=0.1,      # Dropout 比例target_modules=["query", "value"]  # 在自注意力的 query 和 value 矩陣上注入
)  [oai_citation:1?Hugging Face](https://huggingface.co/docs/peft/en/package_reference/lora?utm_source=chatgpt.com)# 1.4 包裝模型
model = get_peft_model(model, lora_config)  # 插入 A, B 矩陣  [oai_citation:2?Hugging Face](https://huggingface.co/docs/peft/main/en/developer_guides/lora?utm_source=chatgpt.com)
model.print_trainable_parameters()          # 輸出可訓練參數比例# 1.5 準備訓練數據(示例)
texts  = ["I love this!", "This is terrible."]
labels = [1, 0]
enc     = tokenizer(texts, padding=True, truncation=True, return_tensors="pt").to(device)
dataset = torch.utils.data.TensorDataset(enc["input_ids"], enc["attention_mask"], torch.tensor(labels))# 1.6 定義 Trainer
training_args = TrainingArguments(output_dir     = "./bert-lora-output",per_device_train_batch_size = 8,num_train_epochs           = 3,learning_rate              = 3e-4,logging_steps              = 10,save_steps                 = 50
)
trainer = Trainer(model=model,args=training_args,train_dataset=dataset
)# 1.7 訓練
trainer.train()  # 僅更新 LoRA 矩陣參數  [oai_citation:3?GitHub](https://github.com/huggingface/peft?utm_source=chatgpt.com)# 1.8 保存 & 加載
model.save_pretrained("bert-lora-model")
# 重新加載時:
# from peft import PeftModel
# model = BertForSequenceClassification.from_pretrained(model_id, num_labels=2)
# model = PeftModel.from_pretrained(model, "bert-lora-model")

?


2. DeepSeek-R1 上的 LoRA 微調示例

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType# 2.1 設備與模型/分詞器加載
device    = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_id  = "deepseek-ai/DeepSeek-R1"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model     = AutoModelForCausalLM.from_pretrained(model_id).to(device)  # 自回歸模型  [oai_citation:4?Hugging Face](https://huggingface.co/docs/transformers/en/peft?utm_source=chatgpt.com)# 2.2 凍結主模型參數,只訓練 LoRA 矩陣
for param in model.parameters():param.requires_grad = False# 2.3 配置 LoRA(自回歸任務)
lora_config = LoraConfig(task_type="CAUSAL_LM",  # 自回歸語言模型inference_mode=False,r=8,lora_alpha=32,lora_dropout=0.05,target_modules=["c_attn", "c_proj"]  # GPT 風格模型的注意力層
)  [oai_citation:5?Hugging Face](https://huggingface.co/docs/peft/en/package_reference/lora?utm_source=chatgpt.com)# 2.4 包裝模型
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()# 2.5 準備訓練數據(示例)
texts = ["Once upon a time,","In a galaxy far, far away,"
]
enc = tokenizer(texts, return_tensors="pt", padding=True, truncation=True).to(device)
# Labels = 輸入右移一位的 input_ids
labels = enc["input_ids"].clone()dataset = torch.utils.data.TensorDataset(enc["input_ids"], enc["attention_mask"], labels)# 2.6 定義 Trainer
training_args = TrainingArguments(output_dir     = "./deepseek-lora-output",per_device_train_batch_size = 4,num_train_epochs           = 3,learning_rate              = 2e-4,logging_steps              = 10,save_steps                 = 50
)
trainer = Trainer(model=model,args=training_args,train_dataset=dataset
)# 2.7 訓練
trainer.train()  # LoRA 矩陣參與梯度更新# 2.8 保存 & 加載
model.save_pretrained("deepseek-lora-model")
# 重新加載時:
# from peft import PeftModel
# base = AutoModelForCausalLM.from_pretrained(model_id)
# model = PeftModel.from_pretrained(base, "deepseek-lora-model")

3.4 QLoRA

# 1. 4-bit 量化
for n,p in model.named_parameters():p.data = quantize_4bit(p.data, dtype='nf4')
# 2. 注入 LoRA + 凍結主體 + 訓練
# 3. model.save_pretrained & from_pretrained 進行加載推理

只是比lora 加了

# 1. 4-bit 量化
for n,p in model.named_parameters():
? ? p.data = quantize_4bit(p.data, dtype='nf4')


通過上述詳細流程,您即可在 2025 年的常見生產場景中,根據數據量、資源與任務需求,靈活選擇和實施軟提示(Prompt/P-Tuning)或參數高效微調(LoRA/QLoRA)方法,實現千億參數模型的高效適配與部署。

?

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/84211.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/84211.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/84211.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

圖解深度學習 - 深度學習的工作原理

上一篇,我們已經知道機器學習是將輸入(比如圖像)映射到目標(比如數字“4”)的過程。這一過程是通過觀察許多輸入和目標的示例來完成的。 我們還知道,深度神經網絡通過一系列簡單的數據變換(層&…

實現圖片自動壓縮算法,canvas壓縮圖片方法

背景: 在使用某些支持webgl的圖形庫(eg:PIXI.js,fabric.js)場景中,如果加載的紋理超過webgl可處理的最大紋理限制,會導致渲染的紋理缺失,甚至無法顯示。 方案 實現圖片自動壓縮算…

周界安全防護新突破:AI智能分析網關V4周界入侵檢測算法的技術應用

一、方案概述 在安防周界防護領域,傳統紅外對射、電子圍欄等防護系統弊端顯著,其誤報率高,易受飛鳥、樹枝等干擾,且在惡劣天氣、復雜光照下難以精準識別入侵。隨著安全需求升級,基于AI智能分析網關V4的周界翻越入侵檢…

解決服務器重裝之后vscode Remote-SSH無法連接的問題

在你的windows命令窗口輸入: ssh-keygen -R 服務器IPssh-keygen 不是內部或外部命令 .找到Git(安裝目錄)/usr/bin目錄下的ssh-keygen.exe(如果找不到,可以在計算機全局搜索) 2.屬性–>高級系統設置–>環境變量–>系統變量,找到Path變量&#…

leetcode 33. Search in Rotated Sorted Array

題目描述 可以發現的是,將數組從中間分開成左右兩部分的時候,一定至少有一部分的數組是有序的。左部分[left,mid-1],右部分[mid1,right]。 第一種情況:左右兩部分都是有序的,說明nums[mid]就是整個數組的最大值。此時…

推薦一款滴滴團隊開源流程圖編輯框架logic-flow

LogicFlow 是一款基于 JavaScript 的流程圖編輯框架,提供直觀的可視化界面,幫助用戶輕松創建、編輯和管理復雜的工作流、業務邏輯或流程模型。其核心優勢在于低代碼化、高度可定制和強交互性,適用于業務系統開發、BPMN 流程設計、決策樹建模等…

java 進階 1.0.3

Thread API說明 自己滾去看文檔 CPU線程調度 每一個線程的優先使用權都是系統隨機分配的,人人平等 誰先分配到就誰先用 也可以耍賴,就是賦予某一個線程擁有之高使用權:優先級 這樣的操作就叫做線程調度 最基本的是系統輪流獲得 java的做法是搶…

匯川EasyPLC MODBUS-RTU通信配置和編程實現

累積流量計算(MODBUS RTU通信數據處理)數據處理相關內容。 累積流量計算(MODBUS RTU通信數據處理)_流量積算儀modbus rtu通訊-CSDN博客文章瀏覽閱讀219次。1、常用通信數據處理MODBUS通信系列之數據處理_modbus模擬的數據變化后會在原來的基礎上累加是為什么-CSDN博客MODBUS通…

【機械視覺】Halcon—【二、Halcon算子全面介紹(超詳細版)】

介紹 Halcon 的算子(operators)按照功能被系統性地劃分為多個類別,官方文檔中目前(Halcon 22.11 版本)共有 19 個主分類,每個主分類下還有若干子分類。 本人在此對這19個分類的常用核心算子進行了一系列的…

Https流式輸出一次輸出一大段,一卡一卡的-解決方案

【背景】 最近遇到一個奇怪的現象,前端vue,后端python,服務部署在服務器上面后,本來一切正常,但公司說要使用https訪問,想著也沒什么問題,切過去發現在沒有更改任何代碼的情況下,ht…

Vue常用自定義指令-積累的魅力【VUE】

前言 在【自定義指令—v2與v3之間的區別【VUE基礎】一文中,整理了自定義指令部分vue2和vue3 兩個版本的區別,有興趣的伙伴或者針對自定義部分比較迷茫的伙伴可以跳轉看一下。此次主要介紹一些自己積累的一些自定義指令的代碼,與大家一起分享。…

【mysql】mysql的高級函數、高級用法

mysql是最常用的數據庫之一,常見的函數用法大家應該都很熟悉,本文主要例舉一些相對出現頻率比較少的高級用法 (注:需注意mysql版本,大部分高級特性都是mysql8才有的) 多值索引與虛擬列 主要是解決字符串索引問題,光說…

C#日期和時間:DateTime轉字符串全面指南

C#日期和時間:DateTime轉字符串全面指南 在 C# 開發中,DateTime類型的時間格式化是高頻操作場景。無論是日志記錄、數據持久化,還是接口數據交互,合理的時間字符串格式都能顯著提升系統的可讀性和兼容性。本文將通過 20 實戰示例…

Canvas設計圖片編輯器全講解(一)Canvas基礎(萬字圖文講解)

一、前序 近兩年AI發展太過迅速,各類AI產品層出不窮,AI繪圖/AI工作流/AI視頻等平臺的蓬勃發展,促使圖片/視頻等復雜內容的創作更加簡單,讓更多普通人有了圖片和視頻創作的機會。另一方面用戶內容消費也逐漸向圖片和視頻傾斜。在“…

Javase易混點專項復習02_static關鍵字

1. static關鍵字1.1概述1.2修飾一個成員變量例:1.2.1靜態屬性與非靜態屬性示例及內存圖對比 1.3修飾一個方法(靜態方法)1.4.static修飾成員的訪問特點總結1.5動態代碼塊和靜態代碼塊1.5.1動態代碼塊1.5.2 靜態代碼塊 1.6帶有繼承的對象創建過…

C++滑動門問題(附兩種方法)

題目如下&#xff1a; 滑動窗口 - 題目 - Liusers OJ ——引用自OJ網站 方法如下&#xff1a; 1.常規思想 #include<bits/stdc.h> using namespace std; int main() {int n,k;int a[110];cin>>n>>k;for(int i0;i<n;i){cin>>a[i];}for(int i0;i…

mysql連接池druid監控配置

文章目錄 前置依賴啟用配置訪問監控一些問題 前置 連接池有很多類型&#xff0c;比如 c3p0&#xff0c;比如 hikariCP&#xff0c;比如 druid。c3p0 一些歷史項目可能用的比較多&#xff0c;hikariCP 需要高性能的項目比較多&#xff0c;druid 性能也很好&#xff0c;而且還提…

Jetson系統燒錄與環境配置全流程詳解(含驅動、GCC、.Net設置)

Jetson系統燒錄與環境配置全流程詳解&#xff08;含驅動、GCC、.Net設置&#xff09; 目錄1. 準備工作與工具安裝1.1 主機系統要求1.2 安裝 SDK Manager 2. JetPack 系統燒錄流程2.1 Jetson 進入恢復模式2.2 使用 SDK Manager 燒錄 JetPack 3. Jetson 系統基礎設置4. 配置 .Net…

分布式緩存:緩存的三種讀寫模式及分類

文章目錄 緩存全景圖Pre緩存讀寫模式概述1. Cache Aside&#xff08;旁路緩存&#xff09;工作流程優缺點 2. Read/Write Through&#xff08;讀寫穿透&#xff09;工作流程優缺點典型場景 3. Write Behind Caching&#xff08;異步寫回&#xff09;工作流程優缺點典型場景 緩存…

Ntfs!FindFirstIndexEntry函數中ReadIndexBuffer函數的作用是新建一個Ntfs!_INDEX_LOOKUP_STACK結構

第一部分&#xff1a; 0: kd> kc # 00 Ntfs!FindFirstIndexEntry 01 Ntfs!NtfsRestartIndexEnumeration 02 Ntfs!NtfsQueryDirectory 03 Ntfs!NtfsCommonDirectoryControl 04 Ntfs!NtfsFsdDirectoryControl 05 nt!IofCallDriver 06 nt!IopSynchronousServiceTail 07 nt!Nt…