Serverless GPU Workloads: RunPod and Modal Deployments in Practice
GPU compute is expensive, and keeping a cluster idling is a quick way to burn through a startup’s runway. As I moved from managing persistent Kubernetes clusters to more fluid, event-driven architectures, I realized that for most inference tasks and fine-tuning jobs, I didn't need a dedicated server. I needed an ephemeral execution environment that vanished the second the job finished.
RunPod and Modal have become my go-to tools for this. They solve the "cold start vs. cost" trade-off in different ways, and choosing between them usually comes down to whether you want infrastructure control or pure developer experience.
The Architectural Shift: RunPod vs. Modal
When I look at these two, I categorize them by their abstraction level.
RunPod feels like a high-octane layer on top of raw cloud instances. You get a "Pod" which is essentially a containerized GPU instance. It’s perfect when you need a persistent environment to debug, or when you have long-running batch jobs that need specific environment variables or custom storage mounts.
Modal, on the other hand, is closer to a function-as-a-service (FaaS) model. You write Python code, decorate your functions, and Modal handles the container orchestration, auto-scaling, and GPU provisioning. It’s incredibly fast for inference endpoints, but it’s more "opinionated" about how you structure your code.
Practical Implementation: Deploying a Whisper Model
I recently needed to spin up an audio transcription service. I chose Modal for this because the traffic was intermittent, and I didn't want to pay for a GPU to sit idle between requests.
Here is how I structured the deployment to ensure the model weights were cached and the GPU only spun up when the function was invoked:
import modal
# Define the image with necessary dependencies
# We use a slim image to keep build times low
image = (
modal.Image.debian_slim()
.pip_install("openai-whisper", "torch", "torchaudio")
)
app = modal.App("whisper-transcription")
@app.function(
image=image,
gpu="a10g", # Request a specific GPU type
timeout=600, # Set a hard timeout to prevent runaway costs
)
def transcribe(audio_bytes: bytes):
import whisper
import io
import torch
# Best practice: Load model once per container lifecycle
# Modal caches the container, so this runs only on cold starts
model = whisper.load_model("base", device="cuda")
# Convert bytes to a temp file or stream
audio_file = io.BytesIO(audio_bytes)
# Run inference
result = model.transcribe(audio_file)
return result["text"]
# Usage: modal deploy script.py
Operational Trade-offs
Cold Starts
Modal’s biggest hurdle is the cold start. If your model image is massive (e.g., Llama-3 70B), the time it takes to pull the container and load the weights into VRAM can be 30+ seconds. If you need sub-second latency, RunPod’s persistent pods are the better choice. I keep a RunPod instance running for high-frequency internal tools and offload the "bursty" public API traffic to Modal.
Storage and State
One mistake I see developers make is trying to store database state directly on these ephemeral workers. Don't. Always keep your state external (Redis, S3, or a managed Postgres). For model weights, use Modal’s NetworkFileSystem. It allows you to mount a persistent volume across different function invocations so you aren't downloading GBs of data every time a container starts.
Debugging Tips for Serverless GPUs
- Watch the logs, not the SSH: You can't SSH into a Modal function easily. Use
modal logs <app_id>to stream stdout. If the container crashes before the code starts, check the image build logs in the web dashboard. - GPU VRAM limits: If you get an OOM (Out of Memory) error, don't just jump to the most expensive H100. Often, you can optimize your inference with
bitsandbytes(4-bit quantization). I’ve saved thousands by squeezing models onto cheaper A10G or L4 GPUs instead of A100s. - The "Idle" trap: In RunPod, always set a "Stop" command or use the API to shut down pods when your queue is empty. I once left a 4x A6000 pod running for a weekend because I forgot to verify the auto-shutdown logic. Set up a simple health-check script that monitors your queue depth and triggers a shutdown via API if it hits zero for more than 15 minutes.
Choosing between these two isn't about which is "better"—it's about matching the tool to the lifecycle of your workload. If you are building a product where the compute cost is a variable expense tied to user activity, Modal is hard to beat. If you are building a platform or a persistent background service, RunPod gives you the visibility and control you'll eventually crave.
Aditya Shenvi
AI Engineer & Full-Stack Architect. Passionate about building intelligent systems, elegant UIs, and scaling web infrastructure. Open to exciting engineering opportunities in April 2026 and beyond.