Empirical finding: uni_pc is the closest match to upstream's
UniPCMultistep (same algorithm, just comfy's port), but its multistep
polynomial extrapolation works against you at high step counts when
the per-step clamp_denoised non-linearity compounds. At 38 steps it
produced bad output; at ~20 steps it works well and is noticeably
closer to the HF Space than dpmpp_2m at 38.
The earlier "uni_pc produces bad output, route around it" framing was
wrong -- the real issue was step count, not the sampler. Updated the
README to reflect that, and bumped the example workflow.
UniPC is multistep so 15-25 steps is the typical range for flow
models; pushing past 25 starts to amplify any per-step error
accumulation through the polynomial fit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README:
- Dropped marketing voice and bold flourishes.
- Removed the "How it works (the surgery)" paper-length section. The
short architecture pointer at the bottom tells the user where to look
in the code; anything more belongs in the code, not the README.
- Removed the "math verified bit-exact" framing. The unit tests verify
the math functions in isolation, not the end-to-end output, and
presenting it like proof of fidelity was overclaiming.
- Removed the contradictory uni_pc recommendation that conflicted with
the dpmpp recommendation a few lines later.
- New "Known limitations" section is honest about what differs from the
HF Space (static vs dynamic shift, sampler choice) instead of burying
those as a tooltip.
- Documented that image editing isn't supported.
Example workflow:
- Default sampler dpmpp_2m_sde -> dpmpp_2m. The SDE variant was a guess
based on math theory about the clamp_denoised nonlinearity; empirical
testing says dpmpp_2m matches the HF Space output more closely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AsymFLUX.2-klein is a text-to-image model -- the upstream HF card lists
it as t2i and the `image=` kwarg on `PixelFlux2KleinPipeline.__call__`
is plumbing inherited from `Flux2KleinPipeline`, not a trained-for
capability. Output quality on reference-conditioned generation never
matched the t2i output, and shipping a workflow + docs for an
unsupported path is debt.
The model-side `/s` reference-token scaling was already dropped in the
cleanup commit. This commit removes the user-facing artifacts:
- example_workflows/asymflux2_edit.json (deleted)
- README "Image editing" section (deleted)
The historical `Support image editing` commit (922550b) stays in the
log -- anyone curious about the design can still read it there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Practical finding: uni_pc + simple produces poor output even though it
is algorithmically the same as upstream's UniPCMultistep. Cause is the
hard nonlinearity introduced by our clamp_denoised hook: x.clamp(-1, 1)
in RGB space puts a kink in denoised(sigma), and UniPC's multistep
polynomial extrapolation requires denoised(sigma) to be smooth. SDE
samplers (dpmpp_2m_sde, dpmpp_sde) average over the kink with per-step
noise injection and produce clean output.
Both example workflows now ship sampler=dpmpp_2m_sde, scheduler=simple.
README sampler section updated with the explanation + the
clamp_denoised=False workaround if the user wants to A/B against uni_pc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
At low sigma, AsymFlow's `u_comp = (x_t_comp + s*u_a_comp) / sigma_clamped`
amplifies the orthogonal complement by ~3x at sigma=0.32 (our smallest
non-zero step), producing x0 magnitudes well outside the [-1, 1] gamut
that affine-normalised Oklab expects. Sampler accumulates this drift
over 38 steps → washed-out / oversaturated output.
`patch_clamp_denoised` registers a post-CFG hook that:
1. Decodes the predicted x0 from Oklab to sRGB in [-1, 1]
2. clamps to gamut
3. Re-encodes back to Oklab
This matches upstream `pipeline_pixelflux2_klein` `clamp_denoised`. The
hook composes with our orthogonal CFG hook -- comfy chains post-CFG
functions in registration order; orthog applies first (refines the CFG
bias), then clamp pulls the result back to gamut.
- surgery: patch_clamp_denoised(model_patcher) using oklab_math
decode/encode at the lakonik defaults (mean=(0.56, 0, 0.01), std=0.16);
isfinite fallback to standard denoised on any numerical issue.
- apply_adapter: new `clamp_denoised` toggle (default True).
- Both example workflows ship with the toggle on.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- surgery.py: in the wrapped forward, divide ref_latents by `s` (the
AsymFlow scale_buffer, ~2.4375) before handing them to comfy's flux2
`_forward`. This matches upstream's `condition_hidden_states / s` step
in `_AsymFlux2Transformer2DModel.forward` — without this, reference
tokens reach `x_embedder` at ~2.4x the magnitude the model trained
with and editing produces jank.
- example_workflows/asymflux2_edit.json: end-to-end edit workflow using
comfy's stock `ReferenceLatent` node. Encoded source image attaches to
positive conditioning; KSampler.latent_image stays as empty pixel
latent (sampling from pure noise, not img2img).
- README: editing section with the wiring diagram and the two common
mistakes (don't pipe encoded latent into KSampler.latent_image; don't
attach reference to the negative branch).
Comfy's flux2 `_forward` already supports ref_latents and uses
`ref_index_scale=10.0` which matches upstream's
`_prepare_condition_latent_ids(scale=10)` for position IDs — no other
wiring needed on our side.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four nodes that run the LakonLab AsymFLUX.2-klein 9B model natively in
ComfyUI on top of a stock FLUX.2-klein-base-9B loaded via Load Diffusion
Model. No diffusers, no lakonlab package required.
- AsymFLUX2 Apply Adapter: patches a FLUX.2-klein MODEL into AsymFLUX.2 via
ModelPatcher.add_object_patch (so the base model is never mutated):
swaps img_in / final_layer.linear / final_layer.adaLN_modulation[1] to
the adapter's 3ch x 16x16 patch shapes, attaches proj_buffer +
scale_buffer, wraps diffusion_model.forward with AsymFlow calibration +
velocity (orthogonal decomposition via proj_buffer), applies the
rank-256 LoRA, sets a flux-shift of 17 (converted to comfy's mu=log(17)
internally), overrides latent_format to a 3-channel pass-through, and
registers an optional orthogonal CFG post-hook bit-exact equivalent to
upstream guidance_jit (in velocity space for numeric stability).
- AsymFLUX2 Empty Pixel Latent: 1:1 3-channel pixel latent.
- AsymFLUX2 Oklab Encode / Decode: deterministic IMAGE <-> Oklab LATENT.
Math verified bit-exact against lakonlab.models.architectures.asymflow.
common.AsymFlowMixin.asymflow_velocity and gaussian_flow.guidance_jit on
synthetic inputs. The Oklab transform is a direct port of
OklabColorEncoder; the AsymFlow velocity uses the same s, k, and
proj_buffer subspace decomposition as upstream.
See PORTING.md for the architecture diff between the FLUX.2-klein base
and the AsymFLUX.2 adapter (full overwrites of img_in / final_layer +
extra proj/scale buffers + 116 rank-256 LoRA tensors on attn.to_out, ff,
ff_context, and the timestep MLP).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>