247 lines
7.9 KiB
Python
247 lines
7.9 KiB
Python
import os
|
|
import torch
|
|
from PIL import Image
|
|
from transformers import AutoProcessor, LlavaForConditionalGeneration
|
|
|
|
import folder_paths
|
|
import comfy.model_management as mm
|
|
|
|
from .common import bf16_or_fp16, mie_log, describe_images_core, image_to_pil_image
|
|
|
|
MY_CATEGORY = "🐑 JoyCaption"
|
|
MODELS_DIR = os.path.join(folder_paths.models_dir, "Joy_caption_alpha")
|
|
|
|
|
|
class JoyCaptionLoader:
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {"required": {
|
|
"model_name": (
|
|
[
|
|
'fancyfeast/llama-joycaption-alpha-two-hf-llava'
|
|
],
|
|
{
|
|
"default": 'fancyfeast/llama-joycaption-alpha-two-hf-llava'
|
|
}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("MIE_JOYCAPTION_MODEL",)
|
|
RETURN_NAMES = ("model",)
|
|
FUNCTION = "load_model"
|
|
CATEGORY = MY_CATEGORY
|
|
|
|
def load_model(self, model_name):
|
|
the_model_path = os.path.join(MODELS_DIR, os.path.basename(model_name))
|
|
device = mm.get_torch_device()
|
|
|
|
if not os.path.exists(the_model_path):
|
|
mie_log(f"Downloading {model_name} to: {the_model_path}")
|
|
from huggingface_hub import snapshot_download
|
|
snapshot_download(repo_id=model_name,
|
|
local_dir=the_model_path,
|
|
local_dir_use_symlinks=False)
|
|
model = LlavaForConditionalGeneration.from_pretrained(
|
|
model_name,
|
|
trust_remote_code=True,
|
|
)
|
|
|
|
dtype = bf16_or_fp16()
|
|
model = model.to(dtype).to(device).eval()
|
|
|
|
processor = AutoProcessor.from_pretrained(model_name)
|
|
|
|
return {
|
|
'model': model,
|
|
'processor': processor,
|
|
},
|
|
|
|
|
|
def describe_single_image(image, model, question, seed, temperature, top_p, max_new_tokens, do_sample):
|
|
processor = model['processor']
|
|
model = model['model']
|
|
device = mm.get_torch_device()
|
|
dtype = bf16_or_fp16()
|
|
|
|
# 设置随机种子
|
|
torch.manual_seed(seed)
|
|
torch.cuda.manual_seed(seed)
|
|
|
|
with torch.no_grad():
|
|
# Load image
|
|
pil_image = image_to_pil_image(image)
|
|
|
|
# Build the conversation
|
|
convo = [
|
|
{
|
|
"role": "system",
|
|
"content": "You are a helpful image captioner.",
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": question,
|
|
},
|
|
]
|
|
|
|
# Format the conversation
|
|
# WARNING: HF's handling of chat's on Llava models is very fragile. This specific combination of processor.apply_chat_template(), and processor() works
|
|
# but if using other combinations always inspect the final input_ids to ensure they are correct. Often times you will end up with multiple <bos> tokens
|
|
# if not careful, which can make the model perform poorly.
|
|
convo_string = processor.apply_chat_template(convo, tokenize=False, add_generation_prompt=True)
|
|
|
|
# Process the inputs
|
|
inputs = processor(text=[convo_string], images=[pil_image], return_tensors="pt").to(device)
|
|
inputs['pixel_values'] = inputs['pixel_values'].to(dtype)
|
|
|
|
# Generate the captions
|
|
generate_ids = model.generate(
|
|
**inputs,
|
|
max_new_tokens=max_new_tokens,
|
|
do_sample=do_sample,
|
|
suppress_tokens=None,
|
|
use_cache=True,
|
|
temperature=temperature,
|
|
top_k=None,
|
|
top_p=top_p,
|
|
)[0]
|
|
|
|
# Trim off the prompt
|
|
generate_ids = generate_ids[inputs['input_ids'].shape[1]:]
|
|
|
|
# Decode the caption
|
|
caption = processor.tokenizer.decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
|
caption = caption.strip()
|
|
|
|
return caption
|
|
|
|
|
|
class JoyCaptionDescribeImage:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"model": ("MIE_JOYCAPTION_MODEL",),
|
|
"image": ("IMAGE",),
|
|
"question": ("STRING", {
|
|
"multiline": True,
|
|
"default": "Describe this image in detail."
|
|
}),
|
|
"seed": ("INT", {
|
|
"default": 42,
|
|
"min": 0,
|
|
"max": 0xffffffffffffffff
|
|
}),
|
|
"temperature": ("FLOAT", {
|
|
"default": 0.1,
|
|
"min": 0.0,
|
|
"max": 1.0
|
|
}),
|
|
"top_p": ("FLOAT", {
|
|
"default": 0.95,
|
|
"min": 0.0,
|
|
"max": 1.0
|
|
}),
|
|
"max_new_tokens": ("INT", {
|
|
"default": 512,
|
|
"min": 1,
|
|
"max": 2048
|
|
}),
|
|
"do_sample": ("BOOLEAN", {
|
|
"default": True
|
|
}),
|
|
"keep_model_loaded": ("BOOLEAN", {
|
|
"default": True
|
|
}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("text",)
|
|
FUNCTION = "describe_image"
|
|
CATEGORY = MY_CATEGORY
|
|
|
|
def describe_image(self, model, image, question, seed, temperature, top_p, max_new_tokens, keep_model_loaded,
|
|
do_sample):
|
|
answer = describe_single_image(image, model, question, seed, temperature, top_p, max_new_tokens, do_sample)
|
|
|
|
if not keep_model_loaded:
|
|
print("Offloading model...")
|
|
model.to(mm.unet_offload_device())
|
|
mm.soft_empty_cache()
|
|
|
|
return (answer,)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, seed, **kwargs):
|
|
return seed
|
|
|
|
|
|
class JoyCaptionImageUnderDirectory:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"model": ("MIE_JOYCAPTION_MODEL",),
|
|
"directory": ("STRING", {"default": "X://path/to/files"}),
|
|
"question": ("STRING", {
|
|
"multiline": True,
|
|
"default": "Write a descriptive caption for this image in a formal tone."
|
|
}),
|
|
"seed": ("INT", {
|
|
"default": 42,
|
|
"min": 0,
|
|
"max": 0xffffffffffffffff
|
|
}),
|
|
"temperature": ("FLOAT", {
|
|
"default": 0.1,
|
|
"min": 0.0,
|
|
"max": 1.0
|
|
}),
|
|
"top_p": ("FLOAT", {
|
|
"default": 0.95,
|
|
"min": 0.0,
|
|
"max": 1.0
|
|
}),
|
|
"max_new_tokens": ("INT", {
|
|
"default": 512,
|
|
"min": 1,
|
|
"max": 2048
|
|
}),
|
|
"do_sample": ("BOOLEAN", {
|
|
"default": True
|
|
}),
|
|
"keep_model_loaded": ("BOOLEAN", {
|
|
"default": True
|
|
}),
|
|
"save_to_new_directory": ("BOOLEAN", {
|
|
"default": False,
|
|
}),
|
|
},
|
|
"optional": {
|
|
"save_directory": ("STRING",),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("log",)
|
|
FUNCTION = "describe_images"
|
|
CATEGORY = MY_CATEGORY
|
|
|
|
def describe_images(self, model, directory, question, seed, temperature, top_p, max_new_tokens, do_sample,
|
|
save_to_new_directory, save_directory, keep_model_loaded):
|
|
mie_log(
|
|
f"Describing images in {directory} and save to {save_directory if save_to_new_directory else directory}")
|
|
result = describe_images_core(directory, save_to_new_directory, save_directory, describe_single_image,
|
|
model, question, seed, temperature, top_p, max_new_tokens, do_sample)
|
|
if not keep_model_loaded:
|
|
print("Offloading model...")
|
|
model.to(mm.unet_offload_device())
|
|
mm.soft_empty_cache()
|
|
|
|
return result
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, seed, **kwargs):
|
|
return seed
|