技術約15分で読めます

Anima-Turbo INT8ConvRot量子化版をM1 MaxのComfyUIで動かす

いけさん目次

CivitAIに Anima-Turbo v1.0 INT8ConvRot Quantized が上がっていた。早速チェックしてみると、UNet・テキストエンコーダ(qwen_3_06b_base)・VAE(hdrVAEAnimaKrea2QWEN)の3点をINT8ConvRotという手法で量子化し、モデルカードには「RTX 20シリーズ以上で推論50%以上高速化」「VRAM大幅削減」「画質劣化は軽微」と書かれている。

ただしこの加速効果の説明はCUDA環境(RTX 20シリーズ以上でINT8ConvRot加速、RTX 10シリーズは加速非対応だが高速推論は維持)が前提で、Apple Silicon / MPSでの挙動には触れられていない。M1 Max上のComfyUIで実際にロードできるか、速度・VRAM・画質がどう変わるかを実測してみた。

検証の範囲

画像生成は普通はほとんどComfyUI経由なので、検証もまずそのワークフローにINT8ConvRot版を組み込み、モデルのロードからテキストエンコーダ、10ステップのサンプリング、VAEデコードまで通るか、速度・VRAM・画質がどう変わるかを確かめた。

Apple GPU上で量子化行列積そのものを実行でき、fp16より速くなるかは、ComfyUIを外してPyTorchの最小コード、MLX、直接書いたMetalカーネルだけで測る別記事の単体ベンチへ分けた。

ここで使うComfyUI APIは、ローカルの/promptへワークフローを1回送信し、完了するまで/historyを1秒ごとに確認しているだけである。テンソルや各レイヤーの演算をHTTP経由で往復させているわけではなく、実際の推論はComfyUIサーバープロセス内のPyTorch/MPSで進む。APIを挟むことで変わるのは、最大1秒程度の計測粒度だけになる。

検証環境

項目内容
マシンMac (M1 Max, 64GB RAM)
UIComfyUI 0.24.1
比較対象A(基準)anima-base-v1.0.safetensors + anima-turbo-lora-v0.2.safetensors(fp16)
比較対象B(INT8ConvRot・フル)animaTurboV10Int8convrotQuantized_v10.safetensors + 付属int8 TE/VAE
比較対象C(INT8ConvRot・混合)UNetのみint8、TE/VAEは既存fp16(qwen_3_06b_base / qwen_image_vae)
共通条件同一prompt・seed(123456789)、832×1024、steps10・cfg1・er_sde/simple

モデル構成

コンポーネント基準(fp16)INT8ConvRot版
UNetanima-base-v1.0.safetensors + Turbo LoRA v0.2animaTurboV10Int8convrotQuantized_v10.safetensors(2.1GB)
Text Encoderqwen_3_06b_base.safetensorsanimaTurboV10Int8convrotQuantized_v10_txt.safetensors(718MB)
VAEqwen_image_vae.safetensorshdrVAEAnimaKrea2QWEN_int8.safetensors(484MB)

ロードすら通らなかった

3パターンとも同一プロンプト・同一シードでComfyUI APIから流した。結果、INT8ConvRot版は「フル」「混合(UNetのみint8)」の両方ともロード段階でエラーになり、画像生成まで到達しなかった。

ケース結果所要時間VRAM使用量
A: 基準(fp16 + Turbo LoRA)成功58.6秒約15.0GB
B: INT8ConvRot フル失敗(ロードエラー)
C: INT8ConvRot 混合(UNetのみint8)失敗(ロードエラー)

エラーは両ケースとも同じ箇所で発生した。

KeyError: 'int8_tensorwise'
  File "comfy/ops.py", line 1074, in _load_quantized_module
    qconfig = QUANT_ALGOS[module.quant_format]

BはCLIPLoader(テキストエンコーダのロード)、CはUNETLoaderでそれぞれ同じKeyErrorが出た。量子化済みsafetensorsの内部メタデータに書かれたquant_formatint8_tensorwiseだが、ComfyUI側の量子化フォーマット登録テーブルにこのキーが存在しない。

