Files
ussoewwin-ComfyUI-QwenImage…/speed_improvement_proposal.md
T

9.3 KiB
Raw Blame History

ComfyUI-QwenImageLoraLoader 速度改善提案書

作成日: 2026年1月7日
分析対象: ComfyUI-QwenImageLoraLoader ノード全体


概要

本提案書は、ComfyUI-QwenImageLoraLoaderノードにおける潜在的な速度改善ポイントを分析し、具体的な改善案を提示するものである。


1. 現状分析

1.1 主要ファイル構成

ファイル 行数 役割
nunchaku_code/lora_qwen.py 1521行 LoRA合成・適用のコアロジック
wrappers/qwenimage.py 387行 ComfyUI用モデルラッパー
wrappers/zimageturbo.py 478行 Z-Image-Turbo用ラッパー
nodes/lora/qwenimage.py 382行 LoRAローダーノード定義

1.2 処理フロー

[LoRAローダーノード] 
    ↓
[モデルラッピング(初回のみ)]
    ↓
[forward()呼び出し]
    ↓
[LoRA変更検出]
    ↓
[compose_loras_v2() → LoRA合成]
    ↓
[モデル推論実行]

2. 速度ボトルネック分析

2.1 【最重要】 lora_qwen.py - LoRAファイルの重複読み込み

場所: compose_loras_v2() 関数(1191-1210行)

問題点:

# 1回目: デバッグログ用(1193行目)
first_lora_state_dict = _load_lora_state_dict(first_lora_path_or_dict)
for key in first_lora_state_dict.keys():
    parsed_res = _classify_and_map_key(key)  # キー分類処理
    ...

# 2回目: 実際の処理用(1210行目)  
for lora_path_or_dict, strength in lora_configs:
    lora_state_dict = _load_lora_state_dict(lora_path_or_dict)  # ← 同じファイルを再読み込み!
    for key, value in lora_state_dict.items():
        parsed = _classify_and_map_key(key)  # ← キー分類処理も再実行!

Caution

同一LoRAファイルを2回読み込み、キー分類処理も2回実行している

改善案:

  1. 読み込み結果のキャッシュ再利用

    • 1回目で読み込んだfirst_lora_state_dictを2回目のループで再利用
  2. _classify_and_map_key()結果のメモ化

    • @functools.lru_cacheでキー分類結果をキャッシュ
    • 同じキーに対する重複計算を完全に排除

推定改善効果: LoRA読み込み・処理時間 40-50% 短縮


2.2 【高優先度】 lora_qwen.py - キーマッピング処理

場所: _classify_and_map_key() 関数(320-374行)

問題点:

  • すべてのLoRAキーに対して、KEY_MAPPING配列(約30個の正規表現パターン)を順次走査している
  • safetensorsファイルには数百〜数千のキーが含まれるため、O(n×m)の計算量が発生

改善案:

  1. プレフィックスベースのディスパッチ導入

    • キーの先頭部分(transformer_blocks、single_transformer_blocks、layersなど)でグループ分けし、該当グループのパターンのみ検索
  2. 正規表現のプリコンパイルキャッシュ確認

    • 現状: re.compile()済み ✅
    • 追加: functools.lru_cacheデコレータで_classify_and_map_key()自体をキャッシュ可能

推定改善効果: LoRA読み込み時間 20-40% 短縮


2.3 【高優先度】 lora_qwen.py - LoRA状態辞書の読み込み

場所: _load_lora_state_dict() 関数(435-448行)

問題点:

with safe_open(path, framework="pt", device="cpu") as f:
    for key in f.keys():
        state_dict[key] = f.get_tensor(key)
  • すべてのテンソルをCPUメモリに一括読み込みしている
  • 大容量LoRAファイル(100MB以上)で顕著なメモリ圧迫と読み込み遅延

改善案:

  1. 遅延読み込み(Lazy Loading)の導入

    • 必要なキーのみを逐次読み込む
    • 未使用キー(LoKR形式など)はスキップ
  2. メモリマップドI/O

    • mmap=Trueオプションの検討(safetensorsサポート状況確認要)

推定改善効果: 大容量LoRA読み込み時間 30-50% 短縮、メモリ使用量削減


2.4 【中優先度】 qwenimage.py (wrapper) - LoRA変更検出

場所: forward() メソッド(128-142行)

現状のコード:

loras_changed = False
if self._applied_loras is None or len(self._applied_loras) != len(self.loras):
    loras_changed = True
else:
    for applied, current in zip(self._applied_loras, self.loras):
        if applied != current:
            loras_changed = True
            break

