@graphty/webgpu-graph-algorithms 0.6.3 → 0.6.5
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 +62 -32
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-BXqgCifx.js → context-hzGggHeM.js} +68 -24
- package/dist/chunks/context-hzGggHeM.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/accelerator.d.ts +8 -6
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +57 -6
- package/dist/src/accelerator.js.map +1 -1
- package/dist/src/algorithms/bellman-ford.d.ts +60 -0
- package/dist/src/algorithms/bellman-ford.d.ts.map +1 -0
- package/dist/src/algorithms/bellman-ford.js +301 -0
- package/dist/src/algorithms/bellman-ford.js.map +1 -0
- package/dist/src/algorithms/bfs.d.ts +67 -0
- package/dist/src/algorithms/bfs.d.ts.map +1 -0
- package/dist/src/algorithms/bfs.js +534 -0
- package/dist/src/algorithms/bfs.js.map +1 -0
- package/dist/src/algorithms/closeness.d.ts +53 -0
- package/dist/src/algorithms/closeness.d.ts.map +1 -0
- package/dist/src/algorithms/closeness.js +323 -0
- package/dist/src/algorithms/closeness.js.map +1 -0
- package/dist/src/algorithms/scope.d.ts +3 -1
- package/dist/src/algorithms/scope.d.ts.map +1 -1
- package/dist/src/algorithms/scope.js +1 -0
- package/dist/src/algorithms/scope.js.map +1 -1
- package/dist/src/algorithms/sssp.d.ts +72 -0
- package/dist/src/algorithms/sssp.d.ts.map +1 -0
- package/dist/src/algorithms/sssp.js +586 -0
- package/dist/src/algorithms/sssp.js.map +1 -0
- package/dist/src/constants.d.ts +10 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +10 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +8 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts +4 -4
- package/dist/src/kernel/prelude.d.ts.map +1 -1
- package/dist/src/kernel/prelude.js +39 -5
- package/dist/src/kernel/prelude.js.map +1 -1
- package/dist/src/kernel/uniform-ring.d.ts +8 -0
- package/dist/src/kernel/uniform-ring.d.ts.map +1 -1
- package/dist/src/kernel/uniform-ring.js +13 -0
- package/dist/src/kernel/uniform-ring.js.map +1 -1
- package/dist/src/kernels.d.ts +45 -4
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +368 -3
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/primitives/advance.d.ts +63 -0
- package/dist/src/primitives/advance.d.ts.map +1 -0
- package/dist/src/primitives/advance.js +95 -0
- package/dist/src/primitives/advance.js.map +1 -0
- package/dist/src/primitives/compact.d.ts +89 -0
- package/dist/src/primitives/compact.d.ts.map +1 -0
- package/dist/src/primitives/compact.js +233 -0
- package/dist/src/primitives/compact.js.map +1 -0
- package/dist/src/primitives/core-shape.d.ts +22 -1
- package/dist/src/primitives/core-shape.d.ts.map +1 -1
- package/dist/src/primitives/core-shape.js +33 -3
- package/dist/src/primitives/core-shape.js.map +1 -1
- package/dist/src/primitives/frontier.d.ts +151 -0
- package/dist/src/primitives/frontier.d.ts.map +1 -0
- package/dist/src/primitives/frontier.js +250 -0
- package/dist/src/primitives/frontier.js.map +1 -0
- package/dist/src/types/accelerator.d.ts +16 -7
- package/dist/src/types/accelerator.d.ts.map +1 -1
- package/dist/src/types/traversal.d.ts +53 -0
- package/dist/src/types/traversal.d.ts.map +1 -0
- package/dist/src/types/traversal.js +10 -0
- package/dist/src/types/traversal.js.map +1 -0
- package/dist/src/wgsl/advance-expand.wgsl.d.ts +19 -0
- package/dist/src/wgsl/advance-expand.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/advance-expand.wgsl.js +69 -0
- package/dist/src/wgsl/advance-expand.wgsl.js.map +1 -0
- package/dist/src/wgsl/bf-relax.wgsl.d.ts +22 -0
- package/dist/src/wgsl/bf-relax.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bf-relax.wgsl.js +58 -0
- package/dist/src/wgsl/bf-relax.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts +15 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.js +24 -0
- package/dist/src/wgsl/bfs-bitset-build.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts +20 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.js +67 -0
- package/dist/src/wgsl/bfs-bottom-up.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-contract.wgsl.d.ts +20 -0
- package/dist/src/wgsl/bfs-contract.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-contract.wgsl.js +55 -0
- package/dist/src/wgsl/bfs-contract.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-fused.wgsl.d.ts +25 -0
- package/dist/src/wgsl/bfs-fused.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-fused.wgsl.js +78 -0
- package/dist/src/wgsl/bfs-fused.wgsl.js.map +1 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts +18 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js +42 -0
- package/dist/src/wgsl/bfs-unvisited-flags.wgsl.js.map +1 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.d.ts +17 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.js +65 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.js.map +1 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.d.ts +20 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.js +96 -0
- package/dist/src/wgsl/closeness-sweep.wgsl.js.map +1 -0
- package/dist/src/wgsl/compact-scatter.wgsl.d.ts +9 -0
- package/dist/src/wgsl/compact-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/compact-scatter.wgsl.js +17 -0
- package/dist/src/wgsl/compact-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.d.ts +10 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.js +19 -0
- package/dist/src/wgsl/dedupe-claim.wgsl.js.map +1 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.d.ts +12 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.js +46 -0
- package/dist/src/wgsl/dedupe-filter.wgsl.js.map +1 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.d.ts +53 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.js +164 -0
- package/dist/src/wgsl/frontier-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/sssp-pred.wgsl.d.ts +28 -0
- package/dist/src/wgsl/sssp-pred.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/sssp-pred.wgsl.js +80 -0
- package/dist/src/wgsl/sssp-pred.wgsl.js.map +1 -0
- package/dist/src/wgsl/sssp-relax.wgsl.d.ts +30 -0
- package/dist/src/wgsl/sssp-relax.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/sssp-relax.wgsl.js +72 -0
- package/dist/src/wgsl/sssp-relax.wgsl.js.map +1 -0
- package/dist/webgpu-graph-algorithms.js +3155 -384
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +5 -4
- package/src/accelerator.ts +65 -7
- package/src/algorithms/bellman-ford.ts +387 -0
- package/src/algorithms/bfs.ts +626 -0
- package/src/algorithms/closeness.ts +395 -0
- package/src/algorithms/scope.ts +4 -1
- package/src/algorithms/sssp.ts +768 -0
- package/src/constants.ts +10 -0
- package/src/index.ts +14 -1
- package/src/kernel/prelude.ts +39 -4
- package/src/kernel/uniform-ring.ts +14 -0
- package/src/kernels.ts +447 -6
- package/src/primitives/advance.ts +131 -0
- package/src/primitives/compact.ts +323 -0
- package/src/primitives/core-shape.ts +41 -3
- package/src/primitives/frontier.ts +372 -0
- package/src/types/accelerator.ts +18 -5
- package/src/types/traversal.ts +56 -0
- package/src/wgsl/advance-expand.wgsl.ts +68 -0
- package/src/wgsl/bf-relax.wgsl.ts +57 -0
- package/src/wgsl/bfs-bitset-build.wgsl.ts +23 -0
- package/src/wgsl/bfs-bottom-up.wgsl.ts +66 -0
- package/src/wgsl/bfs-contract.wgsl.ts +54 -0
- package/src/wgsl/bfs-fused.wgsl.ts +77 -0
- package/src/wgsl/bfs-unvisited-flags.wgsl.ts +41 -0
- package/src/wgsl/closeness-reduce.wgsl.ts +64 -0
- package/src/wgsl/closeness-sweep.wgsl.ts +95 -0
- package/src/wgsl/compact-scatter.wgsl.ts +16 -0
- package/src/wgsl/dedupe-claim.wgsl.ts +18 -0
- package/src/wgsl/dedupe-filter.wgsl.ts +45 -0
- package/src/wgsl/frontier-finalize.wgsl.ts +163 -0
- package/src/wgsl/sssp-pred.wgsl.ts +79 -0
- package/src/wgsl/sssp-relax.wgsl.ts +71 -0
- package/dist/chunks/context-BXqgCifx.js.map +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `advance` primitive of design 6 row 8 (P8-T5 / P8-T12; the P8 plan's PD-1, PD-23, DEP-P8-A, DEP-P8-E): the
|
|
3
|
+
* block-mapped expansion of a `Frontier`'s input queue into its edge queue by the `advance-expand` kernel, a DIRECT
|
|
4
|
+
* grid-stride dispatch over a grid planned for n entries that loops to `frontierCount` and runs only when the `path`
|
|
5
|
+
* word `frontier-finalize` role 0 wrote earlier in the same pass says the level is two-phase; the host never knows
|
|
6
|
+
* the frontier's size. The kernel leaves `edgeCount` (clamped later by role 1),
|
|
7
|
+
* `edgeCountUnclamped` (the overflow detector) and `frontierDegreeSum` in the counters block.
|
|
8
|
+
*
|
|
9
|
+
* Design 6 row 8 names three structures -- block-mapped, a WORKGROUP_PER_ROW tier for rows above 1,024 arcs, and a
|
|
10
|
+
* subgroup tier. Here they are: the block-mapped body is the large-frontier kernel and already balances a hub row
|
|
11
|
+
* over all `WG` lanes of its block (a 10,000-degree entry costs its block 40 strips, never one lane 10,000 reads);
|
|
12
|
+
* the workgroup-per-row structure IS `bfs-fused` (P8-T7), one workgroup per frontier entry, which the selector
|
|
13
|
+
* chooses for a small frontier such as a hub-only level, so `advance-expand` declares no `TIER` override and the tier
|
|
14
|
+
* choice is the fused / two-phase choice; and the subgroup tier is not a second body but the same body compiled
|
|
15
|
+
* against the prelude's subgroup helper block (`wg_scan_u32` becomes `subgroupExclusiveAdd` / `subgroupAdd` plus the
|
|
16
|
+
* elected-lane slot carry), which `needs: ["subgroups"]` in the registry entry switches on when the device has the
|
|
17
|
+
* feature. Both forms return each lane's prefix in LANE order, so the arcs land at the same queue positions; only
|
|
18
|
+
* the order ACROSS workgroups (the `atomicAdd` reservation order) is schedule-dependent, which is why a test compares
|
|
19
|
+
* sorted queues.
|
|
20
|
+
*
|
|
21
|
+
* A windowed core (spec 4.2: `colIdx` above `maxStorageBufferBindingSize`, bound as arc windows) is executed, not
|
|
22
|
+
* refused (P8-T12 lifts DEP-P8-E for the frontier family): one dispatch per window, the frontier and its count the
|
|
23
|
+
* same in every window, each dispatch binding that window's `colIdx` and
|
|
24
|
+
* carrying its owned arc range in `arcBase` / `arcEnd` (`coreWindows`: the ranges partition the arcs, which P4's
|
|
25
|
+
* overlapping window bounds do not). The body clips every row to the range, so a row straddling two windows emits
|
|
26
|
+
* each arc exactly once, and the per-window dispatches accumulate into the same edge queue through the same
|
|
27
|
+
* `edgeCount` reservation while `edgeCountUnclamped` and `frontierDegreeSum` add up across windows to the level's
|
|
28
|
+
* totals. `src/primitives/**` never imports `src/context.ts`.
|
|
29
|
+
*/
|
|
30
|
+
import { type Kernel } from "../kernel/kernel.js";
|
|
31
|
+
import { type CoreBinding } from "../memory/residency.js";
|
|
32
|
+
import { type CoreWindow } from "./core-shape.js";
|
|
33
|
+
import { type Frontier } from "./frontier.js";
|
|
34
|
+
import { type ReduceScope } from "./reduce.js";
|
|
35
|
+
/** A prepared advance (design 6 row 8): records one expansion of a frontier per level. */
|
|
36
|
+
export interface AdvancePlanner {
|
|
37
|
+
/** The compiled `advance-expand` kernel (so `bfs-fused` can share its bind groups' shape). */
|
|
38
|
+
readonly kernel: Kernel;
|
|
39
|
+
/** The arc windows every level dispatches over: one over the whole core, or the windowed core's (P8-T12). */
|
|
40
|
+
readonly windows: readonly CoreWindow[];
|
|
41
|
+
/**
|
|
42
|
+
* Records the expansion of `frontier.input` into `frontier.edgeQueue` as a DIRECT grid-stride dispatch per window
|
|
43
|
+
* (`planGridStride(n)`: the frontier holds at most n entries; the kernel loops to the count word and runs only
|
|
44
|
+
* when the selector's path word says the level is two-phase): one `FrontierParams` record per window
|
|
45
|
+
* (`edgeCapacity`, `n`, `wg`, `stride` and the window's arc range) and the kernel bound to the window's graph
|
|
46
|
+
* arrays, the input queue, the counters block and the edge queue (`Kernel.bind` reuses the bind groups of the
|
|
47
|
+
* same buffers and ranges, so the two sides of a frontier cost two bind-group sets per window). A frontier
|
|
48
|
+
* whose `n` is not the core's row count is E_INVALID_ARGUMENT before anything is recorded.
|
|
49
|
+
* @param pass - the compute pass
|
|
50
|
+
* @param frontier - the queue to expand (its `input` side)
|
|
51
|
+
*/
|
|
52
|
+
record(pass: GPUComputePassEncoder, frontier: Frontier): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Compiles `advance-expand` for the core's weights pattern (no permutation: the frontier names rows directly) so
|
|
56
|
+
* `record` is synchronous, and resolves the core's windows once. The planner lives exactly as long as the scope:
|
|
57
|
+
* never use it after the scope's dispose().
|
|
58
|
+
* @param scope - the caller's scope (pipelines, params)
|
|
59
|
+
* @param core - the resident core arrays of the snapshot the frontier walks (windowed or not)
|
|
60
|
+
* @returns the planner
|
|
61
|
+
*/
|
|
62
|
+
export declare function prepareAdvance(scope: ReduceScope, core: CoreBinding): Promise<AdvancePlanner>;
|
|
63
|
+
//# sourceMappingURL=advance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advance.d.ts","sourceRoot":"","sources":["../../../src/primitives/advance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,KAAK,UAAU,EAA2B,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,0FAA0F;AAC1F,MAAM,WAAW,cAAc;IAC3B,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6GAA6G;IAC7G,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,qBAAqB,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;CACjE;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAGnG"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `advance` primitive of design 6 row 8 (P8-T5 / P8-T12; the P8 plan's PD-1, PD-23, DEP-P8-A, DEP-P8-E): the
|
|
3
|
+
* block-mapped expansion of a `Frontier`'s input queue into its edge queue by the `advance-expand` kernel, a DIRECT
|
|
4
|
+
* grid-stride dispatch over a grid planned for n entries that loops to `frontierCount` and runs only when the `path`
|
|
5
|
+
* word `frontier-finalize` role 0 wrote earlier in the same pass says the level is two-phase; the host never knows
|
|
6
|
+
* the frontier's size. The kernel leaves `edgeCount` (clamped later by role 1),
|
|
7
|
+
* `edgeCountUnclamped` (the overflow detector) and `frontierDegreeSum` in the counters block.
|
|
8
|
+
*
|
|
9
|
+
* Design 6 row 8 names three structures -- block-mapped, a WORKGROUP_PER_ROW tier for rows above 1,024 arcs, and a
|
|
10
|
+
* subgroup tier. Here they are: the block-mapped body is the large-frontier kernel and already balances a hub row
|
|
11
|
+
* over all `WG` lanes of its block (a 10,000-degree entry costs its block 40 strips, never one lane 10,000 reads);
|
|
12
|
+
* the workgroup-per-row structure IS `bfs-fused` (P8-T7), one workgroup per frontier entry, which the selector
|
|
13
|
+
* chooses for a small frontier such as a hub-only level, so `advance-expand` declares no `TIER` override and the tier
|
|
14
|
+
* choice is the fused / two-phase choice; and the subgroup tier is not a second body but the same body compiled
|
|
15
|
+
* against the prelude's subgroup helper block (`wg_scan_u32` becomes `subgroupExclusiveAdd` / `subgroupAdd` plus the
|
|
16
|
+
* elected-lane slot carry), which `needs: ["subgroups"]` in the registry entry switches on when the device has the
|
|
17
|
+
* feature. Both forms return each lane's prefix in LANE order, so the arcs land at the same queue positions; only
|
|
18
|
+
* the order ACROSS workgroups (the `atomicAdd` reservation order) is schedule-dependent, which is why a test compares
|
|
19
|
+
* sorted queues.
|
|
20
|
+
*
|
|
21
|
+
* A windowed core (spec 4.2: `colIdx` above `maxStorageBufferBindingSize`, bound as arc windows) is executed, not
|
|
22
|
+
* refused (P8-T12 lifts DEP-P8-E for the frontier family): one dispatch per window, the frontier and its count the
|
|
23
|
+
* same in every window, each dispatch binding that window's `colIdx` and
|
|
24
|
+
* carrying its owned arc range in `arcBase` / `arcEnd` (`coreWindows`: the ranges partition the arcs, which P4's
|
|
25
|
+
* overlapping window bounds do not). The body clips every row to the range, so a row straddling two windows emits
|
|
26
|
+
* each arc exactly once, and the per-window dispatches accumulate into the same edge queue through the same
|
|
27
|
+
* `edgeCount` reservation while `edgeCountUnclamped` and `frontierDegreeSum` add up across windows to the level's
|
|
28
|
+
* totals. `src/primitives/**` never imports `src/context.ts`.
|
|
29
|
+
*/
|
|
30
|
+
import { WebGpuGraphError } from "../errors.js";
|
|
31
|
+
import { planGridStride } from "../kernel/dispatch.js";
|
|
32
|
+
import { FRONTIER_PARAMS, graphBindings, graphOverrides, kernelSpec } from "../kernels.js";
|
|
33
|
+
import { coreWindows, rowCountOf } from "./core-shape.js";
|
|
34
|
+
/**
|
|
35
|
+
* Compiles `advance-expand` for the core's weights pattern (no permutation: the frontier names rows directly) so
|
|
36
|
+
* `record` is synchronous, and resolves the core's windows once. The planner lives exactly as long as the scope:
|
|
37
|
+
* never use it after the scope's dispose().
|
|
38
|
+
* @param scope - the caller's scope (pipelines, params)
|
|
39
|
+
* @param core - the resident core arrays of the snapshot the frontier walks (windowed or not)
|
|
40
|
+
* @returns the planner
|
|
41
|
+
*/
|
|
42
|
+
export async function prepareAdvance(scope, core) {
|
|
43
|
+
const kernel = await scope.pipelines.kernel(kernelSpec("advance-expand", graphOverrides(core, null)));
|
|
44
|
+
return new AdvancePlannerImpl(scope, core, kernel);
|
|
45
|
+
}
|
|
46
|
+
/** The planner: the compiled kernel over one core's windows. */
|
|
47
|
+
class AdvancePlannerImpl {
|
|
48
|
+
/**
|
|
49
|
+
* Wraps the resolved kernel; use prepareAdvance().
|
|
50
|
+
* @param scope - the caller's scope
|
|
51
|
+
* @param core - the core the kernel was compiled for
|
|
52
|
+
* @param kernel - the `advance-expand` kernel
|
|
53
|
+
*/
|
|
54
|
+
constructor(scope, core, kernel) {
|
|
55
|
+
this.scope = scope;
|
|
56
|
+
this.kernel = kernel;
|
|
57
|
+
this.windows = coreWindows(core);
|
|
58
|
+
this.n = rowCountOf(core, "advance");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Records one direct expansion per window (see the interface).
|
|
62
|
+
* @param pass - the compute pass
|
|
63
|
+
* @param frontier - the queue to expand
|
|
64
|
+
*/
|
|
65
|
+
record(pass, frontier) {
|
|
66
|
+
if (frontier.n !== this.n) {
|
|
67
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `advance: the frontier holds ${frontier.n} vertices, the core ${this.n} rows`, {
|
|
68
|
+
argument: "frontier",
|
|
69
|
+
value: frontier.n,
|
|
70
|
+
expected: this.n,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const { scope } = this;
|
|
74
|
+
const plan = planGridStride(frontier.n, scope.workgroupSize, scope.caps); // the frontier holds at most n entries
|
|
75
|
+
for (const w of this.windows) {
|
|
76
|
+
const params = scope.params(FRONTIER_PARAMS, {
|
|
77
|
+
wg: scope.workgroupSize,
|
|
78
|
+
edgeCapacity: frontier.edgeCapacity,
|
|
79
|
+
n: frontier.n,
|
|
80
|
+
arcBase: w.arcBase,
|
|
81
|
+
arcEnd: w.arcEnd,
|
|
82
|
+
stride: plan.stride ?? scope.workgroupSize,
|
|
83
|
+
});
|
|
84
|
+
const bound = this.kernel.bind({
|
|
85
|
+
...graphBindings(w.core, null),
|
|
86
|
+
frontierIn: frontier.input,
|
|
87
|
+
counters: frontier.counters,
|
|
88
|
+
edgeQueue: frontier.edgeQueue,
|
|
89
|
+
P: params.binding,
|
|
90
|
+
});
|
|
91
|
+
this.kernel.dispatch(pass, bound, plan, [params.offset]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=advance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advance.js","sourceRoot":"","sources":["../../../src/primitives/advance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3F,OAAO,EAAmB,WAAW,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwB3E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAkB,EAAE,IAAiB;IACtE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,gBAAgB,EAAE,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACtG,OAAO,IAAI,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AACvD,CAAC;AAED,gEAAgE;AAChE,MAAM,kBAAkB;IAMpB;;;;;OAKG;IACH,YAAY,KAAkB,EAAE,IAAiB,EAAE,MAAc;QAC7D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,IAA2B,EAAE,QAAkB;QAClD,IAAI,QAAQ,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,gBAAgB,CACtB,oBAAoB,EACpB,+BAA+B,QAAQ,CAAC,CAAC,uBAAuB,IAAI,CAAC,CAAC,OAAO,EAC7E;gBACI,QAAQ,EAAE,UAAU;gBACpB,KAAK,EAAE,QAAQ,CAAC,CAAC;gBACjB,QAAQ,EAAE,IAAI,CAAC,CAAC;aACnB,CACJ,CAAC;QACN,CAAC;QACD,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;QACvB,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,uCAAuC;QACjH,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,eAAe,EAAE;gBACzC,EAAE,EAAE,KAAK,CAAC,aAAa;gBACvB,YAAY,EAAE,QAAQ,CAAC,YAAY;gBACnC,CAAC,EAAE,QAAQ,CAAC,CAAC;gBACb,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,aAAa;aAC7C,CAAC,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC3B,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC;gBAC9B,UAAU,EAAE,QAAQ,CAAC,KAAK;gBAC1B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,SAAS,EAAE,QAAQ,CAAC,SAAS;gBAC7B,CAAC,EAAE,MAAM,CAAC,OAAO;aACpB,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,CAAC;IACL,CAAC;CACJ"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `compact` and `dedupe` primitive driver (spec 6 row 4; P8-T3, the P8 plan's PD-1 / DEP-P8-A). `compact` is
|
|
3
|
+
* flag, scan, scatter: the flags are the caller's (one u32 per entry, written by the kernel that produced the queue),
|
|
4
|
+
* `exclusiveScan` turns them into offsets, and `compact-scatter` lands every flagged entry at its offset in queue
|
|
5
|
+
* order -- order-preserving, so bitwise reproducible -- and writes the total into one word of the caller's block.
|
|
6
|
+
* `dedupe` is Davidson's ownership trick in two dispatches: `dedupe-claim` stores every entry's index into
|
|
7
|
+
* `owner[vertex]`, `dedupe-filter` keeps the entries that still own their vertex. Two dispatches because a plain
|
|
8
|
+
* store read back in the same dispatch is a data race (WGSL 6.5.7); between dispatches the relaxed atomics make
|
|
9
|
+
* last-writer-wins well defined, so exactly one index per distinct vertex survives (set-deterministic: the SET is
|
|
10
|
+
* fixed, the order inside `out` follows the schedule).
|
|
11
|
+
*
|
|
12
|
+
* `owner` needs NO reset between calls, whatever it holds: a word is read only by an entry `i` whose vertex `v` a
|
|
13
|
+
* claim of the SAME call has just written, so a stale index or garbage at `owner[v]` is overwritten before any lane
|
|
14
|
+
* compares against it, and a word no current entry names is never read. The design's row implies a reset; the `fill`
|
|
15
|
+
* over `n` per round is the dispatch this saves.
|
|
16
|
+
*
|
|
17
|
+
* The count of `compact` is host-known. `dedupe`'s callers (the SSSP piles of P8-T9) only know their entry count on
|
|
18
|
+
* the device, so `countIndex` names a word of the counters block and the kernels read `min(counters[countIndex],
|
|
19
|
+
* count)` -- the device word clamped to the capacity `count` -- or `count` alone when `countIndex` is `U32_MAX`. The
|
|
20
|
+
* counters block is bound WHOLE and indexed, for the count word and for the output word alike, because
|
|
21
|
+
* `Kernel.bind` rejects a binding whose offset is not a multiple of 256 and a four-byte word never is.
|
|
22
|
+
*
|
|
23
|
+
* The driver owns no device objects: the caller supplies a ReduceScope (the same record `reduce` takes) and the
|
|
24
|
+
* compute pass to record into. `src/primitives/**` never imports `src/context.ts`.
|
|
25
|
+
*/
|
|
26
|
+
import { type Binding } from "../types/memory.js";
|
|
27
|
+
import { type ReduceScope } from "./reduce.js";
|
|
28
|
+
/** One compaction: `count` entries of `queue` whose `flags` word is non-zero go to `out` in order; the total goes to `outCount[outIndex]`. */
|
|
29
|
+
export interface CompactRecord {
|
|
30
|
+
/** The entries (at least 4 x count bytes). */
|
|
31
|
+
readonly queue: Binding;
|
|
32
|
+
/** One u32 per entry, 0 or 1 (at least 4 x count bytes). */
|
|
33
|
+
readonly flags: Binding;
|
|
34
|
+
/** The entry count (a non-negative integer below 2^32; host-known). */
|
|
35
|
+
readonly count: number;
|
|
36
|
+
/** The compacted entries (at least 4 x count bytes; the words past the total are not written). */
|
|
37
|
+
readonly out: Binding;
|
|
38
|
+
/** The block whose word `outIndex` receives the total (bound whole). */
|
|
39
|
+
readonly outCount: Binding;
|
|
40
|
+
/** The word of `outCount` to write. */
|
|
41
|
+
readonly outIndex: number;
|
|
42
|
+
}
|
|
43
|
+
/** One dedupe: the entries of `queue` are filtered to one per distinct vertex into `out`; the surviving count is ADDED to `outCount[outIndex]`, which the caller zeroes before the pass. */
|
|
44
|
+
export interface DedupeRecord {
|
|
45
|
+
/** The entries, vertex indices below the owner's length (at least 4 x count bytes). */
|
|
46
|
+
readonly queue: Binding;
|
|
47
|
+
/** The entry count when `countIndex` is `U32_MAX`, else the capacity the device count is clamped to (a non-negative integer below 2^32). */
|
|
48
|
+
readonly count: number;
|
|
49
|
+
/** `U32_MAX` for a host-known count; else the word of `counters` holding the entry count on the device. */
|
|
50
|
+
readonly countIndex: number;
|
|
51
|
+
/** The block the count word is read from; with a device count it must be the SAME range as `outCount` (both kernels read the word through their own binding), and it is bound but never read when `countIndex` is `U32_MAX`. */
|
|
52
|
+
readonly counters: Binding;
|
|
53
|
+
/** The ownership words, one per vertex (the caller's promise: at least 4 x n bytes; never reset). */
|
|
54
|
+
readonly owner: Binding;
|
|
55
|
+
/** The surviving entries (at least 4 x count bytes; the words past the count are not written). */
|
|
56
|
+
readonly out: Binding;
|
|
57
|
+
/** The block whose word `outIndex` accumulates the surviving count (bound whole; zeroed by the caller before the pass). */
|
|
58
|
+
readonly outCount: Binding;
|
|
59
|
+
/** The word of `outCount` to add to; never equal to `countIndex`. */
|
|
60
|
+
readonly outIndex: number;
|
|
61
|
+
}
|
|
62
|
+
/** A prepared compact / dedupe (spec 6 row 4): records the dispatches of one call into a pass. */
|
|
63
|
+
export interface CompactPlanner {
|
|
64
|
+
/**
|
|
65
|
+
* Records the compaction (the flags' exclusive scan, then the scatter). For count 0 nothing is recorded and
|
|
66
|
+
* `outCount[outIndex]` keeps its prior value: a four-byte write at an unaligned offset is impossible, so every
|
|
67
|
+
* caller zeroes its block before the pass.
|
|
68
|
+
* @param pass - the compute pass
|
|
69
|
+
* @param record - the buffers and the count
|
|
70
|
+
*/
|
|
71
|
+
record(pass: GPUComputePassEncoder, record: CompactRecord): void;
|
|
72
|
+
/**
|
|
73
|
+
* Records the claim and the filter over `plan1d(count)`; nothing for count 0 (`outCount[outIndex]` then keeps
|
|
74
|
+
* its prior value).
|
|
75
|
+
* @param pass - the compute pass
|
|
76
|
+
* @param record - the buffers, the count and the count word
|
|
77
|
+
*/
|
|
78
|
+
recordDedupe(pass: GPUComputePassEncoder, record: DedupeRecord): void;
|
|
79
|
+
/** Dispatches the last record*() issued: compact 0 for count 0, else the scan's `2 x levels - 1` plus one (`2 x levels`); dedupe 0 for a direct count 0, else 2. */
|
|
80
|
+
readonly lastDispatches: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Prepares the three pipelines and the scan of a scope (compiles once) so record() is synchronous. The planner
|
|
84
|
+
* lives exactly as long as the scope (the scan keeps a scratch word of it): never use it after the scope's dispose().
|
|
85
|
+
* @param scope - the caller's scope (device, caps, cache, scratch, params)
|
|
86
|
+
* @returns the planner
|
|
87
|
+
*/
|
|
88
|
+
export declare function prepareCompact(scope: ReduceScope): Promise<CompactPlanner>;
|
|
89
|
+
//# sourceMappingURL=compact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compact.d.ts","sourceRoot":"","sources":["../../../src/primitives/compact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,8IAA8I;AAC9I,MAAM,WAAW,aAAa;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,kGAAkG;IAClG,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAED,4LAA4L;AAC5L,MAAM,WAAW,YAAY;IACzB,uFAAuF;IACvF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,4IAA4I;IAC5I,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2GAA2G;IAC3G,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gOAAgO;IAChO,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,kGAAkG;IAClG,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,2HAA2H;IAC3H,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAED,kGAAkG;AAClG,MAAM,WAAW,cAAc;IAC3B;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACjE;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACtE,oKAAoK;IACpK,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAMhF"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `compact` and `dedupe` primitive driver (spec 6 row 4; P8-T3, the P8 plan's PD-1 / DEP-P8-A). `compact` is
|
|
3
|
+
* flag, scan, scatter: the flags are the caller's (one u32 per entry, written by the kernel that produced the queue),
|
|
4
|
+
* `exclusiveScan` turns them into offsets, and `compact-scatter` lands every flagged entry at its offset in queue
|
|
5
|
+
* order -- order-preserving, so bitwise reproducible -- and writes the total into one word of the caller's block.
|
|
6
|
+
* `dedupe` is Davidson's ownership trick in two dispatches: `dedupe-claim` stores every entry's index into
|
|
7
|
+
* `owner[vertex]`, `dedupe-filter` keeps the entries that still own their vertex. Two dispatches because a plain
|
|
8
|
+
* store read back in the same dispatch is a data race (WGSL 6.5.7); between dispatches the relaxed atomics make
|
|
9
|
+
* last-writer-wins well defined, so exactly one index per distinct vertex survives (set-deterministic: the SET is
|
|
10
|
+
* fixed, the order inside `out` follows the schedule).
|
|
11
|
+
*
|
|
12
|
+
* `owner` needs NO reset between calls, whatever it holds: a word is read only by an entry `i` whose vertex `v` a
|
|
13
|
+
* claim of the SAME call has just written, so a stale index or garbage at `owner[v]` is overwritten before any lane
|
|
14
|
+
* compares against it, and a word no current entry names is never read. The design's row implies a reset; the `fill`
|
|
15
|
+
* over `n` per round is the dispatch this saves.
|
|
16
|
+
*
|
|
17
|
+
* The count of `compact` is host-known. `dedupe`'s callers (the SSSP piles of P8-T9) only know their entry count on
|
|
18
|
+
* the device, so `countIndex` names a word of the counters block and the kernels read `min(counters[countIndex],
|
|
19
|
+
* count)` -- the device word clamped to the capacity `count` -- or `count` alone when `countIndex` is `U32_MAX`. The
|
|
20
|
+
* counters block is bound WHOLE and indexed, for the count word and for the output word alike, because
|
|
21
|
+
* `Kernel.bind` rejects a binding whose offset is not a multiple of 256 and a four-byte word never is.
|
|
22
|
+
*
|
|
23
|
+
* The driver owns no device objects: the caller supplies a ReduceScope (the same record `reduce` takes) and the
|
|
24
|
+
* compute pass to record into. `src/primitives/**` never imports `src/context.ts`.
|
|
25
|
+
*/
|
|
26
|
+
import { U32_MAX } from "../constants.js";
|
|
27
|
+
import { WebGpuGraphError } from "../errors.js";
|
|
28
|
+
import { plan1d, planGridStride } from "../kernel/dispatch.js";
|
|
29
|
+
import { COMPACT_PARAMS, kernelSpec } from "../kernels.js";
|
|
30
|
+
import { prepareScan } from "./scan.js";
|
|
31
|
+
/**
|
|
32
|
+
* Prepares the three pipelines and the scan of a scope (compiles once) so record() is synchronous. The planner
|
|
33
|
+
* lives exactly as long as the scope (the scan keeps a scratch word of it): never use it after the scope's dispose().
|
|
34
|
+
* @param scope - the caller's scope (device, caps, cache, scratch, params)
|
|
35
|
+
* @returns the planner
|
|
36
|
+
*/
|
|
37
|
+
export async function prepareCompact(scope) {
|
|
38
|
+
const scatter = await scope.pipelines.kernel(kernelSpec("compact-scatter"));
|
|
39
|
+
const claim = await scope.pipelines.kernel(kernelSpec("dedupe-claim"));
|
|
40
|
+
const filter = await scope.pipelines.kernel(kernelSpec("dedupe-filter"));
|
|
41
|
+
const scan = await prepareScan(scope);
|
|
42
|
+
return new CompactPlannerImpl(scope, scatter, claim, filter, scan);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The E_INVALID_ARGUMENT of a bad count.
|
|
46
|
+
* @param what - the primitive's name for the message
|
|
47
|
+
* @param count - the count
|
|
48
|
+
*/
|
|
49
|
+
function checkCount(what, count) {
|
|
50
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX) {
|
|
51
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: count must be a non-negative integer below 2^32`, {
|
|
52
|
+
argument: "count",
|
|
53
|
+
value: count,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The E_INVALID_ARGUMENT of a binding shorter than `words` u32.
|
|
59
|
+
* @param what - the primitive's name for the message
|
|
60
|
+
* @param name - the argument name
|
|
61
|
+
* @param binding - the binding
|
|
62
|
+
* @param words - the words it must hold
|
|
63
|
+
*/
|
|
64
|
+
function checkWords(what, name, binding, words) {
|
|
65
|
+
if (binding.size < 4 * words) {
|
|
66
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} is smaller than 4 x ${words} bytes`, {
|
|
67
|
+
argument: name,
|
|
68
|
+
value: binding.size,
|
|
69
|
+
expected: 4 * words,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The E_INVALID_ARGUMENT of a word index outside a block.
|
|
75
|
+
* @param what - the primitive's name for the message
|
|
76
|
+
* @param name - the argument name
|
|
77
|
+
* @param index - the word index
|
|
78
|
+
* @param block - the block it indexes
|
|
79
|
+
*/
|
|
80
|
+
function checkWordIndex(what, name, index, block) {
|
|
81
|
+
if (!Number.isSafeInteger(index) || index < 0 || 4 * (index + 1) > block.size) {
|
|
82
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} ${index} is outside the block`, {
|
|
83
|
+
argument: name,
|
|
84
|
+
value: index,
|
|
85
|
+
expected: `0 <= ${name} < ${Math.floor(block.size / 4)}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The argument checks of record() (E_INVALID_ARGUMENT before anything is recorded).
|
|
91
|
+
* @param r - the record
|
|
92
|
+
*/
|
|
93
|
+
function checkCompactArguments(r) {
|
|
94
|
+
checkCount("compact", r.count);
|
|
95
|
+
checkWords("compact", "queue", r.queue, r.count);
|
|
96
|
+
checkWords("compact", "flags", r.flags, r.count);
|
|
97
|
+
checkWords("compact", "out", r.out, r.count);
|
|
98
|
+
checkWordIndex("compact", "outIndex", r.outIndex, r.outCount);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The argument checks of recordDedupe(): the count, the two entry buffers (`out` holds at
|
|
102
|
+
* most one entry per owner word, so it is checked against `min(count, owner words)`: the SSSP piles of P8-T9 dedupe
|
|
103
|
+
* a raw half of `arcCount` capacity into an `n`-word pile), the output word, and -- with a device count -- a count
|
|
104
|
+
* word that is inside `counters`, is not the output word (the filter's atomicAdd would corrupt the count other
|
|
105
|
+
* workgroups of the same dispatch still read) and lives in the block `outCount` names (the two kernels read it
|
|
106
|
+
* through their own binding).
|
|
107
|
+
* @param r - the record
|
|
108
|
+
*/
|
|
109
|
+
function checkDedupeArguments(r) {
|
|
110
|
+
checkCount("dedupe", r.count);
|
|
111
|
+
checkWords("dedupe", "queue", r.queue, r.count);
|
|
112
|
+
checkWords("dedupe", "out", r.out, Math.min(r.count, Math.floor(r.owner.size / 4)));
|
|
113
|
+
checkWordIndex("dedupe", "outIndex", r.outIndex, r.outCount);
|
|
114
|
+
if (r.countIndex === U32_MAX) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
checkWordIndex("dedupe", "countIndex", r.countIndex, r.counters);
|
|
118
|
+
if (r.countIndex === r.outIndex) {
|
|
119
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "dedupe: countIndex must not be the output word outIndex", {
|
|
120
|
+
argument: "countIndex",
|
|
121
|
+
value: r.countIndex,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (r.counters.buffer !== r.outCount.buffer ||
|
|
125
|
+
r.counters.offset !== r.outCount.offset ||
|
|
126
|
+
r.counters.size !== r.outCount.size) {
|
|
127
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "dedupe: counters must be the same range as outCount when countIndex names a device word", {
|
|
128
|
+
argument: "counters",
|
|
129
|
+
value: r.counters.size,
|
|
130
|
+
expected: r.outCount.size,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** The planner: the three resolved kernels and the scan over one scope. */
|
|
135
|
+
class CompactPlannerImpl {
|
|
136
|
+
/**
|
|
137
|
+
* Wraps the resolved kernels; use prepareCompact().
|
|
138
|
+
* @param scope - the caller's scope
|
|
139
|
+
* @param scatter - the `compact-scatter` kernel
|
|
140
|
+
* @param claim - the `dedupe-claim` kernel
|
|
141
|
+
* @param filter - the `dedupe-filter` kernel
|
|
142
|
+
* @param scan - the scan planner of the same scope
|
|
143
|
+
*/
|
|
144
|
+
constructor(scope, scatter, claim, filter, scan) {
|
|
145
|
+
this.dispatches = 0;
|
|
146
|
+
this.scope = scope;
|
|
147
|
+
this.scatter = scatter;
|
|
148
|
+
this.claim = claim;
|
|
149
|
+
this.filter = filter;
|
|
150
|
+
this.scan = scan;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Dispatches the last record*() issued.
|
|
154
|
+
* @returns the count
|
|
155
|
+
*/
|
|
156
|
+
get lastDispatches() {
|
|
157
|
+
return this.dispatches;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Records the scan and the scatter (see the interface).
|
|
161
|
+
* @param pass - the compute pass
|
|
162
|
+
* @param r - the record
|
|
163
|
+
*/
|
|
164
|
+
record(pass, r) {
|
|
165
|
+
checkCompactArguments(r);
|
|
166
|
+
if (r.count === 0) {
|
|
167
|
+
this.dispatches = 0;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const size = 4 * r.count;
|
|
171
|
+
const offsets = { buffer: this.scope.scratch(size, "compact/offsets"), offset: 0, size, window: null };
|
|
172
|
+
this.scan.record(pass, r.flags, r.count, offsets);
|
|
173
|
+
const params = this.scope.params(COMPACT_PARAMS, {
|
|
174
|
+
count: r.count,
|
|
175
|
+
outIndex: r.outIndex,
|
|
176
|
+
countIndex: U32_MAX,
|
|
177
|
+
stride: 0,
|
|
178
|
+
});
|
|
179
|
+
const bound = this.scatter.bind({
|
|
180
|
+
queue: r.queue,
|
|
181
|
+
flags: r.flags,
|
|
182
|
+
offsets,
|
|
183
|
+
out: r.out,
|
|
184
|
+
outCount: r.outCount,
|
|
185
|
+
P: params.binding,
|
|
186
|
+
});
|
|
187
|
+
this.scatter.dispatch(pass, bound, plan1d(r.count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
|
|
188
|
+
this.dispatches = this.scan.lastDispatches + 1;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Records the claim and the filter over plan1d(count) (see the interface).
|
|
192
|
+
* @param pass - the compute pass
|
|
193
|
+
* @param r - the record
|
|
194
|
+
*/
|
|
195
|
+
recordDedupe(pass, r) {
|
|
196
|
+
checkDedupeArguments(r);
|
|
197
|
+
if (r.count === 0) {
|
|
198
|
+
this.dispatches = 0;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const plan = planGridStride(r.count, this.scope.workgroupSize, this.scope.caps);
|
|
202
|
+
const bound = this.bindDedupe(r, plan.stride ?? this.scope.workgroupSize);
|
|
203
|
+
this.claim.dispatch(pass, bound.claim, plan, [bound.offset]);
|
|
204
|
+
this.filter.dispatch(pass, bound.filter, plan, [bound.offset]);
|
|
205
|
+
this.dispatches = 2;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* One params record (both kernels read the same values) and the two bind groups.
|
|
209
|
+
* @param r - the record
|
|
210
|
+
* @param stride - the grid-stride plan's stride (entries per pass over the grid)
|
|
211
|
+
* @returns the bound kernels and the record's dynamic offset
|
|
212
|
+
*/
|
|
213
|
+
bindDedupe(r, stride) {
|
|
214
|
+
const params = this.scope.params(COMPACT_PARAMS, {
|
|
215
|
+
count: r.count,
|
|
216
|
+
outIndex: r.outIndex,
|
|
217
|
+
countIndex: r.countIndex,
|
|
218
|
+
stride,
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
claim: this.claim.bind({ queue: r.queue, owner: r.owner, counters: r.counters, P: params.binding }),
|
|
222
|
+
filter: this.filter.bind({
|
|
223
|
+
queue: r.queue,
|
|
224
|
+
owner: r.owner,
|
|
225
|
+
out: r.out,
|
|
226
|
+
outCount: r.outCount,
|
|
227
|
+
P: params.binding,
|
|
228
|
+
}),
|
|
229
|
+
offset: params.offset,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=compact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compact.js","sourceRoot":"","sources":["../../../src/primitives/compact.ts"],"names":[],"mappings":"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"}
|
|
@@ -44,6 +44,25 @@ export declare function arcCountOf(core: CoreBinding): number;
|
|
|
44
44
|
* @returns the binding; E_INVALID_ARGUMENT when the core is not windowed or the array is absent
|
|
45
45
|
*/
|
|
46
46
|
export declare function windowBinding(core: CoreBinding, name: "colIdx" | "weights" | "arcToEdge", w: ArcWindow): Binding;
|
|
47
|
+
/** One dispatch of a frontier-walking kernel over a core (P8-T12): the arc range it owns and the core with its arc-indexed arrays bound to that window (the core itself, over every arc, when it is not windowed). */
|
|
48
|
+
export interface CoreWindow {
|
|
49
|
+
readonly arcBase: number;
|
|
50
|
+
readonly arcEnd: number;
|
|
51
|
+
readonly core: CoreBinding;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The per-window dispatches of a kernel that walks rows named by a FRONTIER rather than by a row range (P8-T12:
|
|
55
|
+
* `advance-expand`, `bfs-fused`, `bfs-bottom-up`, `sssp-pred`): every window sees every frontier entry and clips
|
|
56
|
+
* each row to `[arcBase, arcEnd)`, so the ranges must PARTITION `[0, arcCount)` or a row is expanded twice. P4's
|
|
57
|
+
* windows do not: `planArcWindows` opens a window at the previous window's end aligned DOWN to 64 arcs, so two
|
|
58
|
+
* consecutive windows overlap by up to 63 arcs at an unaligned row boundary (the overlap belongs to the earlier
|
|
59
|
+
* window's rows, which a row-range dispatch such as `degree` never revisits). Here a window owns `[start, next
|
|
60
|
+
* window's start)` -- the last one `[start, end)` -- which is inside its binding (`next.start <= end`) and
|
|
61
|
+
* partitions the arcs exactly; a window whose owned range is empty dispatches, harmlessly, over nothing.
|
|
62
|
+
* @param core - the core (windowed or not)
|
|
63
|
+
* @returns one entry per window, in arc order; exactly one entry over `[0, arcCount)` when the core is not windowed
|
|
64
|
+
*/
|
|
65
|
+
export declare function coreWindows(core: CoreBinding): readonly CoreWindow[];
|
|
47
66
|
/**
|
|
48
67
|
* Rejects a windowed core: the pull cannot accumulate its affine epilogue across windows (DEP-P4-B).
|
|
49
68
|
* @param core - the core
|
|
@@ -67,7 +86,9 @@ export declare function assertWholeCore(core: CoreBinding, arcCount: number, lim
|
|
|
67
86
|
* package. `plan` is "perArray" because views always upload per array (spec 4.3 lines 1182-1186) and `windows` is
|
|
68
87
|
* null because a view is never windowed, which is what makes assertNotWindowed pass for a view. `serial` is -1: a
|
|
69
88
|
* view is not a core and no caller of this function reads serial (nothing in src/primitives or src/algorithms
|
|
70
|
-
* reads CoreBinding.serial).
|
|
89
|
+
* reads CoreBinding.serial). A zero-size `colIdx` or `weights` binding (the reverse view of an arc-less directed
|
|
90
|
+
* snapshot uploads zero-byte arc arrays) becomes null, the core's own spelling of "no arcs", so `graphBindings` binds
|
|
91
|
+
* the dummy instead of a zero-length range (P8-T8: the one-node directed fixture of the BFS suite).
|
|
71
92
|
* @param v - the view binding, from residency.view(s, "reverse") or view(s, "edgeList")
|
|
72
93
|
* @param arcCount - the arc count of the view, from v.scalars.arcCount[0]; it must agree with the colIdx binding,
|
|
73
94
|
* which is what record() derives the arc window from (E_INVALID_ARGUMENT { argument: "arcCount" } otherwise)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core-shape.d.ts","sourceRoot":"","sources":["../../../src/primitives/core-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElE,iJAAiJ;AACjJ,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,gIAAgI;AAChI,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CA4B5D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAcvE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAUhH;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQ3G;AAED
|
|
1
|
+
{"version":3,"file":"core-shape.d.ts","sourceRoot":"","sources":["../../../src/primitives/core-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElE,iJAAiJ;AACjJ,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,gIAAgI;AAChI,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CA4B5D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAcvE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAEpD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAUhH;AAED,sNAAsN;AACtN,MAAM,WAAW,UAAU;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,UAAU,EAAE,CAapE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQ3G;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqCxE"}
|
|
@@ -83,6 +83,32 @@ export function windowBinding(core, name, w) {
|
|
|
83
83
|
}
|
|
84
84
|
return { buffer, offset: w.offset, size: 4 * (w.end - w.start), window: w };
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The per-window dispatches of a kernel that walks rows named by a FRONTIER rather than by a row range (P8-T12:
|
|
88
|
+
* `advance-expand`, `bfs-fused`, `bfs-bottom-up`, `sssp-pred`): every window sees every frontier entry and clips
|
|
89
|
+
* each row to `[arcBase, arcEnd)`, so the ranges must PARTITION `[0, arcCount)` or a row is expanded twice. P4's
|
|
90
|
+
* windows do not: `planArcWindows` opens a window at the previous window's end aligned DOWN to 64 arcs, so two
|
|
91
|
+
* consecutive windows overlap by up to 63 arcs at an unaligned row boundary (the overlap belongs to the earlier
|
|
92
|
+
* window's rows, which a row-range dispatch such as `degree` never revisits). Here a window owns `[start, next
|
|
93
|
+
* window's start)` -- the last one `[start, end)` -- which is inside its binding (`next.start <= end`) and
|
|
94
|
+
* partitions the arcs exactly; a window whose owned range is empty dispatches, harmlessly, over nothing.
|
|
95
|
+
* @param core - the core (windowed or not)
|
|
96
|
+
* @returns one entry per window, in arc order; exactly one entry over `[0, arcCount)` when the core is not windowed
|
|
97
|
+
*/
|
|
98
|
+
export function coreWindows(core) {
|
|
99
|
+
if (core.windows === null) {
|
|
100
|
+
return [{ arcBase: 0, arcEnd: arcCountOf(core), core }];
|
|
101
|
+
}
|
|
102
|
+
return core.windows.map((w, k, all) => ({
|
|
103
|
+
arcBase: w.start,
|
|
104
|
+
arcEnd: k + 1 < all.length ? all[k + 1].start : w.end,
|
|
105
|
+
core: {
|
|
106
|
+
...core,
|
|
107
|
+
colIdx: windowBinding(core, "colIdx", w),
|
|
108
|
+
weights: core.weights === null ? null : windowBinding(core, "weights", w),
|
|
109
|
+
},
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
86
112
|
/**
|
|
87
113
|
* Rejects a windowed core: the pull cannot accumulate its affine epilogue across windows (DEP-P4-B).
|
|
88
114
|
* @param core - the core
|
|
@@ -116,7 +142,9 @@ export function assertWholeCore(core, arcCount, limit, algorithm) {
|
|
|
116
142
|
* package. `plan` is "perArray" because views always upload per array (spec 4.3 lines 1182-1186) and `windows` is
|
|
117
143
|
* null because a view is never windowed, which is what makes assertNotWindowed pass for a view. `serial` is -1: a
|
|
118
144
|
* view is not a core and no caller of this function reads serial (nothing in src/primitives or src/algorithms
|
|
119
|
-
* reads CoreBinding.serial).
|
|
145
|
+
* reads CoreBinding.serial). A zero-size `colIdx` or `weights` binding (the reverse view of an arc-less directed
|
|
146
|
+
* snapshot uploads zero-byte arc arrays) becomes null, the core's own spelling of "no arcs", so `graphBindings` binds
|
|
147
|
+
* the dummy instead of a zero-length range (P8-T8: the one-node directed fixture of the BFS suite).
|
|
120
148
|
* @param v - the view binding, from residency.view(s, "reverse") or view(s, "edgeList")
|
|
121
149
|
* @param arcCount - the arc count of the view, from v.scalars.arcCount[0]; it must agree with the colIdx binding,
|
|
122
150
|
* which is what record() derives the arc window from (E_INVALID_ARGUMENT { argument: "arcCount" } otherwise)
|
|
@@ -131,8 +159,10 @@ export function coreOfView(v, arcCount) {
|
|
|
131
159
|
expected: "a view with a rowPtr binding (reverse)",
|
|
132
160
|
});
|
|
133
161
|
}
|
|
134
|
-
|
|
135
|
-
|
|
162
|
+
// an arc-less DIRECTED snapshot's reverse view uploads zero-byte arc arrays; the core spells "no arcs" as null
|
|
163
|
+
// (graphBindings then binds the rowPtr dummy), and a zero-size binding is never bound (spec 3.6)
|
|
164
|
+
const colIdx = v.bindings.colIdx === undefined || v.bindings.colIdx.size === 0 ? null : v.bindings.colIdx;
|
|
165
|
+
const weights = v.bindings.weights === undefined || v.bindings.weights.size === 0 ? null : v.bindings.weights;
|
|
136
166
|
const bound = colIdx === null ? 0 : colIdx.size / 4;
|
|
137
167
|
if (bound !== arcCount) {
|
|
138
168
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `the ${v.view} view binds ${bound} arcs but its arcCount scalar says ${arcCount}`, {
|