n4nAI

Gemini 1.5 Pro vision latency: image size vs response time

Engineering analysis of how image resolution and file size affect Gemini 1.5 Pro vision latency, with practical resizing thresholds and code.

n4n Team4 min read793 words

Audio narration

Coming soon — every post will get a voice note here.

Most teams blame network transfer when Gemini 1.5 Pro vision latency image size feels slow, but the bottleneck sits inside the model’s visual encoder, not in bytes over the wire. The encoder tiles the image into fixed patches, and each patch becomes a constant number of tokens regardless of JPEG quality. That mechanism dictates every latency and cost decision downstream.

How Gemini 1.5 Pro tokenizes images

Gemini 1.5 Pro does not ingest raw pixels as a single blob. It divides the input image into a grid of square patches (reported patch sizes for similar multimodal transformers are in the 128–256 px range). Each patch maps to a fixed token budget. The total image tokens equal:

tiles_x = ceil(width / patch_size)
tiles_y = ceil(height / patch_size)
image_tokens = tiles_x * tiles_y * tokens_per_tile

Latency tracks image_tokens far more tightly than file size. A 4000×3000 PNG and the same scene saved as a 200 KB JPEG produce identical tile counts and therefore near-identical prefill time.

This is why naive compression tricks—dropping JPEG quality from 90 to 30—do nothing for responsiveness when dimensions are unchanged.

Measuring latency vs dimensions

To see the effect, isolate dimensions from compression. Use lossless PNG at several resolutions and time the round trip to the API. The OpenAI-compatible chat endpoint accepts base64 images, so the test harness is small:

import requests, time, base64

def probe(img_path, endpoint, api_key, model="gemini-1.5-pro"):
    with open(img_path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    payload = {
        "model": model,
        "messages": [{
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
                {"type": "text", "text": "Caption this image in one sentence."}
            ]
        }]
    }
    t0 = time.time()
    r = requests.post(f"{endpoint}/v1/chat/completions",
                      json=payload, headers={"Authorization": f"Bearer {api_key}"})
    dt = time.time() - t0
    return dt, r.json()["usage"]

Run this against a gateway or direct provider. When routing through n4n.ai’s OpenAI-compatible endpoint, the returned usage reflects the tile-derived token count, making it trivial to confirm savings after resizing.

File size vs pixel count: the decoupling

Consider a 24 MP photograph (6000×4000). Two encodings:

Encoding File size Dimensions Tiles (256 px) Relative latency
JPEG q90 8.2 MB 6000×4000 24×16 = 384 1.0× baseline
JPEG q20 0.9 MB 6000×4000 24×16 = 384 ~1.0× baseline
Resized 0.4 MB 1024×683 4×3 = 12 ~0.03× baseline

The byte count drops 9× via compression with zero latency change. Resizing to 1024 px on the long side cuts tiles 32× and produces the only meaningful speedup.

Engineers optimizing for throughput should stop profiling image bytes and start counting tiles.

Resizing thresholds that matter

Tile count grows with the product of axis multiples. Concrete numbers for a 256 px patch assumption:

  • 512×512 → 2×2 = 4 tiles
  • 1024×1024 → 4×4 = 16 tiles
  • 2048×2048 → 8×8 = 64 tiles
  • 4096×4096 → 16×16 = 256 tiles

Latency scales roughly linearly with these counts. The practical ceiling for most scene-understanding tasks is 1024–1536 px on the longest side. Beyond that, you pay tile multiplication for marginal semantic gain.

For document OCR or small-text extraction, keep the long side ≥ 2048 px; the encoder needs native resolution to preserve glyph edges.

Preprocessing pipeline

A minimal PIL routine that caps the longest side while preserving aspect ratio:

from PIL import Image

def fit_max(image: Image.Image, max_side: int = 1024) -> Image.Image:
    w, h = image.size
    if max(w, h) <= max_side:
        return image
    scale = max_side / max(w, h)
    return image.resize((int(w * scale), int(h * scale)), Image.LANCZOS)

# usage
img = Image.open("scan.jpg")
img = fit_max(img, 1536)
img.save("scan_1536.png")  # lossless to avoid confounding variables

This converts a 6000×4000 scan to 1536×1024 (6×4 = 24 tiles) from 384—a 16× tile reduction.

Tradeoffs: when not to shrink

Resizing is not free. Downsampling destroys high-frequency detail. Three cases where you should not blindly cap size:

  1. Dense textOCR on legal documents degrades sharply below 2048 px long side.
  2. Fine anomaly detection – manufacturing defects measured in single pixels vanish under LANCZOS.
  3. Facial recognition / biometric cues – unless you have explicit consent and need, avoid sending full-res altogether for privacy, not latency.

If the task demands detail, split the image into overlapping tiles at native resolution and query each tile. That keeps tile count manageable per call while preserving information.

Caching and routing directives

Repeated calls with the same image should use provider cache-control hints. Gemini supports cached content for unchanged prefixes; an inference gateway that honors client routing directives will forward your cache_control block and bill only the delta. This compounds with resizing: fewer tiles cached means cheaper storage and faster warm hits.

When you resize once upstream and cache the resized tensor (or base64), subsequent requests skip the encoder prefill entirely.

Decision rules for production

Engineers building multimodal features should adopt a fixed policy:

  • Default: Resize all user images to max_side=1024 unless the task tag says document or detail.
  • Document: Resize to max_side=2048, accept 4× tile cost.
  • Batch: Never compress for latency; log tile count from usage and alert if > 64 tiles per image.
  • Verification: Use per-token metering (e.g., via a gateway that returns exact image token counts) to confirm your preprocessor changed the billed tokens.

Takeaway

Gemini 1.5 Pro vision latency image size is governed by patch tiles, not kilobytes. Compressing JPEGs is a false optimization; resizing dimensions is the only lever that moves prefill time. Cap the long side at 1024 px for general tasks, 2048 px for text, and measure tile counts from real usage responses to keep your pipeline honest. Do that, and vision latency becomes a predictable linear function of your resize policy.

Tagsgemini-1-5-provision-modellatency-benchmarkmultimodal

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All multimodal and vision latency benchmarks posts →