webgpu-bench 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +120 -0
  3. package/dist/benchmarks/common.d.ts +71 -0
  4. package/dist/benchmarks/common.d.ts.map +1 -0
  5. package/dist/benchmarks/common.js +104 -0
  6. package/dist/benchmarks/common.js.map +1 -0
  7. package/dist/benchmarks/flopsCommon.d.ts +52 -0
  8. package/dist/benchmarks/flopsCommon.d.ts.map +1 -0
  9. package/dist/benchmarks/flopsCommon.js +78 -0
  10. package/dist/benchmarks/flopsCommon.js.map +1 -0
  11. package/dist/benchmarks/flopsConvert.d.ts +18 -0
  12. package/dist/benchmarks/flopsConvert.d.ts.map +1 -0
  13. package/dist/benchmarks/flopsConvert.js +61 -0
  14. package/dist/benchmarks/flopsConvert.js.map +1 -0
  15. package/dist/benchmarks/flopsF16.d.ts +12 -0
  16. package/dist/benchmarks/flopsF16.d.ts.map +1 -0
  17. package/dist/benchmarks/flopsF16.js +54 -0
  18. package/dist/benchmarks/flopsF16.js.map +1 -0
  19. package/dist/benchmarks/flopsF32.d.ts +12 -0
  20. package/dist/benchmarks/flopsF32.d.ts.map +1 -0
  21. package/dist/benchmarks/flopsF32.js +50 -0
  22. package/dist/benchmarks/flopsF32.js.map +1 -0
  23. package/dist/benchmarks/flopsI8.d.ts +16 -0
  24. package/dist/benchmarks/flopsI8.d.ts.map +1 -0
  25. package/dist/benchmarks/flopsI8.js +83 -0
  26. package/dist/benchmarks/flopsI8.js.map +1 -0
  27. package/dist/benchmarks/flopsMath.d.ts +28 -0
  28. package/dist/benchmarks/flopsMath.d.ts.map +1 -0
  29. package/dist/benchmarks/flopsMath.js +98 -0
  30. package/dist/benchmarks/flopsMath.js.map +1 -0
  31. package/dist/benchmarks/streamBandwidth.d.ts +17 -0
  32. package/dist/benchmarks/streamBandwidth.d.ts.map +1 -0
  33. package/dist/benchmarks/streamBandwidth.js +78 -0
  34. package/dist/benchmarks/streamBandwidth.js.map +1 -0
  35. package/dist/data/generate.d.ts +23 -0
  36. package/dist/data/generate.d.ts.map +1 -0
  37. package/dist/data/generate.js +41 -0
  38. package/dist/data/generate.js.map +1 -0
  39. package/dist/gpu/benchmarkRunner.d.ts +98 -0
  40. package/dist/gpu/benchmarkRunner.d.ts.map +1 -0
  41. package/dist/gpu/benchmarkRunner.js +178 -0
  42. package/dist/gpu/benchmarkRunner.js.map +1 -0
  43. package/dist/gpu/buffers.d.ts +8 -0
  44. package/dist/gpu/buffers.d.ts.map +1 -0
  45. package/dist/gpu/buffers.js +41 -0
  46. package/dist/gpu/buffers.js.map +1 -0
  47. package/dist/gpu/context.d.ts +16 -0
  48. package/dist/gpu/context.d.ts.map +1 -0
  49. package/dist/gpu/context.js +71 -0
  50. package/dist/gpu/context.js.map +1 -0
  51. package/dist/gpu/timing.d.ts +20 -0
  52. package/dist/gpu/timing.d.ts.map +1 -0
  53. package/dist/gpu/timing.js +55 -0
  54. package/dist/gpu/timing.js.map +1 -0
  55. package/dist/index.d.ts +12 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +7 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/sampling.d.ts +96 -0
  60. package/dist/sampling.d.ts.map +1 -0
  61. package/dist/sampling.js +239 -0
  62. package/dist/sampling.js.map +1 -0
  63. package/dist/shaders/flopsF16Mat4.d.ts +7 -0
  64. package/dist/shaders/flopsF16Mat4.d.ts.map +1 -0
  65. package/dist/shaders/flopsF16Mat4.js +40 -0
  66. package/dist/shaders/flopsF16Mat4.js.map +1 -0
  67. package/dist/shaders/flopsF16Matvec.d.ts +14 -0
  68. package/dist/shaders/flopsF16Matvec.d.ts.map +1 -0
  69. package/dist/shaders/flopsF16Matvec.js +58 -0
  70. package/dist/shaders/flopsF16Matvec.js.map +1 -0
  71. package/dist/shaders/flopsF16Scalar.d.ts +8 -0
  72. package/dist/shaders/flopsF16Scalar.d.ts.map +1 -0
  73. package/dist/shaders/flopsF16Scalar.js +71 -0
  74. package/dist/shaders/flopsF16Scalar.js.map +1 -0
  75. package/dist/shaders/flopsF16Vec4.d.ts +7 -0
  76. package/dist/shaders/flopsF16Vec4.d.ts.map +1 -0
  77. package/dist/shaders/flopsF16Vec4.js +35 -0
  78. package/dist/shaders/flopsF16Vec4.js.map +1 -0
  79. package/dist/shaders/flopsF32Div.d.ts +10 -0
  80. package/dist/shaders/flopsF32Div.d.ts.map +1 -0
  81. package/dist/shaders/flopsF32Div.js +71 -0
  82. package/dist/shaders/flopsF32Div.js.map +1 -0
  83. package/dist/shaders/flopsF32F16Convert.d.ts +11 -0
  84. package/dist/shaders/flopsF32F16Convert.d.ts.map +1 -0
  85. package/dist/shaders/flopsF32F16Convert.js +73 -0
  86. package/dist/shaders/flopsF32F16Convert.js.map +1 -0
  87. package/dist/shaders/flopsF32Log.d.ts +11 -0
  88. package/dist/shaders/flopsF32Log.d.ts.map +1 -0
  89. package/dist/shaders/flopsF32Log.js +71 -0
  90. package/dist/shaders/flopsF32Log.js.map +1 -0
  91. package/dist/shaders/flopsF32Mat4.d.ts +9 -0
  92. package/dist/shaders/flopsF32Mat4.d.ts.map +1 -0
  93. package/dist/shaders/flopsF32Mat4.js +38 -0
  94. package/dist/shaders/flopsF32Mat4.js.map +1 -0
  95. package/dist/shaders/flopsF32Matvec.d.ts +14 -0
  96. package/dist/shaders/flopsF32Matvec.d.ts.map +1 -0
  97. package/dist/shaders/flopsF32Matvec.js +58 -0
  98. package/dist/shaders/flopsF32Matvec.js.map +1 -0
  99. package/dist/shaders/flopsF32Pow.d.ts +10 -0
  100. package/dist/shaders/flopsF32Pow.d.ts.map +1 -0
  101. package/dist/shaders/flopsF32Pow.js +71 -0
  102. package/dist/shaders/flopsF32Pow.js.map +1 -0
  103. package/dist/shaders/flopsF32Rsqrt.d.ts +12 -0
  104. package/dist/shaders/flopsF32Rsqrt.d.ts.map +1 -0
  105. package/dist/shaders/flopsF32Rsqrt.js +72 -0
  106. package/dist/shaders/flopsF32Rsqrt.js.map +1 -0
  107. package/dist/shaders/flopsF32Scalar.d.ts +11 -0
  108. package/dist/shaders/flopsF32Scalar.d.ts.map +1 -0
  109. package/dist/shaders/flopsF32Scalar.js +72 -0
  110. package/dist/shaders/flopsF32Scalar.js.map +1 -0
  111. package/dist/shaders/flopsF32Sincos.d.ts +9 -0
  112. package/dist/shaders/flopsF32Sincos.d.ts.map +1 -0
  113. package/dist/shaders/flopsF32Sincos.js +68 -0
  114. package/dist/shaders/flopsF32Sincos.js.map +1 -0
  115. package/dist/shaders/flopsF32Sqrt.d.ts +9 -0
  116. package/dist/shaders/flopsF32Sqrt.d.ts.map +1 -0
  117. package/dist/shaders/flopsF32Sqrt.js +69 -0
  118. package/dist/shaders/flopsF32Sqrt.js.map +1 -0
  119. package/dist/shaders/flopsF32Vec4.d.ts +9 -0
  120. package/dist/shaders/flopsF32Vec4.d.ts.map +1 -0
  121. package/dist/shaders/flopsF32Vec4.js +33 -0
  122. package/dist/shaders/flopsF32Vec4.js.map +1 -0
  123. package/dist/shaders/flopsI32Div.d.ts +10 -0
  124. package/dist/shaders/flopsI32Div.d.ts.map +1 -0
  125. package/dist/shaders/flopsI32Div.js +71 -0
  126. package/dist/shaders/flopsI32Div.js.map +1 -0
  127. package/dist/shaders/flopsI32F16Convert.d.ts +10 -0
  128. package/dist/shaders/flopsI32F16Convert.d.ts.map +1 -0
  129. package/dist/shaders/flopsI32F16Convert.js +71 -0
  130. package/dist/shaders/flopsI32F16Convert.js.map +1 -0
  131. package/dist/shaders/flopsI32F32Convert.d.ts +10 -0
  132. package/dist/shaders/flopsI32F32Convert.d.ts.map +1 -0
  133. package/dist/shaders/flopsI32F32Convert.js +71 -0
  134. package/dist/shaders/flopsI32F32Convert.js.map +1 -0
  135. package/dist/shaders/flopsI8Dp4a.d.ts +23 -0
  136. package/dist/shaders/flopsI8Dp4a.d.ts.map +1 -0
  137. package/dist/shaders/flopsI8Dp4a.js +49 -0
  138. package/dist/shaders/flopsI8Dp4a.js.map +1 -0
  139. package/dist/shaders/flopsI8Mat4.d.ts +9 -0
  140. package/dist/shaders/flopsI8Mat4.d.ts.map +1 -0
  141. package/dist/shaders/flopsI8Mat4.js +41 -0
  142. package/dist/shaders/flopsI8Mat4.js.map +1 -0
  143. package/dist/shaders/flopsI8Matvec.d.ts +16 -0
  144. package/dist/shaders/flopsI8Matvec.d.ts.map +1 -0
  145. package/dist/shaders/flopsI8Matvec.js +57 -0
  146. package/dist/shaders/flopsI8Matvec.js.map +1 -0
  147. package/dist/shaders/flopsI8MatvecDp4a.d.ts +17 -0
  148. package/dist/shaders/flopsI8MatvecDp4a.d.ts.map +1 -0
  149. package/dist/shaders/flopsI8MatvecDp4a.js +60 -0
  150. package/dist/shaders/flopsI8MatvecDp4a.js.map +1 -0
  151. package/dist/shaders/flopsI8Scalar.d.ts +9 -0
  152. package/dist/shaders/flopsI8Scalar.d.ts.map +1 -0
  153. package/dist/shaders/flopsI8Scalar.js +70 -0
  154. package/dist/shaders/flopsI8Scalar.js.map +1 -0
  155. package/dist/shaders/flopsI8Vec4.d.ts +7 -0
  156. package/dist/shaders/flopsI8Vec4.d.ts.map +1 -0
  157. package/dist/shaders/flopsI8Vec4.js +31 -0
  158. package/dist/shaders/flopsI8Vec4.js.map +1 -0
  159. package/dist/shaders/flopsU32PackUnpack.d.ts +11 -0
  160. package/dist/shaders/flopsU32PackUnpack.d.ts.map +1 -0
  161. package/dist/shaders/flopsU32PackUnpack.js +55 -0
  162. package/dist/shaders/flopsU32PackUnpack.js.map +1 -0
  163. package/dist/shaders/streamRead.d.ts +10 -0
  164. package/dist/shaders/streamRead.d.ts.map +1 -0
  165. package/dist/shaders/streamRead.js +33 -0
  166. package/dist/shaders/streamRead.js.map +1 -0
  167. package/dist/shaders/streamWrite.d.ts +8 -0
  168. package/dist/shaders/streamWrite.d.ts.map +1 -0
  169. package/dist/shaders/streamWrite.js +29 -0
  170. package/dist/shaders/streamWrite.js.map +1 -0
  171. package/dist/stats.d.ts +31 -0
  172. package/dist/stats.d.ts.map +1 -0
  173. package/dist/stats.js +67 -0
  174. package/dist/stats.js.map +1 -0
  175. package/dist/suite.d.ts +25 -0
  176. package/dist/suite.d.ts.map +1 -0
  177. package/dist/suite.js +196 -0
  178. package/dist/suite.js.map +1 -0
  179. package/dist/types.d.ts +171 -0
  180. package/dist/types.d.ts.map +1 -0
  181. package/dist/types.js +2 -0
  182. package/dist/types.js.map +1 -0
  183. package/package.json +45 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben Houston
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # webgpu-profiler
2
+
3
+ A focused benchmark suite for the raw ceilings of a WebGPU device: memory **bandwidth** (read and write)
4
+ and **FLOPS** (fp32, fp16, and int8, at scalar / vec4 / mat4 / register-resident-matvec granularity), run head to head in the
5
+ browser, built on [`vgpu`](https://github.com/vercel-labs/vgpu).
6
+
7
+ Run it live: click **Run benchmark suite** on the website (`packages/website`) and watch results stream
8
+ into a table as each kernel finishes.
9
+
10
+ ## What it measures
11
+
12
+ Every benchmark deliberately isolates a single resource — memory read, memory write, or ALU throughput —
13
+ by keeping the other two as close to zero as the WebGPU compute model allows, rather than modeling a
14
+ specific real-world op. That makes each number directly comparable to the device's published
15
+ bandwidth/FLOPS specs.
16
+
17
+ | Benchmark | What it tests |
18
+ | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `read-bandwidth` | Streams a large buffer in via `vec4<f32>` loads folded with addition only (no multiply, one scalar written per thread) — a read-bandwidth-bound probe of peak storage-buffer read throughput |
20
+ | `write-bandwidth` | Streams computed `vec4<f32>` values out into a large buffer with no buffer reads at all — a write-bandwidth-bound probe of peak storage-buffer write throughput |
21
+ | `flops-f32-scalar` | Raw fp32 FLOPS: eight _independent_ scalar FMA chains per thread, unrolled 4x, so the ALU always has work in flight (a single dependent chain would measure FMA latency + loop overhead, not throughput) |
22
+ | `flops-f32-vec4` | One FMA chain held in a `vec4<f32>` register. On scalar-SIMT GPUs (Apple, NVIDIA, AMD) that is 4 independent scalar FMAs per step — it measures 4-wide instruction-level parallelism, not a wider ALU |
23
+ | `flops-f32-mat4` | `x = m * x + c` chained with a `mat4x4<f32>` (a bounded contraction, so it stays numerically stable over any iteration count) |
24
+ | `flops-f32-matvec` | Register-resident matvec tile: a 4x8 f32 weight tile held in registers, applied to an 8-wide input every iteration via `dot()`, outputs feeding back as the next inputs. The dot-product-accumulate shape of a GEMV inner loop with zero buffer traffic |
25
+ | `flops-f16-scalar` / `-vec4` / `-mat4` / `-matvec` | The same four shapes, entirely in `f16` (skipped if the device lacks `shader-f16`) |
26
+ | `flops-i8-scalar` / `-vec4` / `-mat4` | The scalar / vec4 shapes on `i32` (WGSL has no first-class `i8` type), and a 4x4 integer matvec emulated as four `vec4<i32>` rows combined with `dot()` (WGSL has no `mat4x4<i32>`) |
27
+ | `flops-i8-matvec` | The register-resident matvec tile with int8-range weights/inputs held unpacked as `vec4<i32>` and integer `dot()` accumulation — the no-extension int8 path |
28
+ | `flops-i8-matvec-dp4a` | The same tile with weights/inputs kept packed four int8 lanes per `u32`, each 4-wide dot product a single `dot4I8Packed` (skipped without `packed_4x8_integer_dot_product`) |
29
+ | `flops-i8-dp4a` | `dot4I8Packed` accumulated in a tight loop to isolate the instruction's peak throughput (skipped without the feature) |
30
+
31
+ The two bandwidth benchmarks stream the same deterministically-generated buffer (default 4096 &times;
32
+ 4096 f32, padded to a multiple of 4). The raw-FLOPS kernels' loop trip count and per-lane operands are
33
+ runtime values (derived from a uniform and the thread id), so the shader compiler can't constant-fold or
34
+ hoist the FMA chain away. Each kernel has its own ceiling on the trip count (256 for the unrolled scalar
35
+ chains, 512 for the matvec tiles, 1024 otherwise); the actual count is calibrated per GPU so a single
36
+ dispatch takes ~10ms (see below), which keeps per-thread setup, the final store, and dispatch overhead
37
+ negligible without ever freezing the display. `computeIterations` pins it. The integer variants rely on i32/u32 wraparound (defined, trap-free
38
+ two's-complement behavior in WGSL) to stay bounded instead of the float variants' damped/contraction
39
+ operands.
40
+
41
+ ## How it samples (and why the number is the _best_ run)
42
+
43
+ Benchmarks aren't run one after another to completion. That's the naive approach, and on a phone it
44
+ fails in a specific way: a few seconds of saturated GPU is enough to hit thermal throttling, so every
45
+ benchmark after the first couple measures a slowed-down device, and the numbers depend on the run
46
+ order. Instead the suite:
47
+
48
+ 1. **Prepares every kernel up front** (shader compile, buffers, bind groups) so rows that can't run —
49
+ missing `shader-f16` / `packed_4x8_integer_dot_product`, a failed shader compile — resolve as
50
+ `skipped` / `error` immediately and the table has its final shape before any timing starts.
51
+ 2. **Sizes each dispatch for this GPU.** A GPU dispatch can't be preempted, so a long one freezes
52
+ the display for its whole duration. The raw-FLOPS kernels' loop trip count is therefore not fixed:
53
+ on its first visit each kernel is probed with a tiny trip count (16) and ramped up — never more than
54
+ 4x per step — until one dispatch takes about `targetDispatchMs` (10ms), capped at the kernel's own
55
+ default (256–1024). A desktop lands near the cap; a phone lands on a much smaller count; neither
56
+ ever runs a dispatch longer than a few tens of ms, even during calibration. `computeIterations`
57
+ pins the count and disables this.
58
+ 3. **Samples round-robin, in a fresh random order each round.** Each round takes one short timed measurement (~100ms, `targetMs`: a batch
59
+ of those ~10ms dispatches in one command buffer) of every still-active kernel, with an idle gap
60
+ (`idleMs`, 100ms) between measurements so the GPU duty-cycles instead of running flat out. The
61
+ calibration probes double as warm-up; one further discarded warmup measurement (`warmups`) follows.
62
+ 4. **Reports the best run.** Every noise source a benchmark meets — throttling, clock ramp, compositor
63
+ frames, other apps — only ever makes a run _slower_, so the minimum is the least-contaminated
64
+ estimate of the device's capability. Throughput/bandwidth are derived from `stats.min`; the table
65
+ updates after every measurement and its numbers only ever improve.
66
+ 5. **Converges when the best stops improving.** A kernel retires once it has at least `minRounds` (3)
67
+ kept measurements and its best hasn't improved by more than `improvementTolerance` (1%) over the
68
+ last `stableRounds` (2) of them. On a cool GPU that's 3 measurements per kernel; a GPU still
69
+ ramping its clock keeps sampling, up to `maxRounds` (10, flagged `max-rounds`).
70
+ 6. **Discards throttled runs.** A measurement more than `throttleThreshold` (20%) slower than that
71
+ kernel's best is thermal noise, not information: it's recorded in `throttledMs` for diagnostics
72
+ but never feeds `stats` or convergence.
73
+ 7. **Pauses the whole suite when the device is throttling.** If half the kernels in a round come back
74
+ throttled (`throttledFraction`, at least two of them), the suite sleeps
75
+ for `cooldownMs` (3s) and tries again — throttling recovers, so a later round can still beat the
76
+ current best. After `maxCooldowns` (3) pauses it gives up: whatever is still unconverged is
77
+ reported with its best run so far and `stopReason: 'throttled'` (shown as a warning in the table).
78
+
79
+ `onProgress` reports round boundaries and cooldowns; all of the knobs above are `SuiteOptions`.
80
+ GPU timing uses `timestamp-query` when available (pure device-side time, no CPU/driver overhead);
81
+ otherwise it falls back to wall-clock around `queue.onSubmittedWorkDone()`. "Available" is checked, not
82
+ trusted: every timestamp reading is compared with wall clock (minus the fixed submit/readback overhead,
83
+ measured with empty submits at calibration) and a kernel is demoted to wall-clock timing once the GPU
84
+ reading comes in under half of that twice running. Safari needs this — outside a cross-origin-isolated
85
+ context its timestamps are quantized to the point of reporting ~1ms for a 70ms batch — and all batch
86
+ sizing uses wall clock regardless, since a batch sized from a bogus timestamp is a multi-second command
87
+ buffer that hangs the GPU process. Two further hygiene rules: a measurement during which the tab was
88
+ hidden is dropped (background tabs get a throttled event loop and a lower-priority GPU queue) and the
89
+ suite waits for the page to be visible again; and the idle gap between measurements ends on
90
+ `requestIdleCallback`, so the results table's re-render and the compositor frame it triggers land in the
91
+ gap rather than inside the next measurement. Mean / median / stddev /
92
+ ci95 over the kept runs are still in `BenchmarkResult.stats` if you want a sustained number — the
93
+ table just shows the best.
94
+
95
+ ## Monorepo layout
96
+
97
+ - `packages/webgpu-bench` — the benchmark suite itself (WGSL shaders, data generation, timing
98
+ harness, orchestration). Framework-agnostic, browser-only, consumed as TypeScript source.
99
+ - `packages/website` — TanStack Start + Tailwind CSS + shadcn/ui app that runs the suite client-side and
100
+ renders results incrementally. Dev server on **port 3500**.
101
+
102
+ ## Development
103
+
104
+ ```bash
105
+ pnpm install
106
+ pnpm dev # starts the website on http://localhost:3500
107
+ pnpm build # typecheck webgpu-bench + build the website
108
+ pnpm lint
109
+ pnpm tsc
110
+ ```
111
+
112
+ Requires a WebGPU-capable browser (recent Chrome/Edge desktop). `f16`/`int8`-dot-product benchmarks report
113
+ themselves as "skipped" with an explanation when the browser/GPU doesn't support the underlying WebGPU
114
+ feature, rather than failing the whole run.
115
+
116
+ ## Deployment
117
+
118
+ `packages/website/Dockerfile` builds the whole workspace and serves the TanStack Start (Nitro) production
119
+ server on port 3500. `.github/workflows/deploy.yml` deploys it to Cloud Run on push to `main` (reuses the
120
+ generic `deploy-service.yml` workflow).
@@ -0,0 +1,71 @@
1
+ import type { GpuContext } from '../gpu/context.ts';
2
+ import type { KernelHarness, MeasurementConfig, WorkKnob } from '../gpu/benchmarkRunner.ts';
3
+ import type { BenchmarkCategory, BenchmarkResult, MetricDef } from '../types.ts';
4
+ /** Shared metric definitions. `key` is what a JSON export would key the value by, stable across every benchmark that reports it. */
5
+ export declare const FLOPS_METRIC: MetricDef;
6
+ export declare const OPS_METRIC: MetricDef;
7
+ export declare const BYTES_METRIC: MetricDef;
8
+ /**
9
+ * Compiles a shader module and builds a compute pipeline from it, watching
10
+ * for validation errors via error scopes rather than trusting the WebGPU
11
+ * calls to throw. Both `createShaderModule` and `createComputePipeline`
12
+ * (`'auto'` layout) succeed synchronously even when the WGSL fails to
13
+ * compile or the pipeline is otherwise invalid — the failure only surfaces
14
+ * later as a device-level 'uncapturederror' when something dispatches
15
+ * against it. Left unchecked, that means the compute pass silently does
16
+ * nothing: the command buffer still submits and completes almost
17
+ * instantly, so a GPU-timestamp read of a never-touched, zero-initialized
18
+ * query buffer comes back as exactly 0ns elapsed — reported as a
19
+ * misleadingly "ok" benchmark result with 0s timings, rather than the
20
+ * shader-compile failure it actually is.
21
+ */
22
+ export declare function createPipeline(device: GPUDevice, label: string, code: string, constants?: Record<string, number>): Promise<GPUComputePipeline>;
23
+ /** `<metric.unit>/s`, from the amount of that unit moved/computed in one op and that op's best time. */
24
+ export declare function metricPerSecond(amountPerOp: number, perOpMs: number): number;
25
+ /** Per-measurement knobs (`targetMs`, `targetDispatchMs`, `warmups`) shared by every benchmark. */
26
+ export type HarnessConfig = MeasurementConfig;
27
+ /** Everything about a benchmark that's known before it's measured. */
28
+ export interface BenchmarkMeta {
29
+ id: string;
30
+ label: string;
31
+ description: string;
32
+ /** WGSL source of the kernel, for display alongside the result. */
33
+ source: string;
34
+ category: BenchmarkCategory;
35
+ rows: number;
36
+ cols: number;
37
+ /** What this benchmark's throughput number counts. */
38
+ metric: MetricDef;
39
+ /** Amount of `metric.unit` moved/computed per op. */
40
+ amountPerOp: number;
41
+ }
42
+ /**
43
+ * A benchmark whose GPU resources are built and which is ready to be
44
+ * measured by the scheduler, or one that has already resolved to a
45
+ * `skipped` row (missing device feature).
46
+ */
47
+ export type PreparedBenchmark = {
48
+ kind: 'kernel';
49
+ meta: BenchmarkMeta;
50
+ harness: KernelHarness;
51
+ /** For kernels with a work knob: the metadata (problem size, FLOPs, bytes per op) at a given work setting. */
52
+ metaAtWork?: (work: number) => BenchmarkMeta;
53
+ } | {
54
+ kind: 'skipped';
55
+ result: BenchmarkResult;
56
+ };
57
+ export interface PrepareKernelOptions extends HarnessConfig, BenchmarkMeta {
58
+ ctx: GpuContext;
59
+ workgroupsPerIteration: [number, number, number];
60
+ pipeline: GPUComputePipeline;
61
+ bindGroup: GPUBindGroup;
62
+ work?: WorkKnob;
63
+ metaAtWork?: (work: number) => BenchmarkMeta;
64
+ }
65
+ /** Packages a built pipeline/bind group as a kernel the sampling scheduler can measure. */
66
+ export declare function prepareKernelBenchmark(opts: PrepareKernelOptions): PreparedBenchmark;
67
+ export declare function skippedResult(meta: BenchmarkMeta, message: string): BenchmarkResult;
68
+ export declare function errorResult(meta: BenchmarkMeta, error: unknown): BenchmarkResult;
69
+ /** A blank row for a benchmark: identity + problem size, no timings yet. */
70
+ export declare function rowFromMeta(meta: BenchmarkMeta): BenchmarkResult;
71
+ //# sourceMappingURL=common.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/benchmarks/common.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEjF,oIAAoI;AACpI,eAAO,MAAM,YAAY,EAAE,SAAsE,CAAC;AAClG,eAAO,MAAM,UAAU,EAAE,SAA0D,CAAC;AACpF,eAAO,MAAM,YAAY,EAAE,SAA0D,CAAC;AAEtF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,OAAO,CAAC,kBAAkB,CAAC,CAa7B;AAED,wGAAwG;AACxG,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,mGAAmG;AACnG,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAE9C,sEAAsE;AACtE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,MAAM,EAAE,SAAS,CAAC;IAClB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,aAAa,CAAC;IACvB,8GAA8G;IAC9G,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;CAC9C,GACD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,oBAAqB,SAAQ,aAAa,EAAE,aAAa;IACxE,GAAG,EAAE,UAAU,CAAC;IAChB,sBAAsB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,SAAS,EAAE,YAAY,CAAC;IACxB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;CAC9C;AAED,2FAA2F;AAC3F,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,iBAAiB,CAiCpF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,CAMnF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,GAAG,eAAe,CAMhF;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,eAAe,CAgBhE"}
@@ -0,0 +1,104 @@
1
+ /** Shared metric definitions. `key` is what a JSON export would key the value by, stable across every benchmark that reports it. */
2
+ export const FLOPS_METRIC = { key: 'flops', unit: 'FLOP', name: 'Floating-point ops' };
3
+ export const OPS_METRIC = { key: 'ops', unit: 'OP', name: 'Operations' };
4
+ export const BYTES_METRIC = { key: 'bytes', unit: 'B', name: 'Bandwidth' };
5
+ /**
6
+ * Compiles a shader module and builds a compute pipeline from it, watching
7
+ * for validation errors via error scopes rather than trusting the WebGPU
8
+ * calls to throw. Both `createShaderModule` and `createComputePipeline`
9
+ * (`'auto'` layout) succeed synchronously even when the WGSL fails to
10
+ * compile or the pipeline is otherwise invalid — the failure only surfaces
11
+ * later as a device-level 'uncapturederror' when something dispatches
12
+ * against it. Left unchecked, that means the compute pass silently does
13
+ * nothing: the command buffer still submits and completes almost
14
+ * instantly, so a GPU-timestamp read of a never-touched, zero-initialized
15
+ * query buffer comes back as exactly 0ns elapsed — reported as a
16
+ * misleadingly "ok" benchmark result with 0s timings, rather than the
17
+ * shader-compile failure it actually is.
18
+ */
19
+ export async function createPipeline(device, label, code, constants) {
20
+ device.pushErrorScope('validation');
21
+ const module = device.createShaderModule({ label, code });
22
+ const pipeline = device.createComputePipeline({
23
+ label,
24
+ layout: 'auto',
25
+ compute: { module, entryPoint: 'main', constants },
26
+ });
27
+ const error = await device.popErrorScope();
28
+ if (error) {
29
+ throw new Error(`Failed to create pipeline "${label}": ${error.message}`);
30
+ }
31
+ return pipeline;
32
+ }
33
+ /** `<metric.unit>/s`, from the amount of that unit moved/computed in one op and that op's best time. */
34
+ export function metricPerSecond(amountPerOp, perOpMs) {
35
+ return amountPerOp / (perOpMs / 1000);
36
+ }
37
+ /** Packages a built pipeline/bind group as a kernel the sampling scheduler can measure. */
38
+ export function prepareKernelBenchmark(opts) {
39
+ const { ctx, pipeline, bindGroup, workgroupsPerIteration } = opts;
40
+ return {
41
+ kind: 'kernel',
42
+ meta: {
43
+ id: opts.id,
44
+ label: opts.label,
45
+ description: opts.description,
46
+ source: opts.source,
47
+ category: opts.category,
48
+ rows: opts.rows,
49
+ cols: opts.cols,
50
+ metric: opts.metric,
51
+ amountPerOp: opts.amountPerOp,
52
+ },
53
+ metaAtWork: opts.metaAtWork,
54
+ harness: {
55
+ device: ctx.device,
56
+ useTimestamps: ctx.info.supportsTimestampQuery,
57
+ targetMs: opts.targetMs,
58
+ targetDispatchMs: opts.targetDispatchMs,
59
+ warmups: opts.warmups,
60
+ maxIterations: opts.maxIterations,
61
+ work: opts.work,
62
+ encode: (pass, iterations) => {
63
+ pass.setPipeline(pipeline);
64
+ pass.setBindGroup(0, bindGroup);
65
+ for (let i = 0; i < iterations; i++) {
66
+ pass.dispatchWorkgroups(...workgroupsPerIteration);
67
+ }
68
+ },
69
+ },
70
+ };
71
+ }
72
+ export function skippedResult(meta, message) {
73
+ return {
74
+ ...rowFromMeta(meta),
75
+ status: 'skipped',
76
+ message,
77
+ };
78
+ }
79
+ export function errorResult(meta, error) {
80
+ return {
81
+ ...rowFromMeta(meta),
82
+ status: 'error',
83
+ message: error instanceof Error ? error.message : String(error),
84
+ };
85
+ }
86
+ /** A blank row for a benchmark: identity + problem size, no timings yet. */
87
+ export function rowFromMeta(meta) {
88
+ return {
89
+ id: meta.id,
90
+ label: meta.label,
91
+ description: meta.description,
92
+ source: meta.source,
93
+ category: meta.category,
94
+ status: 'running',
95
+ rows: meta.rows,
96
+ cols: meta.cols,
97
+ innerIterations: 0,
98
+ timesMs: [],
99
+ throttledMs: [],
100
+ metric: meta.metric,
101
+ timingMethod: 'cpu-wallclock',
102
+ };
103
+ }
104
+ //# sourceMappingURL=common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/benchmarks/common.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import type { GpuContext } from '../gpu/context.ts';
2
+ import { type HarnessConfig, type PreparedBenchmark } from './common.ts';
3
+ import type { MetricDef } from '../types.ts';
4
+ export interface FlopsKernelSpec {
5
+ id: string;
6
+ label: string;
7
+ description: string;
8
+ wgsl: string;
9
+ /**
10
+ * What this kernel's per-iteration count represents. Defaults to
11
+ * `FLOPS_METRIC`; kernels doing integer or bit-twiddling work (not IEEE
12
+ * floating-point ops) should pass `OPS_METRIC` instead.
13
+ */
14
+ metric?: MetricDef;
15
+ /** Amount of `metric.unit` (same MAC-as-2 convention throughout) performed per loop iteration by a single thread. */
16
+ flopsPerIteration: number;
17
+ /**
18
+ * Loop trip count to use when the harness doesn't override it. Kernels
19
+ * that do more work per iteration (unrolled chains, matvec tiles) use
20
+ * fewer trips so every kernel's dispatch lands in the same several-ms
21
+ * range — long enough that per-thread setup, the final store, and
22
+ * dispatch overhead are negligible next to the ALU work being measured.
23
+ */
24
+ defaultIterations?: number;
25
+ /**
26
+ * Smallest loop trip count calibration may settle on. Also the first,
27
+ * deliberately cheap probe a fresh device sees. Default 16.
28
+ */
29
+ minIterations?: number;
30
+ /** Only raw-FLOPS kernels using f16 need this; skips with an explanation if the device lacks `shader-f16`. */
31
+ requiresF16?: boolean;
32
+ /** Only the `dot4I8Packed` kernel needs this; skips with an explanation if the device lacks `packed_4x8_integer_dot_product`. */
33
+ requiresI8Dot?: boolean;
34
+ }
35
+ export interface FlopsHarnessConfig extends HarnessConfig {
36
+ threads?: number;
37
+ iterations?: number;
38
+ }
39
+ /**
40
+ * Shared runner for the raw-FLOPS "little read/write, lots of ALU" compute
41
+ * benchmarks: every kernel here shares the same `Params{threads,iterations}`
42
+ * uniform + single scalar-per-thread output buffer layout, so only the WGSL
43
+ * and the FLOPs-per-iteration accounting differ between them.
44
+ *
45
+ * The loop trip count is exposed to the sampler as a work knob: unless
46
+ * `harness.iterations` pins it, the sampler calibrates it (between
47
+ * `minIterations` and the kernel's default) so a single dispatch takes about
48
+ * `targetDispatchMs` on this particular GPU — a fixed count that's a few ms
49
+ * on a desktop is a display-freezing second on a phone.
50
+ */
51
+ export declare function prepareFlopsBenchmark(ctx: GpuContext, spec: FlopsKernelSpec, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
52
+ //# sourceMappingURL=flopsCommon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flopsCommon.d.ts","sourceRoot":"","sources":["../../src/benchmarks/flopsCommon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACvB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,qHAAqH;IACrH,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8GAA8G;IAC9G,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iIAAiI;IACjI,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,eAAe,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAmE5B"}
@@ -0,0 +1,78 @@
1
+ import { createUniformBuffer, createEmptyStorageBuffer } from "../gpu/buffers.js";
2
+ import { createPipeline, prepareKernelBenchmark, skippedResult, FLOPS_METRIC, } from "./common.js";
3
+ const DEFAULT_THREADS = 1024 * 1024;
4
+ const DEFAULT_ITERATIONS = 1024;
5
+ const DEFAULT_MIN_ITERATIONS = 16;
6
+ /**
7
+ * Shared runner for the raw-FLOPS "little read/write, lots of ALU" compute
8
+ * benchmarks: every kernel here shares the same `Params{threads,iterations}`
9
+ * uniform + single scalar-per-thread output buffer layout, so only the WGSL
10
+ * and the FLOPs-per-iteration accounting differ between them.
11
+ *
12
+ * The loop trip count is exposed to the sampler as a work knob: unless
13
+ * `harness.iterations` pins it, the sampler calibrates it (between
14
+ * `minIterations` and the kernel's default) so a single dispatch takes about
15
+ * `targetDispatchMs` on this particular GPU — a fixed count that's a few ms
16
+ * on a desktop is a display-freezing second on a phone.
17
+ */
18
+ export async function prepareFlopsBenchmark(ctx, spec, harness = {}) {
19
+ const threads = harness.threads ?? DEFAULT_THREADS;
20
+ const maxIterations = harness.iterations ?? spec.defaultIterations ?? DEFAULT_ITERATIONS;
21
+ const metaAtWork = (iterations) => ({
22
+ id: spec.id,
23
+ label: spec.label,
24
+ description: spec.description,
25
+ source: spec.wgsl,
26
+ category: 'compute',
27
+ rows: threads,
28
+ cols: iterations,
29
+ metric: spec.metric ?? FLOPS_METRIC,
30
+ amountPerOp: threads * iterations * spec.flopsPerIteration,
31
+ });
32
+ const meta = metaAtWork(maxIterations);
33
+ if (spec.requiresF16 && !ctx.info.supportsF16) {
34
+ return {
35
+ kind: 'skipped',
36
+ result: skippedResult(meta, 'Device/browser does not support the "shader-f16" WebGPU feature.'),
37
+ };
38
+ }
39
+ if (spec.requiresI8Dot && !ctx.info.supportsI8Dot) {
40
+ return {
41
+ kind: 'skipped',
42
+ result: skippedResult(meta, 'Device/browser does not support the "packed_4x8_integer_dot_product" WebGPU feature.'),
43
+ };
44
+ }
45
+ const { device } = ctx;
46
+ const pipeline = await createPipeline(device, spec.id, spec.wgsl);
47
+ const paramsBuf = createUniformBuffer(device, new Uint32Array([threads, maxIterations]), 'params');
48
+ const outBuf = createEmptyStorageBuffer(device, threads * 4, 'out');
49
+ const bindGroup = device.createBindGroup({
50
+ layout: pipeline.getBindGroupLayout(0),
51
+ entries: [
52
+ { binding: 0, resource: { buffer: paramsBuf } },
53
+ { binding: 1, resource: { buffer: outBuf } },
54
+ ],
55
+ });
56
+ // An explicit `iterations` pins the trip count; otherwise let the sampler size it.
57
+ const work = harness.iterations === undefined
58
+ ? {
59
+ min: Math.min(maxIterations, spec.minIterations ?? DEFAULT_MIN_ITERATIONS),
60
+ max: maxIterations,
61
+ apply: (iterations) => device.queue.writeBuffer(paramsBuf, 0, new Uint32Array([threads, iterations])),
62
+ }
63
+ : undefined;
64
+ return prepareKernelBenchmark({
65
+ ...meta,
66
+ ctx,
67
+ workgroupsPerIteration: [Math.ceil(threads / 64), 1, 1],
68
+ pipeline,
69
+ bindGroup,
70
+ work,
71
+ metaAtWork,
72
+ targetMs: harness.targetMs,
73
+ targetDispatchMs: harness.targetDispatchMs,
74
+ warmups: harness.warmups,
75
+ maxIterations: harness.maxIterations,
76
+ });
77
+ }
78
+ //# sourceMappingURL=flopsCommon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flopsCommon.js","sourceRoot":"","sources":["../../src/benchmarks/flopsCommon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { GpuContext } from '../gpu/context.ts';
2
+ import { type FlopsHarnessConfig } from './flopsCommon.ts';
3
+ import type { PreparedBenchmark } from './common.ts';
4
+ /**
5
+ * Bit-twiddling and type-conversion ops: what quantized/packed formats
6
+ * actually cost to get in and out of, as opposed to the flops-i8-* kernels
7
+ * (which measure compute once data is already unpacked into i32/u32
8
+ * registers). "Ops/s" here again isn't IEEE FLOPs, same caveat as flopsMath.ts.
9
+ */
10
+ /** Manual (shift+mask) byte pack/unpack: no pack4x8 (or unpack4x8) builtin, just what those compile to. */
11
+ export declare function prepareFlopsU32PackUnpack(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
12
+ /** i32<->f32 round-trip: convert, FMA, convert back — the delta against the fp32 scalar test isolates conversion cost. */
13
+ export declare function prepareFlopsI32F32Convert(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
14
+ /** fp32<->fp16 round-trip via pack2x16float/unpack2x16float — no shader-f16 feature needed. */
15
+ export declare function prepareFlopsF32F16Convert(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
16
+ /** i32<->f16 round-trip, chained through f32 (there's no native int<->f16 builtin). */
17
+ export declare function prepareFlopsI32F16Convert(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
18
+ //# sourceMappingURL=flopsConvert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flopsConvert.d.ts","sourceRoot":"","sources":["../../src/benchmarks/flopsConvert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAyB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAMlF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;GAKG;AAEH,2GAA2G;AAC3G,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAe5B;AAED,0HAA0H;AAC1H,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAe5B;AAED,+FAA+F;AAC/F,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAe5B;AAED,uFAAuF;AACvF,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAe5B"}
@@ -0,0 +1,61 @@
1
+ import { prepareFlopsBenchmark } from "./flopsCommon.js";
2
+ import { flopsU32PackUnpackWgsl } from "../shaders/flopsU32PackUnpack.js";
3
+ import { flopsI32F32ConvertWgsl } from "../shaders/flopsI32F32Convert.js";
4
+ import { flopsF32F16ConvertWgsl } from "../shaders/flopsF32F16Convert.js";
5
+ import { flopsI32F16ConvertWgsl } from "../shaders/flopsI32F16Convert.js";
6
+ import { OPS_METRIC } from "./common.js";
7
+ /**
8
+ * Bit-twiddling and type-conversion ops: what quantized/packed formats
9
+ * actually cost to get in and out of, as opposed to the flops-i8-* kernels
10
+ * (which measure compute once data is already unpacked into i32/u32
11
+ * registers). "Ops/s" here again isn't IEEE FLOPs, same caveat as flopsMath.ts.
12
+ */
13
+ /** Manual (shift+mask) byte pack/unpack: no pack4x8 (or unpack4x8) builtin, just what those compile to. */
14
+ export function prepareFlopsU32PackUnpack(ctx, harness = {}) {
15
+ return prepareFlopsBenchmark(ctx, {
16
+ id: 'flops-u32-packunpack',
17
+ label: 'u32 byte pack/unpack ops',
18
+ description: 'Eight independent u32 lanes, each step unpacking 4 bytes via shift+mask, incrementing them, and repacking the same way — no pack4x8 (or unpack4x8) builtin, just the bit-twiddling those compile to. 25 ops/lane/step, no unrolling.',
19
+ wgsl: flopsU32PackUnpackWgsl,
20
+ metric: OPS_METRIC,
21
+ flopsPerIteration: 200,
22
+ defaultIterations: 256,
23
+ }, harness);
24
+ }
25
+ /** i32<->f32 round-trip: convert, FMA, convert back — the delta against the fp32 scalar test isolates conversion cost. */
26
+ export function prepareFlopsI32F32Convert(ctx, harness = {}) {
27
+ return prepareFlopsBenchmark(ctx, {
28
+ id: 'flops-i32-f32-convert',
29
+ label: 'i32<->f32 convert FLOPS',
30
+ description: 'Eight independent chains per thread, unrolled 4x: xi -> f32(xi)*a+b -> back to i32 each step. Same FMA as the fp32 scalar test plus a convert on each side, so the gap against that test isolates int<->float conversion cost.',
31
+ wgsl: flopsI32F32ConvertWgsl,
32
+ metric: OPS_METRIC,
33
+ flopsPerIteration: 128,
34
+ defaultIterations: 256,
35
+ }, harness);
36
+ }
37
+ /** fp32<->fp16 round-trip via pack2x16float/unpack2x16float — no shader-f16 feature needed. */
38
+ export function prepareFlopsF32F16Convert(ctx, harness = {}) {
39
+ return prepareFlopsBenchmark(ctx, {
40
+ id: 'flops-f32-f16-convert',
41
+ label: 'f32<->f16 convert FLOPS',
42
+ description: 'Eight independent vec2<f32> chains per thread, unrolled 4x: pack2x16float then unpack2x16float (round-trips through fp16 bits) plus a vec2 FMA to keep the chain moving. Unlike flops-f16-*, this needs no shader-f16 device feature — it measures the conversion, not f16 compute.',
43
+ wgsl: flopsF32F16ConvertWgsl,
44
+ metric: OPS_METRIC,
45
+ flopsPerIteration: 192,
46
+ defaultIterations: 256,
47
+ }, harness);
48
+ }
49
+ /** i32<->f16 round-trip, chained through f32 (there's no native int<->f16 builtin). */
50
+ export function prepareFlopsI32F16Convert(ctx, harness = {}) {
51
+ return prepareFlopsBenchmark(ctx, {
52
+ id: 'flops-i32-f16-convert',
53
+ label: 'i32<->f16 convert FLOPS',
54
+ description: 'Eight independent i32 chains per thread, unrolled 4x: xi -> f32 -> pack2x16float -> unpack2x16float -> f32*a+b -> i32. There is no native int<->f16 conversion, so this is what the real path (through f32) costs.',
55
+ wgsl: flopsI32F16ConvertWgsl,
56
+ metric: OPS_METRIC,
57
+ flopsPerIteration: 192,
58
+ defaultIterations: 256,
59
+ }, harness);
60
+ }
61
+ //# sourceMappingURL=flopsConvert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flopsConvert.js","sourceRoot":"","sources":["../../src/benchmarks/flopsConvert.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAA2B,MAAM,kBAAkB,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;GAKG;AAEH,2GAA2G;AAC3G,MAAM,UAAU,yBAAyB,CACvC,GAAe,EACf,UAA8B,EAAE;IAEhC,OAAO,qBAAqB,CAC1B,GAAG,EACH;QACE,EAAE,EAAE,sBAAsB;QAC1B,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,sOAAsO;QACxO,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,UAAU;QAClB,iBAAiB,EAAE,GAAG;QACtB,iBAAiB,EAAE,GAAG;KACvB,EACD,OAAO,CACR,CAAC;AACJ,CAAC;AAED,0HAA0H;AAC1H,MAAM,UAAU,yBAAyB,CACvC,GAAe,EACf,UAA8B,EAAE;IAEhC,OAAO,qBAAqB,CAC1B,GAAG,EACH;QACE,EAAE,EAAE,uBAAuB;QAC3B,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,gOAAgO;QAClO,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,UAAU;QAClB,iBAAiB,EAAE,GAAG;QACtB,iBAAiB,EAAE,GAAG;KACvB,EACD,OAAO,CACR,CAAC;AACJ,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,yBAAyB,CACvC,GAAe,EACf,UAA8B,EAAE;IAEhC,OAAO,qBAAqB,CAC1B,GAAG,EACH;QACE,EAAE,EAAE,uBAAuB;QAC3B,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,qRAAqR;QACvR,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,UAAU;QAClB,iBAAiB,EAAE,GAAG;QACtB,iBAAiB,EAAE,GAAG;KACvB,EACD,OAAO,CACR,CAAC;AACJ,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,yBAAyB,CACvC,GAAe,EACf,UAA8B,EAAE;IAEhC,OAAO,qBAAqB,CAC1B,GAAG,EACH;QACE,EAAE,EAAE,uBAAuB;QAC3B,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,oNAAoN;QACtN,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,UAAU;QAClB,iBAAiB,EAAE,GAAG;QACtB,iBAAiB,EAAE,GAAG;KACvB,EACD,OAAO,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { GpuContext } from '../gpu/context.ts';
2
+ import { type FlopsHarnessConfig } from './flopsCommon.ts';
3
+ import type { PreparedBenchmark } from './common.ts';
4
+ /** Raw fp16 FLOPS: eight independent scalar f16 FMA chains per thread, unrolled 4x. */
5
+ export declare function prepareFlopsF16Scalar(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
6
+ /** fp16 vec4 FLOPS: one f16 FMA chain on a vec4<f16> register (4 independent lanes). */
7
+ export declare function prepareFlopsF16Vec4(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
8
+ /** fp16 mat4 FLOPS: x = m*x + c chained with a mat4x4<f16>. */
9
+ export declare function prepareFlopsF16Mat4(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
10
+ /** fp16 register-resident matvec tile: 4x8 f16 weights in registers, two dot() calls per output row. */
11
+ export declare function prepareFlopsF16Matvec(ctx: GpuContext, harness?: FlopsHarnessConfig): Promise<PreparedBenchmark>;
12
+ //# sourceMappingURL=flopsF16.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flopsF16.d.ts","sourceRoot":"","sources":["../../src/benchmarks/flopsF16.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAyB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAKlF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,uFAAuF;AACvF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAenH;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAejH;AAED,+DAA+D;AAC/D,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAejH;AAED,wGAAwG;AACxG,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAenH"}