Transformers documentation

レガシーモデルの貢献

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

レガシーモデルの貢献

まず、よりモジュール化されたアプローチで新しいモデルを追加してみてください。これにより、Transformersへのモデルの貢献が大幅に容易になります!

Transformersの多くのモデルは、開発者や研究者によって貢献されています。オープンソースを第一に考えるプロジェクトとして、コミュニティが主体的かつ自律的にさらなるモデルを追加できるよう支援することに注力しています。

Transformersにモデルを追加すると、以下のことを学ぶことができます:

  • オープンソースのベストプラクティスについて詳しく
  • モデルのアーキテクチャについて
  • Transformersの設計原則について
  • 大規模モデルを効率的にテストする方法
  • Black や Ruff などの Python ユーティリティを使用して、クリーンで読みやすいコードを作成する方法

挑戦的ですが、やりがいのあるプロセスです。

このガイドでは、TransformersにBrandNewLlama PyTorchモデルの例を追加する手順を説明します。始める前に、ライブラリについて一通り理解しておくことをお勧めします。

Transformersの概要

Transformersは、独自の哲学と設計方針を持つ、意見の強いライブラリです。これらの選択により、Transformersを持続可能な形でスケーリングし、メンテナンスすることが可能になっています。

Philosophyドキュメントで、私たちの設計原則について詳しく学んでください。

これらの設計上の選択には、以下のようなものがあります。

  • composition > over-abstraction
  • 重複したコードは、可読性とアクセシビリティを大幅に向上させる場合、必ずしも悪いわけではありません
  • モデルファイルは自己完結型であり、必要なモデルコードはすべてmodeling_mymodel.pyファイル内に含まれています。

これらの設計上の選択は、モデルと関わるすべての人にとって重要です。読みやすく、理解しやすく、変更しやすくなります。

このセクションでは、モデルクラスと設定クラスの相互作用、およびTransformersのコードスタイルについて説明します。

モデルと設定

Transformers のすべてのモデルは、ベースとなる PreTrainedModel クラスと PreTrainedConfig クラスを継承しています。設定はモデルの設計図です。

コードの可読性を保つため、どのモデルにおいても抽象化のレベルは最大2段階に抑えられています。この例のモデルであるBrandNewLlamaは、BrandNewLlamaPreTrainedModelとPreTrainedModelを継承しています。新しいモデルがPreTrainedModelにのみ依存していることが重要であり、これによりfrom_pretrained()とsave_pretrained()メソッドを使用できます。

modeling.pyファイルには、forwardメソッドなど他の重要な関数が定義されています。

特定のモデルヘッド(シーケンス分類や言語モデリングなど)は、抽象度を低く保つため、ベースモデルを継承するのではなく、フォワードパス内でベースモデルを呼び出すべきです。

新しいモデルには、PreTrainedModel の属性として保存される設定、例えば BrandNewLlamaConfig が必要です。

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ループのインデックスとして使用する場合を除き、1文字の変数名は強く非推奨です。

  • 短いコードよりも、たとえ長くなっても明示的なコードが優先されます。

  • nn.Sequentialのサブクラス化は避けてください。代わりにnn.Moduleをサブクラス化することで、print文やブレークポイントを使用してコードを素早くデバッグできます。

  • 関数のシグネチャには型アノテーションを付与してください。付与しない場合は、変数名を適切に設定し、理解しやすくしてください。

新しいモデルの追加に関する問題

特定のモデルを追加するには、New model addition の issue を作成してください。

GitHub上でNew modelラベルでフィルタリングして、既存のモデルリクエストを表示および追加できます。

BrandNewLlamaに慣れるには、今が好機です。モデルの技術的な設計と実装を理解するには、その研究論文を読むことが役立ちます。理論的な詳細に過度にこだわる必要はありません。代わりに、実用的な側面に焦点を当ててください。以下の質問をガイドとして、読み進めてください。

  • BrandNewLlamaはどのようなタイプのモデルですか。エンコーダー、デコーダー、それともエンコーダーデコーダーモデルですか。
  • BrandNewLlamaはどのようなタスクに使用できますか?
  • BrandNewLlamaは他のモデルと何が異なりますか?
  • Transformersの中で、BrandNewLlamaに最も類似しているモデルはどれですか?
  • BrandNewLlama はどのトークナイザーを使用していますか?

モデルについてさらに詳しく学ぶだけでなく、以下のヒントを活用して、モデルの追加をより迅速に行いましょう。

