Downloads · 30 days
0
cgb/flux2-klein-4b-onnx-webgpu
flux2-klein-4b-onnx-webgpu is a text-to-image model from cgb. Use it when you need an image from a text prompt. It is set up for onnx. The card lists the license as apache-2.0.
FLUX.2 Klein 4B exported to ONNX so it runs entirely inside a web browser on WebGPU, through ONNX Runtime Web. No server, no API key, no upload: the weights are cached by the browser and every image is generated on th…
Downloads · 30 days
0
Access
Public
Updated Sep 8, 2026
Repo size
6.7 GB
Likes
0
Public
Click a slice to open those files.
.data6.7 GB · 100%
From the Hugging Face model README
FLUX.2 Klein 4B exported to ONNX so it runs entirely inside a web browser on WebGPU, through ONNX Runtime Web. No server, no API key, no upload: the weights are cached by the browser and every image is generated on the visitor's own GPU.
Generate an image at freegen.ai/imagine
That is this exact pack, driven by the pipeline described below. 512x512 at 4 steps takes about 22 seconds on a recent discrete GPU. First use downloads 6.5 GB once and then works offline.
Prompt: "an old bookshop with a cat in the window". 512x512, 4 steps, generated in a browser.
A re-export of the Apache-2.0 upstream, black-forest-labs/FLUX.2-klein-4B, into a form ONNX Runtime Web can actually execute. The text encoder is Qwen3-4B, also Apache-2.0. Everything here carries the same licence as the upstream, including commercial use.
It exists because the only other browser build of this model declares no licence at all, which makes it unusable in a product. The weights themselves are Apache-2.0 and free to re-export, so we did.
| File | Precision | Size | Cosine vs PyTorch |
|---|---|---|---|
text_encoder_q4.onnx | int4 weight-only, block size 128 | 2.20 GB | 0.999232 |
transformer_q8.onnx | int8 weight-only, block size 128 | 4.14 GB | 0.999426 |
vae_decoder.onnx | float32 | 0.20 GB | 1.000000 |
Anything above 2 GB is split into shards. A browser cannot allocate a single
2 GiB ArrayBuffer, and ONNX Runtime reads each external data file into one, so
an unsharded 4 GB weight file fails with nothing more useful than "Failed to
fetch". *.manifest.json lists the shards for each graph:
{ "shards": [{ "path": "transformer_q8-00000.data" }, ...] }
Pass them to InferenceSession.create as externalData, mapping each recorded
path to its URL.
This is three graphs and a flow-matching loop, not a single model, so it needs a driver. The shape of one:
// 1. Encode the prompt. Qwen2 tokenizer, chat template, padded to 512.
const { prompt_embeds } = await textEncoder.run({ input_ids, attention_mask });
// 2. Denoise. sigma is passed straight in as `timestep`.
let latent = gaussianNoise(sequence * 128); // sequence = (h/16) * (w/16)
for (let step = 0; step < steps; step++) {
const { noise_pred } = await transformer.run({
hidden_states: f16(latent), // [1, sequence, 128]
encoder_hidden_states: prompt_embeds, // [1, 512, 7680]
timestep: f16([sigmas[step]]),
img_ids, txt_ids // [t, h, w, l] positions
});
const delta = sigmas[step + 1] - sigmas[step];
for (let i = 0; i < latent.length; i++) latent[i] += delta * noise_pred[i];
}
// 3. Decode. Unpack tokens to channel-first first: token i holds every
// channel of the pixel at (i / width, i % width).
const { sample } = await vaeDecoder.run({ packed_latent });
Two things that are easy to get wrong:
base_shift 0.5, max_shift 1.15 over 256 to 4096 image
tokens. A FLUX.1-era schedule gives visibly worse images. At 1024 image tokens
mu is 0.63, giving [1, 0.849, 0.652, 0.385, 0] for 4 steps.Inputs and outputs:
Each of these was found by verifying against PyTorch rather than trusting the export, and each one silently produces wrong images if you get it wrong.
joint_attention_dim. The export does the reshape so a caller cannot get it
wrong. It is truncated to 28 of 36 layers: layer 27 is the deepest state read,
and one extra layer is kept because the final norm is applied to the last
hidden state, so cutting at 27 changes the very tensor being read and drops
cosine to 0.81.Cos kernel on any backend,
so the graph cannot execute at all.LayerNormalization nodes do not.scaling_factor. It denormalises with batch-norm running
statistics, then unpatchifies 2x2, then decodes. All three steps are folded
into the exported graph, and it is left unquantized: it is small, and
quantizing a decoder reliably adds banding and colour shifts for almost no
saving.t / 1000
where t = sigma * 1000.Every graph is checked against the PyTorch reference on identical inputs before it is published.
FreeGen runs open models entirely on your own machine. Nothing is uploaded, there is no account, and there is no per-use cost.