Transformers 文档

向 Transformers 添加新模型

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

向 Transformers 添加新模型

尝试首先使用更模块化的方法添加新模型。 这使得向 Transformers 贡献模型变得非常容易!

Transformers 中的许多模型都由开发人员和研究人员贡献。 作为一个开源优先的项目,我们致力于授权社区积极和独立地添加更多模型。

当您向 Transformers 添加模型时,您将学到

  • 更多关于开源最佳实践的知识
  • 关于模型架构的知识
  • 关于 Transformers 的设计原则的知识
  • 如何高效测试大型模型
  • 如何使用 Python 实用程序(如 BlackRuff)来创建干净且可读的代码

这是一个具有挑战性但回报丰厚的过程。

本指南将引导您完成向 Transformers 添加示例 BrandNewLlama PyTorch 模型的过程。 在开始之前,最好先熟悉一下该库。

Transformers 概述

Transformers 是一个有主见的库,它有自己独特的理念和设计选择。 这些选择有助于我们可持续地扩展和维护 Transformers。

理念文档中了解更多关于我们的设计原则。

其中一些设计选择是

  • 组合 > 过度抽象
  • 如果重复代码可以大大提高可读性和可访问性,那么重复代码并不总是坏事
  • 模型文件是自包含的,所有必要的模型代码都可以在 modeling_mymodel.py 文件中找到

这些设计选择对于与模型交互的每个人都很重要。 它更容易阅读、理解和修改。

本节介绍模型和配置类如何交互以及 Transformers 代码风格。

模型和配置

所有 Transformers 的模型都继承自一个基础的 PreTrainedModelPretrainedConfig 类。 配置是模型的蓝图。

为了保持代码的可读性,任何模型都不会超过两个级别的抽象。 此处的示例模型 BrandNewLlama 继承自 BrandNewLlamaPreTrainedModelPreTrainedModel。 新模型仅依赖于 PreTrainedModel 非常重要,这样它才能使用 from_pretrained()save_pretrained() 方法。

其他重要功能(如 forward 方法)在 modeling.py 文件中定义。

特定的模型头(例如,序列分类或语言建模)应该在 forward 传递中调用基础模型,而不是从它继承,以保持低抽象性。

新模型需要一个配置,例如 BrandNewLlamaConfig,它作为 PreTrainedModel 的属性存储。

model = BrandNewLlamaModel.from_pretrained("username/brand_new_llama")
model.config

PretrainedConfig 提供了 from_pretrained()save_pretrained() 方法。

当您使用 PreTrainedModel.save_pretrained() 时,它会自动调用 PretrainedConfig.save_pretrained(),以便模型和配置都一起保存。

模型保存到 model.safetensors 文件,配置保存到 config.json 文件。

代码风格

Transformers 偏爱干净且可读的代码,而不是更抽象的代码风格。 一些代码风格选择包括

  • 代码应该对非英语用户可访问。 选择描述性的变量名,避免缩写。 例如,“activation”优于“act”。 非常不鼓励使用单字母变量名,除非它是 for 循环中的索引。

  • 显式代码优先 - 即使它更长 - 而不是更短的代码。

  • 避免子类化 nn.Sequential。 子类化 nn.Module 代替,以便可以使用打印语句或断点快速调试代码。

  • 函数签名应该进行类型注释。 否则,请使用良好的变量名,以便它们更易于理解。

新模型添加问题

打开一个新模型添加 issue 以添加特定模型。

在 GitHub 上按 New model 标签筛选,以查看和添加任何现有的模型请求。

现在是熟悉 BrandNewLlama 的好时机。 阅读模型研究论文以了解其技术设计和实现很有帮助。 您不一定需要过多担心理论细节。 而是专注于实际细节。 使用以下问题来指导您的阅读。

  • BrandNewLlama 是什么类型的模型? 它是编码器、解码器还是编码器-解码器模型?
  • BrandNewLlama 可以用于什么任务?
  • BrandNewLlama 与其他模型的不同之处是什么?
  • Transformers 中哪些模型与 BrandNewLlama 最相似?
  • BrandNewLlama 使用什么分词器?