各コントリビューターには、Transformersにモデルを追加する際の独自のスタイルとワークフローがあります。例として、Gemmaがどのように追加されたかを確認してください。

  • 車輪の再発明は避けてください! 既存のモデルやトークナイザーをじっくりと探索し、コピーや再利用できるものがないか確認しましょう。Grepやripgrepは、この作業に最適なツールです。
  • これは科学というより、エンジニアリングの課題です。モデルの理論的な側面よりも、より実用的な側面(例えば、効率的なデバッグ環境の構築)に焦点を当ててください。
  • 遠慮せず、お気軽にヘルプを求めてください!私たちはあなたをサポートするためにここにあります。🤗

開発環境

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」または開発用依存関係付きでeditable installを実行します。

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 ColabまたはJupyter)とローカルPythonスクリプトの2つの選択肢があります。

元のモデルを実行するためにGPU環境を構築することはコストがかかる可能性があるため、おすすめしません。代わりに、まずCPU環境で作業し、Transformersでモデルが正常に動作することを確認してください。正常に動作することを確認できたら、その後でGPU上で検証を行えます。

Notebookは、コードをセル単位で実行できるため、論理的なコンポーネントを分離するのに役立ちます。また、中間結果を保存できるため、デバッグサイクルを高速化することもできます。さらに、他のコントリビューターと共同作業を行う際に、Notebookを共有することも可能です。

デメリットは、これらに慣れていない場合、使い慣れるまでに多少の時間がかかる可能性があることです。

モデルのアーキテクチャが既存のモデルと同一の場合は、既存のモデルのアーキテクチャを再利用できるため、チェックポイントの変換に進んでください。

以下のコマンドを実行して、新しいモデルに関する基本的な情報を含むアンケートを開始し、完了してください。このコマンドは、適応させる必要がある一部のモデルコードを自動的に生成することで、プロセスの開始を助けてくれます。

transformers add-new-model-like

プルリクエストを作成する

コードの適応を開始する前に、進捗を追跡し、Transformersチームからフィードバックを得るためにプルリクエストを作成してください。これが作業中であることを明確にするため、プルリクエストのタイトルを[WIP] Add BrandNewLlamaにしてください。

メインブランチから、説明のついた名前を付けたブランチを作成します。

git checkout -b add_brand_new_bert

コードをコミットし、その後、mainブランチを取得してリベースします。

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

変更をブランチにプッシュし、Compare & pull requestをクリックしてGitHubでプルリクエストを開きます。作業中であることを示すために、プルリクエストをdraftとして開きます。

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

質問、フィードバック、コメント、レビューのために、pull request に Hugging Face チームメンバーの GitHub ハンドルを追加して、関連するメンバーを巻き込んでください。コードの特定の部分にチームメンバーを誘導するには、Files changed タブをクリックし、行番号の左にある + をクリックしてコメントを追加します。質問や問題が解決したら、Resolve をクリックして問題が解決したことを示します。これにより、会話が整理され、クリーンな状態を保つことができます。

定期的に作業をコミットしてプッシュし、現在のmainブランチで作業を更新することを忘れないでください。

git fetch upstream
git merge upstream/main

元のチェックポイント

まず元のモデル実装に取り組んで、その仕組みを理解するようにしてください。

元のモデルのリポジトリにドキュメントが不足している場合や、コードベースが複雑な場合、これは困難を伴う可能性があります。しかし、これをTransformersでモデルを実装するモチベーションとして活用してください。あなたの貢献により、モデルはすべての人にとってよりアクセスしやすく、使いやすくなります!

以下の手順に従い、元のリポジトリの構成を把握してください。

  • 事前学習済み重みを特定します。
  • モデルに事前学習済み重みをロードする方法を特定します。
  • モデルとは独立してトークナイザーを実行する方法を特定します。
  • Trace one forward passをトレースして、必要なクラスと関数を理解してください。おそらく、実装が必要なのはこれらのクラスと関数のみです。
  • モデルの重要なコンポーネント(モデルクラス、モデルサブクラス、自己注意レイヤーなど)をすべて特定します。
  • 元のリポジトリでモデルのデバッグ方法を検討してください。printステートメントを追加したり、ipdbのようなインタラクティブなデバッガや、PyCharmのような効率的な統合開発環境(IDE)を使用したりできます。

最後の点は特に重要です。Transformersで再実装するには、元のモデル内部で何が起きているかを徹底的に理解する必要があるためです。問題が発生した場合は、元のリポジトリでissueやpull requestを開いてください。

良い第一歩として、小型の事前学習済みチェックポイントを読み込み、入力として整数ベクトルの例を用いて単一のフォワードパスを再現してみることです。例えば、疑似コードでは次のようになります。

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)

