268 lines
9.7 KiB
Python
268 lines
9.7 KiB
Python
import streamlit as st
|
|
import torch
|
|
import numpy as np
|
|
from transformers import AutoConfig, AutoModelForCausalLM
|
|
from janus.models import MultiModalityCausalLM, VLChatProcessor
|
|
from PIL import Image
|
|
|
|
# -----------------------------
|
|
# 1. Load Model and Processor
|
|
# -----------------------------
|
|
@st.cache_resource
|
|
def load_model_and_processor(model_path="deepseek-ai/Janus-1.3B"):
|
|
config = AutoConfig.from_pretrained(model_path)
|
|
language_config = config.language_config
|
|
# Force eager attention implementation (sometimes needed depending on environment)
|
|
language_config._attn_implementation = 'eager'
|
|
|
|
vl_gpt_model = AutoModelForCausalLM.from_pretrained(
|
|
model_path,
|
|
language_config=language_config,
|
|
trust_remote_code=True
|
|
)
|
|
vl_gpt_model = vl_gpt_model.to(torch.bfloat16 if torch.cuda.is_available() else torch.float16)
|
|
if torch.cuda.is_available():
|
|
vl_gpt_model = vl_gpt_model.cuda()
|
|
|
|
vl_chat_proc = VLChatProcessor.from_pretrained(model_path)
|
|
return vl_gpt_model, vl_chat_proc
|
|
|
|
vl_gpt, vl_chat_processor = load_model_and_processor()
|
|
tokenizer = vl_chat_processor.tokenizer
|
|
cuda_device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
|
|
|
|
|
# -------------------------------------
|
|
# 2. Multimodal Understanding Section
|
|
# -------------------------------------
|
|
@torch.inference_mode()
|
|
def multimodal_understanding(image, question, seed, top_p, temperature):
|
|
# Clear CUDA cache before generating
|
|
if torch.cuda.is_available():
|
|
torch.cuda.empty_cache()
|
|
|
|
# Set seed
|
|
torch.manual_seed(seed)
|
|
np.random.seed(seed)
|
|
if torch.cuda.is_available():
|
|
torch.cuda.manual_seed(seed)
|
|
|
|
# Build conversation
|
|
conversation = [
|
|
{
|
|
"role": "User",
|
|
"content": f"<image_placeholder>\n{question}",
|
|
"images": [image],
|
|
},
|
|
{"role": "Assistant", "content": ""},
|
|
]
|
|
|
|
# Prepare inputs
|
|
pil_image = Image.open(image).convert("RGB")
|
|
prepared_inputs = vl_chat_processor(
|
|
conversations=conversation, images=[pil_image], force_batchify=True
|
|
).to(cuda_device, dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float16)
|
|
|
|
# Prepare input embeddings
|
|
inputs_embeds = vl_gpt.prepare_inputs_embeds(**prepared_inputs)
|
|
|
|
# Generate output
|
|
outputs = vl_gpt.language_model.generate(
|
|
inputs_embeds=inputs_embeds,
|
|
attention_mask=prepared_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=512,
|
|
do_sample=(temperature > 0),
|
|
temperature=temperature,
|
|
top_p=top_p
|
|
)
|
|
|
|
answer = tokenizer.decode(outputs[0].cpu().tolist(), skip_special_tokens=True)
|
|
return answer
|
|
|
|
|
|
# ----------------------------------
|
|
# 3. Image Generation Support Code
|
|
# ----------------------------------
|
|
@torch.inference_mode()
|
|
def generate(
|
|
input_ids,
|
|
width,
|
|
height,
|
|
temperature=1.0,
|
|
parallel_size=5,
|
|
cfg_weight=5.0,
|
|
image_token_num_per_image=576,
|
|
patch_size=16
|
|
):
|
|
if torch.cuda.is_available():
|
|
torch.cuda.empty_cache()
|
|
|
|
# Expand input tokens for conditional & unconditional branches
|
|
tokens = torch.zeros((parallel_size * 2, len(input_ids)), dtype=torch.int).to(cuda_device)
|
|
for i in range(parallel_size * 2):
|
|
tokens[i, :] = input_ids
|
|
if i % 2 != 0:
|
|
tokens[i, 1:-1] = vl_chat_processor.pad_id
|
|
|
|
# Convert tokens to embeddings
|
|
inputs_embeds = vl_gpt.language_model.get_input_embeddings()(tokens)
|
|
generated_tokens = torch.zeros((parallel_size, image_token_num_per_image), dtype=torch.int).to(cuda_device)
|
|
|
|
pkv = None
|
|
for i in range(image_token_num_per_image):
|
|
outputs = vl_gpt.language_model.model(
|
|
inputs_embeds=inputs_embeds,
|
|
use_cache=True,
|
|
past_key_values=pkv
|
|
)
|
|
pkv = outputs.past_key_values
|
|
hidden_states = outputs.last_hidden_state
|
|
logits = vl_gpt.gen_head(hidden_states[:, -1, :])
|
|
logit_cond = logits[0::2, :]
|
|
logit_uncond = logits[1::2, :]
|
|
# Classifier-Free Guidance
|
|
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(dim=1), next_token.unsqueeze(dim=1)], dim=1).view(-1)
|
|
# Prepare the next image embeddings
|
|
img_embeds = vl_gpt.prepare_gen_img_embeds(next_token)
|
|
inputs_embeds = img_embeds.unsqueeze(dim=1)
|
|
|
|
# Decode the image tokens
|
|
patches = vl_gpt.gen_vision_model.decode_code(
|
|
generated_tokens.to(dtype=torch.int),
|
|
shape=[parallel_size, 8, width // patch_size, height // patch_size]
|
|
)
|
|
|
|
return generated_tokens.to(dtype=torch.int), patches
|
|
|
|
def unpack(decoded_patches, width, height, parallel_size=5):
|
|
decoded_patches = decoded_patches.to(torch.float32).cpu().numpy().transpose(0, 2, 3, 1)
|
|
decoded_patches = np.clip((decoded_patches + 1) / 2 * 255, 0, 255)
|
|
|
|
visual_img = np.zeros((parallel_size, width, height, 3), dtype=np.uint8)
|
|
visual_img[:, :, :] = decoded_patches
|
|
return visual_img
|
|
|
|
@torch.inference_mode()
|
|
def generate_image(prompt, seed=None, guidance=5):
|
|
if torch.cuda.is_available():
|
|
torch.cuda.empty_cache()
|
|
|
|
# Set the seed for reproducible results
|
|
if seed is not None:
|
|
torch.manual_seed(seed)
|
|
if torch.cuda.is_available():
|
|
torch.cuda.manual_seed(seed)
|
|
np.random.seed(seed)
|
|
|
|
width = 384
|
|
height = 384
|
|
parallel_size = 5
|
|
|
|
# Prepare input text (the model expects a conversation format)
|
|
messages = [{'role': 'User', 'content': prompt},
|
|
{'role': 'Assistant', 'content': ''}]
|
|
text = vl_chat_processor.apply_sft_template_for_multi_turn_prompts(
|
|
conversations=messages,
|
|
sft_format=vl_chat_processor.sft_format,
|
|
system_prompt=''
|
|
)
|
|
text += vl_chat_processor.image_start_tag
|
|
|
|
input_ids = torch.LongTensor(tokenizer.encode(text))
|
|
output, patches = generate(
|
|
input_ids,
|
|
width // 16 * 16,
|
|
height // 16 * 16,
|
|
cfg_weight=guidance,
|
|
parallel_size=parallel_size
|
|
)
|
|
|
|
images = unpack(patches, width // 16 * 16, height // 16 * 16)
|
|
pil_images = [
|
|
Image.fromarray(images[i]).resize((1024, 1024), Image.LANCZOS)
|
|
for i in range(parallel_size)
|
|
]
|
|
return pil_images
|
|
|
|
|
|
# ---------------------------
|
|
# 4. Build Streamlit Layout
|
|
# ---------------------------
|
|
def main():
|
|
st.title("Janus - Streamlit Demo")
|
|
|
|
# Create two tabs: one for Multimodal Understanding, one for Text-to-Image
|
|
tab1, tab2 = st.tabs(["Multimodal Understanding", "Text-to-Image Generation"])
|
|
|
|
# Sidebar for image upload and parameters
|
|
with st.sidebar:
|
|
st.header("Upload Image")
|
|
uploaded_image = st.file_uploader("Upload an image", type=["png", "jpg", "jpeg"])
|
|
|
|
st.header("Parameters")
|
|
|
|
# Multimodal Understanding Parameters
|
|
with st.expander("Multimodal Understanding Settings", expanded=True):
|
|
seed = st.number_input("Seed", min_value=0, value=42, step=1)
|
|
top_p = st.slider("top_p", min_value=0.0, max_value=1.0, value=0.95, step=0.05)
|
|
temperature = st.slider("temperature", min_value=0.0, max_value=1.0, value=0.1, step=0.05)
|
|
|
|
# Text-to-Image Parameters
|
|
with st.expander("Text-to-Image Settings", expanded=True):
|
|
seed_t2i = st.number_input("Seed (Optional)", min_value=0, value=12345, step=1)
|
|
cfg_weight = st.slider("CFG Weight", min_value=1.0, max_value=10.0, value=5.0, step=0.5)
|
|
|
|
# Main content area
|
|
with tab1:
|
|
st.subheader("Ask a question about your image")
|
|
if uploaded_image:
|
|
st.image(uploaded_image, use_column_width=True)
|
|
question = st.text_input("Question", value="Explain this meme...")
|
|
|
|
if st.button("Chat"):
|
|
if not uploaded_image:
|
|
st.warning("Please upload an image before chatting.")
|
|
else:
|
|
with st.spinner('Analyzing your image...'):
|
|
answer = multimodal_understanding(
|
|
image=uploaded_image,
|
|
question=question,
|
|
seed=seed,
|
|
top_p=top_p,
|
|
temperature=temperature
|
|
)
|
|
st.text_area("Response", value=answer, height=150)
|
|
|
|
with tab2:
|
|
st.subheader("Generate Images From Text")
|
|
prompt = st.text_area("Prompt", value="A cute baby fox in autumn leaves, digital art, cinematic lighting...")
|
|
|
|
if st.button("Generate Images"):
|
|
with st.spinner('Generating images... This may take a minute.'):
|
|
images = generate_image(prompt=prompt, seed=seed_t2i, guidance=cfg_weight)
|
|
st.write("Generated Images:")
|
|
cols = st.columns(2)
|
|
idx = 0
|
|
for i in range(2): # 2 rows
|
|
for j in range(2): # 2 cols
|
|
if idx < len(images):
|
|
with cols[j]:
|
|
st.image(images[idx], use_column_width=True)
|
|
idx += 1
|
|
|
|
# Tips / example prompts
|
|
with st.expander("Example Prompts"):
|
|
st.write("1. A cyberpunk samurai meditating in a neon-lit Japanese garden, cherry blossoms falling.")
|
|
st.write("2. A magical library with floating books, ethereal lighting, dust particles in the air, hyperrealistic detail.")
|
|
st.write("3. A steampunk-inspired coffee machine with brass gears and copper pipes, Victorian era style, morning light.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |