Skip to content

Adding KV-Cache Support to Flux2.Klein Image Editing - #465

Draft
amepas wants to merge 11 commits into
mainfrom
onboarding-imageedit-kv-flux2klein
Draft

Adding KV-Cache Support to Flux2.Klein Image Editing#465
amepas wants to merge 11 commits into
mainfrom
onboarding-imageedit-kv-flux2klein

Conversation

@amepas

@amepas amepas commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Speeding up Image Editing on Flux2.Klein models by onboarding Flux2.Klein-9B-KV model variant. This is a separate model (same architecture, different weights) that was trained to support caching KV for reference images during image editing.

Usage

Just adding the use_kv=True flag to the 9B model call will switch to the KV-cache model support.

python generate_flux2klein.py
  prompt="add a bunch of animals swimming in the water in front of the castle and get rid of all birds" \
  image_paths="['/path/to/img1.png', '/path/to/img2.png']" \
  use_kv=True

Speed-Ups

Block-sizes can be tuned for each setting. Using default settings still gives substantial improvements

Reference Images ($1024 \times 1024$) Latency Speedup
1 Image 1.28x
2 Images 1.32x
4 Images 1.74x

Visual Verification

Prompt: change the painting so she is facing forward instead of looking over her shoulder

Original:
image

Edited:
image

Correctness

E2E parity test against reference diffusers implementation shows high visual similarity (SSIM>0.9). Smoke test also included to prevent any implementation regressions in future PRs.

amepas added 10 commits August 14, 2026 01:44
…weight loading, causal splash attention, and end-to-end optimizations

- Implemented Flax NNX Transformer architecture (NNXFlux2KleinTransformer2DModel) with support for both 4B (5 double / 20 single layers) and 9B (8 double / 24 single layers) configurations.
- Integrated Flax Qwen3 text encoder with 3-layer intermediate hidden states extraction (layers 9, 18, 27), custom causal splash attention, and proper sharding constraints.
- Implemented FlaxAutoencoderKL VAE decoder with fused batch normalization unscaling and channel re-layout.
- Added fused end-to-end denoising loop scan with Flow Match Euler scheduler.
- Added concurrent AOT XLA compilation across Qwen3, Flux transformer, and VAE.
- Implemented fast host-memory streaming weight converter for safetensors shards directly into NNX State PyTree.
- Optimized splash attention block sizes and Ulysses context parallelism sharding.
- Added comprehensive unit tests (nnx_flux2klein_test.py) and end-to-end smoke test suite (generate_flux2klein_smoke_test.py).
… & 9B)

- Implement NNXAutoencoderKLFlux2 (pure Flax NNX VAE encoder and decoder) with zero-copy weight loading and verified float32 parity
- Implement multi-image conditioning helpers (prepare_multi_image_ids, prepare_image_latents, patchify/unpatchify)
- Implement FlaxFlux2KleinImageEditPipeline for end-to-end multi-image editing inference
- Implement generate_flux2klein_image_edit.py CLI runner and default YAML configs for 4B and 9B
- Add comprehensive unit tests and parity test suite across all 7 implementation phases
…o single pipeline and CLI

- Refactored FlaxFlux2KleinPipeline to conditionally handle multi-image editing when images are provided, eliminating duplicate pipeline definitions.
- Unified generate_flux2klein.py and base configs (base_flux2klein.yml, base_flux2klein_9B.yml) to accept image_paths for multi-image conditioning.
- Added test_flux2klein_4b_image_edit_smoke to generate_flux2klein_smoke_test.py.
- Added test_flux2klein_image_edit_e2e_parity.py for end-to-end parity verification against PyTorch Diffusers reference.
… obsolete dev tests

- Deleted separate YAML configs (base_flux2klein_image_edit.yml, base_flux2klein_image_edit_9B.yml).
- Deleted separate CLI runner (generate_flux2klein_image_edit.py) and separate pipeline (flux2klein_image_edit_pipeline.py).
- Removed obsolete intermediate parity/scratch scripts in favor of test_flux2klein_image_edit_e2e_parity.py and generate_flux2klein_smoke_test.py.
- Cleaned up export in maxdiffusion.pipelines.flux.__init__.
- Renamed test_flux2klein_image_edit_e2e_parity.py to edit_flux2klein_e2e_test.py.
- Added test_flux2klein_4b_image_edit_smoke and test_flux2klein_9b_image_edit_smoke in generate_flux2klein_smoke_test.py.
- Added golden reference images ref_flux2klein_4b_image_edit.png and ref_flux2klein_9b_image_edit.png conditioned on ref_flux2klein_4b.png with prompt 'change the lighting to evening'.
- Verified SSIM >= 0.95 assertion on both 4B and 9B image edit smoke tests on TPU VM.
…e loading

