Module 4: Image Generation

4. Comparison: DALL-E 3 vs Stable Diffusion

Description

Choosing between DALL-E 3 and Stable Diffusion is not a question of "which is better" — it's an engineering decision based on context: budget, volume, level of control needed, iteration speed, and reproducibility requirements. This capsule gives you the tools to make that decision with data: a detailed comparison table, benchmark code that sends the same prompt to both, cost analysis by scenario, and a programmatic decision tree.

Why it matters: In production, it's common to use both models in a single system: DALL-E 3 as the main generator (quality) and Stable Diffusion as a fallback (cost, availability). Understanding their differences lets you design resilient architectures and optimize costs without sacrificing quality.


Detailed Comparison Table

Technical criteria

CriterionDALL-E 3 (OpenAI)Stable Diffusion (SDXL via Replicate)
Cost per image$0.04-0.12$0.002-0.02
Base qualityExcellent, consistentGood, variable depending on parameters
Prompt understandingSuperior (rewrites + interprets)Literal (what you write is what it generates)
Negative promptsNot supportedYes, highly effective
Parameter controlLimited (size, quality, style)Extensive (steps, cfg, seed, scheduler, etc.)
ReproducibilityLow (no exposed seed)High (seed = deterministic)
Speed10-25 seconds5-40 seconds (depends on model/steps)
Resolutions3 fixed (1024², 1792x1024, 1024x1792)Flexible (multiples of 64)
Text in imagesAcceptablePoor
Consistency across generationsHighMedium (requires seed)
APIOpenAI official, stableReplicate, Stability AI, multiple
Available modelsdall-e-3, dall-e-2SDXL, SD3, Flux, hundreds of variants
Native inpaintingOnly with dall-e-2Yes, specialized models
Open sourceNoYes
Works offline/localNoYes (with GPU)
Rate limitsStrict (per OpenAI tier)Based on credits/balance
Content policyStrict, rejects sensitive promptsMore permissive (depends on hosting)
Prompt rewritingYes (revised prompt)No

Business criteria

CriterionDALL-E 3Stable Diffusion
Initial setupMinimal (OpenAI API key)Low (Replicate API key)
Learning curveLow (few parameters)Medium (many parameters to optimize)
Cost scalabilityLinear, predictableLinear, much lower
Vendor lock-inHigh (OpenAI only)Low (multiple APIs, local execution)
Enterprise supportYes (OpenAI Enterprise)Limited (Stability AI, or self-hosted)
SLA / uptime99.9% (OpenAI)Variable (depends on the provider)
ComplianceSOC 2, data not used for trainingDepends on hosting (Replicate, self-hosted)

Benchmark: Same Prompt on Both Models

Benchmark setup

This code sends the same prompt to DALL-E 3 and Stable Diffusion, measures time and saves results for visual comparison:

import time
import json
import requests
from pathlib import Path
from openai import OpenAI

import replicate

client = OpenAI()

SDXL_MODEL = "stability-ai/sdxl:39ed52f2a40e4be0e682a3e7d0645ef75e93dd3bd23a9c5fe73e589d1a3adc3b"


def benchmark_dalle3(prompt: str, save_path: str) -> dict:
    start = time.time()
    response = client.images.generate(
        model="dall-e-3",
        prompt=prompt,
        size="1024x1024",
        quality="standard",
        style="vivid",
        n=1
    )
    elapsed = time.time() - start

    url = response.data[0].url
    img_data = requests.get(url, timeout=30).content
    Path(save_path).parent.mkdir(parents=True, exist_ok=True)
    Path(save_path).write_bytes(img_data)

    return {
        "model": "dall-e-3",
        "time_seconds": round(elapsed, 2),
        "cost_usd": 0.04,
        "revised_prompt": response.data[0].revised_prompt,
        "saved_to": save_path,
    }


