Transformers 中支持 4D 掩码

社区文章 发布于 2024 年 1 月 8 日

随着最近合并的 PR*,Transformers 现在可以接受自定义的 4D 注意力掩码作为 .forward() 方法的参数。为什么是 4D,它带来了哪些机会?

理解 Transformers 中的注意力掩码

在自然语言处理领域,Transformers 中的注意力掩码通常是一个微妙但至关重要的组成部分。我们通常很少关注注意力掩码(双关语?),并传入 None,这是许多应用程序的默认参数。由分词器生成的标准注意力掩码是 2D 张量,形状为 [batch_size, total_sequence_length],填充为 1。这种格式表示序列中简单的线性注意力机制。

然而,深入研究 Transformer 架构,你会发现这些掩码会经历一个转换(另一个双关语?)。在模型内部,2D 张量掩码会变成 4D 张量,形状为 [batch_size, heads, input_ids_length, total_sequence_length]。这种格式允许更细致的注意力策略,例如因果解码,它使用一个全 1 的下三角矩阵,有时在存在键值(KV)缓存时还会补充一个全 1 的矩形。这种结构确保每个 token 只关注自身及其前面的邻居。

tensor([[[[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]]]])

定制 4D 掩码的出现

传统上,这些 4D 掩码是内部表示,用户无法直接访问或修改。最近合并的拉取请求允许将自定义 4D 掩码作为参数传递给 Transformers 中的 .forward() 方法,这标志着一个重大进步。此功能允许更复杂的注意力模式,这对于需要非顺序 token 处理或单个输入张量中包含多个序列的任务至关重要。

重要提示:如果向函数提供自定义掩码,很可能需要提供一个形状为 [batch_size, total_sequence_length] 的 position_ids 张量——它将允许位置编码调整自身以适应非顺序的 token 顺序。并非所有模型都接受自定义 position_ids 参数,但许多现代模型(如 Llama)都接受。其他模型可能需要打补丁。

以下是几个使用 4D 掩码改进内存和/或效率的用例

1. 内存高效的束搜索

考虑束搜索,这是一种在语言模型中生成文本的常见策略。传统上,具有共同前缀和不同结尾的序列批次是分开处理的,消耗大量内存。通过 4D 掩码,这些序列可以紧凑地表示在一个序列中,从而显着节省内存。

假设在束搜索的第 1 步我们得到 2 个束

  • 猫坐在**那**
  • 猫坐在**我**

在第 2 步,我们评估下一个位置的 4 个候选

  • 猫坐在垫子**上**
  • 猫坐在地板**上**
  • 猫坐在我的椅子**上**
  • 猫坐在我的桌子**上**

通常,这些将被评估为大小为 4、长度为 5 的批次。以下是如何将它们打包成长度为 9 的 1 个序列

token 地板 椅子 桌子
position_ids 0 1 2 3 3 4 4 4 4
蒙版 1 1 1 1 . . . . .
1 1 1 . 1 . . . .
1 1 1 1 . 1 . . .
1 1 1 1 . . 1 . .
1 1 1 . 1 . . 1 .
1 1 1 . 1 . . . 1

假设共同前缀已经处理并添加到 KV 缓存中,我们需要将以下内容传递给 model.forward()

  • input_ids 作为分词后的序列 垫 地板 椅子 桌子
  • position_ids 作为形状为 (1, 9) 的张量,如上所示
  • attention_mask 形状为 (1, 1, 5, 9)
  • kv_cache 长度等于 5 个 token(那些 position_id <= 3 的 token)。

结果是内存节省了大约 2 倍,随着使用更长、更多的束,节省的内存会更多。

此方法来自 Xupeng Miao 等人的论文 SpecInfer: 加速生成式大型语言模型服务与推测推理和 Token 树验证,作者在其中引入了拓扑感知因果掩码,以便在推测解码中构建 token 候选树时将所有 token 的树注意力计算融合到单个内核中。

image/png

2. SFT(有监督微调)训练中的序列打包。

基于此处的讨论:https://github.com/huggingface/trl/issues/805

当使用不同长度的序列对语言模型进行微调时,标准方法是使用序列结束 (EOS) token 分隔序列,如 T5 论文中介绍的那样。然而,这并不能阻止跨序列注意力。通过使用 4D 掩码,即使序列打包在一起,注意力也可以仅限于单个序列。这种方法显著降低了序列之间交叉污染的风险。

请参见下图了解方法说明

image/png

3. 前瞻解码。

Yichao Fu 等人提出的新方法“前瞻解码”,在“使用前瞻解码打破 LLM 推理的顺序依赖”一文中,将 token 生成和验证融合在一个pass中。这项技术,以前需要自定义 forward 函数来操作 token 注意力,现在可以使用 4D 注意力掩码进行简化。在 4D 掩码实现之前,它需要自定义 forward 函数,并对 token 的注意力进行精细操作。现在只需提供正确的掩码即可完成此操作。

image/png

注意力掩码的未来

展望未来,注意力掩码的演变可能不会止步于四维。引入第五维,表示层索引,可能会开启新的实验可能性,例如特定于层的注意力模式。另一个值得探索的领域是 head 维度,它允许像 Big Bird 那样进行稀疏注意力。

结论

在 Transformers 中集成自定义 4D 注意力掩码为处理复杂语言任务提供了更大的灵活性和效率。通过启用更复杂的注意力机制,这些掩码为新应用和现有应用的性能改进打开了大门。

社区

@poedator 关于 SFT (有监督微调) 训练中的序列打包,您有示例脚本吗?如果有的话,可以提供一下吗?非常感谢。

·
文章作者

不,我没有现成的脚本。请查看上面第 2 节中的链接或在其他地方搜索

@poedator ,感谢您的贡献。我想知道您在第 1 节中提到的管道,即 4D 自定义注意力掩码 + 自定义位置编码,在当前 transformers 版本 4.49.0 中是否仍然有效。我正在使用 modeling_llama 文件中的 LLamaForCausalLM 来使用 Llama 模型,并且我从解包和打包方法中获得了不一致的 logits。

·
文章作者

嗨, @jzx03
我不知道任何相关代码目前是否损坏,但 Transformers 是一个庞大且快速增长的库,以前也发生过类似的事情。最好您发布一个可重现的示例作为问题,显示 4.49 和某个早期版本中的行为差异。请参阅我如何在 https://github.com/huggingface/transformers/issues/29525 中完成此操作。
还要检查相关测试,例如 https://github.com/huggingface/transformers/blob/be37d34f44ff1bc928e59ffb8a30adecab8835a8/tests/models/llama/test_modeling_llama.py#L811,看看它们是否仍然正常工作,或者,可以扩展测试以涵盖您发现的故障。

注册登录 发表评论