除了更多地了解您的模型外,还可以使用以下提示来帮助您更快地添加模型。

每位贡献者都有独特的风格和工作流程来向 Transformers 添加模型。 例如,看看 Gemma 是如何添加的。

  • 不要重新发明轮子! 花时间探索现有模型和分词器,看看可以复制和重用什么。 Grepripgrep 是非常棒的工具。
  • 这更像是一个工程挑战,而不是科学挑战。 专注于更实际的方面(例如,设置高效的调试环境),而不是模型的理论方面。
  • 不要害羞,大胆求助! 我们在这里支持您。 🤗

开发环境

单击 Transformers 存储库上的 Fork 按钮以创建您自己的副本进行工作。 将存储库克隆到本地磁盘,并将基础存储库添加为远程仓库。

git clone https://github.com/[your Github handle]/transformers.git
cd transformers
git remote add upstream https://github.com/huggingface/transformers.git

创建一个虚拟环境,并使用“dev”或开发依赖项对库执行可编辑安装

python -m venv .env
source .env/bin/activate
pip install -e ".[dev]"

由于 Transformers 增长的可选依赖项数量,此命令可能会失败。 在这种情况下,请安装“quality”依赖项。 还要确保您已安装深度学习框架。

pip install -e ".[quality]"

返回父目录并克隆并安装原始 BrandNewLlama 存储库。

git clone https://github.com/org_that_created_brand_new_llama_org/brand_new_llama.git
cd brand_new_bert
pip install -e .

返回到您的 Transformers 克隆副本以开始移植 BrandNewLlama。

cd transformers

有两种可能的调试环境用于运行原始模型:笔记本电脑(Google ColabJupyter)或本地 Python 脚本。

我们不建议设置 GPU 环境来运行原始模型,因为它可能很昂贵。 而是先在 CPU 环境中工作,以验证模型在 Transformers 中是否有效。 一旦有效,您就可以在 GPU 上验证它。

笔记本电脑非常适合逐个单元格执行代码,这有助于将逻辑组件彼此分离。 它还可以加速调试周期,因为可以存储中间结果。 与其他贡献者合作时,您还可以共享笔记本电脑。

缺点是,如果您不习惯使用它们,则可能需要一段时间才能适应。

如果模型架构与现有模型相同,请跳到添加转换脚本,因为您可以重用现有模型的架构。

运行以下命令以启动并完成问卷,其中包含有关新模型的一些基本信息。 此命令通过自动生成您需要调整的一些模型代码来快速启动该过程。

transformers-cli add-new-model-like

创建拉取请求

在开始调整代码之前,请创建一个拉取请求以跟踪您的进度并从 Transformers 团队获得反馈。 将您的拉取请求标题命名为 [WIP] 添加 BrandNewLlama,以便清楚地表明这是一项正在进行的工作。

从您的 main 分支创建一个具有描述性名称的分支。

git checkout -b add_brand_new_bert

提交代码,然后获取并变基到 main 分支。

git add .
git commit
git fetch upstream
git rebase upstream/main

将任何更改推送到您的分支,然后单击 比较和拉取请求 以在 GitHub 上打开拉取请求。 将拉取请求作为草稿打开,以表明这是一项正在进行的工作。

git push -u origin a-descriptive-name-for-my-changes

通过在拉取请求中添加 Hugging Face 团队成员的 GitHub 句柄,将他们包含在内以获取问题、反馈、评论和审查。 通过单击 文件已更改 选项卡,然后单击行号左侧的 + 以添加评论,将团队成员定向到您想要的代码特定部分。 当问题得到解决时,单击 解决 以表明问题已解决。 这可以使对话保持井井有条和清晰。

请记住定期提交和推送您的工作,并使用当前的 main 分支更新您的工作。

git fetch upstream
git merge upstream/main

原始检查点

首先花一些时间处理原始模型实现,以了解其工作原理。