- Replace img.max() > 1.0 heuristic with dtype-aware normalization in FlaxFlux2KleinPipeline
- Handle integer uint8 and floating point ranges explicitly
- Add try-except error handling for reference image loading in generate_flux2klein
- Remove duplicate unpatchify_latents in models/flux/util.py
- Apply pyink formatting and resolve ruff linter issues
@github-actions

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the Flux2Klein model, including new configuration files, NNX-based model implementations for the Transformer and VAE, and a dedicated pipeline. It also adds end-to-end parity and smoke tests. The review identified several critical issues: missing imports in flux2klein_pipeline.py and generate_flux2klein.py that will cause runtime errors, and the incorrect use of nnx.silu instead of jax.nn.silu across multiple model files.

Comment thread src/maxdiffusion/pipelines/flux/flux2klein_pipeline.py
Comment on lines +88 to +89
try:
fb_dir = snapshot_download(repo_id=repo_id, local_files_only=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

snapshot_download is called here but is not imported in this scope (it is only imported inside main). This will cause a NameError when encode_prompt is executed. Please import snapshot_download.

    try:
      from huggingface_hub import snapshot_download
      fb_dir = snapshot_download(repo_id=repo_id, local_files_only=True)

def __call__(self, x: jax.Array) -> jax.Array:
x = self.linear_in(x)
x1, x2 = jnp.split(x, 2, axis=-1)
hidden = nnx.silu(x1) * x2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

nnx.silu is not a valid attribute of flax.nnx. Please use jax.nn.silu instead.

Suggested change
hidden = nnx.silu(x1) * x2
hidden = jax.nn.silu(x1) * x2

temb = temb.astype(hidden_states.dtype)

temb_silu = jax.nn.silu(temb)
temb_silu = nnx.silu(temb)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

nnx.silu is not a valid attribute of flax.nnx. Please use jax.nn.silu instead.

Suggested change
temb_silu = nnx.silu(temb)
temb_silu = jax.nn.silu(temb)

num_img_tokens = hidden_states.shape[1] - num_ref_tokens
ref_timestep = jnp.full_like(timestep_scaled, ref_fixed_timestep * 1000.0)
ref_temb = self.time_text_embed(ref_timestep, guidance_scaled, pooled_projections).astype(hidden_states.dtype)
ref_temb_silu = nnx.silu(ref_temb)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

nnx.silu is not a valid attribute of flax.nnx. Please use jax.nn.silu instead.

Suggested change
ref_temb_silu = nnx.silu(ref_temb)
ref_temb_silu = jax.nn.silu(ref_temb)

Comment on lines +155 to +159
h = self.norm1(x)
h = nnx.silu(h)
h = self.conv1(h)
h = self.norm2(h)
h = nnx.silu(h)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

nnx.silu is not a valid attribute of flax.nnx. Please use jax.nn.silu instead.

Suggested change
h = self.norm1(x)
h = nnx.silu(h)
h = self.conv1(h)
h = self.norm2(h)
h = nnx.silu(h)
h = self.norm1(x)
h = jax.nn.silu(h)
h = self.conv1(h)
h = self.norm2(h)
h = jax.nn.silu(h)

Comment on lines +452 to +454
x = self.conv_norm_out(x)
x = nnx.silu(x)
x = self.conv_out(x)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

nnx.silu is not a valid attribute of flax.nnx. Please use jax.nn.silu instead.

Suggested change
x = self.conv_norm_out(x)
x = nnx.silu(x)
x = self.conv_out(x)
x = self.conv_norm_out(x)
x = jax.nn.silu(x)
x = self.conv_out(x)

Comment on lines +536 to +538
x = self.conv_norm_out(x)
x = nnx.silu(x)
x = self.conv_out(x)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

nnx.silu is not a valid attribute of flax.nnx. Please use jax.nn.silu instead.

Suggested change
x = self.conv_norm_out(x)
x = nnx.silu(x)
x = self.conv_out(x)
x = self.conv_norm_out(x)
x = jax.nn.silu(x)
x = self.conv_out(x)

…and clean attention block sizing

- Added test_flux2klein_9b_kv_image_edit_smoke in generate_flux2klein_smoke_test.py
- Added flux2klein_kv_pipeline_e2e_test.py achieving 0.9037 SSIM and 23.30 dB PSNR cross-framework parity
- Added test reference images in tests/images/flux2klein/
- Refactored _select_flash_block_sizes to mathematically bound block sizes without breaking cross-attention or large KV-cache sequences
- Added hierarchical jax.named_scope annotations to Flux transformer blocks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant