mcpField
I
import-model

import-model

github.com/modular開発2071.4k 回インストール3 日前に更新
リポジトリ

import-model について

Hugging Face のモデルを MAX ModuleV3 に移植するための手順を案内するスキルです。設定と重みの調査、ベースとなるアーキテクチャの選定、差分の実装、モデル提供前の出力検証を支援します。

主な機能

  1. 01Hugging Face の設定と重みを調査
  2. 02既存アーキテクチャと移植上の障害を確認
  3. 03モデルを比較してベースを選定
  4. 04ModuleV3 移植の骨組みを生成
  5. 05アーキテクチャ差分を実装して出力を検証

活用シーン

  1. 01モデルの MAX 移植が必要かを評価
  2. 02ベース構成から ModuleV3 実装を計画
  3. 03Hugging Face の出力と照合して移植を検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/modular/skills --skill import-model

インストール後、Claude Code で/import-modelを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

.gitattributes
.gitignore
pixi.toml
README.md
references/
distributed-transformer.md
divergences.md
implement-graph.md
layer-by-layer-debugging.md
map-to-max.md
max-vs-port-isolation.md
native-arch-check.md
pitfalls-config.md
pitfalls-graph.md
pitfalls-serving.md
pitfalls-weights.md
pitfalls.md
plan-and-veto.md
read-config-json.md
read-modeling-code.md
recognize-walls.md
rename-weights.md
rewrite-donor-docstrings.md
serve-and-iterate.md
state-dict-audit.md
template.md
tests-and-ci.md
validation-tiers.md
scripts/
__init__.py
check_port.py
check_walls.py

詳細

モデルを MAX にインポートする

入力: Hugging Face のモデル ID($ARGUMENTS)。

作業を進めながら移植を記録できるように、references/template.md をコピーします。参照先ファイルのパスはこのスキルのルートからの相対パスなので、別の場所にコピーしても引き続き解決できます。

モデルを MAX に移植するとは、Hugging Face の transformers にあるモデルの modeling_<type>.py と同じ計算を行う ModuleV3 モデルを作成することです。次に、公開済みの重みを使ってコンパイルし、出力が一致することを確認します。ModuleV3 は MAX のモデル API です。レイヤーは max.experimental.nn の Module サブクラスで、Tensor の値を受け取る forward() を備えています。演算は max.experimental.functional(F としてインポート)から使用します。パイプラインは F.lazy() 内でモジュールを構築し、compile(weights=...) でコンパイルします。単一 GPU、テンソル並列、エキスパート並列のモデルはすべて ModuleV3 を使用します。複数 GPU のモデルでは、DeviceMesh を介してシャーディングします。

ワークフローのフェーズは、判断と計画、実装、検証です。フェーズ 1 は読み取りと計画です。フェーズ 2 は移植で、モジュール内の異なるサブレイヤーをすべて実装します。フェーズ 3 は検証で、実装が完了してからのみ行います。ガード(ワークフローを停止させる前提条件)によって、アクティビティ間の移行を制御します。ガード自体は独立した手順ではありません。

アンチパターン: scaffold.py を実行し、arch.py を調整して、<slug>.py がまだドナー(llama3_modulev3、olmo3 など)を実装している状態でサービングすることです。この移植では、<slug>.py がドナーのアーキテクチャを引き続き実行するため、ロジット検証に失敗します。implement-graph.md の完了基準を満たすまで、検証スクリプトを実行しないでください。

各フェーズから、詳細を説明するリファレンスにリンクしています。すべてを最初に読むのではなく、現在取り組んでいるアクティビティのリファレンスを読んでください。

環境: すべてのコマンドは、MAX がインストールされている pixi 環境を通して実行します(pixi run python …、pixi run max serve …)。pixi.toml があるスキルのルートから実行し、シェルの PATH 上にある python や max を直接使用しないでください。

