Open Doufanfan opened 1 year ago
+1
这个属于模型注意力训练机制的att_mask方法的问题。上面的两个设计的核心思路是一样的, prefix tokens + prompt 部分不被遮掩,answer部分被遮掩,这两部分使用了通用双向注意力机制,同时prefix tokens + prompt 部分使用了语言模型因果注意力机制,两者作用叠加。区别是代码整体组织不一样、past_key_values的应用也不一样。
一、chatglm1中get_masks的实现逻辑看似简单,是因为input_ids和前置tokens的注意力编码不在get_masks函数中,另外past_key_values的应用方式也不一样,并且也没有组织在get_masks函数中。完整的代码分析过程如下:
def get_masks(self, input_ids, device):
batch_size, seq_length = input_ids.shape
context_lengths = [seq.tolist().index(self.config.bos_token_id) for seq in input_ids]
attention_mask = torch.ones((batch_size, seq_length, seq_length), device=device)
attention_mask.tril_() #### 注意: 构造语言模型的上三角注意力掩码矩阵
for i, context_length in enumerate(context_lengths): # ### 在上三角注意力掩码矩阵基础上融入数据建模过程句子tokens掩码范围信息,很重要,在glm2和llama1&2中上三角掩码矩阵和数据建模过程句子tokens掩码矩阵是分开构建,再逐乘或追加来融合的。glm1这里具有合二为一的功效。
attention_mask[i, :, :context_length] = 1 # ######## 注意关键这行,即prompt部分tokens对应位置置1,answer部分为0
attention_mask.unsqueeze_(1)
attention_mask = (attention_mask < 0.5).bool() ###### 注意这行,即prompt部分tokens对应位置置0,answer部分为1
对应了注意力计算中的attention_scores.masked_fill_(attention_mask, -10000.0) 见下文。
return attention_mask
if past_key_values is None:
if self.pre_seq_len is not None:
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
)
。。。。。。
if self.pre_seq_len is not None and attention_mask is not None:
prefix_attention_mask = torch.ones(batch_size, 1, input_ids.size(-1), self.pre_seq_len).to(
attention_mask.device)
prefix_attention_mask = (prefix_attention_mask < 0.5).bool()
attention_mask = torch.cat((prefix_attention_mask, attention_mask), dim=3)
。。。。。。。 def attention_fn( self, query_layer, key_layer, value_layer, attention_mask, hidden_size_per_partition, layer_id, layer_past=None, #################### 即上述的 past_key_values scaling_attention_score=True, use_cache=False, ): if layer_past is not None: past_key, past_value = layer_past[0], layer_past[1] key_layer = torch.cat((past_key, key_layer), dim=0) value_layer = torch.cat((past_value, value_layer), dim=0) seq_len, b, nh, hidden_size = key_layer.shape
if use_cache:
present = (key_layer, value_layer)
else:
present = None
attention_scores = matmul_result.view(*output_size) if not (attention_mask == 0).all(): attention_scores.maskedfill(attention_mask, -10000.0) # ## 重点:元素1的位置替换为负一万,被softmax后概率为0,即要被mask的answer部分。 dtype = attention_scores.dtype
总结:(1)注意力attention_mask充分关注了prefix+prompt部分的上下文信息,注意力计算中对answer部分要做mask。 (2)注意力attention_mask仅考虑了初始化prefix前置序列,没有考虑之后历史序列past_key_values缓存部分的tokens的注意力掩码构建,即没有充分发挥出kv缓存部分在推理生成过程的因果注意力机制,从 context_lengths = [seq.tolist().index(self.config.bos_token_id) for seq in input_ids]和attention_mask[i, :, :context_length] = 1两行代码就可以看出没有实现真正的动态kv的掩码注意,并且没有在其他地方做代码补充。【【【特别重点的关键分析: 从attention_mask = torch.cat((prefix_attention_mask, attention_mask), dim=3)、和prefix_attention_mask, attention_mask的构建过程必须要清楚的是:chatGLM1 这里仅仅是考虑了在当前input_ids的att mask前面增加prefix部分的att mask, 而没有考虑past_key_values部分的所达最新位置的注意力掩码计算,这也正是glm1的att mask的不足之出。因此glm2对此做了改进,并且代码组织特别好,重新组织在get_masks函数中。另外要说的是,glm1和glm2和llama1&2都使用了语言模型上三角因果注意力掩码机制,只是这glm1/glm2/llama1&2这三个的计算方法均不一样。关于glm2和llama1&2的att mask构建代码分析过程如下】】】
二、chatglm2 中相关问题的完整的代码分析过程如下: def get_masks(self, input_ids, past_key_values, padding_mask=None): batch_size, seq_length = input_ids.shape full_attention_mask = torch.ones(batch_size, seq_length, seq_length, device=input_ids.device) full_attentionmask.tril() ### 重要:构建实际input_ids中序列长度的上三角0掩码矩阵 past_length = 0 if past_key_values: past_length = past_key_values[0][0].shape[0] ###### 极为关键的地方:几何意义是:提取注意力计算中kv缓存信息中的第一个层缓存的kv对的注意力K的序列长度shape[0],她代表的是prefix+之前的实际tokens长度,即因果注意力的因的部分!!!######### if past_length: full_attention_mask = torch.cat((torch.ones(batch_size, seq_length, past_length, device=input_ids.device), full_attention_mask), dim=-1) if padding_mask is not None: full_attention_mask = full_attention_mask * padding_mask.unsqueeze(1) ### 关键点:要理解这里逐乘法运算的集合意义:信息融合之外,保留了answer部分tokens对应位置为0,即要被mask的部分。 if not past_length and padding_mask is not None: full_attention_mask -= padding_mask.unsqueeze(-1) - 1 full_attention_mask = (full_attention_mask < 0.5).bool() # prefix+prompt对应位置为0,answer部分为1, 为了呼应注意力计算中的attention_scores.maskedfill(attention_mask, -10000.0) ,将answer部分被mask。 full_attentionmask.unsqueeze(1) return full_attention_mask
---------------------------- chatHLM2-6B get_masks函数 数学过程解析------------------- 上面关键代码已经注释,下面分析数学过程(重要:因果注意力+通用双向注意力,与llama1&2的因果注意力+通用双向注意力思路相同,但实现方法不同,并且chatglm2最后又一点点改进):
(1)首先,full_attentionmask.tril() ### 重要:构建实际input_ids中序列长度的上三角0掩码矩阵:设input_ids的序列长度seq_length为6
e.g:
[
[1 0 0 0 0 0]
[1 1 0 0 0 0]
[1 1 1 0 0 0]
[1 1 1 1 0 0]
[1 1 1 1 1 0]
[1 1 1 1 1 1]
] --------------------------- 1表示要注意,0是不要猪的
(2)当前置tokens长度有值时,假设前置序列长度pre_seq_len为2,执行: full_attention_mask = torch.cat((torch.ones(batch_size, seq_length, past_length, device=input_ids.device), full_attention_mask), dim=-1)
***核心**: 因为前置部分需要主已,所以构建前置全1矩阵 6 x 2: 注意为什么是6 x 2的矩阵,因为这个前置注意力计算公式设计有关,6代表原始input_ids真实序列,2为前置长度,前置注意力计算的是当前模型喂入的真实序列每个token与prefix前置历史包括当前真实序列中全部tokens的个个关联度。对于前置注意力的数学计算与代码逻辑实现,有机会给大家再细说,这里不再啰嗦。
构建前置全1矩阵 6 x 2: [ [1 1] [1 1] [1 1] [1 1] [1 1] [1 1] ] --------------------------- 1表示要注意
torch.cat后得到full_attention_mask:
[ 矩阵 6 x 8 [1 1 1 0 0 0 0 0] [1 1 1 1 0 0 0 0] [1 1 1 1 1 0 0 0] [1 1 1 1 1 1 0 0] [1 1 1 1 1 1 1 0] [1 1 1 1 1 1 1 1] ] --------------------------- 1表示要注意,0是不要猪的
(3) full_attention_mask = full_attention_mask * padding_mask.unsqueeze(1) ### 关键点:要理解这里逐乘法运算的集合意义:信息融合之外,保留了answer部分tokens对应位置为0,即要被mask的部分。
padding_mask即合并了前置tokens的通用双向注意力 ,形态[b, prefix+seq_len],
追踪代码:
if past_key_values is None:
past_key_values = self.get_prompt(batch_size=batch_size, device=input_ids.device,
dtype=inputs_embeds.dtype)
if attention_mask is not None: ###### 对应 prefix+prompt
attention_mask = torch.cat([attention_mask.new_ones((batch_size, self.pre_seq_len)),
attention_mask], dim=-1)
可见, padding_mask即合并了前置tokens的通用双向注意力 ,形态[b, prefix+seq_len], 每条样本数据都是例如:
[1 1 1 1 1 0 0 0 ] 2+6的长度
因词,padding_mask.unsqueeze(1)增加一个新的维度后如下:
[ # b
[ 矩阵: 1 X 8
[[1 1 1 1 1 0 0 0 ] ]
]
.....
]
再与上面的full_attention_mask 逐乘法运算得到新的full_attention_mask:【矩阵6x8与矩阵1x8的逐乘法】 [ [ 矩阵 6 x 8 [1 1 1 0 0 0 0 0] [1 1 1 1 0 0 0 0] [1 1 1 1 1 0 0 0] [1 1 1 1 1 0 0 0] [1 1 1 1 1 0 0 0] [1 1 1 1 1 0 0 0] ] --------------------------- 1表示要注意,0是不要猪的 ..... ]
(4) 计算 full_attention_mask = (full_attention_mask < 0.5).bool() 这就很简单了,逻辑运算full_attention_mask < 0.5即可,把上面的0改为True, 1改为False 得到: 最终的full_attention_mask: [ [ 矩阵 6 x 8 [Flase Flase Flase True True True True True ] [Flase Flase Flase False True True True True ] [Flase Flase Flase False False True True True ] [Flase Flase Flase False False True True True ] [Flase Flase Flase False False True True True ] [Flase Flase Flase False False True True True ] ] --------------------------- 1表示要注意,0是不要猪的 ..... ]
上面分析这是使用了前置tokens的注意力计算结果。如果设置pre_seq_len=None,就会禁用前置注意力机制了。就是简单了把上三角因果注意力与通用双向注意力合并即可,比较简单,可以参考下面的llama1&2的计算详解。这里不在赘述。
上面的计算结果 full_attention_mask 在以下代买被英勇:
if full_attention_mask is None: ######## get_masks考虑因果注意力+通用双向注意力。 if (attention_mask is not None and not attention_mask.all()) or (past_key_values and seq_length != 1): full_attention_mask = self.get_masks(input_ids, past_key_values, padding_mask=attention_mask)
(5)关于前置tokens部分如何累积可参考代码:【注意在训练中,28个层每层都会这样计算前置的pre_seq_len个tokens,不会有多的,只有在验证和推理时才会在pre_seq_len个tokens上累积缓存历史信细。前置注意力计算的是当前模型喂入的真实序列每个token与prefix前置历史包括当前真实序列中全部tokens的个个关联度。对于前置注意力的数学计算与代码逻辑实现,有机会给大家再细说,这里不再啰嗦。 】 class SelfAttention(torch.nn.Module): 。。。。。。。 if kv_cache is not None: cache_k, cache_v = kv_cache key_layer = torch.cat((cache_k, key_layer), dim=0) ####### 重点:增加了缓存信息。 value_layer = torch.cat((cache_v, value_layer), dim=0) if use_cache: if kv_cache is None: kv_cache = torch.cat((key_layer.unsqueeze(0).unsqueeze(0), value_layer.unsqueeze(0).unsqueeze(0)), dim=1) else: kv_cache = (key_layer, value_layer) ######### 即past_key_values的一个元组元素。 else: kv_cache = None
(6)关于上述计算得到的 full_attention_mask还要进一步加工才能用来参与dot(q,k^T)/sqrt(dim) +mask 的计算。 full_attention_mask: [ [ 矩阵 6 x 8 [Flase Flase Flase True True True True True ] [Flase Flase Flase False True True True True ] [Flase Flase Flase False False True True True ] [Flase Flase Flase False False True True True ] [Flase Flase Flase False False True True True ] [Flase Flase Flase False False True True True ] ] --------------------------- 1表示要注意,0是不要猪的 ..... ]
加工如下: attention_mask_scores = attention_scores.masked_fill( full_attention_mask, float("-inf"))
其中attention_scores是通过上述(5)计算的追加了前置tokens的k信息,以及原本q信息计算的,形态也是 6x8矩阵。 上述加工目的是将full_attention_mask的True对应到attention_scores中相应位置改为负无穷数,其他数不变。负无穷softmax后得到几乎为0的数,银子,即被mask掉了。 这样mask机制就兼备了语言模型因果注意力机制和通用双向注意力机制,妙哉!
最后,要注意,在训练中use_cache参数被强制为None,因此只在非训练时past_key_values才真正起作用,使用缓存信息,加速推理。
(三) llama1&2 att mask构建:
语言模型上三角掩码注意力机制: def make_causal_mask( input_ids_shape: torch.Size, dtype: torch.dtype, device: torch.device, past_key_values_length: int = 0 ): """ Make causal mask used for bi-directional self-attention. """ bsz, tgt_len = input_ids_shape mask = torch.full((tgt_len, tgt_len), torch.finfo(dtype).min, device=device) # M[seq,seq]
构建全-oo矩阵: """ mask[ [-oo -oo -oo -oo -oo -oo] [-oo -oo -oo -oo -oo -oo] [-oo -oo -oo -oo -oo -oo] [-oo -oo -oo -oo -oo -oo] [-oo -oo -oo -oo -oo -oo] [-oo -oo -oo -oo -oo -oo] ] """
mask_cond = torch.arange(mask.size(-1), device=device) # vector [0, 1, ..., seq-1]
”“”解析下面这行 vector逐元素加1得到 [1, 2, ..., seq]向量,进一步转换为形态x[seq, 1]的矩阵。进行逻辑运算mask_cond[0, 1, ..., seq-1] < x[seq, 1]得:
e.g: 6x6矩阵,1为True,0为False x[ [1 0 0 0 0 0] [1 1 0 0 0 0] [1 1 1 0 0 0] [1 1 1 1 0 0] [1 1 1 1 1 0] [1 1 1 1 1 1] ]
mask.masked_fill(mask_cond < (mask_cond + 1).view(mask.size(-1), 1), 0) mask = mask.to(dtype)
将mask[seq, seq]全-oo矩阵中的对应x矩阵中为True的元素位置置0,相当于把上面x矩阵中的0和1位置对调。 得: mask[ [0 -oo -oo -oo -oo -oo] [0 0 -oo -oo -oo -oo] [0 0 0 -oo -oo -oo] [0 0 0 0 -oo -oo] [0 0 0 0 0 -oo] [0 0 0 0 0 0 ] ]
0位置表示要注意的token,-oo位置表示不需注意的token。 “”“
if past_key_values_length > 0: mask = torch.cat([torch.zeros(tgt_len, past_key_values_length, dtype=dtype, device=device), mask], dim=-1) return mask[None, None, :, :].expand(bsz, 1, tgt_len, tgt_len + past_key_values_length)
"""mask:
[ # b
[ # 1
[ # seq x (prefix-history+seq)
[0 .. 0 0 -oo -oo -oo -oo -oo]
[0 .. 0 0 0 -oo -oo -oo -oo]
[0 .. 0 0 0 0 -oo -oo -oo]
[0 .. 0 0 0 0 0 -oo -oo]
[0 .. 0 0 0 0 0 0 -oo]
[0 .. 0 0 0 0 0 0 0 ]
]
]
...
]
"""
数据建模层面的句子tokens掩码机制
def _expand_mask(mask: torch.Tensor, dtype: torch.dtype, tgt_len: Optional[int] = None):
"""
:param mask: 输入的句子的mask_ids矩阵[bsz, seq_len],其中1为要注意的token,0为不注意的。含prefix
:param tgt_len: 实际的seq,不含prefix
"""
bsz, src_len = mask.size()
expanded_mask = mask[:, None, None, :].expand(bsz, 1, tgt_len, src_len).to(dtype)
""" 增加空间维度,并扩展其维度大小得到: expanded_mask[b, 1, seq, prefix-history+seq]:
[
[
[
[1 ... 1 1 1 0 0]
[1 ... 1 1 1 0 0]
[1 ... 1 1 1 0 0]
[1 ... 1 1 1 0 0]
[1 ... 1 1 1 0 0]
[1 ... 1 1 1 0 0]
]
]
...
]
"""
inverted_mask = 1.0 - expanded_mask # 0改1,不注意的token。1改0,要注意的token。 x = inverted_mask.masked_fill(inverted_mask.to(torch.bool), torch.finfo(dtype).min) """ 解析: 将inverted_mask中的True(即1)替换为非常小的负数。在softmax时,注意力值会接近于零,即不被注意。得到x[b, 1, seq, seq]:
[ [ [ [0 ...0 0 0 -oo -oo] [0 ...0 0 0 -oo -oo] [0 ...0 0 0 -oo -oo] [0 ...0 0 0 -oo -oo] [0 ...0 0 0 -oo -oo] [0 ...0 0 0 -oo -oo] ] ] ... ] """ return x
逐加法合并上三角注意力掩码和句子tokens注意力掩码
def _prepare_decoder_attention_mask(attention_mask, input_shape, inputs_embeds, past_key_values_length): combined_attention_mask = None if input_shape[-1] > 1: combined_attention_mask = _make_causal_mask( input_shape, inputs_embeds.dtype, device=inputs_embeds.device, past_key_values_length=past_key_values_length, # kv缓存 )
if attention_mask is not None:
expanded_attn_mask = _expand_mask(
attention_mask, # mask_ids with shape shape [b, prefix-history+seq]
inputs_embeds.dtype,
tgt_len=input_shape[-1]
).to(inputs_embeds.device)
"""
combined_attention_mask 》》》mask:
[ # b
[ # 1
[ # seq x (prefix-history+seq)
[0 .. 0 0 -oo -oo -oo -oo -oo]
[0 .. 0 0 0 -oo -oo -oo -oo]
[0 .. 0 0 0 0 -oo -oo -oo]
[0 .. 0 0 0 0 0 -oo -oo]
[0 .. 0 0 0 0 0 0 -oo]
[0 .. 0 0 0 0 0 0 0 ]
]
]
...
]
expanded_attn_mask 》》》
[ # b
[ # 1
[ # seq x (prefix-history+seq)
[0 ...0 0 0 -oo -oo]
[0 ...0 0 0 -oo -oo]
[0 ...0 0 0 -oo -oo]
[0 ...0 0 0 -oo -oo]
[0 ...0 0 0 -oo -oo]
[0 ...0 0 0 -oo -oo]
]
]
...
]
》》》》》 这两个的逐加合并有0、1、-oo三个元素,兼顾了语言模型因果注意力机制和数据建模层面的掩码机制。可谓十分的完美。
"""
combined_attention_mask = (
expanded_attn_mask if combined_attention_mask is None
else expanded_attn_mask + combined_attention_mask
)
#### 合并结果的样子:【注意是逐加法运算,另外注意负无穷数加任何数还是负无穷】
[ # b [ # 1 [ # seq x (prefix-history+seq) [0 .. 0 0 -oo -oo -oo -oo -oo] [0 .. 0 0 0 -oo -oo -oo -oo] [0 .. 0 0 0 0 -oo -oo -oo] [0 .. 0 0 0 0 0 -oo -oo] [0 .. 0 0 0 0 0 -oo -oo] [0 .. 0 0 0 0 0 -oo -oo] ] ] ... ]
return combined_attention_mask
************** 代码追踪(到自注意力中):******************
query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
kv_seq_len = key_states.shape[-2] # 重点 seq
if past_key_value is not None:
kv_seq_len += past_key_value[0].shape[-2] # 重点:prefix+seq
cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len) #重点: 提取词嵌入的前kv_seq_len的位置的向量表征
query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids)
if past_key_value is not None:
# **** 重重重点:向kv中融合历史的past_key_value信息,kv对累积记忆历史信息 *******
key_states = torch.cat([past_key_value[0], key_states], dim=2) # k[bsz,k_h_num,prefix+q_len,128]
value_states = torch.cat([past_key_value[1], value_states], dim=2) # v[bsz,v_h_num,prefix+q_len,128]
past_key_value = (key_states, value_states) if use_cache else None
总结:
llama1&2 中att_mask构建算法,考虑了prefix history信息、语言模型因果注意力机制、数据建模层面的掩码机制, 两个注意力掩码的逐加合并有0、1、-oo三个元素,兼顾了语言模型因果注意力机制和数据建模层面的掩码机制。可谓十分的完美。
我们改进后的chatglm2模型的att mask的优点总结: (1)能力上与上面分析的llama1&2已经完全对齐。 (2)构建算法的代码更简洁,尽在一个gat_mask函数中。
💓💓感谢回答~ 看完之后依然有些问题👁👁
两个设计的核心思路是一样的(我理解是说chatglm1和chatglm2的mask设计?),即 prefix tokens + prompt 部分不被遮掩,answer部分被遮掩,区别是代码整体组织不一样、past_key_values的应用也不一样。
我认为1是这样,2不是。2是prefix tokens部分双向注意力(不被遮掩),prompt+answer部分单向注意力(被遮掩)。llama1、2的代码暂时还没看过,所以具体实现不是太清楚~ 之后可以学习学习~
glm1目前只是看过代码,没有测试输出1的中间过程确认,不过看代码认为1的mask实现的逻辑是prefix tokens + prompt 部分是双向注意力,answer是单向注意力。这个后续也可以运行验证一下。 glm2训练的过程中我把不同阶段的mask打印了出来,下面是整个过程和结论,如果有不正确的,希望帮我指出~~
第2阶段是在ChatGLMModel的forward中进行了mask的计算:
if self.pre_seq_len is not None:
if past_key_values is None:
past_key_values = self.get_prompt(batch_size=batch_size, device=input_ids.device,
dtype=inputs_embeds.dtype)
if attention_mask is not None:
attention_mask = torch.cat([attention_mask.new_ones((batch_size, self.pre_seq_len)),
attention_mask], dim=-1)
if full_attention_mask is None:
if (attention_mask is not None and not attention_mask.all()) or (past_key_values and seq_length != 1):
full_attention_mask = self.get_masks(input_ids, past_key_values, padding_mask=attention_mask)
def get_masks(self, input_ids, past_key_values, padding_mask=None):
batch_size, seq_length = input_ids.shape
full_attention_mask = torch.ones(batch_size, seq_length, seq_length, device=input_ids.device)
full_attention_mask.tril_()
past_length = 0
if past_key_values:
past_length = past_key_values[0][0].shape[0] # config.pre_seq_len
if past_length:
full_attention_mask = torch.cat((torch.ones(batch_size, seq_length, past_length,
device=input_ids.device), full_attention_mask), dim=-1)
if padding_mask is not None:
full_attention_mask = full_attention_mask * padding_mask.unsqueeze(1) # padding_mask.unsqueeze(1) = [batch_size, 1, seq_len]
if not past_length and padding_mask is not None:
full_attention_mask -= padding_mask.unsqueeze(-1) - 1
full_attention_mask = (full_attention_mask < 0.5).bool()
full_attention_mask.unsqueeze_(1)
return full_attention_mask
从输出的full_attention_mask来看,self.pre_seq_len is None或者有值时,prompt部分都是单向注意力。
1)self.pre_seq_len is None时:
# full_attention_mask
Out[59]:
tensor([[[[False, True, True, True, True, True, True, True, True, True],
[False, False, True, True, True, True, True, True, True, True],
[False, False, False, True, True, True, True, True, True, True],
[False, False, False, False, True, True, True, True, True, True],
[False, False, False, False, False, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False, False],
[False, False, False, False, False, False, False, False, False, False]]]])
2)self.pre_seq_len = 5 时:
# full_attention_mask
Out[91]:
tensor([[[False, False, False, False, False, False, True, True, True, True, True, True, True, True, True],
[False, False, False, False, False, False, False, True, True, True, True, True, True, True, True],
[False, False, False, False, False, False, False, False, True, True, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, True, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, False, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, False, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, False, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, False, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, False, True, True, True, True, True],
[False, False, False, False, False, False, False, False, False, False, True, True, True, True, True]]])
也一个新的问题💓:get_masks中将full_attention_mask和padding_mask.unsqueeze(1)点乘之后,如何满足past_length is None(if not past_length and padding_mask is not None
),还会计算full_attention_mask -= padding_mask.unsqueeze(-1) - 1
,但是在past_length有值的时候又不会计算这一步。这一步计算的主要作用就是将seq中padding的token对应的(1, seq) mask向量全部置为 > 0.5的数,最后计算为True,之后在attention_score那一步会置为-inf,但是不太理解为什么past_length有值又不这么处理,感觉做不做这一步好像都没有影响,之后算loss的时候这些step的label也是-100,是否需要将padding的token对应的整条mask向量置为-inf感觉都不会影响最终训练loss的计算
def get_masks(self, input_ids, past_key_values, padding_mask=None):
batch_size, seq_length = input_ids.shape
full_attention_mask = torch.ones(batch_size, seq_length, seq_length, device=input_ids.device)
full_attention_mask.tril_()
past_length = 0
if past_key_values:
past_length = past_key_values[0][0].shape[0] # config.pre_seq_len
if past_length:
full_attention_mask = torch.cat((torch.ones(batch_size, seq_length, past_length,
device=input_ids.device), full_attention_mask), dim=-1)
if padding_mask is not None:
full_attention_mask = full_attention_mask * padding_mask.unsqueeze(1) # padding_mask.unsqueeze(1) = [batch_size, 1, seq_len]
if not past_length and padding_mask is not None:
full_attention_mask -= padding_mask.unsqueeze(-1) - 1
full_attention_mask = (full_attention_mask < 0.5).bool()
full_attention_mask.unsqueeze_(1)
return full_attention_mask
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm,所以似乎没有双向注意力部分,都是单向注意力;也就是说GLM本身在设计的时候,是prefix-llm,但是应用在chatGLM时,讲双向注意力部分去掉了?
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm,所以似乎没有双向注意力部分,都是单向注意力;也就是说GLM本身在设计的时候,是prefix-llm,但是应用在chatGLM时,讲双向注意力部分去掉了?
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm
感觉这个比较符合我的认知,哈哈哈哈,还能找到对应的链接吗?想看看👁👁
glm论文的研究目标是想提出一个在nlu和nlg效果都比较好的、统一3种模型结构的模型,按照论文的掩码示意图感觉就是一个encoder-decoder的结构。之前看的文章也基本会从causal llm和prefix llm两个维度介绍llm,prefix llm都会举例chatglm。我是直接触的chatglm2,结果感觉它模型结构一直跟之前的介绍对不上,强迫症都要犯了💊💊🤣🤣
感觉1->2还是有结构上的调整,中间的变换过程应该是没论文吧?我好像没找到2对应的论文😅
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm,所以似乎没有双向注意力部分,都是单向注意力;也就是说GLM本身在设计的时候,是prefix-llm,但是应用在chatGLM时,讲双向注意力部分去掉了?
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm
感觉这个比较符合我的认知,哈哈哈哈,还能找到对应的链接吗?想看看👁👁
glm论文的研究目标是想提出一个在nlu和nlg效果都比较好的、统一3种模型结构的模型,按照论文的掩码示意图感觉就是一个encoder-decoder的结构。之前看的文章也基本会从causal llm和prefix llm两个维度介绍llm,prefix llm都会举例chatglm。我是直接触的chatglm2,结果感觉它模型结构一直跟之前的介绍对不上,强迫症都要犯了💊💊🤣🤣
感觉1->2还是有结构上的调整,中间的变换过程应该是没论文吧?我好像没找到2对应的论文😅
https://github.com/THUDM/ChatGLM2-6B/issues/64#issuecomment-1608693056 here
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm,所以似乎没有双向注意力部分,都是单向注意力;也就是说GLM本身在设计的时候,是prefix-llm,但是应用在chatGLM时,讲双向注意力部分去掉了?
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm
感觉这个比较符合我的认知,哈哈哈哈,还能找到对应的链接吗?想看看👁👁 glm论文的研究目标是想提出一个在nlu和nlg效果都比较好的、统一3种模型结构的模型,按照论文的掩码示意图感觉就是一个encoder-decoder的结构。之前看的文章也基本会从causal llm和prefix llm两个维度介绍llm,prefix llm都会举例chatglm。我是直接触的chatglm2,结果感觉它模型结构一直跟之前的介绍对不上,强迫症都要犯了💊💊🤣🤣 感觉1->2还是有结构上的调整,中间的变换过程应该是没论文吧?我好像没找到2对应的论文😅
#64 (comment) here
感谢🎉🎉 还有个地方想请教一下👁👁 chatglm2在模型结构设计上是不是直接嵌入了p-tuning v2的功能,我理解调用的方式就是通过设置model.config.pre_seq_len来实现。
chatglm2-6b和llama1&2都同时使用了语言模型因果注意力机制和通用双向注意力机制,具体的计算过程可以再次参考上述我的解读分析,我做了更详细的补充,以及每步为什么这样计算就讲了,已经很详细了。大家要一切以官方最新代码为准!一切都在代码里。
** 非常感谢大家对GLM的持续关注! **
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm,所以似乎没有双向注意力部分,都是单向注意力;也就是说GLM本身在设计的时候,是prefix-llm,但是应用在chatGLM时,讲双向注意力部分去掉了?
作者在之前的issue里好像说过,chatglm2是causal llm,不是prefix llm
感觉这个比较符合我的认知,哈哈哈哈,还能找到对应的链接吗?想看看👁👁 glm论文的研究目标是想提出一个在nlu和nlg效果都比较好的、统一3种模型结构的模型,按照论文的掩码示意图感觉就是一个encoder-decoder的结构。之前看的文章也基本会从causal llm和prefix llm两个维度介绍llm,prefix llm都会举例chatglm。我是直接触的chatglm2,结果感觉它模型结构一直跟之前的介绍对不上,强迫症都要犯了💊💊🤣🤣 感觉1->2还是有结构上的调整,中间的变换过程应该是没论文吧?我好像没找到2对应的论文😅
#64 (comment) here
感谢🎉🎉 还有个地方想请教一下👁👁 chatglm2在模型结构设计上是不是直接嵌入了p-tuning v2的功能,我理解调用的方式就是通过设置model.config.pre_seq_len来实现。
没有吧,你看的是不是p-tuning部分的代码呢
@lilongxian @Doufanfan 请问如何理解full_attention_mask -= padding_mask.unsqueeze(-1) - 1
的这个if
这一步?
@lilongxian @Doufanfan 请问如何理解
full_attention_mask -= padding_mask.unsqueeze(-1) - 1
的这个if
这一步?
同问?
chatglm是prefix llm,chatglm2是causal llm。 llama系列是causal llm。
首先是chatglm1中get_masks的实现逻辑:
对应chatglm1的tokenizer编码方式:
GLM1:input_ids 为 “c + t”: a1, a2 ,...,am, [gmask],[bos], b1, b2,...,bn,[eos]
a部分为双向attention,b部分-生成部分为单向attention,这个和论文
GLM: General Language Model Pretraining with Autoregressive Blank Infilling
中提到的预训练实现方式更一致。然后是chatglm2中get_masks的实现逻辑:
对应chatglm1的tokenizer编码方式:
GLM2:input_ids 为 “c + t”: [gMASK],<sop>, a1,a2,...,am, b1,b2,...bn,<eos>
a部分、b部分-生成部分均为单向attention,和论文的预训练方式不太能对应,除了增加的微调部分是双向attention,其余部分是decode-only的结构。
想了解这样修改的原因和目的是什么?有对应的论文介绍吗?是不是chatglm1->chatglm2也从prefix lm->causal lm?