Files
2025-01-30 02:43:45 +08:00

312 lines
12 KiB
Python

import sys
import os
import torch
import numpy as np
import folder_paths
import time
import re
from PIL import Image
from transformers import AutoConfig, AutoModelForCausalLM
# 关键路径处理:将当前目录添加到系统路径
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, current_dir) # 添加当前目录到Python路径
try:
from janus.models import MultiModalityCausalLM, VLChatProcessor
from janus.utils.io import load_pil_images
except ImportError as e:
print(f"路径调试信息:")
print(f"当前目录: {current_dir}")
print(f"目录内容: {os.listdir(current_dir)}")
print(f"sys.path: {sys.path}")
raise
# 添加模型路径配置
current_directory = os.path.dirname(os.path.abspath(__file__))
folder_paths.folder_names_and_paths["Janus"] = ([os.path.join(folder_paths.models_dir, "Janus")], folder_paths.supported_pt_extensions)
# 辅助函数
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
class Janus_ModelLoader:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_path": ("STRING", {"default": "deepseek-ai/Janus-Pro-7B"}),
}
}
RETURN_TYPES = ("JANUS_MODEL", "PROCESSOR", "TOKENIZER")
RETURN_NAMES = ("model", "processor", "tokenizer")
FUNCTION = "load_model"
CATEGORY = "🧩Janus"
def load_model(self, model_path):
# 加载配置
config = AutoConfig.from_pretrained(model_path)
language_config = config.language_config
language_config._attn_implementation = 'eager'
# 加载模型
vl_gpt = AutoModelForCausalLM.from_pretrained(
model_path,
language_config=language_config,
trust_remote_code=True
).to(torch.bfloat16 if torch.cuda.is_available() else torch.float16)
if torch.cuda.is_available():
vl_gpt = vl_gpt.cuda()
# 加载处理器
processor = VLChatProcessor.from_pretrained(model_path)
tokenizer = processor.tokenizer
return (vl_gpt, processor, tokenizer)
class Janus_MultimodalUnderstanding:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("JANUS_MODEL",),
"processor": ("PROCESSOR",),
"tokenizer": ("TOKENIZER",),
"image": ("IMAGE",),
"question": ("STRING", {"default": "describe the image", "multiline": True}),
"seed": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0.0, "max": 1.0, "step": 0.05}),
"temperature": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step": 0.05}),
},
"optional": {
"max_new_tokens": ("INT", {"default": 512, "min": 16, "max": 2048}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("response",)
FUNCTION = "understand"
CATEGORY = "🧩Janus"
def understand(self, model, processor, tokenizer, image, question, seed, top_p, temperature, max_new_tokens=512):
# 修复种子范围问题
seed = seed % (2**32)
# 设置随机种子(添加CUDA同步)
torch.manual_seed(seed)
np.random.seed(seed % (2**32 - 1)) # 适配numpy种子范围
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
torch.cuda.synchronize()
try:
# 图像预处理(添加维度验证)
if isinstance(image, list):
image_tensor = image[0]
else:
image_tensor = image
pil_image = tensor2pil(image_tensor)
if pil_image.mode != "RGB":
pil_image = pil_image.convert("RGB")
# 构建对话(添加异常处理)
try:
conversation = [{
"role": "<|User|>",
"content": f"<image_placeholder>\n{question}",
"images": [pil_image],
}, {
"role": "<|Assistant|>",
"content": ""
}]
except Exception as e:
print(f"对话构建失败: {e}")
return ("Error: Invalid conversation format",)
# 处理输入(添加维度调试)
try:
prepare_inputs = processor(
conversations=conversation,
images=[pil_image],
force_batchify=True
).to(model.device, dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float16)
print(f"输入张量形状 - input_ids: {prepare_inputs.input_ids.shape}")
print(f"注意力掩码形状: {prepare_inputs.attention_mask.shape}")
except Exception as e:
print(f"输入处理失败: {e}")
return ("Error: Input processing failed",)
# 生成过程(添加参数验证)
try:
inputs_embeds = model.prepare_inputs_embeds(**prepare_inputs)
print(f"输入嵌入形状: {inputs_embeds.shape}")
generation_config = {
"inputs_embeds": inputs_embeds,
"attention_mask": prepare_inputs.attention_mask,
"pad_token_id": tokenizer.eos_token_id,
"bos_token_id": tokenizer.bos_token_id,
"eos_token_id": tokenizer.eos_token_id,
"max_new_tokens": max_new_tokens,
"do_sample": temperature > 0,
"temperature": temperature if temperature > 0 else 1.0,
"top_p": top_p,
}
# 执行生成(添加时间监控)
start_time = time.time()
outputs = model.language_model.generate(**generation_config)
print(f"生成耗时: {time.time() - start_time:.2f}秒")
except Exception as e:
print(f"生成失败: {e}")
return ("Error: Generation failed",)
# 解码输出(添加异常处理)
try:
full_output = outputs[0].cpu().tolist()
answer = tokenizer.decode(full_output, skip_special_tokens=True)
# 清理特殊标记
clean_pattern = r'<\|.*?\|>'
clean_answer = re.sub(clean_pattern, '', answer).strip()
return (clean_answer,)
except Exception as e:
print(f"解码失败: {e}")
return ("Error: Output decoding failed",)
except Exception as e:
print(f"处理过程中出现未捕获的异常: {e}")
return ("Error: Unexpected processing error",)
class Janus_ImageGeneration:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("JANUS_MODEL",),
"processor": ("PROCESSOR",),
"tokenizer": ("TOKENIZER",),
"prompt": ("STRING", {"multiline": True, "default": "Master shifu racoon wearing drip attire"}),
"seed": ("INT", {"default": 12345, "min": 0, "max": 0xffffffffffffffff}),
"cfg_weight": ("FLOAT", {"default": 5.0, "min": 1.0, "max": 10.0, "step": 0.5}),
"temperature": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("images",)
FUNCTION = "generate"
CATEGORY = "🧩Janus"
def generate(self, model, processor, tokenizer, prompt, seed, cfg_weight, temperature):
# 清理缓存并设置种子
torch.cuda.empty_cache()
seed = seed % (2**32)
torch.manual_seed(seed)
np.random.seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
# 固定参数(与原始代码一致)
width = 384
height = 384
parallel_size = 5
patch_size = 16
image_token_num = 576
# 构建输入文本
messages = [{'role': '<|User|>', 'content': prompt},
{'role': '<|Assistant|>', 'content': ''}]
text = processor.apply_sft_template_for_multi_turn_prompts(
conversations=messages,
sft_format=processor.sft_format,
system_prompt=''
) + processor.image_start_tag
# 生成输入ID
input_ids = torch.LongTensor(tokenizer.encode(text)).to(model.device)
# 初始化Tokens(严格保持原始结构)
tokens = torch.zeros((parallel_size * 2, len(input_ids)), dtype=torch.int, device=model.device)
for i in range(parallel_size * 2):
tokens[i, :] = input_ids
if i % 2 != 0:
tokens[i, 1:-1] = processor.pad_id
# 生成过程(保持原始循环结构)
inputs_embeds = model.language_model.get_input_embeddings()(tokens)
generated_tokens = torch.zeros((parallel_size, image_token_num), dtype=torch.int, device=model.device)
pkv = None
for i in range(image_token_num):
with torch.no_grad():
outputs = model.language_model.model(
inputs_embeds=inputs_embeds,
use_cache=True,
past_key_values=pkv
)
pkv = outputs.past_key_values
# 原始分类器自由引导实现
logits = model.gen_head(outputs.last_hidden_state[:, -1, :])
logit_cond = logits[0::2, :]
logit_uncond = logits[1::2, :]
logits = logit_uncond + cfg_weight * (logit_cond - logit_uncond)
# 采样逻辑
probs = torch.softmax(logits / temperature, dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
generated_tokens[:, i] = next_token.squeeze(dim=-1)
# 准备下一轮输入(保持原始视图操作)
next_token = torch.cat([next_token.unsqueeze(1), next_token.unsqueeze(1)], dim=1).view(-1)
img_embeds = model.prepare_gen_img_embeds(next_token)
inputs_embeds = img_embeds.unsqueeze(dim=1)
# 图像解码(严格保持原始实现)
patches = model.gen_vision_model.decode_code(
generated_tokens.to(dtype=torch.int),
shape=[parallel_size, 8, width//patch_size, height//patch_size]
)
# 后处理(原始unpack逻辑)
dec = patches.to(torch.float32).cpu().numpy().transpose(0, 2, 3, 1)
dec = np.clip((dec + 1) / 2 * 255, 0, 255).astype(np.uint8)
visual_img = np.zeros((parallel_size, width, height, 3), dtype=np.uint8)
visual_img[:, :, :] = dec
# 转换为ComfyUI图像格式
output_images = []
for i in range(parallel_size):
pil_img = Image.fromarray(visual_img[i]).resize((768, 768), Image.LANCZOS)
output_images.append(pil2tensor(pil_img))
return (torch.cat(output_images, dim=0),)
NODE_CLASS_MAPPINGS = {
"Janus_ModelLoader": Janus_ModelLoader,
"Janus_MultimodalUnderstanding": Janus_MultimodalUnderstanding,
"Janus_ImageGeneration": Janus_ImageGeneration
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Janus_ModelLoader": "🧩Janus Model Loader",
"Janus_MultimodalUnderstanding": "🧩Janus Multimodal Understanding",
"Janus_ImageGeneration": "🧩Janus Image Generation"
}