diffusion: Metal-side Stage-1 sampling + Accelerate host sampler#2
Closed
JoshuaOliphant wants to merge 3 commits into
Closed
Conversation
Replace the three scalar 262k-vocab passes per canvas position (argmax, partition sum, entropy+sample) with vDSP/vvexpf reductions. Argmax is bit-exact with the scalar path; Z, H, and the sampled cumulative differ only by reduction order. ~7% step-time reduction on M3 Max (434->406ms, 256-token canvas, Q4_K_M). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eduction Implement ggml_backend_metal_diffusion_sample behind the same backend-reg proc-address boundary as the CUDA sampler. Shared Metal buffers are unified memory, so sc_dev rows are reduced in place with vDSP/vvexpf (no device kernel, no logits fetch); private-storage tensors fall back to the host path. llama_diffusion_device_sample now resolves CUDA first, then Metal. DG_DEVSAMPLE_CHECK: 0 argmax mismatches, 0 token diffs, 0 entropy delta across all steps (device and host paths share the same Accelerate math). ~10ms/step on M3 Max (406->396ms, 256-token canvas, Q4_K_M). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… into metal-diffusion-sampling # Conflicts: # src/models/diffusion-gemma.cpp
Author
|
Closing this — it was opened against a fork and hasn't been picked up. Cleaning up my open PRs. Thanks! |
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.
I've been running this PR on an M3 Max (48GB) this week and ended up writing two patches for the Metal side; this PR contributes them to
diffusion-visual-updates.1. Accelerate-vectorized host sampler (
examples/diffusion/diffusion.cpp)The host worker makes three scalar passes over the 262k vocab per canvas position. On Apple platforms I replaced those with vDSP/vvexpf reductions:
vDSP_maxvifor the argmax (matches the scalar loop bit for bit, first-index tie-breaking included), onevvexpfpass with entropy computed asH = log Z - dot(e, z)/Z, and a block-wise scan for the multinomial crossing. Non-Apple builds keep the scalar path unchanged; the diff is purely additive. On my machine this took a 256-token canvas step from 434ms to 406ms (Q4_K_M).2. Metal Stage-1 device sampling via zero-copy host reduction (
ggml-metal)Metal currently warns "on-device sampling unsupported on this backend" and falls back every run. This adds
ggml_backend_metal_diffusion_samplebehind the same backend-reg proc-address boundary as the CUDA sampler. The twist: it dispatches no kernel. Shared (and mapped) Metal buffers are unified memory, so the implementation readssc_devin place and runs the same Accelerate reduction; private-storage tensors return false and fall back to the host path.llama_diffusion_device_sampleresolves CUDA first, then Metal, and only caches a successful resolution (late-registered backends under dynamic loading still get picked up; the cache is a relaxed atomic since this is a publicllama.hentry point).I scoped a real MSL port and concluded it buys little here: the CUDA kernel's main win is skipping the 268MB PCIe D2H, and unified memory never pays that tax. The remaining step time on Apple silicon is the forward pass itself.
Hardening found in review
Before submitting I ran a multi-angle review over the diff; three things got fixed as a result, all worth knowing about:
vDSP_sveblock sums vs the sequential in-block scan created a wider mis-fallback window than the CUDA kernel's identical-serial slices. The scan now carries the sequential cum into the next block, so then_vocab-1default only remains when the cumulative sum truly never reaches the target.-inflogit safety: the closed-form entropy NaNs on-inflogits (the scalar path'sp > 0guard didn't). AvDSP_vthrfloor at-80keeps masked-token rows finite; normal rows are unaffected (tail contribution ~1e-30 against Z >= 1).extern "C"impl declaration lives once inggml-metal-device.hso the defining and calling TUs cannot drift silently.Known and accepted: the reduction is duplicated between the example's
__APPLE__worker and the backend file; ggml's public API has no portable host-visibility test (Metal shared buffers deliberately reportis_host=false), so the backend boundary is where theis_sharedknowledge lives. Happy to unify if you'd rather take a different shape.ggml_vec_soft_max_f32in ggml-cpu implements the same core pattern but is an internal header, hence not reused here.Validation
DG_DEVSAMPLE_CHECK=1: 0 argmax mismatches, 0 sampled-token diffs, 0 entropy delta on every step, at default andt=[0.2,0.5]temperatures, including multi-block (-n 512) runs (both paths share the reduction code, so agreement is exact rather than within tolerance).-DGGML_METAL=OFFbuild compiles and linksllama-diffusion-cli(dispatcher degrades cleanly).Numbers (M3 Max 48GB, 256-token canvas, diffusiongemma-26B-A4B Q4_K_M,
-fa on)🤖 Generated with Claude Code