Skip to content

Compile expression metrics to WGSL so the GPU backend serves them - #9658

Draft
kube wants to merge 8 commits into
claude/opt-proto-smooth-flowfrom
claude/opt-proto-gpu-metrics
Draft

Compile expression metrics to WGSL so the GPU backend serves them#9658
kube wants to merge 8 commits into
claude/opt-proto-smooth-flowfrom
claude/opt-proto-gpu-metrics

Conversation

@kube

@kube kube commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Important

Experimental
Behind the WebGPU feature flag.

Summary

Before this PR, the GPU backend measured one kind of metric: a place's token count, sampled into a per-frame histogram of unsigned integers with one bin per count. Every expression metric, which is every optimizer objective and most of the bundled examples' metrics, forced the whole experiment back to the CPU, and the editor's GPU switch refused it with a sentence of its own before core was asked. Frame 0 was built on the host from the initial marking, and the shader compiler was keyed on the artifact object's identity.

This PR compiles metric HIR to WGSL. The lowered metric function travels on its artifact under the existing includeHir option, one device-free probe decides per metric whether the emitter can translate it, and the gate, the compilation report and the editor's switch all read that probe. The emitter learns a place's live tokens as a runtime-length span: tokens.length reads the count and tokens.reduce with a numeric accumulator becomes a loop over the live slots, the shape the dynamics already emit. Every metric, place counts included, is sampled as an f32 and binned through one window with a calibrated range, exact integer labels kept for integer metrics. Frame 0 is sampled on the device before the first step. Of the bundled examples' 30 model metrics, 28 now run on the GPU; the two .concat bodies stay on the CPU with the reason shown per metric. Measured on the SIR example, the GPU's Infected Fraction agrees with the CPU within 0.1% per frame at 2000 runs.

Links

Changes

Metric HIR to the shader

  • Metric artifacts carry their HIR

    HirMetricArtifact.hir is written under includeHir, the option the webgpu experiment and study paths already request. Artifact version stays 4.

  • One probe decides translatability

    tryTranslateMetric runs the real sample emitter against placeholder place bindings and classifies the metric as integer or real from its return type.

  • The gate admits translatable expressions

    toGpuMetricSpecs refuses transition firings, time aggregations, an artifact without HIR and an untranslatable body, each with its own sentence.

  • Setup cache keyed on the artifact fingerprint

    An edited expression with a stable id never reuses a stale shader.

Emitter

  • Token spans

    A place's live tokens are a tokenSpan value: .length reads f32(counts[p]), .reduce emits var plus a for loop over the live slots with the body's statements spliced inside, the index parameter bound to the loop variable.

  • Stated bails

    .concat, positional indexing, string and uuid attributes, Infinity, NaN and zero-argument Math.min/Math.max bail at the probe instead of failing at shader creation.

  • Metric state binder

    metric-sample.ts builds state.places.<name>.{count, tokens} over the real layout or over probe placeholders; swept parameters resolve to their per-run locals.

Histograms

  • One f32 window path

    lo and stride are f32 uniforms in the same two config words; the observed range travels as order-preserving u32 keys through the existing atomics; window_bin settles each sample against the exact edges. Integer windows bin exactly below 2^24 and keep integer labels; real windows label bin centres with a stride / 2 extent.

  • Frame 0 sampled on the device

    Sampling moves to the top of the frame iteration, so row f holds frame f and the host-built frame 0 is deleted with no change to the buffer.

  • Blind windows always probed

    A metric without a ceiling gets a probe of up to 128 runs before the full attempt.

  • Non-finite samples halt the run

    A NaN or infinite value sets a per-metric status and the experiment fails naming the metric.

  • sampleRuns honoured

    The sample guard follows the spec's active, completed or all; the histogram gains the final-frame row so finished runs report their last values like the CPU.

Report and editor

  • Compilation report gains metric rows

    One row per model metric with the probe's status and reason; the accepted metrics compile into the reported shader.

  • GPU switch follows the report

    useGpuAvailability compiles the net with the form's metrics and reads summarizeGpuUnavailability; its own refusal sentence is deleted.

  • Optimization drawer

    The custom objective's live code reaches the gate; a state constraint adds its time-aggregated indicator so the switch reads the run-time refusal.

  • Studies run on the GPU when their objective translates

Known issues

  • Place counts above 2^24 round in f32 before binning

    Unreachable under the state size gate; stated as the exactness bound.

  • The compilation panel's metric rows have no reachable detail

    Clicking one is a no-op because metric ids are not canvas items.

  • A state constraint's run-time fallback names the constraint by id while the drawer names it by position
  • Errored runs are excluded from all sampling on the GPU

    The CPU includes them; a GPU run error fails the experiment, so no frame diverges.

Next steps

  • .concat over several places' tokens as a list of spans
  • Time aggregation on the device
  • Per-run readback for optimizer objectives and constraint verdicts
  • A satellites row in the parity harness to bench the reduce loops

Test coverage

  • emit-wgsl.test.ts:

    Token span .length, .reduce loop text, index parameter, boolean seed, nested reduce, .concat and indexing bails, string attribute bail, non-finite constant bails.

  • compile-net-shader.test.ts:

    Quoted f32 window uniforms, helpers, sample block, status halt; SIR and capped satellites expression samples with token reads built from the layout; the run-sampling guards; same-scope redeclaration and brace scans over every integration method.

  • try-translate-metric.test.ts:

    Exhaustive matrix over every bundled example's model metrics, integer classification per metric, the two .concat refusals.

  • metric-windows.test.ts, runner/histogram-frames.test.ts, gpu-experiment-handle/calibration.test.ts:

    Integer planning unchanged, real planning, observed-sign clamp, constant rule, order-key monotonicity, centre labels, always-probe.

  • gpu-metric-frames.test.ts, compilation-report.test.ts, artifacts.test.ts, gpu-backend-cache.test.ts:

    Gate sentences, metric rows per example, HIR on the artifact only with includeHir, fingerprint keys.

  • create-optimization-drawer.test.tsx, create-experiment-drawer.test.tsx, provider.test.tsx:

    The GPU switch offered for a translatable objective, refused for .concat and for state constraints, the provider's fallback reason.

  • Manual:

    naga 30 validated the SIR and satellites shaders with expression metrics. Dev / GpuParity in a WebGPU browser measured the SIR Infected Fraction row, recorded in the performance page.

How to test

  • Open Petrinaut preview on Vercel in a WebGPU browser
  • Viewport controls > Settings > Simulation > WebGPU, Compilation output
  • Menu > Load example > SIR Model
  • Bottom panel > Compilation

    Expect a metric row for Infected Fraction reading GPU

  • Simulate > Experiments > Create > Add metric > Infected Fraction > Backend GPU > Run

    Expect the drawer's Compute column to read GPU and the metric chart to stream from frame 0

  • Menu > Load example > Production With Machine Failure > Bottom panel > Compilation

    Expect Average machine damage refused with a sentence naming .concat

  • Simulate > Optimizations > Create with the default objective

    Expect the Backend switch enabled; with a state constraint added, disabled with the time-aggregation reason

@kube kube self-assigned this Sep 11, 2026
@kube
kube added this pull request to stack #9549 September 11, 2026 01:15
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Sep 11, 2026 2:45am UTC
petrinaut Ready Ready Preview Sep 11, 2026 2:45am UTC
petrinaut-docs Ready Ready Preview Sep 11, 2026 2:45am UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 11, 2026 2:45am UTC

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team type/eng > backend Owned by the @backend team area/apps > hash.design Affects the `hash.design` design site (app) labels Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

1 participant