デバッグ

問題が発生した場合は、元のモデルのコードベースに応じて、次のデバッグ戦略のいずれかを選択する必要があります。

サブコンポーネント
モデルとトークナイザ

この戦略は、元のモデルをより小さなサブコンポーネントに分割することに基づいています。例えば、コードがイージーマードで簡単に実行できる場合などが該当します。このアプローチはより困難を伴いますが、いくつかの利点があります。

  1. 後で元のモデルと自分の実装を比較しやすくなります。各コンポーネントがTransformersの実装の対応するコンポーネントと一致していることを自動的に検証できます。printステートメントに基づく視覚的な比較に頼るよりも、これが望ましいです。
  2. モデル全体を移植するよりも、個々のコンポーネントを移植する方が簡単です。
  3. モデルを小さな部分に分解することで、その動作をより理解しやすくなります。
  4. コンポーネントごとのテストにより、コードを変更した際に後段でリグレッション(機能劣化)が発生することを防ぐことが容易になります。

モデルをより小さなコンポーネントに分解する方法の良い例として、ELECTRAの統合チェックを参照してください。

Whichever strategy you choose、まず初期のレイヤーをデバッグし、最後に最終のレイヤーをデバッグすることをお勧めします。以下のレイヤーの出力を、printステートメントまたはサブコンポーネント関数を用いて、この順序で取得してください。

  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 の場合、元のモデルをより小さなサブコンポーネントに分解して中間値を取得するためのスクリプトを作成する必要があります。

  • フォワードパスが10秒を超える大きなチェックポイントよりも、小さな事前学習済みチェックポイントでデバッグする方が高速です。大きなチェックポイントしか利用できない場合は、重みをランダムに初期化してダミーモデルを作成し、その重みを保存してTransformersの実装と比較してください。

  • モデルのフォワードパスを呼び出す最も簡単な方法を見つけてください。理想的には、この関数(predict、evaluate、forward、または__call__と呼ばれる場合があります)は、フォワードパスを1回だけ呼び出すべきです。フォワードパスを複数回呼び出す関数は、デバッグがより困難になります。

  • トークン化をフォワードパスから分離します。フォワードパス内で文字列入力が input ids に変換される箇所を特定し、そこから作業を開始します。入力文字列の代わりに input ids を直接入力するために、小さなスクリプトを作成するか、元のコードを修正する必要がある場合があります。

  • モデルがトレーニングモードにないことを確認してください。モデル内の複数の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())

_init_weights メソッド内の BrandNewLlamaPreTrainedModel でランダム初期化が行われます。すべてのリーフモジュールは、設定の変数に応じて初期化されます。重みそのものやその .data に直接インプレース操作を呼び出すのではなく、transformers.initialization モジュールのヘルパーを使用して値を設定してください。

from transformers import initialization as init

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

重みの初期化には、常にtransformers.initializationのヘルパーを使用してください。module.bias.zero_()のようなインプレース操作や、module.weight.dataにアクセスする処理は、どのパラメータがすでに読み込まれているかをフラグする_is_hf_initializedをバイパスしてしまいます。from_pretrained()はチェックポイントの読み込み後に_init_weightsを実行するため、インプレース操作によって、読み込まれた重みがランダムな値で無言に上書きされてしまいます。これはTransformersモデルにおいて、TRF012ルールにより強制されています。

from_pretrained()の実行中、モデルは通常metaデバイス上で初期化されます。ローダーは非永続バッファを空のテンソルとして再作成するため、__init__内で代入された値は保持されません。これらのバッファは_init_weights内で初期化し、読み込み後に正しい値が設定されるようにしてください。例として、BERTが位置IDとトークンタイプIDを初期化する方法を参照してください。

モデルに合わせて適応させる必要がある場合、初期化スキームの外観は異なる場合があります。独自のreset_parametersメソッドを持つサブモジュールは、それを直接呼び出すことができます。例えば、Wav2Vec2ForPreTrainingは、最後の2つの線形層でnn.Linearを初期化します。この呼び出しは安全です。from_pretrained()は_init_weightsを実行する間、_is_hf_initializedフラグを尊重するように、基盤となるtorch.nn.init関数をパッチするため、読み込まれた重みが上書きされることはありません。

def _init_weights(self, module):
    """Initialize the weights"""
    if isinstance(module, Wav2Vec2ForPreTraining):
        module.project_hid.reset_parameters()
        module.project_q.reset_parameters()

チェックポイントをTransformersに変換する

元のチェックポイントは、Transformers互換のチェックポイントに変換する必要があります。