def benchmark_sd(prompt: str, save_path: str) -> dict:
    start = time.time()
    output = replicate.run(
        SDXL_MODEL,
        input={
            "prompt": prompt,
            "negative_prompt": "blurry, low quality, distorted, deformed, ugly, watermark",
            "width": 1024,
            "height": 1024,
            "num_inference_steps": 25,
            "guidance_scale": 7.5,
        }
    )
    elapsed = time.time() - start

    url = output[0] if isinstance(output, list) else str(output)
    img_data = requests.get(url, timeout=30).content
    Path(save_path).parent.mkdir(parents=True, exist_ok=True)
    Path(save_path).write_bytes(img_data)

    return {
        "model": "sdxl",
        "time_seconds": round(elapsed, 2),
        "cost_usd": 0.005,
        "saved_to": save_path,
    }

Run the benchmark

def run_benchmark(prompts: list[str], output_dir: str = "generated/benchmark") -> list[dict]:
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    results = []

    for i, prompt in enumerate(prompts):
        print(f"\n--- Prompt {i+1}/{len(prompts)} ---")
        print(f"'{prompt[:80]}...'")

        dalle_result = benchmark_dalle3(prompt, f"{output_dir}/dalle_{i:02d}.png")
        print(f"  DALL-E 3: {dalle_result['time_seconds']}s, ${dalle_result['cost_usd']}")

        sd_result = benchmark_sd(prompt, f"{output_dir}/sd_{i:02d}.png")
        print(f"  SDXL:     {sd_result['time_seconds']}s, ${sd_result['cost_usd']}")

        results.append({
            "prompt": prompt,
            "dalle": dalle_result,
            "sd": sd_result,
            "time_ratio": round(dalle_result["time_seconds"] / max(sd_result["time_seconds"], 0.1), 2),
            "cost_ratio": round(dalle_result["cost_usd"] / max(sd_result["cost_usd"], 0.001), 1),
        })

    report_path = f"{output_dir}/benchmark_report.json"
    Path(report_path).write_text(json.dumps(results, indent=2, ensure_ascii=False))
    print(f"\nReport saved to: {report_path}")

    return results


benchmark_prompts = [
    "A professional headshot portrait with studio lighting, neutral background",
    "A colorful abstract painting with geometric shapes and bold colors",
    "A technical architecture diagram showing microservices with arrows",
    "A photorealistic landscape of mountains reflected in a lake at sunset",
    "A cute cartoon robot holding a book, children's illustration style",
]

results = run_benchmark(benchmark_prompts)

Cost Analysis by Scenario

Real scenarios

def cost_analysis(scenario: str, images_per_month: int, dalle_config: str = "standard") -> dict:
    dalle_prices = {
        "standard": 0.04,
        "hd": 0.08,
        "landscape_standard": 0.08,
        "landscape_hd": 0.12,
    }
    sd_price = 0.005

    dalle_cost = images_per_month * dalle_prices.get(dalle_config, 0.04)
    sd_cost = images_per_month * sd_price
    savings = dalle_cost - sd_cost
    savings_pct = (savings / dalle_cost) * 100 if dalle_cost > 0 else 0

    return {
        "scenario": scenario,
        "images_per_month": images_per_month,
        "dalle_monthly": round(dalle_cost, 2),
        "sd_monthly": round(sd_cost, 2),
        "monthly_savings": round(savings, 2),
        "savings_pct": round(savings_pct, 1),
        "annual_savings": round(savings * 12, 2),
    }

scenarios = [
    cost_analysis("Startup - Blog thumbnails", 100),
    cost_analysis("E-commerce - Product photos", 500, "hd"),
    cost_analysis("Marketing - Campaign creatives", 1000, "landscape_standard"),
    cost_analysis("Enterprise - Document diagrams", 5000),
    cost_analysis("Platform - User-generated content", 50000),
]

print(f"{'Scenario':<40} {'Imgs/mo':>10} {'DALL-E':>10} {'SD':>10} {'Savings':>10} {'Savings %':>10}")
print("-" * 90)
for s in scenarios:
    print(
        f"{s['scenario']:<40} {s['images_per_month']:>10,} "
        f"${s['dalle_monthly']:>8,.2f} ${s['sd_monthly']:>8,.2f} "
        f"${s['monthly_savings']:>8,.2f} {s['savings_pct']:>9.1f}%"
    )
    print(f"{'':>40} {'Annual savings:':<20} ${s['annual_savings']:>8,.2f}")