問題点:

  • 毎回のforward()でリスト全体を走査
  • LoRAパス文字列の比較(文字列比較はコストが高い)

改善案:

  1. ハッシュベースの変更検出

    # LoRA追加時にハッシュを計算して保持
    self._loras_hash = hash(tuple((str(p), s) for p, s in self.loras))
    
  2. フラグベースの明示的通知

    • LoRA追加時にself._loras_dirty = Trueを設定
    • forward()では単純なフラグチェックのみ

推定改善効果: forward()オーバーヘッド 5-10% 削減


2.5 【中優先度】 lora_qwen.py - QKV/GLU融合処理

場所: _fuse_qkv_lora()(850-987行)、_fuse_glu_lora()(792-847行)

問題点:

  • テンソル連結(torch.cat)が複数回発生
  • ゼロ初期化した大きなテンソルを作成してから部分コピー

現状のコード例:

B_fused = torch.zeros(out_q + out_k + out_v, 3 * r, dtype=B_q.dtype, device=B_q.device)
B_fused[:out_q, :r] = B_q
B_fused[out_q: out_q + out_k, r: 2 * r] = B_k
B_fused[out_q + out_k:, 2 * r:] = B_v

改善案:

  1. ブロック対角行列の直接構築

    • torch.block_diag()の利用検討
  2. 事前アロケーションの最適化

    • テンソルプール導入でアロケーション削減

推定改善効果: LoRA合成時間 10-15% 短縮


2.6 デバッグログについて

Note

開発必須項目のため改善対象外
詳細なデバッグログ出力(compose_loras_v2() 内のキーインスペクション等)は、開発・デバッグに必須の機能であるため、速度改善の対象外とする。


2.7 【低優先度】 nodes/lora/qwenimage.py - 動的インポート

場所: load_lora() メソッド(97-110行)

問題点:

spec = importlib.util.spec_from_file_location(
    "wrappers.qwenimage",
    os.path.join(lora_loader_dir, "wrappers", "qwenimage.py")
)
wrappers_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(wrappers_module)
  • LoRA適用のたびに毎回モジュールを読み込み実行している

改善案:

  1. モジュールレベルでの事前インポート

    • ファイル先頭で一度だけインポート
  2. キャッシュ済みモジュールの再利用

    if "wrappers.qwenimage" not in sys.modules:
        # インポート実行
    else:
        wrappers_module = sys.modules["wrappers.qwenimage"]
    

推定改善効果: LoRAノード実行時間 2-5% 短縮


2.8 【低優先度】 qwenimage.py (wrapper) - テンソルIDキャッシュ

場所: __init__() メソッド(55-61行)

現状:

self._img_ids_cache = {}
self._txt_ids_cache = {}
self._linspace_cache_h = {}
self._linspace_cache_w = {}

確認事項:

  • キャッシュが実際に活用されているか確認が必要
  • キャッシュヒット率の計測が望ましい

改善案:

  • 現状のキャッシュ実装が適切であれば変更不要
  • LRU制限の追加でメモリリーク防止

2.9 【低優先度】 lora_qwen.py - モジュールパス解決

場所: _get_module_by_name() 関数(382-404行)

問題点:

  • ドット区切りパスを毎回split(".")してトラバース
  • 同一パスへの繰り返しアクセスでも毎回トラバース

改善案:

  1. パス解決結果のキャッシュ
    • @functools.lru_cacheの導入
    • 注意: モデル構造変更時にキャッシュ無効化が必要

推定改善効果: compose処理時間 3-5% 短縮


3. 優先度別まとめ

即効性が高い改善(推奨順)

優先度 改善項目 推定効果 実装難易度
最重要 LoRA重複読み込み排除 40-50% 低
高 キーマッピング最適化 20-40% 中
高 LoRA遅延読み込み 30-50% 中
中 ハッシュベース変更検出 5-10% 低
中 QKV/GLU融合最適化 10-15% 中
低 動的インポート排除 2-5% 低
低 モジュールパスキャッシュ 3-5% 低

4. 備考

4.1 計測の必要性

上記の改善効果はコード分析に基づく推定値である。実際の改善を行う前に、以下の計測を推奨する:

  1. プロファイリング実施

    • cProfileまたはpy-spyによるボトルネック特定
  2. ベンチマークテスト作成

    • 改善前後の定量比較

4.2 互換性への配慮

  • 外部APIの変更は最小限に抑える
  • 既存のワークフローが動作することを確認

4.3 この提案書について

  • 本提案書はコード分析のみに基づく
  • 実装変更は行っていない
  • 実装着手前にユーザー様のレビュー・承認を要する

以上