draw.instant

AI images that form as you type.

Real-time diffusion in your browser. Nothing uploads. No login, no account, no credits. Powered by fused WebGPU dispatches — every speedup on this page is measured live on your GPU, and the PyTorch head-to-head ships in the repo (bench-torch.py).

Checking WebGPU support…
[ canvas — preview renders here ]
idle
1
42

No guidance control — SD-Turbo is distilled to run guidance-free, and CFG would double the U-Net cost per step.

fused · ms
—
naive · ms
—
speedup
—
backend
—

Probe: element-wise chain (mul·add → relu → sub → tanh → mul → sigmoid) over 1M floats. Fused = 1 dispatch, chain lives in registers. Naive = 6 dispatches, intermediates in global memory. Both paths are diffed against each other at boot (max abs Δ reported below); the timing delta is pure dispatch + memory-traffic overhead.

Waiting for probe…

v1 · fused FFN block · real SD-Turbo shapes
FFN naive · ms
—
FFN fused · ms
—
speedup
—
effective TFLOPS
—

FFN block: (matmul → GELU → matmul → residual) at mid-block shape [2·1024·1280]·[1280·5120]·[5120·1280]. Naive = 4 dispatches with intermediates in global memory. Fused = 2 dispatches with GELU + residual baked into matmul epilogues. Same tiled matmul in both paths — the delta is fusion, not kernel quality.

Waiting for FFN block benchmark…

v1.1 · fused attention · flash-attention-style single dispatch
attn naive · ms
—
attn fused · ms
—
speedup
—
max abs Δ
—

Self-attention at SD mid-block shape [B=2·H=20·S=256·head=64]. Naive = 3 dispatches (scores → softmax → attn-matmul) with scores + probs matrices materialized in global memory. Fused = 1 dispatch, scores stay in workgroup memory, softmax reduces cooperatively, output emits directly.

Waiting for attention benchmark…

v1.2 · full transformer block · attention + FFN + LN + residuals, fused end-to-end
block naive · ms
—
block fused · ms
—
speedup
—
dispatches
—

One full SD-Turbo transformer block [B=2·S=256·D=1280·heads=20×64·D_FFN=5120]. Naive = 14 dispatches (LN → Q/K/V → scores → softmax → attn → out-proj → residual → LN → FFN-up → GELU → FFN-down → residual). Fused = 9 dispatches (flash-attn collapses 3 into 1, GELU folds into FFN-up matmul, both residuals fold into their producer matmul epilogues). Runs ~16× per U-Net forward pass.

Waiting for full-block benchmark…

v2.5.0 · fused GroupNorm · building block toward our WGSL U-Net
GN naive · ms
—
GN fused · ms
—
speedup
—
max abs Δ
—

GroupNorm at SD-Turbo mid-block feature-map shape [B=1·C=1280·G=32·16×16]. Naive = 2 dispatches (stats → normalize, with mean/rsigma materialized in scratch). Fused = 1 dispatch, one workgroup per (b,g), 256-thread parallel tree-reduce for mean and variance, stats kept in workgroup scalars. Runs ~30× per U-Net forward pass.

Waiting for GroupNorm benchmark…

v2.5.1 · fused Conv2d 3×3 · silu + residual in the epilogue
conv naive · ms
—
conv fused · ms
—
speedup
—
effective TFLOPS
—

Direct 3×3 Conv2d at SD-Turbo mid-block shape [B=1·C=1280·16×16·k=3]. Naive = 3 dispatches (conv → silu → residual-add), with Y re-read + re-written twice. Fused = 1 dispatch, SiLU + residual baked into the conv's per-output-value epilogue. Conv is called ~34× per U-Net forward pass.

Waiting for Conv2d benchmark…

v2.5.2 · fused ResNet block · GN+SiLU+Conv ×2 + skip, end-to-end
resnet naive · ms
—
resnet fused · ms
—
speedup
—
dispatches
—