Break-even point: when is DALL-E's quality worth it?

FactorChoose DALL-E 3Choose Stable Diffusion
Volume< 1,000 imgs/mo> 1,000 imgs/mo
UseBrand/enterprise/marketingBatch, prototype, experimental
TeamNo expertise in SD tuningCan optimize parameters
ControlDoesn't need negative promptsNeeds reproducibility, seeds
VendorAlready uses OpenAI, a single APIPrefers provider independence
EditingDoesn't need advanced inpaintingNeeds ControlNet, inpainting

Programmatic Decision Tree

Simple version

def choose_generator(
    budget_per_image: float = 0.05,
    need_negative_prompt: bool = False,
    need_reproducibility: bool = False,
    images_per_month: int = 100,
    use_case: str = "general",
) -> str:
    if need_negative_prompt:
        return "sd"

    if need_reproducibility:
        return "sd"

    if budget_per_image < 0.02:
        return "sd"

    if images_per_month > 5000:
        return "sd"

    if use_case in ["brand", "product", "enterprise", "marketing"]:
        return "dalle"

    return "dalle"

Version with scoring

def choose_generator_scored(
    budget_per_image: float = 0.05,
    need_negative_prompt: bool = False,
    need_reproducibility: bool = False,
    need_inpainting: bool = False,
    images_per_month: int = 100,
    quality_priority: str = "high",
    use_case: str = "general",
) -> dict:
    dalle_score = 0
    sd_score = 0

    if budget_per_image >= 0.04:
        dalle_score += 2
    elif budget_per_image >= 0.02:
        dalle_score += 1
        sd_score += 1
    else:
        sd_score += 3

    if need_negative_prompt:
        sd_score += 3
    if need_reproducibility:
        sd_score += 2
    if need_inpainting:
        sd_score += 3

    if images_per_month > 5000:
        sd_score += 2
    elif images_per_month > 1000:
        sd_score += 1

    quality_scores = {"high": 2, "medium": 0, "low": -1}
    dalle_score += quality_scores.get(quality_priority, 0)

    use_case_dalle = {"brand", "product", "enterprise", "marketing", "editorial"}
    use_case_sd = {"prototype", "batch", "experimental", "inpainting", "gaming"}

    if use_case in use_case_dalle:
        dalle_score += 2
    elif use_case in use_case_sd:
        sd_score += 2

    recommendation = "dalle" if dalle_score > sd_score else "sd"
    confidence = abs(dalle_score - sd_score) / max(dalle_score + sd_score, 1)

    return {
        "recommendation": recommendation,
        "dalle_score": dalle_score,
        "sd_score": sd_score,
        "confidence": round(confidence, 2),
        "reasoning": (
            f"DALL-E: {dalle_score} pts, SD: {sd_score} pts. "
            f"{'High' if confidence > 0.3 else 'Low'} confidence."
        ),
    }

print(choose_generator_scored(
    budget_per_image=0.10,
    images_per_month=200,
    quality_priority="high",
    use_case="brand",
))

print(choose_generator_scored(
    budget_per_image=0.01,
    images_per_month=10000,
    need_negative_prompt=True,
    need_reproducibility=True,
    quality_priority="medium",
    use_case="batch",
))

Fallback System: DALL-E + Stable Diffusion

The most useful pattern in production: try DALL-E 3 first (better quality), and if it fails, fall back to Stable Diffusion automatically.

import time
from openai import BadRequestError, RateLimitError, APIError


