Skip to content

Verified structures: catalog, gates, transactional attach, and provider export#150

Open
LiangSu8899 wants to merge 39 commits into
mainfrom
feat/hf-kernels-structures
Open

Verified structures: catalog, gates, transactional attach, and provider export#150
LiangSu8899 wants to merge 39 commits into
mainfrom
feat/hf-kernels-structures

Conversation

@LiangSu8899

@LiangSu8899 LiangSu8899 commented Jul 17, 2026

Copy link
Copy Markdown
Member

Implements the structural layer tracked in #149: structures as specs with references and gates, qualified implementations over Hub kernels, and an export path that packages a qualified host as a standard model runtime.

What's here

flash_rt/structures/

  • catalog/ — structure specs (decoder_ffn, vision_ffn): boundary contract, weight slots, variants, and a ~20-line plain-torch reference per structure (the ground truth the gate scores against).
  • registry.py — spec lookup and loading; pure metadata, no side effects.
  • gates.py — the parity qualification gate: structure-agnostic calling convention, symbolic-dimension resolution, cosine / max-abs / p99 metrics against explicit thresholds, and a plan digest that binds every qualification record to the exact execution plan it qualified.
  • impls/fp8_static implementations of both structures over the Hub FP8 FFN kernels: bind() packs weights and calibrates static scales from real-distribution data, in full-boundary and host-MLP-seam forms.
  • bindings/ — per-host adapters mapping structure slots to module paths and checkpoint layouts: the Pi0.5 family (three), Qwen2.5, Qwen3-8B, Qwen3-VL-8B (text + vision), and SmolVLA (text + expert + vision).
  • attach.py — transactional swap-in: resolve everything before touching the model, roll back entirely on any failure, detach() restores the original modules exactly.
  • provider.py — packages host-captured CUDA graphs plus their boundary windows as an frt_model_runtime_v1, the same runtime face the native pipelines export — so a torch host with structures attached becomes one more producer of the standard serving contract.

Structures vs standalone kernel swaps

The load-bearing A/B, measured on a real fine-tuned Pi0.5 checkpoint with real observation data — same Hub kernels, same calibrated scales; the only variable is the composition:

Comparison Result
Per-op standalone swap vs eager host Net negative at every tested M (M=10/50/512) — boundary quant/dequant eats the kernel win (e.g. 75µs vs 28µs eager at M=10)
Structure (composed region) vs per-op standalone 1.65–2.1× across the same M sweep
Structure vs eager host Net win only where the workload supports it (1.43× at M=512); smaller-M sites are refused by the gate instead of activated at a loss
Hand-written sync-free host optimizations vs torch.compile max-autotune Absorbed by the compiler (1.009× residual)
Structures vs the same max-autotune baseline 1.35× survives on top of the strongest compile — the win is representation change + fusion beyond the IR, not something the compiler recovers
Full pipeline (63 seams) + capture, serving tick 43.45 ms/tick (default compile) / 40.50 ms/tick (max-autotune-no-cudagraphs) — faster than the same host's in-process compiled ladder arms (48.54 / 40.84 ms), passing the same serving adoption/tick acceptance as the native pipelines

The refusal rows are the point of the gate design: workload sensitivity is a guardrail, not a caveat.

Cross-model reuse

The same two structure definitions, zero spec changes, across model families and host forms — each new host costs a ~20-line binding. Layer gates at 0.999 on the structure boundary (residual included), then a family-level end-to-end gate (accuracy budget and net win):

