本記事は生成AIと共同で執筆しています。事実関係は可能な範囲で公式ドキュメント等と照合していますが、誤りが含まれている可能性があります。重要な判断を行う前にご自身でも一次情報をご確認ください。

結論(先出し)

デジタルアーカイブの画像検索基盤で、Hugging Face transformers を 4.57.6 から 5.5.0 に更新する Dependabot の PR が 2 本(サービスごとに 1 本ずつ)届きました。同じメジャー更新ですが、対応した結果は次のように分かれました。

  • SigLIP2(transformers にネイティブ実装されているモデル)を使うサービスは、5.5.0 へ更新できました。ただし get_text_features / get_image_features の戻り値がテンソルから出力オブジェクトに変わっており、.pooler_output を取り出す修正が 1 箇所必要でした。この属性は 4.x が返していたものと同一のテンソルのため、Elasticsearch に投入済みの既存ベクトルとの互換は保てます。
  • clip-japanese-base(trust_remote_code=True で読み込む独自実装のモデル)を使うサービスは、5.5.0 では 2 段階の非互換により動作せず、4.57.6 に据え置きました。いずれもモデルリポジトリ側のコードに起因するもので、利用側の設定では回避できないようでした。

同じ transformers 4.57.6 から 5.5.0 への Dependabot メジャー更新 PR 2 本に対する判断の分岐図。左の clip-siglip2(SigLIP2、transformers ネイティブ実装)は戻り値の変更を 1 箇所吸収して 5.5.0 へ更新(成功)。右の clip-ja(clip-japanese-base、trust_remote_code)は、① meta device 初期化で .item() が実行できず失敗、② timm を更新しても post_init() 契約の差で失敗、の 2 段階で詰まり、4.57.6 に据え置き(理由を明文化)という結果になったことを示す。

「モデルが transformers 本体に実装されているか、trust_remote_code か」で更新の可否が分かれた事例として、確認手順と根拠を記録します。

対象のシステム

史料画像の類似検索・テキストからの画像検索のために、CLIP(Contrastive Language-Image Pre-training)系の画像埋め込みサービスを 2 つ、CPU 推論の Docker コンテナとして運用しています。

サービスモデル読み込み方埋め込み次元
clip-siglip2google/siglip2-base-patch16-256transformers ネイティブ実装768
clip-jaline-corporation/clip-japanese-basetrust_remote_code=True(独自クラス CLYPModel)512

どちらも生成した埋め込みベクトルを Elasticsearch の dense_vector に格納し、コサイン類似度で検索しています。この構成では、ライブラリ更新後も「同じ入力から同じベクトルが出ること」が重要になります。埋め込みの計算結果が変わると、投入済みのベクトルすべてと比較できなくなり、再インデックスが必要になるためです。

依存関係の確認

まず、更新後の依存関係が解決できるかを uv pip compile で確認しました。transformers 5.5.0 のメタデータ(PyPI)で関係する制約は次の通りでした。

Requires-Python: >=3.10.0
huggingface-hub  >=1.5.0, <2.0
numpy            >=1.17
tokenizers       >=0.22.0, <=0.23.0
safetensors      >=0.4.3

このうち実際に requirements の変更が必要だったのは huggingface_hub だけでした。従来 >=0.30,<1.0 で固定していたため、5.5.0 の要求する >=1.5 とは両立せず、こちらもメジャー更新(調査時の解決結果は 1.23.0)になります。一方 numpy は >=1.17 のため、既存の numpy==1.26.4 の固定はそのまま維持できました。

-transformers==4.57.6
-huggingface_hub>=0.30,<1.0
+transformers==5.5.0
+huggingface_hub>=1.5,<2.0

依存解決自体は 2 サービスとも競合なく通りました。問題はこの先の実行時です。

SigLIP2 側: 戻り値の変更を 1 箇所吸収して移行

clip-siglip2 のコンテナを 5.5.0 でビルドし、モデルのロードと推論を試したところ、最初のテキストエンコードで次のエラーになりました。

AttributeError: 'BaseModelOutputWithPooling' object has no attribute 'shape'

