Repository navigation
moss image: serve pictures on CUDA (encoder, decoder and host for #1573) - #2281
devopsfortunato wants to merge 14 commits into
Conversation
|
Thanks @devopsfortunato, and for taking the split on #1573 the way @BobClawblaw handed it over. The CUDA host is careful: recognised only by
On #1573: the issue's plan was a Metal host gated on an M5, and this is the CUDA one. That's fine as the first host. We'll note on the issue that the Metal host is still open for whoever has a Mac and the checkpoint. |
ad9acd6 to
fafccae
Compare
|
Thanks — all four were fair. Here is where each stands. 1. The rebase: done, and one correctionYou were right that the stack no longer applied. Worse than it looked from my side: I had been rebasing onto my fork's
Rebased with On the head question: 2. The reference data, with hashesThe gates read these files. Sizes and sha256:
Generated with
3. The text-family A/B: started, and blocked by the GPUThe shape of the change first, because it explains what a mismatch would even look like: Main side, on this host, served from The branch side of those three is what I still owe you. It needs the same checkpoint resident, the host's own long-running server has taken the 78 GB again (the engine then reports Gemma is the harder half, and I need your help: nothing Gemma-shaped I can reach is served by the CUDA engine. 4. The kernel contract: in progressGates 1-4 with planted controls for the sixteen kernels, and the five-SM records, are being written now as the next commit on this branch, in the order that failed twice before when attempted whole: |
The Qwen A/B, both sidesSame checkpoint (
Identical, reply for reply ( Gemma: still blocked, and I need a checkpoint nameThree tries, three refusals at load, all with the engine's own words:
So on this host nothing Gemma-shaped that I can reach is a geometry your CUDA engine serves. Please name the Gemma checkpoint you run (repo id and revision), and I will run the same three prompts through both binaries and post the table. Failing that, the golden corpus is the standing evidence for the Gemma template path: Kernel contractUnder way, one kernel end to end first as promised: |
|
Thanks @devopsfortunato for the rebase onto our main, the head table, the reference files with their hashes, and
|
|
/atc exchange f468aaab2e5bf073c3f23f586868c4e80f0a962cf42f480b328609506b9b925f |
|
Thanks @devopsfortunato. The seven decoder kernels and three of the encoder's now have gate suites, planted controls and five-SM records. Still open before this leaves draft:
|
…lines under Unreleased, and the decoder's records no longer pending (ashhart#2281, part of ashhart#1573)
…o a second device's download cannot free through a dead driver (ashhart#2281)
…picture, byte-identical to the served one (ashhart#2281, part of ashhart#1573)
|
Thanks @devopsfortunato. This push adds contracts for
|
…so all nine of its kernels carry controls and five-SM records (ashhart#2281, part of ashhart#1573)
…the tokenizer fallback's path (ashhart#1573)
… control and five-SM records (ashhart#1573)
… and five-SM records (ashhart#1573)
…and enforces fp16 instead of waiving it (ashhart#1573)
…decoder's 3x3 serves its residual launches (ashhart#1573)
… with their controls and records (ashhart#1573)
…lines under Unreleased, and the decoder's records no longer pending (ashhart#2281, part of ashhart#1573)
…o a second device's download cannot free through a dead driver (ashhart#2281)
…so all nine of its kernels carry controls and five-SM records (ashhart#2281, part of ashhart#1573)
a0e3062 to
2f2d59b
Compare
What this changes
On main, the family's CUDA path is the transformer alone: the prompt encoder and the VAE decoder are Metal-only, with
moss_image_case.pyrunning them in torch. This branch ports both to CUDA and adds the host that serves a picture, sotensorfold-native serveon a GB10 answersPOST /v1/images/generationsnatively with no torch in the path:cuda_encoder.zig+moss_image_encoder.cu+tf-moss-image-encoder— the checkpoint's Qwen3.5-9B prompt encoder (32 layers, full attention every fourth, Gated DeltaNet elsewhere, final norm), one kernel per Metal kernel, driven by the sameencoder_configrefusals.cuda_decoder.zig+moss_image_decoder.cu+tf-moss-image-decoder— the FLUX.2 small VAE decoder, float32 throughout, seven kernels.moss_image_host.zig+moss_image_spec.zig— encoder, transformer and decoder resident once, one picture at a time with a stated queue,VTable.submit_image/Info.image, the checkpoint's own chat bracket, the pipeline's schedule, noise and CFG (empty negative, block 22 skipped, guidance 1 skipping the negative pass), the picture quantised and PNG-encoded into the caller's list,finishedlast, cancellation between steps.cuda.zigrecognises the checkpoint bymodel_index.json(MOSSImagePipeline) before any token family's path; a checkpoint without it is unchanged.cuda_model.ziggainsfitso a host plans one scratch and serves every geometry inside it;hf_text.ziggains atokenizer/fallback for diffusers layouts (models that keeptokenizer.jsonat their folder root never reach it).tf-moss-imagegains--prompt,--pictureand--seed, so the tool can make a picture natively instead of reading torch's rows from a case.c75e45e0e):cuda/download_stage.zigcached a*const DriverthatDriver.close()never dropped, so a decoder downloading after its encoder's driver closed freed host memory through the dead driver (SIGSEGV). The fix releases the thread's stage first, with a regression test. Any two-stage CUDA tool could hit it; the serving host never closes mid-run, which is why it had not.The issue's acceptance gate: the served picture equals the tool's, byte for byte
At the default guidance, seed 42, 50 steps, 1024×1024, with the case's prompt:
POST /v1/images/generations4ade0c108d19cd12dcdaec601ab56b7b0a51af118aab037504d0ee3aee21b738tf-moss-image --prompt … --picture … --seed 424ade0c108d19cd12dcdaec601ab56b7b0a51af118aab037504d0ee3aee21b738cmpreports identical; the served hash was reproduced by two independent server builds. The tool's encoding takes 0.355 s and the decode 3.667 s. Against the publisher's own picture the served one is 27.45 dB, which is the transformer's bf16 sensitivity on this prompt, not a pipeline difference.What made the first attempt differ is worth recording: the case's
latentswere dumped by this host's aarch64 torch, while the host starts fromnoise.zig, the port of x86-AVX torch's normal fill (the family's recorded hash for seed 42 at 1024² is378421c7…). Same seed, different CPU stream, different trajectory —--seedstarts the tool from the host's own noise, and everything else already agreed.The kernel contract
Every new kernel has a float64 reference, gates 1-4, a planted control that comes out red as planted, and register/shared-memory records for sm_80, sm_86, sm_89, sm_120 and sm_121. The five-SM limits check reads
(record current)for every moss launch, includingtf_me_gdn's 255 registers / 1024 B shared at 128 threads, which is the hardware ceiling and does not spill past the limits.Three findings came out of building these gates, all in the tests, none in a kernel:
siluis not monotone — it dips to −0.278 before z ≈ −1.278 — so a monotone interval mapper inverted the bounds; the suite now uses asiluRangethat includes the knee.tf_me_convskips taps withrow+k < 3rather than multiplying by zero, so a reference doing0 * wplanted NaNs the kernel never makes.tf_me_gdn_prep'sA_LOGandDT_BIASare bf16 tensors whiletf_me_gdn'sQN/KN/G/BETAare fp32; a first reference fed fp32 and gate 1 caught it.Declarations that are deliberate rather than waivers, stated in the source where they apply:
tf_me_conv's four-tap sums cannot separate a reduced accumulator (f16 = 0, measured0×),tf_me_gdn's output followsss^-1/2so a bf16 sum's error is under an output ulp, andtf_me_conv'smagisΣ|p|(the tap terms) because that is the errorsilucarries to the output.Checks on this head
a0e3062cc.zig build install -Doptimize=ReleaseFast -Dnvcc=/usr/local/cuda/bin/nvcc -Dsm=121— exit 0;zig build native …— exit 0.zig build test— exit 0,545/545 steps succeeded; 7200/7259 tests passed (59 skipped).zig build test-kernel-contract— exit 0.zig build test-golden— exit 0,322/335 + 55/55 equal to server goldens, 0 unexpected differences(the corpus drives a real Qwen and Gemma tool-call template through the real routes, identical before and after this branch).zig build test-server-security test-pull-security— exit 0.zig fmt --check build.zig zig— exit 0.lean_check.py— 425 problems, the same as the base (0 new).hf_text.zig567 lines andengine_api.zig590 — both under 600.Reference data, with hashes
The gates read these files, produced by
tools/zig/moss_image_case.pyfrom the checkpoint at85975793d8b3d432f1b6e27f352abaa4c2a4c0d3on torch 2.14.1+cu130, diffusers 0.42.1, transformers 5.12.1, numpy 2.5.4:case.safetensors37a4b3594788fdcdfb0d7691eee8a6ae28b5adab3543fad0006c2b3949031f1blatents.f3288061b8cbb9def5c9cea8dc7a5c001cd340a270906e7b2d718bfe6346d5a5f07exact.latents.f32134ad4f6d92df93c7240a3e424181b005a42661ed762d13bdec332e94bd14604reference.pngda244b88cb488ba3bc495dec72282bdd2482845d2e7e719107c3f544b44f999cprompt.txtd96b2fbe4d920b020e7f259a0b5383632c11dd55ec28221995f90b40f2148438encoder.safetensors(the encoder gate)2fbe7ffc5a284e1b17d4ab1d229da97f6343d0980ec550dd61413a7d08f6cd9bvaeruns in float64 on the CPU on purpose: this card's cuDNN convolutions sit at TF32's noise level (its own fp32 decode is 3.8e-3 from float64), too coarse to gate a decoder.The text-family A/B
Same checkpoint (
Qwen3.8-Flash-Next-FP4-Local), same prompts,temperature 0, one binary frommain@474c3b048and one from this head:token_sha257219994f82257219994f8215aa3d6f1b4515aa3d6f1b4536c7652fea0c36c7652fea0cFor Gemma the golden corpus carries the template path:
gemma_tool,gemma_tool-single,gemma_tool-stream,gemma_tool-single-streamdrive a real Gemma tool-call template through the real routes, and the corpus is identical on both sides (322/335 + 55/55, 0 unexpected). The BF16 Gemma release is refused by this family by name (UnsupportedGemmaQuantization), matching the 4-bit conversion being what main serves.What this pull request does not do
callForresolves a served shape by(rows, cols, cin), and the decoder's last level has two launches on one triple: the 96→96 block conv (which loads a residual) andconv_out96→3 (the picture). The suite serves the residual, becauseaddgates a load path nothing else covers andconv_outis exercised end to end by the byte-equal receipt above. Keying the harness oncout/flags would cover both; that is a repo-wide contract change, not this PR.tf_moss_image's 8-bit rule exists twice —mi.host.quantizefor the tool andmoss_image_host.quantizefor the server — because the host's file was in another lane's hands. They are the same rule and the byte-equal receipt proves it today; the host should delegate tomi.host.quantizenext.Checklist