Black Forest Labs logo

FLUX.2 [klein] 4B

FLUX.2vklein 4B distilledCurrent
Context--
CategoryImage Model

About FLUX.2 [klein] 4B

FLUX.2 klein 4B is a distilled image-generation and multi-reference editing model developed by Black Forest Labs. The official checkpoint has 4 billion parameters and is released under Apache 2.0. Its model card reports approximately 13GB VRAM for local use; requirements depend on the runtime and configuration. It supports text-to-image and image-to-image workflows. Generated text may be distorted, and outputs can miss the prompt or reflect biases. This record refers to black-forest-labs/FLUX.2-klein-4B, not the base checkpoint or klein 9B. Source: the official Black Forest Labs Hugging Face model card, checked September 12, 2026.

Capabilities

text to imageimage to imagemulti reference editing

Input Modalities

textimage

Output Modalities

image

Technical Details

Category
Image Model

Tags

image generationimage editingmulti-referenceopen weightsApache 2.0distilled

Run this model yourself

Local and cloud setup for FLUX.2 [klein] 4B, with exact configurations, sources and clear evidence limits.

Find a model for your hardware
Local hardwareDocumented setupSources checked Oct 16, 2026

Generate a fixed 512 px image with FLUX.2 Klein4B on a CPU desktop

Use the complete official FLUX.2 Klein4B distilled Diffusers bundle: transformer, Qwen3 text encoder, tokenizer, scheduler and VAE. Native CPU float32 generates one 512 × 512 PNG from a fixed original geometric prompt in four denoising steps. The configured prompt bound is 512 positions including its chat template; image latent positions are separate. This private educational experiment has unknown CPU fit, speed and image quality.

Hardware
Intel Core i5-12400 desktop, 8 inference threads, 64 GiB RAM
Runtime
Native Diffusers FLUX.2 Klein4B complete CPU float32 image pipeline diffusers0.37.1 source ad3a3afc3a4d3068bbb12f58129c855087ffc6d6; Transformers4.57.6; HFhub0.36.2; torch2.6.0+cpu; Python3.12 · CPU
Model format
Diffusers safetensors bundle · Whole native pipeline CPU float32; no CUDA/offload/quantization/custom kernel · Unquantized official bundle; selected complete CPU float32
Workload
512 token context · 1 concurrent

Based on source documentation; this setup has not been executed by OpenTools. Memory: unknown. No compatible measurement available.

Read the complete setup guide

Before you start

  • Use an owned Intel Core i5-12400 Ubuntu 24.04 x86_64 desktop with 64 GiB RAM, 80 GiB free disk and eight threads as planning allocations, not proven fit or minima.
  • Review the actual Apache 2.0 grant for this exact 4B bundle and dependency licenses. This guide uses the distilled 4B checkpoint, not 9B/base/dev or a transformer-only single-file workflow.
  • Use Python 3.12, torch 2.6 CPU, Diffusers 0.37.1, Transformers 4.57.6, Hugging Face Hub 0.36.2, Accelerate 1.12.0 and safetensors 0.7.0. Source metadata supports their version-range intersection; none has been installed or model-tested here. Save resolution and stop on pip check/import/shape errors.
  • The private endpoint accepts only its fixed benign geometric prompt selector, no arbitrary prompts/images/URLs/files. Generated imagery still needs human inspection for prompt adherence, artifacts and appropriateness; no downstream suitability is promised.

Exact model and requirements

Artifact:
Black Forest Labs / FLUX.2 Klein4B distilled complete Diffusers bundle · checkpoint black-forest-labs/FLUX.2-klein-4B
Publisher revision:
e7b7dc27f91deacad38e78976d1f2b499d76a294
Artifact digest:
A multi-file pipeline has no single publisher aggregate byte digest. Each of19 required files is independently bound in the reader manifest by publisher LFS SHA256 or exact Git blob SHA1.
Download size:
14.87 GiB
Memory requirement (unknown):
Unknown — No measured full-process peak for full FP32 transformer/text encoder/VAE plus512image activations.
System RAM / disk:
Unknown / Unknown
Placement:
single device. One CPU host, one prompt, one image, one serial request. All complete native modules remain on CPU float32; no GPU or disk offload.
KV cache:
No autoregressive text generation or retained text K/V cache. Native Qwen3 encoder runs use_cache=False; diffusion latents are distinct.
Workload:
512 token context; 512 input tokens; Not applicable; concurrency 1; batch 1; cache cold; repetitions Unknown.
Tokenizer:
Bundle Qwen2TokenizerFast and native user chat template, thinking disabled;512 padded prompt positions · e7b7dc27f91deacad38e78976d1f2b499d76a294
Sampling / reasoning:
Fixed original prompt: a red cube beside a blue sphere on white. Complete formatted prompt is counted before native truncation and rejected above 512. Native pipeline pads to 512 positions, uses Qwen3 hidden layers 9/18/27 with no text generation/cache. One512 × 512 image, four distilled steps, guidance_scale 1.0, CPU generator seed 0. Seed is a reproducibility parameter, not a cross-hardware image equality guarantee. 512 is the chosen native prompt-encoder maximum including template; the unpadded count is reported at reader execution. It is not the Qwen3 theoretical 40960 capacity or total joint image/text attention. VAE downsampling8 and2 × 2 latent packing give 1024 image latent positions at 512square, separate from text. Four steps are the publisher distilled recipe, not a text sampler.
Runtime prerequisites:
Ubuntu 24.04 x86_64; See prerequisites above.

Official artifact · License

The actual bundle LICENSE.md and publisher model card grant FLUX.2 Klein4B under Apache License2.0, including this selected complete bundle. Preserve applicable license/copyright/NOTICE and change/patent conditions. Do not transfer noncommercial 9B or FLUX.2 dev terms from names in VAE metadata to this 4B bundle. Publisher encourages safety filtering; its card says recommendations do not modify the grant. No safety classifier is claimed here; fixed benign geometry prompt, manual inspection and applicable laws still matter. This is no legal clearance or trademark endorsement.

  • 15,964,212,614 bytes is the sum of four official safetensors file sizes, not full installed footprint or runtime RAM. The standalone7.75 GB transformer file alone is not the complete pipeline.
  • 64 GiB RAM, 80 GiB disk and eight threads are planning choices. CPU float32 increases weight storage relative to source tensor dtype; image attention, decoder, temporary load buffers, packages and OS add unknown peak memory.

One-time setup and hardware

hardware
Unknown — Existing desktop purchase price and incremental hardware cost depend on the reader; unknown.
Existing desktop purchase price and incremental hardware cost depend on the reader; unknown. Tax: unknown.
setup
Unknown — Initial provisioning/build time and labor cost are unknown.
Initial provisioning/build time and labor cost are unknown. Tax: unknown.

Recurring costs

electricity
Unknown — Actual wall power, electricity rate and powered hours are unknown; no TDP-to-wall-power conversion.
Actual wall power, electricity rate and powered hours are unknown; no TDP-to-wall-power conversion. Tax: unknown.
storage
Unknown — Incremental cost of local disk capacity is unknown. The 80 GiB free-disk walkthrough allocation is an author choice, not a measured requirement.
Incremental cost of local disk capacity is unknown. The 80 GiB free-disk walkthrough allocation is an author choice, not a measured requirement. Tax: unknown.
egress
Unknown — Data transfer price, allowances and traffic volume are unknown.
Data transfer price, allowances and traffic volume are unknown. Tax: unknown.
administration
Unknown — Maintenance labor rate and hours are unknown.
Maintenance labor rate and hours are unknown. Tax: unknown.

