論文PDF地址:https://arxiv.org/pdf/2110.07602.pdf
P-Tuning v2
摘錄自第三部分
桔色塊指代可訓練的prompt embedding;藍色塊是由固定(凍結)的預訓練語言模型 存儲或計算的embedding。
Deep Prompt Tuning
continuous prompts(連續提示) 僅僅能夠插入到input embedding序列層。如此,有兩個問題:首先由於序列長度的約束限制,可調參數的數量有限。其次,輸入的embedding對模型預測有間接的影響。
爲了解決這些問題,P-Tuning v2使用deep prompt tuning的方案。正如上圖的b部分,prompt作爲prefix token插入到不同的層中。一方面,p-tuning v2有更多可調的特定任務參數(從 0.01% 到 0.1%~3%),擴大了任務的容量也提高了參數效率;另一方面,添加到更深層的prompt對模型的預測會有更多直接的影響。
理解
在P-tuning v2的方案中,從圖直觀來看,有兩個關鍵的點:
- prompts會加在序列的前端,而不僅僅是插入到input embedding
- 每一層都會插入prompts
v2版本主要基於p-tuning和prefix-tuning技術。prompt 向量是在模型的 embedding 層與其他輸入 token 的 embedding 相拼接的,且通過在預訓練模型的每一層引入可訓練的 prompt 向量來提高模型對特定任務的適應性。
p-tuning主要是利用一個prompt encoder,將prompt先encoder再與input embedding進行拼接。
prefix-tuning是在Transformer的Encoder和Decoder的網絡中都加了一些特定的前綴。
而基於這兩種技術的v2版本,則是將兩者結合。在embedding與transformer模塊都做了prompt向量的插入。
ChatGLM中,首先要對prompt做encode,作爲前綴prefix拼接插入到input embedding與transformer模型中。
# 轉載請備註出處:https://www.cnblogs.com/zhiyong-ITNote/
class PrefixEncoder(torch.nn.Module):
"""
The torch.nn model to encode the prefix
Input shape: (batch-size, prefix-length)
Output shape: (batch-size, prefix-length, 2*layers*hidden)
"""
def __init__(self, config):
super().__init__()
self.prefix_projection = config.prefix_projection
if self.prefix_projection:
# Use a two-layer MLP to encode the prefix
self.embedding = torch.nn.Embedding(config.pre_seq_len, config.hidden_size)
self.trans = torch.nn.Sequential(
torch.nn.Linear(config.hidden_size, config.hidden_size),
torch.nn.Tanh(),
torch.nn.Linear(config.hidden_size, config.num_layers * config.hidden_size * 2)
)
else:
self.embedding = torch.nn.Embedding(config.pre_seq_len, config.num_layers * config.hidden_size * 2)
def forward(self, prefix: torch.Tensor):
if self.prefix_projection:
prefix_tokens = self.embedding(prefix)
past_key_values = self.trans(prefix_tokens)
else:
past_key_values = self.embedding(prefix)
return past_key_values
在ChatGLMModel中調用並插入到每一個transformer模型層中。
class ChatGLMModel(ChatGLMPreTrainedModel):
'''
省略其它....
'''
def __init__(self, config: ChatGLMConfig, empty_init=True):
if self.pre_seq_len is not None:
for param in self.parameters():
param.requires_grad = False
self.prefix_tokens = torch.arange(self.pre_seq_len).long()
# encode prompt
self.prefix_encoder = PrefixEncoder(config)
self.dropout = torch.nn.Dropout(0.1)
# 調用prompt
def get_prompt(self, batch_size, device, dtype=torch.half):
prefix_tokens = self.prefix_tokens.unsqueeze(0).expand(batch_size, -1).to(device)
# 調用prompt並返回
past_key_values = self.prefix_encoder(prefix_tokens).type(dtype)
past_key_values = past_key_values.view(
batch_size,
self.pre_seq_len,
self.num_layers * 2,
self.num_attention_heads,
self.hidden_size // self.num_attention_heads
)
# seq_len, b, nh, hidden_size
past_key_values = self.dropout(past_key_values)
past_key_values = past_key_values.permute([2, 1, 0, 3, 4]).split(2)
# past_key_values = [(v[0], v[1]) for v in past_key_values]
return past_key_values
# 返回transformer模型
def get_layer(layer_id):
return GLMBlock(
self.hidden_size,
self.num_attention_heads,
self.layernorm_epsilon,
layer_id,
inner_hidden_size=self.inner_hidden_size,
hidden_size_per_attention_head=self.hidden_size_per_attention_head,
layernorm=LayerNorm,
use_bias=True,
params_dtype=self.params_dtype,
position_encoding_2d=self.position_encoding_2d,
empty_init=empty_init
)
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
position_ids: Optional[torch.LongTensor] = None,
attention_mask: Optional[torch.Tensor] = None,
past_key_values: Optional[Tuple[Tuple[torch.Tensor, torch.Tensor], ...]] = None,
inputs_embeds: Optional[torch.LongTensor] = None,
use_cache: Optional[bool] = None,
output_attentions: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
) -> Union[Tuple[torch.Tensor, ...], BaseModelOutputWithPast]:
# 其它代碼
if past_key_values is None:
if self.pre_seq_len is not None:
# 調用prompt
past_key_values = self.get_prompt(batch_size=input_ids.shape[0], device=input_ids.device,
dtype=inputs_embeds.dtype)
else:
past_key_values = tuple([None] * len(self.layers))
if attention_mask is None:
attention_mask = self.get_masks(
input_ids,
device=input_ids.device
)
# 其它代碼
for i, layer in enumerate(self.layers):
if output_hidden_states:
all_hidden_states = all_hidden_states + (hidden_states,)
# 準備參數傳遞到layer
layer_past = past_key_values[i]
# 每個layer 是一個GLMBlock即transformer模型層
if self.gradient_checkpointing and self.training:
# 將prompt傳遞到每個層中
layer_ret = torch.utils.checkpoint.checkpoint(
layer,
hidden_states,
position_ids,
attention_mask,
torch.tensor(i),
layer_past,
use_cache,
output_attentions
)
else:
layer_ret = layer(
hidden_states,
position_ids=position_ids,
attention_mask=attention_mask,
layer_id=torch.tensor(i),
layer_past=layer_past,
use_cache=use_cache,
output_attentions=output_attentions
)
# 其它代碼