Repository navigation
Conversation
…reference case tool
Every pipeline of idol.metal and idol_attention.metal is registered: id_linear's eight forms (plain and GELU), id_split's two, id_gemm32, id_norm (plain, affine, modulated), id_norm32 (affine, modulated), id_norm_split, id_repo, id_qk, id_gate_add, id_embed, id_cond, id_bias32 and the attention's four forms. Each output is checked at idol-sdk's fp16 points as the range its fp32 steps allow; the NAX projections against the tensor unit's 16-deep step bound, the attention against attention_ref's formula with P at the tensor unit's fp32. Inputs at the large served shapes repeat at periods prime to every tile, so float64 stays cheap and every output is checked. 19 controls: rounding points dropped or added (gate 3), fp16 sums in id_norm32, id_repo, id_qk, id_embed and id_gemm32, and the NAX fp32 path in id_gemm32's place (gate 4), and the attention's tail keys unmasked. Gate 5 holds the 24 launches, each reserved by its source. Known finding: id_qk gives a head holding a NaN +-inf in its other outputs where F.normalize gives NaN, as Metal's max drops the NaN norm that clamp_min keeps.
…the values the units multiply
…f-idol-vae and its case Builds the denoiser's feature2 [4101, 128] from the preprocessed picture as idol-sdk's ImageEncoder does: the encoder, quant_conv, the posterior's sample with the SDK's noise as an input, the 2 x 2 patches, the batch norm's statistics and five zero rows. fp32 throughout on the SDK's bf16 weights (FLUX.2-klein-4B, or FLUX.2-dev rounded): Winograd 3x3s, implicit-GEMM downsamples and 1x1s, two-pass GroupNorm, single-head attention. idol_case.py vae records the SDK's MPS rows, its noise and a float64 ruler; tf-idol-vae gates worst row and 1 - cosine within twice the SDK's distance from float64 (ours 5.1e-5 and 5.0e-11 against 2.7e-2 and 5.1e-5), and repeat bytes. All eight kernels register with the contract: 13 forms pass gates 1-4, 12 controls red.
This was referenced Oct 31, 2026
Owner
|
Thanks @jayleaton. Computing in fp32 on the bf16 weights, well inside the float64 gate, is the right trade for an encode that runs once an image. It waits on #1766; please rebase after it lands. Alongside the contract we'll also run the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1766 (Idol's denoiser on Metal). This branch holds #1766's commits too; its own change is the last two commits. Independent of #1767 and #1773.
What this changes
Idol's FLUX.2 VAE image encoder on Metal: the 1,024 x 1,024 picture to the denoiser's
feature2[4101, 128], as idol-sdk'sImageEncoder._flux2_tokensbuilds it, withtf-idol-vaeand anidol_case.py vaecase. For #1757. Details: the "VAE encoder" part ofdocs/recipes/idol.md.conv_in, fourDownEncoderBlock2Dstages (GroupNorm 32 and SiLU, stride-2 downsamples padded right and below), the mid block with its single-head attention over 16,384 positions,conv_norm_out,conv_out,quant_conv. Then the posterior's sample (the SDK samples, it does not take the mean), the 2 x 2 patches as 128 channels, the batch norm's saved statistics, and five zero rows ahead of the 4,096 patches.encodetakes the noise [128, 128, 32] as an input and the case records the SDK's.zig/kernels/metal/idol_vae.metal, eight kernels of Idol's own. No shared kernel changes. MOSS-Image: the image decoder on Metal #1687 (MOSS-Image's FLUX.2 VAE decoder) is the mirror image of this; the two share no code yet, and once both land the convolutions and GroupNorm could move to one place.Weights and precision
black-forest-labs/FLUX.2-klein-4Bvae/ate7b7dc27f91deacad38e78976d1f2b499d76a294(Apache 2.0, ungated, bf16). Klein's file is FLUX.2-dev's fp32 VAE rounded to bf16; the loader takes either and rounds an fp32 file to bf16 as torch does, and FLUX.2-dev's file gives the same rows bit for bit.The SDK runs the encoder in bf16 on the tensor units. This port computes in fp32 on the same bf16 weights: Winograd F(2x2, 3x3) with
G g G^Tmade in float64 and rounded once, implicit-GEMM stride-2 and 1x1 convolutions, two-pass GroupNorm, fp32 attention,precise::exp. None of it uses the tensor units, whose fp32 GEMM truncates its operands (#1772).iv_tokenskeeps a NaN logvar astorch.clampdoes.Kernel contract
zig/tests/contract/metal_idol_vae*.zig: all eight kernels in 13 forms against float64, with twelve controls (half-staged inputs, symmetric padding, a dropped Winograd term, half SiLU, half sums, attention without its rescale, swapped patch axes, a NaN logvar lost toclamp).Three controls go red by small margins: the half-staged 1x1 at 1.25 times its bound, Winograd's half patch at 1.05 times, attention without rescale at 1.15 times. These kernels use no tensor ops; their records reserve their thread counts in the source, and the
--offlineM1/M2 compile was not run.Receipt
Environment: Mac Studio M5 Ultra 256 GB, macOS 27; this branch on idol: Idol's denoiser as a native Metal family #1766's
94a039249; idol-sdk4702a7f0on torch 2.13.0 (MPS) with diffusers 0.42.1 (sha256-pinned files); the SDK's preprocessed avocado example, seed 42.Exactness, against float64 on the CPU with the SDK's own noise:
The gate (twice the SDK's distance, worst row and cosine, zero padding rows) passes by about 500 times on worst row. Against the SDK: cosine 0.999949, worst row 0.0275; the case's SDK rows equal the SDK's own recorded
feature2bit for bit. Each down block, the mid block and the moments sit within 4e-5 (worst pixel) of float64. Five more encodes repeat byte for byte.vae.zig's test plants a 2% row error and a non-zero padding value, and the gate fails each.Speed: 0.185 s an encode against the SDK's 0.095 s (medians of five): this port is half the SDK's speed, because it runs fp32 off the tensor units where the SDK runs bf16 on them. The attention (about 40 ms) reads K and V once every 8 queries; wider query tiles, or the hi/lo split onto the tensor units, are the obvious next steps. It runs once an image, against about 28 s of denoising.
Memory: 3.28 GB peak footprint (
/usr/bin/time -l, the whole run with its case and stage copies), 2 GB of it four 512 MB tensors at the 1,024 x 1,024 x 128 stage.Tests:
zig build test test-golden -Dcpu=apple_m1: 585 of 585 steps, 0 fail (before the rebase onto94a039249, a docs-only change; after it, plainzig build248 of 248).zig fmt --check build.zig zig: clean.lean_check.py: 424, the same as main.Not run: CUDA (none in this PR); Metal M1 to M4.
Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. (n/a.)SUMMARYline is in the receipt.