Download code/PYTHON.md from sunandbean/flux2-klein-p100a: direct link, hf CLI and curl.
- Browser
- Download file 3.59 kB
-
https://huggingface.co/sunandbean/flux2-klein-p100a/resolve/main/code/PYTHON.md
- Command line
-
hf download hf://sunandbean/flux2-klein-p100a/code/PYTHON.md
-
curl -L -o PYTHON.md https://huggingface.co/sunandbean/flux2-klein-p100a/resolve/main/code/PYTHON.md
tt_flux2_klein — Python reference
FLUX.2 [klein] 4B on one Tenstorrent Blackhole p100a. The Qwen3-4B text encoder, the DiT and the VAE all run on the card; only the scheduler and the image post-processing are host.
Install
pip install -e . # inside a tt-metal / ttnn environment; ttnn is not on PyPI
pip install -e ".[server]" # also fastapi / uvicorn / pydantic
Weights
black-forest-labs/FLUX.2-klein-4B, pinned to e7b7dc27f91deacad38e78976d1f2b499d76a294,
downloaded into the HF cache on first use. KLEIN_SNAPSHOT points at a directory you manage instead.
klein's text_encoder is bit-identical to Qwen/Qwen3-4B (398 of 398 tensors), so
KLEIN_TEXT_ENCODER can point at that checkpoint if you already have it.
API
open_device(trace_region_size=0, l1_small_size=98304)
Opens a 1×1 mesh with DispatchCoreType.WORKER. Pair with close_device(dev).
KleinTT(dev, snapshot=None, te_dir=None, dit_prec=None, te_prec=None, device_vae=True)
Loads the text encoder, the DiT and (unless device_vae=False) the VAE onto the card.
KleinTT.generate(prompt, width, height, seed, images=None) -> (PIL.Image, Timing)
| Argument | Meaning |
|---|---|
width, height |
512–1920, multiples of 8, at most 1920×1088 pixels. |
images |
None for text to image; 1–3 PIL.Image for editing or reference-guided generation. They are passed at their original aspect ratio, reduced to ≤1024² area and center-cropped to a multiple of 16. |
seed |
torch.manual_seed(seed), matching diffusers. |
Steps are fixed at 4 with guidance_scale 1.0 — klein is distilled, so there is no CFG and each step
is a single DiT call.
Timing.values holds text_encoder_s, reference_encode_s, context_s, dit_s, vae_decode_s.
Lower-level entry points
| Method | |
|---|---|
encode(prompt) |
prompt embeddings: layers 9, 18 and 27 concatenated to [512, 7680] |
references(images) |
reference tokens and grids, with the 4-entry encode cache |
denoise(emb, width, height, seed, timing, ref_tokens, grids) |
the 4-step loop |
decode(latents) |
VAE decode on the card |
Reference encodings are cached on pixel content, last 4 kept (REF_CACHE_SIZE). Reusing a source
image across prompts skips the host VAE encode, about 2.9 s at 1024².
Modules
| Module | Role |
|---|---|
pipeline.py |
KleinTT, the end-to-end loop and the reference cache |
host.py |
host maths: prompt layout, token ids, RoPE, the 4-step sigma schedule and compute_empirical_mu, patch and latent handling, reference preprocessing |
common.py |
model-independent checkpoint and tensor helpers, shared with the tt-z-image-turbo port |
tt_dit.py |
5 dual-stream + 20 single-stream blocks on the card |
tt_text_encoder.py |
Qwen3-4B on the card |
tt_vae.py |
the VAE on the card, over vendor_tt_dit |
vendor_tt_dit/ |
tt_dit VAE from tt-metal 01d6e7b, with non-square slicing and GroupNorm grid fixes |
ref_algo.py |
torch reference implementations used by the verification scripts |
device.py |
open / close / DRAM stats / timing |
server.py |
the HTTP app |
Things that are easy to get wrong
- Modulation is global per step, not per block.
- All 512 padded text rows go into the DiT with no mask — that is upstream behaviour, reproduced here.
- Text-encoder layers 28–36 are never used.
- RoPE is 32 dims per axis at theta 2000 with
repeat_interleave_real=True(adjacent pairs). - Reference image i gets the
tcoordinate10 + 10*i.