既存の変換スクリプトを探して、コピーし、適応させ、再利用することをお試しください!

  • TensorFlowからPyTorchへモデルを移植する場合、BERTの変換スクリプトが良い出発点となる場合があります。
  • PyTorchからPyTorchへのモデル移植を行う場合、BARTの変換スクリプトが良い出発点となる場合があります。

すべての必須の重みが初期化されていることを確認し、初期化に使用されなかったチェックポイントの重みをすべて出力して、モデルが正しく変換されたことを確認してください。

変換中に、誤った形状のステートメントや名前割り当てが発生する場合があります。これは、BrandNewLlamaConfigのパラメータが正しくない、アーキテクチャが間違っている、実装のinitメソッドにバグがある、またはチェックポイントの重みの1つを転置する必要があることが原因である可能性が高いです。

すべてのチェックポイントの重みが正しく読み込まれるまで、モデルコードの適応セクションを繰り返し実行してください。モデルでチェックポイントを読み込めるようになったら、フォルダに保存します。このフォルダには、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のレイヤー名は、レイヤーのクラス属性名(dense、intermediate、layer_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)

ランダム初期化された重みと、それに対応する事前学習済みチェックポイントの重みが、同一のshapeとnameを持つことを確認してください。shapeに対するassert文を追加し、チェックポイントの重み名を出力してください。

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.float32 ではなく torch.long など)ことが原因であることが多いです。

出力の精度は1e-3である必要があります。出力の形状と出力値が同一であることを確認してください。出力が同一にならない一般的な理由には、以下が含まれます。

  • 一部のレイヤーが追加されていません(活性化レイヤーまたは残差接続)。
  • word embedding matrixは、tieされていません。
  • 元の実装にオフセットが含まれているため、誤った位置埋め込みが使用されます。
  • Dropout はフォワードパス中に適用されます。このエラーを修正するには、model.training が False であることを確認し、self.training を torch.nn.functional.dropout に渡してください。

元のモデルと実装のフォワードパスを比較し、違いがないか確認してください。理想的には、フォワードパスの両方の実装の中間出力をデバッグして出力し、元の実装と自分の実装がどこで異なるかを特定してください。

  1. 両方の実装でハードコードされたinput_idsが同一であることを確認してください。
  2. input_idsの最初の変換(通常は単語埋め込み)の出力が同一であることを確認し、最終層まで順に確認を進めてください。

2つの実装の間に差分があれば、それはあなたの実装にバグがあることを示しています。

最も効果的な戦略の一つは、両方の実装の同じ位置に多数のprint文を追加し、中間出力の値が一致するものから順次削除していくことです。

両方の実装が同じ出力を生成する場合、出力が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)の追加を推奨します。PreTrainedTokenizerFastの追加方法についてサポートが必要な場合は、PR内で@ArthurZuckerまたは@itazapをメンションしてください。

モデルの準備が整ったので、次はトークナイザーに注目しましょう。トークナイザーは、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を持つ場合、トークナイザーのテストファイルを追加します。このファイルは、モデリングのテストファイルと同様のものです。トークナイザーのテストファイルには、いくつかのハードコードされた統合テストを含める必要があります。

画像プロセッサを実装する

Image processor は現在、バックエンドベースのアーキテクチャを採用しています。デフォルトのバックエンドは TorchvisionBackend で、torchvision ライブラリを使用し、GPU 上で画像処理を実行できます。PIL/NumPy による代替バックエンド (PilBackend) も提供されています。両方のバックエンドは image_processing_backends からインポートされます。ご質問がある場合は、@yonigozlan をタグ付けしてご連絡ください。

この例では画像プロセッサは含まれていませんが、モデルが画像入力に依存する場合は実装が必要になる場合があります。画像プロセッサは、画像をモデルに適した形式に変換する役割を担います。新しい画像プロセッサを実装する前に、既存の画像プロセッサが再利用できるか確認してください。多くのモデルが類似した画像処理手法を共有しているため、再利用できる場合があります。また、画像プロセッサでも modular を使用して既存のコンポーネントを再利用できます。

新しい画像プロセッサの実装が必要な場合、各モデルには2つのプロセッサファイルがあります:

  • image_processing_<model>.py: デフォルトの torchvision ベースのプロセッサ(<Model>ImageProcessor)で、TorchvisionBackend を継承します。これは従来の「fast」プロセッサに置き換わります。
  • image_processing_pil_<model>.py: PIL/NumPy 代替プロセッサ(<Model>ImageProcessorPil)で、PilBackendを継承します。これは従来の「slow」プロセッサに置き換わります。