Full SD-Turbo mid-block ResNet [B=1·C=1280·G=32·16×16·k=3]. Naive = 9 dispatches (GN-stats → GN-norm → SiLU → Conv1 → GN-stats → GN-norm → SiLU → Conv2 → skip-add). Fused = 4 dispatches (GN+SiLU collapsed via shared-mem stats, skip-add folded into Conv2's epilogue). First milestone where v2.5.0 and v2.5.1 chain end-to-end. Runs ~20× per U-Net forward pass.

Waiting for ResNet benchmark…

v2.5.3 · fused cross-attention · flash-attn over S_kv axis
xattn naive · ms
—
xattn fused · ms
—
speedup
—
max abs Δ
—

Cross-attn at SD-Turbo mid-block shape [B=2·H=20·S_q=256·S_kv=77·head=64]. Queries from the image feature map, K/V from text cond. Naive = 3 dispatches with scores [S_q × S_kv] in global memory. Fused = 1 dispatch, softmax kept on-chip in workgroup memory. Called ~32× per U-Net forward pass.

Waiting for cross-attention benchmark…

v2.5.4 · fused timestep embedding · sinusoidal + 2× Linear + SiLU
tembed naive · ms
—
tembed fused · ms
—
speedup
—
max abs Δ
—

Timestep embed: sinusoidal [320] → Linear [1280] → SiLU → Linear [1280]. Fires once per denoise step. Naive = 3 dispatches. Fused = 1 dispatch with both intermediates (emb 320, hidden 1280) living in workgroup memory.

Waiting for timestep-embed benchmark…

v2.5.6 · WGSL UNet · weight parser audit
tensors parsed
—
weight bytes
—
top-level groups
—
parse · s
—

End-to-end test of the hand-rolled ONNX protobuf parser against the real 1.73 GB SD-Turbo U-Net. Opt-in because it's a 1.73 GB download each run (this diagnostic path is deliberately not cached). Proves onnx-parser.js reads every TensorProto from the schmuell bundle without any protobuf library.

Click to fetch + parse the full U-Net and print the tensor-name tree. This is the audit step before v2.5.6 wires wgslUnetForward against real weights.

How it works

Every browser Stable Diffusion today (Web-SD, ONNX Runtime Web, Transformers.js) runs the denoising U-Net as dozens of kernel launches per step, times 20–50 steps. On consumer GPUs the launch overhead dominates — best-in-class browser SDXL Turbo is ~1 second on an RTX 4090. Nobody has broken the interactive barrier.

Our approach: collapse the sequential path.

text → tokens → CLIP embed (cached per prompt) → fused U-Net step × N → VAE decode (once per image) → canvas

The fused U-Net step is where the thesis lives: attention + GEMMs + GroupNorms + SiLU + residual adds collapsed into a single-digit number of dispatches per denoising step — vs. 60+ in ORT Web today. Diffusion is the textbook case where 1-dispatch fusion pays off, because the sequential step loop is launch-overhead-bound.

Roadmap

Shipped — fused kernels (v1.2). Hand-fused WGSL attention, FFN, and full transformer block at SD-Turbo shapes, benchmarked live above: 14 → 9 dispatches per block, correctness gated against the naive path. Plus fused GroupNorm, Conv, ResNet, cross-attention, and timestep embed as building blocks for our own U-Net.

Shipped — the product loop. ORT Web U-Net + our Euler scheduler generate real images on-device: as-you-type live preview, a continuous generate loop, and a camera mirror (img2img). Models persist via the Cache API after the first ~2.5 GB download.

In progress — our WGSL U-Net (v2.5). Replace ORT's graph execution with our own dispatch loop over the fused blocks: ONNX parsed byte-level, weights straight into our kernels. Target: interactive latency (<100 ms per preview refresh) on mid-range consumer GPUs.

Next — shareable. URL-fragment-encoded prompt + seed + model + LoRA. Zero-backend sharing, same pattern as the .flora adapter format.