All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Workspace with
beatlm-core(pure domain) andbeatlm(wgpu umbrella), building for native targets andwasm32-unknown-unkn 6BA5 own. beatlm-coretiming domain: frame budgets, a measurement-fed cost model, delivery promises (fixed latency, same frame, on beat) and a scheduler that delivers results exactly on their promised frame or reports the miss.beatlm::gpu::SliceTimer: GPU time per compute and render pass from timestamp queries, read back without blocking the frame loop.beatlm-bench: a headless synthetic-load benchmark of the fixed-latency promise, with its first baseline recorded in the crate docs.beatlm-bench --mode sync|thread|asyncand--runs, with a CPU breakdown into planning, encoding andqueue.submit.beatlm-core::device::DeviceProfileandbeatlm-core::memory::MemoryPlan: adapter limits as values, and tensor placement in shared arenas.beatlm::gpu::profileandbeatlm::gpu::Arenas: the translation from wgpu limits, and a plan realised as buffers once.SyncPolicy::max_in_flight: how many output slots a host keeps.beatlm-bench --workload net: a small planned network with GPU-checked kernels and wgpu counters around the frame loop.beatlm-core::cost::Estimator: mean plus sigmas, windowed quantile and capped deviation.beatlm-bench --estimator,--background-load, and thedelivery_rateandgpu_frames_over_periodreport lines.beatlm-core::kernel: a typed kernel IR (convolution, layer norm, matrix multiply and element-wise operations), lowering with epilogue fusion that can be switched off, kernel specs checked against a device profile with a baseline variant, and tensor lifetimes for the memory plan derived from the kernels.beatlm::codegen: WGSL generated from kernel specs and validated by naga before a pipeline is created, andKernels, every pipeline and bind group of a program created once.beatlm-bench --fusion on|offand--dump-wgsl, and thedispatches_per_job,dispatches_per_frameandkernelsreport lines.- Kernel splitting:
kernel::Part,Program::splitandProgram::parts_within, with per-part ranges as WGSL override constants. beatlm-bench --max-dispatch-usand--split on|off, a paced calibration before the loop, and thelongest_dispatch_usandkernel_partsreport lines.beatlm::codegen::Generator, validating each distinct WGSL text once, andbeatlm::gpu::MAX_SLICES.beatlm-benchcalibrates split parts in a second pass so the loop starts from their measurements, refuses splits beyond the timer's capacity before building anything, and reportsstartup_missed.beatlm::load: a safetensors reader and a configuration-derived inventory of the tensors Depth Anything V2 Small needs, checked against the file.just setup-depth-anythingandjust reference-depth-anything: user-run setup that fetches the Apache-2.0 Small weights from a pinned commit and writes reference outputs from the transformers implementation; neither is part of the build orjust check.- Kernel IR operations for a ViT encoder: patch embedding, class token, attention scores, softmax and values, exact GELU, LayerScale, and tokens to a feature map; layer norm takes its own epsilon.
beatlm::model: graph building from weights by role, and the Depth Anything V2 Small encoder, checked against the transformers reference.- Kernel IR operations for a DPT decoder: strided 3x3 convolution, 1x1
convolution, transposed convolution with kernel equal to stride, bilinear
resize with
align_corners=True, and a ReLU prologue on 3x3 convolutions. - The whole Depth Anything V2 Small model (
model::depth_anything_v2::model), whose depth matches the transformers reference on the real weights. - Input sides other than 518 (multiples of 14), with the position embedding
resampled on the host at load as transformers does, and
just reference-depth-anything <size>. beatlm-bench --workload depth,--sizeand--submit every|free: the real model under the fixed-latency promise; the measured default is 266 px at a latency of 5 frames on Apple M1 Pro.beatlm-web: a browser host driving the engine fromrequestAnimationFrameover WebGPU, withjust setup-web,build-webandserve-web;beatlm::gpu::required_limitsasks the adapter for exactly what a plan needs.beatlm-web --model depth: Depth Anything V2 Small in the browser, with the weights served locally byjust serve-webunder/models/(never in the build output), checked against the reference at the same side, andlatency_msfor a promise in time.beatlm::load::depth_anything_v2::reference: the reference input, file layout, tolerances and error measures shared by the native test and the page.beatlm-web:estimatorselects the cost model's estimator with the bench's names; depth runs report where slice times run long (tail_*) and whether a job fits its promised frames (capacity_frames_needed).beatlm::gpu::PassSpan: each timed pass's begin and end timestamps, kept inFrameTimingbeside the durations.Estimator::FrameScaled(opt-in,--estimator frame-scaled,estimator=frame-scaled): plans warm slices at their loop median times a measured frame scale; withCostModel::record_frame,record_calibration,planning_estimateand theframe_ratio/frame_scalereport lines.beatlm-websync=onbeat: results delivered on beats from a Web Audio clock (bpm,bpm_change,margin_ms,beats), started by a click, with beat landing error and tempo-change lines in the report.pass-costworkload (beatlm-bench --workload pass-cost,beatlm-web ?model=pass-cost): what a compute pass and its timestamps cost, natively and in the browser;Kernels::recordandNet::recordrecord a kernel into an open pass.beatlm-webrender_iters: the render pass's per-pixel loop count for synthetic, depth and on-beat runs (default 50, as the bench).render_order(beatlm-bench --render-order,beatlm-web render_order,free|after; the bench defaults tofree, the browser toafter):aftermakes the render wait for the frame's slices by reading every arena of the plan, refusing a plan of more arenas than one shader stage binds before the bench calibrates or the page fetches the weights;pass_overlap_*,pass_orderandslice_vs_render_overlapreport how the render and the slices overlap, andpass_overlap_next_framewhere the next frame's first slice began against the render's end.
- Design is documented in the crate docs; the separate ADR directory is gone.
- The concurrency model is a synchronous
tickper frame, decided by measurement and recorded in thebeatlmcrate docs. - The scheduler runs jobs strictly in submission order; leftover budget is no longer given to a later job's slice, because jobs share one memory plan.
CostModel::newtakes anEstimator; the default is the 0.9 quantile of the last 32 measurements, chosen by measurement (breaking).beatlm-bench --workload netruns generated kernels; the hand-writtennet.wgslis gone, and the network gained biases, a ReLU, a GELU, a residual add and a scale for fusion to absorb.- Matrix products and 3x3 convolutions use a register-tiled kernel (64x64 or 64x32 outputs per workgroup, 4x4 per invocation); the 3x3 convolution is an implicit GEMM. Splits fall on 64-row boundaries. Depth Anything V2 Small runs about four times faster with unchanged results.
- On-beat deadlines are projected against the expected frame budget and the
jobs queued ahead (
Scheduler::set_expected_budget); a job that cannot make its deadline is abandoned and reported at once (Missed::due); the beat grid lives inSyncPolicy::OnBeatand can be replaced mid-session (replace_beat_grid,time::BeatMap).Scheduler::newno longer takes a grid or returns aResult(breaking). beatlm-webpaces frames by the display: everyrequestAnimationFramecallback is a frame, started at the callback's timestamp, with the period measured from the display instead of a fixed 60 Hz grid. Latency promised in frames is shorter in time on a faster display. The report addsdisplay_period_usandcallback_interval_us.- A job projected to miss its deadline is abandoned at once only when another job waits behind it; a lone one keeps running and is decided at its deadline, so its slices are measured and estimates can come down.
FrameTimestamps::resolvereturns aResolvedFrame, which the host hands toSliceTimer::submittedstraight after thequeue.submitcarrying the encoder; that call requests the map.SliceTimer::start_readbackis gone (breaking).- The code both hosts compile (the synthetic workload and its shaders, the
slot-backed
Net, thepass-costandpass_overlap_*lines, the report's statistics) moved into a new unpublished crate,beatlm-host, which the bench and the page depend on instead of compiling the bench's files by#[path]. No behaviour or report line changed. - The cost-model tuning both hosts share (window, quantile, alpha, sigmas,
cap, priors, margin, and the startup, calibration and render windows
derived from the window) and the four bare estimator presets live once in
beatlm_host::presets; the per-slice sample cutoff is onebeatlm_host::stats::MIN_SAMPLES. Every value is unchanged. - The report lines both hosts write come from one writer,
beatlm_host::report, pinned by golden tests of each host's full report. In the page's reportframe_ratioandframe_scalemoved from afterjob_estimate_usto after thepass_overlap_*lines, where the bench writes them; no other line changed name, order or format. - The clock-free pieces of a frame's step (the render budget and forecast,
admission under
submit=every|free, the timer-capacity check and the timer label) live once inbeatlm_host::frame. The page's calibration now runs the bench's timer-capacity checks before each pass, so a split the loop's timer cannot hold is refused before calibration spends its frames. - Both hosts drive one clock-free frame step,
beatlm_host::step::FrameStep(plan, after_submit, absorb, complete, finish into aTally), and keep only their clock, device calls and I/O. The order plan → after_submit → complete is held by types. No plan, count or report line changed. - Which calibration frames count (the second half of each pass) and the
first pass's per-kernel medians come from
beatlm_host::calibration; each host still drives the two passes itself. - A slice whose median is 0 (it runs under one timestamp step) is left out
of every statistic that compares measurements with a median, in both
hosts: the page's
tail_outliers,tail_outlier_frames,tail_busy_previousandtail_loop_over_calibrationno longer count it, aspass_overlap_*already did.tail_quantizationkeeps it.
-
SliceTimer: a frame resolved into an encoder that was then dropped instead of submitted is no longer read; its slot held an earlier frame's timestamps, which were reported as its own and fed to the cost model. -
SliceTimer::invalidate_submitted: a host that learns the device rejected a submitted command buffer marks what it submitted, and those frames are counted intimings_failedinstead of reporting an earlier lap's timestamps.beatlm-webstill marks them when an error outside every frame's scope (below) has been reported before it collects. Natively a rejected command buffer still panics in wgpu's default handler. -
beatlm-webputs a validation error scope around each frame'sfinishandsubmitand holds the frame (SliceTimer::submitted_held,HeldFrame) until the scope settles: a frame the device rejected is never read, and only that frame loses its timing.SliceTimer::collect_readyreturns frames in submission order across calls.gpu_errorscounts a rejected frame once. -
beatlm-web: a depth result reaches the screen only in the frame that delivers it; a later job on the same slot could show its depth before then. -
The lock no longer resolves the yanked
tokio1.54.0 (viawasm-bindgen-futures); it pins 1.53.2.