Google Cloud Run with GPUs vs dedicated serverless GPU providers for spiky LLM traffic
Platform as a Service (PaaS)

Google Cloud Run with GPUs vs dedicated serverless GPU providers for spiky LLM traffic

10 min read

Most teams only realize their GPU strategy is wrong the first time a prompt goes viral and their LLM service either blows the budget or falls over. If you’re running on Google Cloud already, Cloud Run with GPUs looks like the obvious place to start—but for truly spiky LLM traffic, dedicated serverless GPU providers behave very differently in practice.

Quick Answer: Cloud Run with GPUs is great if you’re already deep in GCP and your LLM traffic is relatively steady, but it gets painful when you need fast autoscaling, frequent model changes, and strict latency SLAs. Dedicated serverless GPU platforms (like Modal) are built for exactly this: they give you sub‑second cold starts, elastic GPU capacity across clouds, and Python-first control over scaling logic—without you juggling quotas, regions, or cluster warm pools.

Why This Matters

LLM workloads don’t look like traditional web apps. You get bursty eval traffic, unpredictable agent fan‑out, and latency-sensitive user flows that can’t wait 30–90 seconds for a GPU to boot. If you design around average load, you blow up user experience on spikes; if you design around peak, you pay for idle GPUs all day.

Choosing between Cloud Run with GPUs and a dedicated serverless GPU provider is really choosing how you want to pay for this: in complexity, latency, or dollars. The right fit can:

Key Benefits:

  • Hit latency targets under spiky load: Avoid 30–60 second cold starts and keep per-request latency dominated by the model, not your infrastructure.
  • Scale up and down with your evals and agents: Handle 10×–100× bursts (eval suites, batch jobs, MCP tools) without pre-warming clusters or overprovisioning.
  • Ship faster with code-defined infra: Express GPUs, images, scaling, and endpoints in code, iterate quickly, and keep infra changes traceable alongside your app logic.

Core Concepts & Key Points

ConceptDefinitionWhy it's important
Cold start behaviorThe time from “no capacity” to “first token” for a new container or GPUFor LLMs, cold start usually dwarfs model compute; sub‑second cold starts fundamentally change what you can promise in latency SLAs.
Autoscaling modelHow the platform decides when and how many replicas to spin up or downSpiky LLM traffic (agents, evals) needs fast, fine-grained scaling to thousands of replicas without warm pools or manual tuning.
Capacity & quotasHow GPU capacity is provisioned, limited, and spread across regionsLLM teams quickly hit quotas on single clouds; multi‑cloud capacity pools and intelligent scheduling reduce “GPU not available” incidents.

How It Works (Step-by-Step)

Let’s walk through what actually happens when you run spiky LLM traffic on each option.

1. Environment & Image Definition

On Cloud Run with GPUs

You typically:

  1. Write a Dockerfile with:
    • Base image (NVIDIA + CUDA + Python)
    • System packages
    • Python dependencies (PyTorch, vLLM, your stack)
  2. Build and push to Artifact Registry.
  3. Wire this image into a Cloud Run service with GPU type, memory, and max instances.

Every environment change means rebuilding/pushing the image and redeploying the service.

On a dedicated serverless GPU provider (Modal)

You define your environment in Python:

import modal

image = (
    modal.Image.debian_slim()
    .pip_install(
        "torch==2.2.2",
        "vllm==0.4.2",
        "transformers==4.40.0",
    )
)

app = modal.App("llm-server")

@app.cls(image=image, gpu="A100:1", concurrency_limit=4)
class LLMServer:
    @modal.enter()
    def load_model(self):
        from vllm import LLM
        self.llm = LLM("meta-llama/Meta-Llama-3-70B-Instruct")

    @modal.method()
    def generate(self, prompt: str) -> str:
        return self.llm.generate(prompt, max_tokens=256)[0].outputs[0].text

Then:

modal deploy llm_server.py

Change dependencies? Update the Python code, let Modal rebuild the Image, and redeploy—no manual Docker plumbing.

2. Cold Starts & Model Initialization

For LLMs, the dominant cost isn’t starting the container; it’s loading tens of GB of weights from storage into GPU RAM and then into the SMs. You want to do that once per container, and reuse that container aggressively.