cd <path-to-skill>
pixi install
pixi run python scripts/inspect_hf.py <HF_MODEL_ID>
# Or: pixi run test-scripts   # smoke-test all scripts (no GPU)

デフォルトの環境は Linux(x86-64 および ARM64)と Apple silicon 搭載の macOS にインストールでき、CUDA は不要です。この環境の PyTorch は、CPU または Apple silicon GPU 上で HF の参照モデルを実行します。NVIDIA ホストでは、pixi run -e cuda … を使うと、CUDA 対応 PyTorch で同じコマンドを実行できるため、HF の参照モデルも GPU 上で実行されます。

ヘルパースクリプトは、このスキルの scripts/ ディレクトリにあります(リポジトリにコピーするか、ベンダーとして取り込んでください)。すべてのヘルパーは、同じ引数名と終了コードを使用する統合ディスパッチャーからも実行できます。

pixi run python scripts/import_model.py inspect <HF_MODEL_ID>
pixi run python scripts/import_model.py list-archs --donors
pixi run python scripts/import_model.py scaffold <HF_MODEL_ID> --start-from olmo3 --output-dir ./
pixi run python scripts/import_model.py list-archs --match LlamaForCausalLM
pixi run python scripts/import_model.py check-walls <HF_MODEL_ID>
pixi run python scripts/import_model.py list-keys <HF_MODEL_ID> --summary
pixi run python scripts/import_model.py gates <HF_MODEL_ID> --port-dir <port_dir>
pixi run python scripts/import_model.py compare <HF_MODEL_ID> --port 8000

移植先の構成:

  • <port_dir>:arch.py と __init__.py 内の ARCHITECTURES を含むスラッグフォルダーです(通常は <output_dir>/<slug> で、末尾にスラッシュは付けません)。このパスを --custom-architectures と run_oss_gates.py --port-dir の両方に渡します。

MAX は dirname(<port_dir>) を sys.path に追加し、basename(<port_dir>) をモジュールとしてインポートすることで、--custom-architectures <port_dir> を解決します。親ディレクトリを渡すと、誤ったモジュール名(たとえば、スラッグを置くべき場所にある custom-arch)がインポートされます。

編集時にインポートエラーや API エラーが発生した場合は、インストール済みの MAX パッケージにある max/pipelines/architectures/<donor>/ のドナーアーキテクチャからインポートをコピーしてください。詳しくは pitfalls-config.md § インポートと設定 API の落とし穴を参照してください。


フェーズ 1:判断と計画

ガード:このアーキテクチャはすでに MAX に登録されていますか? コードを書く前に、MAX がモデルの config.json::architectures[0] にあるアーキテクチャクラスをすでに登録しているか確認します。 pixi run python scripts/list_native_archs.py --match <Class> がスラッグを返した場合は、pixi run max serve --model-path <HF_MODEL_ID> を実行して終了します。移植は不要です。手順の詳細は、 native-arch-check.mdを参照してください。

config.json を読む

設定を取得し、すべてのフィールドを確認します。

pixi run python -c "from transformers import AutoConfig; \
  print(AutoConfig.from_pretrained('<HF_MODEL_ID>', trust_remote_code=True))"

または、Hub から生の config.json を取得し、ネイティブアーキテクチャのチェックを実行して、MAX API に対応付けられたすべてのキーを出力するヘルパーを使います。

pixi run python scripts/inspect_hf.py <HF_MODEL_ID>

次に、重みをダウンロードせずに safetensors のメタデータ(キー、形状、dtype)を一覧表示します。

pixi run python scripts/list_checkpoint_keys.py <HF_MODEL_ID> --summary

この要約では、テンソル数と dtype を集計します。重みアダプターを作成する前に、--summary を付けずにもう一度実行して、キー名を一覧表示してください。