ComfyUIが対応している量子化フォーマットにint8_tensorwiseが無い

comfy/quant_ops.pyQUANT_ALGOSを直接確認すると、ComfyUI 0.24.1(2026-06-04時点のコード、gitコミットba9ffa0)がネイティブ対応する量子化フォーマットは次の4つだけだった。

フォーマット対応レイアウト
float8_e4m3fnTensorCoreFP8E4M3Layout
float8_e5m2TensorCoreFP8E5M2Layout
nvfp4TensorCoreNVFP4Layout
mxfp8(CK利用可能時のみ)TensorCoreMXFP8Layout

いずれもNVIDIAのTensor Core向けFP8/FP4系フォーマットで、int8_tensorwiseという名前のキーはComfyUIのコア・comfy_extras・インストール済みcustom_nodesのどこにも存在していない(grep -rl int8_tensorwiseで0件)。

_load_quantized_moduleはロード対象のstate_dictに埋め込まれたquant_formatメタデータを見てレジストリを引くだけの処理で、GPU種別やバックエンドを見ていない。同じComfyUI 0.24.1であればCUDA環境でも同じKeyErrorが出るはずで、モデルカードが謳う「RTX 20シリーズ以上でINT8ConvRot加速」は、少なくとも素のComfyUI 0.24.1上では検証以前にそもそも動かせる環境ではなかった。

ComfyUIのint8対応はv0.27.0から

safetensorsのヘッダーを直接確認すると、量子化された各レイヤーにはweight(I8)、weight_scale(F32)に加えてcomfy_quantというU8[72]のサイドカーテンソルが付いている。これはComfyUI自身の量子化スキームが書き込む管理用テンソルで、外部の汎用量子化ツールの出力ではなく、ComfyUI向けに量子化されたモデルであることが分かる。

ComfyUIのGitHubリポジトリを直接確認したところ、検証機のv0.24.1(2026-06-04時点のコミット)は最新v0.30.1から325コミット遅れていた。git log -S"int8_tensorwise"で該当コミットを追うと、int8量子化モデル対応は Support int8 models. (#14636) で追加され、初出タグはv0.27.0(2026-06-25マージ、検証機のコミットの3週間後)。さらに Support int8 convrot embedding lookup (#15035) というConvRotそのものを指すコミットも存在する。

v0.30.1comfy/quant_ops.pyを確認すると、QUANT_ALGOSint8_tensorwiseTensorWiseINT8Layout)とconvrot_w4a4TensorCoreConvRotW4A4Layout)の両方が登録済みだった。

というわけでロード失敗の原因は、検証機のComfyUIがint8量子化モデル対応より前のバージョンだったのが理由。そう言えば前もバージョン違いで本実験前に動かないみたいなことを言ってた気がするので、メイン使用のComfyUIをついでに引き上げておいた方がいいかと思った。

v0.30.1にアップグレードした

comfy/model_management.pyのMPS/float8まわりと、Anima用のsupported_inference_dtypesはv0.24.1→v0.30.1間で変更はなかった(過去にComfyUIアプデでQwen Image Editの推論dtypeが変わり生成が10分かかるようになった記事があるため、同種の回帰がないか差分を先に確認した)ので、この2点に限れば今回のアップグレードで新規に壊れるリスクは低いと判断した。

local/v0.24.1ブランチはロールバック用にそのまま残し、local/v0.30.1ブランチ(v0.30.1タグ)を新規に切って切り替えた。venv内の依存関係をrequirements.txt基準で更新すると、PyTorchが2.10.0から2.13.0に上がった。

起動するとcomfyui_version: 0.30.1を確認でき、UNETLoader/CLIPLoaderのロードエラーは解消した。フル・混合の両方とも量子化レイヤーの読み込み自体は通った。

ロードは通ったが、推論の最初の行列積で別のエラーが出た

KSamplerの最初のブロック(comfy/ldm/cosmos/predict2.pyadaln_modulation_self_attn)で新しいエラーが出た。

