JP Last sync: 2026-01-15

Chapter 3: Multimodal Generation

Text-to-Image, Video Understanding, and Any-to-Any Models

Reading Time: 35-40 min Code Examples: 10 Exercises: 4

3.1 Text-to-Image Generation

Text-to-image models generate images from natural language descriptions. This capability has transformed creative workflows and democratized visual content creation.

Leading Text-to-Image Models (2025)

Model Strengths Limitations Best For
DALL-E 3 Prompt accuracy, text rendering Less artistic stylization Marketing, product design
Midjourney v6 Artistic quality, aesthetics Weaker text in images Concept art, creative work
Stable Diffusion XL Open-source, customizable Steeper learning curve Custom fine-tuning, research
Janus-Pro Unified model, MIT license Newer, less ecosystem Open-source projects

Diffusion Models: The Foundation

Modern text-to-image models use diffusion, a process that learns to reverse the gradual addition of noise to images:

graph LR subgraph Forward["Forward Process (Training)"] X0[Clean Image x₀] --> |Add Noise| X1[x₁] X1 --> |Add Noise| X2[x₂] X2 --> |...| XT[Pure Noise xₜ] end subgraph Reverse["Reverse Process (Generation)"] NT[Random Noise] --> |Denoise| N1[Partially Denoised] N1 --> |Denoise| N2[...] N2 --> |Denoise| IMG[Generated Image] end TXT[Text Prompt] --> |Conditioning| N1 style TXT fill:#9b59b6,color:white

Diffusion Process

Forward: Gradually add Gaussian noise to training images

$$q(x_t | x_{t-1}) = \mathcal{N}(x_t; \sqrt{1-\beta_t}x_{t-1}, \beta_t \mathbf{I})$$

Reverse: Learn to predict and remove noise, conditioned on text

$$p_\theta(x_{t-1} | x_t, c) = \mathcal{N}(x_{t-1}; \mu_\theta(x_t, t, c), \Sigma_\theta(x_t, t, c))$$

Latent Diffusion (Stable Diffusion Architecture)

graph TB subgraph Encoding IMG1[Image 512x512] --> VAE_E[VAE Encoder] VAE_E --> LAT[Latent 64x64x4] end subgraph Diffusion["Diffusion in Latent Space"] LAT --> UNET[U-Net Denoiser] TXT2[Text Prompt] --> CLIP2[CLIP Text Encoder] CLIP2 --> COND[Text Conditioning] COND --> UNET UNET --> LAT2[Denoised Latent] end subgraph Decoding LAT2 --> VAE_D[VAE Decoder] VAE_D --> OUT[Generated Image] end style UNET fill:#9b59b6,color:white style COND fill:#e3f2fd

Generating Images with Stable Diffusion

# Text-to-Image with Stable Diffusion
from diffusers import StableDiffusionPipeline
import torch

# Load model
pipe = StableDiffusionPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    torch_dtype=torch.float16,
    variant="fp16"
)
pipe = pipe.to("cuda")

# Generate image
prompt = "A serene Japanese garden with cherry blossoms, koi pond, \
          traditional wooden bridge, morning mist, photorealistic, 8k"

negative_prompt = "blurry, low quality, distorted, ugly"

image = pipe(
    prompt=prompt,
    negative_prompt=negative_prompt,
    num_inference_steps=50,
    guidance_scale=7.5,
    height=1024,
    width=1024
).images[0]

image.save("japanese_garden.png")

Using DALL-E 3 API

# DALL-E 3 via OpenAI API
import openai
from PIL import Image
import requests
from io import BytesIO

client = openai.OpenAI(api_key="your-api-key")

response = client.images.generate(
    model="dall-e-3",
    prompt="A futuristic Tokyo cityscape at night with flying cars, \
            neon signs in Japanese, cyberpunk aesthetic",
    size="1024x1024",
    quality="hd",
    n=1
)

# Download and display image
image_url = response.data[0].url
image_response = requests.get(image_url)
image = Image.open(BytesIO(image_response.content))
image.save("cyberpunk_tokyo.png")

# Get the revised prompt (DALL-E 3 may modify your prompt)
print(f"Revised prompt: {response.data[0].revised_prompt}")

3.2 Video Understanding Models