inspect_hf.py の表の各行は、1 つの config.json キーを pipeline_config.model.huggingface_config(architectures と torch_dtype の場合は arch.py の SupportedArchitecture)に対応付けています。移植元を選んだら、--start-from <donor_slug> を指定して再実行してください。移植元の設定ソースに一度も記載されていない各キーが示されます。これらのキーが、実装する設定の差分です。つまり、設定フィールドとそれを使用するモジュールコードです。ただし、モデルコードが読み取らないフィールド(bos_token_id、initializer_range)は除きます。フィールドの意味と一般的な差分については、read-config-json.md を参照してください。

移植を決める前に、重大な阻害要因がないか確認します。

pixi run python scripts/check_walls.py <HF_MODEL_ID>

終了コード 0 → 続行します。終了コード 1 → recognize-walls.md を確認します。終了コード 2 → 阻害要因が解決されるか、対象外として範囲が定められるまで中止します。

モデルカードを読む

https://huggingface.co/<HF_MODEL_ID> を開き、次の点についてモデルカードを確認します。

  • 論文またはブログ記事。 アーキテクチャのセクションに目を通します。著者はそこで変更点(QK-norm、MLA、スライディングウィンドウアテンション、MoE ルーティング)を挙げています。
  • カードに記載された「工夫」。 「we introduce」「unlike prior models」「this is the first model to」のような表現は、差分があることを示しています。見落とすと、実装中にバグが発生します。

カードに既知のファミリー(Llama、Mistral、Qwen、Gemma)のモデルだと記載されている場合、以下の移植元との比較では、そのファミリーで最も近い、すでに移植済みのバリアントから始めます。

モデルカードの記載によっては、阻害要因の確認が必要です。カスタム CUDA カーネル、公開された参考実装のないカスタムアテンション、FP8/FP4 のみで公開されている重み、ALiBi、再帰層または状態空間層などです。カードにこれらのいずれかが記載されている場合は、先に進む前に recognize-walls.md を読んでください。公開されている MAX のインターフェイスだけでは移植できないモデルもあります。

プランを提案し、異議を受け付ける

コードを書く前に、デフォルトでどのように進めるかを短い段落にまとめ、ユーザーの確認または異議を待ちます。これまでに確認した内容に基づいて、配布形態、移植元、量子化バリアント、検証の深さ、対象ハードウェアをすべて含めてください。モデルに ModuleV3 にはまだない要素(レイヤー、分散プリミティブ、量子化パス)が必要な場合は、その要素を明記し、max.experimental.nn に追加するのか、ユーザーの判断が必要なのかを述べてください。何も指定しないまま質問するのではなく、デフォルト案を示し、ユーザーが異議を伝えられるようにしてください。

詳しいガイダンスと段落の例については、plan-and-veto.md を参照してください。

推定した重みのバイト数が 1 つの GPU に収まらない場合は、--start-from を選ぶ前に distributed-transformer.md を読んでください。分散形態は、アテンションのファミリーだけよりも重要です。

他の MAX アーキテクチャと比較する

コピー元として最も近い ModuleV3 アーキテクチャを選びます。「最も近い」 とは、次の点が一致していることを意味します。

  • Attention の形状(Dense、GQA、MLA)
  • MLP の形状(ゲートあり/なし、Dense/ルーティングあり)
  • ヘッドの構成(重み共有/非共有、単一 Linear/複数ステップ)
  • 分布の形状

インストール済みの MAX に含まれる ModuleV3 アーキテクチャを一覧表示します(スラッグの一覧をハードコードしないでください)。

pixi run python scripts/list_native_archs.py --donors

HF のシグナルからコピー元スラッグを選ぶヒントは、 map-to-max.md にあります。簡単にまとめると、次のとおりです。

使用するモデルコピー元として選ぶモデル
Llama 3 系(GQA、RoPE、SwiGLU MLP)llama3_modulev3
QK-norm、スライディングアテンションと全体アテンションの混在olmo3
Gemma 系(1 + weight RMSNorm、デュアルノルム)gemma3_modulev3
Phi 系(融合された qkv_proj と gate_up_proj)phi3_modulev3
Granite 系(MuP スカラー)granite_modulev3
MoE(スパースエキスパート、top-k ルーティング)gpt_oss_modulev3
MLA(潜在 KV)、単一 GPUdeepseekV2_modulev3
MLA + MoE、マルチ GPU(TP、DP + EP)deepseekV3_modulev3
ハイブリッドアテンションと状態空間レイヤーnemotron_h_modulev3
ビジョン言語(--full-copy でスキャフォールドを作成)gemma3multimodal_modulev3、kimik2_5_modulev3

選んだアーキテクチャのディレクトリを開き、モデルファイル(model.py が _instantiate_module() でインスタンス化するモジュール。たとえば olmo3/olmo3.py や llama3_modulev3/llama3.py)と、その layers/ を読みます。 ポートでどのモジュールを変更し、どのモジュールをそのまま使うのかを明確にします。

次に、対応する Hugging Face のモデリングファイルを読みます。

pixi run python -c "from transformers.models.<model_type> import modeling_<model_type>; \
  print(modeling_<model_type>.__file__)"

__init__、attention の forward、MLP の forward、ブロッククラス、最終ヘッドを読みます。それぞれを MAX の対応するものと比較します。各項目で確認すべき点については、 read-modeling-code.md を参照してください。サーブする前に、 RoPE のレイアウト、norm の式、活性化関数を divergences.md の症状一覧と照合してください。関数名が rotate_half でも、隣接するペアを回転させる場合があります (ERNIE 4.5)。コピー元のデフォルト設定では、誤ったペアを読み取ります。

この作業の成果物は、HF とコピー元の MAX アーキテクチャの実際の差分をまとめた 差分一覧です。差分ごとに 1 行を使ってください(attention、MLP/MoE、ブロックの接続、 ヘッド、RoPE、マスク)。フェーズ 2 では、一覧のすべての項目を実装します。構造上の差分が 3 つ以下なら、適切なコピー元です。差分が多い場合は、より近いコピー元を選ぶか、 モジュール全体を書き換える計画を立ててください。空の差分一覧や、「Llama に似ている」 というだけの一覧のまま検証に進まないでください。


フェーズ 2:実装

ファイル構成をスキャフォールドする

scaffold.py が書き出すのはスケルトンだけです。モデルは実装しません。

pixi run python scripts/scaffold.py <HF_MODEL_ID> --start-from <donor_slug> --output-dir <output_dir>

これは Hub の config.json から architectures[0] を読み取って arch.py::name に設定し、選択した ModuleV3 アーキテクチャをサブクラス化した スケルトンを <output_dir>/<slug>/ に書き出します。slug の既定値は、 サイズのサフィックスを含むリポジトリ名です(ernie_4_5_0_3b_pt)。 アーキテクチャファミリーにちなんだパッケージ名を付けるには、--slug を渡します。 生成されるクラスは <Short>Model と <Short>Config(Ernie4_5Model)です。 Hugging Face のクラスと名前が重複する場合があるため、並べて確認するときは名前空間を指定してください。

  • arch.py:移植元が指定するすべての SupportedArchitecture キーワード(memory_planner=、batching=、reasoning_parser= など)をそのまま保持した 登録用のシェルです。PagedMemoryPlanner.with_activation_reservation(...) のように、 設定済みのプランナーも含まれます。スキャフォールドでは name=、リポジトリ ID、 エンコーディング、重みの形式、および移植先独自のクラスとアダプターを書き込みます (name=、エンコーディング、memory_planner= を確認してください)。 移植元にプランナーの指定がない場合、このフィールドは TODO を付けて省略されます。 インポート先を追跡できない値は TODO になり、警告が表示されます。
  • model_config.py:移植元の Config サブクラスです。移植元が読み込まないフィールド用の from_donor() フックを含みます(実装時に配線し直す必要があります)。
  • model.py:パイプラインモデルのシェルです。_create_model_config() は移植元の Config を独自の型に変換し、_instantiate_module() はルートモジュールを構築して、 移植元の配置に従ってデバイスまたはメッシュ上に配置します。 移植元のバッチプロセッサーを継承するため、batch_processor.py がなくても 入力の準備は引き続き機能します。
  • weight_adapters.py:移植元の名前変更処理に委譲します(チェックポイント用に書き換える必要があります)。
  • <slug>.py:移植元のモジュール(実装時に HF に合わせて編集する必要があります)