NotImplementedError: The operator 'aten::_int_mm' is not currently implemented for the MPS device.
If you want this op to be considered for addition please comment on
https://github.com/pytorch/pytorch/issues/141287 ...
As a temporary fix, you can set the environment variable
`PYTORCH_ENABLE_MPS_FALLBACK=1` to use the CPU as a fallback for this op.

トレースを辿ってみると、ComfyUIの量子化バックエンドcomfy_kitchenのint8線形層実装(comfy_kitchen/backends/eager/quantization.pyint8_linearfast_int8_mm)が、int8×int8行列積にtorch._int_mmを呼んでいた。このオペレータ自体がPyTorch本体でMPSカーネル未実装(PyTorch issue #141287)で、フル・混合どちらのケースも同じ箇所で同じ例外になった。この例外は、ComfyUIを通さない最小コードでも再現した。

ComfyUI側の対応(v0.27.0でのint8サポート追加)とPyTorch側のMPSカーネル実装は別の話で、今回はComfyUIが対応してもPyTorch側が対応してなかった。

PYTORCH_ENABLE_MPS_FALLBACK=1で試したが、Turboの意味がなくなるほど遅かった

エラーメッセージが提示するCPUフォールバックを試した。PYTORCH_ENABLE_MPS_FALLBACK=1を付けてComfyUIを再起動し、int8フル構成を再実行した。

基準(fp16 + Turbo LoRA)は10ステップ・49.3秒で完了する設定だが、CPUフォールバック版のint8フル構成は27分経過してもKSamplerの10ステップが終わらなかった。ブロックごと・ステップごとに_int_mmを呼ぶたびにMPS↔CPU間のテンソル転送とCPU側の演算が入るため、実行を中断した。

INT8ConvRot版が謳う「50%以上の高速化」どころか、素のfp16+Turbo LoRA構成の何十倍も遅い状態で、Turbo(少ステップ高速生成)としての意味が失われている。

int8×int8のPyTorch MPS経路が存在しない

検証機のComfyUIはorigin/masterから1コミット差(フロントエンドパッケージのバージョン更新のみで無関係)まで上げており、ComfyUIの更新を待って解決するようなものではない。

ComfyUIの量子化バックエンドcomfy_kitchenのパッケージ構成を見ると、backends/配下にはcudatritonhip(AMD ROCm)と汎用のeagerがあり、mps向けのバックエンドはディレクトリごと存在しない。MPSではeager実装が使われる。その中身が前述のfast_int8_mmで、CUDA向けのパディング処理(if a.is_cuda)は持っているが、MPSを想定した分岐はない。hasattr(torch, "int8_mm")という将来のAPIを見込んだ分岐もあるが、PyTorch 2.13.0にはまだ存在せず、結局torch._int_mm(MPS未実装)が呼ばれる。

通常のINT8ConvRot経路では、comfy_kitchenのint8_lineartorch._int_mmを呼び、そのPyTorch演算にはMPSカーネルがない。今回止まった直接の原因は、MPS上でint8の活性とint8の重みを掛ける経路が両者の組み合わせに存在しないことだった。

comfy_kitchenにMPS用の逆量子化経路を足してみた

comfy_kitchen/tensor/int8.pyを確認してみると、量子化されていない重みが来た場合や特定条件では、すでに「逆量子化してから通常のlinear/mm/addmmを呼ぶ」フォールバック経路が実装の中に存在していた。量子化された重みを使う通常経路だけがtorch._int_mmを要求する高速パスに直結していた。

TensorWiseINT8Layout.dequantizeが呼ぶdequantize_int8_simple_dtype/dequantize_int8_convrot_weight_dtypeeager実装は、int8とスケールの単純な要素積で、CUDA固有の命令に依存しない。そこでMPSデバイスの場合だけ、既存の量子化済み重み(QuantizedTensor)をweight.dequantize()で通常のfp16/bf16テンソルへ変換してから、素のtorch.nn.functional.linearmm/addmmも同様)に渡すよう、_handle_int8_linear_tensorwise_handle_int8_mm_tensorwise_handle_int8_addmm_tensorwiseの3箇所に分岐を足した。