def generate_image_with_fallback(
    prompt: str,
    save_path: str | None = None,
    prefer: str = "dalle",
) -> dict:
    generators = {
        "dalle": _try_dalle,
        "sd": _try_sd,
    }

    order = ["dalle", "sd"] if prefer == "dalle" else ["sd", "dalle"]

    for gen_name in order:
        result = generators[gen_name](prompt, save_path)
        if result["status"] == "success":
            result["generator_used"] = gen_name
            result["was_fallback"] = gen_name != order[0]
            return result
        print(f"  {gen_name} failed: {result.get('error', 'unknown')}")

    return {"status": "error", "error": "All generators failed", "prompt": prompt}


def _try_dalle(prompt: str, save_path: str | None) -> dict:
    try:
        response = client.images.generate(
            model="dall-e-3",
            prompt=prompt,
            size="1024x1024",
            quality="standard",
            n=1,
        )
        url = response.data[0].url
        result = {
            "status": "success",
            "url": url,
            "revised_prompt": response.data[0].revised_prompt,
        }
        if save_path:
            img_data = requests.get(url, timeout=30).content
            Path(save_path).parent.mkdir(parents=True, exist_ok=True)
            Path(save_path).write_bytes(img_data)
            result["saved_to"] = save_path
        return result
    except (BadRequestError, RateLimitError, APIError) as e:
        return {"status": "error", "error": str(e)}


def _try_sd(prompt: str, save_path: str | None) -> dict:
    try:
        output = replicate.run(
            SDXL_MODEL,
            input={
                "prompt": prompt,
                "negative_prompt": "blurry, low quality, distorted, deformed",
                "width": 1024,
                "height": 1024,
                "num_inference_steps": 25,
                "guidance_scale": 7.5,
            }
        )
        url = output[0] if isinstance(output, list) else str(output)
        result = {"status": "success", "url": url}
        if save_path:
            img_data = requests.get(url, timeout=60).content
            Path(save_path).parent.mkdir(parents=True, exist_ok=True)
            Path(save_path).write_bytes(img_data)
            result["saved_to"] = save_path
        return result
    except Exception as e:
        return {"status": "error", "error": str(e)}


result = generate_image_with_fallback(
    "A modern office space with natural lighting and green plants",
    save_path="generated/fallback_test.png"
)
print(f"Status: {result['status']}")
print(f"Generator: {result.get('generator_used')}")
print(f"Was fallback: {result.get('was_fallback')}")

Quality vs Cost: Visualization

def quality_cost_matrix() -> list[dict]:
    configs = [
        {"name": "DALL-E 3 HD Landscape", "cost": 0.120, "quality": 9.5, "generator": "dalle"},
        {"name": "DALL-E 3 HD Square", "cost": 0.080, "quality": 9.0, "generator": "dalle"},
        {"name": "DALL-E 3 Std Square", "cost": 0.040, "quality": 8.5, "generator": "dalle"},
        {"name": "SD3 (Stability)", "cost": 0.030, "quality": 8.0, "generator": "sd"},
        {"name": "SDXL (Replicate)", "cost": 0.005, "quality": 7.5, "generator": "sd"},
        {"name": "Flux Dev", "cost": 0.015, "quality": 8.5, "generator": "sd"},
        {"name": "Flux Schnell", "cost": 0.003, "quality": 7.0, "generator": "sd"},
        {"name": "DALL-E 2", "cost": 0.020, "quality": 6.0, "generator": "dalle"},
    ]

    configs.sort(key=lambda x: x["quality"] / max(x["cost"], 0.001), reverse=True)

    print(f"{'Configuration':<25} {'Cost':>8} {'Quality':>8} {'Quality/$':>10}")
    print("-" * 55)
    for c in configs:
        ratio = c["quality"] / c["cost"]
        bar = "█" * int(ratio / 50)
        print(f"{c['name']:<25} ${c['cost']:>6.3f} {c['quality']:>7.1f} {ratio:>9.0f} {bar}")

    return configs

quality_cost_matrix()

The quality/price ratio massively favors the SD/Flux models. DALL-E 3 wins on absolute quality, but with diminishing returns per dollar.


When to Use Each: Quick Guide