Video understanding extends image understanding with temporal reasoning - understanding how scenes change over time.

Challenges in Video Understanding

Video Tokenization Strategies

graph TB subgraph Sparse["Sparse Sampling"] V1[Video] --> S1[Sample every Nth frame] S1 --> F1[Frame 1, 5, 10, ...] end subgraph Dense["Dense + Temporal"] V2[Video] --> S2[All frames] S2 --> TE[Temporal Encoder] TE --> TT[Temporal Tokens] end subgraph Hierarchical["Hierarchical"] V3[Video] --> CL[Clip-level features] CL --> VL[Video-level aggregation] end style TE fill:#9b59b6,color:white
# Video Understanding with Temporal Tokens
import torch
import torch.nn as nn
from transformers import CLIPProcessor, CLIPModel

class SimpleVideoEncoder(nn.Module):
    """Encode video by processing frames and adding temporal information"""

    def __init__(self, clip_model_name="openai/clip-vit-base-patch32"):
        super().__init__()
        self.clip = CLIPModel.from_pretrained(clip_model_name)
        self.clip_processor = CLIPProcessor.from_pretrained(clip_model_name)

        # Learnable temporal position embeddings
        self.temporal_embed = nn.Embedding(100, 512)  # Up to 100 frames

        # Temporal attention
        self.temporal_attn = nn.TransformerEncoder(
            nn.TransformerEncoderLayer(d_model=512, nhead=8, batch_first=True),
            num_layers=2
        )

    def forward(self, frames):
        """
        frames: list of PIL Images or tensor (B, T, C, H, W)
        """
        B, T = len(frames), len(frames[0])

        # Extract frame features with CLIP
        frame_features = []
        for batch in frames:
            inputs = self.clip_processor(images=batch, return_tensors="pt")
            with torch.no_grad():
                features = self.clip.get_image_features(**inputs)
            frame_features.append(features)

        # Stack: (B, T, D)
        frame_features = torch.stack(frame_features)

        # Add temporal position embeddings
        positions = torch.arange(T).unsqueeze(0).expand(B, -1)
        temporal_pos = self.temporal_embed(positions)
        frame_features = frame_features + temporal_pos

        # Apply temporal attention
        video_features = self.temporal_attn(frame_features)

        # Pool to video-level representation
        video_embedding = video_features.mean(dim=1)

        return video_embedding, video_features

State-of-the-Art: Molmo 2 (2025)

Molmo 2 from AI2 represents the current frontier in video understanding:

3.3 Any-to-Any Multimodal Models

"Any-to-Any" models can accept multiple input modalities and generate multiple output modalities:

graph TB subgraph Inputs TI[Text] II[Image] AI[Audio] VI[Video] end subgraph Model["Any-to-Any Model"] UT[Unified Tokenizer] TR[Transformer] DT[Decoder/Generator] end subgraph Outputs TO[Text] IO[Image] AO[Audio] end TI --> UT II --> UT AI --> UT VI --> UT UT --> TR TR --> DT DT --> TO DT --> IO DT --> AO style TR fill:#9b59b6,color:white

GPT-4V/4o Capabilities

# GPT-4o: Multimodal Reasoning
import openai
import base64

client = openai.OpenAI()

def encode_image_to_base64(image_path):
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode('utf-8')

# Analyze multiple images together
def compare_images(image_paths, question):
    content = [{"type": "text", "text": question}]

    for path in image_paths:
        content.append({
            "type": "image_url",
            "image_url": {
                "url": f"data:image/jpeg;base64,{encode_image_to_base64(path)}"
            }
        })

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": content}],
        max_tokens=500
    )

    return response.choices[0].message.content

# Example: Compare before/after images
result = compare_images(
    ["room_before.jpg", "room_after.jpg"],
    "What changes were made to this room? List specific differences."
)
print(result)

DeepSeek Janus-Pro: Open-Source Unified Model

Janus-Pro (January 2025) is a breakthrough open-source model with unified understanding and generation:

Janus-Pro Architecture

# Using Janus-Pro for Understanding and Generation
from transformers import AutoModelForCausalLM, AutoProcessor
import torch