如果原始模型存储库缺少文档,或者代码库很复杂,这可能会很困难。 但是您应该将此作为您在 Transformers 中实现模型的动力。 您的贡献使每个人都更容易访问和用户友好!

通过执行以下操作来了解原始存储库。

  • 找到预训练权重。
  • 弄清楚如何将预训练权重加载到模型中。
  • 弄清楚如何在独立于模型的情况下运行分词器。
  • 跟踪一次前向传递,以了解哪些类和函数是必需的。 这些可能是您必须实现的唯一类和函数。
  • 找到模型的所有重要组件(模型类、模型子类、自注意力层等)。
  • 弄清楚如何在原始存储库中调试模型。 添加打印语句,使用交互式调试器(如 ipdb)或高效的集成开发环境 (IDE)(如 PyCharm)。

最后一点尤其重要,因为您需要彻底了解原始模型内部发生的事情,然后才能在 Transformers 中重新实现它。 如果您遇到任何问题,请随时在原始存储库中打开 issue 和拉取请求。

一个好的第一步是加载一个预训练检查点,并尝试使用输入示例整数向量重现一次前向传递。 例如,在伪代码中,这可能如下所示。

model = BrandNewLlamaModel.load_pretrained_checkpoint("/path/to/checkpoint/")
input_ids = [0, 4, 5, 2, 3, 7, 9]  # vector of input ids
original_output = model.generate(input_ids)

调试

如果您遇到问题,您需要根据原始模型的代码库选择以下调试策略之一。

子组件
模型和分词器

此策略依赖于将原始模型分解为更小的子组件,例如当代码可以轻松在 eager 模式下运行时。 虽然更困难,但此方法有一些优点。

  1. 稍后将原始模型与您的实现进行比较会更容易。 您可以自动验证每个单独的组件是否与其在 Transformers 实现中的相应组件匹配。 这比依赖于基于打印语句的视觉比较要好。
  2. 移植各个组件而不是整个模型更容易。
  3. 通过将模型分解为更小的部分,更容易理解模型的工作原理。
  4. 由于组件到组件的测试,在您稍后更改代码时,更容易防止回归。

有关如何将模型分解为更小的组件的良好示例,请参阅 ELECTRA 集成检查

无论您选择哪种策略,都建议先调试初始层,最后调试最终层。 按以下顺序检索以下层的输出(使用打印语句或子组件函数)。

  1. 传递给模型的 input ids
  2. 词嵌入
  3. 第一个 Transformer 层的输入
  4. 第一个 Transformer 层的输出
  5. 以下 n-1 个 Transformer 层的输出
  6. 整个模型的输出

input ids 应该只是一个整数数组,例如 input_ids = [0, 4, 4, 3, 2, 4, 1, 7, 19]

层输出通常由多维浮点数组组成。

[[
 [-0.1465, -0.6501,  0.1993,  ...,  0.1451,  0.3430,  0.6024],
 [-0.4417, -0.5920,  0.3450,  ..., -0.3062,  0.6182,  0.7132],
 [-0.5009, -0.7122,  0.4548,  ..., -0.3662,  0.6091,  0.7648],
 ...,
 [-0.5613, -0.6332,  0.4324,  ..., -0.3792,  0.7372,  0.9288],
 [-0.5416, -0.6345,  0.4180,  ..., -0.3564,  0.6992,  0.9191],
 [-0.5334, -0.6403,  0.4271,  ..., -0.3339,  0.6533,  0.8694]]],

每个 Transformers 模型输出都应具有 1e-3 的精度或误差容限。 这解释了因使用不同的库框架而产生的任何输出差异。 将原始模型的中间输出与 Transformers 实现进行比较,以确保它们几乎相同。 拥有高效的调试环境对于此步骤至关重要。

以下是一些关于高效调试环境的提示。

  • 要调试中间结果,这取决于原始模型存储库正在使用的机器学习框架。 对于 PyTorch,您应该编写一个脚本将原始模型分解为更小的子组件,以检索中间值。 对于 TensorFlow,您可能需要使用 tf.print。 对于 Flax,请确保模型在 forward 传递期间未进行 jitted 处理(有关更多详细信息,请参阅此 GitHub Issue)。

  • 使用较小的预训练检查点进行调试比使用前向传递超过 10 秒的较大检查点更快。 如果只有大型检查点可用,请创建一个具有随机初始化权重的虚拟模型,并保存这些权重以与 Transformers 实现进行比较。

  • 找到调用模型 forward 传递的最简单方法。 理想情况下,此函数(可能称为 predictevaluateforward__call__)应该仅调用 forward 传递一次。 调试多次调用 forward 传递的函数更困难。

  • 将分词与 forward 传递分开。 找到字符串输入在 forward 传递中更改为 input ids 的位置,并从此处开始。 您可能需要创建一个小脚本或修改原始代码,以直接输入 input ids 而不是输入字符串。

  • 确保模型不是处于训练模式。 这可能会由于模型中的多个 dropout 层而产生随机输出。 调试环境中的 forward 传递应该是确定性的,以便不使用 dropout 层。

一旦您能够运行原始检查点,您就可以开始为 Transformers 调整模型代码了。

调整模型代码

transformers-cli add-new-model-like 命令应已生成模型和配置文件。

  • src/transformers/models/brand_new_llama/modeling_brand_new_llama.py
  • src/transformers/models/brand_new_llama/configuration_brand_new_llama.py

如果您回答它是仅解码器模型,则 modeling.py 文件中自动生成的代码具有与 Llama 相同的架构,如果您回答它是编码器-解码器模型,则它将具有与 BART 相同的架构。 生成的代码只是一个起点。 根据您对新模型的研究,您需要通过调整生成的代码来实现这些特定更改。 这可能涉及更改自注意力层、归一化层的顺序等等。

模型初始化

此时,您的代码不必是干净的,甚至不必完全正确,快速创建初稿,然后迭代改进它会更有效率。 最重要的是,您的模型可以从 Transformers 实例化。 以下命令从具有随机权重的配置创建模型,验证 __init__ 方法是否有效。

from transformers import BrandNewLlama, BrandNewLlamaConfig
model = BrandNewLlama(BrandNewLlamaConfig())

随机初始化发生在 BrandNewLlamaPreTrainedModel_init_weights 方法中。 所有叶模块都根据配置的变量进行初始化。

def _init_weights(self, module):
    """Initialize the weights"""
    if isinstance(module, nn.Linear):
        module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
        if module.bias is not None:
            module.bias.data.zero_()
    elif isinstance(module, nn.Embedding):
        module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
        if module.padding_idx is not None:
            module.weight.data[module.padding_idx].zero_()
    elif isinstance(module, nn.LayerNorm):
        module.bias.data.zero_()
        module.weight.data.fill_(1.0)

如果您需要调整初始化方案以适应您的模型,则初始化方案可能看起来有所不同。 例如,Wav2Vec2ForPreTraining 在其最后两个线性层中初始化 nn.Linear

_is_hf_initialized 标志确保子模块仅初始化一次。 将 module.project_qmodule.project_hid 设置为 True 可确保稍后不会覆盖自定义初始化。 _init_weights 函数不会应用于这些模块。

def _init_weights(self, module):
    """Initialize the weights"""
    if isinstance(module, Wav2Vec2ForPreTraining):
        module.project_hid.reset_parameters()
        module.project_q.reset_parameters()
        module.project_hid._is_hf_initialized = True
        module.project_q._is_hf_initialized = True
    elif isinstance(module, nn.Linear):
        module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
        if module.bias is not None:
            module.bias.data.zero_()

将检查点转换为 Transformers

原始检查点必须转换为与 Transformers 兼容的检查点。

尝试查找现有的转换脚本进行复制、适配和重用,以用于您的模型!

  • 如果您要将模型从 TensorFlow 移植到 PyTorch,一个好的起点可能是 BERT 转换脚本
  • 如果您要将模型从 PyTorch 移植到 PyTorch,一个好的起点可能是 BART 转换脚本

确保所有必需的权重都已初始化,并打印出所有未用于初始化的检查点权重,以确保模型已正确转换。

在转换过程中,您可能会遇到错误的形状声明或名称分配。这很可能是由于 BrandNewLlamaConfig 中的参数不正确、架构错误、实现中的 init 方法存在错误,或者您需要转置其中一个检查点权重。

不断迭代适配模型代码部分,直到所有检查点权重都正确加载。一旦您可以在模型中加载检查点,请将其保存到文件夹中。这应该包含一个 model.safetensors 文件和一个 config.json 文件。

model.save_pretrained("/path/to/converted/checkpoint/folder")

为了帮助进行转换,下一节简要描述 PyTorch 模型如何存储和定义层权重和名称。

PyTorch 层权重和名称

创建一个基本的 PyTorch 模型来理解层名称是如何定义的以及权重是如何初始化的,这将很有帮助。

from torch import nn

class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.dense = nn.Linear(10, 10)
        self.intermediate = nn.Linear(10, 10)
        self.layer_norm = nn.LayerNorm(10)

PyTorch 层名称由层的类属性名称(denseintermediatelayer_norm)定义。创建 SimpleModel 的实例以使用随机权重填充所有层。

model = SimpleModel()
print(model)
SimpleModel(
  (dense): Linear(in_features=10, out_features=10, bias=True)
  (intermediate): Linear(in_features=10, out_features=10, bias=True)
  (layer_norm): LayerNorm((10,), eps=1e-05, elementwise_affine=True)
)

特定层的权重值是随机初始化的。

print(model.dense.weight.data)
tensor([[-0.0818,  0.2207, -0.0749, -0.0030,  0.0045, -0.1569, -0.1598,  0.0212,
         -0.2077,  0.2157],
        [ 0.1044,  0.0201,  0.0990,  0.2482,  0.3116,  0.2509,  0.2866, -0.2190,
          0.2166, -0.0212],
        [-0.2000,  0.1107, -0.1999, -0.3119,  0.1559,  0.0993,  0.1776, -0.1950,
         -0.1023, -0.0447],
        [-0.0888, -0.1092,  0.2281,  0.0336,  0.1817, -0.0115,  0.2096,  0.1415,
         -0.1876, -0.2467],
        [ 0.2208, -0.2352, -0.1426, -0.2636, -0.2889, -0.2061, -0.2849, -0.0465,
          0.2577,  0.0402],
        [ 0.1502,  0.2465,  0.2566,  0.0693,  0.2352, -0.0530,  0.1859, -0.0604,
          0.2132,  0.1680],
        [ 0.1733, -0.2407, -0.1721,  0.1484,  0.0358, -0.0633, -0.0721, -0.0090,
          0.2707, -0.2509],
        [-0.1173,  0.1561,  0.2945,  0.0595, -0.1996,  0.2988, -0.0802,  0.0407,
          0.1829, -0.1568],
        [-0.1164, -0.2228, -0.0403,  0.0428,  0.1339,  0.0047,  0.1967,  0.2923,
          0.0333, -0.0536],
        [-0.1492, -0.1616,  0.1057,  0.1950, -0.2807, -0.2710, -0.1586,  0.0739,
          0.2220,  0.2358]]).

在转换脚本中,随机权重应替换为原始检查点中相应层的精确权重。

# retrieve matching layer weights with recursive algorithm
layer_name = "dense"
pretrained_weight = array_of_dense_layer

model_pointer = getattr(model, "dense")
model_pointer.weight.data = torch.from_numpy(pretrained_weight)

验证随机初始化的权重及其对应的预训练检查点权重是否具有相同的形状名称。为形状添加断言语句,并打印出检查点权重名称。

assert (
    model_pointer.weight.shape == pretrained_weight.shape
), f"Pointer shape of random weight {model_pointer.shape} and array shape of checkpoint weight {pretrained_weight.shape} mismatched"

logger.info(f"Initialize PyTorch weight {layer_name} from {pretrained_weight.name}")