Cloud Run with GPUs:

  • Containers boot on demand based on HTTP traffic.
  • GPU initialization + model loading typically happens on each new instance.
  • Cold starts on GPUs often run in the tens of seconds range, especially if:
    • You’re pulling large images over the network.
    • You’re streaming weights from remote storage.
  • Mitigation usually means:
    • Keeping a floor of “always on” instances.
    • Accepting higher baseline cost to avoid cold-path latency.

Dedicated serverless GPU (Modal):

  • Designed to keep LLM servers hot with sub‑second cold starts on the service level.
  • Patterns Modal encourages:
    • Load the model once per container in @modal.enter().
    • Serve many requests per container via @modal.method() or an HTTP endpoint.
  • Modal’s runtime is optimized for fast container launch and model loading:
    • Images are cached and started via its own “AI-native runtime,” which benchmarks “100x faster than Docker” startup for typical workloads.
    • Storage is colocated with compute and optimized for high throughput model loading.

Example: expose the same class as a web endpoint:

from fastapi import FastAPI
from pydantic import BaseModel

web_app = FastAPI()

class Prompt(BaseModel):
    prompt: str

@app.function()
@modal.asgi_app()
def fastapi_app():
    server = LLMServer()

    @web_app.post("/generate")
    async def generate(prompt: Prompt):
        return {"text": await server.generate.remote(prompt.prompt)}

    return web_app

From a latency perspective: your per-request budget shrinks to “LLM compute + a few ms of routing overhead,” instead of “LLM compute + 30s of GPU cold start.”

3. Autoscaling for Spiky LLM Traffic

The interesting case isn’t a steady 1 rps—it’s:

  • Evals: 100k prompts fired at once.
  • Batch: nightly document ingestion with 1k+ files.
  • Agents: every user request fans out to multiple tools and LLM calls.

Cloud Run with GPUs:

  • Can scale to many instances, but:
    • Each new instance has its cold start + model load.
    • Scaling behavior is opaque; you typically tune concurrency per instance and max instances, then hope the autoscaler behaves.
  • For large spikes, you often:
    • Pre‑warm capacity by setting minimum instances > 0.
    • Overprovision to cover peaks.
  • Debugging under‑ or over‑scaling:
    • Look at Cloud Run metrics in Cloud Monitoring.
    • Tweak settings, redeploy, retest.

Dedicated serverless GPU (Modal):

  • Autoscaling is the default assumption:
    • Functions scale from zero to thousands of concurrent containers.
    • You control concurrency at the function or class level (concurrency_limit=).
  • Pattern for spiky workloads:
@app.function(gpu="A100:1", concurrency_limit=2, timeout=600)
def run_eval(prompt: str) -> str:
    return LLMServer().generate.remote(prompt)

Fan out over thousands of prompts:

prompts = [f"Eval prompt {i}" for i in range(100_000)]
results = list(run_eval.map(prompts))

Modal handles:

  • Rapid scaling up to meet load.
  • Scheduling across a multi-cloud GPU pool (H100, A100, A10G, etc.) so you’re not bottlenecked by a single-region quota.
  • Scaling back to zero when done—no idle GPU cost.

For long-running or queued workloads, you can push work with .spawn() and later FunctionCall.get(), or schedule via modal.Cron.

4. Capacity, Quotas, and Multi-Cloud

Cloud Run with GPUs:

  • Bound by your GCP project’s GPU quotas:
    • Per-region GPU quotas per accelerator type (A100, L4, etc).
    • You request quota increases and wait.
  • If you hit a sudden spike:
    • Autoscaler can’t scale beyond quota.
    • You see throttling or requests waiting for capacity.
  • You’re also tied to GCP’s specific GPU SKUs and region availability.

Dedicated serverless GPU (Modal):

  • Uses a multi-cloud capacity pool:
    • “Access to thousands of GPUs across clouds.”
    • Intelligent scheduling routes work to where GPUs are available.
  • You don’t manage:
    • Quotas for each cloud.
    • Regional GPU scarcity.
  • You do control:
    • GPU type per function ("A10G", "A100:2", "H100").
    • Data residency and compliance requirements; Modal exposes explicit controls for this.

For spiky LLM traffic, this changes the conversation from “will this region’s quota hold up?” to “what GPU profile should I use for this workload?”

5. Networking, Security, and Observability

