← All posts
Automated bulk image generation with Z-Image-Turbo on gcube
gcube · hands-on

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 picks GEN_COUNT images’ worth of prompts at random from the list; with false (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/height on each item, or use the ZIMG_WIDTH/ZIMG_HEIGHT environment variables below.

Workload settings

Container Settings

SettingValue
Registry TypeAWS ECR
Container Imagepublic.ecr.aws/g3x5o1w3/gcube/demo/z-image:latest
Container Port8000
Max concurrent requests20

Container Environment Variables

KEYVALUE (example)Description
GEN_COUNT100Number of images to generate automatically. Leave empty for manual mode (generate from the UI only)
CONDITIONS_FILEconditions.jsonPrompt list file (relative to the storage root)
RUN_ID20260701Label that identifies a run. We recommend changing it for each new run (defaults to default)
RANDOM_PICKtrueWhether to pick prompts at random from the list (default false = cycle in order)
ZIMG_WIDTH / ZIMG_HEIGHT768 / 576Default resolution. Values set on an item in conditions.json take priority

Automatic generation starts only when both GEN_COUNT and CONDITIONS_FILE are set. If either is empty, the container starts in manual mode, and you can test by entering prompts in the UI.

Storage mount

StorageSubpathMount path
(the gcube storage you created)//workspace

GPU Settings

SettingRecommended value
GPU1 — confirmed stable on RTX 5090 (Tier 2); RTX 5060 (Tier 3) also supported
VRAMPeak about 7 GB during generation (at 768×576) → 8 GB cards work
ReplicasStart with 1; throughput scales linearly as you add more
Minimum CUDA Version12.8.0
Shared Memory1 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:

  1. The container starts → the model loads (built into the image, so no download)
  2. In automatic mode, generation of GEN_COUNT images begins
  3. 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_COUNT images on its own. 3 replicas × GEN_COUNT=100 means 300 images in total. Adjust the replica count and GEN_COUNT together to hit your target.

Using the UI

When you open the service URL, the Generate tab appears.

AreaDescription
GalleryGenerated images. Filter by All / AUTO / MANUAL; click an image to open its detail view
Job StatusAutomatic generation progress, plus pause, resume and cancel controls
Manual GenerateEnter a prompt yourself to test generation
ReplicasList of active replicas. Select one to filter to its results only
ResourcesPer-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).

GPUImages per minute (IPM)Time per image
RTX 509034.11.8 s
RTX 4080 SUPER17.03.5 s
RTX 4060 Ti4.313.9 s
RTX 30704.114.6 s
RTX 40603.517.0 s
RTX 50603.318.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