Choose DALL-E 3 when:

  • Visual quality directly impacts revenue (brand, premium e-commerce)
  • You don't have time to iterate on parameters (you need "generate and done")
  • You already use OpenAI and want a single bill/API
  • Volume is low-to-medium (< 1,000/mo) and the absolute cost is acceptable
  • You need the best semantic understanding of complex prompts

Choose Stable Diffusion when:

  • Volume is high (> 1,000/mo) and the savings justify the complexity
  • You need fine control (negative prompts, seeds, schedulers)
  • You need reproducibility (same seed = same image)
  • You have inpainting or advanced editing capabilities
  • You don't want vendor lock-in with OpenAI
  • You're experimenting and need to iterate fast without worrying about costs

Use both (fallback) when:

  • You're building a product where availability is critical
  • You want DALL-E quality for the normal flow, SD as a backup
  • Different features require different generators (brand → DALL-E, batch → SD)

Troubleshooting

ProblemWith DALL-E 3With Stable Diffusion
Blurry imageChange quality="hd"Raise steps to 30+, guidance to 8
Doesn't resemble the promptReview revised_prompt; be more explicitRaise guidance_scale; improve prompt
Deformed handsAdd "with correct hands" to the promptAdd "bad hands, extra fingers" to the negative
Illegible text in imageDALL-E 3 is limited with typographySD is worse; avoid text in images
Content policy rejectionRephrase; see capsule 2 (reprompting)Switch to SD, which is more permissive
Too slowNo speed control in DALL-EReduce steps; use Flux Schnell
Too expensiveReduce resolution; use standardAlready cheap; reduce steps if necessary
Inconsistent resultsExpected (no seed in DALL-E 3)Set a seed for reproducibility
Fallback doesn't workCheck error handling in _try_dalleCheck REPLICATE_API_TOKEN

Exercises

Exercise 1: Visual benchmark with automatic analysis

Implement a benchmark that sends 3 prompts to both generators, downloads the images, and generates a JSON report with times, costs and image paths for manual comparison.

See solution
import json
import time
from datetime import datetime
from pathlib import Path


def full_benchmark(prompts: list[str], output_dir: str = "generated/full_benchmark") -> dict:
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    results = []

    for i, prompt in enumerate(prompts):
        print(f"\n[{i+1}/{len(prompts)}] {prompt[:60]}...")
        entry = {"prompt": prompt, "index": i}

        start = time.time()
        try:
            dalle_response = client.images.generate(
                model="dall-e-3", prompt=prompt, size="1024x1024", quality="standard", n=1
            )
            dalle_time = time.time() - start
            dalle_url = dalle_response.data[0].url
            dalle_path = f"{output_dir}/dalle_{i:02d}.png"
            Path(dalle_path).write_bytes(requests.get(dalle_url, timeout=30).content)
            entry["dalle"] = {
                "time": round(dalle_time, 2),
                "cost": 0.04,
                "path": dalle_path,
                "revised_prompt": dalle_response.data[0].revised_prompt[:100],
            }
            print(f"  DALL-E 3: {dalle_time:.1f}s")
        except Exception as e:
            entry["dalle"] = {"error": str(e)}
            print(f"  DALL-E 3: ERROR - {e}")

        start = time.time()
        try:
            sd_output = replicate.run(
                SDXL_MODEL,
                input={
                    "prompt": prompt,
                    "negative_prompt": "blurry, low quality, distorted",
                    "width": 1024, "height": 1024,
                    "num_inference_steps": 25, "guidance_scale": 7.5,
                }
            )
            sd_time = time.time() - start
            sd_url = sd_output[0] if isinstance(sd_output, list) else str(sd_output)
            sd_path = f"{output_dir}/sd_{i:02d}.png"
            Path(sd_path).write_bytes(requests.get(sd_url, timeout=60).content)
            entry["sd"] = {"time": round(sd_time, 2), "cost": 0.005, "path": sd_path}
            print(f"  SDXL:     {sd_time:.1f}s")
        except Exception as e:
            entry["sd"] = {"error": str(e)}
            print(f"  SDXL: ERROR - {e}")

        results.append(entry)

    report = {
        "timestamp": datetime.now().isoformat(),
        "total_prompts": len(prompts),
        "results": results,
        "totals": {
            "dalle_cost": sum(r["dalle"].get("cost", 0) for r in results),
            "sd_cost": sum(r["sd"].get("cost", 0) for r in results),
            "dalle_avg_time": round(
                sum(r["dalle"].get("time", 0) for r in results) / len(results), 2
            ),
            "sd_avg_time": round(
                sum(r["sd"].get("time", 0) for r in results) / len(results), 2
            ),
        },
    }

    report_path = f"{output_dir}/report.json"
    Path(report_path).write_text(json.dumps(report, indent=2, ensure_ascii=False))
    print(f"\nReport: {report_path}")
    return report

