Fixed Reactor to Pytorch2.6.0 on ComfyUI
昨日、A1111系WebUIでの懸案事項だった、Adetailer周りのPytorch2.6.0に対する障害が解決し、一つ問題解決…と相成りましたが、
その後の検証で、ComfyUI上のReactorにおいては未だ問題が修正されておらず、同種のエラーが今尚継続して発生する事がわかりました。
引き続き、上記事の対策(Pytorch側のコード修正)を採る事で回避は出来るんですが…
上記事でも触れていますが、機能的に更新されているPytorch側のセキュリティをわざわざ2.5.1と同様に下げる形の対処は、本来あるべき姿としては邪道な訳です。
あるべき姿は、あくまでも拡張機能側でPytorch2.6.0に対応する事ですが、A1111系に関しては私にその対処を行うだけの能力がなく、そのまま拡張機能側の更新を待つしかありませんでしたが、
ComfyUI側の問題が、現時点判っている範囲ではReactorのみに限定されている為(Detailer Pipe周りでは何故か当初から発生しなかった)、「あるべき姿」の形での修正が何とかならないか...と考え、今度は何とかなりました。
パスは、\ComfyUI\custom_nodes\comfyui-reactor-node\scripts\r_masking\subcore.pyです。
全体のコードです。
import numpy as np
import cv2
from PIL import Image
import torch
import torch.serialization
import scripts.r_masking.core as core
from reactor_utils import tensor_to_pil
import logging
# 警告メッセージを抑制するために一時的にロギングレベルを変更
original_level = logging.getLogger().level
logging.getLogger().setLevel(logging.ERROR)
try:
from ultralytics import YOLO
# ultralytics関連のクラス
from ultralytics.nn.tasks import DetectionModel
from ultralytics.nn.modules.conv import Conv
# 基本的なブロックとヘッド - 必須のクラス
basic_classes = []
try:
from ultralytics.nn.modules.block import C2f, C3, C2, SPPF, Bottleneck, BottleneckCSP
from ultralytics.nn.modules.head import Detect, Segment, Pose, OBB, Classify
basic_classes = [C2f, C3, C2, SPPF, Bottleneck, BottleneckCSP, Detect, Segment, Pose, OBB, Classify]
except ImportError:
pass
# torch関連の標準クラス
from torch.nn.modules.container import Sequential, ModuleList
from torch.nn.modules.conv import Conv2d, ConvTranspose2d
from torch.nn.modules.batchnorm import BatchNorm2d
from torch.nn.modules.activation import SiLU, ReLU, LeakyReLU, Hardswish
from torch.nn.modules.linear import Linear, Identity
from torch.nn.modules.dropout import Dropout
from torch.nn.modules.pooling import MaxPool2d, AvgPool2d
from torch.nn.modules.padding import ZeroPad2d, ReflectionPad2d
from torch.nn.modules.upsampling import Upsample
from torch.nn.modules.normalization import GroupNorm, LayerNorm
# 追加のultralytics特有のクラス - 静かに失敗させる
additional_classes = []
try:
from ultralytics.nn.modules.block import DFL, Proto, RepC3, C3TR, C3Ghost, GhostConv, HGBlock, SPP
additional_classes.extend([DFL, Proto, RepC3, C3TR, C3Ghost, GhostConv, HGBlock, SPP])
except ImportError:
pass
try:
from ultralytics.nn.modules.block import RepConv, Focus, ChannelAttention, SpatialAttention, CBAM
additional_classes.extend([RepConv, Focus, ChannelAttention, SpatialAttention, CBAM])
except ImportError:
pass
try:
from ultralytics.nn.modules.block import Concat, TransformerBlock, C3STR, C2f_DCN, Space2Depth
additional_classes.extend([Concat, TransformerBlock, C3STR, C2f_DCN, Space2Depth])
except ImportError:
pass
# 必要なクラスをセーフリストに追加
safe_classes = [
# ultralytics関連
DetectionModel, Conv,
# torch関連
Sequential, ModuleList, Conv2d, ConvTranspose2d, BatchNorm2d,
SiLU, ReLU, LeakyReLU, Hardswish, Linear, Identity, Dropout,
MaxPool2d, AvgPool2d, ZeroPad2d, ReflectionPad2d, Upsample,
GroupNorm, LayerNorm
]
# 基本クラスと追加クラスを追加
safe_classes.extend(basic_classes)
safe_classes.extend(additional_classes)
# セーフリストに追加
torch.serialization.add_safe_globals(safe_classes)
except Exception as e:
# エラーは記録するが、警告は表示しない
logging.error(f"YOLOモデルの初期化中にエラーが発生しました: {e}")
# ロギングレベルを元に戻す
logging.getLogger().setLevel(original_level)
# 代替方法: weights_only=Falseを使用する関数
def load_yolo(model_path: str):
try:
# 通常の方法でロードを試みる
return YOLO(model_path)
except Exception as e:
# エラーをログに記録するが、コンソールには表示しない
logging.error(f"通常のロード方法でエラーが発生しました: {e}")
# 代替方法: weights_only=Falseを使用
original_torch_load = torch.load
try:
# 一時的にtorch.loadを上書き
torch.load = lambda *args, **kwargs: original_torch_load(*args, weights_only=False, **kwargs)
model = YOLO(model_path)
return model
except ModuleNotFoundError:
# https://github.com/ultralytics/ultralytics/issues/3856
YOLO("yolov8n.pt")
return YOLO(model_path)
finally:
# 元のtorch.loadに戻す
torch.load = original_torch_load
# ... 残りのコードは変更なし ...
def inference_bbox(
model,
image: Image.Image,
confidence: float = 0.3,
device: str = "",
):
pred = model(image, conf=confidence, device=device)
bboxes = pred[0].boxes.xyxy.cpu().numpy()
cv2_image = np.array(image)
if len(cv2_image.shape) == 3:
cv2_image = cv2_image[:, :, ::-1].copy() # Convert RGB to BGR for cv2 processing
else:
# Handle the grayscale image here
# For example, you might want to convert it to a 3-channel grayscale image for consistency:
cv2_image = cv2.cvtColor(cv2_image, cv2.COLOR_GRAY2BGR)
cv2_gray = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2GRAY)
segms = []
for x0, y0, x1, y1 in bboxes:
cv2_mask = np.zeros(cv2_gray.shape, np.uint8)
cv2.rectangle(cv2_mask, (int(x0), int(y0)), (int(x1), int(y1)), 255, -1)
cv2_mask_bool = cv2_mask.astype(bool)
segms.append(cv2_mask_bool)
n, m = bboxes.shape
if n == 0:
return [[], [], [], []]
results = [[], [], [], []]
for i in range(len(bboxes)):
results[0].append(pred[0].names[int(pred[0].boxes[i].cls.item())])
results[1].append(bboxes[i])
results[2].append(segms[i])
results[3].append(pred[0].boxes[i].conf.cpu().numpy())
return results
class UltraBBoxDetector:
bbox_model = None
def __init__(self, bbox_model):
self.bbox_model = bbox_model
def detect(self, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
drop_size = max(drop_size, 1)
detected_results = inference_bbox(self.bbox_model, tensor_to_pil(image), threshold)
segmasks = core.create_segmasks(detected_results)
if dilation > 0:
segmasks = core.dilate_masks(segmasks, dilation)
items = []
h = image.shape[1]
w = image.shape[2]
for x, label in zip(segmasks, detected_results[0]):
item_bbox = x[0]
item_mask = x[1]
y1, x1, y2, x2 = item_bbox
if x2 - x1 > drop_size and y2 - y1 > drop_size: # minimum dimension must be (2,2) to avoid squeeze issue
crop_region = core.make_crop_region(w, h, item_bbox, crop_factor)
if detailer_hook is not None:
crop_region = detailer_hook.post_crop_region(w, h, item_bbox, crop_region)
cropped_image = core.crop_image(image, crop_region)
cropped_mask = core.crop_ndarray2(item_mask, crop_region)
confidence = x[2]
# bbox_size = (item_bbox[2]-item_bbox[0],item_bbox[3]-item_bbox[1]) # (w,h)
item = core.SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, label, None)
items.append(item)
shape = image.shape[1], image.shape[2]
segs = shape, items
if detailer_hook is not None and hasattr(detailer_hook, "post_detection"):
segs = detailer_hook.post_detection(segs)
return segs
def detect_combined(self, image, threshold, dilation):
detected_results = inference_bbox(self.bbox_model, core.tensor2pil(image), threshold)
segmasks = core.create_segmasks(detected_results)
if dilation > 0:
segmasks = core.dilate_masks(segmasks, dilation)
return core.combine_masks(segmasks)
def setAux(self, x):
pass
「# ... 残りのコードは変更なし ...」から上部分が改造したコードですが、何回か失敗した後に、追加する形で改造したので、余り美しくはないと思いますが…一応何とかなる筈です。
Curdor先生が捻り出した理屈としては
この方法では:
1.まず安全なグローバルを使用してロードを試みます
(想定されるカスタムクラスを、なるべくセーフリストに加えました)
2.それが失敗した場合、weights_only=Falseを使用して代替方法を試みます
3.最終的にはどちらかの方法でモデルをロードできるはずです
YOLOモデルは多くのカスタムクラスを使用しているため、すべてを列挙するのは難しいですが、この方法なら確実にモデルをロードできるはずです。
…
失敗する度に、想定されるYoloモデルにおけるカスタムクラスを可能な限りピックアップして追加したのですが、想定に限界があるらしく、最後はPytorch2.6.0におけるセキュリティ更新を全否定する形で、「weights_only=False」を使うよ…という趣旨です。
私がここまで作成してきたjsonの中では、以下がReactorを使う形になっていますが、これによってPytorch本体を弄らずともノードが完走するようになりました。

関連記事も、順次この記事に合わせて改訂していきます。
また、最新のPytorch対応繋がりですが、本体プログラムがかなり古くなってきているLama-Cleanerに関しても、Pytorch関連の警告が出る為、その対処の為のコードを作成して公開しました。