スキャフォールド後も、<slug>.py は移植元の計算を行います。モジュールを実装するまで、 サービングを開始しないでください。

スキャフォールドは ruff format で移植コードを整形し、移植元の huggingface_config.rope_theta の読み取りを get_rope_theta(huggingface_config) に書き換えます。この関数は Transformers 4 と Transformers 5 の両方で値を読み取ります。このスキルの以前のバージョンで スキャフォールドした移植コードでは、引き続き model.py で huggingface_config.rope_theta を読み取るため、check_port.py が AttributeError: ... has no attribute 'rope_theta' で失敗することがあります。 再度スキャフォールドするか、max.pipelines.lib.pipeline_variants.utils から get_rope_theta をインポートして、同じ修正を手作業で行ってください。

移植元のコードからコピーしたコードに含まれる docstring とコードコメントは、そのまま残ります。 既定のスキャフォールドでは、生成ファイルに新しい docstring を書き込みます。 --full-copy モードでは移植元のテキストが保持され、移植元のファイルをコピーした場合や、 手作業でサブクラス化して編集した場合も同様です。クラス名を変更しても、 ファイルの役割について記されたテキストは変わりません。これらのファイルには移植元を説明する docstring が残り、シングル GPU 対応、QK ノルム、アテンション後ノルムなど、移植先にはない 動作が記載されている場合があります。これらの docstring の書き換えは必須の実装手順です。 すべてのモジュール docstring が従うべきパターンについては、 rewrite-donor-docstrings.md を参照してください。 実装の完了を宣言する前に実行する監査チェックリストも記載されています。

モジュールを実装する

フェーズ 1 では、Config マップと差分リストを作成しました。実装作業では、 それらをコードに反映します。

チェックリスト、作業順序、ModuleV3 API の全体、アンチパターン、完了基準については、 implement-graph.md を参照してください。

順番に進めます:

  1. model_config.py:ドナーの設定は、認識しているキーを読み取ります。Phase 1 / inspect_hf.py の config.json に含まれるそれ以外の各キーについて、移植先の設定にフィールドを宣言し、ドナーの完成済み設定を引数にして model.py::_create_model_config() が呼び出す from_donor() で値を設定します。construct_kv_params() のヘッド数と head_dim が HF と一致することを確認してください。パイプラインは設定の construct_kv_params() を使って KV キャッシュのサイズを決めますが、from_donor() はドナーの設定から kv_params をコピーし、各モジュールはそのコピーを読み取ります。construct_kv_params()、get_head_dim()、または get_num_layers() をオーバーライドする場合は、キャッシュとモジュールの値が一致するよう、オーバーライドした値を使って from_donor() でも kv_params を設定してください。
  2. weight_adapters.py:チェックポイントの safetensor キーを、ルートモジュールの parameters が返すパラメーター名に対応付けます。ルートのラッパープレフィックス(olmo3 の language_model. など)も含めてください。まず list_checkpoint_keys.py を実行してください。rename-weights.md を参照してください。state-dict-audit.md に従ってカバレッジ監査を組み込みます。compile() はパラメーターの欠落や形状の不一致を検出し、監査はどのパラメーターにも使われていないチェックポイントテンソルを検出します。
  3. <slug>.py:差分リストの各項目について、ドナーモジュールを編集または置き換え、MAX の forward() が HF の forward() を再現するようにします。
    • Attention(Q/K/V、RoPE、mask、GQA、softcap など)
    • MLP または MoE(活性化、ルーティング、共有エキスパートなど)
    • デコーダーブロック(正規化の順序と残差の接続。Llama のものと置き換え可能ではありません)
    • 最終正規化と LM ヘッド(tie、logit scale、softcap)
  4. arch.py:name= が architectures[0] と一致し、default_encoding が Hub の torch_dtype と一致することを確認します。スキャフォールドに含まれる memory_planner=(ドナーからコピーされたもの)はそのまま残してください。これがないと、MAX は KV キャッシュ用のアクティベーションメモリーを予算に含めず、max_batch_size の推論をスキップします。スキャフォールドに memory_planner の TODO が残っている場合、ドナーには設定されていません。サービング前に解決してください。KV キャッシュを使う移植では memory_planner=PagedMemoryPlanner が必要です。独自にメモリーを推定するアーキテクチャ(diffusion、embedding)のみ、未設定のままにしてください。スキャフォールドにはドナーの batching= も引き継がれています。移植先のバッチ処理がドナーと異なる場合にのみ変更してください。
  5. model.py:_instantiate_module() はルートモジュールを構築して .to(self.devices[0]) を呼び出すか、マルチ GPU 移植の場合は default_device(mesh) 内でモジュールを構築します。HF がバックボーンを別の方法でラップしている(VL、マルチモーダルなど)場合に限り、さらに編集してください。