Install the runtime

  1. 1. Install the compatible native CPU stack

    Use a dedicated virtualenv and official matched CPU wheel hashes. Diffusers 0.37.1 is chosen because its HFhub range intersects Transformers 4.57.6; latest Diffusers 0.41.0 requires a different hub generation and is not substituted. No all-extra, CUDA, offload, optional flash kernel or remote code is enabled.

    Command
    set -e
    sudo apt-get update
    sudo apt-get install -y python3.12 python3.12-venv curl ca-certificates openssl
    mkdir -p "$HOME/flux2-klein4b-image/model"
    cd "$HOME/flux2-klein4b-image"
    python3.12 -m venv .venv
    .venv/bin/python -m pip install 'https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=59e78aa0c690f70734e42670036d6b541930b8eabbaa18d94e090abf14cc4d91' 'https://download-r2.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=d6874431e678ba107b60a83f255c33f3755f06bad587b1b919aa514ec325dcd8'
    .venv/bin/python -m pip install 'diffusers==0.37.1' 'transformers==4.57.6' 'tokenizers==0.22.2' 'huggingface-hub==0.36.2' 'accelerate==1.12.0' 'safetensors==0.7.0' 'numpy==2.2.3' 'Pillow==11.1.0'
    .venv/bin/python -m pip check
    .venv/bin/python -m pip freeze > resolved-environment.txt

    Expected result: Pinned direct versions resolve and pip check passes; exact transitive/system resolution is saved. No native execution/compatibility or fit claim.

Download the model

  1. 1. Write the complete immutable component manifest

    This reader-authored manifest selects 19 exact bundle files, including all four weight files. LFS blobs use publisher SHA256; regular source files use their exact Git blob SHA1. SHA1 is Git identity, not a modern adversarial integrity guarantee; immutable official HTTPS revision remains the provenance boundary.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    cat > component-manifest.json <<'MANIFEST'
    {
      "repository": "black-forest-labs/FLUX.2-klein-4B",
      "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294",
      "files": [
        {
          "file": "LICENSE.md",
          "bytes": 9584,
          "algorithm": "git-blob-sha1",
          "digest": "0afa7632c1a195ea6627a3c53e6ebf18d1be3aa5"
        },
        {
          "file": "model_index.json",
          "bytes": 446,
          "algorithm": "git-blob-sha1",
          "digest": "554a39bf1019138dd0b28809b745df8ef33657c8"
        },
        {
          "file": "scheduler/scheduler_config.json",
          "bytes": 486,
          "algorithm": "git-blob-sha1",
          "digest": "7e53605e2643440b310fa554f087641893883c5f"
        },
        {
          "file": "text_encoder/config.json",
          "bytes": 1536,
          "algorithm": "git-blob-sha1",
          "digest": "86f951d170cfd1fdc1c8a57a041e05c27b7b789b"
        },
        {
          "file": "text_encoder/generation_config.json",
          "bytes": 214,
          "algorithm": "git-blob-sha1",
          "digest": "f5af93d0c7764fa38e3b1cd3aaba1b6307246d3b"
        },
        {
          "file": "text_encoder/model-00001-of-00002.safetensors",
          "bytes": 4967215360,
          "algorithm": "sha256",
          "digest": "8c0506e7f4936fa7e26183a4fd8da4e2bdbc5990ba64ae441f965d51228f36ea"
        },
        {
          "file": "text_encoder/model-00002-of-00002.safetensors",
          "bytes": 3077766632,
          "algorithm": "sha256",
          "digest": "82f2bd839378541b0557bfabaf37c7d3d637071fdcb73302dedd7cf61162ce07"
        },
        {
          "file": "text_encoder/model.safetensors.index.json",
          "bytes": 32855,
          "algorithm": "git-blob-sha1",
          "digest": "b65d8063815c8679d85bb919337b36fae8de75cf"
        },
        {
          "file": "tokenizer/added_tokens.json",
          "bytes": 707,
          "algorithm": "git-blob-sha1",
          "digest": "b54f9135e44c1e81047e8d05cb027af8bc039eed"
        },
        {
          "file": "tokenizer/chat_template.jinja",
          "bytes": 4168,
          "algorithm": "git-blob-sha1",
          "digest": "01be9b307daa2d425f7c168c9fb145a286e0afb4"
        },
        {
          "file": "tokenizer/merges.txt",
          "bytes": 1671853,
          "algorithm": "git-blob-sha1",
          "digest": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7"
        },
        {
          "file": "tokenizer/special_tokens_map.json",
          "bytes": 613,
          "algorithm": "git-blob-sha1",
          "digest": "ac23c0aaa2434523c494330aeb79c58395378103"
        },
        {
          "file": "tokenizer/tokenizer.json",
          "bytes": 11422654,
          "algorithm": "sha256",
          "digest": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4"
        },
        {
          "file": "tokenizer/tokenizer_config.json",
          "bytes": 5404,
          "algorithm": "git-blob-sha1",
          "digest": "ddaf69808214a44fdd26d3785b66c1367c78277a"
        },
        {
          "file": "tokenizer/vocab.json",
          "bytes": 2776833,
          "algorithm": "git-blob-sha1",
          "digest": "4783fe10ac3adce15ac8f358ef5462739852c569"
        },
        {
          "file": "transformer/config.json",
          "bytes": 541,
          "algorithm": "git-blob-sha1",
          "digest": "f21592ae2bc9f43052e78785bde7414f219ae369"
        },
        {
          "file": "transformer/diffusion_pytorch_model.safetensors",
          "bytes": 7751109744,
          "algorithm": "sha256",
          "digest": "9f29f9edcfdae452a653ffb51a534ca4decd389952c225724ff3b94042612a6e"
        },
        {
          "file": "vae/config.json",
          "bytes": 821,
          "algorithm": "git-blob-sha1",
          "digest": "c3f38eb4c188a96a462519159356bd0fcc28cd14"
        },
        {
          "file": "vae/diffusion_pytorch_model.safetensors",
          "bytes": 168120878,
          "algorithm": "sha256",
          "digest": "ca70d2202afe6415bdbcb8793ba8cd99fd159cfe6192381504d6c4d3036e0f04"
        }
      ]
    }
    MANIFEST
    sha256sum --check <<'SHA256'
    f03107bcd53ab215deeee44072681f15658a94a755ae242bb42741dfca6405e3  component-manifest.json
    SHA256

    Expected result: Exact complete manifest verifies before any reader weight retrieval.

  2. 2. Retrieve and verify all native components

    Reader-only downloads from one exact ungated publisher revision. Do not fetch the standalone transformer-only file or float revisions. Stop on any size/hash mismatch; no model loading occurs in this retrieval script.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    .venv/bin/python - <<'PYDOWNLOAD'
    import hashlib,json,pathlib,shutil
    from huggingface_hub import hf_hub_download
    m=json.load(open('component-manifest.json'))
    for row in m['files']:
     src=hf_hub_download(m['repository'],row['file'],revision=m['revision'])
     dst=pathlib.Path('model')/row['file'];dst.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(src,dst)
     assert dst.stat().st_size==row['bytes']
     h=hashlib.sha256() if row['algorithm']=='sha256' else hashlib.sha1()
     if row['algorithm']=='git-blob-sha1':h.update(('blob '+str(row['bytes'])+'\0').encode())
     with dst.open('rb') as f:
      for chunk in iter(lambda:f.read(1024*1024),b''):h.update(chunk)
     assert h.hexdigest()==row['digest'],row['file']
    print('All19 components verified; model loading has not run.')
    PYDOWNLOAD

    Expected result: All 19 exact files verify. Download cache duplicates disk usage;80 GiB is an allocation, not a measured peak. Offline loading follows only after complete verification.