当形状或名称不匹配时,您可能已将不正确的检查点权重分配给了随机初始化的层。形状不正确可能是因为 BrandNewLlama 参数与原始模型参数不完全匹配。但也可能是 PyTorch 层实现需要先转置权重。

实现前向传播

如果模型加载正确,则接下来应实现前向传播。它接受一些输入并返回模型输出。

model = BrandNewLlamaModel.from_pretrained("/path/to/converted/checkpoint/folder")
input_ids = [0, 4, 4, 3, 2, 4, 1, 7, 19]
output = model.generate(input_ids).last_hidden_states

如果您的前向传播与原始模型的输出不完全相同,或者如果它返回错误,请不要气馁。检查前向传播是否抛出任何错误。这通常是因为维度错误或使用了错误的数据类型(torch.long 而不是 torch.float32)。

您的输出应具有 1e-3 的精度。确保输出形状和输出值相同。输出不相同的一些常见原因包括

  • 某些层未添加(激活层或残差连接)。
  • 词嵌入矩阵未绑定。
  • 使用了错误的位置嵌入,因为原始实现包含偏移量。
  • 在前向传播期间应用了 Dropout。通过确保 model.trainingFalse 并将 self.training 传递给 torch.nn.functional.dropout 来修复此错误。

比较原始模型和您的实现的前向传播,以检查是否存在任何差异。理想情况下,调试并打印出前向传播的两种实现的中间输出,以查明原始实现与您的实现不同的地方。

  1. 确保两种实现中的硬编码 input_ids 相同。
  2. 验证 input_ids 的第一次转换(通常是词嵌入)的输出是否相同,并逐步检查到最后一层。

两种实现之间的任何差异都应指向您实现中的错误。

最佳策略之一是在两种实现中的相同位置添加许多打印语句,然后在中间输出输出相同的值时,逐步删除它们。

当两种实现产生相同的输出时,验证输出是否在 1e-3 的精度范围内。

torch.allclose(original_output, output, atol=1e-3)

这通常是该过程中最困难的部分。如果您已走到这一步,恭喜您!

如果您在此步骤中遇到困难或卡住,请随时在您的拉取请求中寻求帮助。

添加模型测试

虽然模型可以工作,但您仍然需要添加测试以确保它与 Transformers 兼容。测试非常重要,因为它们可以帮助用户通过查看特定测试来理解您的工作,并且可以防止您的模型在未来进行任何更改时出现故障。

Cookiecutter 应该已为您的模型添加了一个测试文件。运行下面的测试文件以确保所有常见测试都通过。

pytest tests/models/brand_new_llama/test_modeling_brand_new_llama.py

集成测试应首先添加,因为它们的作用与您之前用于在 Transformers 中实现新模型的调试脚本相同。Cookiecutter 添加了这些模型测试的模板 BrandNewLlamaModelIntegrationTests,应将其填写完整。为确保它通过,请运行以下命令。

macOS
Windows
RUN_SLOW=1 pytest -sv tests/models/brand_new_llama/test_modeling_brand_new_llama.py::BrandNewLlamaModelIntegrationTests

BrandNewLlama 的所有独特功能都应在 BrandNewLlamaModelTester/BrandNewLlamaModelTest 下的单独测试中进行测试。此测试经常被忽略,但它非常重要,因为

  • 它通过展示模型的创新功能如何工作,帮助将您在此过程中获得的知识转移到社区
  • 未来的贡献者可以通过运行这些特殊测试来快速测试对模型的更改

实现分词器

我们建议添加一个快速分词器 (PreTrainedTokenizerFast) 以给用户最佳性能。请随时在您的 PR 中标记 @ArthurZucker@itazap,以获得有关如何添加 PreTrainedTokenizerFast 的帮助。

模型完成后,是时候关注分词器了。分词器应与 Transformers 中现有的分词器相同或非常相似。

在您的实现中查找并加载原始分词器文件。在原始存储库中创建一个脚本,该脚本输入一个字符串并返回 input_ids。伪代码应类似于下面的代码。

