Automated bulk image generation with Z-Image-Turbo on gcube
on this page
When you use an image generation API, costs climb alarmingly as the image count grows. Generate a few hundred images for a training dataset or a few dozen for design drafts, and sooner or later you’ll ask yourself, “Wouldn’t it be better to just rent a GPU and run this myself?”
In this post, we deploy the open-source image generation model Z-Image-Turbo as a gcube workload and run it as a service that generates images in bulk from a list of prompts and lets you monitor everything from a web UI. We cover everything from connecting storage to using the UI, ready to use right away.
Environment — Model: Z-Image-Turbo uint4 quantized (
Disty0/Z-Image-Turbo-SDNQ-uint4-svd-r32) · Container: image about 14 GB (model built in), port 8000, Minimum CUDA Version 12.8 · Tested GPUs: RTX 5090 (Tier 2), RTX 5060 (Tier 3, 8 GB) · Default generation settings: 768×576, 8 steps
What is Z-Image-Turbo?
Z-Image-Turbo is a lightweight image generation model that produces images in just a few steps (8 steps). For this deployment we use the uint4 (4-bit) quantized version. The key point is that the model’s memory footprint shrinks enough to run on an 8 GB consumer GPU.
The container we deploy is set up like this:
- A single FastAPI app serves both the image generation REST API and the web UI (Generate / Dashboard / Images tabs)
- The model is built into the container image, so there’s no download at runtime and redeploys are fast
- Work is spread across N replicas, each with its own GPU and model, generating independently
- No external API costs: all inference runs directly on gcube GPUs
Even with multiple replicas, you see a single UI. Every replica mounts the same storage and writes results to one place, so you see the same gallery and dashboard no matter which replica responds.
Preparing to deploy: storage and conditions.json
Connecting storage
First, create a storage volume in Storage Management in the gcube console (one time only). This storage is mounted at /workspace in the container, and the subfolders it needs are created automatically the first time the container starts. You don’t need to create any folders in advance.
/workspace ← gcube storage mount point
├─ conditions.json ← prompt list for automatic generation (upload it yourself)
├─ outputs/
│ ├─ auto/ ← automatically generated PNGs (kept)
│ └─ manual/ ← PNGs generated manually from the UI
└─ current/<workload>/<RUN_ID>/ ← metadata for UI rendering (isolated per run)
The original PNGs are saved once in outputs/, while the metadata used for display is kept separately under current/ for each run (RUN_ID). So starting a new run never mixes its results with earlier ones.
Preparing conditions.json
This is the list of prompts used for automatic bulk generation. Upload it to the storage root as conditions.json, and the container reads it as /workspace/conditions.json. The format is a JSON array, and only prompt is required for each item.
[
{ "prompt": "a serene mountain lake at sunrise, cinematic" },
{ "prompt": "a futuristic city skyline, neon lights", "width": 768, "height": 576, "steps": 8 },
{ "prompt": "a cute corgi puppy running in a field" }
]
- If an item has only
prompt, the rest use default values (the seed is random) - With
RANDOM_PICK=true, it picksGEN_COUNTimages’ worth of prompts at random from the list; withfalse(the default), it cycles through them in order
On an 8 GB GPU, we recommend 768×576. At 1024, peak VRAM during generation (about 8.5 GB) exceeds 8 GB and spills over into system RAM, and speed drops by 6–8x. At 768×576, the peak is about 7 GB and fits safely. Set
width/heighton each item, or use theZIMG_WIDTH/ZIMG_HEIGHTenvironment variables below.
Workload settings
Container Settings
| Setting | Value |
|---|---|
| Registry Type | AWS ECR |
| Container Image | public.ecr.aws/g3x5o1w3/gcube/demo/z-image:latest |
| Container Port | 8000 |
| Max concurrent requests | 20 |
Container Environment Variables
| KEY | VALUE (example) | Description |
|---|---|---|
GEN_COUNT | 100 | Number of images to generate automatically. Leave empty for manual mode (generate from the UI only) |
CONDITIONS_FILE | conditions.json | Prompt list file (relative to the storage root) |
RUN_ID | 20260701 | Label that identifies a run. We recommend changing it for each new run (defaults to default) |
RANDOM_PICK | true | Whether to pick prompts at random from the list (default false = cycle in order) |
ZIMG_WIDTH / ZIMG_HEIGHT | 768 / 576 | Default resolution. Values set on an item in conditions.json take priority |
Automatic generation starts only when both
GEN_COUNTandCONDITIONS_FILEare set. If either is empty, the container starts in manual mode, and you can test by entering prompts in the UI.
Storage mount
| Storage | Subpath | Mount path |
|---|---|---|
| (the gcube storage you created) | / | /workspace |
GPU Settings
| Setting | Recommended value |
|---|---|
| GPU | 1 — confirmed stable on RTX 5090 (Tier 2); RTX 5060 (Tier 3) also supported |
| VRAM | Peak about 7 GB during generation (at 768×576) → 8 GB cards work |
| Replicas | Start with 1; throughput scales linearly as you add more |
| Minimum CUDA Version | 12.8.0 |
| Shared Memory | 1 GB |
Expected time by GPU tier (100 images, 768×576): about 3 minutes on an RTX 5090 (34 images per minute) and about 30 minutes on an RTX 5060 (3.3 images per minute). Startup and inference stability are verified on the 5060 as well, so for large jobs it’s more economical to add replicas and spread the work. We compare GPUs in more detail below.
Deploying and checking
Once you deploy, it proceeds in this order:
- The container starts → the model loads (built into the image, so no download)
- In automatic mode, generation of
GEN_COUNTimages begins - The web UI is ready → you can open it at the service URL
If the workload’s Logs tab shows the following, everything is working.
[ MODEL ] loading Disty0/Z-Image-Turbo-SDNQ-uint4-svd-r32 ... (MEM_MODE=VRAM)
[ MODEL ] ready
[ AUTO ] 자동화 시작: 100장 / conditions.json / random=True
The last line is printed in Korean and means “Automation started: 100 images / conditions.json / random=True.” If you deployed in manual mode, you’ll see [ AUTO ] 수동 모드 (“manual mode”) instead.
On the first deploy, if the node doesn’t have the image cached yet, pulling the large image (about 14 GB) can take several minutes. Once it’s on a node, later redeploys to that node are fast thanks to the cache. Two things to know in advance:
- Redeploying changes the service URL. The old URL returns 404, so always use the new one.
- Each replica generates
GEN_COUNTimages on its own. 3 replicas ×GEN_COUNT=100means 300 images in total. Adjust the replica count andGEN_COUNTtogether to hit your target.
Using the UI
When you open the service URL, the Generate tab appears.
| Area | Description |
|---|---|
| Gallery | Generated images. Filter by All / AUTO / MANUAL; click an image to open its detail view |
| Job Status | Automatic generation progress, plus pause, resume and cancel controls |
| Manual Generate | Enter a prompt yourself to test generation |
| Replicas | List of active replicas. Select one to filter to its results only |
| Resources | Per-replica VRAM and RAM usage, uptime and generation time stats |
You control jobs with the buttons in Job Status. Clicking a button opens a popup where you choose the target replicas, and the command is sent only to the ones you check.
- Pause — temporarily stops automatic generation
- Resume — continues generating from where it paused
- Cancel — ends the current run session
The Dashboard tab shows a summary of the replica count, total images generated and average GPU utilization, plus a card for each replica (click it for gauges, time-series charts and a mini gallery). In an image’s detail view, you can check the prompt, seed and resolution, download the image, and regenerate with these settings.
Reading the UI — A colored replica indicator means active; white means unresponsive (Dead). The LOADING chip means the model is still loading, and the AUTO/MANUAL badges distinguish automatically and manually generated images.
When generation is done, take the results straight from outputs/ in your storage, and clean up directly in the storage once you’ve reviewed them (don’t delete conditions.json).
Which GPU should you choose?
We ran the same container on 6 GPUs under identical conditions (768×576 · 8 steps).
| GPU | Images per minute (IPM) | Time per image |
|---|---|---|
| RTX 5090 | 34.1 | 1.8 s |
| RTX 4080 SUPER | 17.0 | 3.5 s |
| RTX 4060 Ti | 4.3 | 13.9 s |
| RTX 3070 | 4.1 | 14.6 s |
| RTX 4060 | 3.5 | 17.0 s |
| RTX 5060 | 3.3 | 18.3 s |
Reading it is simple: if you’re in a hurry, use the 5090; to generate a lot cheaply, use budget cards plus replicas.
- 8 GB cards take 14–18 seconds per image, which is perfectly practical for bulk generation when you’re not in a rush. They cost only about $0.08–0.15 an hour (₩100–199), so they actually come out ahead on cost per image.
- Running multiple containers on one GPU doesn’t help. A single image generation request already saturates the GPU’s compute, and the measured gain was only about 10%. To increase throughput, add replicas (more GPUs) instead; that scales linearly.
- If performance on an 8 GB card is strangely low, resolution is almost always the cause. Check the spill warning above again (768×576 recommended). If it’s slow even though GPU utilization is at 100%, that’s spill.
Wrapping up
All you need is a prompt list and a storage mount, and a Z-Image-Turbo bulk generation pipeline runs on gcube as is, with no external API costs, even on an 8 GB GPU. We recommend deploying first in manual mode (with GEN_COUNT left empty) and generating a few images from the UI. Once you have a feel for it, scale up with conditions.json and more replicas.
Reference links