Distributed speech-to-image pipeline
Live narration in, illustrated story panels out, fanned out across a fleet of GPU pods.

- Designer & builder
- Pure Billion Technologies
- Aug 2026 – present
TL;DR
- Live narration in, illustrated story panels out: someone tells a story and its characters and scenes are drawn as it unfolds.
- Each scene fans out across parallel GPU pods as competing candidates, rendered in about one job's time, with progress streamed live to the browser.
- Designed and built end to end: the Python FastAPI + Celery rewrite, the pod router, the live story pipeline and the AWS GPU deployment, with 115 tests.
- 3
- 4
- 115
How each GPU is wired
Three GPUs render in parallel
Progress comes back over the ComfyUI /ws websocket. The worker republishes it on Redis pub/sub, and the API streams it to the client over SSE.
event: progress data: {"jobId": "j2", "pct": 55}The problem
The product turns a spoken story into illustrations in real time. Audio streams in (Hindi is translated to English on the fly), a language model decides which characters exist and what each scene shows, and image models draw character portraits and multi-character scene panels that stay consistent from panel to panel.
The render tier is the hard part. ComfyUI workflows are heavy, long-running GPU jobs, nothing like a millisecond API call. The system needed one job per GPU at a time, several candidates per scene so the best one can be kept, progress people can actually watch, and a GPU fleet that can be switched on and off to control cost.
Designed and built it: the architecture, the migration from a NestJS/BullMQ stack to Python FastAPI + Celery, the queue router and workers, the live story pipeline, and the AWS deployment and operations scripts.
How it works
Stream the narration
The browser captures microphone audio with an AudioWorklet (16 kHz PCM) and streams it over a WebSocket to a FastAPI session; Sarvam's streaming API turns the speech, including Hindi, into English text.
An LLM directs each scene
An utterance buffer flushes complete thoughts to a scene director (OpenAI gpt-4o-mini or a self-hosted Qwen3 on vLLM) that registers new characters and decides when a scene is ready to draw.
The producer fans out
FastAPI writes job rows to PostgreSQL and dispatches Celery tasks: character portraits go to a single text-to-image queue, and each scene panel fans out as one candidate per image-to-image pod, each with its own seed and the characters' reference images.
An in-flight-aware router
A Redis-backed router counts in-flight jobs per pod and orders pods least-busy first, rotating ties, so back-to-back requests land on different GPUs and adding a pod adds a candidate automatically.
One job per GPU, never lost
Each Celery worker drives exactly one ComfyUI pod with concurrency 1, prefetch 1 and acks_late, so a crashed worker's job is redelivered rather than dropped. Reference images are deduplicated per pod by content hash, and results land in MinIO.
Progress you can watch
Workers publish progress on Redis pub/sub; the API streams it to the browser over SSE and WebSocket events, multiplexing every candidate in a batch into one stream.
A GPU fleet on AWS
A central EC2 instance runs the API and workers behind an ALB; four GPU instances on private subnets run only ComfyUI from a pinned AMI. Scripts start the fleet and warm every model, and on shutdown drain Celery before stopping the GPUs.
Key decisions
One queue per pod, behind an in-flight-aware router
A scene's candidates must land on different GPUs to render in parallel; per-pod queues make placement explicit, and the router keeps load even.
Concurrency 1, prefetch 1, acks_late
Heavy workflows compete for GPU memory, so one at a time keeps each job predictable; acknowledging late means a crash redelivers the job instead of losing it.
Fan candidates out in parallel
Three idle pods produce three options in about one job's wall-clock time, at three times the GPU cost, and the best one is kept.
Push progress over Redis pub/sub and SSE
People watch panels fill in live, and one batch stream carries every candidate.
Rewrite in FastAPI + Celery, keeping the schema
The existing PostgreSQL schema and storage layout were preserved, and the new stack used its own Redis database, so the cutover could run alongside the old one.
GPU hosts run only ComfyUI
GPU machines stay stateless, so the whole fleet can be started, warmed, drained and stopped by script to control cost.
Outcomes
- A live narration-to-illustration pipeline: speech in, character portraits and scene panels out.
- Scene candidates rendered in parallel across GPU pods in about one job's time.
- Production on AWS: four 96 GB RTX PRO 6000 GPU instances behind a central API and worker host, started, warmed and drained by script.
- 115 tests across the producer, the workers and the image-generation package.
Stack and links
Stack
- Python
- FastAPI
- Celery
- Redis
- PostgreSQL
- MinIO
- ComfyUI
- WebSockets
- AWS EC2 GPUs