Skip to main content
Published on
Kategorie
AI
Autor
Alain Ritter
Lesezeit
5 min read
Last edited:
Last edited:

pxlngnbloginvokeai-design-pipeline

My image pipeline with InvokeAI – and where it fails at 16 GB of VRAM

How I built a local pipeline for blog cover images with InvokeAI and Krea 2 Turbo: from the prompt recipe through the API to the script, plus the limits of a 16 GB AMD card – HIP OOM, tiled VAE decoding and a ten-minute cold start.

My image pipeline with InvokeAI – and where it fails at 16 GB of VRAM
Cover image: AI-generated

The cover images of this blog are created locally, on my own graphics card, without a cloud service. Behind them is a small pipeline made of InvokeAI, the Krea 2 Turbo model and a script that turns one sentence of scene into a finished WebP. I like the result. But the road there consisted to a good part of memory limits, and those are the more instructive part.

The system

  • AMD card of the RX 6800 class (gfx1030) with 16 GB VRAM
  • InvokeAI as a server, operated through its HTTP API
  • Krea 2 Turbo as a quantized GGUF (Q4_K_M): a transformer with around 12 billion parameters, plus a Qwen3-VL encoder as the text encoder
  • Models live on an external drive, not on the system partition

The model isn’t built for this card, it is squeezed into it by a hair. That shapes every setting.

The pipeline in four steps

  1. Write the scene: one concrete motif per post, derived from the topic, in English.
  2. Append the style: a fixed style sentence that stays the same for all images.
  3. Generate: the script loads the workflow graph from an earlier output PNG (InvokeAI stores it there as metadata), swaps in prompt and seed and puts the job into the queue via the API.
  4. Store: the result is written as WebP into the blog folder, and imageAi: true is added to the frontmatter so the image is labelled as an AI image.

The call is deliberately small:

python3 scripts/blog-image.py <post-key> "<scene>" --seed 11

The trick with the graph from the PNG saves me from rebuilding the workflow myself: whatever has worked once in the interface is the template.

How the recipe has changed

In the beginning there were style presets (Blueprint, Dark-Cyan-HUD). They delivered interchangeable icons that fit every topic and therefore none. After that came a phase with hard-surface machines, borrowed from a game project: it looked good, but it was a style that fit almost only technical topics.

Lastly, I used the model the way it was trained: a scene from the topic, plus a short, fixed style sentence. No preset chain, no negative lists. What I learned along the way:

  • Most important first: motif and shape into the first sentence, style at the end.
  • No negative prompt: Turbo runs with CFG 1, and at that setting a negative prompt has no effect. Exclusions only work if you describe them positively.
  • Text in the image is hard to forbid: even “no text” doesn’t always help with documents, then the model scribbles pseudo-script. It is better to describe the sheets as “abstract strokes, no letters”.
  • Coloured materials break the palette: a rusty crane immediately fell out of the frame, a grey one didn’t.
  • Ambiguity is worth two seeds: I roll two variants and pick from the contact sheet.

Where 16 GB isn’t enough

Now to the actual topic. A 12-billion-parameter model plus encoder plus image decoder on 16 GB: it works, but only with measures.

1. The image size is hard-limited. At 1024 × 576 everything runs through, at 1280 × 720 there is a HIP OOM, the AMD variant of the out-of-memory error. The cover images are therefore 1024 × 576. For a blog that’s enough, for larger output I would have to upscale.

2. Too little working-memory reserve. By default InvokeAI leaves about 3 GB of VRAM free as a working reserve. With Krea 2 that wasn’t enough, the run aborted with OOM. Only device_working_mem_gb: 5 solved it. So part of the VRAM doesn’t belong to the model, but to the computing.

3. The decoder wants a lot at once. When decoding the latents into the image, an SDXL run demanded about 9 GB in one block. The solution is tiled decoding: the image is decoded in tiles, force_tiled_decode: true. It costs some time and saves the peak consumption.

4. A kernel benchmark hangs the card. During the VAE decode of the FLUX.2 model the card got stuck in MIOpen’s kernel tuning. The environment variable MIOPEN_FIND_MODE=FAST skips the benchmark and has been in the start script ever since.

5. The cold start is long. Transformer and encoder have to go from disk into memory. For the first image after the server starts, that takes eight to ten minutes for me, the encoder alone needs a good seven. With max_cache_ram_gb: 18 both models stay in RAM so the next run doesn’t load from disk again. After that an image takes about a minute and a half to two minutes.

The configuration that holds all of this together is short:

force_tiled_decode: true # decoder in tiles, no 9 GB peak demand
device_working_mem_gb: 5 # more working reserve than the default of 3 GB
max_cache_ram_gb: 18 # keep models in RAM, no cold start per image

What this means for the pipeline

The pipeline is not an interactive tool but a batch tool. Whoever wants a single image waits ten minutes the first time. Whoever wants twenty starts the server once and lets the queue run through: my pass over all blog cover images took only about half an hour, because the cold start happened only once.

The point behind it is the same as with local language models: on small hardware, it isn’t model quality that decides, but how cleanly you know the bottlenecks. Whoever knows where the memory goes can distribute it. Whoever doesn’t only sees an error message and swaps the model.

What’s still open

  • Higher resolutions only work through upscaling, which isn’t in the pipeline yet.
  • The cold start is a convenience problem. A permanently running service would hide it, but then permanently costs RAM.
  • I still set the frontmatter by hand, the script only prints a hint.