HF の modeling_<type>.py は、検証に失敗してからではなく、編集しながら読んでください。HF とドナーが一致する部分に限り、ドナーのサブクラスを作成します。差分リストで異なると示されたモジュールは書き直してください。

次の条件を満たしたら、実装は完了です。implement-graph.md のすべての項目にチェックが付いていることを確認してください。特に、以下の項目が重要です。

  • すべての差分に、対応するコード変更がある。
  • 重みを、孤立キーなしでコンパイルできる。
  • rewrite-donor-docstrings.md のスキャフォールドコメント監査を実行し、各一致箇所を OK、Wrong、Stale のいずれかに分類している。

実装完了を宣言する前に監査を実行してください。max serve と compare_layers.py は docstring を読み取らないため、後続の手順で監査が見落としたドナーの記述が見つかることはありません。

簡易 grep の手順(分類ルールの詳細は rewrite-donor-docstrings.md を参照してください):

grep -rniE 'qwen|llama|mistral|cohere|gemma|phi|deepseek|olmo|granite|qwen3|mixtral|single-GPU|single GPU|RMSNorm|QK-norm' <port_dir>/

サブクラス方式で移植する場合、該当箇所の大半はドナーのインポートと、スキャフォールドが記述する系譜の行です。これらは正確です。該当箇所にある docstring とコメントを読んでください。 実装完了の報告では、監査を明示的に証明してください (たとえば、「rewrite-donor-docstrings.md のパターンに従って docstring を書き換え、rg で N 件のヒットがあり、すべて正当な系譜への参照です」など)。その記述なしに、実装完了を報告しないでください。

事前チェック(Hub 設定と arch 登録。初回の serve の前に実行):

pixi run python scripts/run_oss_gates.py <HF_MODEL_ID> --port-dir <port_dir>

ガード:ローカルのスモークテストゲート(Phase 3 の前に必須)。 max serve はリクエストに応答する前にモデル全体をコンパイルするため、パラメーター名や形状が間違っていると、コンパイルを丸ごと実行して初めて判明します。まずポートチェックを実行してください。このチェックでは、max serve と同じ方法でポートをインポートし、コンパイルせずに F.lazy() の下でルートモジュールをビルドします。さらに、適合させたチェックポイントで埋められないすべてのパラメーター、形状または dtype の不一致、どのパラメーターからも読み取られないすべてのテンソルを一覧表示します。

pixi run python scripts/check_port.py <HF_MODEL_ID> --port-dir <port_dir>

