为什么LLM(大模型)使用的是左填充

参考:Why current LLM uses left padding? | Trace Logits

transformer - While fine-tuning a decoder only LLM like LLaMA on chat dataset, what kind of padding should one use? - Artificial Intelligence Stack Exchange

https://github.com/huggingface/transformers/issues/14521

首先先说结论,为什么这样用?

1、因为在生成模型中(causal language model (like GPT series, LLaMA)),大多数都是这样实现的,所以需要对齐。

2、在生成的过程中,生成算法总是用最后一个token的logits去预测下一个token,如果使用右填充,使用的是pad的logits对下一个token采样,这可能会导致生成结果出现错误。

比如:输入,我喜欢苹果[pad] [pad] 。

期望预测和生成的内容:我喜欢吃苹果,因为它很好吃。

右填充模型输出:我喜欢吃苹果[pad] [pad],因为它很好吃。

左填充模型输出:[pad][pad]我喜欢吃苹果,因为它很好吃。

[pad] 卡在文本中间,这会造成模型生成的结果可能会很差。

也有人生成模型FAIR LLaMA2使用了右填充,但是实现起来相对复杂:

def generate(...)
  ...
	pad_id = self.tokenizer.pad_id
	tokens = torch.full((bsz, total_len), pad_id, dtype=torch.long, device="cuda")
  # right-padding
	for k, t in enumerate(prompt_tokens):
      tokens[k, : len(t)] = torch.tensor(t, dtype=torch.long, device="cuda")
  ...
  # only partial of the prompt are fed
      logits = self.model.forward(tokens[:, prev_pos:cur_pos], prev_pos)
  ...
      next_token = torch.argmax(logits[:, -1], dim=-1)
  ...
      next_token = next_token.reshape(-1)
  # only replace token if prompt has already been generated
      next_token = torch.where(
          input_text_mask[:, cur_pos], tokens[:, cur_pos], next_token
  )
  # so that only padded position are replaced with newly generated tokens
      tokens[:, cur_pos] = next_token

首先,为什么需要填充?

因为神经网络的输入一般是一个矩阵,利用矩阵运算可以大大的加快训练速度,如果句子的长度不一,就会造成输入的一个batch不是一个矩阵,就需要0填充。

如何对一个token进行采样?

确切的答案是:这取决于你使用的生成函数

然而,在大多数实现中,您必须向左填充。

以下是一个GPT2的实现:

def sample_sequence(model, length, start_token=None, batch_size=None, context=None, temperature=1, top_k=0, device='cuda', sample=True, enc=None):
    if start_token is None:
				 # if start_token is None, use context
        assert context is not None, 'Specify exactly one of start_token and context!'
        context = torch.tensor(context, device=device, dtype=torch.long).unsqueeze(
            0).repeat(batch_size, 1)
    else:
				 # if start_token isn't None, use start_token as the beginning of each sentences
        assert context is None, 'Specify exactly one of start_token and context!'
        context = torch.full((batch_size, 1), start_token,
                             device=device, dtype=torch.long)
    prev = context
    output = context
    # past is KV-cache
    past = None
    with torch.no_grad():
        for i in trange(length):  # generate `length` tokens for all sentences
            logits, past = model(prev, past=past)

            # logits.shape=[batch, text, vocab_szie], in Causal model, the logits of the last token in each sentence is used to predict next token, so pick `-1` here
            logits = logits[:, -1, :] / temperature
            logits = top_k_logits(logits, k=top_k)
            log_probs = F.softmax(logits, dim=-1)
            if sample:
                prev = torch.multinomial(log_probs, num_samples=1)
            else:
                _, prev = torch.topk(log_probs, k=1, dim=-1)

            # concatenate the sampled tokens to the original sentences, 
            # e.g. output = [I have] and sampled `an`
            # output = [I have an]
            output = torch.cat((output, prev), dim=1)

    return output

注意 logits = logits[:,-1,:]这一行,如果使用右填充,则会得到,我有一个苹果。[pad],[pad]。。。

然而,算法总是选择最后一个 token 的 logits 来预测下一个 token,如果我们在右侧进行填充,则模型实际上是使用 [pad] 的 logits 来预测下一个 token 。尽管使用注意力掩码,为具有 [pad] 标记的位置分配了注意力分数,但只要算法使用[pad]开始采样,该torch.multinomial函数就会使用错误的 logits,从而导致不正确的预测下一个token。您可以在这里看到类似的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值