Host (family) Structures Layer gates Family e2e gate Outcome
Pi0.5 (robotics VLA, fine-tuned, lerobot host) decoder_ffn ×2 + vision_ffn, 63 seams 63/63 e2e 1.623× (default) / serving tick 40.50 ms; action cos 0.9997
Qwen2.5-1.5B (LLM, transformers host) decoder_ffn ×28 28/28 logits 0.9982 prefill 1.387×
Qwen3-8B (current-gen LLM) decoder_ffn ×36 36/36 logits 0.9975 prefill 1.509× (70.7→46.9 ms)
Qwen3-VL-8B (current-gen VLM) decoder_ffn (text) + vision_ffn (visual) text 35/36, vision 27/27 text 0.9972 activated; vision 0.9906 refused 1.434× (150.7→105.1 ms) on the activated set
SmolVLA (compact VLA) decoder_ffn ×2 + vision_ffn, 44 seams 44/44 per-family marginal-to-negative (0.94–1.01×) whole host refused — mechanics verified, no net win at these dims
GR00T N1.6-3B (NVIDIA VLA, Isaac-GR00T native host) decoder_ffn (Eagle LLM) + vision_ffn ×2 (SigLIP tower and the DiT action-head feed-forward), 75 seams 75/75 LLM within measurement noise (1.03× / 0.87× across runs), vision 0.90×, DiT 0.88× whole host refused — marginal-and-unstable is treated as no win

