@graphty/webgpu-graph-algorithms 0.5.1 → 0.6.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.
- package/README.md +98 -52
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-BR7fx3vR.js → context-BXqgCifx.js} +190 -40
- package/dist/chunks/context-BXqgCifx.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/algorithms/components.d.ts.map +1 -1
- package/dist/src/algorithms/components.js +12 -13
- package/dist/src/algorithms/components.js.map +1 -1
- package/dist/src/algorithms/degree.d.ts +6 -8
- package/dist/src/algorithms/degree.d.ts.map +1 -1
- package/dist/src/algorithms/degree.js +58 -35
- package/dist/src/algorithms/degree.js.map +1 -1
- package/dist/src/algorithms/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/pagerank.js +16 -14
- package/dist/src/algorithms/pagerank.js.map +1 -1
- package/dist/src/algorithms/power-iteration.d.ts +2 -2
- package/dist/src/algorithms/power-iteration.d.ts.map +1 -1
- package/dist/src/algorithms/power-iteration.js +17 -14
- package/dist/src/algorithms/power-iteration.js.map +1 -1
- package/dist/src/constants.d.ts +38 -8
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +38 -8
- package/dist/src/constants.js.map +1 -1
- package/dist/src/errors.d.ts +3 -2
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +2 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/dispatch.d.ts +8 -3
- package/dist/src/kernel/dispatch.d.ts.map +1 -1
- package/dist/src/kernel/dispatch.js +18 -7
- package/dist/src/kernel/dispatch.js.map +1 -1
- package/dist/src/kernel/kernel.d.ts +30 -1
- package/dist/src/kernel/kernel.d.ts.map +1 -1
- package/dist/src/kernel/kernel.js +49 -5
- package/dist/src/kernel/kernel.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts.map +1 -1
- package/dist/src/kernel/prelude.js +6 -1
- package/dist/src/kernel/prelude.js.map +1 -1
- package/dist/src/kernel/profiler.d.ts +15 -3
- package/dist/src/kernel/profiler.d.ts.map +1 -1
- package/dist/src/kernel/profiler.js +27 -4
- package/dist/src/kernel/profiler.js.map +1 -1
- package/dist/src/kernels.d.ts +17 -7
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +323 -16
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/layouts/calibrate.d.ts +51 -0
- package/dist/src/layouts/calibrate.d.ts.map +1 -0
- package/dist/src/layouts/calibrate.js +172 -0
- package/dist/src/layouts/calibrate.js.map +1 -0
- package/dist/src/layouts/force-simulation.d.ts +39 -4
- package/dist/src/layouts/force-simulation.d.ts.map +1 -1
- package/dist/src/layouts/force-simulation.js +71 -19
- package/dist/src/layouts/force-simulation.js.map +1 -1
- package/dist/src/layouts/forceatlas2.d.ts +107 -36
- package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
- package/dist/src/layouts/forceatlas2.js +296 -100
- package/dist/src/layouts/forceatlas2.js.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.d.ts +73 -27
- package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -1
- package/dist/src/layouts/fruchterman-reingold.js +230 -70
- package/dist/src/layouts/fruchterman-reingold.js.map +1 -1
- package/dist/src/layouts/model-common.d.ts +41 -3
- package/dist/src/layouts/model-common.d.ts.map +1 -1
- package/dist/src/layouts/model-common.js +74 -3
- package/dist/src/layouts/model-common.js.map +1 -1
- package/dist/src/layouts/repulsion-grid.d.ts +152 -0
- package/dist/src/layouts/repulsion-grid.d.ts.map +1 -0
- package/dist/src/layouts/repulsion-grid.js +318 -0
- package/dist/src/layouts/repulsion-grid.js.map +1 -0
- package/dist/src/layouts/spring-electrical.d.ts +75 -30
- package/dist/src/layouts/spring-electrical.d.ts.map +1 -1
- package/dist/src/layouts/spring-electrical.js +231 -74
- package/dist/src/layouts/spring-electrical.js.map +1 -1
- package/dist/src/memory/residency.d.ts +6 -2
- package/dist/src/memory/residency.d.ts.map +1 -1
- package/dist/src/memory/residency.js +84 -14
- package/dist/src/memory/residency.js.map +1 -1
- package/dist/src/primitives/core-shape.d.ts +38 -2
- package/dist/src/primitives/core-shape.d.ts.map +1 -1
- package/dist/src/primitives/core-shape.js +71 -3
- package/dist/src/primitives/core-shape.js.map +1 -1
- package/dist/src/primitives/grid-pyramid.d.ts +71 -0
- package/dist/src/primitives/grid-pyramid.d.ts.map +1 -0
- package/dist/src/primitives/grid-pyramid.js +143 -0
- package/dist/src/primitives/grid-pyramid.js.map +1 -0
- package/dist/src/primitives/grid.d.ts +118 -0
- package/dist/src/primitives/grid.d.ts.map +1 -0
- package/dist/src/primitives/grid.js +225 -0
- package/dist/src/primitives/grid.js.map +1 -0
- package/dist/src/primitives/histogram.d.ts +67 -0
- package/dist/src/primitives/histogram.d.ts.map +1 -0
- package/dist/src/primitives/histogram.js +190 -0
- package/dist/src/primitives/histogram.js.map +1 -0
- package/dist/src/primitives/radix-sort.d.ts +75 -0
- package/dist/src/primitives/radix-sort.d.ts.map +1 -0
- package/dist/src/primitives/radix-sort.js +168 -0
- package/dist/src/primitives/radix-sort.js.map +1 -0
- package/dist/src/primitives/scan.d.ts +44 -0
- package/dist/src/primitives/scan.d.ts.map +1 -0
- package/dist/src/primitives/scan.js +151 -0
- package/dist/src/primitives/scan.js.map +1 -0
- package/dist/src/primitives/segmented-reduce.d.ts +25 -17
- package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
- package/dist/src/primitives/segmented-reduce.js +166 -47
- package/dist/src/primitives/segmented-reduce.js.map +1 -1
- package/dist/src/primitives/spmv.d.ts +18 -14
- package/dist/src/primitives/spmv.d.ts.map +1 -1
- package/dist/src/primitives/spmv.js +94 -58
- package/dist/src/primitives/spmv.js.map +1 -1
- package/dist/src/primitives/verify.d.ts +49 -0
- package/dist/src/primitives/verify.d.ts.map +1 -0
- package/dist/src/primitives/verify.js +229 -0
- package/dist/src/primitives/verify.js.map +1 -0
- package/dist/src/types/context.d.ts +53 -0
- package/dist/src/types/context.d.ts.map +1 -1
- package/dist/src/types/layout.d.ts +20 -0
- package/dist/src/types/layout.d.ts.map +1 -1
- package/dist/src/wgsl/counting-scatter.wgsl.d.ts +8 -0
- package/dist/src/wgsl/counting-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/counting-scatter.wgsl.js +17 -0
- package/dist/src/wgsl/counting-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +23 -11
- package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-attraction.wgsl.js +98 -20
- package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +6 -2
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +22 -1
- package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
- package/dist/src/wgsl/grid-cell-key.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.js +30 -0
- package/dist/src/wgsl/grid-cell-key.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.js +29 -0
- package/dist/src/wgsl/grid-centroid-hub.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-centroid.wgsl.d.ts +8 -0
- package/dist/src/wgsl/grid-centroid.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-centroid.wgsl.js +29 -0
- package/dist/src/wgsl/grid-centroid.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-downsample.wgsl.d.ts +7 -0
- package/dist/src/wgsl/grid-downsample.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-downsample.wgsl.js +28 -0
- package/dist/src/wgsl/grid-downsample.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-far-field.wgsl.d.ts +13 -0
- package/dist/src/wgsl/grid-far-field.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-far-field.wgsl.js +98 -0
- package/dist/src/wgsl/grid-far-field.wgsl.js.map +1 -0
- package/dist/src/wgsl/grid-near-field.wgsl.d.ts +19 -0
- package/dist/src/wgsl/grid-near-field.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/grid-near-field.wgsl.js +129 -0
- package/dist/src/wgsl/grid-near-field.wgsl.js.map +1 -0
- package/dist/src/wgsl/histogram.wgsl.d.ts +7 -0
- package/dist/src/wgsl/histogram.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/histogram.wgsl.js +15 -0
- package/dist/src/wgsl/histogram.wgsl.js.map +1 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.d.ts +8 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.js +26 -0
- package/dist/src/wgsl/indirect-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/radix-hist.wgsl.d.ts +9 -0
- package/dist/src/wgsl/radix-hist.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/radix-hist.wgsl.js +31 -0
- package/dist/src/wgsl/radix-hist.wgsl.js.map +1 -0
- package/dist/src/wgsl/radix-scatter.wgsl.d.ts +9 -0
- package/dist/src/wgsl/radix-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/radix-scatter.wgsl.js +40 -0
- package/dist/src/wgsl/radix-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/scan-add.wgsl.d.ts +6 -0
- package/dist/src/wgsl/scan-add.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/scan-add.wgsl.js +14 -0
- package/dist/src/wgsl/scan-add.wgsl.js.map +1 -0
- package/dist/src/wgsl/scan-block.wgsl.d.ts +8 -0
- package/dist/src/wgsl/scan-block.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/scan-block.wgsl.js +30 -0
- package/dist/src/wgsl/scan-block.wgsl.js.map +1 -0
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +22 -8
- package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/segmented-reduce.wgsl.js +84 -15
- package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -1
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts +22 -11
- package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/spmv-pull.wgsl.js +110 -36
- package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/webgpu-graph-algorithms.js +3815 -1003
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +5 -4
- package/src/algorithms/components.ts +12 -16
- package/src/algorithms/degree.ts +58 -43
- package/src/algorithms/pagerank.ts +20 -18
- package/src/algorithms/power-iteration.ts +19 -18
- package/src/constants.ts +38 -8
- package/src/errors.ts +3 -1
- package/src/index.ts +14 -4
- package/src/kernel/dispatch.ts +18 -7
- package/src/kernel/kernel.ts +59 -5
- package/src/kernel/prelude.ts +9 -0
- package/src/kernel/profiler.ts +28 -4
- package/src/kernels.ts +356 -18
- package/src/layouts/calibrate.ts +187 -0
- package/src/layouts/force-simulation.ts +91 -23
- package/src/layouts/forceatlas2.ts +331 -106
- package/src/layouts/fruchterman-reingold.ts +255 -74
- package/src/layouts/model-common.ts +98 -3
- package/src/layouts/repulsion-grid.ts +451 -0
- package/src/layouts/spring-electrical.ts +257 -78
- package/src/memory/residency.ts +126 -20
- package/src/primitives/core-shape.ts +91 -4
- package/src/primitives/grid-pyramid.ts +221 -0
- package/src/primitives/grid.ts +349 -0
- package/src/primitives/histogram.ts +273 -0
- package/src/primitives/radix-sort.ts +246 -0
- package/src/primitives/scan.ts +197 -0
- package/src/primitives/segmented-reduce.ts +214 -56
- package/src/primitives/spmv.ts +125 -65
- package/src/primitives/verify.ts +249 -0
- package/src/types/context.ts +56 -0
- package/src/types/layout.ts +22 -0
- package/src/wgsl/counting-scatter.wgsl.ts +16 -0
- package/src/wgsl/fa2-attraction.wgsl.ts +98 -20
- package/src/wgsl/fa2-stats-finalize.wgsl.ts +22 -1
- package/src/wgsl/grid-cell-key.wgsl.ts +29 -0
- package/src/wgsl/grid-centroid-hub.wgsl.ts +28 -0
- package/src/wgsl/grid-centroid.wgsl.ts +28 -0
- package/src/wgsl/grid-downsample.wgsl.ts +27 -0
- package/src/wgsl/grid-far-field.wgsl.ts +97 -0
- package/src/wgsl/grid-near-field.wgsl.ts +128 -0
- package/src/wgsl/histogram.wgsl.ts +14 -0
- package/src/wgsl/indirect-finalize.wgsl.ts +25 -0
- package/src/wgsl/radix-hist.wgsl.ts +30 -0
- package/src/wgsl/radix-scatter.wgsl.ts +39 -0
- package/src/wgsl/scan-add.wgsl.ts +13 -0
- package/src/wgsl/scan-block.wgsl.ts +29 -0
- package/src/wgsl/segmented-reduce.wgsl.ts +84 -15
- package/src/wgsl/spmv-pull.wgsl.ts +110 -36
- package/dist/chunks/context-BR7fx3vR.js.map +0 -1
package/src/memory/residency.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* withColumns() siblings, and tombstoned so a live user learns of the release through `isReleased` (spec 4.5).
|
|
6
6
|
* Uploads follow the pure planner of ./upload-plan.ts: the arena path writes ONE buffer with ONE writeBuffer of the
|
|
7
7
|
* hot prefix (or the full arena when a cold segment is needed and fits) and binds segments at
|
|
8
|
-
* `segment.byteOffset - arena.byteOffset`; the perArray path writes one buffer per array;
|
|
9
|
-
*
|
|
8
|
+
* `segment.byteOffset - arena.byteOffset`; the perArray path writes one buffer per array; the windowed path (P4-T7,
|
|
9
|
+
* PD-8) writes one buffer per PlannedArray range of every arc-indexed array and binds window 0 as the default
|
|
10
|
+
* colIdx / weights, the whole window list and the per-array buffers riding on the CoreBinding for the row-walking
|
|
11
|
+
* primitives to bind per window. Identity permutations are never materialised: presence is
|
|
10
12
|
* decided from counts and flags, and the arcToEdge / edgeToArc getters are read only when a segment exists. Nothing
|
|
11
13
|
* is freed by garbage collection; the once-only warning above warnUnreleasedSnapshots names the missing release.
|
|
12
14
|
*/
|
|
@@ -34,6 +36,9 @@ const DEFAULT_NEED: readonly CoreArrayName[] = Object.freeze(["rowPtr", "colIdx"
|
|
|
34
36
|
/** The five core arrays in arena (hot-to-cold) order. */
|
|
35
37
|
const CORE_ORDER: readonly CoreArrayName[] = Object.freeze(["rowPtr", "colIdx", "weights", "arcToEdge", "edgeToArc"]);
|
|
36
38
|
|
|
39
|
+
/** The arc-indexed core arrays: the ones a windowed plan splits into window buffers (upload-plan.ts ARC_INDEXED). */
|
|
40
|
+
const ARC_INDEXED: readonly ("colIdx" | "weights" | "arcToEdge")[] = Object.freeze(["colIdx", "weights", "arcToEdge"]);
|
|
41
|
+
|
|
37
42
|
/** Every resident buffer is bound by kernels, filled by writeBuffer and readable back (tests, readbacks of state). */
|
|
38
43
|
const RESIDENT_USAGE = BufferUsage.STORAGE | BufferUsage.COPY_DST | BufferUsage.COPY_SRC;
|
|
39
44
|
|
|
@@ -53,6 +58,8 @@ export interface CoreBinding {
|
|
|
53
58
|
readonly arcToEdge: Binding | null;
|
|
54
59
|
readonly edgeToArc: Binding | null;
|
|
55
60
|
readonly windows: readonly ArcWindow[] | null;
|
|
61
|
+
/** The buffers each arc-indexed array was split into by the windowed plan (indexed by ArcWindow.bufferIndex; an absent array has none); non-null iff `plan === "windowed"`. */
|
|
62
|
+
readonly arcBuffers: Readonly<Record<"colIdx" | "weights" | "arcToEdge", readonly GPUBuffer[]>> | null;
|
|
56
63
|
readonly hasWeights: boolean;
|
|
57
64
|
}
|
|
58
65
|
|
|
@@ -63,7 +70,14 @@ export interface CoreBinding {
|
|
|
63
70
|
*/
|
|
64
71
|
export interface ViewBinding {
|
|
65
72
|
readonly view:
|
|
66
|
-
|
|
73
|
+
| "reverse"
|
|
74
|
+
| "coo"
|
|
75
|
+
| "edgeList"
|
|
76
|
+
| "outDegree"
|
|
77
|
+
| "inDegree"
|
|
78
|
+
| "degreeOrder"
|
|
79
|
+
| "reverseDegreeOrder"
|
|
80
|
+
| "mate";
|
|
67
81
|
readonly bindings: Readonly<Record<string, Binding>>;
|
|
68
82
|
readonly scalars: Readonly<Record<string, readonly number[]>>;
|
|
69
83
|
}
|
|
@@ -151,9 +165,24 @@ interface ResidencyRecord {
|
|
|
151
165
|
* `rev.rowPtr` object.
|
|
152
166
|
*/
|
|
153
167
|
readonly packKeys: Map<string, object>;
|
|
168
|
+
/** One marker object per window buffer, keyed `<name>:<bufferIndex>` (the packKey pattern: never one of the snapshot's own arrays). */
|
|
169
|
+
readonly windowKeys: Map<string, object>;
|
|
170
|
+
/** The windowed plan's windows, or null on the arena / perArray plans. */
|
|
171
|
+
windows: readonly ArcWindow[] | null;
|
|
172
|
+
/** The window buffers of every arc-indexed array uploaded so far (windowed plan only). */
|
|
173
|
+
readonly arcBuffers: Map<CoreArrayName, GPUBuffer[]>;
|
|
154
174
|
released: boolean;
|
|
155
175
|
}
|
|
156
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Whether a core array is arc-indexed (split into window buffers by a windowed plan).
|
|
179
|
+
* @param name - the core array
|
|
180
|
+
* @returns true for colIdx, weights and arcToEdge
|
|
181
|
+
*/
|
|
182
|
+
function isArcIndexed(name: CoreArrayName): name is "colIdx" | "weights" | "arcToEdge" {
|
|
183
|
+
return (ARC_INDEXED as readonly CoreArrayName[]).includes(name);
|
|
184
|
+
}
|
|
185
|
+
|
|
157
186
|
/**
|
|
158
187
|
* Whether an ArrayBuffer-like is a SharedArrayBuffer (checked by tag so the global need not exist).
|
|
159
188
|
* @param buffer - the buffer to test
|
|
@@ -294,8 +323,9 @@ export class GraphResidency {
|
|
|
294
323
|
|
|
295
324
|
/**
|
|
296
325
|
* Uploads (or finds) the core; `need` defaults to ["rowPtr", "colIdx", "weights"]; cold segments on demand
|
|
297
|
-
* (spec 4.2). Never materialises an identity permutation. A windowed plan
|
|
298
|
-
*
|
|
326
|
+
* (spec 4.2). Never materialises an identity permutation. A windowed plan (spec 4.2, PD-8) uploads every
|
|
327
|
+
* arc-indexed array as the plan's buffer ranges and binds window 0 as its default binding; rowPtr and edgeToArc
|
|
328
|
+
* stay whole. A tombstoned serial is lifted and re-uploaded (PLAN DECISION 7).
|
|
299
329
|
* @param s - the snapshot
|
|
300
330
|
* @param need - the core arrays to bind (rowPtr is always included; absent arrays are ignored)
|
|
301
331
|
* @returns the core binding (a frozen object; grows as cold segments are added)
|
|
@@ -306,18 +336,6 @@ export class GraphResidency {
|
|
|
306
336
|
const wanted = need ?? DEFAULT_NEED;
|
|
307
337
|
const names = CORE_ORDER.filter((name) => (name === "rowPtr" || wanted.includes(name)) && isPresent(s, name));
|
|
308
338
|
const plan = planUpload(s, this.caps, names);
|
|
309
|
-
if (plan.kind === "windowed") {
|
|
310
|
-
throw new WebGpuGraphError(
|
|
311
|
-
"E_TOO_LARGE",
|
|
312
|
-
`snapshot ${s.serial}: an arc array of ${4 * s.arcCount} bytes needs arc windows (${plan.windows.length}), which P1-P3 plan but do not execute`,
|
|
313
|
-
{
|
|
314
|
-
needed: 4 * s.arcCount,
|
|
315
|
-
limit: this.caps.limits.maxStorageBufferBindingSize,
|
|
316
|
-
path: "windowed",
|
|
317
|
-
algorithm: null,
|
|
318
|
-
},
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
339
|
const record = this.ensureRecord(s);
|
|
322
340
|
if (record.plan === null) {
|
|
323
341
|
record.plan = plan.kind;
|
|
@@ -331,10 +349,19 @@ export class GraphResidency {
|
|
|
331
349
|
);
|
|
332
350
|
record.arena = { buffer: resident.buffer, segments: plan.segments };
|
|
333
351
|
}
|
|
352
|
+
if (plan.kind === "windowed") {
|
|
353
|
+
record.windows = plan.windows;
|
|
354
|
+
}
|
|
334
355
|
}
|
|
335
356
|
for (const name of names) {
|
|
336
357
|
if (!record.bindings.has(name)) {
|
|
337
|
-
|
|
358
|
+
const planned = plan.kind === "windowed" ? plan.arrays.find((a) => a.name === name) : undefined;
|
|
359
|
+
record.bindings.set(
|
|
360
|
+
name,
|
|
361
|
+
planned !== undefined && planned.buffers.length > 0 && record.windows !== null && isArcIndexed(name)
|
|
362
|
+
? this.bindWindowed(record, s, name, planned.buffers, record.windows[0])
|
|
363
|
+
: this.bindCore(record, s, name),
|
|
364
|
+
);
|
|
338
365
|
}
|
|
339
366
|
}
|
|
340
367
|
const rowPtr = record.bindings.get("rowPtr");
|
|
@@ -350,7 +377,15 @@ export class GraphResidency {
|
|
|
350
377
|
weights,
|
|
351
378
|
arcToEdge: record.bindings.get("arcToEdge") ?? null,
|
|
352
379
|
edgeToArc: record.bindings.get("edgeToArc") ?? null,
|
|
353
|
-
windows:
|
|
380
|
+
windows: record.windows,
|
|
381
|
+
arcBuffers:
|
|
382
|
+
record.windows === null
|
|
383
|
+
? null
|
|
384
|
+
: Object.freeze({
|
|
385
|
+
colIdx: Object.freeze([...(record.arcBuffers.get("colIdx") ?? [])]),
|
|
386
|
+
weights: Object.freeze([...(record.arcBuffers.get("weights") ?? [])]),
|
|
387
|
+
arcToEdge: Object.freeze([...(record.arcBuffers.get("arcToEdge") ?? [])]),
|
|
388
|
+
}),
|
|
354
389
|
hasWeights: weights !== null,
|
|
355
390
|
});
|
|
356
391
|
}
|
|
@@ -448,7 +483,6 @@ export class GraphResidency {
|
|
|
448
483
|
});
|
|
449
484
|
}
|
|
450
485
|
|
|
451
|
-
|
|
452
486
|
/**
|
|
453
487
|
* The reverse adjacency's bindings. On an UNDIRECTED snapshot graph-format invariant I7 makes reverse() return
|
|
454
488
|
* the FORWARD arrays, so the core bindings ARE the reverse bindings and nothing is uploaded -- delegating to
|
|
@@ -464,6 +498,19 @@ export class GraphResidency {
|
|
|
464
498
|
const rev = s.reverse();
|
|
465
499
|
if (!s.directed) {
|
|
466
500
|
const core = this.core(s, ["rowPtr", "colIdx", "weights"]);
|
|
501
|
+
if (core.plan === "windowed") {
|
|
502
|
+
// a view is never windowed (spec 4.3): window 0's colIdx must not pose as the whole reverse adjacency
|
|
503
|
+
throw new WebGpuGraphError(
|
|
504
|
+
"E_TOO_LARGE",
|
|
505
|
+
`snapshot ${s.serial}: the undirected reverse view is the core, which needs arc windows (${core.windows?.length ?? 0}) that no view executes`,
|
|
506
|
+
{
|
|
507
|
+
needed: 4 * s.arcCount,
|
|
508
|
+
limit: this.caps.limits.maxStorageBufferBindingSize,
|
|
509
|
+
path: "windowed",
|
|
510
|
+
algorithm: null,
|
|
511
|
+
},
|
|
512
|
+
);
|
|
513
|
+
}
|
|
467
514
|
const bindings: Record<string, Binding> = { rowPtr: core.rowPtr };
|
|
468
515
|
if (core.colIdx !== null) {
|
|
469
516
|
bindings.colIdx = core.colIdx;
|
|
@@ -796,6 +843,59 @@ export class GraphResidency {
|
|
|
796
843
|
return { buffer: resident.buffer, offset: 0, size: resident.byteLength, window: null };
|
|
797
844
|
}
|
|
798
845
|
|
|
846
|
+
/**
|
|
847
|
+
* Uploads one arc-indexed array as the windowed plan's buffer ranges (spec 4.2: an array above maxBufferSize is
|
|
848
|
+
* split across buffers at window boundaries), each keyed on a marker object of the record (windowKey), and
|
|
849
|
+
* returns window 0's binding so a caller that ignores windows still binds something valid (PD-8).
|
|
850
|
+
* @param record - the snapshot's record
|
|
851
|
+
* @param s - the snapshot
|
|
852
|
+
* @param name - a PRESENT arc-indexed core array
|
|
853
|
+
* @param ranges - the plan's buffer ranges of the array
|
|
854
|
+
* @param first - window 0
|
|
855
|
+
* @returns the binding of window 0
|
|
856
|
+
*/
|
|
857
|
+
private bindWindowed(
|
|
858
|
+
record: ResidencyRecord,
|
|
859
|
+
s: GraphSnapshot,
|
|
860
|
+
name: "colIdx" | "weights" | "arcToEdge",
|
|
861
|
+
ranges: readonly { readonly byteOffset: number; readonly byteLength: number }[],
|
|
862
|
+
first: ArcWindow,
|
|
863
|
+
): Binding {
|
|
864
|
+
const array = coreArray(s, name);
|
|
865
|
+
const buffers = ranges.map(
|
|
866
|
+
(range, index) =>
|
|
867
|
+
this.upload(
|
|
868
|
+
record,
|
|
869
|
+
this.windowKey(record, `${name}:${index}`),
|
|
870
|
+
new Uint8Array(array.buffer, array.byteOffset + range.byteOffset, range.byteLength),
|
|
871
|
+
`residency:core:${record.serial}:${name}:w${index}`,
|
|
872
|
+
).buffer,
|
|
873
|
+
);
|
|
874
|
+
record.arcBuffers.set(name, buffers);
|
|
875
|
+
return {
|
|
876
|
+
buffer: buffers[first.bufferIndex],
|
|
877
|
+
offset: first.offset,
|
|
878
|
+
size: 4 * (first.end - first.start),
|
|
879
|
+
window: first,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* The upload key of one window buffer: a marker object allocated once per record and `<name>:<bufferIndex>`.
|
|
885
|
+
* @param record - the owning record
|
|
886
|
+
* @param memoKey - `<name>:<bufferIndex>`
|
|
887
|
+
* @returns the stable marker object
|
|
888
|
+
*/
|
|
889
|
+
private windowKey(record: ResidencyRecord, memoKey: string): object {
|
|
890
|
+
const existing = record.windowKeys.get(memoKey);
|
|
891
|
+
if (existing !== undefined) {
|
|
892
|
+
return existing;
|
|
893
|
+
}
|
|
894
|
+
const created = {};
|
|
895
|
+
record.windowKeys.set(memoKey, created);
|
|
896
|
+
return created;
|
|
897
|
+
}
|
|
898
|
+
|
|
799
899
|
/**
|
|
800
900
|
* Uploads `data` into a new buffer keyed on `key`, or returns the resident already uploaded for that key by
|
|
801
901
|
* the same owner (PLAN DECISION 11: a resident belongs to one record).
|
|
@@ -866,6 +966,9 @@ export class GraphResidency {
|
|
|
866
966
|
record.bindings.clear();
|
|
867
967
|
record.views.clear();
|
|
868
968
|
record.packKeys.clear();
|
|
969
|
+
record.windowKeys.clear();
|
|
970
|
+
record.arcBuffers.clear();
|
|
971
|
+
record.windows = null;
|
|
869
972
|
record.arena = null;
|
|
870
973
|
record.plan = null;
|
|
871
974
|
this.bySerial.delete(record.serial);
|
|
@@ -894,6 +997,9 @@ export class GraphResidency {
|
|
|
894
997
|
bindings: new Map<CoreArrayName, Binding>(),
|
|
895
998
|
views: new Map<string, ViewBinding>(),
|
|
896
999
|
packKeys: new Map<string, object>(),
|
|
1000
|
+
windowKeys: new Map<string, object>(),
|
|
1001
|
+
windows: null,
|
|
1002
|
+
arcBuffers: new Map<CoreArrayName, GPUBuffer[]>(),
|
|
897
1003
|
released: false,
|
|
898
1004
|
};
|
|
899
1005
|
this.bySerial.set(s.serial, record);
|
|
@@ -1,13 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The shape helpers every core-walking primitive shares (spec 4.1): the row count and the arc count a CoreBinding
|
|
3
|
-
* implies, the
|
|
3
|
+
* implies, the per-window binding of a windowed core (P4-T7, PD-8), the windowed rejections the pull and the
|
|
4
|
+
* whole-core drivers keep (DEP-P4-B), and the ViewBinding -> CoreBinding adapter the P7 pull kernels need.
|
|
4
5
|
* Moved out of segmented-reduce.ts by M8b-T4 so spmv.ts can use them without duplicating them; the `primitive`
|
|
5
6
|
* argument keeps each caller's error details byte-identical to what they were when the helpers were private.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { WebGpuGraphError } from "../errors.js";
|
|
9
10
|
import { type CoreBinding, type ViewBinding } from "../memory/residency.js";
|
|
10
|
-
import { type Binding } from "../types/memory.js";
|
|
11
|
+
import { type ArcWindow, type Binding } from "../types/memory.js";
|
|
12
|
+
|
|
13
|
+
/** The lanes one TIER 1 (mid-degree) row is folded by, in segmentedReduce and spmvPull alike (PD-6: bitwise the same on every subgroup size). */
|
|
14
|
+
export const MID_TIER_LANES = 32;
|
|
15
|
+
|
|
16
|
+
/** The degree tiers of degreeOrder(): the permutation binding and the CPU-side segmentOffsets [0, hiEnd, midEnd, lowEnd, n]. */
|
|
17
|
+
export interface DegreeTiers {
|
|
18
|
+
readonly perm: Binding;
|
|
19
|
+
readonly segmentOffsets: readonly [number, number, number, number, number];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The DegreeTiers of a degreeOrder / reverseDegreeOrder view (the perm binding and the five segment offsets
|
|
24
|
+
* [0, hiEnd, midEnd, lowEnd, n] of graph-format's cuGraph thresholds 1024 / 32 / 1).
|
|
25
|
+
* @param view - residency.view(s, "degreeOrder") or view(s, "reverseDegreeOrder")
|
|
26
|
+
* @returns the tiers; E_INVALID_ARGUMENT when the view has no perm binding or its segmentOffsets are not five
|
|
27
|
+
* ascending numbers ending at the row count (4 bytes per row of the perm binding)
|
|
28
|
+
*/
|
|
29
|
+
export function degreeTiersOf(view: ViewBinding): DegreeTiers {
|
|
30
|
+
const { perm }: { readonly perm?: Binding | undefined } = view.bindings;
|
|
31
|
+
if (perm === undefined) {
|
|
32
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `the ${view.view} view has no perm binding`, {
|
|
33
|
+
argument: "view",
|
|
34
|
+
value: view.view,
|
|
35
|
+
expected: "a view with a perm binding (degreeOrder, reverseDegreeOrder)",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const so: readonly number[] | undefined = view.scalars.segmentOffsets;
|
|
39
|
+
const ascending =
|
|
40
|
+
so !== undefined &&
|
|
41
|
+
so.length === 5 &&
|
|
42
|
+
so[0] === 0 &&
|
|
43
|
+
so.every((value, k) => Number.isInteger(value) && (k === 0 || value >= so[k - 1]));
|
|
44
|
+
if (!ascending || so[4] !== perm.size / 4) {
|
|
45
|
+
throw new WebGpuGraphError(
|
|
46
|
+
"E_INVALID_ARGUMENT",
|
|
47
|
+
`the ${view.view} view's segmentOffsets are not five ascending numbers ending at the row count`,
|
|
48
|
+
{
|
|
49
|
+
argument: "view",
|
|
50
|
+
value: so === undefined ? null : Array.from(so),
|
|
51
|
+
expected: `[0, hiEnd, midEnd, lowEnd, ${perm.size / 4}] ascending`,
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const segmentOffsets: readonly [number, number, number, number, number] = [so[0], so[1], so[2], so[3], so[4]];
|
|
56
|
+
return Object.freeze({ perm, segmentOffsets });
|
|
57
|
+
}
|
|
11
58
|
|
|
12
59
|
/**
|
|
13
60
|
* The row count of a core from its rowPtr binding (4(n + 1) bytes).
|
|
@@ -41,18 +88,57 @@ export function arcCountOf(core: CoreBinding): number {
|
|
|
41
88
|
}
|
|
42
89
|
|
|
43
90
|
/**
|
|
44
|
-
*
|
|
91
|
+
* The binding of one arc window of a windowed core's arc-indexed array (spec 4.2): the buffer the window was placed
|
|
92
|
+
* in, at the window's offset, over its arcs; the kernel reads `array[arc - P.arcBase]` with `arcBase = w.start`.
|
|
93
|
+
* @param core - a windowed core (arcBuffers non-null)
|
|
94
|
+
* @param name - the arc-indexed array
|
|
95
|
+
* @param w - one of core.windows
|
|
96
|
+
* @returns the binding; E_INVALID_ARGUMENT when the core is not windowed or the array is absent
|
|
97
|
+
*/
|
|
98
|
+
export function windowBinding(core: CoreBinding, name: "colIdx" | "weights" | "arcToEdge", w: ArcWindow): Binding {
|
|
99
|
+
const buffer = core.arcBuffers?.[name][w.bufferIndex];
|
|
100
|
+
if (buffer === undefined) {
|
|
101
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `windowBinding: the core has no window buffers of ${name}`, {
|
|
102
|
+
argument: "core",
|
|
103
|
+
value: name,
|
|
104
|
+
expected: "a windowed core whose plan uploaded the array",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return { buffer, offset: w.offset, size: 4 * (w.end - w.start), window: w };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Rejects a windowed core: the pull cannot accumulate its affine epilogue across windows (DEP-P4-B).
|
|
45
112
|
* @param core - the core
|
|
46
113
|
* @param primitive - the caller's name, used in the message and the feature detail
|
|
47
114
|
*/
|
|
48
115
|
export function assertNotWindowed(core: CoreBinding, primitive: string): void {
|
|
49
116
|
if (core.plan === "windowed" || core.windows !== null) {
|
|
50
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${primitive}: windowed cores are executed
|
|
117
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${primitive}: windowed cores are not executed by the pull`, {
|
|
51
118
|
feature: `${primitive}.windowed`,
|
|
52
119
|
});
|
|
53
120
|
}
|
|
54
121
|
}
|
|
55
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Rejects a windowed core for a driver that walks the whole core (DEP-P4-B: pageRank, the power iterations and
|
|
125
|
+
* connectedComponents): `E_TOO_LARGE { needed, limit, path: "windowed", algorithm }` (spec 3.8 / 3.12), the refusal
|
|
126
|
+
* GraphResidency.core() itself issued before P4-T7 executed windows for degree and segmentedReduce.
|
|
127
|
+
* @param core - the core
|
|
128
|
+
* @param arcCount - the snapshot's arc count (4 bytes per arc of each arc-indexed array)
|
|
129
|
+
* @param limit - the device's maxStorageBufferBindingSize
|
|
130
|
+
* @param algorithm - the driver's name
|
|
131
|
+
*/
|
|
132
|
+
export function assertWholeCore(core: CoreBinding, arcCount: number, limit: number, algorithm: string): void {
|
|
133
|
+
if (core.plan === "windowed") {
|
|
134
|
+
throw new WebGpuGraphError(
|
|
135
|
+
"E_TOO_LARGE",
|
|
136
|
+
`${algorithm}: the arc arrays need a windowed upload (${core.windows?.length ?? 0} windows), which ${algorithm} does not execute (DEP-P4-B)`,
|
|
137
|
+
{ needed: 4 * arcCount, limit, path: "windowed", algorithm },
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
56
142
|
/**
|
|
57
143
|
* The CoreBinding shape of a residency VIEW (spec 4.3). residency.view() returns a ViewBinding -- a name -> Binding
|
|
58
144
|
* record -- while graphBindings / graphOverrides and every core-walking primitive read rowPtr / colIdx / weights as
|
|
@@ -98,6 +184,7 @@ export function coreOfView(v: ViewBinding, arcCount: number): CoreBinding {
|
|
|
98
184
|
arcToEdge: null,
|
|
99
185
|
edgeToArc: null,
|
|
100
186
|
windows: null,
|
|
187
|
+
arcBuffers: null,
|
|
101
188
|
hasWeights: weights !== null,
|
|
102
189
|
});
|
|
103
190
|
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grid pyramid (spec 6 row 12, 7.7 G4-G5; P4-T9): the planner that records, into the caller's pass, the finest
|
|
3
|
+
* centroids (G4, `grid-centroid`: thread per cell over `cells + 1`, the pseudo-cell included), the hub-cell
|
|
4
|
+
* completion (G4a: the T1 `indirect-finalize` over `hubCounters[0]` into `hubArgs` with `wg = 1`, so the finalize's
|
|
5
|
+
* `ceil(count / wg)` is ONE workgroup per hub cell; G4b: `grid-centroid-hub`, one workgroup per hub cell, dispatched
|
|
6
|
+
* indirectly; PD-13, DEP-P4-I) and one `grid-downsample` dispatch per coarser
|
|
7
|
+
* level (G5). Level 0 holds `[sum m x, sum m y, sum m z, sum m]` per cell; every parent is the sum of its 2^dim
|
|
8
|
+
* children; the pseudo-cell (index `cells` of level 0) is never a child. No atomics touch the sums (design 6 row 12:
|
|
9
|
+
* bitwise reproducible); the only atomics are the hub append and the occupancy max.
|
|
10
|
+
*
|
|
11
|
+
* The named grid buffers (`pyramid`, `hubList`, `hubCounters`, `hubArgs`) are the caller's (the model's
|
|
12
|
+
* `BufferSpec`s, so `inspect(name)` reaches them); the static params of the finalize and of every level are written
|
|
13
|
+
* ONCE at bind() through the scope's params writer (PD-11), so record() writes no uniform. `src/primitives/**` never
|
|
14
|
+
* imports `src/context.ts`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { WebGpuGraphError } from "../errors.js";
|
|
18
|
+
import { type DispatchPlan, plan1d } from "../kernel/dispatch.js";
|
|
19
|
+
import { type BoundKernel, type Kernel } from "../kernel/kernel.js";
|
|
20
|
+
import { GRID_LEVEL_PARAMS, INDIRECT_PARAMS, kernelSpec } from "../kernels.js";
|
|
21
|
+
import { type Binding } from "../types/memory.js";
|
|
22
|
+
import { type GridSpec } from "./grid.js";
|
|
23
|
+
import { type ReduceScope } from "./reduce.js";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The buffers the pyramid build reads and writes (the model's named buffers). The parameter type of
|
|
27
|
+
* GridPyramidPlanner.bind (knip: exported for the signature, not imported by name).
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export interface GridPyramidBindings {
|
|
31
|
+
/** `array<vec4f>` positions (xyz, mass). */
|
|
32
|
+
readonly pos: Binding;
|
|
33
|
+
/** The `Fa2Params` uniform ring (`dim`, `gridMax`); `record()` takes the iteration's dynamic offset. */
|
|
34
|
+
readonly params: Binding;
|
|
35
|
+
/** `n` words: the sorted node indices (the T8 build). */
|
|
36
|
+
readonly sortedIdx: Binding;
|
|
37
|
+
/** `cells + 2` words: the exclusive scan of the cell histogram (the T8 build). */
|
|
38
|
+
readonly cellStart: Binding;
|
|
39
|
+
/** `pyramidCells` vec4f: every level, level 0 first with the pseudo-cell at index `cells`. */
|
|
40
|
+
readonly pyramid: Binding;
|
|
41
|
+
/** The hub cells' indices, appended by G4 (at least one word; at most `floor(n / (GRID_HUB_CELL + 1))` are ever written, so `ceil(n / GRID_HUB_CELL)` words always suffice). */
|
|
42
|
+
readonly hubList: Binding;
|
|
43
|
+
/** Two u32 (bound whole, at least 8 bytes): `[0]` the hub count, `[1]` the largest cell occupancy; the caller zeroes both before every build (K1, T10). */
|
|
44
|
+
readonly hubCounters: Binding;
|
|
45
|
+
/** One 16-byte indirect args slot with the INDIRECT usage: G4a writes it, G4b dispatches from it. */
|
|
46
|
+
readonly hubArgs: Binding;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Where `record()` stops: after level 0 is whole (G4, G4a and G4b) or after every coarser level (G5, the default). */
|
|
50
|
+
export type GridPyramidStage = "G4" | "G5";
|
|
51
|
+
|
|
52
|
+
/** A prepared pyramid build (spec 7.7 G4-G5): binds once per load, records the stages of one iteration into a pass. */
|
|
53
|
+
export interface GridPyramidPlanner {
|
|
54
|
+
/**
|
|
55
|
+
* Binds the named buffers and writes the static params of the finalize and of every level (PD-11); called once
|
|
56
|
+
* per load (a second call rebinds and writes fresh params, so it belongs to a reload, never to an iteration).
|
|
57
|
+
* @param bindings - the buffers
|
|
58
|
+
*/
|
|
59
|
+
bind(bindings: GridPyramidBindings): void;
|
|
60
|
+
/**
|
|
61
|
+
* Records G4, G4a, G4b and then G5 for every coarser level at the `Fa2Params` slot `paramsOffset`; `upTo: "G4"`
|
|
62
|
+
* stops after G4b (level 0 is whole: G4a and G4b are G4's completion).
|
|
63
|
+
* @param pass - the compute pass
|
|
64
|
+
* @param paramsOffset - the dynamic offset of this iteration's `Fa2Params`
|
|
65
|
+
* @param upTo - the last stage to record (default "G5")
|
|
66
|
+
*/
|
|
67
|
+
record(pass: GPUComputePassEncoder, paramsOffset: number, upTo?: GridPyramidStage): void;
|
|
68
|
+
/** Dispatches the last record() issued: 3 after `upTo: "G4"`, `3 + (levels - 1)` for a full record. */
|
|
69
|
+
readonly lastDispatches: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Prepares the pyramid's pipelines over a scope (G4, the finalize, G4b and G5; compiles once) so bind() and record()
|
|
74
|
+
* are synchronous. The planner lives exactly as long as the scope.
|
|
75
|
+
* @param scope - the caller's scope (device, caps, cache, scratch, params)
|
|
76
|
+
* @param spec - the grid
|
|
77
|
+
* @returns the planner
|
|
78
|
+
*/
|
|
79
|
+
export async function preparePyramid(scope: ReduceScope, spec: GridSpec): Promise<GridPyramidPlanner> {
|
|
80
|
+
const centroid = await scope.pipelines.kernel(kernelSpec("grid-centroid"));
|
|
81
|
+
const finalize = await scope.pipelines.kernel(kernelSpec("indirect-finalize"));
|
|
82
|
+
const hub = await scope.pipelines.kernel(kernelSpec("grid-centroid-hub"));
|
|
83
|
+
const downsample = await scope.pipelines.kernel(kernelSpec("grid-downsample"));
|
|
84
|
+
return new GridPyramidPlannerImpl(scope, spec, { centroid, finalize, hub, downsample });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The four kernels of the build. */
|
|
88
|
+
interface Kernels {
|
|
89
|
+
readonly centroid: Kernel;
|
|
90
|
+
readonly finalize: Kernel;
|
|
91
|
+
readonly hub: Kernel;
|
|
92
|
+
readonly downsample: Kernel;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** What bind() prepared: the bound groups and, per coarser level, its bound group with its params offset. */
|
|
96
|
+
interface Bound {
|
|
97
|
+
readonly hubArgs: Binding;
|
|
98
|
+
readonly centroid: BoundKernel;
|
|
99
|
+
readonly finalize: BoundKernel;
|
|
100
|
+
readonly finalizeOffset: number;
|
|
101
|
+
readonly hub: BoundKernel;
|
|
102
|
+
readonly levels: readonly { readonly bound: BoundKernel; readonly offset: number; readonly parentCells: number }[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The planner: G4, G4a, G4b and G5 over one scope. */
|
|
106
|
+
class GridPyramidPlannerImpl implements GridPyramidPlanner {
|
|
107
|
+
private readonly scope: ReduceScope;
|
|
108
|
+
private readonly spec: GridSpec;
|
|
109
|
+
private readonly kernels: Kernels;
|
|
110
|
+
private bound: Bound | null = null;
|
|
111
|
+
private dispatches = 0;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Wraps the resolved kernels; use preparePyramid().
|
|
115
|
+
* @param scope - the caller's scope
|
|
116
|
+
* @param spec - the grid
|
|
117
|
+
* @param kernels - the four kernels
|
|
118
|
+
*/
|
|
119
|
+
constructor(scope: ReduceScope, spec: GridSpec, kernels: Kernels) {
|
|
120
|
+
this.scope = scope;
|
|
121
|
+
this.spec = spec;
|
|
122
|
+
this.kernels = kernels;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Dispatches the last record() issued.
|
|
127
|
+
* @returns the count
|
|
128
|
+
*/
|
|
129
|
+
get lastDispatches(): number {
|
|
130
|
+
return this.dispatches;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Binds the buffers and writes the static params (see the interface).
|
|
135
|
+
* @param bindings - the buffers
|
|
136
|
+
*/
|
|
137
|
+
bind(bindings: GridPyramidBindings): void {
|
|
138
|
+
const { centroid, finalize, hub, downsample } = this.kernels;
|
|
139
|
+
const { spec, scope } = this;
|
|
140
|
+
const b = bindings;
|
|
141
|
+
const finalizeParams = scope.params(INDIRECT_PARAMS, {
|
|
142
|
+
countIndex: 0,
|
|
143
|
+
wg: 1, // the finalize plans ceil(count / wg) workgroups over ITEMS; G4b's item is a hub cell, one workgroup each
|
|
144
|
+
slot: 0,
|
|
145
|
+
pad0: 0,
|
|
146
|
+
});
|
|
147
|
+
const levels: { readonly bound: BoundKernel; readonly offset: number; readonly parentCells: number }[] = [];
|
|
148
|
+
let parentSide = spec.g;
|
|
149
|
+
for (let level = 0; level + 1 < spec.levels; level++) {
|
|
150
|
+
parentSide /= 2;
|
|
151
|
+
const parentCells = parentSide ** spec.dim;
|
|
152
|
+
const params = scope.params(GRID_LEVEL_PARAMS, {
|
|
153
|
+
childBase: spec.levelOffsets[level],
|
|
154
|
+
parentBase: spec.levelOffsets[level + 1],
|
|
155
|
+
parentSide,
|
|
156
|
+
parentCells,
|
|
157
|
+
depth: spec.dim === 3 ? 2 : 1,
|
|
158
|
+
pad0: 0,
|
|
159
|
+
pad1: 0,
|
|
160
|
+
pad2: 0,
|
|
161
|
+
});
|
|
162
|
+
levels.push({
|
|
163
|
+
bound: downsample.bind({ pyramid: b.pyramid, P: params.binding }),
|
|
164
|
+
offset: params.offset,
|
|
165
|
+
parentCells,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
this.bound = {
|
|
169
|
+
hubArgs: b.hubArgs,
|
|
170
|
+
centroid: centroid.bind({
|
|
171
|
+
sortedIdx: b.sortedIdx,
|
|
172
|
+
cellStart: b.cellStart,
|
|
173
|
+
pos: b.pos,
|
|
174
|
+
pyramid: b.pyramid,
|
|
175
|
+
hubList: b.hubList,
|
|
176
|
+
hubCounters: b.hubCounters,
|
|
177
|
+
P: b.params,
|
|
178
|
+
}),
|
|
179
|
+
finalize: finalize.bind({ counters: b.hubCounters, args: b.hubArgs, P: finalizeParams.binding }),
|
|
180
|
+
finalizeOffset: finalizeParams.offset,
|
|
181
|
+
hub: hub.bind({
|
|
182
|
+
sortedIdx: b.sortedIdx,
|
|
183
|
+
cellStart: b.cellStart,
|
|
184
|
+
pos: b.pos,
|
|
185
|
+
pyramid: b.pyramid,
|
|
186
|
+
hubList: b.hubList,
|
|
187
|
+
hubCount: b.hubCounters,
|
|
188
|
+
P: b.params,
|
|
189
|
+
}),
|
|
190
|
+
levels,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Records the stages (see the interface).
|
|
196
|
+
* @param pass - the compute pass
|
|
197
|
+
* @param paramsOffset - the `Fa2Params` dynamic offset
|
|
198
|
+
* @param upTo - the last stage
|
|
199
|
+
*/
|
|
200
|
+
record(pass: GPUComputePassEncoder, paramsOffset: number, upTo?: GridPyramidStage): void {
|
|
201
|
+
const { bound, scope, spec } = this;
|
|
202
|
+
if (bound === null) {
|
|
203
|
+
throw new WebGpuGraphError("E_NOT_LOADED", "gridPyramid: record() before bind()", { argument: "bind" });
|
|
204
|
+
}
|
|
205
|
+
const { centroid, finalize, hub, downsample } = this.kernels;
|
|
206
|
+
const one: DispatchPlan = { x: 1, y: 1, z: 1, items: 1, stride: null };
|
|
207
|
+
centroid.dispatch(pass, bound.centroid, plan1d(spec.cells + 1, scope.workgroupSize, scope.caps), [paramsOffset]);
|
|
208
|
+
finalize.dispatch(pass, bound.finalize, one, [bound.finalizeOffset]);
|
|
209
|
+
hub.dispatchIndirect(pass, bound.hub, bound.hubArgs, 0, [paramsOffset]);
|
|
210
|
+
this.dispatches = 3;
|
|
211
|
+
if ((upTo ?? "G5") === "G4") {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const level of bound.levels) {
|
|
215
|
+
downsample.dispatch(pass, level.bound, plan1d(level.parentCells, scope.workgroupSize, scope.caps), [
|
|
216
|
+
level.offset,
|
|
217
|
+
]);
|
|
218
|
+
this.dispatches += 1;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|