torchvision バックエンドファイルは、PIL ファイルがインポートするカスタム kwargs クラスも定義します。両方のファイルは @auto_docstring デコレーターを使用しています。手動でクラス docstring を追加しないでください。ステップバイステップの手順と完全な例については、IMAGE_PROCESSOR_REFACTORING_GUIDE.md を参照してください。

tests/models/your_model_name/test_image_processing_your_model_name.py に、image processor 用のテストを追加してください。これらのテストは、他の image processor 用のテストと同様のものとし、image processor が画像入力を正しく処理していることを検証する必要があります。image processor に固有の機能や処理方法が含まれている場合は、それらについても具体的なテストを追加してください。

プロセッサを実装する

モデルがテキストや画像など複数のモダリティを受け入れる場合、プロセッサを追加する必要があります。プロセッサは、モデルに渡す前に異なるモダリティの前処理を一元管理します。

プロセッサは、__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 に、モデルとトークナイザーのエンドツーエンド統合テストを追加してください。

テストでは、モデルが期待通りに動作することを示すために、意味のあるテキストからテキストへの例を提供する必要があります。例えば、ソースからターゲットへの翻訳ペア、記事から要約へのペア、質問から回答へのペアを含めることができます。

チェックポイントが下流タスクでファインチューニングされていない場合、モデルのテストが十分です。

最後に、モデルの内部テンソルに.to(self.device)ステートメントを追加して、テストがGPU上で実行できることを確認してください。GPUにアクセスできない場合は、こちらで対応いたします。

ドキュメントを追加

モデルが有用なのは、ユーザーがその使い方を理解している場合に限られます。そのため、ドキュメントやdocstringを追加することが重要です。Cookiecutterは、モデルに関する情報を記入できるテンプレートファイルdocs/source/model_doc/brand_new_llama.mdを追加しました。

これは一般的にユーザーがモデルと初めて接する場面となるため、ドキュメントは明確かつ簡潔であるべきです。モデルの使用方法を示す例を追加することは、非常に有用な場合が多いです。

src/transformers/models/brand_new_llama/modeling_brand_new_llama.py にドキュメント文字列が追加され、必要なすべての入力と出力が含まれていることを確認してください。ドキュメントとドキュメント文字列の作成に関するガイドを参照してください。

リファクタリング

コードのスタイルがライブラリの他の部分と一致していることを確認し、整理整頓する時間です。以下のコマンドを実行すると、誤ったスタイルが自動的に修正されます。

make style

コードスタイルが品質チェックに合格することを確認するには、以下のコマンドを実行してください。

make check-repo

Transformersの厳格なデザインテストにより、プルリクエスト内で他のテストやチェック(docstringの欠落や命名規則の誤りなど)が失敗する可能性があります。これらの問題で詰まった場合は、お手伝いできます。

コードが正しく実行されることを確認した後、より読みやすく、すっきりとした形にリファクタリングしたい場合があります。

Hubにアップロード

すべてのチェックポイントをHubに変換してアップロードします。モデルカードを追加して、モデルに関する透明性と文脈をより明確にします。モデルカードには、チェックポイントの特定の特性、モデルの学習方法、および使用例を示すコード例を記載する必要があります。

多くの場合、ユーザーが実行できるインタラクティブなノートブックを追加することは、モデルの推論の使い方や下流タスクでのファインチューニングのやり方を示すのに優れた方法です。必須ではありませんが、ノートブックを含めることで、モデルの採用をより促進できます。

また、モデルに適切な名前を付けること、およびモデルをアップロードするために必要なアクセス権限を取得することについては、Transformers チームに相談する必要があります。

モデルをアップロードするには、push_to_hub()メソッドを使用します。

brand_new_bert.push_to_hub("brand_new_llama")

モデルを Hub にアップロードする詳細については、共有ガイドを参照してください。

モデルのマージ

ついにプルリクエストをマージし、モデルをTransformersに正式に追加する準備が整いました。すべてのテストがパスし、すべてのコメントやフィードバックに対応していることを確認してください。

Transformers に新しいモデルを追加したことをお祝いします!🥳

これは非常に重要な貢献です。あなたの取り組みにより、世界中の開発者や研究者にとってTransformersがより利用しやすくなりました。自分の貢献を誇りに思い、その成果をコミュニティと共有してください!

関連項目

  • モデル構造のルール — modeling_*.py および configuration_*.py ファイルに適用される静的ルールです。PR を作成する前に、make typing を実行して確認してください。
  • Pull request チェック — PR に対して実行される CI チェックの内容と、それらをパスする方法に関する完全なリファレンス。
Update on GitHub