Transformers 文档

向 Transformers 添加新模型

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

向 Transformers 添加新模型

请先尝试以更模块化的方式添加新模型。这能显著简化向 Transformers 贡献模型的过程!

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

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

  • 更多关于开源最佳实践的知识
  • 关于模型架构的知识
  • 关于 Transformers 设计原则的知识
  • 如何高效测试大型模型
  • 如何使用 BlackRuff 等 Python 工具创建清晰可读的代码

这是一个充满挑战但回报丰厚的过程。

本指南将引导你向 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,以便可以使用 print 语句或断点快速调试代码。

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

新模型添加问题

打开新模型添加问题,以添加特定模型。

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

现在是熟悉 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 上验证它。

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

缺点是如果你不习惯它们,可能需要一些时间来适应。

如果模型架构与现有模型相同,请跳过此步骤,直接添加转换脚本,因为你可以重用现有模型的架构。

运行以下命令,开始并完成问卷,填写有关新模型的一些基本信息。此命令通过自动生成一些你将需要修改的模型代码来启动此过程。

transformers add-new-model-like

创建拉取请求

在开始修改代码之前,请创建一个拉取请求以跟踪你的进度并从 Transformers 团队获取反馈。将你的拉取请求标题命名为 [WIP] Add BrandNewLlama,这样就能清楚地表明这是一个正在进行中的工作。

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

git checkout -b add_brand_new_bert

提交代码,然后拉取并 rebase 到主分支。

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

将所有更改推送到你的分支,然后点击 Compare & pull request 在 GitHub 上打开一个拉取请求。以*草稿*形式打开拉取请求,表示其正在进行中。

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

通过在拉取请求中添加 Hugging Face 团队成员的 GitHub 用户名,以便他们回答问题、提供反馈、评论和审查。点击 Files changed 选项卡,然后点击行号左侧的 + 以添加评论,将团队成员引导到你想要关注的代码特定部分。当问题或故障解决后,点击 Resolve 表示问题已解决。这能保持对话有条理且清晰。

请记住定期提交并推送你的工作,并用当前主分支更新你的工作。

git fetch upstream
git merge upstream/main

原始检查点

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

如果原始模型仓库缺少文档或代码库复杂,这可能会很困难。但你应该以此作为在 Transformers 中实现模型的动力。你的贡献能使其对每个人都更易于访问和使用!

通过以下步骤熟悉原始仓库:

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

最后一点尤为重要,因为在将原始模型重新实现到 Transformers 中之前,你需要对原始模型内部发生的事情有透彻的理解。如果你遇到任何问题,请随时在原始仓库中打开问题和拉取请求。

一个好的第一步是加载一个*小型*预训练检查点,并尝试使用一个示例整数输入向量重现单次前向传播。例如,在伪代码中,这可能看起来像以下内容。

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. 传递给模型的输入 ID
  2. 词嵌入
  3. 第一个 Transformer 层的输入
  4. 第一个 Transformer 层的输出
  5. 后续 n-1 个 Transformer 层的输出
  6. 整个模型的输出

输入 ID 应该只是一个整数数组,例如 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,确保模型在向前传播期间*没有被 JIT 编译*(有关更多详细信息,请参阅此 GitHub 问题)。

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

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

  • 将分词与前向传播分开。找到字符串输入在前向传播中如何变为输入 ID 的位置,并从这里开始。你可能需要创建一个小脚本或修改原始代码以直接输入输入 ID,而不是输入字符串。

  • 确保模型*不处于*训练模式。这可能由于模型中的多个 dropout 层而产生随机输出。调试环境中的前向传播应该是*确定性*的,这样 dropout 层就不会被使用。

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

调整模型代码

transformers 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*。确保输出形状和输出值相同。输出不相同常见原因包括:

  • 未添加某些层(激活层或残差连接)。
  • 词嵌入矩阵未绑定。
  • 使用了错误的 positional embeddings,因为原始实现包含偏移量。
  • 在前向传播期间应用了 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 中实现新模型的调试脚本相同。这些模型测试的模板 BrandNewLlamaModelIntegrationTests 已由 Cookiecutter 添加,并应填写完整。为了确保其通过,请运行以下命令。

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 上执行图像处理,显著提高处理速度。我们建议除了“慢速”图像处理器(BaseImageProcessor)之外,还添加一个快速图像处理器(BaseImageProcessorFast),以向用户提供最佳性能。欢迎标记 @yonigozlan 以获取添加 BaseImageProcessorFast 的帮助。

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

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

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

transformers 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。你应该为你的贡献感到自豪,并与社区分享你的成就!

模型添加时间线

模型添加有四种时间线,具体取决于模型贡献者和社区对架构的需求。

  • 0 日集成:如果你计划发布 Transformers-first 版本,这是一个很好的选择,因为我们可以确保文档清晰并尽可能优化你的模型(量化、FlashAttention、KV-cache 等)。我们还可以帮助你添加模型,提供早期审查并确保其按预期工作。

    请提前几天(最好是几周)联系 transformers@huggingface.co,特别是如果架构特别新颖,以确保模型集成。我们将在 Transformers 的私人分支上合作,直到你的检查点和发布准备就绪。

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

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

  • 发布后集成:对于没有普遍需求或我们没有足够带宽集成的模型,将在发布后进行添加。

    如果你有兴趣向 Transformers 贡献模型,这是一个好机会。查看标有“新模型”的开放问题。随意尝试最受欢迎的模型,以增加你的贡献影响力。我们将全程为你提供帮助!

  • Hub 优先发布:Transformers 远程代码功能允许直接在 Hub 上共享基于 Transformers 的项目。如果你没有足够的精力直接将模型添加到 Transformers 中,这是一个不错的选择。

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

< > 在 GitHub 上更新