# Load Janus-Pro
model = AutoModelForCausalLM.from_pretrained(
    "deepseek-ai/Janus-Pro-7B",
    torch_dtype=torch.bfloat16,
    device_map="auto",
    trust_remote_code=True
)
processor = AutoProcessor.from_pretrained(
    "deepseek-ai/Janus-Pro-7B",
    trust_remote_code=True
)

# Image Understanding
def understand_image(image_path, question):
    from PIL import Image
    image = Image.open(image_path)

    conversation = [
        {"role": "user", "content": [
            {"type": "image", "image": image},
            {"type": "text", "text": question}
        ]}
    ]

    inputs = processor(conversation, return_tensors="pt").to(model.device)
    outputs = model.generate(**inputs, max_new_tokens=256)
    return processor.decode(outputs[0], skip_special_tokens=True)

# Image Generation
def generate_image(prompt):
    inputs = processor(
        text=prompt,
        return_tensors="pt",
        mode="generation"
    ).to(model.device)

    outputs = model.generate(**inputs, max_new_tokens=1024)
    image = processor.decode_image(outputs)
    return image

# Example usage
answer = understand_image("chart.png", "What trend does this chart show?")
generated = generate_image("A majestic mountain landscape at sunset")

3.4 Audio-Visual Models

Audio-visual models integrate speech, sound, and visual information:

Applications

# Audio-Visual Processing Example
import torch
import torchaudio
from transformers import Wav2Vec2Processor, Wav2Vec2Model

class AudioVisualEncoder(nn.Module):
    """Combine audio and visual features"""

    def __init__(self, visual_dim=512, audio_dim=768, hidden_dim=512):
        super().__init__()

        # Audio encoder (Wav2Vec2)
        self.audio_processor = Wav2Vec2Processor.from_pretrained(
            "facebook/wav2vec2-base"
        )
        self.audio_encoder = Wav2Vec2Model.from_pretrained(
            "facebook/wav2vec2-base"
        )

        # Projection layers
        self.visual_proj = nn.Linear(visual_dim, hidden_dim)
        self.audio_proj = nn.Linear(audio_dim, hidden_dim)

        # Cross-modal attention
        self.cross_attn = nn.MultiheadAttention(hidden_dim, num_heads=8)

        # Fusion
        self.fusion = nn.Linear(hidden_dim * 2, hidden_dim)

    def forward(self, visual_features, audio_waveform, sample_rate=16000):
        # Process audio
        audio_inputs = self.audio_processor(
            audio_waveform,
            sampling_rate=sample_rate,
            return_tensors="pt"
        )
        audio_features = self.audio_encoder(**audio_inputs).last_hidden_state

        # Project to common space
        visual_proj = self.visual_proj(visual_features)
        audio_proj = self.audio_proj(audio_features)

        # Cross-modal attention (audio queries visual)
        attended, _ = self.cross_attn(
            audio_proj.transpose(0, 1),
            visual_proj.transpose(0, 1),
            visual_proj.transpose(0, 1)
        )
        attended = attended.transpose(0, 1)

        # Fuse modalities
        combined = torch.cat([audio_proj, attended], dim=-1)
        fused = self.fusion(combined)

        return fused

3.5 Practical Considerations

Choosing the Right Model

Task Recommended Model Reasoning
Marketing visuals DALL-E 3 Best prompt adherence, text rendering
Artistic concepts Midjourney Superior aesthetic quality
Custom training Stable Diffusion Open-source, LoRA support
Visual conversation GPT-4V/Claude 3 Best reasoning capabilities
Open-source unified Janus-Pro MIT license, competitive quality

Cost Considerations

Image generation costs vary significantly:

3.6 Summary

Chapter 3 Key Takeaways

Exercises

Exercise 1: Prompt Engineering for Images

Generate the same concept with DALL-E and Stable Diffusion. Compare results and analyze how prompt modifications affect output quality.

Exercise 2: Video Frame Analysis

Extract keyframes from a video and use GPT-4V to describe what happens. Then implement temporal averaging to create a video summary.

Exercise 3: Model Comparison

For a visual QA task, compare responses from GPT-4V, Claude 3, and Janus-Pro. Analyze strengths and weaknesses of each.

Exercise 4: Cost Optimization

Design a system that routes image generation requests to different models based on quality requirements and budget constraints.