transformers 4.x の SiglipModel.get_text_features() はプール済みのテンソル([batch, 768])を直接返していましたが、5.x では BaseModelOutputWithPooling という出力オブジェクトを返すようになっています。中身を確認すると、フィールドは次の 2 つでした。

type: BaseModelOutputWithPooling
  last_hidden_state  [1, 64, 768]
  pooler_output      [1, 768]

ここで重要なのは、.pooler_output が 4.x の戻り値と同一のテンソルであることです。4.x の実装は内部で pooler_output を取り出して返していたので、5.x で自分で取り出せば同じ値になり、既存の埋め込みベクトルとの互換が保てます。誤って last_hidden_state を自前でプーリングしたりすると、投入済みベクトルと比較できない値になります。

transformers 4.x と 5.x の get_text_features の戻り値の違いを示す図。4.x は Tensor batch, 768を直接返す。5.x は BaseModelOutputWithPooling オブジェクトを返し、その中に .last_hidden_state [batch, 64, 768] と .pooler_output [batch, 768] がある。4.x の戻り値と 5.x の .pooler_output が同一のテンソルであることを矢印で示し、.pooler_output を取れば既存の埋め込みベクトルと互換になることを注記している。

修正はヘルパー関数を 1 つ足すだけでした。テンソルが直接返る 4.x 系とオブジェクトが返る 5.x 系の両方を受けるようにしておくと、ライブラリの行き来にも耐えられます。

def _pooled(out: object) -> torch.Tensor:
    """4.x はテンソルを直接返し、5.x は BaseModelOutputWithPooling を返す。
    .pooler_output は 4.x の戻り値と同一のテンソル。"""
    if isinstance(out, torch.Tensor):
        return out
    pooled = getattr(out, "pooler_output", None)
    if isinstance(pooled, torch.Tensor):
        return pooled
    raise RuntimeError(f"unexpected SigLIP feature output type: {type(out)!r}")

この修正後、Docker コンテナ内でモデルのロードからテキスト・画像両方の推論まで通ることを確認しました(transformers 5.5.0 / huggingface_hub 1.23.0、出力 768 次元、L2 ノルム 1.0)。SigLIP2 は transformers 本体に実装されているモデルなので、メジャー更新でもこの程度の追従で済んでいます。

clip-japanese-base 側: 2 段階の非互換で見送り

問題はもう一方の clip-ja です。line-corporation/clip-japanese-base は独自アーキテクチャのモデルで、モデルリポジトリに同梱された実装(modeling_clyp.pyCLYPModel)を trust_remote_code=True で実行して読み込みます。この実装は transformers 4.x の API を前提に書かれています。

段階 1: meta device 上での初期化に耐えられない

5.5.0 でロードすると、モデルの構築中に次のエラーで停止しました。

File ".../timm/models/eva.py", line 475, in __init__
    dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)]
RuntimeError: Tensor.item() cannot be called on meta tensors

transformers 5.x は from_pretrained でのモデル構築(cls(config))を torch.device("meta") コンテキストの下で実行するようになりました。meta device のテンソルは形状だけを持つプレースホルダで実値を持たないため、__init__ の中で .item().tolist() のような「テンソルの値に依存する処理」を行うコードはそこで失敗します。clip-japanese-base の画像タワーは timm(PyTorch Image Models)の EVA02 バックボーンを構築時に呼び出しており、固定していた timm 1.0.11 の __init__ がまさにこのパターンでした。

インストールされた transformers 5.5.0 の modeling_utils.py を確認した範囲では、この meta device コンテキストは初期化パスに無条件に組み込まれており、利用側から無効化するフラグは見当たりませんでした。4.x で同種の挙動を制御していた low_cpu_mem_usage=False も、5.x では受け付けられなくなっており(削除済みの引数として扱われる)、実際に指定しても結果は変わりませんでした。

段階 2: timm を上げても、次は post_init() の契約で失敗

timm の新しい版(調査時点の最新 1.0.28)では EVA02 の該当箇所が meta device でも通るようになっており、段階 1 は越えられました。しかし今度は別のエラーになります。

AttributeError: 'CLYPModel' object has no attribute 'all_tied_weights_keys'

transformers 5.x では、PreTrainedModel を継承するクラスは __init__ の最後に self.post_init() を呼ぶことが求められ、all_tied_weights_keys などの内部属性はそこで初期化されます。この規約は公式の Model structure rules に明文化されていますが、4.x 時代に書かれた CLYPModel はこの呼び出しを行っていないため、ロード処理の途中で属性が見つからず失敗します。

段階 1 は timm の更新で回避できましたが、段階 2 はモデルリポジトリ同梱のコードそのものの修正が必要で、利用側では対処できません(モデルクラスへのモンキーパッチという手はありますが、次の非互換が出るたびに繰り返すことになり、埋め込みの正しさを担保する検証コストも見合わないと判断しました)。clip-ja は transformers 4.57.6 に据え置き、requirements.txt のコメントに理由を明文化した上で、Dependabot の PR はクローズしました。

なお 4.x に留まる場合のセキュリティ面も確認しています。4.57.6 は正規表現の DoS(ReDoS: regular expression denial of service)系の修正を含んでおり、5.x でのみ修正されているモデルロード系の GHSA(GitHub Security Advisory)2 件は「バージョン固定した信頼済みモデルのみをロードし、Trainer は使わない」という利用形態では該当しないと判断しました。

transformers 5 側の位置づけ

調査した限り、これらは transformers 5.x の意図的な仕様変更で、モデル実装側に追従を求めるという整理のようです。

  • meta device での初期化により、モデル構築時のメモリ確保と重みの二重ロードを避ける設計になっています。前述の Model structure rules には、PreTrainedModel を継承するクラスが self.post_init() を呼ぶこと(規則 TRF013)や、重み共有を _tied_weights_keys で宣言すること(TRF004 ほか)がモデル実装者向けの規約として明記されています。同ドキュメントは trust_remote_code についても、「リモートコードは transformers 側でレビュー・保守できない」として本体のネイティブ実装では使わない方針を示しており(規則 TRF014)、独自実装のモデルが本体の規約変更に追従しにくい構造的な背景がうかがえます。なお __init__ 内で .item() のような値依存の操作を避けるべきという点は、このドキュメントに個別の規則として挙がっているわけではなく、meta device 初期化の挙動から結果的に要求されるものです。
  • 同種の報告は transformers の issue(#43957#43646)のほか、BiRefNet・InternVL・RMBG-2.0 など trust_remote_code を使う複数のモデルのリポジトリで挙がっています。確認した範囲では、初期化時の meta device を無効化する利用側オプションは見当たりませんでした。
  • 対応はモデルリポジトリ側で個別に進んでいます。たとえば音声モデル dasheng は、torch.linspace(...)device="cpu" を渡してテンソルを実デバイス上で作るようにする一行の修正コミットを公開しています(段階 1 の .item() エラーはこの形で解消できます)。clip-japanese-base についても、リポジトリ側で 5.x 対応の revision が公開されれば、その時点で更新を再検討できます。

更新可否の判断ポイント

今回の経験を、transformers のメジャー更新 PR を受け取ったときの確認手順として整理すると次のようになります。

  1. モデルが transformers 本体のネイティブ実装か、trust_remote_code か確認する。ネイティブ実装なら本体側で 5.x 対応が済んでいるため、追従は利用側コードの範囲で収まる可能性が高いです。trust_remote_code の場合は、モデルリポジトリのコードが 5.x の規約(meta device 初期化・post_init() 呼び出し)に対応しているかに依存します。
  2. 依存解決を先に確認する。transformers 5.5.0 は huggingface_hub 1.x 系(>=1.5)を要求するため、<1.0 で固定しているとそこで止まります。numpy は >=1.17 なので 1.x 系のままでも通ります。
  3. 埋め込み用途では、戻り値の互換を確認する。get_text_features / get_image_features が出力オブジェクトを返すようになった場合、既存ベクトルと同じ値になるフィールド(SigLIP 系では .pooler_output)を選ぶ必要があります。
  4. 判断の根拠を残す。更新を見送る場合は、requirements のコメント等に「なぜ・どの条件が解ければ再開できるか」を書いておくと、次に同じ PR が来たときに調査をやり直さずに済みます。

参考