input_str = "This is a long example input string containing special characters .$?-, numbers 2872 234 12 and words."
model = BrandNewLlamaModel.load_pretrained_checkpoint("/path/to/checkpoint/")
input_ids = model.tokenize(input_str)

您可能需要在原始存储库中搜索以查找正确的分词器函数,或者修改原始存储库克隆中的现有分词器以仅返回 input_ids。您的分词器脚本应类似于以下内容。

from transformers import BrandNewLlamaTokenizer

input_str = "This is a long example input string containing special characters .$?-, numbers 2872 234 12 and words."
tokenizer = BrandNewLlamaTokenizer.from_pretrained("/path/to/tokenizer/folder/")
input_ids = tokenizer(input_str).input_ids

当两种实现具有相同的 input_ids 时,添加一个分词器测试文件。此文件类似于建模测试文件。分词器测试文件应包含几个硬编码的集成测试。

实现图像处理器

快速图像处理器使用 torchvision 库,可以在 GPU 上执行图像处理,从而显着提高处理速度。我们建议添加一个快速图像处理器 (BaseImageProcessorFast) 以及“慢速”图像处理器 (BaseImageProcessor),以便为用户提供最佳性能。请随时标记 @yonigozlan 以获得有关添加 BaseImageProcessorFast 的帮助。

虽然此示例不包含图像处理器,但如果您的模型需要图像输入,则可能需要实现一个。图像处理器负责将图像转换为适合您模型的格式。在实现新的图像处理器之前,请检查 Transformers 库中是否可以重用现有的图像处理器,因为许多模型共享类似的图像处理技术。请注意,您也可以使用 modular 用于图像处理器以重用现有组件。

如果您确实需要实现新的图像处理器,请参考现有的图像处理器以了解预期的结构。慢速图像处理器 (BaseImageProcessor) 和快速图像处理器 (BaseImageProcessorFast) 的设计不同,因此请确保您遵循基于您要实现的处理器类型的正确结构。

运行以下命令(仅当您尚未通过 transformers-cli add-new-model-like 命令创建快速图像处理器时)以生成必要的导入并为快速图像处理器创建预填充的模板。修改模板以适合您的模型。

transformers-cli add-fast-image-processor --model-name your_model_name

此命令将生成必要的导入并为快速图像处理器提供预填充的模板。然后,您可以修改它以适合您模型的需求。

tests/models/your_model_name/test_image_processing_your_model_name.py 中为图像处理器添加测试。这些测试应与其他图像处理器的测试类似,并且应验证图像处理器是否正确处理图像输入。如果您的图像处理器包含独特的功能或处理方法,请确保也为这些功能添加特定测试。

实现处理器

如果您的模型接受多种模态(例如文本和图像),则需要添加处理器。处理器集中处理不同模态的预处理,然后再将其传递给模型。

处理器应在其 __call__ 函数中调用适当的特定于模态的处理器,以正确处理每种类型的输入。请务必检查库中现有的处理器以了解其预期结构。Transformers 在 __call__ 函数签名中使用了以下约定。

def __call__(
    self,
    images: ImageInput = None,
    text: Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]] = None,
    audio=None,
    videos=None,
    **kwargs: Unpack[YourModelProcessorKwargs],
) -> BatchFeature:
    ...

YourModelProcessorKwargs 是一个 TypedDict,其中包含所有典型的处理参数以及特定处理器可能需要的任何额外参数。

tests/models/your_model_name/test_processor_your_model_name.py 中为处理器添加测试。这些测试应与其他处理器的测试类似,并且应验证处理器是否正确处理不同的模态。

集成测试

现在您有了模型和分词器,请为模型和分词器添加端到端集成测试到 tests/models/brand_new_llama/test_modeling_brand_new_llama.py

测试应提供一个有意义的文本到文本示例,以显示模型按预期工作。例如,您可以包含源到目标翻译对、文章到摘要对或问题到答案对。

如果检查点尚未在下游任务上进行微调,则模型测试就足够了。