Cloud Run with GPUs:

  • Networking:
    • You get standard Cloud Run HTTPS endpoints.
    • Private ingress is possible in a VPC.
  • Security:
    • IAM roles and service accounts.
  • Observability:
    • Logs in Cloud Logging.
    • Metrics in Cloud Monitoring, but you assemble your own dashboards.

Dedicated serverless GPU (Modal):

  • Networking:
    • Expose functions via @modal.fastapi_endpoint, @modal.asgi_app, @modal.web_server.
    • Use Tunnels to connect local development to Modal.
  • Security:
    • Containers run under gVisor-based isolation.
    • SOC2 & HIPAA.
    • Proxy Auth Tokens (requires_proxy_auth=True) to protect endpoints.
    • Team controls and data residency controls for governance.
  • Observability:
    • Integrated logging and traces per function/container in the Modal apps page.
    • Function-level stats (latency, errors, concurrency) without extra config.

This matters when you’re debugging timeouts or weird throughput effects at 2 a.m.—you want to see function-level traces and logs where you write the code, not across three consoles.

Common Mistakes to Avoid

  • Treating LLMs like vanilla web apps: If you ignore cold-start and model load behavior, you’ll discover your “fast” Cloud Run service takes 30+ seconds on the real cold path. Always profile first-token latency under cold conditions.
  • Underestimating quota and burst needs: Teams often prototype on GPUs with a single region quota, then hit a wall when evals or agents start fanning out. Plan for burst capacity across GPU types and clouds, not just steady state.

Real-World Example

Imagine you’re shipping an LLM-powered code assistant:

  • Traffic pattern:
    • 0–10 rps most of the time.
    • 200+ rps during product launches or large customer onboardings.
    • Weekly eval runs that slam the model with 100k prompts in a few minutes.
  • Requirements:
    • P95 latency under 1–2 seconds at the application level.
    • No “first request takes 30 seconds” behavior during a demo.

On Cloud Run with GPUs, you might:

  • Build a vLLM server Docker image.
  • Deploy a Cloud Run service with 2–4 A100 instances minimum to keep things warm.
  • Set max instances high enough for spikes, subject to GPU quotas.
  • Accept that you’re:
    • Paying for those 2–4 GPUs 24/7.
    • Still exposed to cold-start latency when scaling beyond the warm pool.
    • Doing manual quota management and capacity planning.

On Modal, you instead:

  • Define a LLMServer class with @app.cls, @modal.enter to load the model once per container, and a generate method.
  • Expose it as a FastAPI endpoint with @modal.asgi_app.
  • For evals, run your eval harness with:
@app.function(gpu="A100:1", timeout=900)
def eval_prompt(prompt: str):
    return LLMServer().generate.remote(prompt)

def run_all_evals(prompts: list[str]):
    # Fan out across thousands of containers
    for result in eval_prompt.map(prompts):
        process_result(result)

During a spike:

  • Modal rapidly scales to thousands of containers, routing across its multi-cloud GPU pool.
  • Your P95 latency remains dominated by LLM compute, not cold starts.
  • When traffic drops, everything scales back to zero. You’re not sitting on idle GPUs.

Pro Tip: For LLM workloads, always implement a stateful server pattern (class with @modal.enter to load weights once) instead of loading the model inside a stateless function. This single change is often the difference between “LLM infra feels slow and expensive” and “LLM infra feels like calling a local function.”

Summary

For spiky LLM traffic, the tradeoff is simple:

  • Cloud Run with GPUs is a good fit if:

    • You’re already all‑in on GCP.
    • Your LLM traffic is relatively steady, or you can afford warm pools and idle GPU cost.
    • You’re comfortable managing quotas, Docker images, and slower cold starts.
  • Dedicated serverless GPU providers like Modal are a better fit if:

    • You need sub‑second cold-start behavior at the service level.
    • Your workloads are bursty: evals, batch fan‑out, agents, MCP tools.
    • You want to express infra as Python—Images, GPUs, scaling, endpoints—then let a multi-cloud capacity pool handle the rest.

In practice, most LLM teams end up needing elastic GPU scaling, fast iteration, and predictable latency more than they need “everything in one cloud console.” That’s where a serverless GPU platform, built specifically around LLM use cases, tends to win.

Next Step

Get Started