compile() が失敗する場合、終了コード 1 を返します。実際のチェックポイントを読み取るため、初回実行時には重みがダウンロードされます。編集によってスキャフォールドのフォーマットが崩れるため、チェックの前に毎回ポートのフォーマットと lint を実行してください。

pixi run ruff format <port_dir>
pixi run ruff check <port_dir>

ruff check でエラーが報告されないことを確認してください。run_oss_gates.py は壁、チェックポイントのメタデータ、arch.py の名前とエンコーディングを確認しますが、このチェックの代わりにはなりません。重みの形式に関する事前チェックについては、serve-and-iterate.md を参照してください。


Phase 3:検証

一貫性のあるテキストが生成されるか確認する

前提条件: モジュールの実装が完了していること。実装中に「どうなるか」を見るために serve しないでください。まず設定、アダプター、モジュールを修正してください。

まず HF のリファレンスを健全性チェックします。 MAX を使う前に、モデルカードで指定されたプロンプトテンプレートを使って、HF のみで実行してください。HF 自体が意味不明なテキストを生成する場合、リファレンスに問題があり、それとの比較ではポートを修正しても解消しない不一致が報告されます。

次に serve します。

pixi run max serve --model-path <HF_MODEL_ID> --custom-architectures <port_dir>

max serve はデバイスのメモリのうち --device-memory-utilization(デフォルトは 0.9)で指定された割合を KV キャッシュ用に確保します。確保時に起動が失敗する場合(メモリ不足、またはデバイスが許容するサイズを超えるバッファが原因)、たとえば --device-memory-utilization 0.6 のように割合を下げてください。HF リファレンスをサーバーと同じマシンで実行する場合も、両者がメモリを奪い合うため、割合を下げてください。float32 のリファレンスでは、そうしないとディスクにスワップされ、比較に数分かかる場合があります。

指示チューニング済みモデルは、チャットテンプレートを適用する /v1/chat/completions 経由でプローブします。ベースモデルには、プレーンなプロンプトを使って /v1/completions を使用します。PrefixLM には、モデルカードに記載されたプロンプト形式が必要です。回答前に推論するモデル(デフォルトで <think> ブロックを出力するモデルなど)では、MAX 側で chat_template_kwargs を使い、HF の apply_chat_template() では同じキーワード(enable_thinking=False)を指定して、両方の側で推論をオフにしてください。そうしないと、比較対象が推論テキストだけになります。HF 側でチャットテンプレートを適用したテキストをトークナイズするときは、テンプレートですでに追加されているため、add_special_tokens=False を指定してください。それぞれの結果に応じて、次の手順に進みます。

  • サーバーがロード中にクラッシュする → 設定とアダプターを修正します。
  • サーバーは起動するが、意味不明なテキストを返す → 差異の原因を調査します。
  • サーバーが妥当なテキストを返す → max_tokens=64+ で実行し、その後 verify ゲートを実行します。差分の一部が欠けているポートでも、流暢に回答することがあります。

HF リファレンスの完全な健全性チェック、エンコーダー/埋め込みモデルの slug serve フロー、修正とテストの反復を徹底する方法については、 serve-and-iterate.md を参照してください。

パリティ/一貫性の失敗(debug-model を実行)

サーバーは起動しても、出力が誤っていることがあります。たとえば、意味不明なテキスト、インデックス K での誤った貪欲トークン、誤った argmax との高い logit コサイン類似度、あるいは N トークンまでは一貫したテキストを生成するものの、その後に逸脱する場合です。そのようなときは、スカラー出力のタップを追加するのをやめて、debug-model スキルを読み込んでください。

このスキルでは、モデルが読み込まれて実行されるものの、出力が誤っている場合に対処します。ワークフローでは、次の手順が必要です。

  1. 同じプロンプトとチェックポイントで HF のサニティチェックを行う
  2. レイヤーごとに HF と MAX のテンソルダンプを比較する(F.print を目視するのではありません)
  3. 再コンパイルする前に数値検証を行う、並列調査エージェント
  4. ダンプが一致しているのに生成テキストが異なる場合は、serve と pipeline の二分探索を行う