Configure

  1. 1. Create a stable private service key

    Generate only when absent; preserve existing access. Do not print or share the local key.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    umask 077
    if [ ! -e .api-key ]; then openssl rand -hex 32 > .api-key; fi
    chmod 600 .api-key
    test -s .api-key || { echo "Restore empty key before continuing"; exit 1; }

    Expected result: Private64-character lowercase hexadecimal key preserved or created.

  2. 2. Create the native image harness

    Reader-authored wrapper, not publisher server. It rechecks every component before explicit native local_files_only safetensors component loading. Reject any missing, unexpected, mismatched or error diagnostic before assembling the native pipeline. All modules use CPU float32; fixed prompt is chat-formatted with thinking disabled and checked before native 512 truncation. No full model binary was fetched or executed by the author.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    cat > flux2_image.py <<'PYSERVER'
    """Reader-authored educational fixed-input FLUX.2 Klein4B harness; not publisher server."""
    import hashlib
    import hmac
    import json
    import math
    import pathlib
    from http.server import BaseHTTPRequestHandler, HTTPServer
    
    ROOT = pathlib.Path(__file__).resolve().parent
    PORT = 8995
    
    
    PROMPT = 'A simple red cube beside a blue sphere on a plain white background, clean studio illustration.'
    
    
    def validate_image(result):
        import base64
        import struct
        png, count = result
        if type(count) is not int or not 0 < count <= 512:
            raise ValueError('prompt_budget')
        if not isinstance(png, bytes) or not 57 <= len(png) <= 8*1024*1024:
            raise ValueError('png_bound')
        if png[:8] != b'\x89PNG\r\n\x1a\n' or png[12:16] != b'IHDR':
            raise ValueError('png_header')
        if struct.unpack('>II', png[16:24]) != (512, 512) or png[-8:-4] != b'IEND':
            raise ValueError('png_shape_or_end')
        return {'fixture': 'author-t2i-v1', 'prompt': PROMPT,
                'prompt_tokens_before_padding': count, 'padded_prompt_positions': 512,
                'image_shape': [512, 512, 3], 'configured_image_latent_positions': 1024,
                'denoising_steps': 4, 'seed': 0, 'guidance_scale': 1.0,
                'mime_type': 'image/png', 'png_base64': base64.b64encode(png).decode('ascii'),
                'generated_text_tokens': None}
    
    
    def make_encoder():
        # Reader-run only. Fake tests do not import Torch, Diffusers or Pillow.
        import os
        os.environ['HF_HUB_OFFLINE'] = '1'
        os.environ['TRANSFORMERS_OFFLINE'] = '1'
        import torch
        from diffusers import (Flux2KleinPipeline, Flux2Transformer2DModel,
                               AutoencoderKLFlux2, FlowMatchEulerDiscreteScheduler)
        from transformers import Qwen3ForCausalLM, Qwen2TokenizerFast
        manifest = json.loads((ROOT / 'component-manifest.json').read_text())
        model_root = ROOT / 'model'
        for row in manifest['files']:
            file = model_root / row['file']
            if file.stat().st_size != row['bytes']:
                raise RuntimeError('component_size')
            hasher = hashlib.sha256() if row['algorithm'] == 'sha256' else hashlib.sha1()
            if row['algorithm'] == 'git-blob-sha1':
                hasher.update(('blob '+str(row['bytes'])+'\0').encode())
            with file.open('rb') as stream:
                for chunk in iter(lambda: stream.read(1024*1024), b''):
                    hasher.update(chunk)
            if hasher.hexdigest() != row['digest']:
                raise RuntimeError('component_hash')
        torch.set_num_threads(8)
        def load_checked(cls, subfolder):
            component, info = cls.from_pretrained(
                str(model_root / subfolder), torch_dtype=torch.float32,
                local_files_only=True, use_safetensors=True, output_loading_info=True)
            for key in ['missing_keys', 'unexpected_keys', 'mismatched_keys', 'error_msgs']:
                if info.get(key):
                    raise RuntimeError('component_loading_diagnostics_'+subfolder+'_'+key)
            return component.float().cpu().eval()
        transformer = load_checked(Flux2Transformer2DModel, 'transformer')
        text_encoder = load_checked(Qwen3ForCausalLM, 'text_encoder')
        vae = load_checked(AutoencoderKLFlux2, 'vae')
        tokenizer = Qwen2TokenizerFast.from_pretrained(str(model_root / 'tokenizer'), local_files_only=True)
        scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
            str(model_root / 'scheduler'), local_files_only=True)
        pipe = Flux2KleinPipeline(transformer=transformer, text_encoder=text_encoder,
                                  vae=vae, tokenizer=tokenizer, scheduler=scheduler,
                                  is_distilled=True)
        pipe = pipe.to('cpu')
        pipe.set_progress_bar_config(disable=True)
        if not json.loads((model_root / 'model_index.json').read_text())['is_distilled'] or not pipe.config.is_distilled:
            raise RuntimeError('wrong_variant')
        formatted = pipe.tokenizer.apply_chat_template(
            [{'role': 'user', 'content': PROMPT}], tokenize=False,
            add_generation_prompt=True, enable_thinking=False)
        token_count = len(pipe.tokenizer(formatted, truncation=False)['input_ids'])
        if not 0 < token_count <= 512:
            raise RuntimeError('prompt_would_truncate')
    
        def encode():
            import io
            import numpy as np
            from PIL import Image
            def check_latents(pipeline, step, timestep, values):
                if not torch.isfinite(values['latents']).all():
                    raise RuntimeError('nonfinite_latents')
                return values
            with torch.inference_mode():
                image = pipe(prompt=PROMPT, height=512, width=512, num_inference_steps=4,
                             guidance_scale=1.0, max_sequence_length=512,
                             num_images_per_prompt=1, output_type='np',
                             callback_on_step_end=check_latents,
                             callback_on_step_end_tensor_inputs=['latents'],
                             generator=torch.Generator(device='cpu').manual_seed(0)).images[0]
            if image.shape != (512, 512, 3) or not np.isfinite(image).all():
                raise RuntimeError('image_shape_or_finiteness')
            if image.min() < 0 or image.max() > 1:
                raise RuntimeError('image_range')
            image = Image.fromarray((image*255).round().astype('uint8'), mode='RGB')
            buf = io.BytesIO()
            image.save(buf, format='PNG')
            return buf.getvalue(), token_count
        return encode
    
    
    PYSERVER

    Expected result: First complete native loader and PNG-validation fragment written.

  3. 3. Complete and verify private transport

    Append fixed-input private serial HTTP transport. No model code runs in syntax/hash checks.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    cat >> flux2_image.py <<'PYSERVER'
    def create_server(encoder, key, port=PORT):
        if not isinstance(key, str) or len(key) != 64 or any(c not in '0123456789abcdef' for c in key):
            raise ValueError('key_format')
    
        class Handler(BaseHTTPRequestHandler):
            def setup(self):
                super().setup()
                self.connection.settimeout(5)
    
            def log_message(self, *_):
                pass  # Do not log Bearer keys or request bodies.
    
            def reply(self, status, obj):
                data = json.dumps(obj, allow_nan=False, separators=(',', ':')).encode()
                self.send_response(status)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Content-Length', str(len(data)))
                self.send_header('Connection', 'close')
                self.end_headers()
                self.wfile.write(data)
                self.close_connection = True
    
            def authorized(self):
                values = self.headers.get_all('Authorization', [])
                return len(values) == 1 and hmac.compare_digest(values[0].encode(), ('Bearer ' + key).encode())
    
            def do_GET(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/health':
                    return self.reply(404, {'error': 'not_found'})
                return self.reply(200, {'ready': True, 'padded_prompt_positions': 512, 'configured_image_latent_positions': 1024,
                                        'generated_text_tokens': None})
    
            def do_POST(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/v1/fixed-image':
                    return self.reply(404, {'error': 'not_found'})
                if self.headers.get('Transfer-Encoding') is not None:
                    return self.reply(400, {'error': 'transfer_encoding_unsupported'})
                lengths = self.headers.get_all('Content-Length', [])
                if len(lengths) != 1:
                    return self.reply(411, {'error': 'content_length_required'})
                raw = lengths[0]
                if not raw or len(raw) > 4 or any(c not in '0123456789' for c in raw):
                    return self.reply(413, {'error': 'invalid_or_excessive_length'})
                n = int(raw)
                if n < 1 or n > 256:
                    return self.reply(413, {'error': 'body_limit'})
                if self.headers.get_content_type() != 'application/json':
                    return self.reply(415, {'error': 'json_required'})
                try:
                    body = self.rfile.read(n)
                except (TimeoutError, OSError):
                    return self.reply(408, {'error': 'body_timeout'})
                if len(body) != n:
                    return self.reply(400, {'error': 'incomplete_body'})
                try:
                    def unique(pairs):
                        obj = {}
                        for k, v in pairs:
                            if k in obj:
                                raise ValueError('duplicate_key')
                            obj[k] = v
                        return obj
                    request = json.loads(body.decode('utf-8'), object_pairs_hook=unique,
                                         parse_constant=lambda _: (_ for _ in ()).throw(ValueError('constant')))
                except (ValueError, UnicodeError, RecursionError):
                    return self.reply(400, {'error': 'invalid_json'})
                if type(request) is not dict or request != {'fixture': 'author-t2i-v1'}:
                    return self.reply(400, {'error': 'fixed_fixture_required'})
                try:
                    result = validate_image(encoder())
                except Exception:
                    return self.reply(500, {'error': 'pipeline_or_png_failure'})
                return self.reply(200, result)
    
        return HTTPServer(('127.0.0.1', port), Handler)
    
    
    if __name__ == '__main__':
        secret = (ROOT / '.api-key').read_text().strip()
        encoder = make_encoder()
        with create_server(encoder, secret) as server:
            server.serve_forever()
    PYSERVER
    .venv/bin/python -m py_compile flux2_image.py
    sha256sum --check <<'SHA256'
    d4e624292a3cec1f07856e464c0020b3d2db6591555e04ffe673ac3f2dffaefb  flux2_image.py
    SHA256

    Expected result: Exact wrapper bytes and Python syntax verify without model loading.

Start the model

  1. 1. Start the complete CPU image experiment

    Foreground serial endpoint. Full pipeline load and first image may be slow or exceed RAM; no fit/speed is promised. Explicit native loaders select all model_index components, reject loading diagnostics and assemble the native pipeline with offline safetensors and CPU float32; no VAE/text-encoder substitution. Each denoising-step latent tensor and final NumPy image must be finite before PNG conversion.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 .venv/bin/python flux2_image.py

    Expected result: After actual reader-side component verification and native load, loopback 127.0.0.1:8995 serves. Stop on component/import/load/template errors.

Connect to your endpoint

http://127.0.0.1:8995/v1/fixed-image

Same trusted desktop loopback only.

Access: loopback. Private Bearer key required for health and image generation. Send a JSON object with fixture set to author-t2i-v1. Response includes base64 PNG, dimensions, prompt positions, steps and seed. No arbitrary prompts, uploads, URLs or files are accepted.

Verify the setup

  1. 1. Validate the PNG and inspect the generated image

    Run in a second server terminal. Check private health, missing-key 401, fixed response metadata and a valid decoded 512 RGB PNG. Inspect image.png yourself for the red cube/blue sphere on white; valid dimensions do not establish semantic fidelity, safety or quality. No generated example or success claim is supplied.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    FLUX_KEY="$(cat .api-key)"
    curl --fail http://127.0.0.1:8995/health -H "Authorization: Bearer $FLUX_KEY"
    curl --fail http://127.0.0.1:8995/v1/fixed-image -H "Authorization: Bearer $FLUX_KEY" -H 'Content-Type: application/json' -d '{"fixture":"author-t2i-v1"}' -o image.json
    curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8995/health
    unset FLUX_KEY
    .venv/bin/python - <<'PYVERIFY'
    import base64,io,json
    from PIL import Image
    d=json.load(open('image.json'));assert d['generated_text_tokens'] is None
    assert d['image_shape']==[512,512,3] and d['padded_prompt_positions']==512
    assert 0<d['prompt_tokens_before_padding']<=512 and d['configured_image_latent_positions']==1024
    assert d['denoising_steps']==4 and d['seed']==0
    b=base64.b64decode(d['png_base64'],validate=True);assert len(b)<=8*1024*1024
    with Image.open(io.BytesIO(b)) as image:
     assert image.format=='PNG' and image.size==(512,512) and image.mode=='RGB';image.verify()
    open('image.png','wb').write(b)
    print('Inspect image.png; PNG validity is not semantic quality proof.')
    PYVERIFY

    Expected result: Authenticated health/generation return structured JSON; missing auth 401. Exact PNG metadata/Pillow verify passes only after reader execution. Inspect and record actual visual defects or loading failures.

Performance evidence

No compatible measurement available. Documented support and estimated memory do not establish response speed.

Limits and troubleshooting

  • Complete official 4B distilled bundle is selected, not transformer-only 7.75 GB weights or9B/base/dev. Main tensor file metadata plus all required configs/processors are pinned; no model binary was downloaded.
  • 512 configured prompt positions include the native user template with thinking disabled. Actual pre-padding prompt count is checked at reader execution; too-long prompts stop before native silent truncation. This is not 40960 theoretical Qwen3 context or 1024 image latent positions.
  • Native four-step distilled image synthesis returns PNG pixels, not autoregressive output tokens. Guidance_scale 1.0 avoids CFG; no chat completion/reasoning output or text-generation benchmark is claimed.
  • Full CPU float32 through native .to(cpu) is source-qualified, not installed/executed compatibility proof. 64 GiB RAM, 80 GiB disk/eight threads are planning allocations; fit, peak memory, latency and image quality remain unknown.
  • Original fixed geometric prompt is not a third-party image fixture. Manual inspection remains necessary; fixed seed does not guarantee equal bytes across hardware/library versions. No downstream safety classifier or commercial suitability is claimed.
  • Reader-authored serial HTTPServer has private key/loopback, bounded strict fixed JSON and PNG-size/shape guards, but no inference deadline, multiuser robustness or public production-service guarantee.
  • All components are verified before offline local loading; no remote-code trust or unpinned auxiliary download is selected. Publisher Git blob SHA1 is identified accurately; weight SHA256s are metadata until reader actual download.
  • Fake PNG protocol tests prove only request/auth/output boundary handling, not actual native tensor finiteness, denoising correctness, prompt adherence, image safety or runtime fit. Native callbacks and pre-PNG guards require finite tensors/arrays at reader execution; they have not run on model weights here.
  • Image editing, multiple references, other resolutions, base variants, alternate supported GPU/quantized/offload backends and other useful modes remain open; this pair does not finalize family coverage.
Component/hash/version or offline load error
Stop and restore exact 19-file manifest and publisher revision. Do not float to main, omit text encoder/VAE or substitute standalone transformer-only weights. Keep safetensors/local_files_only; stop on unresolved dependency errors.
Prompt budget or wrong variant error
Use exact fixed prompt/tokenizer/template and distilled model_index. The pre-padding formatted prompt must fit 512 positions. Do not hide truncation, switch model variants or treat padding count as actual semantic prompt length.
401 or rejected body
Use preserved matching private 64-character key and exact fixture selector. Arbitrary prompts/images/URLs/files or extra fields are rejected.
Slow, out-of-memory, black or inaccurate image
Record actual load/generation failure and inspect PNG. 64 GiB is not measured fit and valid dimensions do not establish semantic quality. Do not claim a smaller/partial pipeline is equivalent; stop the process if resource use is unacceptable.

Stop the model

  1. 1. Stop the private image service

    Press Ctrl+C in its foreground terminal and unset client key. Confirm local loopback endpoint no longer serves.

    Command
    unset FLUX_KEY

    Expected result: Pipeline process and endpoint stop; generated files remain until explicitly removed.

Uninstall

  1. 1. Remove dedicated experiment and selected model cache

    Preserve desired outputs/audit/license records first. Confirm the dedicated directory before deletion. The model-specific download cache is separate; remove only this exact cache if no other experiment needs it. Shared Ubuntu packages remain.

    Command
    rm -r -- "$HOME/flux2-klein4b-image"
    rm -r -- "$HOME/.cache/huggingface/hub/models--black-forest-labs--FLUX.2-klein-4B"

    Expected result: Dedicated bundle/venv/wrapper/key/outputs and this selected model cache are removed if present.

Sources and verification

Sources checked Oct 16, 2026. Verification source

Compare this configuration
Cloud serverDocumented setupSources checked Oct 16, 2026

Generate a fixed 512 px image with FLUX.2 Klein4B on a private CPU VM

Use the complete official FLUX.2 Klein4B distilled Diffusers bundle: transformer, Qwen3 text encoder, tokenizer, scheduler and VAE. Native CPU float32 generates one 512 × 512 PNG from a fixed original geometric prompt in four denoising steps. The configured prompt bound is 512 positions including its chat template; image latent positions are separate. This private educational experiment has unknown CPU fit, speed and image quality.

Hardware
AWS r7i.2xlarge, 64 GiB RAM, Ubuntu x86_64
Runtime
Native Diffusers FLUX.2 Klein4B complete CPU float32 image pipeline diffusers0.37.1 source ad3a3afc3a4d3068bbb12f58129c855087ffc6d6; Transformers4.57.6; HFhub0.36.2; torch2.6.0+cpu; Python3.12 · CPU
Model format
Diffusers safetensors bundle · Whole native pipeline CPU float32; no CUDA/offload/quantization/custom kernel · Unquantized official bundle; selected complete CPU float32
Workload
512 token context · 1 concurrent

Based on source documentation; this setup has not been executed by OpenTools. Memory: unknown. No compatible measurement available.

Read the complete setup guide

Before you start

  • Use an owned AWS r7i.2xlarge Ubuntu 24.04 x86_64 VM in us-east-1 with 64 GiB RAM, eight vCPUs and chosen 80 GiB gp3 volume. Restrict existing SSH access and keep service 8995 closed. Provisioning and applicable billing are reader actions.
  • Review the actual Apache 2.0 grant for this exact 4B bundle and dependency licenses. This guide uses the distilled 4B checkpoint, not 9B/base/dev or a transformer-only single-file workflow.
  • Use Python 3.12, torch 2.6 CPU, Diffusers 0.37.1, Transformers 4.57.6, Hugging Face Hub 0.36.2, Accelerate 1.12.0 and safetensors 0.7.0. Source metadata supports their version-range intersection; none has been installed or model-tested here. Save resolution and stop on pip check/import/shape errors.
  • The private endpoint accepts only its fixed benign geometric prompt selector, no arbitrary prompts/images/URLs/files. Generated imagery still needs human inspection for prompt adherence, artifacts and appropriateness; no downstream suitability is promised.

Exact model and requirements

Artifact:
Black Forest Labs / FLUX.2 Klein4B distilled complete Diffusers bundle · checkpoint black-forest-labs/FLUX.2-klein-4B
Publisher revision:
e7b7dc27f91deacad38e78976d1f2b499d76a294
Artifact digest:
A multi-file pipeline has no single publisher aggregate byte digest. Each of19 required files is independently bound in the reader manifest by publisher LFS SHA256 or exact Git blob SHA1.
Download size:
14.87 GiB
Memory requirement (unknown):
Unknown — No measured full-process peak for full FP32 transformer/text encoder/VAE plus512image activations.
System RAM / disk:
Unknown / Unknown
Placement:
single device. One CPU host, one prompt, one image, one serial request. All complete native modules remain on CPU float32; no GPU or disk offload.
KV cache:
No autoregressive text generation or retained text K/V cache. Native Qwen3 encoder runs use_cache=False; diffusion latents are distinct.
Workload:
512 token context; 512 input tokens; Not applicable; concurrency 1; batch 1; cache cold; repetitions Unknown.
Tokenizer:
Bundle Qwen2TokenizerFast and native user chat template, thinking disabled;512 padded prompt positions · e7b7dc27f91deacad38e78976d1f2b499d76a294
Sampling / reasoning:
Fixed original prompt: a red cube beside a blue sphere on white. Complete formatted prompt is counted before native truncation and rejected above 512. Native pipeline pads to 512 positions, uses Qwen3 hidden layers 9/18/27 with no text generation/cache. One512 × 512 image, four distilled steps, guidance_scale 1.0, CPU generator seed 0. Seed is a reproducibility parameter, not a cross-hardware image equality guarantee. 512 is the chosen native prompt-encoder maximum including template; the unpadded count is reported at reader execution. It is not the Qwen3 theoretical 40960 capacity or total joint image/text attention. VAE downsampling8 and2 × 2 latent packing give 1024 image latent positions at 512square, separate from text. Four steps are the publisher distilled recipe, not a text sampler.
Runtime prerequisites:
Ubuntu 24.04 x86_64; See prerequisites above.

Official artifact · License

The actual bundle LICENSE.md and publisher model card grant FLUX.2 Klein4B under Apache License2.0, including this selected complete bundle. Preserve applicable license/copyright/NOTICE and change/patent conditions. Do not transfer noncommercial 9B or FLUX.2 dev terms from names in VAE metadata to this 4B bundle. Publisher encourages safety filtering; its card says recommendations do not modify the grant. No safety classifier is claimed here; fixed benign geometry prompt, manual inspection and applicable laws still matter. This is no legal clearance or trademark endorsement.

  • 15,964,212,614 bytes is the sum of four official safetensors file sizes, not full installed footprint or runtime RAM. The standalone7.75 GB transformer file alone is not the complete pipeline.
  • 64 GiB RAM, 80 GiB disk and eight threads are planning choices. CPU float32 increases weight storage relative to source tensor dtype; image attention, decoder, temporary load buffers, packages and OS add unknown peak memory.

One-time setup and hardware

hardware
Not applicable — AWS hardware acquisition is included in compute rental; no purchased GPU required. Any separate hardware cost is not applicable.
AWS hardware acquisition is included in compute rental; no purchased GPU required. Any separate hardware cost is not applicable. Tax: unknown.
setup
Unknown — Initial provisioning/build time and labor cost are unknown.
Initial provisioning/build time and labor cost are unknown. Tax: unknown.

Recurring costs

compute
$0.53 / hour
AWS published us-east-1 Linux On-Demand quote USD 0.5292 per billed instance-hour, no paid preinstalled software. Eight billed hours cost USD 4.2336; 24 hours USD 12.7008 compute. Idle running hours bill. No discount, Spot or reservation assumed. Storage, egress, tax and administration additional; capacity unknown. Region: us-east-1. SKU: r7i.2xlarge Linux On-Demand. Tax: excluded. Price dated Oct 15, 2026. Price source
storage
$0.08 / GB month
Captured us-east-1 gp3 quote USD 0.08 per GB-month, with GB defined as 1024^3 bytes. Selected 80 GiB root volume costs USD 6.40 per full month, prorated by provisioned duration, at baseline 3000 IOPS and 125 MB/s. No provisioned extras assumed; retained snapshots/resources separate. Storage persists after stop; tax excluded. Region: us-east-1. SKU: EBS gp3 baseline 3000 IOPS / 125 MB/s. Tax: excluded. Price dated Oct 16, 2026. Price source
egress
Unknown — Data transfer price, allowances and traffic volume are unknown.
Data transfer price, allowances and traffic volume are unknown. Tax: unknown.
administration
Unknown — Maintenance labor rate and hours are unknown.
Maintenance labor rate and hours are unknown. Tax: unknown.

Install the runtime

  1. 1. Install the compatible native CPU stack

    Use a dedicated virtualenv and official matched CPU wheel hashes. Diffusers 0.37.1 is chosen because its HFhub range intersects Transformers 4.57.6; latest Diffusers 0.41.0 requires a different hub generation and is not substituted. No all-extra, CUDA, offload, optional flash kernel or remote code is enabled.

    Command
    set -e
    sudo apt-get update
    sudo apt-get install -y python3.12 python3.12-venv curl ca-certificates openssl
    mkdir -p "$HOME/flux2-klein4b-image/model"
    cd "$HOME/flux2-klein4b-image"
    python3.12 -m venv .venv
    .venv/bin/python -m pip install 'https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=59e78aa0c690f70734e42670036d6b541930b8eabbaa18d94e090abf14cc4d91' 'https://download-r2.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp312-cp312-linux_x86_64.whl#sha256=d6874431e678ba107b60a83f255c33f3755f06bad587b1b919aa514ec325dcd8'
    .venv/bin/python -m pip install 'diffusers==0.37.1' 'transformers==4.57.6' 'tokenizers==0.22.2' 'huggingface-hub==0.36.2' 'accelerate==1.12.0' 'safetensors==0.7.0' 'numpy==2.2.3' 'Pillow==11.1.0'
    .venv/bin/python -m pip check
    .venv/bin/python -m pip freeze > resolved-environment.txt

    Expected result: Pinned direct versions resolve and pip check passes; exact transitive/system resolution is saved. No native execution/compatibility or fit claim.

Download the model

  1. 1. Write the complete immutable component manifest

    This reader-authored manifest selects 19 exact bundle files, including all four weight files. LFS blobs use publisher SHA256; regular source files use their exact Git blob SHA1. SHA1 is Git identity, not a modern adversarial integrity guarantee; immutable official HTTPS revision remains the provenance boundary.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    cat > component-manifest.json <<'MANIFEST'
    {
      "repository": "black-forest-labs/FLUX.2-klein-4B",
      "revision": "e7b7dc27f91deacad38e78976d1f2b499d76a294",
      "files": [
        {
          "file": "LICENSE.md",
          "bytes": 9584,
          "algorithm": "git-blob-sha1",
          "digest": "0afa7632c1a195ea6627a3c53e6ebf18d1be3aa5"
        },
        {
          "file": "model_index.json",
          "bytes": 446,
          "algorithm": "git-blob-sha1",
          "digest": "554a39bf1019138dd0b28809b745df8ef33657c8"
        },
        {
          "file": "scheduler/scheduler_config.json",
          "bytes": 486,
          "algorithm": "git-blob-sha1",
          "digest": "7e53605e2643440b310fa554f087641893883c5f"
        },
        {
          "file": "text_encoder/config.json",
          "bytes": 1536,
          "algorithm": "git-blob-sha1",
          "digest": "86f951d170cfd1fdc1c8a57a041e05c27b7b789b"
        },
        {
          "file": "text_encoder/generation_config.json",
          "bytes": 214,
          "algorithm": "git-blob-sha1",
          "digest": "f5af93d0c7764fa38e3b1cd3aaba1b6307246d3b"
        },
        {
          "file": "text_encoder/model-00001-of-00002.safetensors",
          "bytes": 4967215360,
          "algorithm": "sha256",
          "digest": "8c0506e7f4936fa7e26183a4fd8da4e2bdbc5990ba64ae441f965d51228f36ea"
        },
        {
          "file": "text_encoder/model-00002-of-00002.safetensors",
          "bytes": 3077766632,
          "algorithm": "sha256",
          "digest": "82f2bd839378541b0557bfabaf37c7d3d637071fdcb73302dedd7cf61162ce07"
        },
        {
          "file": "text_encoder/model.safetensors.index.json",
          "bytes": 32855,
          "algorithm": "git-blob-sha1",
          "digest": "b65d8063815c8679d85bb919337b36fae8de75cf"
        },
        {
          "file": "tokenizer/added_tokens.json",
          "bytes": 707,
          "algorithm": "git-blob-sha1",
          "digest": "b54f9135e44c1e81047e8d05cb027af8bc039eed"
        },
        {
          "file": "tokenizer/chat_template.jinja",
          "bytes": 4168,
          "algorithm": "git-blob-sha1",
          "digest": "01be9b307daa2d425f7c168c9fb145a286e0afb4"
        },
        {
          "file": "tokenizer/merges.txt",
          "bytes": 1671853,
          "algorithm": "git-blob-sha1",
          "digest": "31349551d90c7606f325fe0f11bbb8bd5fa0d7c7"
        },
        {
          "file": "tokenizer/special_tokens_map.json",
          "bytes": 613,
          "algorithm": "git-blob-sha1",
          "digest": "ac23c0aaa2434523c494330aeb79c58395378103"
        },
        {
          "file": "tokenizer/tokenizer.json",
          "bytes": 11422654,
          "algorithm": "sha256",
          "digest": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4"
        },
        {
          "file": "tokenizer/tokenizer_config.json",
          "bytes": 5404,
          "algorithm": "git-blob-sha1",
          "digest": "ddaf69808214a44fdd26d3785b66c1367c78277a"
        },
        {
          "file": "tokenizer/vocab.json",
          "bytes": 2776833,
          "algorithm": "git-blob-sha1",
          "digest": "4783fe10ac3adce15ac8f358ef5462739852c569"
        },
        {
          "file": "transformer/config.json",
          "bytes": 541,
          "algorithm": "git-blob-sha1",
          "digest": "f21592ae2bc9f43052e78785bde7414f219ae369"
        },
        {
          "file": "transformer/diffusion_pytorch_model.safetensors",
          "bytes": 7751109744,
          "algorithm": "sha256",
          "digest": "9f29f9edcfdae452a653ffb51a534ca4decd389952c225724ff3b94042612a6e"
        },
        {
          "file": "vae/config.json",
          "bytes": 821,
          "algorithm": "git-blob-sha1",
          "digest": "c3f38eb4c188a96a462519159356bd0fcc28cd14"
        },
        {
          "file": "vae/diffusion_pytorch_model.safetensors",
          "bytes": 168120878,
          "algorithm": "sha256",
          "digest": "ca70d2202afe6415bdbcb8793ba8cd99fd159cfe6192381504d6c4d3036e0f04"
        }
      ]
    }
    MANIFEST
    sha256sum --check <<'SHA256'
    f03107bcd53ab215deeee44072681f15658a94a755ae242bb42741dfca6405e3  component-manifest.json
    SHA256

    Expected result: Exact complete manifest verifies before any reader weight retrieval.

  2. 2. Retrieve and verify all native components

    Reader-only downloads from one exact ungated publisher revision. Do not fetch the standalone transformer-only file or float revisions. Stop on any size/hash mismatch; no model loading occurs in this retrieval script.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    .venv/bin/python - <<'PYDOWNLOAD'
    import hashlib,json,pathlib,shutil
    from huggingface_hub import hf_hub_download
    m=json.load(open('component-manifest.json'))
    for row in m['files']:
     src=hf_hub_download(m['repository'],row['file'],revision=m['revision'])
     dst=pathlib.Path('model')/row['file'];dst.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(src,dst)
     assert dst.stat().st_size==row['bytes']
     h=hashlib.sha256() if row['algorithm']=='sha256' else hashlib.sha1()
     if row['algorithm']=='git-blob-sha1':h.update(('blob '+str(row['bytes'])+'\0').encode())
     with dst.open('rb') as f:
      for chunk in iter(lambda:f.read(1024*1024),b''):h.update(chunk)
     assert h.hexdigest()==row['digest'],row['file']
    print('All19 components verified; model loading has not run.')
    PYDOWNLOAD

    Expected result: All 19 exact files verify. Download cache duplicates disk usage;80 GiB is an allocation, not a measured peak. Offline loading follows only after complete verification.

Configure

  1. 1. Create a stable private service key

    Generate only when absent; preserve existing access. Do not print or share the local key.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    umask 077
    if [ ! -e .api-key ]; then openssl rand -hex 32 > .api-key; fi
    chmod 600 .api-key
    test -s .api-key || { echo "Restore empty key before continuing"; exit 1; }

    Expected result: Private64-character lowercase hexadecimal key preserved or created.

  2. 2. Create the native image harness

    Reader-authored wrapper, not publisher server. It rechecks every component before explicit native local_files_only safetensors component loading. Reject any missing, unexpected, mismatched or error diagnostic before assembling the native pipeline. All modules use CPU float32; fixed prompt is chat-formatted with thinking disabled and checked before native 512 truncation. No full model binary was fetched or executed by the author.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    cat > flux2_image.py <<'PYSERVER'
    """Reader-authored educational fixed-input FLUX.2 Klein4B harness; not publisher server."""
    import hashlib
    import hmac
    import json
    import math
    import pathlib
    from http.server import BaseHTTPRequestHandler, HTTPServer
    
    ROOT = pathlib.Path(__file__).resolve().parent
    PORT = 8995
    
    
    PROMPT = 'A simple red cube beside a blue sphere on a plain white background, clean studio illustration.'
    
    
    def validate_image(result):
        import base64
        import struct
        png, count = result
        if type(count) is not int or not 0 < count <= 512:
            raise ValueError('prompt_budget')
        if not isinstance(png, bytes) or not 57 <= len(png) <= 8*1024*1024:
            raise ValueError('png_bound')
        if png[:8] != b'\x89PNG\r\n\x1a\n' or png[12:16] != b'IHDR':
            raise ValueError('png_header')
        if struct.unpack('>II', png[16:24]) != (512, 512) or png[-8:-4] != b'IEND':
            raise ValueError('png_shape_or_end')
        return {'fixture': 'author-t2i-v1', 'prompt': PROMPT,
                'prompt_tokens_before_padding': count, 'padded_prompt_positions': 512,
                'image_shape': [512, 512, 3], 'configured_image_latent_positions': 1024,
                'denoising_steps': 4, 'seed': 0, 'guidance_scale': 1.0,
                'mime_type': 'image/png', 'png_base64': base64.b64encode(png).decode('ascii'),
                'generated_text_tokens': None}
    
    
    def make_encoder():
        # Reader-run only. Fake tests do not import Torch, Diffusers or Pillow.
        import os
        os.environ['HF_HUB_OFFLINE'] = '1'
        os.environ['TRANSFORMERS_OFFLINE'] = '1'
        import torch
        from diffusers import (Flux2KleinPipeline, Flux2Transformer2DModel,
                               AutoencoderKLFlux2, FlowMatchEulerDiscreteScheduler)
        from transformers import Qwen3ForCausalLM, Qwen2TokenizerFast
        manifest = json.loads((ROOT / 'component-manifest.json').read_text())
        model_root = ROOT / 'model'
        for row in manifest['files']:
            file = model_root / row['file']
            if file.stat().st_size != row['bytes']:
                raise RuntimeError('component_size')
            hasher = hashlib.sha256() if row['algorithm'] == 'sha256' else hashlib.sha1()
            if row['algorithm'] == 'git-blob-sha1':
                hasher.update(('blob '+str(row['bytes'])+'\0').encode())
            with file.open('rb') as stream:
                for chunk in iter(lambda: stream.read(1024*1024), b''):
                    hasher.update(chunk)
            if hasher.hexdigest() != row['digest']:
                raise RuntimeError('component_hash')
        torch.set_num_threads(8)
        def load_checked(cls, subfolder):
            component, info = cls.from_pretrained(
                str(model_root / subfolder), torch_dtype=torch.float32,
                local_files_only=True, use_safetensors=True, output_loading_info=True)
            for key in ['missing_keys', 'unexpected_keys', 'mismatched_keys', 'error_msgs']:
                if info.get(key):
                    raise RuntimeError('component_loading_diagnostics_'+subfolder+'_'+key)
            return component.float().cpu().eval()
        transformer = load_checked(Flux2Transformer2DModel, 'transformer')
        text_encoder = load_checked(Qwen3ForCausalLM, 'text_encoder')
        vae = load_checked(AutoencoderKLFlux2, 'vae')
        tokenizer = Qwen2TokenizerFast.from_pretrained(str(model_root / 'tokenizer'), local_files_only=True)
        scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
            str(model_root / 'scheduler'), local_files_only=True)
        pipe = Flux2KleinPipeline(transformer=transformer, text_encoder=text_encoder,
                                  vae=vae, tokenizer=tokenizer, scheduler=scheduler,
                                  is_distilled=True)
        pipe = pipe.to('cpu')
        pipe.set_progress_bar_config(disable=True)
        if not json.loads((model_root / 'model_index.json').read_text())['is_distilled'] or not pipe.config.is_distilled:
            raise RuntimeError('wrong_variant')
        formatted = pipe.tokenizer.apply_chat_template(
            [{'role': 'user', 'content': PROMPT}], tokenize=False,
            add_generation_prompt=True, enable_thinking=False)
        token_count = len(pipe.tokenizer(formatted, truncation=False)['input_ids'])
        if not 0 < token_count <= 512:
            raise RuntimeError('prompt_would_truncate')
    
        def encode():
            import io
            import numpy as np
            from PIL import Image
            def check_latents(pipeline, step, timestep, values):
                if not torch.isfinite(values['latents']).all():
                    raise RuntimeError('nonfinite_latents')
                return values
            with torch.inference_mode():
                image = pipe(prompt=PROMPT, height=512, width=512, num_inference_steps=4,
                             guidance_scale=1.0, max_sequence_length=512,
                             num_images_per_prompt=1, output_type='np',
                             callback_on_step_end=check_latents,
                             callback_on_step_end_tensor_inputs=['latents'],
                             generator=torch.Generator(device='cpu').manual_seed(0)).images[0]
            if image.shape != (512, 512, 3) or not np.isfinite(image).all():
                raise RuntimeError('image_shape_or_finiteness')
            if image.min() < 0 or image.max() > 1:
                raise RuntimeError('image_range')
            image = Image.fromarray((image*255).round().astype('uint8'), mode='RGB')
            buf = io.BytesIO()
            image.save(buf, format='PNG')
            return buf.getvalue(), token_count
        return encode
    
    
    PYSERVER

    Expected result: First complete native loader and PNG-validation fragment written.

  3. 3. Complete and verify private transport

    Append fixed-input private serial HTTP transport. No model code runs in syntax/hash checks.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    cat >> flux2_image.py <<'PYSERVER'
    def create_server(encoder, key, port=PORT):
        if not isinstance(key, str) or len(key) != 64 or any(c not in '0123456789abcdef' for c in key):
            raise ValueError('key_format')
    
        class Handler(BaseHTTPRequestHandler):
            def setup(self):
                super().setup()
                self.connection.settimeout(5)
    
            def log_message(self, *_):
                pass  # Do not log Bearer keys or request bodies.
    
            def reply(self, status, obj):
                data = json.dumps(obj, allow_nan=False, separators=(',', ':')).encode()
                self.send_response(status)
                self.send_header('Content-Type', 'application/json')
                self.send_header('Content-Length', str(len(data)))
                self.send_header('Connection', 'close')
                self.end_headers()
                self.wfile.write(data)
                self.close_connection = True
    
            def authorized(self):
                values = self.headers.get_all('Authorization', [])
                return len(values) == 1 and hmac.compare_digest(values[0].encode(), ('Bearer ' + key).encode())
    
            def do_GET(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/health':
                    return self.reply(404, {'error': 'not_found'})
                return self.reply(200, {'ready': True, 'padded_prompt_positions': 512, 'configured_image_latent_positions': 1024,
                                        'generated_text_tokens': None})
    
            def do_POST(self):
                if not self.authorized():
                    return self.reply(401, {'error': 'unauthorized'})
                if self.path != '/v1/fixed-image':
                    return self.reply(404, {'error': 'not_found'})
                if self.headers.get('Transfer-Encoding') is not None:
                    return self.reply(400, {'error': 'transfer_encoding_unsupported'})
                lengths = self.headers.get_all('Content-Length', [])
                if len(lengths) != 1:
                    return self.reply(411, {'error': 'content_length_required'})
                raw = lengths[0]
                if not raw or len(raw) > 4 or any(c not in '0123456789' for c in raw):
                    return self.reply(413, {'error': 'invalid_or_excessive_length'})
                n = int(raw)
                if n < 1 or n > 256:
                    return self.reply(413, {'error': 'body_limit'})
                if self.headers.get_content_type() != 'application/json':
                    return self.reply(415, {'error': 'json_required'})
                try:
                    body = self.rfile.read(n)
                except (TimeoutError, OSError):
                    return self.reply(408, {'error': 'body_timeout'})
                if len(body) != n:
                    return self.reply(400, {'error': 'incomplete_body'})
                try:
                    def unique(pairs):
                        obj = {}
                        for k, v in pairs:
                            if k in obj:
                                raise ValueError('duplicate_key')
                            obj[k] = v
                        return obj
                    request = json.loads(body.decode('utf-8'), object_pairs_hook=unique,
                                         parse_constant=lambda _: (_ for _ in ()).throw(ValueError('constant')))
                except (ValueError, UnicodeError, RecursionError):
                    return self.reply(400, {'error': 'invalid_json'})
                if type(request) is not dict or request != {'fixture': 'author-t2i-v1'}:
                    return self.reply(400, {'error': 'fixed_fixture_required'})
                try:
                    result = validate_image(encoder())
                except Exception:
                    return self.reply(500, {'error': 'pipeline_or_png_failure'})
                return self.reply(200, result)
    
        return HTTPServer(('127.0.0.1', port), Handler)
    
    
    if __name__ == '__main__':
        secret = (ROOT / '.api-key').read_text().strip()
        encoder = make_encoder()
        with create_server(encoder, secret) as server:
            server.serve_forever()
    PYSERVER
    .venv/bin/python -m py_compile flux2_image.py
    sha256sum --check <<'SHA256'
    d4e624292a3cec1f07856e464c0020b3d2db6591555e04ffe673ac3f2dffaefb  flux2_image.py
    SHA256

    Expected result: Exact wrapper bytes and Python syntax verify without model loading.

Start the model

  1. 1. Start the complete CPU image experiment

    Foreground serial endpoint. Full pipeline load and first image may be slow or exceed RAM; no fit/speed is promised. Explicit native loaders select all model_index components, reject loading diagnostics and assemble the native pipeline with offline safetensors and CPU float32; no VAE/text-encoder substitution. Each denoising-step latent tensor and final NumPy image must be finite before PNG conversion.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 .venv/bin/python flux2_image.py

    Expected result: After actual reader-side component verification and native load, loopback 127.0.0.1:8995 serves. Stop on component/import/load/template errors.

Connect to your endpoint

http://127.0.0.1:8995/v1/fixed-image

VM loopback only; optional existing SSH tunnel: ssh -N -L 8995:127.0.0.1:8995 USER@VM_IP. Keep EC2 service port 8995 closed; shown checks run on server.

Access: loopback. Private Bearer key required for health and image generation. Send a JSON object with fixture set to author-t2i-v1. Response includes base64 PNG, dimensions, prompt positions, steps and seed. No arbitrary prompts, uploads, URLs or files are accepted.

Verify the setup

  1. 1. Validate the PNG and inspect the generated image

    Run in a second server terminal. Check private health, missing-key 401, fixed response metadata and a valid decoded 512 RGB PNG. Inspect image.png yourself for the red cube/blue sphere on white; valid dimensions do not establish semantic fidelity, safety or quality. No generated example or success claim is supplied.

    Command
    set -e
    cd "$HOME/flux2-klein4b-image"
    FLUX_KEY="$(cat .api-key)"
    curl --fail http://127.0.0.1:8995/health -H "Authorization: Bearer $FLUX_KEY"
    curl --fail http://127.0.0.1:8995/v1/fixed-image -H "Authorization: Bearer $FLUX_KEY" -H 'Content-Type: application/json' -d '{"fixture":"author-t2i-v1"}' -o image.json
    curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8995/health
    unset FLUX_KEY
    .venv/bin/python - <<'PYVERIFY'
    import base64,io,json
    from PIL import Image
    d=json.load(open('image.json'));assert d['generated_text_tokens'] is None
    assert d['image_shape']==[512,512,3] and d['padded_prompt_positions']==512
    assert 0<d['prompt_tokens_before_padding']<=512 and d['configured_image_latent_positions']==1024
    assert d['denoising_steps']==4 and d['seed']==0
    b=base64.b64decode(d['png_base64'],validate=True);assert len(b)<=8*1024*1024
    with Image.open(io.BytesIO(b)) as image:
     assert image.format=='PNG' and image.size==(512,512) and image.mode=='RGB';image.verify()
    open('image.png','wb').write(b)
    print('Inspect image.png; PNG validity is not semantic quality proof.')
    PYVERIFY

    Expected result: Authenticated health/generation return structured JSON; missing auth 401. Exact PNG metadata/Pillow verify passes only after reader execution. Inspect and record actual visual defects or loading failures.

Performance evidence

No compatible measurement available. Documented support and estimated memory do not establish response speed.

Limits and troubleshooting

  • Complete official 4B distilled bundle is selected, not transformer-only 7.75 GB weights or9B/base/dev. Main tensor file metadata plus all required configs/processors are pinned; no model binary was downloaded.
  • 512 configured prompt positions include the native user template with thinking disabled. Actual pre-padding prompt count is checked at reader execution; too-long prompts stop before native silent truncation. This is not 40960 theoretical Qwen3 context or 1024 image latent positions.
  • Native four-step distilled image synthesis returns PNG pixels, not autoregressive output tokens. Guidance_scale 1.0 avoids CFG; no chat completion/reasoning output or text-generation benchmark is claimed.
  • Full CPU float32 through native .to(cpu) is source-qualified, not installed/executed compatibility proof. 64 GiB RAM, 80 GiB disk/eight threads are planning allocations; fit, peak memory, latency and image quality remain unknown.
  • Original fixed geometric prompt is not a third-party image fixture. Manual inspection remains necessary; fixed seed does not guarantee equal bytes across hardware/library versions. No downstream safety classifier or commercial suitability is claimed.
  • Reader-authored serial HTTPServer has private key/loopback, bounded strict fixed JSON and PNG-size/shape guards, but no inference deadline, multiuser robustness or public production-service guarantee.
  • All components are verified before offline local loading; no remote-code trust or unpinned auxiliary download is selected. Publisher Git blob SHA1 is identified accurately; weight SHA256s are metadata until reader actual download.
  • Fake PNG protocol tests prove only request/auth/output boundary handling, not actual native tensor finiteness, denoising correctness, prompt adherence, image safety or runtime fit. Native callbacks and pre-PNG guards require finite tensors/arrays at reader execution; they have not run on model weights here.
  • Image editing, multiple references, other resolutions, base variants, alternate supported GPU/quantized/offload backends and other useful modes remain open; this pair does not finalize family coverage.
Component/hash/version or offline load error
Stop and restore exact 19-file manifest and publisher revision. Do not float to main, omit text encoder/VAE or substitute standalone transformer-only weights. Keep safetensors/local_files_only; stop on unresolved dependency errors.
Prompt budget or wrong variant error
Use exact fixed prompt/tokenizer/template and distilled model_index. The pre-padding formatted prompt must fit 512 positions. Do not hide truncation, switch model variants or treat padding count as actual semantic prompt length.
401 or rejected body
Use preserved matching private 64-character key and exact fixture selector. Arbitrary prompts/images/URLs/files or extra fields are rejected.
Slow, out-of-memory, black or inaccurate image
Record actual load/generation failure and inspect PNG. 64 GiB is not measured fit and valid dimensions do not establish semantic quality. Do not claim a smaller/partial pipeline is equivalent; stop the process if resource use is unacceptable.

Stop the model

  1. 1. Stop the private image service

    Press Ctrl+C in its foreground terminal and unset client key. Close optional SSH tunnel; stopping Python does not stop VM billing.

    Command
    unset FLUX_KEY

    Expected result: Pipeline process and endpoint stop; generated files remain until explicitly removed.

Uninstall

  1. 1. Remove dedicated experiment and selected model cache

    Preserve desired outputs/audit/license records first. Confirm the dedicated directory before deletion. The model-specific download cache is separate; remove only this exact cache if no other experiment needs it. Shared Ubuntu packages remain.

    Command
    rm -r -- "$HOME/flux2-klein4b-image"
    rm -r -- "$HOME/.cache/huggingface/hub/models--black-forest-labs--FLUX.2-klein-4B"

    Expected result: Dedicated bundle/venv/wrapper/key/outputs and this selected model cache are removed if present.

Shut down cloud resources

  1. 1. End cloud billing resources

    In EC2 us-east-1, stop for later reuse or terminate this exact VM. Review Delete on termination for its root volume. Delete unwanted detached EBS volumes, snapshots and reserved/Elastic/public IP resources. Stopped VM storage and retained resources may still bill. Check Bills/Cost Explorer later for final usage.

    Expected result: Exact instance is stopped/terminated as intended; no unwanted attached/retained billed resources remain. Instance compute stops only after its lifecycle transition, not when the Python service stops.

Sources and verification

Sources checked Oct 16, 2026. Verification source

Compare this configuration

Pricing and access

Downloadable weights use Apache 2.0; local compute and deployment still cost resources. Hosted BFL or fal access is billed by the selected service. Do not use token pricing for this image model.

Tools using FLUX.2 [klein] 4B

AI tools and products powered by this model.

Black Forest Labs logo

FLUX models for visual intelligence

1 Tools1 MCP Servers1 ModelsFounded 2024Freiburg im Breisgau, Germany
View full profile

Tools by Black Forest Labs

Other AI tools from the same organization.

MCP Servers by Black Forest Labs

Connect this tool to AI assistants via the Model Context Protocol.