full_benchmark([
    "A minimalist logo for a coffee shop, flat design, warm colors",
    "An aerial photograph of a coastal city at golden hour",
    "A watercolor illustration of a cat reading a book in a library",
])

Exercise 2: Interactive cost calculator

Create a function that takes a usage scenario (monthly volume, preferred configuration, percentage of HD images) and returns a cost comparison between DALL-E 3 and SD, including annual projection and recommendation.

See solution
def cost_calculator(
    monthly_volume: int,
    hd_percentage: float = 0.2,
    landscape_percentage: float = 0.3,
    dalle_config: str = "mixed",
) -> dict:
    standard_square = monthly_volume * (1 - hd_percentage) * (1 - landscape_percentage)
    hd_square = monthly_volume * hd_percentage * (1 - landscape_percentage)
    standard_landscape = monthly_volume * (1 - hd_percentage) * landscape_percentage
    hd_landscape = monthly_volume * hd_percentage * landscape_percentage

    dalle_monthly = (
        standard_square * 0.04 +
        hd_square * 0.08 +
        standard_landscape * 0.08 +
        hd_landscape * 0.12
    )

    sd_monthly = monthly_volume * 0.005

    recommendation = "sd" if monthly_volume > 500 and dalle_monthly > 30 else "dalle"
    if dalle_monthly < 10:
        recommendation = "dalle"

    result = {
        "monthly_volume": monthly_volume,
        "dalle": {
            "monthly": round(dalle_monthly, 2),
            "annual": round(dalle_monthly * 12, 2),
            "per_image_avg": round(dalle_monthly / max(monthly_volume, 1), 4),
        },
        "sd": {
            "monthly": round(sd_monthly, 2),
            "annual": round(sd_monthly * 12, 2),
            "per_image_avg": 0.005,
        },
        "savings_monthly": round(dalle_monthly - sd_monthly, 2),
        "savings_annual": round((dalle_monthly - sd_monthly) * 12, 2),
        "savings_pct": round((1 - sd_monthly / max(dalle_monthly, 0.01)) * 100, 1),
        "recommendation": recommendation,
    }

    print(f"=== Cost Analysis ({monthly_volume:,} imgs/mo) ===")
    print(f"\nDALL-E 3: ${result['dalle']['monthly']:,.2f}/mo (${result['dalle']['annual']:,.2f}/yr)")
    print(f"SD (SDXL): ${result['sd']['monthly']:,.2f}/mo (${result['sd']['annual']:,.2f}/yr)")
    print(f"\nSavings with SD: ${result['savings_monthly']:,.2f}/mo ({result['savings_pct']}%)")
    print(f"Annual savings: ${result['savings_annual']:,.2f}")
    print(f"\nRecommendation: {'DALL-E 3' if recommendation == 'dalle' else 'Stable Diffusion'}")

    return result

cost_calculator(100, hd_percentage=0.5)
cost_calculator(5000, hd_percentage=0.1, landscape_percentage=0.5)

Exercise 3: Fallback system with metrics

Extend the fallback system to record metrics: how many times each generator was used, how many fallbacks occurred, average time, and accumulated cost.

See solution
from dataclasses import dataclass, field

@dataclass
class GeneratorMetrics:
    dalle_calls: int = 0
    dalle_successes: int = 0
    dalle_failures: int = 0
    sd_calls: int = 0
    sd_successes: int = 0
    sd_failures: int = 0
    fallback_count: int = 0
    total_cost: float = 0.0
    dalle_times: list = field(default_factory=list)
    sd_times: list = field(default_factory=list)

    def record(self, generator: str, success: bool, time_s: float, cost: float, was_fallback: bool):
        if generator == "dalle":
            self.dalle_calls += 1
            if success:
                self.dalle_successes += 1
                self.dalle_times.append(time_s)
            else:
                self.dalle_failures += 1
        else:
            self.sd_calls += 1
            if success:
                self.sd_successes += 1
                self.sd_times.append(time_s)
            else:
                self.sd_failures += 1

        if was_fallback:
            self.fallback_count += 1
        if success:
            self.total_cost += cost

    def summary(self) -> dict:
        return {
            "dalle": {
                "calls": self.dalle_calls,
                "success_rate": round(self.dalle_successes / max(self.dalle_calls, 1) * 100, 1),
                "avg_time": round(sum(self.dalle_times) / max(len(self.dalle_times), 1), 2),
            },
            "sd": {
                "calls": self.sd_calls,
                "success_rate": round(self.sd_successes / max(self.sd_calls, 1) * 100, 1),
                "avg_time": round(sum(self.sd_times) / max(len(self.sd_times), 1), 2),
            },
            "fallback_rate": round(self.fallback_count / max(self.dalle_calls + self.sd_calls, 1) * 100, 1),
            "total_cost": round(self.total_cost, 4),
        }


metrics = GeneratorMetrics()


def generate_with_metrics(prompt: str, save_path: str | None = None) -> dict:
    start = time.time()
    try:
        response = client.images.generate(
            model="dall-e-3", prompt=prompt, size="1024x1024", quality="standard", n=1
        )
        elapsed = time.time() - start
        url = response.data[0].url
        if save_path:
            Path(save_path).parent.mkdir(parents=True, exist_ok=True)
            Path(save_path).write_bytes(requests.get(url, timeout=30).content)
        metrics.record("dalle", True, elapsed, 0.04, False)
        return {"status": "success", "generator": "dalle", "url": url, "time": round(elapsed, 2)}
    except Exception as e:
        elapsed = time.time() - start
        metrics.record("dalle", False, elapsed, 0, False)

    start = time.time()
    try:
        output = replicate.run(SDXL_MODEL, input={
            "prompt": prompt, "negative_prompt": "blurry, low quality",
            "width": 1024, "height": 1024, "num_inference_steps": 25,
        })
        elapsed = time.time() - start
        url = output[0] if isinstance(output, list) else str(output)
        if save_path:
            Path(save_path).parent.mkdir(parents=True, exist_ok=True)
            Path(save_path).write_bytes(requests.get(url, timeout=60).content)
        metrics.record("sd", True, elapsed, 0.005, True)
        return {"status": "success", "generator": "sd", "url": url, "time": round(elapsed, 2), "was_fallback": True}
    except Exception as e:
        elapsed = time.time() - start
        metrics.record("sd", False, elapsed, 0, True)
        return {"status": "error", "error": str(e)}


test_prompts = [
    "A futuristic car design concept, metallic blue",
    "A cozy winter cabin in the mountains with snow",
    "Abstract digital art with flowing neon colors",
]

for i, prompt in enumerate(test_prompts):
    result = generate_with_metrics(prompt, save_path=f"generated/metrics_{i}.png")
    print(f"[{i+1}] {result.get('generator', 'none')}: {result['status']}")

print("\n=== Metrics ===")
print(json.dumps(metrics.summary(), indent=2))

Additional Resources

  1. OpenAI Pricing — Up-to-date DALL-E prices
  2. Replicate Pricing — Pay-per-second-of-GPU model
  3. Stability AI Pricing — Prices for the official SD API
  4. OpenAI Rate Limits — Limits per tier
  5. Replicate SDXL — Model documentation on Replicate