最后,尝试确保您的测试可以在 GPU 上运行,方法是将 .to(self.device) 语句添加到模型的内部张量中。如果您无法访问 GPU,我们可以为您处理。

添加文档

如果用户不知道如何使用您的模型,那么您的模型就毫无用处。这就是为什么添加文档和文档字符串非常重要。Cookiecutter 添加了一个模板文件 docs/source/model_doc/brand_new_llama.md,您可以在其中填写有关您的模型的信息。

这通常是用户与模型的第一次交互,因此文档应清晰简洁。添加有关如何使用模型的示例通常非常有用。

确保将文档字符串添加到 src/transformers/models/brand_new_llama/modeling_brand_new_llama.py,并包含所有必要的输入和输出。查看我们的 指南,了解如何编写文档和文档字符串。

重构

是时候整理一下,并确保代码风格与库的其余部分保持一致。运行以下命令以自动修复不正确的样式。

make style

要验证代码风格是否通过质量检查,请运行以下命令。

make quality

由于 Transformers 严格的设计测试,您的拉取请求上可能还有其他失败的测试或检查(缺少文档字符串或命名不正确)。如果您遇到困难,我们可以帮助您解决这些问题。

在确保代码正确运行后,您可能需要重构它,使其更具可读性或更简洁。

上传到 Hub

将所有检查点转换并上传到 Hub。添加模型卡,以提供有关模型的更多透明度和上下文。模型卡应突出显示检查点的特定特征、模型的训练方式以及如何使用它的代码示例。

在许多情况下,添加用户可以运行的交互式笔记本是展示如何使用模型进行推理或在下游任务上对其进行微调的好方法。虽然不是必需的,但包含笔记本可以促进您的模型得到更广泛的应用。

您还应咨询 Transformers 团队,以确定模型的适当名称,并获得上传模型所需的访问权限。

使用 push_to_hub() 方法上传模型。

brand_new_bert.push_to_hub("brand_new_llama")

有关将模型上传到 Hub 的更多信息,请参阅 共享 指南。

合并您的模型

您终于可以合并您的拉取请求,并将模型正式添加到 Transformers!确保所有测试都通过,并且所有评论和反馈都已解决。

恭喜您向 Transformers 添加了新模型!🥳

这是一项非常重要的贡献。您的工作使世界各地的开发人员和研究人员更容易使用 Transformers。您应该为自己的贡献感到自豪,并与社区分享您的成就!

模型添加时间线

根据模型贡献者和社区对架构的需求,模型添加有四个时间线。

  • day-0 集成:如果您计划进行 Transformers 优先发布,这是一个不错的选择,因为我们可以确保文档清晰并尽可能优化您的模型(量化、FlashAttention、KV 缓存等)。我们还可以帮助您添加模型,提供早期审查并确保它按预期工作。

    提前几天(最好是几周)联系 transformers@huggingface.co,特别是当架构特别新颖时,以确保模型集成。我们将共同在一个 Transformers 私有分支上工作,直到您的检查点和发布准备就绪。

  • 同一周集成:如果模型作者没有联系,通常会在同一周添加具有大量请求/需求的模型。

    使用 issue 跟踪器 请求添加特定模型。问题上的活动越多,我们集成它的速度就越快,可能性就越大。

  • 发布后集成:对于没有热门请求/需求的模型,或者如果我们没有带宽来集成它,则在发布后添加。

    如果您有兴趣为 Transformers 贡献模型,这是一个很好的机会。查看标有 “New model” 的未解决问题。请随意尝试最受欢迎的模型,以倍增您的贡献的影响。我们将随时为您提供每一步的帮助!

  • Hub 优先发布:Transformers remote-code 功能允许直接在 Hub 上共享基于 Transformers 的项目。如果您没有带宽直接将模型添加到 Transformers,这是一个不错的选择。

    如果某个模型最终非常受欢迎,那么我们很可能会自己将其集成到 Transformers 中,以便为其提供更好的支持(文档、维护、优化等)。Hub 优先发布是添加模型的最顺畅方式。

< > 在 GitHub 上更新