立ち上げ用の足場とゲートには import-model を使用します。逸脱の原因調査には debug-model を使用します。

クイック logit プローブ(最初の 5 分間のみ)

ダンパーを構築する前に、簡単なサニティチェックを実行してください。

pixi run python scripts/compare_layers.py <HF_MODEL_ID> \
  --port 8000 \
  --prompt "The capital of France is"

同じポートで --custom-architectures <port_dir> を指定して pixi run max serve を実行する必要があります。このプローブは logprob を読み取りますが、これは overlap scheduler に拒否されます。MAX では、arch.py で supports_overlap_scheduler=False を設定しない限り、そのスケジューラーが有効になります。そのため、arch.py で有効のままにする場合は、serve コマンドに --no-enable-overlap-scheduler --force を追加してください。このスクリプトは HF のみのレイヤー統計を出力し、プロンプトの top-1 logprob を比較します。フラグの詳細については、layer-by-layer-debugging.md を参照してください。

logprob が逸脱する場合や、出力が意味不明な場合は、debug-model に切り替えてください。手動の出力タップだけで試行を繰り返さないでください。divergences.md の症状一覧は、比較ツールで失敗レイヤーを特定した後も引き続き役立ちます。

Hugging Face と照合する

事前学習済みの重みを使ってモデルをエンドツーエンドで実行し、その後、同じプロンプトで HF を貪欲サンプリングにより実行します。MAX 側では、モデルがサポートする重みのエンコーディングと一致する dtype を使用してください(ほとんどのモデルは bfloat16 で提供されています)。可能であれば HF 側は float32 で実行してください。bfloat16 の参照値には独自の丸め誤差が加わるため、正しい bfloat16 移植版でも数トークン以内に差が生じることがあります。

出力が最初に異なるトークンで、HF の上位 2 つの logprob を確認します。それらの差が約 0.1 nats 以内であれば、その位置ではモデル間に優劣がなく、どちらのトークンも正解です。最初の数トークンで HF の明確な勝者がいるにもかかわらず逸脱する場合は、通常、トークナイザーまたはチャットテンプレートの不一致、リリースされた重みとの dtype の不一致、あるいは MAX のサンプリングがゼロでないことを意味します。

出力が一致するか、最初の相違が同点の位置で発生する場合、移植は貪欲テキストについて完了です。その他の完了基準は、計画時に選んだ検証の深さによって異なります。スモークテストから logit パリティまでのティアを選んでください。

HF との完全な比較手順、逸脱のトリアージ、検証ティア表については、validation-tiers.md を参照してください。


よくある落とし穴

pitfalls.md を索引として使用してください。症状を見つけたら、該当するカテゴリファイル(config、weights、module、または serving)を読み込み、docstring の監査には rewrite-donor-docstrings.md を参照してください。特に多い落とし穴は次のとおりです。

  • 足場をそのままサーブする。 <slug>.py にあるすべての差分をモジュールに実装するまで、サーブや検証を行わないでください。足場として作成されたモジュールは、ドナーの計算処理を実行します。
  • 名前を変更してもドナーの docstring が残る。 コピーしたドナーファイル(--full-copy または手動でコピーしたもの)では、クラス名は変更されますが、docstring とコメントにはドナーについての説明が残ります。実装が完了したと宣言する前に、それらを書き換えて監査用の grep を実行してください。

テストと CI

移植したモデルに pytest テストを追加するときは、ファイルごとのコンパイル回数を最小限に抑えてください。モジュールスコープのフィクスチャを使って一度だけコンパイルし、@pytest.mark.parametrize のケース間で再利用します。異なるモジュールをコンパイルする必要があるファイルは、Bazel の shard_count で並列化し、ファイルを分割せずに処理します。完全なパターンと例については、tests-and-ci.md を参照してください。

コメント