if input_tensor.device.type == "mps":
    weight_fp = weight.dequantize()
    return torch.nn.functional.linear(input_tensor, weight_fp, bias)

この方式は量子化されたint8×int8の高速演算そのもの(torch._int_mm)を使わず、レイヤーごとに一旦fp16/bf16へ戻してから通常の行列積を実行するだけなので、MPSに存在する演算だけで実行できる。

パッチ適用後、3ケースとも生成できた

ComfyUIを再起動してベンチを再実行した。

ケース結果所要時間VRAM使用量(参考値)
A: 基準(fp16 + Turbo LoRA)成功46.3秒約9.1GB
B: INT8ConvRot フル(UNet+TE+VAE)成功43.3秒測定ノイズあり(後述)
C: INT8ConvRot 混合(UNetのみint8)成功41.2秒約4.0GB

3枚とも破綻のない画像が出力された。

基準(fp16 + Turbo LoRA v0.2)

INT8ConvRot フル構成

INT8ConvRot 混合構成(UNetのみint8)

B・Cはほぼ同じ絵柄で出た(同じUNetを使っているので当然)。基準(A)とは背景・制服の配色が異なるが、これはanima-base-v1.0 + Turbo LoRAという別構成と、Anima-Turboという別チェックポイントを比較しているためで、同一シードでも構図が一致するわけがない。量子化による劣化を見るならB・C同士の比較の方が妥当で、この2枚の間に目視で分かる破綻や劣化は見当たらなかった。

VRAM使用量は測定値がちょっとおかしかった。ComfyUIはケースの切り替え時に前のモデルをアンロードするため、Bの「使用量」がマイナス(空きメモリが増えた)になった。ケース間でモデルのロード・アンロードのタイミングが揃っていないのが原因で、この簡易計測では正確なピークメモリを比較できていない。

速度は基準46.3秒に対しB43.3秒・C41.2秒と、わずかに速いが誤差の範囲に近い。モデルカードが謳う「50%以上の高速化」を裏付ける結果にはならなかった。

比較対象が揃っていなかった問題を直す

ここまでの基準(A)はanima-base-v1.0 + Turbo LoRA v0.2で、これはAnima-Turbo本体が未公開だった時期の代替構成。INT8ConvRot版が量子化した元の「Anima-Turbo v1.0」チェックポイントそのものとは系統が違うため、量子化だけを変数にした比較になっていなかった。

CivitAIの公式Animaモデルページ(circlestone_labs、modelId 2458426)に無量子化のanima-turbo-v1.0anima_turboV10.safetensors、bf16、3.99GB)があったので、これを取得して同じAnima-Turbo系統で揃え直した。Turboはこのチェックポイントに焼き込み済みで、別LoRAは不要。

ケースUNet状態所要時間
公式Anima-Turbo v1.0(無量子化bf16)anima_turboV10.safetensorsパッチ不要、ネイティブに動作42.2秒
INT8ConvRot フルanimaTurboV10Int8convrotQuantized_v10.safetensors+int8 TE/VAE前述のMPS用逆量子化パッチが無いと動かない47.2秒
INT8ConvRot 混合(UNetのみint8)同上+fp16 TE/VAE同上45.2秒

無量子化の公式版の方が、パッチを当てたINT8ConvRot版2種より速かった。 逆量子化パッチはレイヤーごとにint8→スケール適用→fp16変換という追加処理が入るため、量子化前の重みをそのまま使うより遅くなる。量子化によって速くなるどころか、Mac上では余分な変換コストが乗る分だけ遅くなっている。

公式Anima-Turbo v1.0(無量子化bf16)

INT8ConvRot フル(同一シード・同一プロンプト)

