WGSL Shader Execution Layer #
Integration between ShaderM monad, code generation, and WebGPU execution.
This module provides high-level functions to:
- Compile ShaderM computations to WGSL
- Create GPU pipelines
- Execute shaders with buffer management
- Handle synchronization
Usage Pattern:
def myKernel : ShaderM Unit := do
let gid ← globalId
let idx := Exp.vecZ gid
let _input ← declareInputBuffer "input" (.array (.scalar .f32) 1024)
let _output ← declareOutputBuffer "output" (.array (.scalar .f32) 1024)
let val ← readBuffer (ty := .scalar .f32) (n := 1024) "input" idx
writeBuffer (ty := .scalar .f32) "output" idx (Exp.mul val (Exp.litF32 2.0))
-- Execute on GPU
executeShader device myKernel
[("input", inputBuffer), ("output", outputBuffer)]
{x := 256, y := 1, z := 1}
(256, 1, 1)
Create default execution config with specified workgroup count
Equations
- Hesper.WGSL.Execute.ExecutionConfig.default numWorkgroups = { numWorkgroups := numWorkgroups }
Instances For
Create config for 1D dispatch
Equations
- One or more equations did not get rendered due to their size.
Instances For
Subgroup Feature Detection #
Cached runtime check for subgroup support. Queried once per session, used to select between subgroup-based kernels and shared-memory fallback kernels.
Cached subgroup support flag (queried once, then reused)
Check if the device supports subgroup operations (subgroupAdd, etc.).
Result is cached after the first call.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Cached SubgroupMatrix support flag
Check if the device supports Chromium experimental subgroup matrix operations (subgroup_matrix_{left,right,result}, subgroupMatrixLoad, subgroupMatrixMultiplyAccumulate, subgroupMatrixStore). Cached.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Cached ShaderF16 support flag
Check if the device supports ShaderF16 (f16 values, f16 arithmetic).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Pipeline Cache #
Caches compiled GPU pipelines keyed by WGSL source hash. Pipeline compilation is expensive (~1-5ms per shader). With ~270 dispatches per forward pass, caching eliminates 270-1350ms of per-token overhead.
Cached GPU pipeline components
- shaderModule : WebGPU.ShaderModule
- bindGroupLayout : WebGPU.BindGroupLayout
- pipeline : WebGPU.ComputePipeline
- declaredModes : List Monad.BufferAccessMode
Instances For
Global pipeline cache: maps WGSL source hash to cached pipeline. HashMap — a linear Array scan cost ~ms/token at ~600 dispatches.
Reset pipeline cache (call when device is destroyed or for benchmarking)
Equations
Instances For
Bind Group Cache #
Caches WebGPU BindGroups keyed by (pipeline hash, buffer IDs). BindGroup creation involves internal validation and allocation in the WebGPU runtime, costing ~20-30µs per call. With 572 dispatches per token, this adds ~12-17ms overhead.
Since inference reuses the same pipelines with the same pre-allocated buffers,
bind groups are almost always identical across tokens. Caching eliminates
572 redundant createBindGroup calls per token.
Global bind group cache: maps (pipeline + buffer IDs) hash to cached BindGroup
PreparedDispatch (Graph Capture) #
Pre-computed dispatch state for instant replay. Stores the pipeline and bind group so that subsequent tokens skip ALL Lean-side processing:
- No WGSL generation/lookup
- No buffer name matching
- No bind group key computation
- No cache lookups
- Just one FFI call: recordDispatch
Usage:
-- In layer struct:
structure BitLinear where
...
prepared : IO.Ref (Option PreparedDispatch)
-- In forward function:
def forward (device : Device) (layer : BitLinear) ... := do
let (wx, wy, wz) := computeWorkgroups ...
-- Fast path: replay if prepared
if let some p ← layer.prepared.get then
replayPreparedDispatch device p wx wy wz
return
-- Slow path: full execution (first token only)
executeShaderNamed device shader namedBuffers config cacheKey (some layer.prepared)
Pre-computed dispatch: pipeline + bind group, ready for instant replay. The trace* fields carry JS-trace identity through replay (DG_TRACE_JS); zero-cost defaults otherwise.
- pipeline : WebGPU.ComputePipeline
- bindGroup : WebGPU.BindGroup
- traceKey : UInt64
- traceName : String
- traceBufObjs : Array WebGPU.Buffer
Instances For
Command Buffer Batching #
Global batching mode: when enabled, executeShaderNamed records dispatches into
a shared command encoder instead of creating individual command buffers.
This eliminates per-dispatch overhead (encoder creation + submit + wait).
Usage:
beginBatch device -- Create shared encoder
-- All executeShaderNamed calls now record instead of submit
layer1.forward device ...
layer2.forward device ...
endBatch device -- Submit all + wait once
Global batch encoder: when some, executeShaderNamed records into it
Dispatch counter for current batch (for diagnostics)
Begin command buffer batching. All subsequent executeShaderNamed calls
will record into a shared encoder instead of submitting individually.
Equations
- One or more equations did not get rendered due to their size.
Instances For
End command buffer batching. Submits all recorded dispatches and waits.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Split the current batch WITHOUT a CPU sync: submit the recorded encoder (no wait) and start a
fresh one. Keeps Dawn-on-Metal's per-encoder inter-pass barriers correct (it drops them in a
very large single encoder) at a fraction of endBatch's cost. Cross-encoder buffer hazards are
handled by queue ordering + the driver.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Check if currently in batch mode
Equations
- Hesper.WGSL.Execute.isBatching = do let __do_lift ← ST.Ref.get Hesper.WGSL.Execute.batchEncoderRef pure __do_lift.isSome
Instances For
Section profiling #
Lightweight per-section wall-clock timing for the "everything else" bucket
breakdown. When sectionProfilingRef is true, withSection name act wraps
act with monoNanos timestamps and accumulates total ns + call count into
sectionTotalsRef keyed by name. Nested sections are allowed (the outer
section's total includes nested ones). Only meaningful in unbatched mode
where each dispatch auto-syncs.
Per-section kernel-dispatch counter. Populated by withSection when
sectionProfilingRef is on. The caller must wire preDispatch /
postDispatch (readers of the global dispatch counter) so withSection
can compute the dispatches attributed to one section.
Callback to read the global dispatch counter. Set by the CUDA backend
via registerDispatchCounter. When none, per-section dispatch
counting is disabled.
Equations
Instances For
Instances For
Equations
Instances For
Chrome-trace event log for withSection calls. When
sectionTraceRef is on, every withSection invocation appends an
entry (name, start_ns, dur_ns, depth). Dumped to a JSON file by
dumpSectionTrace at end-of-run. Enabled by env
HESPER_TRACE_OUT=<path>.
Dump accumulated section-trace events to a Chrome-trace-format
JSON fragment file (just the traceEvents array body — the merger
in scripts/nsys_to_chrome_trace.py reads it).
Equations
- One or more equations did not get rendered due to their size.
Instances For
DG_TRACE_JS_CKSUM: XOR of per-buffer FNV32 over each bound buffer's first 4KB, emitted after every armed dispatch — the replayer mirrors it to find the FIRST diverging dispatch exactly. Forces a flush + sync per dispatch (trace runs only).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Replay a prepared dispatch directly. Skips ALL Lean-side processing. Works in both batch mode (record into shared encoder) and standalone mode.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Compile a ShaderM computation to WGSL source code
Equations
- Hesper.WGSL.Execute.compileToWGSL computation funcName workgroupSize extensions diagnostics = Hesper.WGSL.CodeGen.generateWGSL funcName workgroupSize extensions diagnostics computation
Instances For
CompiledKernel (Zero-Overhead Dispatch API) #
Separates shader compilation from dispatch. A CompiledKernel holds the compiled
pipeline and binding layout, ready for buffer binding and dispatch without any
string matching, WGSL regeneration, or cache lookups.
Usage:
-- At initialization (once):
let kernel ← buildKernel device myShaderM config
let bg ← bindKernel device kernel [("input", inBuf), ("output", outBuf)]
-- At dispatch time (hot loop, zero overhead):
dispatchKernel device kernel bg (numWorkgroups, 1, 1)
-- Or combine into PreparedDispatch for even fewer indirections:
let prepared := kernel.prepare bg
replayPreparedDispatch device prepared wx wy wz
Pre-compiled kernel: pipeline + layout + binding order.
Created once via buildKernel, reused across dispatches.
- pipeline : WebGPU.ComputePipeline
- bindGroupLayout : WebGPU.BindGroupLayout
- sourceHash : UInt64
Instances For
Create a PreparedDispatch from this kernel and a bind group
Instances For
Compile a ShaderM computation into a reusable CompiledKernel. Uses the global pipeline cache. Thread-safe for repeated calls.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Create a BindGroup by matching named buffers to a CompiledKernel's bindings. Uses the global bind group cache.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Create a BindGroup from pre-sorted buffer array (no name matching). Buffers must be in binding order (matching kernel.declaredNames).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Dispatch a compiled kernel with a pre-built BindGroup. Zero string matching. Works in both batch mode and standalone mode.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Create shader module from ShaderM computation
Equations
- One or more equations did not get rendered due to their size.
Instances For
Execute a ShaderM computation on the GPU with named buffers.
This is the main high-level execution function. It:
- Compiles the ShaderM computation to WGSL
- Looks up or creates the GPU pipeline (cached)
- Binds buffers by name
- Dispatches the compute shader
- Waits for completion
Parameters:
- device: GPU device
- computation: ShaderM monad defining the shader
- namedBuffers: List of (name, buffer) pairs for binding
- config: Execution configuration (workgroup size, dispatch size)
Example:
let kernel : ShaderM Unit := do
let gid ← globalId
let idx := Exp.vecZ gid
let _input ← declareInputBuffer "input" (.array (.scalar .f32) 1024)
let _output ← declareOutputBuffer "output" (.array (.scalar .f32) 1024)
let val ← readBuffer (ty := .scalar .f32) (n := 1024) "input" idx
writeBuffer (ty := .scalar .f32) "output" idx (Exp.mul val (Exp.litF32 2.0))
executeShaderNamed device kernel
[("input", inputBuf), ("output", outputBuf)]
(ExecutionConfig.dispatch1D 1024)
Equations
- One or more equations did not get rendered due to their size.
Instances For
Record a ShaderM computation into a command encoder (no submit, no wait).
This is the batched variant of executeShaderNamed. Instead of creating its own
command encoder and waiting, it records the dispatch into a pre-existing encoder.
The caller must call submitAndWait after recording all dispatches.
Pipeline caching is shared with executeShaderNamed.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Execute a simple ShaderM computation with a single input/output buffer.
Convenience wrapper for the common case of one input buffer and one output buffer.
Example:
let kernel : ShaderM Unit := do
let gid ← globalId
let idx := Exp.vecZ gid
let _input ← declareInputBuffer "input" (.array (.scalar .f32) 1024)
let _output ← declareOutputBuffer "output" (.array (.scalar .f32) 1024)
let val ← readBuffer (ty := .scalar .f32) (n := 1024) "input" idx
writeBuffer (ty := .scalar .f32) "output" idx (Exp.mul val (Exp.litF32 2.0))
executeShaderSimple device kernel inputBuf outputBuf 1024
Equations
- One or more equations did not get rendered due to their size.
Instances For
Print generated WGSL for debugging
Equations
- One or more equations did not get rendered due to their size.