Three qualitatively different gate outcomes, all correct: full activation where the workload wins, family-selective activation where amplification kills one family (Qwen3-VL's deepstack injects vision features into text layers — layer gates alone under-predict e2e there, which is exactly why the family-level gate exists), and whole-host refusal where small dims make FP8 boundary costs unprofitable. Nothing activates at a loss, ever.

The GR00T row is also the strongest reuse statement: the same vision_ffn definition qualifies both a SigLIP encoder tower and a DiT diffusion action head — two different host forms, one spec, and a third independent host stack (Isaac-GR00T native, neither transformers nor lerobot) adapted with three ~20-line bindings and no spec or implementation changes.

Adoption cost vs standalone

What a standalone kernel swap asks of the integrator, per model and per site (this is what in-host integrations hand-roll today, typically ~100+ lines):

# standalone adoption, per site:
#  - collect real-distribution calibration activations with your own hooks
#  - compute static scales; quantize + repack weights into the kernel's layout
#  - patch modules in place; keep originals around for rollback yourself
#  - write your own parity harness, pick your own thresholds
#  - discover per-workload regressions in production

The same adoption through structures:

from flash_rt.structures import attach
from flash_rt.structures.gates import qualify_parity
from flash_rt.structures.impls.decoder_ffn import fp8_static

bound = fp8_static.bind_mlp_seam(weights, variant={"activation": "silu"},
                                 calibration_normed=calib, original=layer.mlp)
# qualify_parity(...) scores it against the structure reference on YOUR data
# and refuses activation outside tolerance — no silent fallback, ever
handle = attach(model, {module_path: bound})   # transactional
...
handle.detach()                                # exact restore

Packing, calibration, layout, parity, and rollback are the mechanism's job, not the integrator's. A new host is a ~20-line binding (module paths + weight layout); every binding in the table above cost exactly that. Next step on this axis: bindings become directly consumable, so adoption collapses further into a lookup + one call while keeping the full gate discipline.

Validation status

All gates run on real checkpoints with real-distribution data (calibration and evaluation sets kept separate): a real fine-tuned Pi0.5 checkpoint with real robot observations, stock transformers hosts with real long-document corpora, real images for the VLM host.

  • reference implementations match the live model outputs at the declared structure boundary;
  • fp8 implementations pass the layer parity gate across every binding in the table;
  • attach/detach is transactional end to end: failed resolution leaves the model untouched, detach restores weights bit-exactly;
  • end-to-end action/logits parity holds on every activated set;
  • a full pipeline captured from the torch host exports through provider.py and passes the same serving adoption/tick acceptance as the native pipelines (per-tick hot input with no recapture, deterministic replay, boundary snapshot/restore).

Boundaries

  • Additive only: no changes to existing kernels, runtime bindings, or pipelines.
  • Hosts are never modified on disk; attach is an in-memory, reversible module swap.
  • Specs and evidence formats are shared; implementations stay native to each host and are never generated or translated.

Introduce flash_rt.structures: a registry over versioned structure
specifications (boundary tensors, framework-neutral weight slots,
reference implementation, qualification gates). First catalog entry is
decoder_ffn, covering the gelu/silu activation, offset/direct norm
weight, and none/ada_ln conditioning variants shared across the
supported decoder families.
Structure-agnostic harness: derives the calling convention from the
specification, resolves symbolic dims from actual tensors with
consistency checks, judges an implementation against the reference
(cosine / max-abs / p99 metrics vs explicit thresholds), and emits a
machine-readable record keyed by a plan digest over spec content,
variant, workload, implementation identity, and environment.
Bind-time weight packing and static-scale calibration feeding the fused
FP8 gate/up -> activation -> down block from
flashrt/flashrt-fp8-swiglu-ffn (gelu and silu entrypoints). Enforces
the support envelope and non-empty calibration inputs at bind. The
parity gate gains a bound-callable mode for implementations whose
weights and variant are baked at bind time.
Maps the transformers-convention checkpoint layout (out-in linear
weights, AdaRMS conditional norm with no learned weight, time-embedding
modulation projected to scale/shift/gate, gated residual) onto the
decoder_ffn slots and conditioning inputs.
Add the optional cond_gate boundary input: under the ada_ln
conditioning variant the output residual becomes x + ffn_out * gate,
matching the AdaRMS decoder families whose norm projection emits
scale/shift/gate. The reference and the fp8_static implementation both
honor it.
attach() swaps host modules atomically: all staged paths are resolved
and validated before the first swap, any failure rolls back completely,
and the returned handle restores the exact originals idempotently.
fp8_static gains bind_mlp_seam for hosts whose replaceable boundary is
the MLP module: shared packing and calibration with the full-structure
bind, the host keeping its own norm, AdaLN gate, and residual. Parity
metrics are exported for host-side acceptance checks.
Weight packing and calibration are setup-time operations; without
no_grad the quantization chain recorded autograd history on the packed
FP8 tensors, keeping every fp32 intermediate alive for the lifetime of
the bound implementation (~1.7 GB per trunk-sized layer).
Second binding of the same structure: the PaliGemma trunk MLPs
(standard learned-weight RMSNorm, no conditioning, plain residual) at
prefix token counts. Together with the pi05 action-expert binding this
covers both M regimes of the model with one structure definition.
Third binding of the same structure, first outside the pi05 family:
Qwen2.5-1.5B-Instruct under a plain transformers host (silu activation,
direct norm-weight convention, no conditioning).
torch.quantile rejects large inputs (LLM-logits-sized qualification
outputs exceed its limit); kthvalue is exact and unbounded.
Second catalog structure: the non-gated LayerNorm + fc1/GELU/fc2 vision
FFN block shared by the SigLIP towers of the supported VLA families,
implemented over the fused FP8 GELU MLP Hub kernel (checkpoint-native
weight layout, biases included).
Introduce the first schedule-layer structure. stage_pipeline specs declare
a stage graph with cadence attributes instead of a tensor boundary; their
parity ground truth is the host's own eager path under an explicit noise
window, so the registry now carries kind/family/stages/conformance fields
and region entries are unchanged.

vla_tick_pipeline (cond_iter_pipeline family, tick specialization):
obs_encode at observation cadence feeding a fixed-shape K-step denoise
loop at tick cadence, noise as a SWAP window, condition buffers read-only.
Includes the GR00T N1.6 binding with its capture rulings (eager backbone,
shallow-copy mutation guard, hoisted noise site).
Same structure declaration as the GR00T N1.6 binding. obs_encode ruled
eager: SmolVLM vision embeddings use a boolean-mask scatter that is
illegal on a capturing stream (same blocker class as SigLIP2 CPU
indexing), so the tick form is eager prefill into static KV buffers plus
a captured denoise loop; noise uses the host's native injectable window.
Consumption now mirrors kernels.get_kernel: a single call discovers
structure seams in the host (pattern-matched from the module tree, no
hand-written binding required), calibrates on the caller's real forward
passes, runs per-layer parity gates and family-level accuracy plus
net-win gates, transactionally activates only what earns, and returns a
Plan with a receipt and exact detach.

Calibration entries: calibration="auto" captures during the provided
forward(s); a file path loads a prior capture or saves this one, so
dataset-scale calibration is a path string. Multi-frame via a sequence
of forwards or frames=N. The net-win gate requires a margin
(min_speedup, default 1.02x) so measurement noise can never activate a
family.

The swap machinery module is renamed attach.py -> swap.py to free the
attach name for the front door; no behavior change.
The single-site counterpart of attach, with get_kernel ergonomics: pull
one structure, bind it to the module you point at, plug it in yourself.
bind extracts weights from the module, calibrates on caller-provided
real inputs, and self-checks the replacement against the original
before handing it back; a part that misses the parity gate raises
GateRefused instead of being returned. Pass residual= to gate at the
declared structure boundary (residual included) — the same measurement
attach uses; without it the check runs on the bare seam, which is
strictly harsher. The returned module carries a certification record
(worst cos, gate boundary, dims, m profile, variant).
structures.capture(fn, windows=..., reference=...) is the schedule-
structure counterpart of the region doors: it warms and records the hot
stage into a CUDA graph on a side stream and returns a replayable
CapturedStage. Declared windows are the tensors the caller may rewrite
between replays (noise, observations, condition buffers); replay reads
their current contents in place. Parity against the host's eager path
under the same window contents and a margin-gated net-win check run
inside the call; a stage that fails either raises CaptureRefused.

Replay timing records its events on the replay stream — default-stream
events only measure enqueue time and overstate the win by orders of
magnitude.
A calibrated single projection (x[M,K] -> y[M,N], optional bias) with
epilogue variants (gelu+quant, residual add) and a negotiable input
dtype: fp8_static marks a producer-negotiated seam where an upstream
norm/adaln producer emits fp8+scale and this region skips its own input
quantization — decided at plan level and re-certified at the composed
boundary. Elementwise work exists only as epilogue variants here, never
as standalone regions. Discovery is qualified (weight-size floor,
sibling q/k/v/o grouped into one family), not a blind scan of every
nn.Linear.
linear_proj gains its first implementation: the fused BF16-entry FP8
projection (FP8 weights, static per-tensor activation scale, fused
input quantization), with work-based qualification measured from
standalone preflight — projections whose GEMM cannot amortize the fixed
quantization cost are refused at bind time and the host keeps its
Linear. Per-calibrated-M buffers are pre-allocated so the hot path is
allocation-free under compile and graph capture.

Discovery matches sibling q/k/v/o(out) projections as one family per
attention block behind a weight-size candidacy floor — never a blind
scan of every nn.Linear. The front door records bind-time refusals
per layer, and impls now share one process-wide hub loader: importing
the same kernel repo twice re-registers its fake ops and raises.
A captured stage's declared windows become frt_model_runtime_v1
boundary windows directly; ports reference them by name. This closes
the absorption edge: attach + capture + export takes a torch host to a
runtime the FlashRT serving mechanisms (Nexus tick, capsule
snapshot/restore) consume exactly like a whitebox-produced one.
Calling the hub loader from forward makes dynamo trace through
kernels.get_kernel's version resolution (network calls,
inspect.Signature) — 26 graph breaks that fragment the surrounding
compiled region, and a fragment boundary can drop host-side constant
construction into eager code that is illegal under CUDA graph capture.
Binding the op function once at bind time keeps the module a single
traceable custom-op call, which is why the FFN implementations (which
already did this) never fragmented.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Tracking] Verified structures — composing HF Kernels into net-win building blocks

1 participant