画質面では、同じチェックポイント系統だけあって構図・髪型・服装はほぼ一致した。色調の差は瞳だけではなく、int8版は瞳が赤紫寄りで目の縁にも赤みが乗り、背景や襟元の青みも強い。ただ構造的な破綻や劣化は見当たらず、モデルカードの「画質劣化は軽微」という主張通りのようだった。

速度は公式無量子化版より遅く、画質は同等なので、この逆量子化パッチでMac上のINT8ConvRot版を使うメリットはダウンロード容量が小さい(2.1GB vs 3.99GB)点だけになる。別のMPS経路を使った場合については、別記事の単体ベンチで切り分ける。

逆量子化パッチで確認できた範囲

このパッチはMPSに存在しないtorch._int_mm(int8×int8の高速行列積)を一切使わず、レイヤーごとにfp16へ逆量子化してから通常の行列積を実行しているだけで、実行時の計算そのものは実質fp16相当になる。INT8ConvRotが本来売りにしている量子化テンソル演算による高速化は今回検証できていない

モデル自体の重みは壊れておらず、逆量子化すれば正しく機能する。Mac(MPS)でもこの経路を通すと生成自体はできたが、体感速度はfp16構成とほぼ同等で、量子化による高速化は今回の方法では再現できなかった。ダウンロード容量(本体2.1GB、TE 718MB、VAE 484MB)が小さい点だけは、ディスク使用量の面でそのままメリットとして残るが、それだけなので、有用だとは言い難い。

動く段階と動かない段階

Macでint8量子化テンソル演算がどの段階まで動くかを切り分けるとこんな感じ。

段階結果
量子化済みテンソルのロード可能(ComfyUI v0.27.0以降でint8_tensorwiseに対応済み)
CPU上でのtorch._int_mm実行可能(検証機のComfyUI venvで最小コードでも成功を確認)
MPS上でのtorch._int_mm実行不可(aten::_int_mm is not currently implemented for the MPS device
PYTORCH_ENABLE_MPS_FALLBACK=1動くが、警告通りCPUフォールバックであり、Mac GPU(MPS)上のint8高速化ではない
今回のパッチ(comfy_kitchen/tensor/int8.pyでMPSだけweight.dequantize()してfp系のlinear/mm/addmmに逃がす)動くが、これもint8×int8の速度検証ではない

正確に言うなら、現状のComfyUI + comfy_kitchen + PyTorch MPSでは、MPS上でINT8ConvRotのint8×int8 GEMMを実行する経路がない。CPUフォールバックか逆量子化経由なら動くが、どちらもMac GPU上のint8高速化検証にはならない。

一応、PyTorchのMPS対応演算は追跡が続いており(PyTorch issue #141287)、AppleもMPS向けカスタムMetalカーネルが書けることを公式に案内はしている。したがって今回未実装なのは「このソフトウェアスタックの通常経路」の話で、Macでは物理的に不可能という話ではない、と思う。

そもそも設計自体がCUDA前提で作られている。ComfyUIのint8_tensorwiseのドキュメントでは、対応GPUの条件がSM 7.5以上と明記されている。これはNVIDIAのCompute Capability世代を指す条件で、モデルカードが謳う「RTX 20シリーズ以上で加速」もこの設計をそのまま反映したもの。Apple SiliconのMPSは端から対象に入っていない。

NVIDIAのGPU Performance Background User’s Guideによると、Turing世代はINT8演算をTensor CoreまたはCUDA coreで実行できる。元の実装は、そのハードウェアへ最適化されたint8行列積を使えることが前提である。今回の基本的なMetalカーネルとは、入力型が同じでも実行される命令と最適化の蓄積が異なる。

そのため、NVIDIA環境でfp16より速くなること自体は技術的には理解できる。ただしモデルカードが掲げる「50%以上高速化」は、今回のM1 Maxの結果からその数値の正誤は判断できない。今回の検証で言えるのは、CUDA向けに成立する高速経路がMPSにはそのまま存在してなかったということくらいかと思う。

量子化行列積をPyTorch、Metal、MPSMatrix、MLXから直接測った結果は、M1 Max上のINT8ConvRot単体ベンチへ続く。