@graphty/webgpu-graph-algorithms 0.6.14 → 0.6.16
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 +52 -52
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-Bi6AhScG.js → context-VIvatQOo.js} +69 -34
- package/dist/chunks/context-VIvatQOo.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/accelerator.d.ts +5 -3
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +101 -5
- package/dist/src/accelerator.js.map +1 -1
- package/dist/src/algorithms/all-pairs.d.ts +41 -0
- package/dist/src/algorithms/all-pairs.d.ts.map +1 -0
- package/dist/src/algorithms/all-pairs.js +181 -0
- package/dist/src/algorithms/all-pairs.js.map +1 -0
- package/dist/src/algorithms/betweenness.d.ts +70 -0
- package/dist/src/algorithms/betweenness.d.ts.map +1 -0
- package/dist/src/algorithms/betweenness.js +538 -0
- package/dist/src/algorithms/betweenness.js.map +1 -0
- package/dist/src/algorithms/closeness.d.ts +15 -5
- package/dist/src/algorithms/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/closeness.js +112 -26
- package/dist/src/algorithms/closeness.js.map +1 -1
- package/dist/src/algorithms/components.d.ts +9 -1
- package/dist/src/algorithms/components.d.ts.map +1 -1
- package/dist/src/algorithms/components.js +2 -2
- package/dist/src/algorithms/components.js.map +1 -1
- package/dist/src/algorithms/label-propagation.d.ts +31 -0
- package/dist/src/algorithms/label-propagation.d.ts.map +1 -0
- package/dist/src/algorithms/label-propagation.js +254 -0
- package/dist/src/algorithms/label-propagation.js.map +1 -0
- package/dist/src/algorithms/simple-symmetric.d.ts +88 -0
- package/dist/src/algorithms/simple-symmetric.d.ts.map +1 -0
- package/dist/src/algorithms/simple-symmetric.js +347 -0
- package/dist/src/algorithms/simple-symmetric.js.map +1 -0
- package/dist/src/algorithms/triangles.d.ts +34 -0
- package/dist/src/algorithms/triangles.d.ts.map +1 -0
- package/dist/src/algorithms/triangles.js +203 -0
- package/dist/src/algorithms/triangles.js.map +1 -0
- package/dist/src/constants.d.ts +53 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +53 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +12 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts.map +1 -1
- package/dist/src/kernel/prelude.js +4 -1
- package/dist/src/kernel/prelude.js.map +1 -1
- package/dist/src/kernels.d.ts +24 -6
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +373 -7
- package/dist/src/kernels.js.map +1 -1
- package/dist/src/memory/residency.js +15 -4
- package/dist/src/memory/residency.js.map +1 -1
- package/dist/src/primitives/coo-to-csr.d.ts +73 -0
- package/dist/src/primitives/coo-to-csr.d.ts.map +1 -0
- package/dist/src/primitives/coo-to-csr.js +183 -0
- package/dist/src/primitives/coo-to-csr.js.map +1 -0
- package/dist/src/primitives/frontier.d.ts +2 -0
- package/dist/src/primitives/frontier.d.ts.map +1 -1
- package/dist/src/primitives/frontier.js +2 -0
- package/dist/src/primitives/frontier.js.map +1 -1
- package/dist/src/primitives/group-by-key.d.ts +82 -0
- package/dist/src/primitives/group-by-key.d.ts.map +1 -0
- package/dist/src/primitives/group-by-key.js +147 -0
- package/dist/src/primitives/group-by-key.js.map +1 -0
- package/dist/src/types/accelerator.d.ts +19 -7
- package/dist/src/types/accelerator.d.ts.map +1 -1
- package/dist/src/types/algorithms.d.ts +4 -0
- package/dist/src/types/algorithms.d.ts.map +1 -1
- package/dist/src/types/all-pairs.d.ts +35 -0
- package/dist/src/types/all-pairs.d.ts.map +1 -0
- package/dist/src/types/all-pairs.js +8 -0
- package/dist/src/types/all-pairs.js.map +1 -0
- package/dist/src/types/betweenness.d.ts +35 -0
- package/dist/src/types/betweenness.d.ts.map +1 -0
- package/dist/src/types/betweenness.js +7 -0
- package/dist/src/types/betweenness.js.map +1 -0
- package/dist/src/types/community.d.ts +18 -0
- package/dist/src/types/community.d.ts.map +1 -0
- package/dist/src/types/community.js +5 -0
- package/dist/src/types/community.js.map +1 -0
- package/dist/src/types/structure.d.ts +27 -0
- package/dist/src/types/structure.d.ts.map +1 -0
- package/dist/src/types/structure.js +8 -0
- package/dist/src/types/structure.js.map +1 -0
- package/dist/src/wgsl/apsp-fw.wgsl.d.ts +25 -0
- package/dist/src/wgsl/apsp-fw.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/apsp-fw.wgsl.js +113 -0
- package/dist/src/wgsl/apsp-fw.wgsl.js.map +1 -0
- package/dist/src/wgsl/apsp-init.wgsl.d.ts +12 -0
- package/dist/src/wgsl/apsp-init.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/apsp-init.wgsl.js +26 -0
- package/dist/src/wgsl/apsp-init.wgsl.js.map +1 -0
- package/dist/src/wgsl/bc-backward.wgsl.d.ts +15 -0
- package/dist/src/wgsl/bc-backward.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bc-backward.wgsl.js +34 -0
- package/dist/src/wgsl/bc-backward.wgsl.js.map +1 -0
- package/dist/src/wgsl/bc-edge-gather.wgsl.d.ts +12 -0
- package/dist/src/wgsl/bc-edge-gather.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bc-edge-gather.wgsl.js +36 -0
- package/dist/src/wgsl/bc-edge-gather.wgsl.js.map +1 -0
- package/dist/src/wgsl/bc-finalize.wgsl.d.ts +21 -0
- package/dist/src/wgsl/bc-finalize.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bc-finalize.wgsl.js +47 -0
- package/dist/src/wgsl/bc-finalize.wgsl.js.map +1 -0
- package/dist/src/wgsl/bc-forward-edge.wgsl.d.ts +15 -0
- package/dist/src/wgsl/bc-forward-edge.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bc-forward-edge.wgsl.js +76 -0
- package/dist/src/wgsl/bc-forward-edge.wgsl.js.map +1 -0
- package/dist/src/wgsl/bc-forward.wgsl.d.ts +23 -0
- package/dist/src/wgsl/bc-forward.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bc-forward.wgsl.js +106 -0
- package/dist/src/wgsl/bc-forward.wgsl.js.map +1 -0
- package/dist/src/wgsl/bc-gather.wgsl.d.ts +9 -0
- package/dist/src/wgsl/bc-gather.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/bc-gather.wgsl.js +20 -0
- package/dist/src/wgsl/bc-gather.wgsl.js.map +1 -0
- package/dist/src/wgsl/closeness-reduce.wgsl.d.ts +4 -1
- package/dist/src/wgsl/closeness-reduce.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/closeness-reduce.wgsl.js +8 -4
- package/dist/src/wgsl/closeness-reduce.wgsl.js.map +1 -1
- package/dist/src/wgsl/closeness-sweep.wgsl.d.ts +4 -2
- package/dist/src/wgsl/closeness-sweep.wgsl.d.ts.map +1 -1
- package/dist/src/wgsl/closeness-sweep.wgsl.js +12 -2
- package/dist/src/wgsl/closeness-sweep.wgsl.js.map +1 -1
- package/dist/src/wgsl/coo-emit.wgsl.d.ts +10 -0
- package/dist/src/wgsl/coo-emit.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/coo-emit.wgsl.js +33 -0
- package/dist/src/wgsl/coo-emit.wgsl.js.map +1 -0
- package/dist/src/wgsl/coo-scatter.wgsl.d.ts +15 -0
- package/dist/src/wgsl/coo-scatter.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/coo-scatter.wgsl.js +32 -0
- package/dist/src/wgsl/coo-scatter.wgsl.js.map +1 -0
- package/dist/src/wgsl/group-by-key-row.wgsl.d.ts +26 -0
- package/dist/src/wgsl/group-by-key-row.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/group-by-key-row.wgsl.js +146 -0
- package/dist/src/wgsl/group-by-key-row.wgsl.js.map +1 -0
- package/dist/src/wgsl/lpa-step.wgsl.d.ts +10 -0
- package/dist/src/wgsl/lpa-step.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/lpa-step.wgsl.js +35 -0
- package/dist/src/wgsl/lpa-step.wgsl.js.map +1 -0
- package/dist/src/wgsl/orient-flags.wgsl.d.ts +9 -0
- package/dist/src/wgsl/orient-flags.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/orient-flags.wgsl.js +21 -0
- package/dist/src/wgsl/orient-flags.wgsl.js.map +1 -0
- package/dist/src/wgsl/run-flags.wgsl.d.ts +8 -0
- package/dist/src/wgsl/run-flags.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/run-flags.wgsl.js +18 -0
- package/dist/src/wgsl/run-flags.wgsl.js.map +1 -0
- package/dist/src/wgsl/tri-intersect.wgsl.d.ts +11 -0
- package/dist/src/wgsl/tri-intersect.wgsl.d.ts.map +1 -0
- package/dist/src/wgsl/tri-intersect.wgsl.js +64 -0
- package/dist/src/wgsl/tri-intersect.wgsl.js.map +1 -0
- package/dist/webgpu-graph-algorithms.js +2828 -321
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +5 -5
- package/src/accelerator.ts +130 -7
- package/src/algorithms/all-pairs.ts +228 -0
- package/src/algorithms/betweenness.ts +739 -0
- package/src/algorithms/closeness.ts +124 -32
- package/src/algorithms/components.ts +2 -2
- package/src/algorithms/label-propagation.ts +280 -0
- package/src/algorithms/simple-symmetric.ts +409 -0
- package/src/algorithms/triangles.ts +240 -0
- package/src/constants.ts +53 -0
- package/src/index.ts +20 -1
- package/src/kernel/prelude.ts +6 -0
- package/src/kernels.ts +411 -10
- package/src/memory/residency.ts +15 -4
- package/src/primitives/coo-to-csr.ts +251 -0
- package/src/primitives/frontier.ts +4 -0
- package/src/primitives/group-by-key.ts +209 -0
- package/src/types/accelerator.ts +26 -6
- package/src/types/algorithms.ts +5 -0
- package/src/types/all-pairs.ts +37 -0
- package/src/types/betweenness.ts +38 -0
- package/src/types/community.ts +18 -0
- package/src/types/structure.ts +28 -0
- package/src/wgsl/apsp-fw.wgsl.ts +112 -0
- package/src/wgsl/apsp-init.wgsl.ts +25 -0
- package/src/wgsl/bc-backward.wgsl.ts +33 -0
- package/src/wgsl/bc-edge-gather.wgsl.ts +35 -0
- package/src/wgsl/bc-finalize.wgsl.ts +46 -0
- package/src/wgsl/bc-forward-edge.wgsl.ts +75 -0
- package/src/wgsl/bc-forward.wgsl.ts +105 -0
- package/src/wgsl/bc-gather.wgsl.ts +19 -0
- package/src/wgsl/closeness-reduce.wgsl.ts +8 -4
- package/src/wgsl/closeness-sweep.wgsl.ts +12 -2
- package/src/wgsl/coo-emit.wgsl.ts +32 -0
- package/src/wgsl/coo-scatter.wgsl.ts +31 -0
- package/src/wgsl/group-by-key-row.wgsl.ts +145 -0
- package/src/wgsl/lpa-step.wgsl.ts +34 -0
- package/src/wgsl/orient-flags.wgsl.ts +20 -0
- package/src/wgsl/run-flags.wgsl.ts +17 -0
- package/src/wgsl/tri-intersect.wgsl.ts +63 -0
- package/dist/chunks/context-Bi6AhScG.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { W as WebGpuGraphError, U as UNIFORM_SLOT_BYTES, B as BufferUsage, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, d as deviceLostError, i as isWebGpuGraphError, b as U32_MAX$2, c as MAX_LEVELS_PER_SUBMIT, R as RADIX_BINS, F as FUSED_FRONTIER_MAX, e as BEAMER_BETA, f as SSSP_DELTA_FACTOR, g as F32_INF_BITS, h as
|
|
2
|
-
import {
|
|
3
|
-
import { renumberPartition, INVALID_INDEX, makeMask, maskTest, expandEdges, fromEdgeArrays } from "@graphty/graph-format";
|
|
1
|
+
import { W as WebGpuGraphError, U as UNIFORM_SLOT_BYTES, B as BufferUsage, M as MAX_WORKGROUPS_PER_DIM, a as WGSL_RESERVED_WORDS, S as STATE_HEADER_BYTES, d as deviceLostError, i as isWebGpuGraphError, b as U32_MAX$2, c as MAX_LEVELS_PER_SUBMIT, R as RADIX_BINS, F as FUSED_FRONTIER_MAX, e as BEAMER_BETA, f as SSSP_DELTA_FACTOR, g as F32_INF_BITS, h as BC_EDGE_PARALLEL_GAMMA, j as BC_BATCH_BUDGET_FRACTION, k as BC_MAX_BATCH, l as BC_BACKWARD_LEVELS_PER_SUBMIT, A as APSP_MAX_DISPATCHES_PER_SUBMIT, m as APSP_TILE, n as GROUP_ROW_THREAD_MAX, o as GROUP_ROW_THREAD_LIMIT, p as GROUP_HASH_LOAD_FACTOR, P as PARALLEL_MERGE_LIMIT, L as LABEL_PROP_PASSES_PER_SUBMIT, q as GRID_COARSEST_SIDE, r as GRID_MIN_SIDE, s as GRID_SORT_BITS, t as FA2_DEFAULTS, u as MAX_ITERATIONS_PER_STEP, v as MAX_1D_ITEMS, w as hasErrorCode, x as FA2_FLAG_FIRST, y as PARTIAL_BYTES, E as EXACT_TILES_PER_PASS, I as INDIRECT_ARGS_STRIDE, z as GRID_HUB_CELL, C as LAYOUT_TUNING_DEFAULTS, D as EXACT_MAX_NODES, H as SETTLE_FLOOR_UNBOUNDED, T as TRACE_RECORD_BYTES, J as GRID_BBOX_MARGIN, K as GRID_EXTENT_FLOOR, N as FR_ADAPTIVE_MAX_ITERATIONS, O as FR_START_TEMPERATURE, Q as FA2_FLAG_ADAPTIVE, V as SETTLE_FLOOR_FRACTION, X as FR_REHEAT_FRACTION, Y as FR_DEFAULTS, Z as SE_DEFAULTS, _ as SETTLE_FLOOR_REFERENCE_NODES, $ as SE_SCALE_REFERENCE_NODES } from "./chunks/context-VIvatQOo.js";
|
|
2
|
+
import { a0, G, a1, a2, a3, a4 } from "./chunks/context-VIvatQOo.js";
|
|
3
|
+
import { renumberPartition, INVALID_INDEX, foldArcs, makeMask, maskTest, expandEdges, fromEdgeArrays } from "@graphty/graph-format";
|
|
4
4
|
class UniformRing {
|
|
5
5
|
/**
|
|
6
6
|
* Creates the ring buffer (`slots x UNIFORM_SLOT_BYTES` bytes, UNIFORM | COPY_DST) through the allocator.
|
|
@@ -211,6 +211,10 @@ function plan1d(items, wg, caps) {
|
|
|
211
211
|
assertWorkgroupSize(wg);
|
|
212
212
|
return grid(Math.ceil(items / wg), items, caps);
|
|
213
213
|
}
|
|
214
|
+
function plan2d(groups, caps) {
|
|
215
|
+
assertCount$1("groups", groups);
|
|
216
|
+
return grid(groups, groups, caps);
|
|
217
|
+
}
|
|
214
218
|
function planGridStride(items, wg, caps, maxGroups) {
|
|
215
219
|
assertCount$1("items", items);
|
|
216
220
|
assertWorkgroupSize(wg);
|
|
@@ -605,6 +609,364 @@ fn advance_expand(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocati
|
|
|
605
609
|
}
|
|
606
610
|
`
|
|
607
611
|
);
|
|
612
|
+
const apspFwWgsl = (
|
|
613
|
+
/* wgsl */
|
|
614
|
+
`
|
|
615
|
+
var<workgroup> tileA: array<f32, APSP_TILE * APSP_TILE>;
|
|
616
|
+
var<workgroup> tileB: array<f32, APSP_TILE * APSP_TILE>;
|
|
617
|
+
|
|
618
|
+
fn tile_row(block: vec2<u32>, c: u32) -> u32 { return block.x * APSP_TILE + c / APSP_TILE; }
|
|
619
|
+
fn tile_col(block: vec2<u32>, c: u32) -> u32 { return block.y * APSP_TILE + c % APSP_TILE; }
|
|
620
|
+
|
|
621
|
+
fn load_cell(block: vec2<u32>, c: u32) -> f32 {
|
|
622
|
+
let i = tile_row(block, c);
|
|
623
|
+
let j = tile_col(block, c);
|
|
624
|
+
if (i < P.n && j < P.n) { return dist[i * P.n + j]; }
|
|
625
|
+
return bitcast<f32>(P.infBits); // outside the matrix: no path through it
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
fn store_cell(block: vec2<u32>, c: u32, v: f32) {
|
|
629
|
+
let i = tile_row(block, c);
|
|
630
|
+
let j = tile_col(block, c);
|
|
631
|
+
if (i >= P.n || j >= P.n) { return; } // an edge tile stores nothing outside n x n
|
|
632
|
+
dist[i * P.n + j] = v;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
@compute @workgroup_size(WG)
|
|
636
|
+
fn apsp_fw(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
637
|
+
let g = group_id(wid);
|
|
638
|
+
let r = P.round;
|
|
639
|
+
let others = max(P.blocks, 2u) - 1u; // the blocks of a strip, the pivot excluded
|
|
640
|
+
let cells = APSP_TILE * APSP_TILE;
|
|
641
|
+
var count = 1u;
|
|
642
|
+
var own = vec2<u32>(r, r); // the block this workgroup updates
|
|
643
|
+
var a = vec2<u32>(r, r); // staged in tileA
|
|
644
|
+
var b = vec2<u32>(r, r); // staged in tileB
|
|
645
|
+
if (PHASE == 1u) {
|
|
646
|
+
count = 2u * others;
|
|
647
|
+
let s = g % others;
|
|
648
|
+
let o = select(s, s + 1u, s >= r); // a strip skips the pivot block
|
|
649
|
+
own = select(vec2<u32>(o, r), vec2<u32>(r, o), g < others); // block row r first, then block column r
|
|
650
|
+
b = own;
|
|
651
|
+
}
|
|
652
|
+
if (PHASE == 2u) {
|
|
653
|
+
count = others * others;
|
|
654
|
+
let i = g / others;
|
|
655
|
+
let j = g % others;
|
|
656
|
+
own = vec2<u32>(select(i, i + 1u, i >= r), select(j, j + 1u, j >= r));
|
|
657
|
+
a = vec2<u32>(own.x, r); // the pivot-column block (i, r)
|
|
658
|
+
b = vec2<u32>(r, own.y); // the pivot-row block (r, j)
|
|
659
|
+
}
|
|
660
|
+
if (g >= count) { return; } // uniform: the workgroup id and uniforms only
|
|
661
|
+
|
|
662
|
+
for (var c = lid.x; c < cells; c = c + WG) {
|
|
663
|
+
tileA[c] = load_cell(a, c);
|
|
664
|
+
if (PHASE != 0u) { tileB[c] = load_cell(b, c); }
|
|
665
|
+
}
|
|
666
|
+
workgroupBarrier(); // every staged cell is visible
|
|
667
|
+
|
|
668
|
+
if (PHASE == 2u) {
|
|
669
|
+
for (var c = lid.x; c < cells; c = c + WG) {
|
|
670
|
+
let x = c / APSP_TILE;
|
|
671
|
+
let y = c % APSP_TILE;
|
|
672
|
+
var v = load_cell(own, c);
|
|
673
|
+
for (var kr = 0u; kr < APSP_TILE; kr = kr + 1u) {
|
|
674
|
+
v = min(v, tileA[x * APSP_TILE + kr] + tileB[kr * APSP_TILE + y]);
|
|
675
|
+
}
|
|
676
|
+
store_cell(own, c, v);
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
let pivotRow = PHASE == 1u && own.x == r; // block row r reads d[x][k] from the pivot
|
|
682
|
+
for (var k = 0u; k < APSP_TILE; k = k + 1u) {
|
|
683
|
+
for (var c = lid.x; c < cells; c = c + WG) {
|
|
684
|
+
let x = c / APSP_TILE;
|
|
685
|
+
let y = c % APSP_TILE;
|
|
686
|
+
if (PHASE == 0u) {
|
|
687
|
+
let via = tileA[x * APSP_TILE + k] + tileA[k * APSP_TILE + y];
|
|
688
|
+
if (via < tileA[c]) { tileA[c] = via; }
|
|
689
|
+
} else {
|
|
690
|
+
let left = select(tileB[x * APSP_TILE + k], tileA[x * APSP_TILE + k], pivotRow);
|
|
691
|
+
let right = select(tileA[k * APSP_TILE + y], tileB[k * APSP_TILE + y], pivotRow);
|
|
692
|
+
let via = left + right;
|
|
693
|
+
if (via < tileB[c]) { tileB[c] = via; }
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
workgroupBarrier(); // step k is complete before step k + 1 reads
|
|
697
|
+
}
|
|
698
|
+
for (var c = lid.x; c < cells; c = c + WG) {
|
|
699
|
+
store_cell(own, c, select(tileB[c], tileA[c], PHASE == 0u));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
`
|
|
703
|
+
);
|
|
704
|
+
const apspInitWgsl = (
|
|
705
|
+
/* wgsl */
|
|
706
|
+
`
|
|
707
|
+
@compute @workgroup_size(WG)
|
|
708
|
+
fn apsp_init(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
709
|
+
let u = linear_id(wid, lid.x);
|
|
710
|
+
if (u >= P.n) { return; }
|
|
711
|
+
let rowBase = u * P.n;
|
|
712
|
+
let end = rowPtr[u + 1u];
|
|
713
|
+
for (var a = rowPtr[u]; a < end; a = a + 1u) {
|
|
714
|
+
let v = colIdx[a];
|
|
715
|
+
let w = select(1.0, weights[a], HAS_WEIGHTS);
|
|
716
|
+
dist[rowBase + v] = min(dist[rowBase + v], w); // parallel arcs collapse to the cheapest
|
|
717
|
+
}
|
|
718
|
+
dist[rowBase + u] = 0.0; // last: a self-loop never displaces the zero
|
|
719
|
+
}
|
|
720
|
+
`
|
|
721
|
+
);
|
|
722
|
+
const bcBackwardWgsl = (
|
|
723
|
+
/* wgsl */
|
|
724
|
+
`
|
|
725
|
+
@compute @workgroup_size(WG)
|
|
726
|
+
fn bc_backward(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
727
|
+
for (var i = linear_id(wid, lid.x); i < P.count; i = i + P.stride) {
|
|
728
|
+
let t = S[P.start + i]; // s * n + w
|
|
729
|
+
let w = t % P.n;
|
|
730
|
+
let base = t - w; // s * n
|
|
731
|
+
let succ = depthK[t] + 1u;
|
|
732
|
+
let sw = f32(sigmaK[t]);
|
|
733
|
+
var acc = 0.0;
|
|
734
|
+
for (var a = rowPtr[w]; a < rowPtr[w + 1u]; a = a + 1u) {
|
|
735
|
+
let v = base + colIdx[a];
|
|
736
|
+
if (depthK[v] == succ) { // v is a successor of w for source s
|
|
737
|
+
acc = acc + (sw / f32(sigmaK[v])) * (1.0 + deltaK[v]);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
deltaK[t] = acc; // written once per (w, s)
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
`
|
|
744
|
+
);
|
|
745
|
+
const bcEdgeGatherWgsl = (
|
|
746
|
+
/* wgsl */
|
|
747
|
+
`
|
|
748
|
+
@compute @workgroup_size(WG)
|
|
749
|
+
fn bc_edge_gather(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
750
|
+
for (var a = linear_id(wid, lid.x); a < P.count; a = a + P.stride) {
|
|
751
|
+
var lo = 0u; // the row w with rowPtr[w] <= a < rowPtr[w + 1]
|
|
752
|
+
var hi = P.n;
|
|
753
|
+
loop {
|
|
754
|
+
if (lo >= hi) { break; }
|
|
755
|
+
let mid = (lo + hi) / 2u;
|
|
756
|
+
if (rowPtr[mid + 1u] <= a) { lo = mid + 1u; } else { hi = mid; }
|
|
757
|
+
}
|
|
758
|
+
let w = lo;
|
|
759
|
+
let nbr = colIdx[a];
|
|
760
|
+
var acc = arcScores[a];
|
|
761
|
+
for (var s = 0u; s < P.k; s = s + 1u) {
|
|
762
|
+
let base = s * P.n;
|
|
763
|
+
let dw = depthK[base + w];
|
|
764
|
+
if (dw != INVALID_INDEX && depthK[base + nbr] == dw + 1u) { // (w, nbr) is on a shortest path from s
|
|
765
|
+
acc = acc + (f32(sigmaK[base + w]) / f32(sigmaK[base + nbr])) * (1.0 + deltaK[base + nbr]);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
arcScores[a] = acc;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
`
|
|
772
|
+
);
|
|
773
|
+
const bcFinalizeWgsl = (
|
|
774
|
+
/* wgsl */
|
|
775
|
+
`
|
|
776
|
+
@compute @workgroup_size(WG)
|
|
777
|
+
fn bc_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
778
|
+
if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)
|
|
779
|
+
if (P.role == 1u) { // the seed of a batch
|
|
780
|
+
for (var i = 0u; i < P.k; i = i + 1u) {
|
|
781
|
+
let t = S[i];
|
|
782
|
+
depthK[t] = 0u; // the source is at depth 0
|
|
783
|
+
sigmaK[t] = 1u; // with one shortest path, itself
|
|
784
|
+
}
|
|
785
|
+
ends[0] = 0u;
|
|
786
|
+
atomicStore(&counters[26], P.k); // stackTop: the seeds are the log's first k entries
|
|
787
|
+
atomicStore(&counters[27], 0u); // sigmaOverflow
|
|
788
|
+
atomicStore(&counters[11], U32_MAX); // level: the first boundary brings it to 0
|
|
789
|
+
atomicStore(&counters[15], 0u); // done
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (atomicLoad(&counters[15]) != 0u) { return; } // done: a no-op level the host recorded past the end
|
|
793
|
+
let level = atomicLoad(&counters[11]) + 1u;
|
|
794
|
+
let top = atomicLoad(&counters[26]);
|
|
795
|
+
ends[level + 1u] = top; // the level's entries end where the log ends now
|
|
796
|
+
let count = top - ends[level];
|
|
797
|
+
atomicStore(&counters[0], count); // frontierCount (the inspect seam reads it)
|
|
798
|
+
atomicStore(&counters[11], level);
|
|
799
|
+
atomicStore(&counters[15], select(0u, 1u, count == 0u)); // an empty level ends the batch
|
|
800
|
+
}
|
|
801
|
+
`
|
|
802
|
+
);
|
|
803
|
+
const bcForwardWgsl = (
|
|
804
|
+
/* wgsl */
|
|
805
|
+
`
|
|
806
|
+
var<workgroup> sh: array<u32, WG>; // the block's degrees, then their inclusive scan
|
|
807
|
+
var<workgroup> rowStart: array<u32, WG>; // the first arc of each entry's row
|
|
808
|
+
var<workgroup> entryOf: array<u32, WG>; // each entry, s * n + u
|
|
809
|
+
var<workgroup> wstart: u32; // the level's first log index
|
|
810
|
+
var<workgroup> wcount: u32; // the level's entry count
|
|
811
|
+
var<workgroup> wwon: atomic<u32>; // the strip's winners
|
|
812
|
+
var<workgroup> wbase: u32; // where the strip's winners go in the log
|
|
813
|
+
|
|
814
|
+
@compute @workgroup_size(WG)
|
|
815
|
+
fn bc_forward(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
816
|
+
let level = atomicLoad(&counters[11]);
|
|
817
|
+
if (lid.x == 0u) {
|
|
818
|
+
let lo = ends[level];
|
|
819
|
+
wstart = lo;
|
|
820
|
+
wcount = ends[level + 1u] - lo;
|
|
821
|
+
}
|
|
822
|
+
let start = workgroupUniformLoad(&wstart);
|
|
823
|
+
let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
|
|
824
|
+
let next = level + 1u;
|
|
825
|
+
for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
|
|
826
|
+
let i = b0 + lid.x;
|
|
827
|
+
var deg = 0u;
|
|
828
|
+
var first = 0u;
|
|
829
|
+
var entry = 0u;
|
|
830
|
+
if (i < count) { // guarded loads into locals (3.5 rule 1)
|
|
831
|
+
entry = S[start + i];
|
|
832
|
+
let u = entry % P.n;
|
|
833
|
+
first = rowPtr[u];
|
|
834
|
+
deg = rowPtr[u + 1u] - first;
|
|
835
|
+
}
|
|
836
|
+
sh[lid.x] = deg;
|
|
837
|
+
rowStart[lid.x] = first;
|
|
838
|
+
entryOf[lid.x] = entry;
|
|
839
|
+
workgroupBarrier();
|
|
840
|
+
for (var s = 1u; s < WG; s = s * 2u) { // Hillis-Steele inclusive scan of the degrees
|
|
841
|
+
var t = 0u;
|
|
842
|
+
if (lid.x >= s) { t = sh[lid.x - s]; }
|
|
843
|
+
workgroupBarrier();
|
|
844
|
+
sh[lid.x] = sh[lid.x] + t;
|
|
845
|
+
workgroupBarrier();
|
|
846
|
+
}
|
|
847
|
+
let aggregate = workgroupUniformLoad(&sh[WG - 1u]); // uniform; includes a barrier
|
|
848
|
+
for (var p0 = 0u; p0 < aggregate; p0 = p0 + WG) { // strip [0, aggregate) WG arcs at a time
|
|
849
|
+
let p = p0 + lid.x;
|
|
850
|
+
var won = false;
|
|
851
|
+
var claimed = 0u;
|
|
852
|
+
if (p < aggregate) {
|
|
853
|
+
var lo = 0u; // upper_bound: the first k with sh[k] > p owns arc p
|
|
854
|
+
var hi = WG;
|
|
855
|
+
loop {
|
|
856
|
+
if (lo >= hi) { break; }
|
|
857
|
+
let mid = (lo + hi) / 2u;
|
|
858
|
+
if (sh[mid] > p) { hi = mid; } else { lo = mid + 1u; }
|
|
859
|
+
}
|
|
860
|
+
let k = lo;
|
|
861
|
+
var exclusive = 0u;
|
|
862
|
+
if (k > 0u) { exclusive = sh[k - 1u]; }
|
|
863
|
+
let origin = entryOf[k]; // s * n + u
|
|
864
|
+
let x = (origin - (origin % P.n)) + colIdx[rowStart[k] + (p - exclusive)]; // s * n + x
|
|
865
|
+
if (atomicLoad(&depthK[x]) == INVALID_INDEX) { // the pre-check of design 16.1
|
|
866
|
+
won = atomicMin(&depthK[x], next) == INVALID_INDEX; // the claim: the one winner appends
|
|
867
|
+
}
|
|
868
|
+
if (atomicLoad(&depthK[x]) == next) { // the count: EVERY arc on a shortest path adds
|
|
869
|
+
let add = atomicLoad(&sigmaK[origin]);
|
|
870
|
+
let old = atomicAdd(&sigmaK[x], add);
|
|
871
|
+
if (old + add < old) { atomicOr(&counters[27], 1u); } // the u32 wrap, reported
|
|
872
|
+
}
|
|
873
|
+
claimed = x;
|
|
874
|
+
}
|
|
875
|
+
var slot = 0u;
|
|
876
|
+
if (won) { slot = atomicAdd(&wwon, 1u); }
|
|
877
|
+
workgroupBarrier();
|
|
878
|
+
if (lid.x == 0u) {
|
|
879
|
+
wbase = atomicAdd(&counters[26], atomicLoad(&wwon)); // stackTop: one global atomic per strip
|
|
880
|
+
atomicStore(&wwon, 0u);
|
|
881
|
+
}
|
|
882
|
+
workgroupBarrier();
|
|
883
|
+
if (won) { S[wbase + slot] = claimed; }
|
|
884
|
+
}
|
|
885
|
+
workgroupBarrier(); // sh, rowStart and entryOf are reused by the next block
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
`
|
|
889
|
+
);
|
|
890
|
+
const bcForwardEdgeWgsl = (
|
|
891
|
+
/* wgsl */
|
|
892
|
+
`
|
|
893
|
+
var<workgroup> wlive: u32; // 1 when the level has entries
|
|
894
|
+
var<workgroup> wwon: atomic<u32>; // the strip's winners
|
|
895
|
+
var<workgroup> wbase: u32; // where the strip's winners go in the log
|
|
896
|
+
|
|
897
|
+
fn claim(x: u32, next: u32) -> bool {
|
|
898
|
+
if (atomicLoad(&depthK[x]) != INVALID_INDEX) { return false; } // the pre-check of design 16.1
|
|
899
|
+
return atomicMin(&depthK[x], next) == INVALID_INDEX;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
fn count_paths(origin: u32, x: u32, next: u32) {
|
|
903
|
+
if (atomicLoad(&depthK[x]) == next) { // every arc on a shortest path adds
|
|
904
|
+
let add = atomicLoad(&sigmaK[origin]);
|
|
905
|
+
let old = atomicAdd(&sigmaK[x], add);
|
|
906
|
+
if (old + add < old) { atomicOr(&counters[27], 1u); } // the u32 wrap, reported
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
@compute @workgroup_size(WG)
|
|
911
|
+
fn bc_forward_edge(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
912
|
+
let level = atomicLoad(&counters[11]);
|
|
913
|
+
if (lid.x == 0u) { wlive = select(0u, 1u, ends[level + 1u] > ends[level]); }
|
|
914
|
+
if (workgroupUniformLoad(&wlive) == 0u) { return; } // uniform: nothing below runs on an empty level
|
|
915
|
+
let next = level + 1u;
|
|
916
|
+
for (var s = 0u; s < P.k; s = s + 1u) {
|
|
917
|
+
let base = s * P.n;
|
|
918
|
+
for (var e0 = group_id(wid) * WG; e0 < P.count; e0 = e0 + P.stride) { // grid-stride over the edges
|
|
919
|
+
let e = e0 + lid.x;
|
|
920
|
+
var a = INVALID_INDEX; // the claims this lane won
|
|
921
|
+
var b = INVALID_INDEX;
|
|
922
|
+
if (e < P.count) {
|
|
923
|
+
let u = base + edgeSrc[e];
|
|
924
|
+
let x = base + edgeDst[e];
|
|
925
|
+
if (atomicLoad(&depthK[u]) == level) {
|
|
926
|
+
if (claim(x, next)) { a = x; }
|
|
927
|
+
count_paths(u, x, next);
|
|
928
|
+
}
|
|
929
|
+
if (UNDIRECTED) { // the other direction of an undirected edge
|
|
930
|
+
if (atomicLoad(&depthK[x]) == level) {
|
|
931
|
+
if (claim(u, next)) { b = u; }
|
|
932
|
+
count_paths(x, u, next);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
let mine = select(0u, 1u, a != INVALID_INDEX) + select(0u, 1u, b != INVALID_INDEX);
|
|
937
|
+
var slot = 0u;
|
|
938
|
+
if (mine != 0u) { slot = atomicAdd(&wwon, mine); }
|
|
939
|
+
workgroupBarrier();
|
|
940
|
+
if (lid.x == 0u) {
|
|
941
|
+
wbase = atomicAdd(&counters[26], atomicLoad(&wwon)); // stackTop: one global atomic per strip
|
|
942
|
+
atomicStore(&wwon, 0u);
|
|
943
|
+
}
|
|
944
|
+
workgroupBarrier();
|
|
945
|
+
if (a != INVALID_INDEX) {
|
|
946
|
+
S[wbase + slot] = a;
|
|
947
|
+
slot = slot + 1u;
|
|
948
|
+
}
|
|
949
|
+
if (b != INVALID_INDEX) { S[wbase + slot] = b; }
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
`
|
|
954
|
+
);
|
|
955
|
+
const bcGatherWgsl = (
|
|
956
|
+
/* wgsl */
|
|
957
|
+
`
|
|
958
|
+
@compute @workgroup_size(WG)
|
|
959
|
+
fn bc_gather(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
960
|
+
for (var w = linear_id(wid, lid.x); w < P.n; w = w + P.stride) {
|
|
961
|
+
var acc = bc[w];
|
|
962
|
+
for (var s = 0u; s < P.k; s = s + 1u) {
|
|
963
|
+
acc = acc + deltaK[s * P.n + w];
|
|
964
|
+
}
|
|
965
|
+
bc[w] = acc;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
`
|
|
969
|
+
);
|
|
608
970
|
const bfRelaxWgsl = (
|
|
609
971
|
/* wgsl */
|
|
610
972
|
`
|
|
@@ -854,13 +1216,14 @@ const closenessReduceWgsl = (
|
|
|
854
1216
|
@compute @workgroup_size(WG)
|
|
855
1217
|
fn closeness_reduce(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
856
1218
|
if (lid.x != 0u) { return; } // one lane; no barrier follows (3.5 rule 1)
|
|
857
|
-
if (P.role
|
|
1219
|
+
if (P.role != 0u) { // the seed of a batch: P.source is its first source
|
|
858
1220
|
let k = min(32u, P.n - P.source);
|
|
859
1221
|
for (var s = 0u; s < k; s = s + 1u) {
|
|
860
|
-
|
|
1222
|
+
var v = P.source + s;
|
|
1223
|
+
if (P.role == 2u) { v = atomicLoad(&perSource[128u + P.bitsBase + P.source + s]); } // a sampled run's list
|
|
861
1224
|
let bit = 1u << s;
|
|
862
|
-
bits[v] = bit;
|
|
863
|
-
bits[P.bitsBase + v] = bit;
|
|
1225
|
+
bits[v] = bits[v] | bit; // visited
|
|
1226
|
+
bits[P.bitsBase + v] = bits[P.bitsBase + v] | bit; // the frontier level 0 reads (region 1: level 0's parity is 0)
|
|
864
1227
|
bits[3u * P.bitsBase + v] = 1u; // flags: level 0's compact turns them into the list
|
|
865
1228
|
}
|
|
866
1229
|
atomicStore(&counters[0], k); // not done
|
|
@@ -908,11 +1271,16 @@ var<workgroup> rowStart: array<u32, WG>; // the first bound arc of each
|
|
|
908
1271
|
var<workgroup> rowOf: array<u32, WG>; // the frontier vertex of each entry (the source end of its arcs)
|
|
909
1272
|
var<workgroup> local: array<atomic<u32>, 32>; // this workgroup's fresh claims per source
|
|
910
1273
|
var<workgroup> wcount: u32; // the frontier list's length
|
|
1274
|
+
var<workgroup> wdist: u32; // the distance of this level's claims
|
|
911
1275
|
|
|
912
1276
|
@compute @workgroup_size(WG)
|
|
913
1277
|
fn closeness_sweep(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
914
|
-
if (lid.x == 0u) {
|
|
1278
|
+
if (lid.x == 0u) {
|
|
1279
|
+
wcount = atomicLoad(&counters[0]); // the frontier list's length (compact's total)
|
|
1280
|
+
wdist = atomicLoad(&counters[11]) + 1u; // the level word: this level claims at level + 1
|
|
1281
|
+
}
|
|
915
1282
|
let count = workgroupUniformLoad(&wcount); // uniform: the block loop below holds barriers
|
|
1283
|
+
let dist = workgroupUniformLoad(&wdist);
|
|
916
1284
|
let nextBase = select(2u * P.bitsBase, P.bitsBase, P.mode == 1u); // the region that is next this level
|
|
917
1285
|
let frontierBase = 3u * P.bitsBase - nextBase; // the other one: the region that is the frontier
|
|
918
1286
|
for (var b0 = group_id(wid) * WG; b0 < count; b0 = b0 + P.stride) { // grid-stride over blocks of WG entries
|
|
@@ -960,6 +1328,9 @@ fn closeness_sweep(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
|
|
|
960
1328
|
if (fresh != 0u) {
|
|
961
1329
|
atomicOr(&bits[nextBase + x], fresh);
|
|
962
1330
|
atomicStore(&bits[3u * P.bitsBase + x], 1u); // flags: x is in the next frontier list (compact reads it)
|
|
1331
|
+
if (P.perNode == 1u) { // a sampled run: x's distance to each source won
|
|
1332
|
+
atomicAdd(&perSource[128u + x], countOneBits(fresh) * dist);
|
|
1333
|
+
}
|
|
963
1334
|
var b = fresh;
|
|
964
1335
|
loop { // one tally per set bit of fresh
|
|
965
1336
|
if (b == 0u) { break; }
|
|
@@ -992,6 +1363,54 @@ fn compact_scatter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
|
|
|
992
1363
|
}
|
|
993
1364
|
`
|
|
994
1365
|
);
|
|
1366
|
+
const cooEmitWgsl = (
|
|
1367
|
+
/* wgsl */
|
|
1368
|
+
`
|
|
1369
|
+
@compute @workgroup_size(WG)
|
|
1370
|
+
fn coo_emit(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
1371
|
+
let i = linear_id(wid, lid.x);
|
|
1372
|
+
if (i >= P.count) { return; } // no barrier follows
|
|
1373
|
+
var a = i;
|
|
1374
|
+
if (INDEXED) { a = order[i]; } // the arc this position takes
|
|
1375
|
+
let e = a / 2u; // arc 2e is edge e as declared, arc 2e + 1 its reverse
|
|
1376
|
+
let forward = (a % 2u) == 0u;
|
|
1377
|
+
let u = edgeSrc[e];
|
|
1378
|
+
let v = edgeDst[e];
|
|
1379
|
+
var w = 1.0;
|
|
1380
|
+
if (WEIGHTED) { w = edgeWeight[e]; }
|
|
1381
|
+
if (u == v) { // a self-loop is dropped: both arcs sort last and never open a run
|
|
1382
|
+
outSrc[i] = INVALID_INDEX;
|
|
1383
|
+
outDst[i] = INVALID_INDEX;
|
|
1384
|
+
outWeight[i] = 0.0;
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
outSrc[i] = select(v, u, forward);
|
|
1388
|
+
outDst[i] = select(u, v, forward);
|
|
1389
|
+
outWeight[i] = w;
|
|
1390
|
+
}
|
|
1391
|
+
`
|
|
1392
|
+
);
|
|
1393
|
+
const cooScatterWgsl = (
|
|
1394
|
+
/* wgsl */
|
|
1395
|
+
`
|
|
1396
|
+
@compute @workgroup_size(WG)
|
|
1397
|
+
fn coo_scatter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
1398
|
+
let i = linear_id(wid, lid.x);
|
|
1399
|
+
if (i >= P.count) { return; } // no barrier follows
|
|
1400
|
+
let s = src[i];
|
|
1401
|
+
var slot = 0u;
|
|
1402
|
+
if (SORTED_INPUT) {
|
|
1403
|
+
if (i > 0u && s < src[i - 1u]) { atomicStore(&cursors[0], 1u); } // unsorted input: the flag the driver refuses
|
|
1404
|
+
let within = i - rowPtr[s]; // the arc's place in its row
|
|
1405
|
+
slot = rowPtr[s] + within;
|
|
1406
|
+
} else {
|
|
1407
|
+
slot = rowPtr[s] + atomicAdd(&cursors[s], 1u); // race order: the row is not sorted in this mode
|
|
1408
|
+
}
|
|
1409
|
+
colIdx[slot] = dst[i];
|
|
1410
|
+
if (WEIGHTED) { outWeight[slot] = weight[i]; }
|
|
1411
|
+
}
|
|
1412
|
+
`
|
|
1413
|
+
);
|
|
995
1414
|
const countingScatterWgsl = (
|
|
996
1415
|
/* wgsl */
|
|
997
1416
|
`
|
|
@@ -1931,6 +2350,130 @@ fn grid_near_field(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
|
|
|
1931
2350
|
}
|
|
1932
2351
|
`
|
|
1933
2352
|
);
|
|
2353
|
+
const groupByKeyRowWgsl = (
|
|
2354
|
+
/* wgsl */
|
|
2355
|
+
`
|
|
2356
|
+
var<workgroup> shMax: array<f32, WG>;
|
|
2357
|
+
var<workgroup> shKey: array<u32, WG>;
|
|
2358
|
+
var<workgroup> shSum: array<u32, WG>;
|
|
2359
|
+
|
|
2360
|
+
fn weight_of(a: u32) -> f32 {
|
|
2361
|
+
if (WEIGHTED) { return weights[a]; }
|
|
2362
|
+
return 1.0;
|
|
2363
|
+
}
|
|
2364
|
+
fn scale_of(maxW: f32, d: u32) -> f32 { // 2^s, maxW x d x 2^s in [2^28, 2^30), from exponents alone
|
|
2365
|
+
let em = i32((bitcast<u32>(maxW) >> 23u) & 255u) - 127;
|
|
2366
|
+
let ed = i32(firstLeadingBit(max(d, 1u)));
|
|
2367
|
+
let s = clamp(28 - em - ed, -126, 126);
|
|
2368
|
+
return bitcast<f32>(u32(s + 127) << 23u);
|
|
2369
|
+
}
|
|
2370
|
+
fn inverse_of(scale: f32) -> f32 { // 2^-s, exact
|
|
2371
|
+
let s = i32((bitcast<u32>(scale) >> 23u) & 255u) - 127;
|
|
2372
|
+
return bitcast<f32>(u32(127 - s) << 23u);
|
|
2373
|
+
}
|
|
2374
|
+
fn quantize(w: f32, scale: f32) -> u32 { // nearest integer, halves up; every step exact
|
|
2375
|
+
let x = max(w * scale, 0.0);
|
|
2376
|
+
let i = u32(x);
|
|
2377
|
+
return select(i, i + 1u, x - f32(i) >= 0.5);
|
|
2378
|
+
}
|
|
2379
|
+
fn better(sum: u32, key: u32, bestSum: u32, bestKey0: u32) -> bool { return sum > bestSum || (sum == bestSum && key < bestKey0); }
|
|
2380
|
+
|
|
2381
|
+
fn row_thread(v: u32) {
|
|
2382
|
+
let lo = rowPtr[v];
|
|
2383
|
+
let hi = rowPtr[v + 1u];
|
|
2384
|
+
var maxW = 0.0;
|
|
2385
|
+
for (var a = lo; a < hi; a = a + 1u) { maxW = max(maxW, weight_of(a)); }
|
|
2386
|
+
let scale = scale_of(maxW, hi - lo);
|
|
2387
|
+
var bk = INVALID_INDEX;
|
|
2388
|
+
var bs = 0u;
|
|
2389
|
+
for (var a = lo; a < hi; a = a + 1u) {
|
|
2390
|
+
let k = keyIn[colIdx[a]];
|
|
2391
|
+
var seen = false;
|
|
2392
|
+
for (var b = lo; b < a; b = b + 1u) { if (keyIn[colIdx[b]] == k) { seen = true; break; } }
|
|
2393
|
+
if (seen) { continue; } // this key was summed at its first arc
|
|
2394
|
+
var sum = 0u;
|
|
2395
|
+
for (var b = a; b < hi; b = b + 1u) { if (keyIn[colIdx[b]] == k) { sum = sum + quantize(weight_of(b), scale); } }
|
|
2396
|
+
if (better(sum, k, bs, bk)) { bk = k; bs = sum; }
|
|
2397
|
+
}
|
|
2398
|
+
bestKey[v] = bk;
|
|
2399
|
+
bestScore[v] = f32(bs) * inverse_of(scale);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
fn row_hash(g: u32, lid: u32) {
|
|
2403
|
+
let v = rows[P.rowsBase + g];
|
|
2404
|
+
let base = rows[P.basesBase + g];
|
|
2405
|
+
let lo = rowPtr[v];
|
|
2406
|
+
let hi = rowPtr[v + 1u];
|
|
2407
|
+
let cap = GROUP_HASH_LOAD_FACTOR * (hi - lo);
|
|
2408
|
+
var m = 0.0;
|
|
2409
|
+
for (var a = lo + lid; a < hi; a = a + WG) { m = max(m, weight_of(a)); }
|
|
2410
|
+
shMax[lid] = m;
|
|
2411
|
+
workgroupBarrier();
|
|
2412
|
+
for (var s = WG / 2u; s > 0u; s = s / 2u) {
|
|
2413
|
+
if (lid < s) { shMax[lid] = max(shMax[lid], shMax[lid + s]); }
|
|
2414
|
+
workgroupBarrier();
|
|
2415
|
+
}
|
|
2416
|
+
let scale = scale_of(shMax[0], hi - lo);
|
|
2417
|
+
for (var j = lid; j < cap; j = j + WG) { // the region is this workgroup's alone: clear it
|
|
2418
|
+
atomicStore(&hashRegion[base + 2u * j], INVALID_INDEX);
|
|
2419
|
+
atomicStore(&hashRegion[base + 2u * j + 1u], 0u);
|
|
2420
|
+
}
|
|
2421
|
+
storageBarrier();
|
|
2422
|
+
var exhausted = false;
|
|
2423
|
+
for (var a = lo + lid; a < hi; a = a + WG) {
|
|
2424
|
+
let k = keyIn[colIdx[a]];
|
|
2425
|
+
let q = quantize(weight_of(a), scale);
|
|
2426
|
+
var slot = lowbias32(k) % cap;
|
|
2427
|
+
var steps = 0u;
|
|
2428
|
+
loop {
|
|
2429
|
+
if (steps >= cap + 64u) { exhausted = true; break; } // bounded: a spurious compare-exchange failure is legal (WGSL 17.8.5)
|
|
2430
|
+
steps = steps + 1u;
|
|
2431
|
+
let r = atomicCompareExchangeWeak(&hashRegion[base + 2u * slot], INVALID_INDEX, k);
|
|
2432
|
+
if (r.exchanged || r.old_value == k) { atomicAdd(&hashRegion[base + 2u * slot + 1u], q); break; }
|
|
2433
|
+
if (r.old_value == INVALID_INDEX) { continue; } // a spurious failure: the same slot again
|
|
2434
|
+
slot = (slot + 1u) % cap; // linear probing
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
if (exhausted) { atomicStore(&hashRegion[0], 1u); }
|
|
2438
|
+
storageBarrier();
|
|
2439
|
+
var bk = INVALID_INDEX;
|
|
2440
|
+
var bs = 0u;
|
|
2441
|
+
for (var j = lid; j < cap; j = j + WG) {
|
|
2442
|
+
let k = atomicLoad(&hashRegion[base + 2u * j]);
|
|
2443
|
+
if (k != INVALID_INDEX) {
|
|
2444
|
+
let sum = atomicLoad(&hashRegion[base + 2u * j + 1u]);
|
|
2445
|
+
if (better(sum, k, bs, bk)) { bk = k; bs = sum; }
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
shKey[lid] = bk;
|
|
2449
|
+
shSum[lid] = bs;
|
|
2450
|
+
workgroupBarrier();
|
|
2451
|
+
for (var s = WG / 2u; s > 0u; s = s / 2u) {
|
|
2452
|
+
if (lid < s && better(shSum[lid + s], shKey[lid + s], shSum[lid], shKey[lid])) {
|
|
2453
|
+
shKey[lid] = shKey[lid + s];
|
|
2454
|
+
shSum[lid] = shSum[lid + s];
|
|
2455
|
+
}
|
|
2456
|
+
workgroupBarrier();
|
|
2457
|
+
}
|
|
2458
|
+
if (lid == 0u) {
|
|
2459
|
+
bestKey[v] = shKey[0];
|
|
2460
|
+
bestScore[v] = f32(shSum[0]) * inverse_of(scale);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
@compute @workgroup_size(WG)
|
|
2465
|
+
fn group_by_key_row(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
2466
|
+
if (TIER == 0u) {
|
|
2467
|
+
let i = linear_id(wid, lid.x);
|
|
2468
|
+
if (i < P.count) { row_thread(rows[P.rowsBase + i]); }
|
|
2469
|
+
return; // TIER is an override: uniform
|
|
2470
|
+
}
|
|
2471
|
+
let g = group_id(wid);
|
|
2472
|
+
if (g >= P.count) { return; } // uniform: the workgroup id and a uniform
|
|
2473
|
+
row_hash(g, lid.x);
|
|
2474
|
+
}
|
|
2475
|
+
`
|
|
2476
|
+
);
|
|
1934
2477
|
const histogramWgsl = (
|
|
1935
2478
|
/* wgsl */
|
|
1936
2479
|
`
|
|
@@ -1965,6 +2508,51 @@ fn indirect_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
|
1965
2508
|
}
|
|
1966
2509
|
`
|
|
1967
2510
|
);
|
|
2511
|
+
const lpaStepWgsl = (
|
|
2512
|
+
/* wgsl */
|
|
2513
|
+
`
|
|
2514
|
+
var<workgroup> moved: atomic<u32>;
|
|
2515
|
+
|
|
2516
|
+
@compute @workgroup_size(WG)
|
|
2517
|
+
fn lpa_step(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
2518
|
+
if (lid.x == 0u) { atomicStore(&moved, 0u); }
|
|
2519
|
+
workgroupBarrier();
|
|
2520
|
+
let v = linear_id(wid, lid.x);
|
|
2521
|
+
if (v < P.n) {
|
|
2522
|
+
let cur = labelsIn[v];
|
|
2523
|
+
let best = bestKey[v];
|
|
2524
|
+
var next = cur;
|
|
2525
|
+
if (best != INVALID_INDEX && best != cur) {
|
|
2526
|
+
let down = best < cur;
|
|
2527
|
+
if (down == (P.direction == 0u)) { next = best; } // the alternating direction rule
|
|
2528
|
+
}
|
|
2529
|
+
labelsOut[v] = next;
|
|
2530
|
+
if (next != cur) { atomicAdd(&moved, 1u); }
|
|
2531
|
+
}
|
|
2532
|
+
workgroupBarrier(); // every lane, unconditionally
|
|
2533
|
+
if (lid.x == 0u) {
|
|
2534
|
+
let m = atomicLoad(&moved);
|
|
2535
|
+
if (m > 0u) { atomicAdd(&counters[P.counterIndex], m); } // one atomic per workgroup
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
`
|
|
2539
|
+
);
|
|
2540
|
+
const orientFlagsWgsl = (
|
|
2541
|
+
/* wgsl */
|
|
2542
|
+
`
|
|
2543
|
+
@compute @workgroup_size(WG)
|
|
2544
|
+
fn orient_flags(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
2545
|
+
let a = linear_id(wid, lid.x);
|
|
2546
|
+
if (a >= P.count) { return; } // no barrier follows
|
|
2547
|
+
let u = src[a];
|
|
2548
|
+
let v = colIdx[a];
|
|
2549
|
+
let du = rowPtr[u + 1u] - rowPtr[u];
|
|
2550
|
+
let dv = rowPtr[v + 1u] - rowPtr[v];
|
|
2551
|
+
let keep = dv > du || (dv == du && v > u); // (degree, id) of the target above the source's
|
|
2552
|
+
flags[a] = select(0u, 1u, keep);
|
|
2553
|
+
}
|
|
2554
|
+
`
|
|
2555
|
+
);
|
|
1968
2556
|
const prFinalizeWgsl = (
|
|
1969
2557
|
/* wgsl */
|
|
1970
2558
|
`
|
|
@@ -2150,6 +2738,20 @@ fn reduce(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) l
|
|
|
2150
2738
|
}
|
|
2151
2739
|
`
|
|
2152
2740
|
);
|
|
2741
|
+
const runFlagsWgsl = (
|
|
2742
|
+
/* wgsl */
|
|
2743
|
+
`
|
|
2744
|
+
@compute @workgroup_size(WG)
|
|
2745
|
+
fn run_flags(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
2746
|
+
let i = linear_id(wid, lid.x);
|
|
2747
|
+
if (i >= P.count) { return; } // no barrier follows
|
|
2748
|
+
let a = keysA[i];
|
|
2749
|
+
var first = a != INVALID_INDEX; // a dropped arc never opens a run
|
|
2750
|
+
if (i > 0u) { first = first && (a != keysA[i - 1u] || keysB[i] != keysB[i - 1u]); }
|
|
2751
|
+
flags[i] = select(0u, 1u, first);
|
|
2752
|
+
}
|
|
2753
|
+
`
|
|
2754
|
+
);
|
|
2153
2755
|
const scanAddWgsl = (
|
|
2154
2756
|
/* wgsl */
|
|
2155
2757
|
`
|
|
@@ -2470,6 +3072,63 @@ fn sssp_relax(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_i
|
|
|
2470
3072
|
}
|
|
2471
3073
|
`
|
|
2472
3074
|
);
|
|
3075
|
+
const triIntersectWgsl = (
|
|
3076
|
+
/* wgsl */
|
|
3077
|
+
`
|
|
3078
|
+
fn contains(lo0: u32, hi0: u32, x: u32) -> bool { // binary search of x in colIdx[lo0, hi0), sorted ascending
|
|
3079
|
+
var lo = lo0;
|
|
3080
|
+
var hi = hi0;
|
|
3081
|
+
loop {
|
|
3082
|
+
if (lo >= hi) { break; }
|
|
3083
|
+
let mid = lo + (hi - lo) / 2u;
|
|
3084
|
+
let y = colIdx[mid];
|
|
3085
|
+
if (y == x) { return true; }
|
|
3086
|
+
if (y < x) { lo = mid + 1u; } else { hi = mid; }
|
|
3087
|
+
}
|
|
3088
|
+
return false;
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
@compute @workgroup_size(WG)
|
|
3092
|
+
fn tri_intersect(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
|
|
3093
|
+
let a = linear_id(wid, lid.x);
|
|
3094
|
+
if (a >= P.count) { return; } // no barrier follows
|
|
3095
|
+
let u = src[a];
|
|
3096
|
+
let v = colIdx[a];
|
|
3097
|
+
let uLo = rowPtr[u];
|
|
3098
|
+
let uHi = rowPtr[u + 1u];
|
|
3099
|
+
let vLo = rowPtr[v];
|
|
3100
|
+
let vHi = rowPtr[v + 1u];
|
|
3101
|
+
let du = uHi - uLo;
|
|
3102
|
+
let dv = vHi - vLo;
|
|
3103
|
+
var search = SEARCH == 2u;
|
|
3104
|
+
if (SEARCH == 0u) { search = du / TRIANGLE_BINARY_SEARCH_RATIO > dv || dv / TRIANGLE_BINARY_SEARCH_RATIO > du; }
|
|
3105
|
+
var found = 0u;
|
|
3106
|
+
if (search) {
|
|
3107
|
+
var sLo = uLo;
|
|
3108
|
+
var sHi = uHi;
|
|
3109
|
+
var lLo = vLo;
|
|
3110
|
+
var lHi = vHi;
|
|
3111
|
+
if (du > dv) { sLo = vLo; sHi = vHi; lLo = uLo; lHi = uHi; }
|
|
3112
|
+
for (var k = sLo; k < sHi; k = k + 1u) {
|
|
3113
|
+
let w = colIdx[k];
|
|
3114
|
+
if (contains(lLo, lHi, w)) { atomicAdd(&counts[w], 1u); found = found + 1u; }
|
|
3115
|
+
}
|
|
3116
|
+
} else {
|
|
3117
|
+
var i = uLo;
|
|
3118
|
+
var j = vLo;
|
|
3119
|
+
loop {
|
|
3120
|
+
if (i >= uHi || j >= vHi) { break; }
|
|
3121
|
+
let x = colIdx[i];
|
|
3122
|
+
let y = colIdx[j];
|
|
3123
|
+
if (x == y) { atomicAdd(&counts[x], 1u); found = found + 1u; i = i + 1u; j = j + 1u; }
|
|
3124
|
+
else if (x < y) { i = i + 1u; }
|
|
3125
|
+
else { j = j + 1u; }
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
if (found > 0u) { atomicAdd(&counts[u], found); atomicAdd(&counts[v], found); }
|
|
3129
|
+
}
|
|
3130
|
+
`
|
|
3131
|
+
);
|
|
2473
3132
|
const wccCompressWgsl = (
|
|
2474
3133
|
/* wgsl */
|
|
2475
3134
|
`
|
|
@@ -2805,7 +3464,9 @@ const FRONTIER_COUNTERS = UniformBlock.define(
|
|
|
2805
3464
|
["thresholdBits", "u32"],
|
|
2806
3465
|
["deltaBits", "u32"],
|
|
2807
3466
|
["path", "u32"],
|
|
2808
|
-
["nextDegreeSum", "u32"]
|
|
3467
|
+
["nextDegreeSum", "u32"],
|
|
3468
|
+
["stackTop", "u32"],
|
|
3469
|
+
["sigmaOverflow", "u32"]
|
|
2809
3470
|
],
|
|
2810
3471
|
{ layout: "storage" }
|
|
2811
3472
|
);
|
|
@@ -2828,9 +3489,19 @@ const FRONTIER_PARAMS = UniformBlock.define("FrontierParams", [
|
|
|
2828
3489
|
["stride", "u32"],
|
|
2829
3490
|
["firstOfSubmit", "u32"],
|
|
2830
3491
|
["iteration", "u32"],
|
|
2831
|
-
["
|
|
3492
|
+
["perNode", "u32"],
|
|
2832
3493
|
["pad2", "u32"]
|
|
2833
3494
|
]);
|
|
3495
|
+
const BC_PARAMS = UniformBlock.define("BcParams", [
|
|
3496
|
+
["n", "u32"],
|
|
3497
|
+
["k", "u32"],
|
|
3498
|
+
["start", "u32"],
|
|
3499
|
+
["count", "u32"],
|
|
3500
|
+
["stride", "u32"],
|
|
3501
|
+
["role", "u32"],
|
|
3502
|
+
["pad0", "u32"],
|
|
3503
|
+
["pad1", "u32"]
|
|
3504
|
+
]);
|
|
2834
3505
|
const BF_PARAMS = UniformBlock.define("BfParams", [
|
|
2835
3506
|
["edgeCount", "u32"],
|
|
2836
3507
|
["stride", "u32"],
|
|
@@ -2847,6 +3518,30 @@ const BF_FLAGS = UniformBlock.define(
|
|
|
2847
3518
|
],
|
|
2848
3519
|
{ layout: "storage" }
|
|
2849
3520
|
);
|
|
3521
|
+
const APSP_PARAMS = UniformBlock.define("ApspParams", [
|
|
3522
|
+
["n", "u32"],
|
|
3523
|
+
["round", "u32"],
|
|
3524
|
+
["blocks", "u32"],
|
|
3525
|
+
["infBits", "u32"]
|
|
3526
|
+
]);
|
|
3527
|
+
const COO_PARAMS = UniformBlock.define("CooParams", [
|
|
3528
|
+
["count", "u32"],
|
|
3529
|
+
["pad0", "u32"],
|
|
3530
|
+
["pad1", "u32"],
|
|
3531
|
+
["pad2", "u32"]
|
|
3532
|
+
]);
|
|
3533
|
+
const GROUP_PARAMS = UniformBlock.define("GroupParams", [
|
|
3534
|
+
["rowsBase", "u32"],
|
|
3535
|
+
["basesBase", "u32"],
|
|
3536
|
+
["count", "u32"],
|
|
3537
|
+
["pad0", "u32"]
|
|
3538
|
+
]);
|
|
3539
|
+
const LPA_PARAMS = UniformBlock.define("LpaParams", [
|
|
3540
|
+
["n", "u32"],
|
|
3541
|
+
["direction", "u32"],
|
|
3542
|
+
["counterIndex", "u32"],
|
|
3543
|
+
["pad0", "u32"]
|
|
3544
|
+
]);
|
|
2850
3545
|
function decl(group, binding, name, kind, wgslType) {
|
|
2851
3546
|
return { group, binding, name, kind, wgslType };
|
|
2852
3547
|
}
|
|
@@ -3616,13 +4311,283 @@ const CLOSENESS_REDUCE = {
|
|
|
3616
4311
|
snippetSlots: [],
|
|
3617
4312
|
phase: "P8"
|
|
3618
4313
|
};
|
|
3619
|
-
const
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
4314
|
+
const BC_FINALIZE = {
|
|
4315
|
+
id: "bc-finalize",
|
|
4316
|
+
body: bcFinalizeWgsl,
|
|
4317
|
+
entryPoint: "bc_finalize",
|
|
4318
|
+
bindings: [
|
|
4319
|
+
decl(1, 0, "counters", "storage", "array<atomic<u32>>"),
|
|
4320
|
+
decl(1, 1, "ends", "storage", "array<u32>"),
|
|
4321
|
+
decl(1, 2, "S", "storage-ro", "array<u32>"),
|
|
4322
|
+
decl(1, 3, "depthK", "storage", "array<u32>"),
|
|
4323
|
+
decl(1, 4, "sigmaK", "storage", "array<u32>"),
|
|
4324
|
+
decl(2, 0, "P", "uniform", "BcParams")
|
|
4325
|
+
],
|
|
4326
|
+
overrideDecls: [],
|
|
4327
|
+
uniforms: [BC_PARAMS],
|
|
4328
|
+
needs: [],
|
|
4329
|
+
snippetSlots: [],
|
|
4330
|
+
phase: "P9"
|
|
4331
|
+
};
|
|
4332
|
+
const BC_FORWARD = {
|
|
4333
|
+
id: "bc-forward",
|
|
4334
|
+
body: bcForwardWgsl,
|
|
4335
|
+
entryPoint: "bc_forward",
|
|
4336
|
+
bindings: [
|
|
4337
|
+
decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
|
|
4338
|
+
decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
|
|
4339
|
+
decl(1, 2, "S", "storage", "array<u32>"),
|
|
4340
|
+
decl(1, 3, "ends", "storage-ro", "array<u32>"),
|
|
4341
|
+
decl(1, 4, "counters", "storage", "array<atomic<u32>>"),
|
|
4342
|
+
decl(1, 5, "depthK", "storage", "array<atomic<u32>>"),
|
|
4343
|
+
decl(1, 6, "sigmaK", "storage", "array<atomic<u32>>"),
|
|
4344
|
+
decl(2, 0, "P", "uniform", "BcParams")
|
|
4345
|
+
],
|
|
4346
|
+
overrideDecls: [],
|
|
4347
|
+
uniforms: [BC_PARAMS],
|
|
4348
|
+
needs: [],
|
|
4349
|
+
snippetSlots: [],
|
|
4350
|
+
phase: "P9"
|
|
4351
|
+
};
|
|
4352
|
+
const BC_BACKWARD = {
|
|
4353
|
+
id: "bc-backward",
|
|
4354
|
+
body: bcBackwardWgsl,
|
|
4355
|
+
entryPoint: "bc_backward",
|
|
4356
|
+
bindings: [
|
|
4357
|
+
decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
|
|
4358
|
+
decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
|
|
4359
|
+
decl(1, 2, "S", "storage-ro", "array<u32>"),
|
|
4360
|
+
decl(1, 3, "depthK", "storage-ro", "array<u32>"),
|
|
4361
|
+
decl(1, 4, "sigmaK", "storage-ro", "array<u32>"),
|
|
4362
|
+
decl(1, 5, "deltaK", "storage", "array<f32>"),
|
|
4363
|
+
decl(2, 0, "P", "uniform", "BcParams")
|
|
4364
|
+
],
|
|
4365
|
+
overrideDecls: [],
|
|
4366
|
+
uniforms: [BC_PARAMS],
|
|
4367
|
+
needs: [],
|
|
4368
|
+
snippetSlots: [],
|
|
4369
|
+
phase: "P9"
|
|
4370
|
+
};
|
|
4371
|
+
const BC_GATHER = {
|
|
4372
|
+
id: "bc-gather",
|
|
4373
|
+
body: bcGatherWgsl,
|
|
4374
|
+
entryPoint: "bc_gather",
|
|
4375
|
+
bindings: [
|
|
4376
|
+
decl(1, 0, "deltaK", "storage-ro", "array<f32>"),
|
|
4377
|
+
decl(1, 1, "bc", "storage", "array<f32>"),
|
|
4378
|
+
decl(2, 0, "P", "uniform", "BcParams")
|
|
4379
|
+
],
|
|
4380
|
+
overrideDecls: [],
|
|
4381
|
+
uniforms: [BC_PARAMS],
|
|
4382
|
+
needs: [],
|
|
4383
|
+
snippetSlots: [],
|
|
4384
|
+
phase: "P9"
|
|
4385
|
+
};
|
|
4386
|
+
const BC_EDGE_GATHER = {
|
|
4387
|
+
id: "bc-edge-gather",
|
|
4388
|
+
body: bcEdgeGatherWgsl,
|
|
4389
|
+
entryPoint: "bc_edge_gather",
|
|
4390
|
+
bindings: [
|
|
4391
|
+
decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
|
|
4392
|
+
decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
|
|
4393
|
+
decl(1, 2, "depthK", "storage-ro", "array<u32>"),
|
|
4394
|
+
decl(1, 3, "sigmaK", "storage-ro", "array<u32>"),
|
|
4395
|
+
decl(1, 4, "deltaK", "storage-ro", "array<f32>"),
|
|
4396
|
+
decl(1, 5, "arcScores", "storage", "array<f32>"),
|
|
4397
|
+
decl(2, 0, "P", "uniform", "BcParams")
|
|
4398
|
+
],
|
|
4399
|
+
overrideDecls: [],
|
|
4400
|
+
uniforms: [BC_PARAMS],
|
|
4401
|
+
needs: [],
|
|
4402
|
+
snippetSlots: [],
|
|
4403
|
+
phase: "P9"
|
|
4404
|
+
};
|
|
4405
|
+
const BC_FORWARD_EDGE = {
|
|
4406
|
+
id: "bc-forward-edge",
|
|
4407
|
+
body: bcForwardEdgeWgsl,
|
|
4408
|
+
entryPoint: "bc_forward_edge",
|
|
4409
|
+
bindings: [
|
|
4410
|
+
decl(1, 0, "edgeSrc", "storage-ro", "array<u32>"),
|
|
4411
|
+
decl(1, 1, "edgeDst", "storage-ro", "array<u32>"),
|
|
4412
|
+
decl(1, 2, "S", "storage", "array<u32>"),
|
|
4413
|
+
decl(1, 3, "ends", "storage-ro", "array<u32>"),
|
|
4414
|
+
decl(1, 4, "counters", "storage", "array<atomic<u32>>"),
|
|
4415
|
+
decl(1, 5, "depthK", "storage", "array<atomic<u32>>"),
|
|
4416
|
+
decl(1, 6, "sigmaK", "storage", "array<atomic<u32>>"),
|
|
4417
|
+
decl(2, 0, "P", "uniform", "BcParams")
|
|
4418
|
+
],
|
|
4419
|
+
overrideDecls: [{ name: "UNDIRECTED", type: "bool", default: false }],
|
|
4420
|
+
uniforms: [BC_PARAMS],
|
|
4421
|
+
needs: [],
|
|
4422
|
+
snippetSlots: [],
|
|
4423
|
+
phase: "P9"
|
|
4424
|
+
};
|
|
4425
|
+
const APSP_INIT = {
|
|
4426
|
+
id: "apsp-init",
|
|
4427
|
+
body: apspInitWgsl,
|
|
4428
|
+
entryPoint: "apsp_init",
|
|
4429
|
+
bindings: GRAPH_SLOTS.concat(decl(1, 0, "dist", "storage", "array<f32>"), decl(2, 0, "P", "uniform", "ApspParams")),
|
|
4430
|
+
overrideDecls: [],
|
|
4431
|
+
uniforms: [APSP_PARAMS],
|
|
4432
|
+
needs: [],
|
|
4433
|
+
snippetSlots: [],
|
|
4434
|
+
phase: "P9"
|
|
4435
|
+
};
|
|
4436
|
+
const APSP_FW = {
|
|
4437
|
+
id: "apsp-fw",
|
|
4438
|
+
body: apspFwWgsl,
|
|
4439
|
+
entryPoint: "apsp_fw",
|
|
4440
|
+
bindings: [decl(1, 0, "dist", "storage", "array<f32>"), decl(2, 0, "P", "uniform", "ApspParams")],
|
|
4441
|
+
overrideDecls: [{ name: "PHASE", type: "u32", default: 0 }],
|
|
4442
|
+
uniforms: [APSP_PARAMS],
|
|
4443
|
+
needs: [],
|
|
4444
|
+
snippetSlots: [],
|
|
4445
|
+
phase: "P9"
|
|
4446
|
+
};
|
|
4447
|
+
const COO_EMIT = {
|
|
4448
|
+
id: "coo-emit",
|
|
4449
|
+
body: cooEmitWgsl,
|
|
4450
|
+
entryPoint: "coo_emit",
|
|
4451
|
+
bindings: [
|
|
4452
|
+
decl(1, 0, "edgeSrc", "storage-ro", "array<u32>"),
|
|
4453
|
+
decl(1, 1, "edgeDst", "storage-ro", "array<u32>"),
|
|
4454
|
+
decl(1, 2, "edgeWeight", "storage-ro", "array<f32>"),
|
|
4455
|
+
decl(1, 3, "order", "storage-ro", "array<u32>"),
|
|
4456
|
+
decl(1, 4, "outSrc", "storage", "array<u32>"),
|
|
4457
|
+
decl(1, 5, "outDst", "storage", "array<u32>"),
|
|
4458
|
+
decl(1, 6, "outWeight", "storage", "array<f32>"),
|
|
4459
|
+
decl(2, 0, "P", "uniform", "CooParams")
|
|
4460
|
+
],
|
|
4461
|
+
overrideDecls: [
|
|
4462
|
+
{ name: "INDEXED", type: "bool", default: false },
|
|
4463
|
+
{ name: "WEIGHTED", type: "bool", default: false }
|
|
4464
|
+
],
|
|
4465
|
+
uniforms: [COO_PARAMS],
|
|
4466
|
+
needs: [],
|
|
4467
|
+
snippetSlots: [],
|
|
4468
|
+
phase: "P11"
|
|
4469
|
+
};
|
|
4470
|
+
const RUN_FLAGS = {
|
|
4471
|
+
id: "run-flags",
|
|
4472
|
+
body: runFlagsWgsl,
|
|
4473
|
+
entryPoint: "run_flags",
|
|
4474
|
+
bindings: [
|
|
4475
|
+
decl(1, 0, "keysA", "storage-ro", "array<u32>"),
|
|
4476
|
+
decl(1, 1, "keysB", "storage-ro", "array<u32>"),
|
|
4477
|
+
decl(1, 2, "flags", "storage", "array<u32>"),
|
|
4478
|
+
decl(2, 0, "P", "uniform", "CooParams")
|
|
4479
|
+
],
|
|
4480
|
+
overrideDecls: [],
|
|
4481
|
+
uniforms: [COO_PARAMS],
|
|
4482
|
+
needs: [],
|
|
4483
|
+
snippetSlots: [],
|
|
4484
|
+
phase: "P11"
|
|
4485
|
+
};
|
|
4486
|
+
const COO_SCATTER = {
|
|
4487
|
+
id: "coo-scatter",
|
|
4488
|
+
body: cooScatterWgsl,
|
|
4489
|
+
entryPoint: "coo_scatter",
|
|
4490
|
+
bindings: [
|
|
4491
|
+
decl(1, 0, "src", "storage-ro", "array<u32>"),
|
|
4492
|
+
decl(1, 1, "dst", "storage-ro", "array<u32>"),
|
|
4493
|
+
decl(1, 2, "weight", "storage-ro", "array<f32>"),
|
|
4494
|
+
decl(1, 3, "rowPtr", "storage-ro", "array<u32>"),
|
|
4495
|
+
decl(1, 4, "cursors", "storage", "array<atomic<u32>>"),
|
|
4496
|
+
decl(1, 5, "colIdx", "storage", "array<u32>"),
|
|
4497
|
+
decl(1, 6, "outWeight", "storage", "array<f32>"),
|
|
4498
|
+
decl(2, 0, "P", "uniform", "CooParams")
|
|
4499
|
+
],
|
|
4500
|
+
overrideDecls: [
|
|
4501
|
+
{ name: "SORTED_INPUT", type: "bool", default: false },
|
|
4502
|
+
{ name: "WEIGHTED", type: "bool", default: false }
|
|
4503
|
+
],
|
|
4504
|
+
uniforms: [COO_PARAMS],
|
|
4505
|
+
needs: [],
|
|
4506
|
+
snippetSlots: [],
|
|
4507
|
+
phase: "P11"
|
|
4508
|
+
};
|
|
4509
|
+
const ORIENT_FLAGS = {
|
|
4510
|
+
id: "orient-flags",
|
|
4511
|
+
body: orientFlagsWgsl,
|
|
4512
|
+
entryPoint: "orient_flags",
|
|
4513
|
+
bindings: [
|
|
4514
|
+
decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
|
|
4515
|
+
decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
|
|
4516
|
+
decl(1, 2, "src", "storage-ro", "array<u32>"),
|
|
4517
|
+
decl(1, 3, "flags", "storage", "array<u32>"),
|
|
4518
|
+
decl(2, 0, "P", "uniform", "CooParams")
|
|
4519
|
+
],
|
|
4520
|
+
overrideDecls: [],
|
|
4521
|
+
uniforms: [COO_PARAMS],
|
|
4522
|
+
needs: [],
|
|
4523
|
+
snippetSlots: [],
|
|
4524
|
+
phase: "P11"
|
|
4525
|
+
};
|
|
4526
|
+
const TRI_INTERSECT = {
|
|
4527
|
+
id: "tri-intersect",
|
|
4528
|
+
body: triIntersectWgsl,
|
|
4529
|
+
entryPoint: "tri_intersect",
|
|
4530
|
+
bindings: [
|
|
4531
|
+
decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
|
|
4532
|
+
decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
|
|
4533
|
+
decl(1, 2, "src", "storage-ro", "array<u32>"),
|
|
4534
|
+
decl(1, 3, "counts", "storage", "array<atomic<u32>>"),
|
|
4535
|
+
decl(2, 0, "P", "uniform", "CooParams")
|
|
4536
|
+
],
|
|
4537
|
+
overrideDecls: [{ name: "SEARCH", type: "u32", default: 0 }],
|
|
4538
|
+
uniforms: [COO_PARAMS],
|
|
4539
|
+
needs: [],
|
|
4540
|
+
snippetSlots: [],
|
|
4541
|
+
phase: "P11"
|
|
4542
|
+
};
|
|
4543
|
+
const GROUP_BY_KEY_ROW = {
|
|
4544
|
+
id: "group-by-key-row",
|
|
4545
|
+
body: groupByKeyRowWgsl,
|
|
4546
|
+
entryPoint: "group_by_key_row",
|
|
4547
|
+
bindings: [
|
|
4548
|
+
decl(1, 0, "rowPtr", "storage-ro", "array<u32>"),
|
|
4549
|
+
decl(1, 1, "colIdx", "storage-ro", "array<u32>"),
|
|
4550
|
+
decl(1, 2, "weights", "storage-ro", "array<f32>"),
|
|
4551
|
+
decl(1, 3, "keyIn", "storage-ro", "array<u32>"),
|
|
4552
|
+
decl(1, 4, "rows", "storage-ro", "array<u32>"),
|
|
4553
|
+
decl(1, 5, "hashRegion", "storage", "array<atomic<u32>>"),
|
|
4554
|
+
decl(1, 6, "bestKey", "storage", "array<u32>"),
|
|
4555
|
+
decl(1, 7, "bestScore", "storage", "array<f32>"),
|
|
4556
|
+
decl(2, 0, "P", "uniform", "GroupParams")
|
|
4557
|
+
],
|
|
4558
|
+
overrideDecls: [
|
|
4559
|
+
{ name: "TIER", type: "u32", default: 0 },
|
|
4560
|
+
{ name: "WEIGHTED", type: "bool", default: false }
|
|
4561
|
+
],
|
|
4562
|
+
uniforms: [GROUP_PARAMS],
|
|
4563
|
+
needs: [],
|
|
4564
|
+
snippetSlots: [],
|
|
4565
|
+
phase: "P11"
|
|
4566
|
+
};
|
|
4567
|
+
const LPA_STEP = {
|
|
4568
|
+
id: "lpa-step",
|
|
4569
|
+
body: lpaStepWgsl,
|
|
4570
|
+
entryPoint: "lpa_step",
|
|
4571
|
+
bindings: [
|
|
4572
|
+
decl(1, 0, "labelsIn", "storage-ro", "array<u32>"),
|
|
4573
|
+
decl(1, 1, "bestKey", "storage-ro", "array<u32>"),
|
|
4574
|
+
decl(1, 2, "labelsOut", "storage", "array<u32>"),
|
|
4575
|
+
decl(1, 3, "counters", "storage", "array<atomic<u32>>"),
|
|
4576
|
+
decl(2, 0, "P", "uniform", "LpaParams")
|
|
4577
|
+
],
|
|
4578
|
+
overrideDecls: [],
|
|
4579
|
+
uniforms: [LPA_PARAMS],
|
|
4580
|
+
needs: [],
|
|
4581
|
+
snippetSlots: [],
|
|
4582
|
+
phase: "P11"
|
|
4583
|
+
};
|
|
4584
|
+
const REGISTRY = Object.freeze({
|
|
4585
|
+
degree: DEGREE,
|
|
4586
|
+
reduce: REDUCE,
|
|
4587
|
+
fill: FILL,
|
|
4588
|
+
"segmented-reduce": SEGMENTED_REDUCE,
|
|
4589
|
+
"fa2-stats-finalize": FA2_STATS_FINALIZE,
|
|
4590
|
+
"fa2-attraction": FA2_ATTRACTION,
|
|
3626
4591
|
"fa2-repulsion-exact": FA2_REPULSION_EXACT,
|
|
3627
4592
|
"fa2-speed-finalize": FA2_SPEED_FINALIZE,
|
|
3628
4593
|
"fa2-integrate": FA2_INTEGRATE,
|
|
@@ -3662,7 +4627,22 @@ const REGISTRY = Object.freeze({
|
|
|
3662
4627
|
"sssp-relax": SSSP_RELAX,
|
|
3663
4628
|
"bf-relax": BF_RELAX,
|
|
3664
4629
|
"closeness-sweep": CLOSENESS_SWEEP,
|
|
3665
|
-
"closeness-reduce": CLOSENESS_REDUCE
|
|
4630
|
+
"closeness-reduce": CLOSENESS_REDUCE,
|
|
4631
|
+
"bc-finalize": BC_FINALIZE,
|
|
4632
|
+
"bc-forward": BC_FORWARD,
|
|
4633
|
+
"bc-backward": BC_BACKWARD,
|
|
4634
|
+
"bc-gather": BC_GATHER,
|
|
4635
|
+
"bc-edge-gather": BC_EDGE_GATHER,
|
|
4636
|
+
"bc-forward-edge": BC_FORWARD_EDGE,
|
|
4637
|
+
"apsp-init": APSP_INIT,
|
|
4638
|
+
"apsp-fw": APSP_FW,
|
|
4639
|
+
"coo-emit": COO_EMIT,
|
|
4640
|
+
"run-flags": RUN_FLAGS,
|
|
4641
|
+
"coo-scatter": COO_SCATTER,
|
|
4642
|
+
"orient-flags": ORIENT_FLAGS,
|
|
4643
|
+
"tri-intersect": TRI_INTERSECT,
|
|
4644
|
+
"group-by-key-row": GROUP_BY_KEY_ROW,
|
|
4645
|
+
"lpa-step": LPA_STEP
|
|
3666
4646
|
});
|
|
3667
4647
|
const bodyOverrides = /* @__PURE__ */ new Map();
|
|
3668
4648
|
function entryOf(id) {
|
|
@@ -3821,7 +4801,7 @@ class ScanPlannerImpl {
|
|
|
3821
4801
|
}
|
|
3822
4802
|
const POISON = 3735928559;
|
|
3823
4803
|
const CHECK_BLOCKS = 32;
|
|
3824
|
-
const RING_SLOTS$
|
|
4804
|
+
const RING_SLOTS$9 = 8;
|
|
3825
4805
|
const checked = /* @__PURE__ */ new WeakMap();
|
|
3826
4806
|
function inputAt(i) {
|
|
3827
4807
|
return i + 1;
|
|
@@ -3849,7 +4829,7 @@ async function runCheck(ctx) {
|
|
|
3849
4829
|
const count = CHECK_BLOCKS * wg + 1;
|
|
3850
4830
|
const bytes = 4 * count;
|
|
3851
4831
|
const lease = ctx.pool.lease();
|
|
3852
|
-
const ring = new UniformRing(ctx.device, ctx.allocator, RING_SLOTS$
|
|
4832
|
+
const ring = new UniformRing(ctx.device, ctx.allocator, RING_SLOTS$9, "device-check/ring");
|
|
3853
4833
|
try {
|
|
3854
4834
|
const scope = {
|
|
3855
4835
|
device: ctx.device,
|
|
@@ -4066,7 +5046,7 @@ function coreOfView(v, arcCount) {
|
|
|
4066
5046
|
hasWeights: weights !== null
|
|
4067
5047
|
});
|
|
4068
5048
|
}
|
|
4069
|
-
function checkDest$
|
|
5049
|
+
function checkDest$7(dest, n) {
|
|
4070
5050
|
if (dest === void 0) {
|
|
4071
5051
|
return null;
|
|
4072
5052
|
}
|
|
@@ -4095,7 +5075,7 @@ async function degree(ctx, s, options) {
|
|
|
4095
5075
|
ctx.assertReady();
|
|
4096
5076
|
await assertDeviceComputes(ctx);
|
|
4097
5077
|
const n = s.nodeCount;
|
|
4098
|
-
const dest = checkDest$
|
|
5078
|
+
const dest = checkDest$7(options?.dest, n);
|
|
4099
5079
|
if (options?.signal?.aborted) {
|
|
4100
5080
|
throw new WebGpuGraphError("E_ABORTED", "degree: the signal was aborted before any work started", {});
|
|
4101
5081
|
}
|
|
@@ -4516,13 +5496,13 @@ function algorithmScope(ctx, label, slots) {
|
|
|
4516
5496
|
ringOverruns: () => ring.overruns
|
|
4517
5497
|
};
|
|
4518
5498
|
}
|
|
4519
|
-
const ALGORITHM$
|
|
5499
|
+
const ALGORITHM$8 = "connectedComponents";
|
|
4520
5500
|
const ROUNDS_PER_BATCH$1 = 4;
|
|
4521
5501
|
const MAX_WCC_ROUNDS = 64;
|
|
4522
5502
|
const SAMPLE_SIZE = 1024;
|
|
4523
5503
|
const MAX_STEPS = 1024;
|
|
4524
|
-
const RING_SLOTS$
|
|
4525
|
-
function checkDest$
|
|
5504
|
+
const RING_SLOTS$8 = 2 * ROUNDS_PER_BATCH$1;
|
|
5505
|
+
function checkDest$6(dest, n) {
|
|
4526
5506
|
if (dest === void 0) {
|
|
4527
5507
|
return null;
|
|
4528
5508
|
}
|
|
@@ -4531,7 +5511,7 @@ function checkDest$4(dest, n) {
|
|
|
4531
5511
|
}
|
|
4532
5512
|
throw new WebGpuGraphError(
|
|
4533
5513
|
"E_INVALID_ARGUMENT",
|
|
4534
|
-
`${ALGORITHM$
|
|
5514
|
+
`${ALGORITHM$8}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
4535
5515
|
{
|
|
4536
5516
|
argument: "dest",
|
|
4537
5517
|
value: `${dest.constructor.name}(${dest.length})`,
|
|
@@ -4541,7 +5521,7 @@ function checkDest$4(dest, n) {
|
|
|
4541
5521
|
}
|
|
4542
5522
|
function coreOf$2(ctx, s) {
|
|
4543
5523
|
const core = ctx.residency.core(s);
|
|
4544
|
-
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$
|
|
5524
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$8);
|
|
4545
5525
|
return core;
|
|
4546
5526
|
}
|
|
4547
5527
|
function bindingOf$3(buffer, size) {
|
|
@@ -4600,8 +5580,8 @@ function checkLabels(raw) {
|
|
|
4600
5580
|
for (let v = 0; v < n; v++) {
|
|
4601
5581
|
const label = raw[v];
|
|
4602
5582
|
if (label >= n) {
|
|
4603
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
4604
|
-
label: `${ALGORITHM$
|
|
5583
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$8}: labels[${v}] = ${label} is not a node index`, {
|
|
5584
|
+
label: `${ALGORITHM$8}/labels`,
|
|
4605
5585
|
message: `the device produced a label outside [0, ${n})`
|
|
4606
5586
|
});
|
|
4607
5587
|
}
|
|
@@ -4617,9 +5597,9 @@ async function connectedComponents(ctx, s, options) {
|
|
|
4617
5597
|
await assertDeviceComputes(ctx);
|
|
4618
5598
|
const n = s.nodeCount;
|
|
4619
5599
|
const renumber = options?.renumber !== false;
|
|
4620
|
-
const dest = checkDest$
|
|
5600
|
+
const dest = checkDest$6(options?.dest, n);
|
|
4621
5601
|
if (options?.signal?.aborted) {
|
|
4622
|
-
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$
|
|
5602
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$8}: the signal was aborted before any work started`, {});
|
|
4623
5603
|
}
|
|
4624
5604
|
if (n === 0) {
|
|
4625
5605
|
options?.onProgress?.(1, 1);
|
|
@@ -4636,7 +5616,7 @@ async function connectedComponents(ctx, s, options) {
|
|
|
4636
5616
|
}
|
|
4637
5617
|
const edges = ctx.residency.view(s, "edgeList");
|
|
4638
5618
|
const edgeCount = edges.scalars.edgeCount[0];
|
|
4639
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
5619
|
+
const scope = algorithmScope(ctx, ALGORITHM$8, RING_SLOTS$8);
|
|
4640
5620
|
try {
|
|
4641
5621
|
const compBytes = 4 * (n + 1);
|
|
4642
5622
|
const comp = scope.scratch(compBytes, "comp");
|
|
@@ -4660,12 +5640,12 @@ async function connectedComponents(ctx, s, options) {
|
|
|
4660
5640
|
const params = wccParams({ items: n, stride: rowPlan.stride ?? n, r: 0, giant: U32_MAX$2 });
|
|
4661
5641
|
compress.dispatch(pass2, compress.bind({ comp: compBinding, P: params.binding }), rowPlan, [params.offset]);
|
|
4662
5642
|
};
|
|
4663
|
-
const
|
|
5643
|
+
const submit2 = (batch) => {
|
|
4664
5644
|
scope.flush();
|
|
4665
5645
|
return batch.submit();
|
|
4666
5646
|
};
|
|
4667
5647
|
queue.writeBuffer(comp, 4 * flagIndex, zero);
|
|
4668
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
5648
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$8}/setup`);
|
|
4669
5649
|
let pass = setup.pass("sample-rounds");
|
|
4670
5650
|
const fillParams = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 1, pad0: 0 });
|
|
4671
5651
|
fill.dispatch(
|
|
@@ -4682,9 +5662,9 @@ async function connectedComponents(ctx, s, options) {
|
|
|
4682
5662
|
}
|
|
4683
5663
|
recordCompress(pass);
|
|
4684
5664
|
setup.endPass();
|
|
4685
|
-
await
|
|
5665
|
+
await submit2(setup).readback;
|
|
4686
5666
|
ctx.assertReady();
|
|
4687
|
-
const sampler = new CommandBatch(ctx, `${ALGORITHM$
|
|
5667
|
+
const sampler = new CommandBatch(ctx, `${ALGORITHM$8}/sample`);
|
|
4688
5668
|
pass = sampler.pass("sample");
|
|
4689
5669
|
const sampleParams = wccParams({ items, stride: 0, r: 0, giant: U32_MAX$2 });
|
|
4690
5670
|
sample.dispatch(
|
|
@@ -4695,14 +5675,14 @@ async function connectedComponents(ctx, s, options) {
|
|
|
4695
5675
|
);
|
|
4696
5676
|
sampler.endPass();
|
|
4697
5677
|
const histRequest = sampler.readback(hist, 0, 4 * items);
|
|
4698
|
-
const histBytes = await
|
|
5678
|
+
const histBytes = await submit2(sampler).readback;
|
|
4699
5679
|
ctx.assertReady();
|
|
4700
5680
|
const giant = modeOf(new Uint32Array(histBytes, histRequest.offset, items));
|
|
4701
5681
|
const edgeBindings = { edgeSrc: edges.bindings.src, edgeDst: edges.bindings.dst, comp: compBinding };
|
|
4702
5682
|
let rounds = 0;
|
|
4703
5683
|
for (; ; ) {
|
|
4704
5684
|
queue.writeBuffer(comp, 4 * flagIndex, zero);
|
|
4705
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
5685
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$8}/rounds`);
|
|
4706
5686
|
pass = batch.pass("edge-rounds");
|
|
4707
5687
|
for (let i = 0; i < ROUNDS_PER_BATCH$1; i++) {
|
|
4708
5688
|
const params = wccParams({ items: edgeCount, stride: edgePlan.stride ?? edgeCount, r: 0, giant });
|
|
@@ -4712,12 +5692,12 @@ async function connectedComponents(ctx, s, options) {
|
|
|
4712
5692
|
}
|
|
4713
5693
|
batch.endPass();
|
|
4714
5694
|
const flagRequest = batch.readback(comp, 4 * flagIndex, 4);
|
|
4715
|
-
const submitted =
|
|
5695
|
+
const submitted = submit2(batch);
|
|
4716
5696
|
const back = await submitted.readback;
|
|
4717
5697
|
rounds += ROUNDS_PER_BATCH$1;
|
|
4718
5698
|
ctx.assertReady();
|
|
4719
5699
|
if (options?.signal?.aborted) {
|
|
4720
|
-
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$
|
|
5700
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$8}: the signal was aborted`, {
|
|
4721
5701
|
batchId: submitted.id
|
|
4722
5702
|
});
|
|
4723
5703
|
}
|
|
@@ -4727,15 +5707,15 @@ async function connectedComponents(ctx, s, options) {
|
|
|
4727
5707
|
if (rounds >= MAX_WCC_ROUNDS) {
|
|
4728
5708
|
throw new WebGpuGraphError(
|
|
4729
5709
|
"E_VALIDATION",
|
|
4730
|
-
`${ALGORITHM$
|
|
4731
|
-
{ label: ALGORITHM$
|
|
5710
|
+
`${ALGORITHM$8}: the changed flag never settled in ${MAX_WCC_ROUNDS} rounds`,
|
|
5711
|
+
{ label: ALGORITHM$8, message: `the changed flag never settled in ${MAX_WCC_ROUNDS} rounds` }
|
|
4732
5712
|
);
|
|
4733
5713
|
}
|
|
4734
5714
|
}
|
|
4735
|
-
const final = new CommandBatch(ctx, `${ALGORITHM$
|
|
5715
|
+
const final = new CommandBatch(ctx, `${ALGORITHM$8}/final`);
|
|
4736
5716
|
recordCompress(final.pass("compress"));
|
|
4737
5717
|
final.endPass();
|
|
4738
|
-
await
|
|
5718
|
+
await submit2(final).readback;
|
|
4739
5719
|
ctx.assertReady();
|
|
4740
5720
|
const raw = !renumber && dest !== null ? dest : new Uint32Array(n);
|
|
4741
5721
|
await ctx.readback.read(comp, 4 * n, raw);
|
|
@@ -4974,8 +5954,20 @@ class TieredPlanner {
|
|
|
4974
5954
|
async function prepareSegmentedReduce(scope, core, options) {
|
|
4975
5955
|
const op = opCode(options.op);
|
|
4976
5956
|
validateValueSnippet(options.valueSnippet);
|
|
4977
|
-
const
|
|
4978
|
-
const
|
|
5957
|
+
const { tiers } = options;
|
|
5958
|
+
const perm = tiers?.perm ?? null;
|
|
5959
|
+
if (tiers !== null && scope.workgroupSize < MID_TIER_LANES) {
|
|
5960
|
+
throw new WebGpuGraphError(
|
|
5961
|
+
"E_UNSUPPORTED",
|
|
5962
|
+
`segmentedReduce: the mid tier folds ${MID_TIER_LANES} lanes per row, more than the workgroup size ${scope.workgroupSize}`,
|
|
5963
|
+
{ feature: "segmentedReduce.tiers" }
|
|
5964
|
+
);
|
|
5965
|
+
}
|
|
5966
|
+
const ranges = tiers === null ? [{ tier: 0, start: 0, end: rowCountOf(core, "segmentedReduce") }] : [
|
|
5967
|
+
{ tier: 2, start: 0, end: tiers.segmentOffsets[1] },
|
|
5968
|
+
{ tier: 1, start: tiers.segmentOffsets[1], end: tiers.segmentOffsets[2] },
|
|
5969
|
+
{ tier: 0, start: tiers.segmentOffsets[2], end: tiers.segmentOffsets[4] }
|
|
5970
|
+
];
|
|
4979
5971
|
const compiled = [];
|
|
4980
5972
|
for (const range of ranges) {
|
|
4981
5973
|
if (range.tier !== 0 && range.end <= range.start) {
|
|
@@ -5098,8 +6090,8 @@ async function prepareSpmvPull(scope, rev, options) {
|
|
|
5098
6090
|
return new SpmvPullPlannerImpl(scope, compiled, perm, options.weights);
|
|
5099
6091
|
}
|
|
5100
6092
|
const PR_BATCH = 8;
|
|
5101
|
-
const RING_SLOTS$
|
|
5102
|
-
function checkDest$
|
|
6093
|
+
const RING_SLOTS$7 = 2 * PR_BATCH + 2;
|
|
6094
|
+
function checkDest$5(dest, n, algorithm) {
|
|
5103
6095
|
if (dest === void 0) {
|
|
5104
6096
|
return null;
|
|
5105
6097
|
}
|
|
@@ -5146,7 +6138,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
5146
6138
|
expected: "a positive integer"
|
|
5147
6139
|
});
|
|
5148
6140
|
}
|
|
5149
|
-
const dest = checkDest$
|
|
6141
|
+
const dest = checkDest$5(options?.dest, n, algorithm);
|
|
5150
6142
|
if (options?.signal?.aborted) {
|
|
5151
6143
|
throw aborted$3(algorithm);
|
|
5152
6144
|
}
|
|
@@ -5176,7 +6168,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
5176
6168
|
const weights = useWeights ? void 0 : null;
|
|
5177
6169
|
const weightedCore = useWeights ? core : { ...core, weights: null, hasWeights: false };
|
|
5178
6170
|
const weightedRev = useWeights ? rev : { ...rev, weights: null, hasWeights: false };
|
|
5179
|
-
const scope = algorithmScope(ctx, algorithm, RING_SLOTS$
|
|
6171
|
+
const scope = algorithmScope(ctx, algorithm, RING_SLOTS$7);
|
|
5180
6172
|
let uploaded = null;
|
|
5181
6173
|
try {
|
|
5182
6174
|
const bytes = 4 * n;
|
|
@@ -5193,7 +6185,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
5193
6185
|
uploaded = ctx.residency.array(personalization, `${algorithm}/personalization`);
|
|
5194
6186
|
}
|
|
5195
6187
|
await ctx.allocator.check();
|
|
5196
|
-
const
|
|
6188
|
+
const normaliser2 = await prepareSegmentedReduce(scope, weightedCore, {
|
|
5197
6189
|
op: "sum",
|
|
5198
6190
|
valueSnippet: "v = weight;",
|
|
5199
6191
|
tiers: null
|
|
@@ -5226,7 +6218,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
5226
6218
|
const batch = new CommandBatch(ctx, algorithm);
|
|
5227
6219
|
let pass = batch.pass("iterations");
|
|
5228
6220
|
if (iterationsRun === 0) {
|
|
5229
|
-
|
|
6221
|
+
normaliser2.record(pass, weightedCore, outWeightSumBinding);
|
|
5230
6222
|
}
|
|
5231
6223
|
const last = iterationsRun + k === maxIterations;
|
|
5232
6224
|
for (let i = 0; i < k + (last ? 1 : 0); i++) {
|
|
@@ -5339,8 +6331,8 @@ async function personalizedPageRank(ctx, s, personalization, options) {
|
|
|
5339
6331
|
return run(ctx, s, normalised2, options, "personalizedPageRank");
|
|
5340
6332
|
}
|
|
5341
6333
|
const BATCH = 8;
|
|
5342
|
-
const RING_SLOTS$
|
|
5343
|
-
function checkDest$
|
|
6334
|
+
const RING_SLOTS$6 = 4 * BATCH + 8;
|
|
6335
|
+
function checkDest$4(dest, n, algorithm) {
|
|
5344
6336
|
if (dest === void 0) {
|
|
5345
6337
|
return null;
|
|
5346
6338
|
}
|
|
@@ -5378,7 +6370,7 @@ function whole(buffer, size) {
|
|
|
5378
6370
|
}
|
|
5379
6371
|
async function runPowerIteration(ctx, n, config) {
|
|
5380
6372
|
await assertDeviceComputes(ctx);
|
|
5381
|
-
const scope = algorithmScope(ctx, config.label, RING_SLOTS$
|
|
6373
|
+
const scope = algorithmScope(ctx, config.label, RING_SLOTS$6);
|
|
5382
6374
|
try {
|
|
5383
6375
|
const bytes = 4 * n;
|
|
5384
6376
|
const ring = (config.alternate === null ? ["rankA", "rankB"] : ["rankA", "rankB", "rankC"]).map(
|
|
@@ -5490,7 +6482,7 @@ function resolve(ctx, s, options, algorithm) {
|
|
|
5490
6482
|
expected: "a positive integer"
|
|
5491
6483
|
});
|
|
5492
6484
|
}
|
|
5493
|
-
const dest = checkDest$
|
|
6485
|
+
const dest = checkDest$4(options?.dest, n, algorithm);
|
|
5494
6486
|
if (options?.signal?.aborted) {
|
|
5495
6487
|
throw aborted$2(algorithm);
|
|
5496
6488
|
}
|
|
@@ -5646,7 +6638,7 @@ function checkCount(what, count) {
|
|
|
5646
6638
|
});
|
|
5647
6639
|
}
|
|
5648
6640
|
}
|
|
5649
|
-
function checkWords$
|
|
6641
|
+
function checkWords$3(what, name, binding, words) {
|
|
5650
6642
|
if (binding.size < 4 * words) {
|
|
5651
6643
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} is smaller than 4 x ${words} bytes`, {
|
|
5652
6644
|
argument: name,
|
|
@@ -5666,15 +6658,15 @@ function checkWordIndex(what, name, index, block) {
|
|
|
5666
6658
|
}
|
|
5667
6659
|
function checkCompactArguments(r) {
|
|
5668
6660
|
checkCount("compact", r.count);
|
|
5669
|
-
checkWords$
|
|
5670
|
-
checkWords$
|
|
5671
|
-
checkWords$
|
|
6661
|
+
checkWords$3("compact", "queue", r.queue, r.count);
|
|
6662
|
+
checkWords$3("compact", "flags", r.flags, r.count);
|
|
6663
|
+
checkWords$3("compact", "out", r.out, r.count);
|
|
5672
6664
|
checkWordIndex("compact", "outIndex", r.outIndex, r.outCount);
|
|
5673
6665
|
}
|
|
5674
6666
|
function checkDedupeArguments(r) {
|
|
5675
6667
|
checkCount("dedupe", r.count);
|
|
5676
|
-
checkWords$
|
|
5677
|
-
checkWords$
|
|
6668
|
+
checkWords$3("dedupe", "queue", r.queue, r.count);
|
|
6669
|
+
checkWords$3("dedupe", "out", r.out, Math.min(r.count, Math.floor(r.owner.size / 4)));
|
|
5678
6670
|
checkWordIndex("dedupe", "outIndex", r.outIndex, r.outCount);
|
|
5679
6671
|
if (r.countIndex === U32_MAX$2) {
|
|
5680
6672
|
return;
|
|
@@ -5822,7 +6814,9 @@ const W = Object.freeze({
|
|
|
5822
6814
|
thresholdBits: 22,
|
|
5823
6815
|
deltaBits: 23,
|
|
5824
6816
|
path: 24,
|
|
5825
|
-
nextDegreeSum: 25
|
|
6817
|
+
nextDegreeSum: 25,
|
|
6818
|
+
stackTop: 26,
|
|
6819
|
+
sigmaOverflow: 27
|
|
5826
6820
|
});
|
|
5827
6821
|
function definedWords(words) {
|
|
5828
6822
|
const out = {};
|
|
@@ -6156,7 +7150,7 @@ class RadixSortPlannerImpl {
|
|
|
6156
7150
|
return src;
|
|
6157
7151
|
}
|
|
6158
7152
|
}
|
|
6159
|
-
const ALGORITHM$
|
|
7153
|
+
const ALGORITHM$7 = "breadthFirstSearch";
|
|
6160
7154
|
const NEXT_DEGREE_MAX_GROUPS = 128;
|
|
6161
7155
|
function bfsRingSlots(windows, levelsPerSubmit) {
|
|
6162
7156
|
return Math.max((5 + 4 * windows) * levelsPerSubmit + 16, RESULT_BATCH_SLOTS + windows);
|
|
@@ -6168,7 +7162,7 @@ function normaliseMaxDepth(maxDepth) {
|
|
|
6168
7162
|
}
|
|
6169
7163
|
return Math.max(0, Math.ceil(maxDepth));
|
|
6170
7164
|
}
|
|
6171
|
-
function checkDest$
|
|
7165
|
+
function checkDest$3(dest, n) {
|
|
6172
7166
|
if (dest === void 0) {
|
|
6173
7167
|
return null;
|
|
6174
7168
|
}
|
|
@@ -6177,7 +7171,7 @@ function checkDest$1(dest, n) {
|
|
|
6177
7171
|
}
|
|
6178
7172
|
throw new WebGpuGraphError(
|
|
6179
7173
|
"E_INVALID_ARGUMENT",
|
|
6180
|
-
`${ALGORITHM$
|
|
7174
|
+
`${ALGORITHM$7}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
6181
7175
|
{
|
|
6182
7176
|
argument: "dest",
|
|
6183
7177
|
value: `${dest.constructor.name}(${dest.length})`,
|
|
@@ -6192,8 +7186,8 @@ function degreeView(ctx, s, name) {
|
|
|
6192
7186
|
const { bindings } = ctx.residency.view(s, name);
|
|
6193
7187
|
const { [name]: binding } = bindings;
|
|
6194
7188
|
if (binding === void 0) {
|
|
6195
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
6196
|
-
label: `${ALGORITHM$
|
|
7189
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$7}: the ${name} view has no ${name} binding`, {
|
|
7190
|
+
label: `${ALGORITHM$7}/${name}`,
|
|
6197
7191
|
message: `the ${name} view has no ${name} binding`
|
|
6198
7192
|
});
|
|
6199
7193
|
}
|
|
@@ -6202,15 +7196,15 @@ function degreeView(ctx, s, name) {
|
|
|
6202
7196
|
function aborted$1(batchId) {
|
|
6203
7197
|
return new WebGpuGraphError(
|
|
6204
7198
|
"E_ABORTED",
|
|
6205
|
-
`${ALGORITHM$
|
|
7199
|
+
`${ALGORITHM$7}: the signal was aborted`,
|
|
6206
7200
|
batchId === void 0 ? {} : { batchId }
|
|
6207
7201
|
);
|
|
6208
7202
|
}
|
|
6209
7203
|
function wordOf$1(block, name) {
|
|
6210
7204
|
const value = block[name];
|
|
6211
7205
|
if (typeof value !== "number") {
|
|
6212
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
6213
|
-
label: `${ALGORITHM$
|
|
7206
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$7}: counters.${name} did not decode to a number`, {
|
|
7207
|
+
label: `${ALGORITHM$7}/counters`,
|
|
6214
7208
|
message: `the field ${name} did not decode to a number`
|
|
6215
7209
|
});
|
|
6216
7210
|
}
|
|
@@ -6221,7 +7215,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6221
7215
|
await assertDeviceComputes(ctx);
|
|
6222
7216
|
const n = s.nodeCount;
|
|
6223
7217
|
if (!Number.isInteger(source) || source < 0 || source >= n) {
|
|
6224
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$
|
|
7218
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$7}: source ${source} is outside [0, ${n})`, {
|
|
6225
7219
|
argument: "source",
|
|
6226
7220
|
value: source,
|
|
6227
7221
|
expected: `an integer in [0, ${n})`
|
|
@@ -6231,7 +7225,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6231
7225
|
if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
6232
7226
|
throw new WebGpuGraphError(
|
|
6233
7227
|
"E_INVALID_ARGUMENT",
|
|
6234
|
-
`${ALGORITHM$
|
|
7228
|
+
`${ALGORITHM$7}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
6235
7229
|
{
|
|
6236
7230
|
argument: "levelsPerSubmit",
|
|
6237
7231
|
value: levelsPerSubmit,
|
|
@@ -6239,7 +7233,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6239
7233
|
}
|
|
6240
7234
|
);
|
|
6241
7235
|
}
|
|
6242
|
-
const dest = checkDest$
|
|
7236
|
+
const dest = checkDest$3(options?.dest, n);
|
|
6243
7237
|
const maxDepth = normaliseMaxDepth(options?.maxDepth);
|
|
6244
7238
|
const predKind = tuning.predKind ?? 1;
|
|
6245
7239
|
if (options?.signal?.aborted) {
|
|
@@ -6250,12 +7244,12 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6250
7244
|
if (s.directed && 4 * s.arcCount > ctx.caps.limits.maxStorageBufferBindingSize) {
|
|
6251
7245
|
throw new WebGpuGraphError(
|
|
6252
7246
|
"E_TOO_LARGE",
|
|
6253
|
-
`${ALGORITHM$
|
|
7247
|
+
`${ALGORITHM$7}: the reverse adjacency of a directed snapshot (${4 * s.arcCount} bytes) needs arc windows, which no view executes (spec 4.3); the bottom-up sweep binds it whole`,
|
|
6254
7248
|
{
|
|
6255
7249
|
needed: 4 * s.arcCount,
|
|
6256
7250
|
limit: ctx.caps.limits.maxStorageBufferBindingSize,
|
|
6257
7251
|
path: "windowed",
|
|
6258
|
-
algorithm: ALGORITHM$
|
|
7252
|
+
algorithm: ALGORITHM$7
|
|
6259
7253
|
}
|
|
6260
7254
|
);
|
|
6261
7255
|
}
|
|
@@ -6263,7 +7257,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6263
7257
|
const backward = coreWindows(reverse);
|
|
6264
7258
|
const outDegree = degreeView(ctx, s, "outDegree");
|
|
6265
7259
|
const inDegree = degreeView(ctx, s, "inDegree");
|
|
6266
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
7260
|
+
const scope = algorithmScope(ctx, ALGORITHM$7, bfsRingSlots(forward.length, levelsPerSubmit));
|
|
6267
7261
|
tuning.onScope?.(scope);
|
|
6268
7262
|
try {
|
|
6269
7263
|
const bytes = 4 * n;
|
|
@@ -6305,7 +7299,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6305
7299
|
const degreePlan = planGridStride(n, wg, ctx.caps, NEXT_DEGREE_MAX_GROUPS);
|
|
6306
7300
|
const fusedPlan = planGridStride(n * wg, wg, ctx.caps);
|
|
6307
7301
|
const bitsPlan = plan1d(bitsWords, wg, ctx.caps);
|
|
6308
|
-
const
|
|
7302
|
+
const recordFill2 = (pass2, dst, value, mode) => {
|
|
6309
7303
|
const params = scope.params(FILL_PARAMS, { count: n, value, mode, pad0: 0 });
|
|
6310
7304
|
fill.dispatch(pass2, fill.bind({ dst, P: params.binding }), fillPlan, [params.offset]);
|
|
6311
7305
|
};
|
|
@@ -6323,16 +7317,16 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6323
7317
|
outIndex: 0
|
|
6324
7318
|
});
|
|
6325
7319
|
};
|
|
6326
|
-
const
|
|
7320
|
+
const submit2 = (batch) => {
|
|
6327
7321
|
scope.flush();
|
|
6328
7322
|
return batch.submit();
|
|
6329
7323
|
};
|
|
6330
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
7324
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$7}/setup`);
|
|
6331
7325
|
const setupPass = setup.pass("fill");
|
|
6332
|
-
|
|
6333
|
-
|
|
7326
|
+
recordFill2(setupPass, depth, INVALID_INDEX, 0);
|
|
7327
|
+
recordFill2(setupPass, iota, 0, 1);
|
|
6334
7328
|
setup.endPass();
|
|
6335
|
-
await
|
|
7329
|
+
await submit2(setup).readback;
|
|
6336
7330
|
ctx.assertReady();
|
|
6337
7331
|
queue.writeBuffer(depth.buffer, depth.offset + 4 * source, Uint32Array.of(0));
|
|
6338
7332
|
frontier.reset(queue, source, { nextFrontierCount: 1, level: U32_MAX$2 });
|
|
@@ -6347,7 +7341,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6347
7341
|
let submits = 0;
|
|
6348
7342
|
for (; ; ) {
|
|
6349
7343
|
queue.writeBuffer(counters.buffer, counters.offset + 4 * W.unvisitedCount, new Uint32Array(3));
|
|
6350
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
7344
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$7}/levels`);
|
|
6351
7345
|
const pass2 = batch.pass("bfs");
|
|
6352
7346
|
recordRebuild(pass2);
|
|
6353
7347
|
const bitsParams = scope.params(FILL_PARAMS, { count: bitsWords, value: 0, mode: 0, pad0: 0 });
|
|
@@ -6436,7 +7430,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6436
7430
|
frontier: batch.readback(frontier.input.buffer, frontier.input.offset, frontier.input.size),
|
|
6437
7431
|
count: batch.readback(compactCount.buffer, compactCount.offset, 4)
|
|
6438
7432
|
};
|
|
6439
|
-
const submitted =
|
|
7433
|
+
const submitted = submit2(batch);
|
|
6440
7434
|
const back2 = await submitted.readback;
|
|
6441
7435
|
levelsRecorded += levelsPerSubmit;
|
|
6442
7436
|
submits += 1;
|
|
@@ -6457,8 +7451,8 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6457
7451
|
if (submits > n + 1) {
|
|
6458
7452
|
throw new WebGpuGraphError(
|
|
6459
7453
|
"E_VALIDATION",
|
|
6460
|
-
`${ALGORITHM$
|
|
6461
|
-
{ label: ALGORITHM$
|
|
7454
|
+
`${ALGORITHM$7}: the done flag never rose in ${submits} submits (a traversal has at most ${n} levels)`,
|
|
7455
|
+
{ label: ALGORITHM$7, message: `the done flag never rose in ${submits} submits` }
|
|
6462
7456
|
);
|
|
6463
7457
|
}
|
|
6464
7458
|
}
|
|
@@ -6472,12 +7466,12 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6472
7466
|
offsets: bindingOf$1(scope.scratch(histBytes, "order/offsets"), histBytes)
|
|
6473
7467
|
};
|
|
6474
7468
|
const parent = bindingOf$1(scope.scratch(bytes, "parent"), bytes);
|
|
6475
|
-
const result = new CommandBatch(ctx, `${ALGORITHM$
|
|
7469
|
+
const result = new CommandBatch(ctx, `${ALGORITHM$7}/result`);
|
|
6476
7470
|
result.copy(depth, keys, bytes);
|
|
6477
7471
|
const pass = result.pass("result");
|
|
6478
|
-
|
|
7472
|
+
recordFill2(pass, vals, 0, 1);
|
|
6479
7473
|
const sorted = sort.record(pass, keys, vals, n, 32, scratch);
|
|
6480
|
-
|
|
7474
|
+
recordFill2(pass, parent, INVALID_INDEX, 0);
|
|
6481
7475
|
const predPlan = planGridStride(n, wg, ctx.caps);
|
|
6482
7476
|
for (const w of forward) {
|
|
6483
7477
|
const predParams = scope.params(FRONTIER_PARAMS, {
|
|
@@ -6502,16 +7496,16 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6502
7496
|
const parentRequest = result.readback(parent.buffer, parent.offset, bytes);
|
|
6503
7497
|
const orderRequest = result.readback(sorted.vals.buffer, sorted.vals.offset, bytes);
|
|
6504
7498
|
const blockRequest = result.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
|
|
6505
|
-
const back = await
|
|
7499
|
+
const back = await submit2(result).readback;
|
|
6506
7500
|
ctx.assertReady();
|
|
6507
7501
|
const block = FRONTIER_COUNTERS.read(new DataView(back), blockRequest.offset);
|
|
6508
7502
|
const visitedCount = wordOf$1(block, "visitedCount");
|
|
6509
7503
|
if (visitedCount > n) {
|
|
6510
7504
|
throw new WebGpuGraphError(
|
|
6511
7505
|
"E_VALIDATION",
|
|
6512
|
-
`${ALGORITHM$
|
|
7506
|
+
`${ALGORITHM$7}: visitedCount ${visitedCount} exceeds the ${n} vertices (a duplicate claim)`,
|
|
6513
7507
|
{
|
|
6514
|
-
label: `${ALGORITHM$
|
|
7508
|
+
label: `${ALGORITHM$7}/visitedCount`,
|
|
6515
7509
|
message: `the device counted ${visitedCount} visits of ${n} vertices`
|
|
6516
7510
|
}
|
|
6517
7511
|
);
|
|
@@ -6535,9 +7529,9 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6535
7529
|
function breadthFirstSearch(ctx, s, source, options) {
|
|
6536
7530
|
return bfsWithTuning(ctx, s, source, options, {});
|
|
6537
7531
|
}
|
|
6538
|
-
const ALGORITHM$
|
|
7532
|
+
const ALGORITHM$6 = "sssp";
|
|
6539
7533
|
const HALF_ALIGN = 64;
|
|
6540
|
-
const RING_SLOTS$
|
|
7534
|
+
const RING_SLOTS$5 = 4 * MAX_LEVELS_PER_SUBMIT + 40;
|
|
6541
7535
|
function bitsOf(value) {
|
|
6542
7536
|
return new Uint32Array(Float32Array.of(value).buffer)[0];
|
|
6543
7537
|
}
|
|
@@ -6563,14 +7557,14 @@ function assertSource(algorithm, source, n) {
|
|
|
6563
7557
|
function wordOf(block, name) {
|
|
6564
7558
|
const value = block[name];
|
|
6565
7559
|
if (typeof value !== "number") {
|
|
6566
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
6567
|
-
label: `${ALGORITHM$
|
|
7560
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$6}: counters.${name} did not decode to a number`, {
|
|
7561
|
+
label: `${ALGORITHM$6}/counters`,
|
|
6568
7562
|
message: `the field ${name} did not decode to a number`
|
|
6569
7563
|
});
|
|
6570
7564
|
}
|
|
6571
7565
|
return value;
|
|
6572
7566
|
}
|
|
6573
|
-
function checkDest(algorithm, dest, n) {
|
|
7567
|
+
function checkDest$2(algorithm, dest, n) {
|
|
6574
7568
|
if (dest === void 0) {
|
|
6575
7569
|
return null;
|
|
6576
7570
|
}
|
|
@@ -6664,7 +7658,7 @@ function predBufferWords(n) {
|
|
|
6664
7658
|
return 2 * Math.ceil(n / 64) * 64 + 64;
|
|
6665
7659
|
}
|
|
6666
7660
|
async function predecessorPass(input) {
|
|
6667
|
-
const { algorithm, ctx, scope, predKernel, recordFill, graph, dist, pred, n, arcCount, source, mode } = input;
|
|
7661
|
+
const { algorithm, ctx, scope, predKernel, recordFill: recordFill2, graph, dist, pred, n, arcCount, source, mode } = input;
|
|
6668
7662
|
const wg = ctx.workgroupSize;
|
|
6669
7663
|
const { queue } = ctx.device;
|
|
6670
7664
|
const bytes = 4 * n;
|
|
@@ -6699,7 +7693,7 @@ async function predecessorPass(input) {
|
|
|
6699
7693
|
for (let iteration = 0; iteration < MAX_LEVELS_PER_SUBMIT; iteration++) {
|
|
6700
7694
|
recordRole(1, iteration);
|
|
6701
7695
|
}
|
|
6702
|
-
|
|
7696
|
+
recordFill2(pass, predArcs, n, INVALID_INDEX);
|
|
6703
7697
|
recordRole(2, 0);
|
|
6704
7698
|
batch.endPass();
|
|
6705
7699
|
const distRequest = batch.readback(dist.buffer, dist.offset, bytes);
|
|
@@ -6731,12 +7725,12 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6731
7725
|
ctx.assertReady();
|
|
6732
7726
|
await assertDeviceComputes(ctx);
|
|
6733
7727
|
const n = s.nodeCount;
|
|
6734
|
-
assertSource(ALGORITHM$
|
|
7728
|
+
assertSource(ALGORITHM$6, source, n);
|
|
6735
7729
|
const roundsPerSubmit = tuning.roundsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
|
|
6736
7730
|
if (!Number.isInteger(roundsPerSubmit) || roundsPerSubmit < 1 || roundsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
6737
7731
|
throw new WebGpuGraphError(
|
|
6738
7732
|
"E_INVALID_ARGUMENT",
|
|
6739
|
-
`${ALGORITHM$
|
|
7733
|
+
`${ALGORITHM$6}: roundsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
6740
7734
|
{
|
|
6741
7735
|
argument: "roundsPerSubmit",
|
|
6742
7736
|
value: roundsPerSubmit,
|
|
@@ -6745,42 +7739,42 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6745
7739
|
);
|
|
6746
7740
|
}
|
|
6747
7741
|
if (tuning.delta !== void 0 && !(Number.isFinite(tuning.delta) && tuning.delta > 0)) {
|
|
6748
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$
|
|
7742
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$6}: delta must be a finite positive number`, {
|
|
6749
7743
|
argument: "delta",
|
|
6750
7744
|
value: tuning.delta,
|
|
6751
7745
|
expected: "a finite positive number"
|
|
6752
7746
|
});
|
|
6753
7747
|
}
|
|
6754
|
-
const dest = checkDest(ALGORITHM$
|
|
6755
|
-
const vector2 = resolveWeights$1(ALGORITHM$
|
|
6756
|
-
const cutoff = normaliseCutoff(ALGORITHM$
|
|
7748
|
+
const dest = checkDest$2(ALGORITHM$6, options?.dest, n);
|
|
7749
|
+
const vector2 = resolveWeights$1(ALGORITHM$6, s, options?.weights);
|
|
7750
|
+
const cutoff = normaliseCutoff(ALGORITHM$6, options?.cutoff);
|
|
6757
7751
|
if (options?.signal?.aborted) {
|
|
6758
|
-
throw aborted(ALGORITHM$
|
|
7752
|
+
throw aborted(ALGORITHM$6);
|
|
6759
7753
|
}
|
|
6760
7754
|
if (vector2 === null || vector2.allOne) {
|
|
6761
7755
|
return unitWeightRoute(ctx, s, source, cutoff, dest, options);
|
|
6762
7756
|
}
|
|
6763
7757
|
if (!vector2.nonNegative) {
|
|
6764
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$
|
|
7758
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$6}: a negative weight has no shortest path here`, {
|
|
6765
7759
|
feature: "sssp.negativeWeights",
|
|
6766
7760
|
hint: "use bellmanFord"
|
|
6767
7761
|
});
|
|
6768
7762
|
}
|
|
6769
7763
|
if (!vector2.finite) {
|
|
6770
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$
|
|
7764
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$6}: a NaN or infinite weight has no bit-pattern order`, {
|
|
6771
7765
|
feature: "sssp.nonFiniteWeights"
|
|
6772
7766
|
});
|
|
6773
7767
|
}
|
|
6774
7768
|
const { arcCount } = s;
|
|
6775
7769
|
const core = ctx.residency.core(s);
|
|
6776
7770
|
const limit = ctx.caps.limits.maxStorageBufferBindingSize;
|
|
6777
|
-
assertWholeCore(core, arcCount, limit, ALGORITHM$
|
|
7771
|
+
assertWholeCore(core, arcCount, limit, ALGORITHM$6);
|
|
6778
7772
|
const cap = Math.ceil(Math.max(1, arcCount) / HALF_ALIGN) * HALF_ALIGN;
|
|
6779
7773
|
if (8 * cap > limit) {
|
|
6780
7774
|
throw new WebGpuGraphError(
|
|
6781
7775
|
"E_TOO_LARGE",
|
|
6782
|
-
`${ALGORITHM$
|
|
6783
|
-
{ needed: 8 * cap, limit, path: "sssp.queue", algorithm: ALGORITHM$
|
|
7776
|
+
`${ALGORITHM$6}: the near-far queue of ${cap} entries per half needs ${8 * cap} bytes, above the ${limit}-byte binding limit (the relax is never windowed)`,
|
|
7777
|
+
{ needed: 8 * cap, limit, path: "sssp.queue", algorithm: ALGORITHM$6 }
|
|
6784
7778
|
);
|
|
6785
7779
|
}
|
|
6786
7780
|
const delta = Math.fround(
|
|
@@ -6789,7 +7783,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6789
7783
|
const deltaBits = bitsOf(delta);
|
|
6790
7784
|
const maxRounds = n + Math.ceil(vector2.sum / delta) + 1;
|
|
6791
7785
|
const maxSubmits = Math.ceil((maxRounds + 1) / roundsPerSubmit) + 1;
|
|
6792
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
7786
|
+
const scope = algorithmScope(ctx, ALGORITHM$6, RING_SLOTS$5);
|
|
6793
7787
|
try {
|
|
6794
7788
|
const wg = ctx.workgroupSize;
|
|
6795
7789
|
const bytes = 4 * n;
|
|
@@ -6819,20 +7813,20 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6819
7813
|
const { counters } = frontier;
|
|
6820
7814
|
const nearIn = frontier.vertices[0];
|
|
6821
7815
|
const farIn = frontier.vertices[1];
|
|
6822
|
-
const
|
|
7816
|
+
const recordFill2 = (pass, dst, count, value) => {
|
|
6823
7817
|
const params = scope.params(FILL_PARAMS, { count, value, mode: 0, pad0: 0 });
|
|
6824
7818
|
fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
|
|
6825
7819
|
};
|
|
6826
|
-
const
|
|
7820
|
+
const submit2 = (batch) => {
|
|
6827
7821
|
scope.flush();
|
|
6828
7822
|
return batch.submit();
|
|
6829
7823
|
};
|
|
6830
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
7824
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$6}/setup`);
|
|
6831
7825
|
const setupPass = setup.pass("fill");
|
|
6832
|
-
|
|
6833
|
-
|
|
7826
|
+
recordFill2(setupPass, dist, n, F32_INF_BITS);
|
|
7827
|
+
recordFill2(setupPass, pred, predWords, INVALID_INDEX);
|
|
6834
7828
|
setup.endPass();
|
|
6835
|
-
await
|
|
7829
|
+
await submit2(setup).readback;
|
|
6836
7830
|
ctx.assertReady();
|
|
6837
7831
|
queue.writeBuffer(dist.buffer, dist.offset + 4 * source, Uint32Array.of(0));
|
|
6838
7832
|
frontier.reset(queue, source, { nextFrontierCount: 1, thresholdBits: deltaBits, deltaBits });
|
|
@@ -6873,7 +7867,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6873
7867
|
let roundsRecorded = 0;
|
|
6874
7868
|
let submits = 0;
|
|
6875
7869
|
for (; ; ) {
|
|
6876
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
7870
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$6}/rounds`);
|
|
6877
7871
|
const pass = batch.pass("sssp");
|
|
6878
7872
|
const near = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 0 });
|
|
6879
7873
|
const far = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 1 });
|
|
@@ -6890,13 +7884,13 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6890
7884
|
batch.endPass();
|
|
6891
7885
|
const doneRequest = batch.readback(counters.buffer, counters.offset + 4 * W.done, 4);
|
|
6892
7886
|
const inspect = tuning.onRound === void 0 ? null : batch.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
|
|
6893
|
-
const submitted =
|
|
7887
|
+
const submitted = submit2(batch);
|
|
6894
7888
|
const back = await submitted.readback;
|
|
6895
7889
|
roundsRecorded += roundsPerSubmit;
|
|
6896
7890
|
submits += 1;
|
|
6897
7891
|
ctx.assertReady();
|
|
6898
7892
|
if (options?.signal?.aborted) {
|
|
6899
|
-
throw aborted(ALGORITHM$
|
|
7893
|
+
throw aborted(ALGORITHM$6, submitted.id);
|
|
6900
7894
|
}
|
|
6901
7895
|
options?.onProgress?.(Math.min(roundsRecorded, maxRounds), maxRounds);
|
|
6902
7896
|
if (inspect !== null && tuning.onRound !== void 0) {
|
|
@@ -6921,30 +7915,30 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6921
7915
|
const needed = Math.max(wordOf(block, "nextFrontierCount"), wordOf(block, "nextFarCount"));
|
|
6922
7916
|
throw new WebGpuGraphError(
|
|
6923
7917
|
"E_TOO_LARGE",
|
|
6924
|
-
`${ALGORITHM$
|
|
6925
|
-
{ needed, limit: cap, path: "sssp.pile", algorithm: ALGORITHM$
|
|
7918
|
+
`${ALGORITHM$6}: a raw pile of ${needed} entries overflowed its ${cap}-entry half`,
|
|
7919
|
+
{ needed, limit: cap, path: "sssp.pile", algorithm: ALGORITHM$6 }
|
|
6926
7920
|
);
|
|
6927
7921
|
}
|
|
6928
7922
|
throw new WebGpuGraphError(
|
|
6929
7923
|
"E_UNSUPPORTED",
|
|
6930
|
-
`${ALGORITHM$
|
|
7924
|
+
`${ALGORITHM$6}: the f32 threshold ${wordOf(block, "thresholdBits")} absorbed the delta ${deltaBits} (as bit patterns); the far pile can no longer be bucketed`,
|
|
6931
7925
|
{ feature: "sssp.thresholdAbsorbed", hint: "the distances outgrew the delta's f32 precision" }
|
|
6932
7926
|
);
|
|
6933
7927
|
}
|
|
6934
7928
|
if (submits > maxSubmits) {
|
|
6935
7929
|
throw new WebGpuGraphError(
|
|
6936
7930
|
"E_VALIDATION",
|
|
6937
|
-
`${ALGORITHM$
|
|
6938
|
-
{ label: `${ALGORITHM$
|
|
7931
|
+
`${ALGORITHM$6}: the done flag never rose in ${submits} submits (at most ${maxRounds} rounds)`,
|
|
7932
|
+
{ label: `${ALGORITHM$6}/rounds`, message: `the done flag never rose in ${submits} submits` }
|
|
6939
7933
|
);
|
|
6940
7934
|
}
|
|
6941
7935
|
}
|
|
6942
7936
|
const passed = await predecessorPass({
|
|
6943
|
-
algorithm: ALGORITHM$
|
|
7937
|
+
algorithm: ALGORITHM$6,
|
|
6944
7938
|
ctx,
|
|
6945
7939
|
scope,
|
|
6946
7940
|
predKernel,
|
|
6947
|
-
recordFill,
|
|
7941
|
+
recordFill: recordFill2,
|
|
6948
7942
|
graph,
|
|
6949
7943
|
dist,
|
|
6950
7944
|
pred,
|
|
@@ -6956,8 +7950,8 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6956
7950
|
if (passed.orphans !== 0) {
|
|
6957
7951
|
throw new WebGpuGraphError(
|
|
6958
7952
|
"E_VALIDATION",
|
|
6959
|
-
`${ALGORITHM$
|
|
6960
|
-
{ label: `${ALGORITHM$
|
|
7953
|
+
`${ALGORITHM$6}: ${passed.orphans} reached node(s) the predecessor key never reached (a kernel bug)`,
|
|
7954
|
+
{ label: `${ALGORITHM$6}/pred`, message: `${passed.orphans} orphan(s) in the predecessor pass` }
|
|
6961
7955
|
);
|
|
6962
7956
|
}
|
|
6963
7957
|
const distOut = dest ?? new Float32Array(n);
|
|
@@ -6976,10 +7970,10 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
6976
7970
|
function sssp(ctx, s, source, options) {
|
|
6977
7971
|
return ssspWithTuning(ctx, s, source, options, {});
|
|
6978
7972
|
}
|
|
6979
|
-
const ALGORITHM$
|
|
7973
|
+
const ALGORITHM$5 = "bellmanFord";
|
|
6980
7974
|
const ROUNDS_PER_BATCH = 8;
|
|
6981
7975
|
const MAX_RETRIES = 16;
|
|
6982
|
-
const RING_SLOTS$
|
|
7976
|
+
const RING_SLOTS$4 = MAX_LEVELS_PER_SUBMIT + 16;
|
|
6983
7977
|
function assertSymmetric(s, vector2) {
|
|
6984
7978
|
const { arcToEdge, edgeToArc } = s;
|
|
6985
7979
|
for (let a = 0; a < s.arcCount; a++) {
|
|
@@ -6987,7 +7981,7 @@ function assertSymmetric(s, vector2) {
|
|
|
6987
7981
|
if (vector2[a] !== vector2[forward]) {
|
|
6988
7982
|
throw new WebGpuGraphError(
|
|
6989
7983
|
"E_UNSUPPORTED",
|
|
6990
|
-
`${ALGORITHM$
|
|
7984
|
+
`${ALGORITHM$5}: weights[${a}] = ${vector2[a]} differs from weights[${forward}] = ${vector2[forward]}, the forward arc of the same undirected edge; the kernel reads one weight per edge`,
|
|
6991
7985
|
{ feature: "bellmanFord.asymmetricUndirectedWeights", hint: "use a directed snapshot" }
|
|
6992
7986
|
);
|
|
6993
7987
|
}
|
|
@@ -6997,10 +7991,10 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
6997
7991
|
ctx.assertReady();
|
|
6998
7992
|
await assertDeviceComputes(ctx);
|
|
6999
7993
|
const n = s.nodeCount;
|
|
7000
|
-
assertSource(ALGORITHM$
|
|
7994
|
+
assertSource(ALGORITHM$5, source, n);
|
|
7001
7995
|
const maxRetries = tuning.maxRetries ?? MAX_RETRIES;
|
|
7002
7996
|
if (!Number.isInteger(maxRetries) || maxRetries < 1) {
|
|
7003
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$
|
|
7997
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$5}: maxRetries must be an integer >= 1`, {
|
|
7004
7998
|
argument: "maxRetries",
|
|
7005
7999
|
value: maxRetries,
|
|
7006
8000
|
expected: "an integer >= 1"
|
|
@@ -7010,7 +8004,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7010
8004
|
if (!Number.isInteger(roundsPerBatch) || roundsPerBatch < 1 || roundsPerBatch > MAX_LEVELS_PER_SUBMIT) {
|
|
7011
8005
|
throw new WebGpuGraphError(
|
|
7012
8006
|
"E_INVALID_ARGUMENT",
|
|
7013
|
-
`${ALGORITHM$
|
|
8007
|
+
`${ALGORITHM$5}: roundsPerBatch must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
7014
8008
|
{
|
|
7015
8009
|
argument: "roundsPerBatch",
|
|
7016
8010
|
value: roundsPerBatch,
|
|
@@ -7018,18 +8012,18 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7018
8012
|
}
|
|
7019
8013
|
);
|
|
7020
8014
|
}
|
|
7021
|
-
const dest = checkDest(ALGORITHM$
|
|
7022
|
-
const vector2 = resolveWeights$1(ALGORITHM$
|
|
7023
|
-
const cutoff = normaliseCutoff(ALGORITHM$
|
|
8015
|
+
const dest = checkDest$2(ALGORITHM$5, options?.dest, n);
|
|
8016
|
+
const vector2 = resolveWeights$1(ALGORITHM$5, s, options?.weights);
|
|
8017
|
+
const cutoff = normaliseCutoff(ALGORITHM$5, options?.cutoff);
|
|
7024
8018
|
if (options?.signal?.aborted) {
|
|
7025
|
-
throw aborted(ALGORITHM$
|
|
8019
|
+
throw aborted(ALGORITHM$5);
|
|
7026
8020
|
}
|
|
7027
8021
|
if (vector2 === null || vector2.allOne) {
|
|
7028
8022
|
const unit = await unitWeightRoute(ctx, s, source, cutoff, dest, options);
|
|
7029
8023
|
return { result: { ...unit, hasNegativeCycle: false }, rounds: 0, retryExhaustedRounds: 0 };
|
|
7030
8024
|
}
|
|
7031
8025
|
if (!vector2.finite) {
|
|
7032
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$
|
|
8026
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$5}: a NaN or infinite weight has no shortest path`, {
|
|
7033
8027
|
feature: "bellmanFord.nonFiniteWeights"
|
|
7034
8028
|
});
|
|
7035
8029
|
}
|
|
@@ -7038,10 +8032,10 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7038
8032
|
}
|
|
7039
8033
|
const { arcCount } = s;
|
|
7040
8034
|
const core = ctx.residency.core(s, ["rowPtr", "colIdx", "weights", "edgeToArc"]);
|
|
7041
|
-
assertWholeCore(core, arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$
|
|
8035
|
+
assertWholeCore(core, arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$5);
|
|
7042
8036
|
const edges = ctx.residency.view(s, "edgeList");
|
|
7043
8037
|
const edgeCount = edges.scalars.edgeCount[0];
|
|
7044
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
8038
|
+
const scope = algorithmScope(ctx, ALGORITHM$5, RING_SLOTS$4);
|
|
7045
8039
|
try {
|
|
7046
8040
|
const wg = ctx.workgroupSize;
|
|
7047
8041
|
const bytes = 4 * n;
|
|
@@ -7068,23 +8062,23 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7068
8062
|
const predKernel = await ctx.pipelines.kernel(kernelSpec("sssp-pred", { ...overrides, MODE: 0 }));
|
|
7069
8063
|
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
7070
8064
|
const graph = graphBindings(core, null, weightsBinding);
|
|
7071
|
-
const
|
|
8065
|
+
const recordFill2 = (pass, dst, count, value, mode = 0) => {
|
|
7072
8066
|
const params = scope.params(FILL_PARAMS, { count, value, mode, pad0: 0 });
|
|
7073
8067
|
fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
|
|
7074
8068
|
};
|
|
7075
|
-
const
|
|
8069
|
+
const submit2 = (batch) => {
|
|
7076
8070
|
scope.flush();
|
|
7077
8071
|
return batch.submit();
|
|
7078
8072
|
};
|
|
7079
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
8073
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$5}/setup`);
|
|
7080
8074
|
const setupPass = setup.pass("fill");
|
|
7081
|
-
|
|
7082
|
-
|
|
8075
|
+
recordFill2(setupPass, dist, n, F32_INF_BITS);
|
|
8076
|
+
recordFill2(setupPass, pred, predWords, INVALID_INDEX);
|
|
7083
8077
|
if (iota !== null) {
|
|
7084
|
-
|
|
8078
|
+
recordFill2(setupPass, iota, edgeCount, 0, 1);
|
|
7085
8079
|
}
|
|
7086
8080
|
setup.endPass();
|
|
7087
|
-
await
|
|
8081
|
+
await submit2(setup).readback;
|
|
7088
8082
|
ctx.assertReady();
|
|
7089
8083
|
queue.writeBuffer(dist.buffer, dist.offset + 4 * source, Uint32Array.of(0));
|
|
7090
8084
|
const edgePlan = planGridStride(edgeCount, wg, ctx.caps);
|
|
@@ -7105,7 +8099,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7105
8099
|
const zero = new Uint32Array(BF_FLAGS.byteLength / 4);
|
|
7106
8100
|
const runRounds = async (count, label) => {
|
|
7107
8101
|
queue.writeBuffer(flags.buffer, flags.offset, zero);
|
|
7108
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
8102
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$5}/${label}`);
|
|
7109
8103
|
const pass = batch.pass("relax");
|
|
7110
8104
|
const params = scope.params(BF_PARAMS, relaxFields);
|
|
7111
8105
|
const bound = relax.bind({ ...relaxBindings, P: params.binding });
|
|
@@ -7114,7 +8108,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7114
8108
|
}
|
|
7115
8109
|
batch.endPass();
|
|
7116
8110
|
const request = batch.readback(flags.buffer, flags.offset, BF_FLAGS.byteLength);
|
|
7117
|
-
const submitted =
|
|
8111
|
+
const submitted = submit2(batch);
|
|
7118
8112
|
const back = await submitted.readback;
|
|
7119
8113
|
ctx.assertReady();
|
|
7120
8114
|
const block = BF_FLAGS.read(new DataView(back), request.offset);
|
|
@@ -7130,9 +8124,9 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7130
8124
|
if (decision.retryExhausted !== 0) {
|
|
7131
8125
|
throw new WebGpuGraphError(
|
|
7132
8126
|
"E_VALIDATION",
|
|
7133
|
-
`${ALGORITHM$
|
|
8127
|
+
`${ALGORITHM$5}: a lane exhausted the ${maxRetries}-retry compare-exchange bound in the decision round, so its change is not a verdict`,
|
|
7134
8128
|
{
|
|
7135
|
-
label: `${ALGORITHM$
|
|
8129
|
+
label: `${ALGORITHM$5}/retry`,
|
|
7136
8130
|
message: "retryExhausted in the decision round",
|
|
7137
8131
|
batchId: decision.id
|
|
7138
8132
|
}
|
|
@@ -7148,7 +8142,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7148
8142
|
retryExhaustedRounds += 1;
|
|
7149
8143
|
}
|
|
7150
8144
|
if (options?.signal?.aborted) {
|
|
7151
|
-
throw aborted(ALGORITHM$
|
|
8145
|
+
throw aborted(ALGORITHM$5, batch.id);
|
|
7152
8146
|
}
|
|
7153
8147
|
options?.onProgress?.(rounds, n);
|
|
7154
8148
|
if (batch.changed === 0 && batch.retryExhausted === 0) {
|
|
@@ -7156,11 +8150,11 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7156
8150
|
}
|
|
7157
8151
|
}
|
|
7158
8152
|
const passed = await predecessorPass({
|
|
7159
|
-
algorithm: ALGORITHM$
|
|
8153
|
+
algorithm: ALGORITHM$5,
|
|
7160
8154
|
ctx,
|
|
7161
8155
|
scope,
|
|
7162
8156
|
predKernel,
|
|
7163
|
-
recordFill,
|
|
8157
|
+
recordFill: recordFill2,
|
|
7164
8158
|
graph,
|
|
7165
8159
|
dist,
|
|
7166
8160
|
pred,
|
|
@@ -7172,7 +8166,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7172
8166
|
if (passed.orphans !== 0 && !hasNegativeCycle) {
|
|
7173
8167
|
throw new WebGpuGraphError(
|
|
7174
8168
|
"E_UNSUPPORTED",
|
|
7175
|
-
`${ALGORITHM$
|
|
8169
|
+
`${ALGORITHM$5}: ${passed.orphans} reached node(s) the tight subgraph never reaches (a cycle of weights below one f32 ulp relaxed once)`,
|
|
7176
8170
|
{
|
|
7177
8171
|
feature: "bellmanFord.roundedCycle",
|
|
7178
8172
|
hint: "a cycle of weights below one f32 ulp relaxed once at a distance above 2^24; scale the weights or shorten the distances"
|
|
@@ -7199,10 +8193,385 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7199
8193
|
async function bellmanFord(ctx, s, source, options) {
|
|
7200
8194
|
return (await bellmanFordWithTuning(ctx, s, source, options, {})).result;
|
|
7201
8195
|
}
|
|
7202
|
-
const ALGORITHM = "
|
|
8196
|
+
const ALGORITHM$4 = "betweennessCentrality";
|
|
8197
|
+
const BYTES_PER_NODE_SOURCE = 16;
|
|
8198
|
+
const SAMPLE_SEED = 2654435769;
|
|
8199
|
+
const RING_SLOTS$3 = BC_BACKWARD_LEVELS_PER_SUBMIT + 16;
|
|
8200
|
+
function planBatchSize(n, remaining, limits) {
|
|
8201
|
+
const needed = 4 * (n + 2);
|
|
8202
|
+
if (needed > limits.maxStorageBufferBindingSize) {
|
|
8203
|
+
throw new WebGpuGraphError(
|
|
8204
|
+
"E_TOO_LARGE",
|
|
8205
|
+
`${ALGORITHM$4}: one source needs ${needed} bytes in its largest binding at n = ${n}, above maxStorageBufferBindingSize = ${limits.maxStorageBufferBindingSize}; a limit of at least ${needed} admits one source per batch`,
|
|
8206
|
+
{ needed, limit: limits.maxStorageBufferBindingSize, path: "binding", algorithm: ALGORITHM$4 }
|
|
8207
|
+
);
|
|
8208
|
+
}
|
|
8209
|
+
const kByBinding = Math.floor(limits.maxStorageBufferBindingSize / (4 * n));
|
|
8210
|
+
const kByBudget = Math.floor(BC_BATCH_BUDGET_FRACTION * limits.maxBufferSize / (BYTES_PER_NODE_SOURCE * n));
|
|
8211
|
+
return Math.max(1, Math.min(kByBinding, kByBudget, BC_MAX_BATCH, remaining));
|
|
8212
|
+
}
|
|
8213
|
+
function drawSources(n, k) {
|
|
8214
|
+
const pool = Array.from({ length: n }, (_, i) => i);
|
|
8215
|
+
let state = SAMPLE_SEED;
|
|
8216
|
+
for (let i = 0; i < k; i++) {
|
|
8217
|
+
state = state + 1831565813 >>> 0;
|
|
8218
|
+
let t = Math.imul(state ^ state >>> 15, state | 1);
|
|
8219
|
+
t = t + Math.imul(t ^ t >>> 7, t | 61) ^ t;
|
|
8220
|
+
const unit = ((t ^ t >>> 14) >>> 0) / 2 ** 32;
|
|
8221
|
+
const j = i + Math.floor(unit * (n - i));
|
|
8222
|
+
[pool[i], pool[j]] = [pool[j], pool[i]];
|
|
8223
|
+
}
|
|
8224
|
+
return pool.slice(0, k);
|
|
8225
|
+
}
|
|
8226
|
+
function badArgument(argument, value, expected) {
|
|
8227
|
+
return new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$4}: ${argument} must be ${expected}`, {
|
|
8228
|
+
argument,
|
|
8229
|
+
value,
|
|
8230
|
+
expected
|
|
8231
|
+
});
|
|
8232
|
+
}
|
|
8233
|
+
function resolveSources(options, n) {
|
|
8234
|
+
const k = options?.k;
|
|
8235
|
+
if (k !== void 0 && (!Number.isInteger(k) || k < 0 || k > n)) {
|
|
8236
|
+
throw badArgument("k", k, `an integer in [0, ${n}]`);
|
|
8237
|
+
}
|
|
8238
|
+
const given = options?.sources;
|
|
8239
|
+
if (given !== void 0) {
|
|
8240
|
+
for (const v of given) {
|
|
8241
|
+
if (!Number.isInteger(v) || v < 0 || v >= n) {
|
|
8242
|
+
throw badArgument("sources", v, `node indices in [0, ${n})`);
|
|
8243
|
+
}
|
|
8244
|
+
}
|
|
8245
|
+
if (k !== void 0 && k !== given.length) {
|
|
8246
|
+
throw badArgument("k", k, `absent or equal to sources.length (${given.length})`);
|
|
8247
|
+
}
|
|
8248
|
+
return [...given];
|
|
8249
|
+
}
|
|
8250
|
+
if (k !== void 0) {
|
|
8251
|
+
return drawSources(n, k);
|
|
8252
|
+
}
|
|
8253
|
+
return Array.from({ length: n }, (_, i) => i);
|
|
8254
|
+
}
|
|
8255
|
+
function recordFill(state, pass, dst, count, value) {
|
|
8256
|
+
const { scope, fill, ctx } = state;
|
|
8257
|
+
const params = scope.params(FILL_PARAMS, { count, value, mode: 0, pad0: 0 });
|
|
8258
|
+
fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, ctx.workgroupSize, ctx.caps), [
|
|
8259
|
+
params.offset
|
|
8260
|
+
]);
|
|
8261
|
+
}
|
|
8262
|
+
function recordBc(state, pass, kernel, resources, fields, items) {
|
|
8263
|
+
const { scope, ctx } = state;
|
|
8264
|
+
const plan = planGridStride(items, ctx.workgroupSize, ctx.caps);
|
|
8265
|
+
const params = scope.params(BC_PARAMS, { ...fields, stride: plan.stride ?? 0 });
|
|
8266
|
+
let bound = state.bound.get(kernel);
|
|
8267
|
+
if (bound === void 0) {
|
|
8268
|
+
bound = kernel.bind({ ...resources, P: params.binding });
|
|
8269
|
+
state.bound.set(kernel, bound);
|
|
8270
|
+
}
|
|
8271
|
+
kernel.dispatch(pass, bound, items === 0 ? plan1d(1, ctx.workgroupSize, ctx.caps) : plan, [params.offset]);
|
|
8272
|
+
}
|
|
8273
|
+
async function submit(state, batch, signal) {
|
|
8274
|
+
state.scope.flush();
|
|
8275
|
+
const submitted = batch.submit();
|
|
8276
|
+
const back = await submitted.readback;
|
|
8277
|
+
state.ctx.assertReady();
|
|
8278
|
+
if (signal?.aborted) {
|
|
8279
|
+
throw aborted(ALGORITHM$4, submitted.id);
|
|
8280
|
+
}
|
|
8281
|
+
return back;
|
|
8282
|
+
}
|
|
8283
|
+
async function runBatch(state, sources, form, levelsPerSubmit, tuning, signal) {
|
|
8284
|
+
const { ctx, n, S, ends, depthK, sigmaK, deltaK, counters } = state;
|
|
8285
|
+
const k = sources.length;
|
|
8286
|
+
const words = n * k;
|
|
8287
|
+
const seeds = Uint32Array.from(sources, (v, s) => s * n + v);
|
|
8288
|
+
ctx.device.queue.writeBuffer(S.buffer, S.offset, seeds);
|
|
8289
|
+
const forwardFields = { n, k, count: form === "edge" ? state.edgeCount : 0 };
|
|
8290
|
+
const forwardItems = form === "edge" ? state.edgeCount : words;
|
|
8291
|
+
let recorded = 0;
|
|
8292
|
+
let levels = 0;
|
|
8293
|
+
let overflow = false;
|
|
8294
|
+
let endsWords = null;
|
|
8295
|
+
for (let first = true; endsWords === null; first = false) {
|
|
8296
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$4}/forward`);
|
|
8297
|
+
const pass = batch.pass("forward");
|
|
8298
|
+
if (first) {
|
|
8299
|
+
recordFill(state, pass, depthK, words, 4294967295);
|
|
8300
|
+
recordFill(state, pass, sigmaK, words, 0);
|
|
8301
|
+
recordFill(state, pass, deltaK, words, 0);
|
|
8302
|
+
recordBc(state, pass, state.finalize, { counters, ends, S, depthK, sigmaK }, { n, k, role: 1 }, 1);
|
|
8303
|
+
}
|
|
8304
|
+
for (let level = 0; level < levelsPerSubmit; level++) {
|
|
8305
|
+
recordBc(state, pass, state.finalize, { counters, ends, S, depthK, sigmaK }, { n, k, role: 0 }, 1);
|
|
8306
|
+
if (form === "edge" && state.forwardEdge !== null && state.edgeSrc !== null && state.edgeDst !== null) {
|
|
8307
|
+
recordBc(
|
|
8308
|
+
state,
|
|
8309
|
+
pass,
|
|
8310
|
+
state.forwardEdge,
|
|
8311
|
+
{ edgeSrc: state.edgeSrc, edgeDst: state.edgeDst, S, ends, counters, depthK, sigmaK },
|
|
8312
|
+
forwardFields,
|
|
8313
|
+
forwardItems
|
|
8314
|
+
);
|
|
8315
|
+
} else {
|
|
8316
|
+
recordBc(
|
|
8317
|
+
state,
|
|
8318
|
+
pass,
|
|
8319
|
+
state.forward,
|
|
8320
|
+
{ rowPtr: state.rowPtr, colIdx: state.colIdx, S, ends, counters, depthK, sigmaK },
|
|
8321
|
+
forwardFields,
|
|
8322
|
+
forwardItems
|
|
8323
|
+
);
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
recorded += levelsPerSubmit;
|
|
8327
|
+
batch.endPass();
|
|
8328
|
+
const endsCount = Math.min(n + 2, recorded + 1);
|
|
8329
|
+
const countersRequest = batch.readback(counters.buffer, counters.offset, FRONTIER_COUNTERS.byteLength);
|
|
8330
|
+
const endsRequest = batch.readback(ends.buffer, ends.offset, 4 * endsCount);
|
|
8331
|
+
const back = await submit(state, batch, signal);
|
|
8332
|
+
const words32 = new Uint32Array(back, countersRequest.offset, FRONTIER_COUNTERS.byteLength / 4);
|
|
8333
|
+
if (words32[W.done] !== 0) {
|
|
8334
|
+
levels = words32[W.level];
|
|
8335
|
+
overflow = words32[W.sigmaOverflow] !== 0;
|
|
8336
|
+
endsWords = new Uint32Array(back, endsRequest.offset, endsCount).slice(0, levels + 1);
|
|
8337
|
+
} else if (recorded > n + 2) {
|
|
8338
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$4}: the done flag never rose in ${recorded} levels`, {
|
|
8339
|
+
label: ALGORITHM$4,
|
|
8340
|
+
message: `the done flag never rose in ${recorded} levels`
|
|
8341
|
+
});
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
const backwardLevels = [];
|
|
8345
|
+
for (let level = levels - 1; level >= 1; level--) {
|
|
8346
|
+
backwardLevels.push(level);
|
|
8347
|
+
}
|
|
8348
|
+
let arrays = null;
|
|
8349
|
+
for (let i = 0; ; i += BC_BACKWARD_LEVELS_PER_SUBMIT) {
|
|
8350
|
+
const chunk = backwardLevels.slice(i, i + BC_BACKWARD_LEVELS_PER_SUBMIT);
|
|
8351
|
+
const last = i + BC_BACKWARD_LEVELS_PER_SUBMIT >= backwardLevels.length;
|
|
8352
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$4}/backward`);
|
|
8353
|
+
const pass = batch.pass("backward");
|
|
8354
|
+
for (const level of chunk) {
|
|
8355
|
+
const start = endsWords[level];
|
|
8356
|
+
const count = endsWords[level + 1] - start;
|
|
8357
|
+
recordBc(
|
|
8358
|
+
state,
|
|
8359
|
+
pass,
|
|
8360
|
+
state.backward,
|
|
8361
|
+
{ rowPtr: state.rowPtr, colIdx: state.colIdx, S, depthK, sigmaK, deltaK },
|
|
8362
|
+
{ n, k, start, count },
|
|
8363
|
+
count
|
|
8364
|
+
);
|
|
8365
|
+
}
|
|
8366
|
+
if (last) {
|
|
8367
|
+
recordBc(state, pass, state.gather, { deltaK, bc: state.bc }, { n, k }, n);
|
|
8368
|
+
if (state.edgeGather !== null && state.arcScores !== null) {
|
|
8369
|
+
recordBc(
|
|
8370
|
+
state,
|
|
8371
|
+
pass,
|
|
8372
|
+
state.edgeGather,
|
|
8373
|
+
{ rowPtr: state.rowPtr, colIdx: state.colIdx, depthK, sigmaK, deltaK, arcScores: state.arcScores },
|
|
8374
|
+
{ n, k, count: state.arcCount },
|
|
8375
|
+
state.arcCount
|
|
8376
|
+
);
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
batch.endPass();
|
|
8380
|
+
const wantArrays = last && tuning.readArrays === true;
|
|
8381
|
+
const requests = wantArrays ? [depthK, sigmaK, deltaK].map((b) => batch.readback(b.buffer, b.offset, 4 * words)) : [];
|
|
8382
|
+
const back = await submit(state, batch, signal);
|
|
8383
|
+
if (wantArrays) {
|
|
8384
|
+
arrays = {
|
|
8385
|
+
depthK: new Uint32Array(back, requests[0].offset, words).slice(),
|
|
8386
|
+
sigmaK: new Uint32Array(back, requests[1].offset, words).slice(),
|
|
8387
|
+
deltaK: new Float32Array(back, requests[2].offset, words).slice()
|
|
8388
|
+
};
|
|
8389
|
+
}
|
|
8390
|
+
if (last) {
|
|
8391
|
+
break;
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
8394
|
+
tuning.onBatch?.({
|
|
8395
|
+
sources,
|
|
8396
|
+
forward: form,
|
|
8397
|
+
levels,
|
|
8398
|
+
ends: endsWords.slice(),
|
|
8399
|
+
sigmaOverflow: overflow,
|
|
8400
|
+
depthK: arrays?.depthK ?? null,
|
|
8401
|
+
sigmaK: arrays?.sigmaK ?? null,
|
|
8402
|
+
deltaK: arrays?.deltaK ?? null
|
|
8403
|
+
});
|
|
8404
|
+
return { levels, overflow };
|
|
8405
|
+
}
|
|
8406
|
+
async function runRaw(ctx, s, sources, withEdges, tuning, options) {
|
|
8407
|
+
const n = s.nodeCount;
|
|
8408
|
+
const pinned = tuning.forward ?? "auto";
|
|
8409
|
+
const levelsPerSubmit = tuning.levelsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
|
|
8410
|
+
if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
8411
|
+
throw badArgument("levelsPerSubmit", levelsPerSubmit, `an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`);
|
|
8412
|
+
}
|
|
8413
|
+
const limits = tuning.limits ?? ctx.caps.limits;
|
|
8414
|
+
const kMax = planBatchSize(n, sources.length, limits);
|
|
8415
|
+
const core = ctx.residency.core(s);
|
|
8416
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$4);
|
|
8417
|
+
const { edgeCount } = s;
|
|
8418
|
+
const mayRunEdge = pinned === "edge" || pinned === "auto" && sources.length > kMax;
|
|
8419
|
+
const edgeView = mayRunEdge && edgeCount > 0 ? ctx.residency.view(s, "edgeList") : null;
|
|
8420
|
+
const scope = algorithmScope(ctx, ALGORITHM$4, RING_SLOTS$3);
|
|
8421
|
+
try {
|
|
8422
|
+
const arrayBytes = 4 * n * kMax;
|
|
8423
|
+
const lease = (bytes, label) => bindingOf(scope.scratch(bytes, label), bytes);
|
|
8424
|
+
const arcBytes = 4 * Math.max(1, s.arcCount);
|
|
8425
|
+
const [fill, finalize, forward, backward, gather] = await Promise.all(
|
|
8426
|
+
["fill", "bc-finalize", "bc-forward", "bc-backward", "bc-gather"].map(
|
|
8427
|
+
(id) => ctx.pipelines.kernel(kernelSpec(id))
|
|
8428
|
+
)
|
|
8429
|
+
);
|
|
8430
|
+
const forwardEdge = edgeView === null ? null : await ctx.pipelines.kernel(kernelSpec("bc-forward-edge", { UNDIRECTED: !s.directed }));
|
|
8431
|
+
const edgeGather = withEdges ? await ctx.pipelines.kernel(kernelSpec("bc-edge-gather")) : null;
|
|
8432
|
+
const state = {
|
|
8433
|
+
ctx,
|
|
8434
|
+
scope,
|
|
8435
|
+
n,
|
|
8436
|
+
S: lease(arrayBytes, "S"),
|
|
8437
|
+
ends: lease(4 * (n + 2), "ends"),
|
|
8438
|
+
depthK: lease(arrayBytes, "depthK"),
|
|
8439
|
+
sigmaK: lease(arrayBytes, "sigmaK"),
|
|
8440
|
+
deltaK: lease(arrayBytes, "deltaK"),
|
|
8441
|
+
counters: lease(FRONTIER_COUNTERS.byteLength, "counters"),
|
|
8442
|
+
bc: lease(4 * n, "bc"),
|
|
8443
|
+
arcScores: withEdges ? lease(arcBytes, "arc-scores") : null,
|
|
8444
|
+
rowPtr: core.rowPtr,
|
|
8445
|
+
colIdx: core.colIdx ?? core.rowPtr,
|
|
8446
|
+
edgeSrc: edgeView?.bindings.src ?? null,
|
|
8447
|
+
edgeDst: edgeView?.bindings.dst ?? null,
|
|
8448
|
+
edgeCount,
|
|
8449
|
+
arcCount: s.arcCount,
|
|
8450
|
+
fill,
|
|
8451
|
+
finalize,
|
|
8452
|
+
forward,
|
|
8453
|
+
forwardEdge,
|
|
8454
|
+
backward,
|
|
8455
|
+
gather,
|
|
8456
|
+
edgeGather,
|
|
8457
|
+
bound: /* @__PURE__ */ new Map()
|
|
8458
|
+
};
|
|
8459
|
+
await ctx.allocator.check();
|
|
8460
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$4}/setup`);
|
|
8461
|
+
const setupPass = setup.pass("setup");
|
|
8462
|
+
recordFill(state, setupPass, state.bc, n, 0);
|
|
8463
|
+
if (state.arcScores !== null) {
|
|
8464
|
+
recordFill(state, setupPass, state.arcScores, arcBytes / 4, 0);
|
|
8465
|
+
}
|
|
8466
|
+
setup.endPass();
|
|
8467
|
+
await submit(state, setup, options?.signal);
|
|
8468
|
+
let overflow = false;
|
|
8469
|
+
let batches = 0;
|
|
8470
|
+
let previousLevels = -1;
|
|
8471
|
+
for (let start = 0; start < sources.length; ) {
|
|
8472
|
+
const k = planBatchSize(n, sources.length - start, limits);
|
|
8473
|
+
let form = pinned === "edge" ? "edge" : "frontier";
|
|
8474
|
+
if (pinned === "auto" && previousLevels >= 0) {
|
|
8475
|
+
form = previousLevels < BC_EDGE_PARALLEL_GAMMA * Math.log2(n) ? "edge" : "frontier";
|
|
8476
|
+
}
|
|
8477
|
+
if (state.forwardEdge === null) {
|
|
8478
|
+
form = "frontier";
|
|
8479
|
+
}
|
|
8480
|
+
const batch = sources.slice(start, start + k);
|
|
8481
|
+
const outcome = await runBatch(state, batch, form, levelsPerSubmit, tuning, options?.signal);
|
|
8482
|
+
overflow = overflow || outcome.overflow;
|
|
8483
|
+
previousLevels = outcome.levels;
|
|
8484
|
+
start += k;
|
|
8485
|
+
batches += 1;
|
|
8486
|
+
options?.onProgress?.(start, sources.length);
|
|
8487
|
+
}
|
|
8488
|
+
const result = new CommandBatch(ctx, `${ALGORITHM$4}/result`);
|
|
8489
|
+
const vertexRequest = result.readback(state.bc.buffer, state.bc.offset, 4 * n);
|
|
8490
|
+
const arcRequest = state.arcScores === null ? null : result.readback(state.arcScores.buffer, state.arcScores.offset, 4 * s.arcCount);
|
|
8491
|
+
const back = await submit(state, result, options?.signal);
|
|
8492
|
+
return {
|
|
8493
|
+
vertex: new Float32Array(back, vertexRequest.offset, n).slice(),
|
|
8494
|
+
perArc: arcRequest === null ? null : new Float32Array(back, arcRequest.offset, s.arcCount).slice(),
|
|
8495
|
+
sourcesUsed: sources.length,
|
|
8496
|
+
sigmaOverflow: overflow,
|
|
8497
|
+
batches
|
|
8498
|
+
};
|
|
8499
|
+
} finally {
|
|
8500
|
+
scope.dispose();
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8503
|
+
function normaliser(s, normalized) {
|
|
8504
|
+
const n = s.nodeCount;
|
|
8505
|
+
const factor = s.directed ? (n - 1) * (n - 2) : (n - 1) * (n - 2) / 2;
|
|
8506
|
+
return normalized === true && factor > 0 ? factor : 1;
|
|
8507
|
+
}
|
|
8508
|
+
async function precheck(ctx, options) {
|
|
8509
|
+
ctx.assertReady();
|
|
8510
|
+
await assertDeviceComputes(ctx);
|
|
8511
|
+
if (options?.endpoints === true) {
|
|
8512
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$4}: endpoints: true is not supported`, {
|
|
8513
|
+
feature: "betweenness.endpoints",
|
|
8514
|
+
hint: "the CPU endpoints branch in algorithms/src/algorithms/centrality/betweenness.ts (predecessors.length === 0 && w !== source) never fires, so there is no convention to match"
|
|
8515
|
+
});
|
|
8516
|
+
}
|
|
8517
|
+
}
|
|
8518
|
+
async function betweennessWithTuning(ctx, s, options, tuning) {
|
|
8519
|
+
await precheck(ctx, options);
|
|
8520
|
+
const n = s.nodeCount;
|
|
8521
|
+
const scores = checkDest$2(ALGORITHM$4, options?.dest, n) ?? new Float32Array(n);
|
|
8522
|
+
const sources = resolveSources(options, n);
|
|
8523
|
+
if (options?.signal?.aborted) {
|
|
8524
|
+
throw aborted(ALGORITHM$4);
|
|
8525
|
+
}
|
|
8526
|
+
if (n === 0 || sources.length === 0) {
|
|
8527
|
+
scores.fill(0);
|
|
8528
|
+
return { scores, iterations: 0, converged: true, precision: "f32", sourcesUsed: 0, sigmaOverflow: false };
|
|
8529
|
+
}
|
|
8530
|
+
const raw = await runRaw(ctx, s, sources, false, tuning, options);
|
|
8531
|
+
const divisor = (s.directed ? 1 : 2) * normaliser(s, options?.normalized);
|
|
8532
|
+
for (let v = 0; v < n; v++) {
|
|
8533
|
+
scores[v] = raw.vertex[v] / divisor;
|
|
8534
|
+
}
|
|
8535
|
+
return {
|
|
8536
|
+
scores,
|
|
8537
|
+
iterations: raw.batches,
|
|
8538
|
+
converged: true,
|
|
8539
|
+
precision: "f32",
|
|
8540
|
+
sourcesUsed: raw.sourcesUsed,
|
|
8541
|
+
sigmaOverflow: raw.sigmaOverflow
|
|
8542
|
+
};
|
|
8543
|
+
}
|
|
8544
|
+
async function edgeBetweennessWithTuning(ctx, s, options, tuning, onArcs) {
|
|
8545
|
+
await precheck(ctx, options);
|
|
8546
|
+
const n = s.nodeCount;
|
|
8547
|
+
const scores = checkDest$2("edgeBetweennessCentrality", options?.dest, s.edgeCount) ?? new Float32Array(s.edgeCount);
|
|
8548
|
+
const sources = resolveSources(options, n);
|
|
8549
|
+
if (options?.signal?.aborted) {
|
|
8550
|
+
throw aborted(ALGORITHM$4);
|
|
8551
|
+
}
|
|
8552
|
+
if (n === 0 || sources.length === 0 || s.arcCount === 0) {
|
|
8553
|
+
scores.fill(0);
|
|
8554
|
+
return { scores, precision: "f32", sourcesUsed: sources.length, sigmaOverflow: false };
|
|
8555
|
+
}
|
|
8556
|
+
const raw = await runRaw(ctx, s, sources, true, tuning, options);
|
|
8557
|
+
const perArc = raw.perArc ?? new Float32Array(s.arcCount);
|
|
8558
|
+
const folded = foldArcs(s, perArc, "sum");
|
|
8559
|
+
const divisor = (s.directed ? 1 : 2) * normaliser(s, options?.normalized);
|
|
8560
|
+
for (let e = 0; e < s.edgeCount; e++) {
|
|
8561
|
+
scores[e] = folded[e] / divisor;
|
|
8562
|
+
}
|
|
8563
|
+
return { scores, precision: "f32", sourcesUsed: raw.sourcesUsed, sigmaOverflow: raw.sigmaOverflow };
|
|
8564
|
+
}
|
|
8565
|
+
function betweennessCentrality(ctx, s, options) {
|
|
8566
|
+
return betweennessWithTuning(ctx, s, options, {});
|
|
8567
|
+
}
|
|
8568
|
+
function edgeBetweennessCentrality(ctx, s, options) {
|
|
8569
|
+
return edgeBetweennessWithTuning(ctx, s, options, {});
|
|
8570
|
+
}
|
|
8571
|
+
const ALGORITHM$3 = "closenessCentrality";
|
|
7203
8572
|
const SOURCES_PER_BATCH = 32;
|
|
7204
8573
|
const PER_SOURCE_WORDS = 4 * SOURCES_PER_BATCH;
|
|
7205
|
-
const RING_SLOTS = 8 * MAX_LEVELS_PER_SUBMIT + 16;
|
|
8574
|
+
const RING_SLOTS$2 = 8 * MAX_LEVELS_PER_SUBMIT + 16;
|
|
7206
8575
|
function reusingScratch(scope) {
|
|
7207
8576
|
const held = /* @__PURE__ */ new Map();
|
|
7208
8577
|
return {
|
|
@@ -7218,33 +8587,48 @@ function reusingScratch(scope) {
|
|
|
7218
8587
|
}
|
|
7219
8588
|
};
|
|
7220
8589
|
}
|
|
7221
|
-
async function weightedRoute(ctx, s, scores, options) {
|
|
8590
|
+
async function weightedRoute(ctx, s, scores, sources, options) {
|
|
7222
8591
|
const n = s.nodeCount;
|
|
7223
|
-
|
|
8592
|
+
const count = sources?.length ?? n;
|
|
8593
|
+
const totals = sources === null ? null : new Float64Array(n);
|
|
8594
|
+
for (let i = 0; i < count; i++) {
|
|
7224
8595
|
if (options?.signal?.aborted) {
|
|
7225
|
-
throw aborted(ALGORITHM);
|
|
8596
|
+
throw aborted(ALGORITHM$3);
|
|
7226
8597
|
}
|
|
8598
|
+
const source = sources === null ? i : sources[i];
|
|
7227
8599
|
const { dist } = await sssp(ctx, s, source, { signal: options?.signal });
|
|
7228
8600
|
let sum = 0;
|
|
7229
8601
|
for (let v = 0; v < n; v++) {
|
|
7230
8602
|
const d = dist[v];
|
|
7231
8603
|
if (v !== source && d !== Infinity) {
|
|
7232
|
-
|
|
8604
|
+
if (totals === null) {
|
|
8605
|
+
sum += d;
|
|
8606
|
+
} else {
|
|
8607
|
+
totals[v] += d;
|
|
8608
|
+
}
|
|
7233
8609
|
}
|
|
7234
8610
|
}
|
|
7235
|
-
|
|
7236
|
-
|
|
8611
|
+
if (totals === null) {
|
|
8612
|
+
scores[source] = sum === 0 ? 0 : 1 / sum;
|
|
8613
|
+
}
|
|
8614
|
+
options?.onProgress?.(i + 1, count);
|
|
8615
|
+
}
|
|
8616
|
+
if (totals !== null) {
|
|
8617
|
+
totals.forEach((sum, v) => {
|
|
8618
|
+
scores[v] = sum === 0 ? 0 : 1 / sum;
|
|
8619
|
+
});
|
|
7237
8620
|
}
|
|
7238
|
-
return { scores, iterations:
|
|
8621
|
+
return { scores, iterations: count, converged: true, precision: "f32", sourcesUsed: count };
|
|
7239
8622
|
}
|
|
7240
|
-
async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
|
|
8623
|
+
async function sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tuning) {
|
|
7241
8624
|
const n = s.nodeCount;
|
|
7242
|
-
|
|
7243
|
-
|
|
8625
|
+
const seedCount = sources?.length ?? n;
|
|
8626
|
+
if (seedCount === 0) {
|
|
8627
|
+
return { scores, iterations: 0, converged: true, precision: "f32", sourcesUsed: 0 };
|
|
7244
8628
|
}
|
|
7245
8629
|
const core = ctx.residency.core(s);
|
|
7246
|
-
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM);
|
|
7247
|
-
const scope = algorithmScope(ctx, ALGORITHM, RING_SLOTS);
|
|
8630
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$3);
|
|
8631
|
+
const scope = algorithmScope(ctx, ALGORITHM$3, RING_SLOTS$2);
|
|
7248
8632
|
try {
|
|
7249
8633
|
const wg = ctx.workgroupSize;
|
|
7250
8634
|
const bytes = 4 * n;
|
|
@@ -7265,7 +8649,17 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
|
|
|
7265
8649
|
FRONTIER_COUNTERS.byteLength
|
|
7266
8650
|
);
|
|
7267
8651
|
const perSourceBytes = 4 * PER_SOURCE_WORDS;
|
|
7268
|
-
const
|
|
8652
|
+
const zeroedWords = PER_SOURCE_WORDS + (sources === null ? 0 : bitsBase);
|
|
8653
|
+
const perSourceAll = 4 * (zeroedWords + (sources === null ? 0 : sources.length));
|
|
8654
|
+
const perSource = bindingOf(scope.scratch(perSourceAll, "per-source"), perSourceAll);
|
|
8655
|
+
if (sources !== null) {
|
|
8656
|
+
ctx.device.queue.writeBuffer(
|
|
8657
|
+
perSource.buffer,
|
|
8658
|
+
perSource.offset + 4 * zeroedWords,
|
|
8659
|
+
Uint32Array.from(sources)
|
|
8660
|
+
);
|
|
8661
|
+
}
|
|
8662
|
+
const totals = sources === null ? null : new Float64Array(n);
|
|
7269
8663
|
await ctx.allocator.check();
|
|
7270
8664
|
const compact = await prepareCompact(reusingScratch(scope));
|
|
7271
8665
|
const sweep = await ctx.pipelines.kernel(kernelSpec("closeness-sweep", graphOverrides(core, null)));
|
|
@@ -7275,29 +8669,34 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
|
|
|
7275
8669
|
const onePlan = plan1d(1, wg, ctx.caps);
|
|
7276
8670
|
const regionPlan = plan1d(bitsBase, wg, ctx.caps);
|
|
7277
8671
|
const sweepPlan = planGridStride(n, wg, ctx.caps);
|
|
7278
|
-
const
|
|
8672
|
+
const recordFill2 = (pass, dst, count, mode) => {
|
|
7279
8673
|
const params = scope.params(FILL_PARAMS, { count, value: 0, mode, pad0: 0 });
|
|
7280
8674
|
fill.dispatch(pass, fill.bind({ dst, P: params.binding }), plan1d(count, wg, ctx.caps), [params.offset]);
|
|
7281
8675
|
};
|
|
7282
|
-
const
|
|
8676
|
+
const submit2 = (batch) => {
|
|
7283
8677
|
scope.flush();
|
|
7284
8678
|
return batch.submit();
|
|
7285
8679
|
};
|
|
7286
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM}/setup`);
|
|
7287
|
-
|
|
8680
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$3}/setup`);
|
|
8681
|
+
recordFill2(setup.pass("fill"), iota, n, 1);
|
|
7288
8682
|
setup.endPass();
|
|
7289
|
-
await
|
|
8683
|
+
await submit2(setup).readback;
|
|
7290
8684
|
ctx.assertReady();
|
|
7291
8685
|
let batches = 0;
|
|
7292
|
-
for (let batchStart = 0; batchStart <
|
|
8686
|
+
for (let batchStart = 0; batchStart < seedCount; batchStart += SOURCES_PER_BATCH) {
|
|
7293
8687
|
let level = 0;
|
|
7294
8688
|
for (let first = true; ; first = false) {
|
|
7295
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM}/levels`);
|
|
8689
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$3}/levels`);
|
|
7296
8690
|
const pass = batch.pass("closeness");
|
|
7297
8691
|
if (first) {
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
const seed = scope.params(FRONTIER_PARAMS, {
|
|
8692
|
+
recordFill2(pass, bits, 4 * bitsBase, 0);
|
|
8693
|
+
recordFill2(pass, perSource, zeroedWords, 0);
|
|
8694
|
+
const seed = scope.params(FRONTIER_PARAMS, {
|
|
8695
|
+
role: sources === null ? 1 : 2,
|
|
8696
|
+
n: seedCount,
|
|
8697
|
+
bitsBase,
|
|
8698
|
+
source: batchStart
|
|
8699
|
+
});
|
|
7301
8700
|
reduce.dispatch(pass, reduce.bind({ counters, perSource, bits, P: seed.binding }), onePlan, [
|
|
7302
8701
|
seed.offset
|
|
7303
8702
|
]);
|
|
@@ -7315,7 +8714,8 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
|
|
|
7315
8714
|
arcBase: 0,
|
|
7316
8715
|
arcEnd: s.arcCount,
|
|
7317
8716
|
mode,
|
|
7318
|
-
stride: sweepPlan.stride ?? wg
|
|
8717
|
+
stride: sweepPlan.stride ?? wg,
|
|
8718
|
+
perNode: sources === null ? 0 : 1
|
|
7319
8719
|
});
|
|
7320
8720
|
return {
|
|
7321
8721
|
bound: sweep.bind({ ...graph, frontierList, counters, bits, perSource, P: params.binding }),
|
|
@@ -7340,18 +8740,26 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
|
|
|
7340
8740
|
batch.endPass();
|
|
7341
8741
|
const doneRequest = batch.readback(counters.buffer, counters.offset + 4 * W.done, 4);
|
|
7342
8742
|
const blockRequest = batch.readback(perSource.buffer, perSource.offset, perSourceBytes);
|
|
7343
|
-
const
|
|
8743
|
+
const nodeRequest = totals === null ? null : batch.readback(perSource.buffer, perSource.offset + perSourceBytes, 4 * n);
|
|
8744
|
+
const submitted = submit2(batch);
|
|
7344
8745
|
const back = await submitted.readback;
|
|
7345
8746
|
ctx.assertReady();
|
|
7346
8747
|
if (options?.signal?.aborted) {
|
|
7347
|
-
throw aborted(ALGORITHM, submitted.id);
|
|
8748
|
+
throw aborted(ALGORITHM$3, submitted.id);
|
|
7348
8749
|
}
|
|
7349
8750
|
if (new Uint32Array(back, doneRequest.offset, 1)[0] !== 0) {
|
|
7350
8751
|
const block = new Uint32Array(back, blockRequest.offset, PER_SOURCE_WORDS);
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
8752
|
+
if (totals === null || nodeRequest === null) {
|
|
8753
|
+
const count = Math.min(SOURCES_PER_BATCH, n - batchStart);
|
|
8754
|
+
for (let i = 0; i < count; i++) {
|
|
8755
|
+
const sum = block[3 * SOURCES_PER_BATCH + i] * 2 ** 32 + block[2 * SOURCES_PER_BATCH + i];
|
|
8756
|
+
scores[batchStart + i] = sum === 0 ? 0 : 1 / sum;
|
|
8757
|
+
}
|
|
8758
|
+
} else {
|
|
8759
|
+
const sums = new Uint32Array(back, nodeRequest.offset, n);
|
|
8760
|
+
for (let v = 0; v < n; v++) {
|
|
8761
|
+
totals[v] += sums[v];
|
|
8762
|
+
}
|
|
7355
8763
|
}
|
|
7356
8764
|
tuning.onBatch?.(batchStart, block.slice());
|
|
7357
8765
|
break;
|
|
@@ -7359,36 +8767,61 @@ async function sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning) {
|
|
|
7359
8767
|
if (level > n + 3) {
|
|
7360
8768
|
throw new WebGpuGraphError(
|
|
7361
8769
|
"E_VALIDATION",
|
|
7362
|
-
`${ALGORITHM}: the done flag never rose in ${level} levels of the batch at ${batchStart}`,
|
|
7363
|
-
{ label: ALGORITHM, message: `the done flag never rose in ${level} levels` }
|
|
8770
|
+
`${ALGORITHM$3}: the done flag never rose in ${level} levels of the batch at ${batchStart}`,
|
|
8771
|
+
{ label: ALGORITHM$3, message: `the done flag never rose in ${level} levels` }
|
|
7364
8772
|
);
|
|
7365
8773
|
}
|
|
7366
8774
|
}
|
|
7367
8775
|
batches += 1;
|
|
7368
|
-
options?.onProgress?.(Math.min(batchStart + SOURCES_PER_BATCH,
|
|
8776
|
+
options?.onProgress?.(Math.min(batchStart + SOURCES_PER_BATCH, seedCount), seedCount);
|
|
7369
8777
|
}
|
|
7370
|
-
|
|
8778
|
+
totals?.forEach((sum, v) => {
|
|
8779
|
+
scores[v] = sum === 0 ? 0 : 1 / sum;
|
|
8780
|
+
});
|
|
8781
|
+
return { scores, iterations: batches, converged: true, precision: "f32", sourcesUsed: seedCount };
|
|
7371
8782
|
} finally {
|
|
7372
8783
|
scope.dispose();
|
|
7373
8784
|
}
|
|
7374
8785
|
}
|
|
8786
|
+
function checkSources(s, sources) {
|
|
8787
|
+
if (sources === void 0) {
|
|
8788
|
+
return null;
|
|
8789
|
+
}
|
|
8790
|
+
if (s.directed) {
|
|
8791
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$3}: sampled sources need an undirected snapshot`, {
|
|
8792
|
+
feature: "closenessCentrality.directedSources",
|
|
8793
|
+
hint: "run the CPU port, which searches the in-arcs"
|
|
8794
|
+
});
|
|
8795
|
+
}
|
|
8796
|
+
for (const v of sources) {
|
|
8797
|
+
if (!Number.isInteger(v) || v < 0 || v >= s.nodeCount) {
|
|
8798
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$3}: a source is not a node index`, {
|
|
8799
|
+
argument: "sources",
|
|
8800
|
+
value: v,
|
|
8801
|
+
expected: `an integer in [0, ${s.nodeCount})`
|
|
8802
|
+
});
|
|
8803
|
+
}
|
|
8804
|
+
}
|
|
8805
|
+
return sources;
|
|
8806
|
+
}
|
|
7375
8807
|
async function closenessWithTuning(ctx, s, options, tuning) {
|
|
7376
8808
|
ctx.assertReady();
|
|
7377
8809
|
await assertDeviceComputes(ctx);
|
|
7378
8810
|
for (const key of ["maxIterations", "tolerance"]) {
|
|
7379
8811
|
if (options?.[key] !== void 0) {
|
|
7380
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM}: ${key} has no meaning for an exact traversal`, {
|
|
8812
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$3}: ${key} has no meaning for an exact traversal`, {
|
|
7381
8813
|
option: key,
|
|
7382
8814
|
hint: "closeness is an exact traversal; the option has no meaning here"
|
|
7383
8815
|
});
|
|
7384
8816
|
}
|
|
7385
8817
|
}
|
|
7386
8818
|
const n = s.nodeCount;
|
|
8819
|
+
const sources = checkSources(s, options?.sources);
|
|
7387
8820
|
const levelsPerSubmit = tuning.levelsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
|
|
7388
8821
|
if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
7389
8822
|
throw new WebGpuGraphError(
|
|
7390
8823
|
"E_INVALID_ARGUMENT",
|
|
7391
|
-
`${ALGORITHM}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
8824
|
+
`${ALGORITHM$3}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
7392
8825
|
{
|
|
7393
8826
|
argument: "levelsPerSubmit",
|
|
7394
8827
|
value: levelsPerSubmit,
|
|
@@ -7396,16 +8829,16 @@ async function closenessWithTuning(ctx, s, options, tuning) {
|
|
|
7396
8829
|
}
|
|
7397
8830
|
);
|
|
7398
8831
|
}
|
|
7399
|
-
const scores = checkDest(ALGORITHM, options?.dest, n) ?? new Float32Array(n);
|
|
8832
|
+
const scores = checkDest$2(ALGORITHM$3, options?.dest, n) ?? new Float32Array(n);
|
|
7400
8833
|
const weighted = options?.weighted ?? s.flags.weighted;
|
|
7401
8834
|
if (options?.signal?.aborted) {
|
|
7402
|
-
throw aborted(ALGORITHM);
|
|
8835
|
+
throw aborted(ALGORITHM$3);
|
|
7403
8836
|
}
|
|
7404
8837
|
if (weighted && s.weights !== null && !s.flags.allWeightsOne) {
|
|
7405
8838
|
if (!s.flags.nonNegativeWeights) {
|
|
7406
8839
|
throw new WebGpuGraphError(
|
|
7407
8840
|
"E_UNSUPPORTED",
|
|
7408
|
-
`${ALGORITHM}: a negative weight has no shortest-path distance to sum`,
|
|
8841
|
+
`${ALGORITHM$3}: a negative weight has no shortest-path distance to sum`,
|
|
7409
8842
|
{
|
|
7410
8843
|
feature: "closenessCentrality.negativeWeights",
|
|
7411
8844
|
hint: "pass weighted: false to ignore the column"
|
|
@@ -7413,24 +8846,261 @@ async function closenessWithTuning(ctx, s, options, tuning) {
|
|
|
7413
8846
|
);
|
|
7414
8847
|
}
|
|
7415
8848
|
if (!s.flags.finiteWeights) {
|
|
7416
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM}: a NaN or infinite weight has no shortest path`, {
|
|
8849
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$3}: a NaN or infinite weight has no shortest path`, {
|
|
7417
8850
|
feature: "closenessCentrality.nonFiniteWeights"
|
|
7418
8851
|
});
|
|
7419
8852
|
}
|
|
7420
|
-
return weightedRoute(ctx, s, scores, options);
|
|
8853
|
+
return weightedRoute(ctx, s, scores, sources, options);
|
|
7421
8854
|
}
|
|
7422
|
-
return sweepRoute(ctx, s, scores, levelsPerSubmit, options, tuning);
|
|
8855
|
+
return sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tuning);
|
|
7423
8856
|
}
|
|
7424
8857
|
function closenessCentrality(ctx, s, options) {
|
|
7425
8858
|
return closenessWithTuning(ctx, s, options, {});
|
|
7426
8859
|
}
|
|
7427
|
-
const
|
|
7428
|
-
|
|
7429
|
-
|
|
8860
|
+
const ALGORITHM$2 = "allPairsShortestPath";
|
|
8861
|
+
const DEFAULT_ROUNDS_PER_SUBMIT = Math.floor(APSP_MAX_DISPATCHES_PER_SUBMIT / 3);
|
|
8862
|
+
function allPairsCeiling(limits) {
|
|
8863
|
+
const binding = limits.maxStorageBufferBindingSize;
|
|
8864
|
+
const bufferSize = limits.maxBufferSize;
|
|
8865
|
+
const limitName = bufferSize < binding ? "maxBufferSize" : "maxStorageBufferBindingSize";
|
|
8866
|
+
const limit = Math.min(binding, bufferSize);
|
|
8867
|
+
let maxNodes = Math.floor(Math.sqrt(limit / 4));
|
|
8868
|
+
while (4 * maxNodes * maxNodes > limit) {
|
|
8869
|
+
maxNodes -= 1;
|
|
8870
|
+
}
|
|
8871
|
+
while (4 * (maxNodes + 1) * (maxNodes + 1) <= limit) {
|
|
8872
|
+
maxNodes += 1;
|
|
8873
|
+
}
|
|
8874
|
+
return { maxNodes, limit, limitName };
|
|
7430
8875
|
}
|
|
7431
|
-
async function
|
|
7432
|
-
|
|
7433
|
-
const
|
|
8876
|
+
async function allPairsWithTuning(ctx, s, options, tuning) {
|
|
8877
|
+
ctx.assertReady();
|
|
8878
|
+
const n = s.nodeCount;
|
|
8879
|
+
const roundsPerSubmit = tuning.roundsPerSubmit ?? DEFAULT_ROUNDS_PER_SUBMIT;
|
|
8880
|
+
if (!Number.isInteger(roundsPerSubmit) || roundsPerSubmit < 1 || roundsPerSubmit > DEFAULT_ROUNDS_PER_SUBMIT) {
|
|
8881
|
+
throw new WebGpuGraphError(
|
|
8882
|
+
"E_INVALID_ARGUMENT",
|
|
8883
|
+
`${ALGORITHM$2}: roundsPerSubmit must be an integer in [1, ${DEFAULT_ROUNDS_PER_SUBMIT}]`,
|
|
8884
|
+
{
|
|
8885
|
+
argument: "roundsPerSubmit",
|
|
8886
|
+
value: roundsPerSubmit,
|
|
8887
|
+
expected: `an integer in [1, ${DEFAULT_ROUNDS_PER_SUBMIT}]`
|
|
8888
|
+
}
|
|
8889
|
+
);
|
|
8890
|
+
}
|
|
8891
|
+
const { maxNodes, limit, limitName } = allPairsCeiling(ctx.caps.limits);
|
|
8892
|
+
if (n > maxNodes) {
|
|
8893
|
+
throw new WebGpuGraphError(
|
|
8894
|
+
"E_TOO_LARGE",
|
|
8895
|
+
`${ALGORITHM$2}: ${n} nodes need a ${4 * n * n}-byte distance matrix in one storage binding; this device's ${limitName} of ${limit} bytes holds at most ${maxNodes} nodes -- raise it through GpuContextOptions.limits`,
|
|
8896
|
+
{
|
|
8897
|
+
needed: 4 * n * n,
|
|
8898
|
+
limit,
|
|
8899
|
+
path: "allPairs.matrix",
|
|
8900
|
+
algorithm: ALGORITHM$2,
|
|
8901
|
+
nodes: n,
|
|
8902
|
+
maxNodes,
|
|
8903
|
+
limitName,
|
|
8904
|
+
hint: `raise ${limitName} through GpuContextOptions.limits`
|
|
8905
|
+
}
|
|
8906
|
+
);
|
|
8907
|
+
}
|
|
8908
|
+
const dist = checkDest$2(ALGORITHM$2, options?.dest, n * n) ?? new Float32Array(n * n);
|
|
8909
|
+
const weighted = (options?.weighted ?? s.weights !== null) && s.weights !== null && !s.flags.allWeightsOne;
|
|
8910
|
+
if (weighted && !s.flags.nonNegativeWeights) {
|
|
8911
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a negative weight is not supported`, {
|
|
8912
|
+
feature: "allPairs.negativeWeights",
|
|
8913
|
+
hint: "the blocked Floyd-Warshall sweep needs non-negative weights; pass weighted: false for hop counts"
|
|
8914
|
+
});
|
|
8915
|
+
}
|
|
8916
|
+
if (weighted && !s.flags.finiteWeights) {
|
|
8917
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$2}: a NaN or infinite weight has no shortest path`, {
|
|
8918
|
+
feature: "allPairs.nonFiniteWeights"
|
|
8919
|
+
});
|
|
8920
|
+
}
|
|
8921
|
+
if (options?.signal?.aborted) {
|
|
8922
|
+
throw aborted(ALGORITHM$2);
|
|
8923
|
+
}
|
|
8924
|
+
if (n === 0) {
|
|
8925
|
+
return { dist, n };
|
|
8926
|
+
}
|
|
8927
|
+
await assertDeviceComputes(ctx);
|
|
8928
|
+
const core = ctx.residency.core(s);
|
|
8929
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$2);
|
|
8930
|
+
const blocks = Math.ceil(n / APSP_TILE);
|
|
8931
|
+
const scope = algorithmScope(ctx, ALGORITHM$2, Math.min(roundsPerSubmit, blocks) + 2);
|
|
8932
|
+
try {
|
|
8933
|
+
const wg = ctx.workgroupSize;
|
|
8934
|
+
const bytes = 4 * n * n;
|
|
8935
|
+
const matrix = bindingOf(scope.scratch(bytes, "dist"), bytes);
|
|
8936
|
+
await ctx.allocator.check();
|
|
8937
|
+
const weightsBinding = weighted ? void 0 : null;
|
|
8938
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
8939
|
+
const init = await ctx.pipelines.kernel(kernelSpec("apsp-init", graphOverrides(core, null, weightsBinding)));
|
|
8940
|
+
const phases = await Promise.all(
|
|
8941
|
+
[0, 1, 2].map((phase) => ctx.pipelines.kernel(kernelSpec("apsp-fw", { PHASE: phase })))
|
|
8942
|
+
);
|
|
8943
|
+
const graph = graphBindings(core, null, weightsBinding);
|
|
8944
|
+
const others = blocks - 1;
|
|
8945
|
+
const phasePlans = [plan2d(1, ctx.caps), plan2d(2 * others, ctx.caps), plan2d(others * others, ctx.caps)];
|
|
8946
|
+
for (let first = 0; first < blocks; first += roundsPerSubmit) {
|
|
8947
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$2}/sweep`);
|
|
8948
|
+
const pass = batch.pass("apsp");
|
|
8949
|
+
if (first === 0) {
|
|
8950
|
+
const fillParams = scope.params(FILL_PARAMS, { count: n * n, value: F32_INF_BITS, mode: 0, pad0: 0 });
|
|
8951
|
+
fill.dispatch(pass, fill.bind({ dst: matrix, P: fillParams.binding }), plan1d(n * n, wg, ctx.caps), [
|
|
8952
|
+
fillParams.offset
|
|
8953
|
+
]);
|
|
8954
|
+
const initParams = scope.params(APSP_PARAMS, { n, round: 0, blocks, infBits: F32_INF_BITS });
|
|
8955
|
+
init.dispatch(
|
|
8956
|
+
pass,
|
|
8957
|
+
init.bind({ ...graph, dist: matrix, P: initParams.binding }),
|
|
8958
|
+
plan1d(n, wg, ctx.caps),
|
|
8959
|
+
[initParams.offset]
|
|
8960
|
+
);
|
|
8961
|
+
}
|
|
8962
|
+
const last = Math.min(first + roundsPerSubmit, blocks);
|
|
8963
|
+
for (let round = first; round < last; round++) {
|
|
8964
|
+
const params = scope.params(APSP_PARAMS, { n, round, blocks, infBits: F32_INF_BITS });
|
|
8965
|
+
for (let phase = 0; phase < 3; phase++) {
|
|
8966
|
+
const kernel = phases[phase];
|
|
8967
|
+
kernel.dispatch(pass, kernel.bind({ dist: matrix, P: params.binding }), phasePlans[phase], [
|
|
8968
|
+
params.offset
|
|
8969
|
+
]);
|
|
8970
|
+
}
|
|
8971
|
+
}
|
|
8972
|
+
batch.endPass();
|
|
8973
|
+
scope.flush();
|
|
8974
|
+
const submitted = batch.submit();
|
|
8975
|
+
await submitted.readback;
|
|
8976
|
+
ctx.assertReady();
|
|
8977
|
+
options?.onProgress?.(last, blocks);
|
|
8978
|
+
if (options?.signal?.aborted) {
|
|
8979
|
+
throw aborted(ALGORITHM$2, submitted.id);
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
await ctx.readback.read(matrix.buffer, bytes, dist);
|
|
8983
|
+
ctx.assertReady();
|
|
8984
|
+
return { dist, n };
|
|
8985
|
+
} finally {
|
|
8986
|
+
scope.dispose();
|
|
8987
|
+
}
|
|
8988
|
+
}
|
|
8989
|
+
function allPairsShortestPath(ctx, s, options) {
|
|
8990
|
+
return allPairsWithTuning(ctx, s, options, {});
|
|
8991
|
+
}
|
|
8992
|
+
function planGroupRows(lengthBound, threadMax = GROUP_ROW_THREAD_MAX) {
|
|
8993
|
+
if (!Number.isInteger(threadMax) || threadMax < 0 || threadMax > GROUP_ROW_THREAD_LIMIT) {
|
|
8994
|
+
throw new WebGpuGraphError(
|
|
8995
|
+
"E_INVALID_ARGUMENT",
|
|
8996
|
+
`planGroupRows: threadMax must be an integer in [0, ${GROUP_ROW_THREAD_LIMIT}]`,
|
|
8997
|
+
{ argument: "threadMax", value: threadMax, expected: `[0, ${GROUP_ROW_THREAD_LIMIT}]` }
|
|
8998
|
+
);
|
|
8999
|
+
}
|
|
9000
|
+
const n = lengthBound.length;
|
|
9001
|
+
const thread = [];
|
|
9002
|
+
const hash = [];
|
|
9003
|
+
for (let v = 0; v < n; v++) {
|
|
9004
|
+
(lengthBound[v] <= threadMax ? thread : hash).push(v);
|
|
9005
|
+
}
|
|
9006
|
+
const words = new Uint32Array(thread.length + 2 * hash.length);
|
|
9007
|
+
words.set(thread, 0);
|
|
9008
|
+
words.set(hash, thread.length);
|
|
9009
|
+
let next = 1;
|
|
9010
|
+
for (let g = 0; g < hash.length; g++) {
|
|
9011
|
+
words[thread.length + hash.length + g] = next;
|
|
9012
|
+
next += 2 * GROUP_HASH_LOAD_FACTOR * lengthBound[hash[g]];
|
|
9013
|
+
}
|
|
9014
|
+
return { words, threadCount: thread.length, hashCount: hash.length, regionWords: next };
|
|
9015
|
+
}
|
|
9016
|
+
async function prepareGroupByKeyRow(scope) {
|
|
9017
|
+
const kernels = /* @__PURE__ */ new Map();
|
|
9018
|
+
for (const tier of [0, 1]) {
|
|
9019
|
+
for (const weighted of [false, true]) {
|
|
9020
|
+
const spec = kernelSpec("group-by-key-row", { TIER: tier, WEIGHTED: weighted });
|
|
9021
|
+
kernels.set(`${tier}/${weighted}`, await scope.pipelines.kernel(spec));
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
9024
|
+
return new GroupByKeyPlannerImpl(scope, kernels);
|
|
9025
|
+
}
|
|
9026
|
+
function checkWords$2(name, binding, words) {
|
|
9027
|
+
if (binding.size < 4 * words) {
|
|
9028
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `groupByKeyRow: ${name} is smaller than 4 x ${words} bytes`, {
|
|
9029
|
+
argument: name,
|
|
9030
|
+
value: binding.size,
|
|
9031
|
+
expected: 4 * words
|
|
9032
|
+
});
|
|
9033
|
+
}
|
|
9034
|
+
}
|
|
9035
|
+
class GroupByKeyPlannerImpl {
|
|
9036
|
+
/**
|
|
9037
|
+
* Wraps the compiled variants; use prepareGroupByKeyRow().
|
|
9038
|
+
* @param scope - the caller's scope
|
|
9039
|
+
* @param kernels - the variants by `tier/weighted`
|
|
9040
|
+
*/
|
|
9041
|
+
constructor(scope, kernels) {
|
|
9042
|
+
this.scope = scope;
|
|
9043
|
+
this.kernels = kernels;
|
|
9044
|
+
}
|
|
9045
|
+
/**
|
|
9046
|
+
* Records the tier dispatches (see the interface).
|
|
9047
|
+
* @param pass - the compute pass
|
|
9048
|
+
* @param r - the record
|
|
9049
|
+
*/
|
|
9050
|
+
record(pass, r) {
|
|
9051
|
+
const { threadCount, hashCount } = r.plan;
|
|
9052
|
+
const rows = threadCount + hashCount;
|
|
9053
|
+
checkWords$2("rows", r.rows, rows + hashCount);
|
|
9054
|
+
checkWords$2("hashRegion", r.hashRegion, r.plan.regionWords);
|
|
9055
|
+
checkWords$2("rowPtr", r.rowPtr, rows + 1);
|
|
9056
|
+
if (rows === 0) {
|
|
9057
|
+
return;
|
|
9058
|
+
}
|
|
9059
|
+
const weighted = r.weights !== null;
|
|
9060
|
+
const tiers = [
|
|
9061
|
+
[0, 0, threadCount],
|
|
9062
|
+
[1, threadCount, hashCount]
|
|
9063
|
+
];
|
|
9064
|
+
for (const [tier, rowsBase, count] of tiers) {
|
|
9065
|
+
if (count === 0) {
|
|
9066
|
+
continue;
|
|
9067
|
+
}
|
|
9068
|
+
const kernel = this.kernels.get(`${tier}/${weighted}`);
|
|
9069
|
+
if (kernel === void 0) {
|
|
9070
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "groupByKeyRow: no variant was prepared", {
|
|
9071
|
+
argument: "tier",
|
|
9072
|
+
value: tier
|
|
9073
|
+
});
|
|
9074
|
+
}
|
|
9075
|
+
const params = this.scope.params(GROUP_PARAMS, {
|
|
9076
|
+
rowsBase,
|
|
9077
|
+
basesBase: threadCount + hashCount,
|
|
9078
|
+
count,
|
|
9079
|
+
pad0: 0
|
|
9080
|
+
});
|
|
9081
|
+
const bound = kernel.bind({
|
|
9082
|
+
rowPtr: r.rowPtr,
|
|
9083
|
+
colIdx: r.colIdx,
|
|
9084
|
+
weights: r.weights ?? r.colIdx,
|
|
9085
|
+
keyIn: r.keyIn,
|
|
9086
|
+
rows: r.rows,
|
|
9087
|
+
hashRegion: r.hashRegion,
|
|
9088
|
+
bestKey: r.bestKey,
|
|
9089
|
+
bestScore: r.bestScore,
|
|
9090
|
+
P: params.binding
|
|
9091
|
+
});
|
|
9092
|
+
const plan = tier === 0 ? plan1d(count, this.scope.workgroupSize, this.scope.caps) : plan2d(count, this.scope.caps);
|
|
9093
|
+
kernel.dispatch(pass, bound, plan, [params.offset]);
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
9096
|
+
}
|
|
9097
|
+
const U32_MAX = 4294967295;
|
|
9098
|
+
async function prepareHistogram(scope) {
|
|
9099
|
+
return prepareHistogramImpl(scope);
|
|
9100
|
+
}
|
|
9101
|
+
async function prepareHistogramImpl(scope) {
|
|
9102
|
+
const histogram = await scope.pipelines.kernel(kernelSpec("histogram"));
|
|
9103
|
+
const fill = await scope.pipelines.kernel(kernelSpec("fill"));
|
|
7434
9104
|
return new HistogramPlannerImpl(scope, histogram, fill);
|
|
7435
9105
|
}
|
|
7436
9106
|
async function prepareCountingSort(scope) {
|
|
@@ -7439,7 +9109,7 @@ async function prepareCountingSort(scope) {
|
|
|
7439
9109
|
const scatter = await scope.pipelines.kernel(kernelSpec("counting-scatter"));
|
|
7440
9110
|
return new CountingSortPlannerImpl(scope, histogram, scan, scatter);
|
|
7441
9111
|
}
|
|
7442
|
-
function checkWords(name, binding, words) {
|
|
9112
|
+
function checkWords$1(name, binding, words) {
|
|
7443
9113
|
if (binding.size < 4 * words) {
|
|
7444
9114
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `histogram: ${name} is smaller than 4 x ${words} bytes`, {
|
|
7445
9115
|
argument: name,
|
|
@@ -7448,122 +9118,867 @@ function checkWords(name, binding, words) {
|
|
|
7448
9118
|
});
|
|
7449
9119
|
}
|
|
7450
9120
|
}
|
|
7451
|
-
function checkHistogramArguments(keys, count, bins, hist) {
|
|
7452
|
-
if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX) {
|
|
7453
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: count must be a non-negative integer below 2^32", {
|
|
7454
|
-
argument: "count",
|
|
7455
|
-
value: count
|
|
7456
|
-
});
|
|
9121
|
+
function checkHistogramArguments(keys, count, bins, hist) {
|
|
9122
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX) {
|
|
9123
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: count must be a non-negative integer below 2^32", {
|
|
9124
|
+
argument: "count",
|
|
9125
|
+
value: count
|
|
9126
|
+
});
|
|
9127
|
+
}
|
|
9128
|
+
if (!Number.isSafeInteger(bins) || bins < 1 || bins > U32_MAX) {
|
|
9129
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: bins must be an integer in [1, 2^32)", {
|
|
9130
|
+
argument: "bins",
|
|
9131
|
+
value: bins
|
|
9132
|
+
});
|
|
9133
|
+
}
|
|
9134
|
+
checkWords$1("keys", keys, count);
|
|
9135
|
+
checkWords$1("hist", hist, bins);
|
|
9136
|
+
}
|
|
9137
|
+
class HistogramPlannerImpl {
|
|
9138
|
+
/**
|
|
9139
|
+
* Wraps the resolved kernels; use prepareHistogram().
|
|
9140
|
+
* @param scope - the caller's scope
|
|
9141
|
+
* @param histogram - the `histogram` kernel
|
|
9142
|
+
* @param fill - the `fill` kernel
|
|
9143
|
+
*/
|
|
9144
|
+
constructor(scope, histogram, fill) {
|
|
9145
|
+
this.dispatches = 0;
|
|
9146
|
+
this.scope = scope;
|
|
9147
|
+
this.histogram = histogram;
|
|
9148
|
+
this.fill = fill;
|
|
9149
|
+
}
|
|
9150
|
+
/**
|
|
9151
|
+
* Dispatches the last record() issued.
|
|
9152
|
+
* @returns the count
|
|
9153
|
+
*/
|
|
9154
|
+
get lastDispatches() {
|
|
9155
|
+
return this.dispatches;
|
|
9156
|
+
}
|
|
9157
|
+
/**
|
|
9158
|
+
* Records the fill and the histogram (see the interface).
|
|
9159
|
+
* @param pass - the compute pass
|
|
9160
|
+
* @param keys - the keys
|
|
9161
|
+
* @param count - the key count
|
|
9162
|
+
* @param bins - the bin count
|
|
9163
|
+
* @param hist - the counts
|
|
9164
|
+
*/
|
|
9165
|
+
record(pass, keys, count, bins, hist) {
|
|
9166
|
+
checkHistogramArguments(keys, count, bins, hist);
|
|
9167
|
+
this.recordZero(pass, hist, bins);
|
|
9168
|
+
this.dispatches = 1;
|
|
9169
|
+
if (count === 0) {
|
|
9170
|
+
return;
|
|
9171
|
+
}
|
|
9172
|
+
const params = this.scope.params(HIST_PARAMS, { count, bins, pad0: 0, pad1: 0 });
|
|
9173
|
+
const bound = this.histogram.bind({ keys, hist, P: params.binding });
|
|
9174
|
+
this.histogram.dispatch(pass, bound, plan1d(count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
|
|
9175
|
+
this.dispatches = 2;
|
|
9176
|
+
}
|
|
9177
|
+
/**
|
|
9178
|
+
* Records a `fill` of `words` zeros into `dst` (PD-12: a dispatch inside the pass, never an encoder clear).
|
|
9179
|
+
* @param pass - the compute pass
|
|
9180
|
+
* @param dst - the words to zero
|
|
9181
|
+
* @param words - how many (>= 1)
|
|
9182
|
+
*/
|
|
9183
|
+
recordZero(pass, dst, words) {
|
|
9184
|
+
const params = this.scope.params(FILL_PARAMS, { count: words, value: 0, mode: 0, pad0: 0 });
|
|
9185
|
+
const bound = this.fill.bind({ dst, P: params.binding });
|
|
9186
|
+
this.fill.dispatch(pass, bound, plan1d(words, this.scope.workgroupSize, this.scope.caps), [params.offset]);
|
|
9187
|
+
}
|
|
9188
|
+
}
|
|
9189
|
+
class CountingSortPlannerImpl {
|
|
9190
|
+
/**
|
|
9191
|
+
* Wraps the resolved planners and kernel; use prepareCountingSort().
|
|
9192
|
+
* @param scope - the caller's scope
|
|
9193
|
+
* @param histogram - the histogram planner (also the zeroing of the cursor)
|
|
9194
|
+
* @param scan - the scan planner
|
|
9195
|
+
* @param scatter - the `counting-scatter` kernel
|
|
9196
|
+
*/
|
|
9197
|
+
constructor(scope, histogram, scan, scatter) {
|
|
9198
|
+
this.dispatches = 0;
|
|
9199
|
+
this.scope = scope;
|
|
9200
|
+
this.histogram = histogram;
|
|
9201
|
+
this.scan = scan;
|
|
9202
|
+
this.scatter = scatter;
|
|
9203
|
+
}
|
|
9204
|
+
/**
|
|
9205
|
+
* Dispatches the last record() issued.
|
|
9206
|
+
* @returns the count
|
|
9207
|
+
*/
|
|
9208
|
+
get lastDispatches() {
|
|
9209
|
+
return this.dispatches;
|
|
9210
|
+
}
|
|
9211
|
+
/**
|
|
9212
|
+
* Records the four stages (see the interface).
|
|
9213
|
+
* @param pass - the compute pass
|
|
9214
|
+
* @param keys - the keys
|
|
9215
|
+
* @param count - the key count
|
|
9216
|
+
* @param bins - the bin count
|
|
9217
|
+
* @param scratch - the histogram and cursor
|
|
9218
|
+
* @param outIndex - the sorted indices
|
|
9219
|
+
* @param outStart - the bin starts
|
|
9220
|
+
*/
|
|
9221
|
+
record(pass, keys, count, bins, scratch, outIndex, outStart) {
|
|
9222
|
+
checkHistogramArguments(keys, count, bins, scratch.hist);
|
|
9223
|
+
checkWords$1("cursor", scratch.cursor, bins);
|
|
9224
|
+
checkWords$1("outIndex", outIndex, count);
|
|
9225
|
+
checkWords$1("outStart", outStart, bins);
|
|
9226
|
+
this.histogram.record(pass, keys, count, bins, scratch.hist);
|
|
9227
|
+
this.scan.record(pass, scratch.hist, bins, outStart);
|
|
9228
|
+
this.histogram.recordZero(pass, scratch.cursor, bins);
|
|
9229
|
+
this.dispatches = this.histogram.lastDispatches + this.scan.lastDispatches + 1;
|
|
9230
|
+
if (count === 0) {
|
|
9231
|
+
return;
|
|
9232
|
+
}
|
|
9233
|
+
const params = this.scope.params(HIST_PARAMS, { count, bins, pad0: 0, pad1: 0 });
|
|
9234
|
+
const bound = this.scatter.bind({ keys, start: outStart, cursor: scratch.cursor, outIndex, P: params.binding });
|
|
9235
|
+
this.scatter.dispatch(pass, bound, plan1d(count, this.scope.workgroupSize, this.scope.caps), [params.offset]);
|
|
9236
|
+
this.dispatches += 1;
|
|
9237
|
+
}
|
|
9238
|
+
}
|
|
9239
|
+
async function prepareCooToCsr(scope) {
|
|
9240
|
+
const histogram = await prepareHistogram(scope);
|
|
9241
|
+
const scan = await prepareScan(scope);
|
|
9242
|
+
const fill = await scope.pipelines.kernel(kernelSpec("fill"));
|
|
9243
|
+
const scatter = /* @__PURE__ */ new Map();
|
|
9244
|
+
for (const sorted of [false, true]) {
|
|
9245
|
+
for (const weighted of [false, true]) {
|
|
9246
|
+
const spec = kernelSpec("coo-scatter", { SORTED_INPUT: sorted, WEIGHTED: weighted });
|
|
9247
|
+
scatter.set(`${sorted}/${weighted}`, await scope.pipelines.kernel(spec));
|
|
9248
|
+
}
|
|
9249
|
+
}
|
|
9250
|
+
return new CooToCsrPlannerImpl(scope, histogram, scan, fill, scatter);
|
|
9251
|
+
}
|
|
9252
|
+
function checkWords(name, binding, words) {
|
|
9253
|
+
if (binding.size < 4 * words) {
|
|
9254
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `cooToCsr: ${name} is smaller than 4 x ${words} bytes`, {
|
|
9255
|
+
argument: name,
|
|
9256
|
+
value: binding.size,
|
|
9257
|
+
expected: 4 * words
|
|
9258
|
+
});
|
|
9259
|
+
}
|
|
9260
|
+
}
|
|
9261
|
+
function checkRecord(r) {
|
|
9262
|
+
for (const [name, value] of [
|
|
9263
|
+
["count", r.count],
|
|
9264
|
+
["n", r.n]
|
|
9265
|
+
]) {
|
|
9266
|
+
if (!Number.isSafeInteger(value) || value < 0 || value >= U32_MAX$2) {
|
|
9267
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `cooToCsr: ${name} must be an integer in [0, 2^32 - 1)`, {
|
|
9268
|
+
argument: name,
|
|
9269
|
+
value
|
|
9270
|
+
});
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
if (r.weights === null !== (r.out.weights === null)) {
|
|
9274
|
+
throw new WebGpuGraphError(
|
|
9275
|
+
"E_INVALID_ARGUMENT",
|
|
9276
|
+
"cooToCsr: weights and out.weights must be both present or both null",
|
|
9277
|
+
{
|
|
9278
|
+
argument: "weights",
|
|
9279
|
+
value: r.weights === null ? "null" : "present",
|
|
9280
|
+
expected: r.out.weights === null ? "null" : "present"
|
|
9281
|
+
}
|
|
9282
|
+
);
|
|
9283
|
+
}
|
|
9284
|
+
checkWords("src", r.src, r.count);
|
|
9285
|
+
checkWords("dst", r.dst, r.count);
|
|
9286
|
+
checkWords("out.rowPtr", r.out.rowPtr, r.n + 1);
|
|
9287
|
+
checkWords("out.colIdx", r.out.colIdx, r.count);
|
|
9288
|
+
checkWords("out.flag", r.out.flag, 1);
|
|
9289
|
+
if (r.weights !== null && r.out.weights !== null) {
|
|
9290
|
+
checkWords("weights", r.weights, r.count);
|
|
9291
|
+
checkWords("out.weights", r.out.weights, r.count);
|
|
9292
|
+
}
|
|
9293
|
+
}
|
|
9294
|
+
class CooToCsrPlannerImpl {
|
|
9295
|
+
/**
|
|
9296
|
+
* Wraps the resolved planners and kernels; use prepareCooToCsr().
|
|
9297
|
+
* @param scope - the caller's scope
|
|
9298
|
+
* @param histogram - the histogram planner
|
|
9299
|
+
* @param scan - the scan planner
|
|
9300
|
+
* @param fill - the `fill` kernel
|
|
9301
|
+
* @param scatter - the `coo-scatter` variants by `sorted/weighted`
|
|
9302
|
+
*/
|
|
9303
|
+
constructor(scope, histogram, scan, fill, scatter) {
|
|
9304
|
+
this.scope = scope;
|
|
9305
|
+
this.histogram = histogram;
|
|
9306
|
+
this.scan = scan;
|
|
9307
|
+
this.fill = fill;
|
|
9308
|
+
this.scatter = scatter;
|
|
9309
|
+
}
|
|
9310
|
+
/**
|
|
9311
|
+
* Records one build (see the interface).
|
|
9312
|
+
* @param pass - the compute pass
|
|
9313
|
+
* @param r - the arcs and the output
|
|
9314
|
+
*/
|
|
9315
|
+
record(pass, r) {
|
|
9316
|
+
checkRecord(r);
|
|
9317
|
+
const { scope } = this;
|
|
9318
|
+
const wg = scope.workgroupSize;
|
|
9319
|
+
const degreeBytes = 4 * (r.n + 1);
|
|
9320
|
+
const degrees = {
|
|
9321
|
+
buffer: scope.scratch(degreeBytes, "cooToCsr/degrees"),
|
|
9322
|
+
offset: 0,
|
|
9323
|
+
size: degreeBytes,
|
|
9324
|
+
window: null
|
|
9325
|
+
};
|
|
9326
|
+
this.histogram.record(pass, r.src, r.count, r.n + 1, degrees);
|
|
9327
|
+
this.scan.record(pass, degrees, r.n + 1, r.out.rowPtr);
|
|
9328
|
+
let cursors = r.out.flag;
|
|
9329
|
+
if (r.sortedInput) {
|
|
9330
|
+
this.zero(pass, r.out.flag, 1);
|
|
9331
|
+
} else if (r.n > 0) {
|
|
9332
|
+
const bytes = 4 * r.n;
|
|
9333
|
+
cursors = { buffer: scope.scratch(bytes, "cooToCsr/cursors"), offset: 0, size: bytes, window: null };
|
|
9334
|
+
this.zero(pass, cursors, r.n);
|
|
9335
|
+
}
|
|
9336
|
+
if (r.count === 0) {
|
|
9337
|
+
return;
|
|
9338
|
+
}
|
|
9339
|
+
const weighted = r.weights !== null;
|
|
9340
|
+
const kernel = this.scatter.get(`${r.sortedInput}/${weighted}`);
|
|
9341
|
+
if (kernel === void 0) {
|
|
9342
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "cooToCsr: no scatter variant was prepared", {
|
|
9343
|
+
argument: "sortedInput",
|
|
9344
|
+
value: r.sortedInput
|
|
9345
|
+
});
|
|
9346
|
+
}
|
|
9347
|
+
const outWeight = r.out.weights ?? {
|
|
9348
|
+
buffer: scope.scratch(4, "cooToCsr/weight-dummy"),
|
|
9349
|
+
offset: 0,
|
|
9350
|
+
size: 4,
|
|
9351
|
+
window: null
|
|
9352
|
+
};
|
|
9353
|
+
const params = scope.params(COO_PARAMS, { count: r.count, pad0: 0, pad1: 0, pad2: 0 });
|
|
9354
|
+
const bound = kernel.bind({
|
|
9355
|
+
src: r.src,
|
|
9356
|
+
dst: r.dst,
|
|
9357
|
+
weight: r.weights ?? r.dst,
|
|
9358
|
+
rowPtr: r.out.rowPtr,
|
|
9359
|
+
cursors,
|
|
9360
|
+
colIdx: r.out.colIdx,
|
|
9361
|
+
outWeight,
|
|
9362
|
+
P: params.binding
|
|
9363
|
+
});
|
|
9364
|
+
kernel.dispatch(pass, bound, plan1d(r.count, wg, scope.caps), [params.offset]);
|
|
9365
|
+
}
|
|
9366
|
+
/**
|
|
9367
|
+
* Records a `fill` of `words` zeros.
|
|
9368
|
+
* @param pass - the compute pass
|
|
9369
|
+
* @param dst - the words
|
|
9370
|
+
* @param words - how many (>= 1)
|
|
9371
|
+
*/
|
|
9372
|
+
zero(pass, dst, words) {
|
|
9373
|
+
const params = this.scope.params(FILL_PARAMS, { count: words, value: 0, mode: 0, pad0: 0 });
|
|
9374
|
+
const bound = this.fill.bind({ dst, P: params.binding });
|
|
9375
|
+
this.fill.dispatch(pass, bound, plan1d(words, this.scope.workgroupSize, this.scope.caps), [params.offset]);
|
|
9376
|
+
}
|
|
9377
|
+
}
|
|
9378
|
+
function sortBitsFor(n) {
|
|
9379
|
+
for (const bits of [8, 16, 24]) {
|
|
9380
|
+
if (n < 2 ** bits) {
|
|
9381
|
+
return bits;
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
return 32;
|
|
9385
|
+
}
|
|
9386
|
+
function scratchWords(scope, words, label) {
|
|
9387
|
+
const size = 4 * Math.max(1, words);
|
|
9388
|
+
return { buffer: scope.scratch(size, label), offset: 0, size, window: null };
|
|
9389
|
+
}
|
|
9390
|
+
function assertBindable(ctx, needed, path, algorithm) {
|
|
9391
|
+
const limit = ctx.caps.limits.maxStorageBufferBindingSize;
|
|
9392
|
+
if (needed > limit) {
|
|
9393
|
+
throw new WebGpuGraphError(
|
|
9394
|
+
"E_TOO_LARGE",
|
|
9395
|
+
`${algorithm}: ${path} needs ${needed} bytes in one binding, above the device limit of ${limit}`,
|
|
9396
|
+
{ needed, limit, path, algorithm }
|
|
9397
|
+
);
|
|
9398
|
+
}
|
|
9399
|
+
}
|
|
9400
|
+
function assertMergeable(n, src, dst, algorithm) {
|
|
9401
|
+
const incident = new Uint32Array(n);
|
|
9402
|
+
for (let e = 0; e < src.length; e++) {
|
|
9403
|
+
if (src[e] !== dst[e]) {
|
|
9404
|
+
incident[src[e]]++;
|
|
9405
|
+
incident[dst[e]]++;
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
const heavy = /* @__PURE__ */ new Map();
|
|
9409
|
+
for (let v = 0; v < n; v++) {
|
|
9410
|
+
if (incident[v] > PARALLEL_MERGE_LIMIT) {
|
|
9411
|
+
heavy.set(v, heavy.size);
|
|
9412
|
+
}
|
|
9413
|
+
}
|
|
9414
|
+
if (heavy.size < 2) {
|
|
9415
|
+
return;
|
|
9416
|
+
}
|
|
9417
|
+
const pairs = new Uint32Array(heavy.size * heavy.size);
|
|
9418
|
+
for (let e = 0; e < src.length; e++) {
|
|
9419
|
+
const a = heavy.get(src[e]);
|
|
9420
|
+
const b = heavy.get(dst[e]);
|
|
9421
|
+
if (a === void 0 || b === void 0 || a === b) {
|
|
9422
|
+
continue;
|
|
9423
|
+
}
|
|
9424
|
+
const key = Math.min(a, b) * heavy.size + Math.max(a, b);
|
|
9425
|
+
if (++pairs[key] > PARALLEL_MERGE_LIMIT) {
|
|
9426
|
+
throw new WebGpuGraphError(
|
|
9427
|
+
"E_UNSUPPORTED",
|
|
9428
|
+
`${algorithm}: nodes ${src[e]} and ${dst[e]} are joined by more than ${PARALLEL_MERGE_LIMIT} parallel edges, more than one weighted merge sums`,
|
|
9429
|
+
{ feature: `${algorithm}.parallelEdges`, hint: "run it unweighted, or merge the parallel edges first" }
|
|
9430
|
+
);
|
|
9431
|
+
}
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9434
|
+
function assertBuildSorted(bytes, build, label) {
|
|
9435
|
+
if (new Uint32Array(bytes, build.flag.offset, 1)[0] !== 0) {
|
|
9436
|
+
throw new WebGpuGraphError("E_VALIDATION", `${label}: the simple graph's arcs reached cooToCsr out of order`, {
|
|
9437
|
+
label: `${label}/simple-graph`,
|
|
9438
|
+
message: "the sorted-input precondition of cooToCsr failed on the device"
|
|
9439
|
+
});
|
|
9440
|
+
}
|
|
9441
|
+
}
|
|
9442
|
+
async function buildSimpleSymmetric(ctx, s, scope, withWeights, label) {
|
|
9443
|
+
const n = s.nodeCount;
|
|
9444
|
+
const wg = ctx.workgroupSize;
|
|
9445
|
+
const cooToCsr = await prepareCooToCsr(scope);
|
|
9446
|
+
const flag = scratchWords(scope, 1, "simple/flag");
|
|
9447
|
+
const rowPtr = scratchWords(scope, n + 1, "simple/rowPtr");
|
|
9448
|
+
const { edgeCount } = s;
|
|
9449
|
+
const list = s.edgeList();
|
|
9450
|
+
let selfLoops = 0;
|
|
9451
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
9452
|
+
if (list.src[e] === list.dst[e]) {
|
|
9453
|
+
selfLoops++;
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
const valid = 2 * (edgeCount - selfLoops);
|
|
9457
|
+
const empty2 = () => {
|
|
9458
|
+
const batch = new CommandBatch(ctx, `${label}/simple-graph`);
|
|
9459
|
+
cooToCsr.record(batch.pass("simple-graph"), {
|
|
9460
|
+
src: flag,
|
|
9461
|
+
dst: flag,
|
|
9462
|
+
weights: null,
|
|
9463
|
+
count: 0,
|
|
9464
|
+
n,
|
|
9465
|
+
sortedInput: true,
|
|
9466
|
+
out: { rowPtr, colIdx: flag, weights: null, flag }
|
|
9467
|
+
});
|
|
9468
|
+
batch.endPass();
|
|
9469
|
+
const graph2 = { n, arcCount: 0, rowPtr, colIdx: null, weights: null, src: null };
|
|
9470
|
+
return { graph: graph2, batch, flag: batch.readback(flag.buffer, 0, 4) };
|
|
9471
|
+
};
|
|
9472
|
+
if (valid === 0) {
|
|
9473
|
+
return empty2();
|
|
9474
|
+
}
|
|
9475
|
+
const arcs = 2 * edgeCount;
|
|
9476
|
+
const tableBytes = Math.max(4, radixHistBytes(arcs, wg));
|
|
9477
|
+
assertBindable(ctx, Math.max(4 * (arcs + 1), tableBytes), "the simple graph's arc arrays", label);
|
|
9478
|
+
if (withWeights) {
|
|
9479
|
+
assertMergeable(n, list.src, list.dst, label);
|
|
9480
|
+
}
|
|
9481
|
+
ctx.residency.core(s, ["rowPtr"]);
|
|
9482
|
+
const edges = ctx.residency.view(s, "edgeList");
|
|
9483
|
+
const edgeWeights = edges.bindings.weights ?? null;
|
|
9484
|
+
const emitPlan = plan1d(arcs, wg, ctx.caps);
|
|
9485
|
+
const emit = /* @__PURE__ */ new Map();
|
|
9486
|
+
for (const indexed of [false, true]) {
|
|
9487
|
+
const spec = kernelSpec("coo-emit", { INDEXED: indexed, WEIGHTED: edgeWeights !== null });
|
|
9488
|
+
emit.set(indexed, await ctx.pipelines.kernel(spec));
|
|
9489
|
+
}
|
|
9490
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
9491
|
+
const runFlags = await ctx.pipelines.kernel(kernelSpec("run-flags"));
|
|
9492
|
+
const sort = await prepareRadixSort(scope);
|
|
9493
|
+
const scan = await prepareScan(scope);
|
|
9494
|
+
const compact = await prepareCompact(scope);
|
|
9495
|
+
const aSrc = scratchWords(scope, arcs, "simple/aSrc");
|
|
9496
|
+
const aDst = scratchWords(scope, arcs, "simple/aDst");
|
|
9497
|
+
const aW = scratchWords(scope, arcs, "simple/aW");
|
|
9498
|
+
const vals = scratchWords(scope, arcs, "simple/vals");
|
|
9499
|
+
const sKeys = scratchWords(scope, arcs, "simple/sortKeys");
|
|
9500
|
+
const sVals = scratchWords(scope, arcs, "simple/sortVals");
|
|
9501
|
+
const hist = {
|
|
9502
|
+
buffer: scope.scratch(tableBytes, "simple/hist"),
|
|
9503
|
+
offset: 0,
|
|
9504
|
+
size: tableBytes,
|
|
9505
|
+
window: null
|
|
9506
|
+
};
|
|
9507
|
+
const offsets = {
|
|
9508
|
+
buffer: scope.scratch(tableBytes, "simple/offsets"),
|
|
9509
|
+
offset: 0,
|
|
9510
|
+
size: tableBytes,
|
|
9511
|
+
window: null
|
|
9512
|
+
};
|
|
9513
|
+
const sortedSrc = scratchWords(scope, arcs, "simple/sortedSrc");
|
|
9514
|
+
const sortedDst = scratchWords(scope, arcs, "simple/sortedDst");
|
|
9515
|
+
const sortedW = scratchWords(scope, arcs, "simple/sortedW");
|
|
9516
|
+
const flags = scratchWords(scope, arcs, "simple/flags");
|
|
9517
|
+
const runIndex = scratchWords(scope, arcs, "simple/runIndex");
|
|
9518
|
+
const dummy = scratchWords(scope, 1, "simple/dummy");
|
|
9519
|
+
await ctx.allocator.check();
|
|
9520
|
+
const recordEmit = (pass3, order, out) => {
|
|
9521
|
+
const kernel = emit.get(order !== null);
|
|
9522
|
+
if (kernel === void 0) {
|
|
9523
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "buildSimpleSymmetric: no coo-emit variant", {
|
|
9524
|
+
argument: "order",
|
|
9525
|
+
value: order === null ? "null" : "present"
|
|
9526
|
+
});
|
|
9527
|
+
}
|
|
9528
|
+
const params2 = scope.params(COO_PARAMS, { count: arcs, pad0: 0, pad1: 0, pad2: 0 });
|
|
9529
|
+
const bound = kernel.bind({
|
|
9530
|
+
edgeSrc: edges.bindings.src,
|
|
9531
|
+
edgeDst: edges.bindings.dst,
|
|
9532
|
+
edgeWeight: edgeWeights ?? edges.bindings.src,
|
|
9533
|
+
order: order ?? dummy,
|
|
9534
|
+
outSrc: out[0],
|
|
9535
|
+
outDst: out[1],
|
|
9536
|
+
outWeight: out[2],
|
|
9537
|
+
P: params2.binding
|
|
9538
|
+
});
|
|
9539
|
+
kernel.dispatch(pass3, bound, emitPlan, [params2.offset]);
|
|
9540
|
+
};
|
|
9541
|
+
const recordIota = (pass3, dst, words) => {
|
|
9542
|
+
const params2 = scope.params(FILL_PARAMS, { count: words, value: 0, mode: 1, pad0: 0 });
|
|
9543
|
+
fill.dispatch(pass3, fill.bind({ dst, P: params2.binding }), plan1d(words, wg, ctx.caps), [params2.offset]);
|
|
9544
|
+
};
|
|
9545
|
+
const bits = sortBitsFor(n);
|
|
9546
|
+
const first = new CommandBatch(ctx, `${label}/simple-sort`);
|
|
9547
|
+
const pass = first.pass("simple-sort");
|
|
9548
|
+
recordEmit(pass, null, [aSrc, aDst, aW]);
|
|
9549
|
+
recordIota(pass, vals, arcs);
|
|
9550
|
+
const byTarget = sort.record(pass, aDst, vals, arcs, bits, { keys: sKeys, vals: sVals, hist, offsets });
|
|
9551
|
+
recordEmit(pass, byTarget.vals, [aSrc, aDst, aW]);
|
|
9552
|
+
const free = byTarget.vals.buffer === vals.buffer ? sVals : vals;
|
|
9553
|
+
const bySource = sort.record(pass, aSrc, byTarget.vals, arcs, bits, {
|
|
9554
|
+
keys: byTarget.keys.buffer === aDst.buffer ? sKeys : aDst,
|
|
9555
|
+
vals: free,
|
|
9556
|
+
hist,
|
|
9557
|
+
offsets
|
|
9558
|
+
});
|
|
9559
|
+
recordEmit(pass, bySource.vals, [sortedSrc, sortedDst, sortedW]);
|
|
9560
|
+
const params = scope.params(COO_PARAMS, { count: arcs, pad0: 0, pad1: 0, pad2: 0 });
|
|
9561
|
+
runFlags.dispatch(pass, runFlags.bind({ keysA: sortedSrc, keysB: sortedDst, flags, P: params.binding }), emitPlan, [
|
|
9562
|
+
params.offset
|
|
9563
|
+
]);
|
|
9564
|
+
const total = scan.record(pass, flags, arcs, runIndex);
|
|
9565
|
+
first.endPass();
|
|
9566
|
+
const totalRequest = first.readback(total.binding.buffer, total.binding.offset + 4 * total.index, 4);
|
|
9567
|
+
scope.flush();
|
|
9568
|
+
const back = await first.submit().readback;
|
|
9569
|
+
ctx.assertReady();
|
|
9570
|
+
const unique = new Uint32Array(back, totalRequest.offset, 1)[0];
|
|
9571
|
+
if (unique === 0 || unique > valid) {
|
|
9572
|
+
throw new WebGpuGraphError("E_VALIDATION", `${label}: ${unique} distinct arcs from ${valid} valid ones`, {
|
|
9573
|
+
label: `${label}/simple-graph`,
|
|
9574
|
+
message: "the run count of the simple graph build is outside [1, valid arcs]"
|
|
9575
|
+
});
|
|
9576
|
+
}
|
|
9577
|
+
const uSrc = scratchWords(scope, arcs, "simple/src");
|
|
9578
|
+
const uDst = scratchWords(scope, arcs, "simple/dst");
|
|
9579
|
+
const runStart = scratchWords(scope, arcs + 1, "simple/runStart");
|
|
9580
|
+
const counts = scratchWords(scope, 1, "simple/count");
|
|
9581
|
+
const colIdx = scratchWords(scope, unique, "simple/colIdx");
|
|
9582
|
+
const weights = withWeights ? scratchWords(scope, unique, "simple/weights") : null;
|
|
9583
|
+
const merged = withWeights ? scratchWords(scope, unique, "simple/merged") : null;
|
|
9584
|
+
ctx.device.queue.writeBuffer(runStart.buffer, 4 * unique, Uint32Array.of(valid));
|
|
9585
|
+
const reduce = merged === null ? null : await prepareSegmentedReduce(scope, runCore(runStart, unique, sortedDst, sortedW, valid), {
|
|
9586
|
+
op: "sum",
|
|
9587
|
+
valueSnippet: "v = weight;",
|
|
9588
|
+
tiers: null
|
|
9589
|
+
});
|
|
9590
|
+
const second = new CommandBatch(ctx, `${label}/simple-graph`);
|
|
9591
|
+
const pass2 = second.pass("simple-graph");
|
|
9592
|
+
const positions = vals;
|
|
9593
|
+
recordIota(pass2, positions, arcs);
|
|
9594
|
+
for (const [queue, out] of [
|
|
9595
|
+
[sortedSrc, uSrc],
|
|
9596
|
+
[sortedDst, uDst],
|
|
9597
|
+
[positions, runStart]
|
|
9598
|
+
]) {
|
|
9599
|
+
compact.record(pass2, { queue, flags, count: arcs, out, outCount: counts, outIndex: 0 });
|
|
9600
|
+
}
|
|
9601
|
+
if (reduce !== null && merged !== null) {
|
|
9602
|
+
reduce.record(pass2, runCore(runStart, unique, sortedDst, sortedW, valid), merged);
|
|
9603
|
+
}
|
|
9604
|
+
cooToCsr.record(pass2, {
|
|
9605
|
+
src: uSrc,
|
|
9606
|
+
dst: uDst,
|
|
9607
|
+
weights: merged,
|
|
9608
|
+
count: unique,
|
|
9609
|
+
n,
|
|
9610
|
+
sortedInput: true,
|
|
9611
|
+
out: { rowPtr, colIdx, weights, flag }
|
|
9612
|
+
});
|
|
9613
|
+
second.endPass();
|
|
9614
|
+
const graph = { n, arcCount: unique, rowPtr, colIdx, weights, src: uSrc };
|
|
9615
|
+
return { graph, batch: second, flag: second.readback(flag.buffer, 0, 4) };
|
|
9616
|
+
}
|
|
9617
|
+
function runCore(runStart, runs, sortedDst, sortedW, valid) {
|
|
9618
|
+
return {
|
|
9619
|
+
serial: -1,
|
|
9620
|
+
plan: "perArray",
|
|
9621
|
+
rowPtr: { ...runStart, size: 4 * (runs + 1) },
|
|
9622
|
+
colIdx: { ...sortedDst, size: 4 * valid },
|
|
9623
|
+
weights: { ...sortedW, size: 4 * valid },
|
|
9624
|
+
arcToEdge: null,
|
|
9625
|
+
edgeToArc: null,
|
|
9626
|
+
windows: null,
|
|
9627
|
+
arcBuffers: null,
|
|
9628
|
+
hasWeights: true
|
|
9629
|
+
};
|
|
9630
|
+
}
|
|
9631
|
+
const ALGORITHM$1 = "labelPropagation";
|
|
9632
|
+
const DEFAULT_MAX_ITERATIONS = 100;
|
|
9633
|
+
const RING_SLOTS$1 = 1024;
|
|
9634
|
+
function checkDest$1(dest, n) {
|
|
9635
|
+
if (dest === void 0) {
|
|
9636
|
+
return null;
|
|
9637
|
+
}
|
|
9638
|
+
if (dest instanceof Uint32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
|
|
9639
|
+
return dest;
|
|
9640
|
+
}
|
|
9641
|
+
throw new WebGpuGraphError(
|
|
9642
|
+
"E_INVALID_ARGUMENT",
|
|
9643
|
+
`${ALGORITHM$1}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
9644
|
+
{
|
|
9645
|
+
argument: "dest",
|
|
9646
|
+
value: `${dest.constructor.name}(${dest.length})`,
|
|
9647
|
+
expected: `Uint32Array(${n}) over an ArrayBuffer`
|
|
9648
|
+
}
|
|
9649
|
+
);
|
|
9650
|
+
}
|
|
9651
|
+
function maxIterationsOf(value) {
|
|
9652
|
+
if (value === void 0) {
|
|
9653
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
9654
|
+
}
|
|
9655
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
9656
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$1}: maxIterations must be a non-negative integer`, {
|
|
9657
|
+
argument: "maxIterations",
|
|
9658
|
+
value,
|
|
9659
|
+
expected: "a non-negative integer"
|
|
9660
|
+
});
|
|
9661
|
+
}
|
|
9662
|
+
return value;
|
|
9663
|
+
}
|
|
9664
|
+
function neighbourBound(s) {
|
|
9665
|
+
const out = s.outDegree();
|
|
9666
|
+
if (!s.directed) {
|
|
9667
|
+
return out;
|
|
7457
9668
|
}
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
});
|
|
9669
|
+
const inDegree = s.inDegree();
|
|
9670
|
+
const bound = new Uint32Array(s.nodeCount);
|
|
9671
|
+
for (let v = 0; v < bound.length; v++) {
|
|
9672
|
+
bound[v] = out[v] + inDegree[v];
|
|
7463
9673
|
}
|
|
7464
|
-
|
|
7465
|
-
checkWords("hist", hist, bins);
|
|
9674
|
+
return bound;
|
|
7466
9675
|
}
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
this.scope = scope;
|
|
7477
|
-
this.histogram = histogram;
|
|
7478
|
-
this.fill = fill;
|
|
9676
|
+
function resultOf(raw, dest) {
|
|
9677
|
+
const n = raw.length;
|
|
9678
|
+
for (let v = 0; v < n; v++) {
|
|
9679
|
+
if (raw[v] >= n) {
|
|
9680
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$1}: labels[${v}] = ${raw[v]} is not a node index`, {
|
|
9681
|
+
label: `${ALGORITHM$1}/labels`,
|
|
9682
|
+
message: `the device produced a label outside [0, ${n})`
|
|
9683
|
+
});
|
|
9684
|
+
}
|
|
7479
9685
|
}
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
9686
|
+
const { labels, count } = renumberPartition(raw, dest ?? void 0);
|
|
9687
|
+
return labelResult(labels, count);
|
|
9688
|
+
}
|
|
9689
|
+
function identityResult(n, dest) {
|
|
9690
|
+
const labels = dest ?? new Uint32Array(n);
|
|
9691
|
+
for (let v = 0; v < n; v++) {
|
|
9692
|
+
labels[v] = v;
|
|
7486
9693
|
}
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
9694
|
+
return labelResult(labels, n);
|
|
9695
|
+
}
|
|
9696
|
+
async function labelPropagation(ctx, s, options) {
|
|
9697
|
+
ctx.assertReady();
|
|
9698
|
+
await assertDeviceComputes(ctx);
|
|
9699
|
+
const n = s.nodeCount;
|
|
9700
|
+
const dest = checkDest$1(options?.dest, n);
|
|
9701
|
+
const maxIterations = maxIterationsOf(options?.maxIterations);
|
|
9702
|
+
const weighted = options?.weighted !== false;
|
|
9703
|
+
if (options?.signal?.aborted) {
|
|
9704
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$1}: the signal was aborted before any work started`, {});
|
|
9705
|
+
}
|
|
9706
|
+
if (n === 0 || s.edgeCount === 0 || maxIterations === 0) {
|
|
9707
|
+
options?.onProgress?.(1, 1);
|
|
9708
|
+
return identityResult(n, dest);
|
|
9709
|
+
}
|
|
9710
|
+
const plan = planGroupRows(neighbourBound(s));
|
|
9711
|
+
assertBindable(ctx, 4 * plan.regionWords, "the group-by hash region", ALGORITHM$1);
|
|
9712
|
+
const scope = algorithmScope(ctx, ALGORITHM$1, RING_SLOTS$1);
|
|
9713
|
+
try {
|
|
9714
|
+
const build = await buildSimpleSymmetric(ctx, s, scope, weighted, ALGORITHM$1);
|
|
9715
|
+
const { graph } = build;
|
|
9716
|
+
if (graph.colIdx === null) {
|
|
9717
|
+
scope.flush();
|
|
9718
|
+
const bytes = await build.batch.submit().readback;
|
|
9719
|
+
ctx.assertReady();
|
|
9720
|
+
assertBuildSorted(bytes, build, ALGORITHM$1);
|
|
9721
|
+
options?.onProgress?.(1, 1);
|
|
9722
|
+
return identityResult(n, dest);
|
|
7501
9723
|
}
|
|
7502
|
-
const
|
|
7503
|
-
const
|
|
7504
|
-
|
|
7505
|
-
|
|
9724
|
+
const wg = ctx.workgroupSize;
|
|
9725
|
+
const words = (count, label) => {
|
|
9726
|
+
const size = 4 * Math.max(1, count);
|
|
9727
|
+
return { buffer: scope.scratch(size, label), offset: 0, size, window: null };
|
|
9728
|
+
};
|
|
9729
|
+
const rows = words(plan.words.length, "rows");
|
|
9730
|
+
const region = words(plan.regionWords, "hashRegion");
|
|
9731
|
+
const labelsA = words(n, "labelsA");
|
|
9732
|
+
const labelsB = words(n, "labelsB");
|
|
9733
|
+
const bestKey = words(n, "bestKey");
|
|
9734
|
+
const bestScore = words(n, "bestScore");
|
|
9735
|
+
const counters = words(LABEL_PROP_PASSES_PER_SUBMIT, "counters");
|
|
9736
|
+
await ctx.allocator.check();
|
|
9737
|
+
const { queue } = ctx.device;
|
|
9738
|
+
queue.writeBuffer(rows.buffer, 0, plan.words);
|
|
9739
|
+
queue.writeBuffer(region.buffer, 0, new Uint32Array(1));
|
|
9740
|
+
const groupBy = await prepareGroupByKeyRow(scope);
|
|
9741
|
+
const step = await ctx.pipelines.kernel(kernelSpec("lpa-step"));
|
|
9742
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
9743
|
+
const nodePlan = plan1d(n, wg, ctx.caps);
|
|
9744
|
+
let { batch } = build;
|
|
9745
|
+
let first = true;
|
|
9746
|
+
let cur = labelsA;
|
|
9747
|
+
let next = labelsB;
|
|
9748
|
+
let done = 0;
|
|
9749
|
+
let previousLast = -1;
|
|
9750
|
+
for (; ; ) {
|
|
9751
|
+
const k = Math.min(LABEL_PROP_PASSES_PER_SUBMIT, maxIterations - done);
|
|
9752
|
+
queue.writeBuffer(counters.buffer, 0, new Uint32Array(k));
|
|
9753
|
+
const pass = batch.pass("passes");
|
|
9754
|
+
if (first) {
|
|
9755
|
+
const iota = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 1, pad0: 0 });
|
|
9756
|
+
fill.dispatch(pass, fill.bind({ dst: labelsA, P: iota.binding }), nodePlan, [iota.offset]);
|
|
9757
|
+
}
|
|
9758
|
+
for (let i = 0; i < k; i++) {
|
|
9759
|
+
groupBy.record(pass, {
|
|
9760
|
+
rowPtr: graph.rowPtr,
|
|
9761
|
+
colIdx: graph.colIdx,
|
|
9762
|
+
weights: weighted ? graph.weights : null,
|
|
9763
|
+
keyIn: cur,
|
|
9764
|
+
plan,
|
|
9765
|
+
rows,
|
|
9766
|
+
hashRegion: region,
|
|
9767
|
+
bestKey,
|
|
9768
|
+
bestScore
|
|
9769
|
+
});
|
|
9770
|
+
const params = scope.params(LPA_PARAMS, {
|
|
9771
|
+
n,
|
|
9772
|
+
direction: (done + i) % 2,
|
|
9773
|
+
counterIndex: i,
|
|
9774
|
+
pad0: 0
|
|
9775
|
+
});
|
|
9776
|
+
step.dispatch(
|
|
9777
|
+
pass,
|
|
9778
|
+
step.bind({ labelsIn: cur, bestKey, labelsOut: next, counters, P: params.binding }),
|
|
9779
|
+
nodePlan,
|
|
9780
|
+
[params.offset]
|
|
9781
|
+
);
|
|
9782
|
+
[cur, next] = [next, cur];
|
|
9783
|
+
}
|
|
9784
|
+
batch.endPass();
|
|
9785
|
+
const movesRequest = batch.readback(counters.buffer, 0, 4 * k);
|
|
9786
|
+
const exhaustedRequest = batch.readback(region.buffer, 0, 4);
|
|
9787
|
+
scope.flush();
|
|
9788
|
+
const submitted = batch.submit();
|
|
9789
|
+
const bytes = await submitted.readback;
|
|
9790
|
+
ctx.assertReady();
|
|
9791
|
+
if (first) {
|
|
9792
|
+
assertBuildSorted(bytes, build, ALGORITHM$1);
|
|
9793
|
+
first = false;
|
|
9794
|
+
}
|
|
9795
|
+
if (new Uint32Array(bytes, exhaustedRequest.offset, 1)[0] !== 0) {
|
|
9796
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$1}: a hash probe exhausted its bound`, {
|
|
9797
|
+
label: `${ALGORITHM$1}/group-by-key`,
|
|
9798
|
+
message: "a compare-exchange loop of the workgroup tier ran out of steps"
|
|
9799
|
+
});
|
|
9800
|
+
}
|
|
9801
|
+
const moves = new Uint32Array(bytes, movesRequest.offset, k);
|
|
9802
|
+
done += k;
|
|
9803
|
+
const lastTwo = k >= 2 ? moves[k - 2] + moves[k - 1] : previousLast + moves[0];
|
|
9804
|
+
previousLast = moves[k - 1];
|
|
9805
|
+
if (options?.signal?.aborted) {
|
|
9806
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$1}: the signal was aborted`, {
|
|
9807
|
+
batchId: submitted.id
|
|
9808
|
+
});
|
|
9809
|
+
}
|
|
9810
|
+
options?.onProgress?.(done, maxIterations);
|
|
9811
|
+
if (lastTwo === 0 || done >= maxIterations) {
|
|
9812
|
+
break;
|
|
9813
|
+
}
|
|
9814
|
+
batch = new CommandBatch(ctx, `${ALGORITHM$1}/passes`);
|
|
9815
|
+
}
|
|
9816
|
+
const raw = new Uint32Array(n);
|
|
9817
|
+
await ctx.readback.read(cur.buffer, 4 * n, raw);
|
|
9818
|
+
ctx.assertReady();
|
|
9819
|
+
return resultOf(raw, dest);
|
|
9820
|
+
} finally {
|
|
9821
|
+
scope.dispose();
|
|
7506
9822
|
}
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
9823
|
+
}
|
|
9824
|
+
const ALGORITHM = "triangleCount";
|
|
9825
|
+
const RING_SLOTS = 1024;
|
|
9826
|
+
function checkDest(dest, n) {
|
|
9827
|
+
if (dest === void 0) {
|
|
9828
|
+
return null;
|
|
9829
|
+
}
|
|
9830
|
+
if (dest instanceof Uint32Array && dest.length === n && dest.buffer instanceof ArrayBuffer) {
|
|
9831
|
+
return dest;
|
|
9832
|
+
}
|
|
9833
|
+
throw new WebGpuGraphError(
|
|
9834
|
+
"E_INVALID_ARGUMENT",
|
|
9835
|
+
`${ALGORITHM}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
9836
|
+
{
|
|
9837
|
+
argument: "dest",
|
|
9838
|
+
value: `${dest.constructor.name}(${dest.length})`,
|
|
9839
|
+
expected: `Uint32Array(${n}) over an ArrayBuffer`
|
|
9840
|
+
}
|
|
9841
|
+
);
|
|
9842
|
+
}
|
|
9843
|
+
function epilogue(perNode, rowPtr) {
|
|
9844
|
+
const n = perNode.length;
|
|
9845
|
+
const coefficient = new Float32Array(n);
|
|
9846
|
+
let sum = 0;
|
|
9847
|
+
let triples = 0;
|
|
9848
|
+
for (let v = 0; v < n; v++) {
|
|
9849
|
+
const d = rowPtr[v + 1] - rowPtr[v];
|
|
9850
|
+
const t = perNode[v];
|
|
9851
|
+
sum += t;
|
|
9852
|
+
if (d >= 2) {
|
|
9853
|
+
const pairs = d * (d - 1) / 2;
|
|
9854
|
+
triples += pairs;
|
|
9855
|
+
coefficient[v] = t / pairs;
|
|
9856
|
+
}
|
|
7517
9857
|
}
|
|
9858
|
+
return { perNode, total: sum / 3, coefficient, transitivity: triples === 0 ? 0 : sum / triples };
|
|
7518
9859
|
}
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
this.scope = scope;
|
|
7530
|
-
this.histogram = histogram;
|
|
7531
|
-
this.scan = scan;
|
|
7532
|
-
this.scatter = scatter;
|
|
9860
|
+
async function triangleCount(ctx, s, options) {
|
|
9861
|
+
return await triangleCountWithSearch(ctx, s, 0, options);
|
|
9862
|
+
}
|
|
9863
|
+
async function triangleCountWithSearch(ctx, s, search, options) {
|
|
9864
|
+
ctx.assertReady();
|
|
9865
|
+
await assertDeviceComputes(ctx);
|
|
9866
|
+
const n = s.nodeCount;
|
|
9867
|
+
const dest = checkDest(options?.dest, n);
|
|
9868
|
+
if (options?.signal?.aborted) {
|
|
9869
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted before any work started`, {});
|
|
7533
9870
|
}
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
*/
|
|
7538
|
-
get lastDispatches() {
|
|
7539
|
-
return this.dispatches;
|
|
9871
|
+
if (n === 0) {
|
|
9872
|
+
options?.onProgress?.(1, 1);
|
|
9873
|
+
return epilogue(dest ?? new Uint32Array(0), new Uint32Array(1));
|
|
7540
9874
|
}
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
9875
|
+
const scope = algorithmScope(ctx, ALGORITHM, RING_SLOTS);
|
|
9876
|
+
try {
|
|
9877
|
+
const build = await buildSimpleSymmetric(ctx, s, scope, false, ALGORITHM);
|
|
9878
|
+
const { graph, batch } = build;
|
|
9879
|
+
const wg = ctx.workgroupSize;
|
|
9880
|
+
const countsBytes = 4 * n;
|
|
9881
|
+
const counts = {
|
|
9882
|
+
buffer: scope.scratch(countsBytes, "counts"),
|
|
9883
|
+
offset: 0,
|
|
9884
|
+
size: countsBytes,
|
|
9885
|
+
window: null
|
|
9886
|
+
};
|
|
9887
|
+
const fill = await ctx.pipelines.kernel(kernelSpec("fill"));
|
|
9888
|
+
const pass = batch.pass("triangles");
|
|
9889
|
+
let orientedFlag = null;
|
|
9890
|
+
const zero = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 0, pad0: 0 });
|
|
9891
|
+
fill.dispatch(pass, fill.bind({ dst: counts, P: zero.binding }), plan1d(n, wg, ctx.caps), [zero.offset]);
|
|
9892
|
+
if (graph.arcCount > 0 && graph.colIdx !== null && graph.src !== null) {
|
|
9893
|
+
const oriented = graph.arcCount / 2;
|
|
9894
|
+
const orient = await ctx.pipelines.kernel(kernelSpec("orient-flags"));
|
|
9895
|
+
const intersect = await ctx.pipelines.kernel(kernelSpec("tri-intersect", { SEARCH: search }));
|
|
9896
|
+
const compact = await prepareCompact(scope);
|
|
9897
|
+
const cooToCsr = await prepareCooToCsr(scope);
|
|
9898
|
+
const words = (count, label) => ({
|
|
9899
|
+
buffer: scope.scratch(4 * count, label),
|
|
9900
|
+
offset: 0,
|
|
9901
|
+
size: 4 * count,
|
|
9902
|
+
window: null
|
|
9903
|
+
});
|
|
9904
|
+
const flags = words(graph.arcCount, "orient/flags");
|
|
9905
|
+
const oSrc = words(graph.arcCount, "orient/src");
|
|
9906
|
+
const oDst = words(graph.arcCount, "orient/dst");
|
|
9907
|
+
const oRowPtr = words(n + 1, "orient/rowPtr");
|
|
9908
|
+
const oColIdx = words(oriented, "orient/colIdx");
|
|
9909
|
+
const scratch = words(1, "orient/count");
|
|
9910
|
+
const oFlag = words(1, "orient/flag");
|
|
9911
|
+
const arcPlan = plan1d(graph.arcCount, wg, ctx.caps);
|
|
9912
|
+
const p1 = scope.params(COO_PARAMS, { count: graph.arcCount, pad0: 0, pad1: 0, pad2: 0 });
|
|
9913
|
+
orient.dispatch(
|
|
9914
|
+
pass,
|
|
9915
|
+
orient.bind({ rowPtr: graph.rowPtr, colIdx: graph.colIdx, src: graph.src, flags, P: p1.binding }),
|
|
9916
|
+
arcPlan,
|
|
9917
|
+
[p1.offset]
|
|
9918
|
+
);
|
|
9919
|
+
compact.record(pass, {
|
|
9920
|
+
queue: graph.src,
|
|
9921
|
+
flags,
|
|
9922
|
+
count: graph.arcCount,
|
|
9923
|
+
out: oSrc,
|
|
9924
|
+
outCount: scratch,
|
|
9925
|
+
outIndex: 0
|
|
9926
|
+
});
|
|
9927
|
+
compact.record(pass, {
|
|
9928
|
+
queue: graph.colIdx,
|
|
9929
|
+
flags,
|
|
9930
|
+
count: graph.arcCount,
|
|
9931
|
+
out: oDst,
|
|
9932
|
+
outCount: scratch,
|
|
9933
|
+
outIndex: 0
|
|
9934
|
+
});
|
|
9935
|
+
cooToCsr.record(pass, {
|
|
9936
|
+
src: oSrc,
|
|
9937
|
+
dst: oDst,
|
|
9938
|
+
weights: null,
|
|
9939
|
+
count: oriented,
|
|
9940
|
+
n,
|
|
9941
|
+
sortedInput: true,
|
|
9942
|
+
out: { rowPtr: oRowPtr, colIdx: oColIdx, weights: null, flag: oFlag }
|
|
9943
|
+
});
|
|
9944
|
+
orientedFlag = oFlag;
|
|
9945
|
+
const p2 = scope.params(COO_PARAMS, { count: oriented, pad0: 0, pad1: 0, pad2: 0 });
|
|
9946
|
+
intersect.dispatch(
|
|
9947
|
+
pass,
|
|
9948
|
+
intersect.bind({ rowPtr: oRowPtr, colIdx: oColIdx, src: oSrc, counts, P: p2.binding }),
|
|
9949
|
+
plan1d(oriented, wg, ctx.caps),
|
|
9950
|
+
[p2.offset]
|
|
9951
|
+
);
|
|
7562
9952
|
}
|
|
7563
|
-
|
|
7564
|
-
const
|
|
7565
|
-
|
|
7566
|
-
|
|
9953
|
+
batch.endPass();
|
|
9954
|
+
const countsRequest = batch.readback(counts.buffer, 0, countsBytes);
|
|
9955
|
+
const rowsRequest = batch.readback(graph.rowPtr.buffer, 0, 4 * (n + 1));
|
|
9956
|
+
const orientedRequest = orientedFlag === null ? null : batch.readback(orientedFlag.buffer, 0, 4);
|
|
9957
|
+
scope.flush();
|
|
9958
|
+
const submitted = batch.submit();
|
|
9959
|
+
const bytes = await submitted.readback;
|
|
9960
|
+
ctx.assertReady();
|
|
9961
|
+
if (options?.signal?.aborted) {
|
|
9962
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM}: the signal was aborted`, { batchId: submitted.id });
|
|
9963
|
+
}
|
|
9964
|
+
assertBuildSorted(bytes, build, ALGORITHM);
|
|
9965
|
+
if (orientedRequest !== null && new Uint32Array(bytes, orientedRequest.offset, 1)[0] !== 0) {
|
|
9966
|
+
throw new WebGpuGraphError(
|
|
9967
|
+
"E_VALIDATION",
|
|
9968
|
+
`${ALGORITHM}: the oriented arcs reached cooToCsr out of order`,
|
|
9969
|
+
{
|
|
9970
|
+
label: `${ALGORITHM}/oriented`,
|
|
9971
|
+
message: "the sorted-input precondition of cooToCsr failed on the device"
|
|
9972
|
+
}
|
|
9973
|
+
);
|
|
9974
|
+
}
|
|
9975
|
+
const perNode = dest ?? new Uint32Array(n);
|
|
9976
|
+
perNode.set(new Uint32Array(bytes, countsRequest.offset, n));
|
|
9977
|
+
const rowPtr = new Uint32Array(bytes, rowsRequest.offset, n + 1);
|
|
9978
|
+
options?.onProgress?.(1, 1);
|
|
9979
|
+
return epilogue(perNode, rowPtr);
|
|
9980
|
+
} finally {
|
|
9981
|
+
scope.dispose();
|
|
7567
9982
|
}
|
|
7568
9983
|
}
|
|
7569
9984
|
function nextPow2(x) {
|
|
@@ -12412,6 +14827,15 @@ function copyBetweenness(defaults) {
|
|
|
12412
14827
|
}
|
|
12413
14828
|
return Object.freeze(copy);
|
|
12414
14829
|
}
|
|
14830
|
+
function withBetweennessDefaults(defaults, options, nodeCount) {
|
|
14831
|
+
if (defaults === void 0 || options?.sources !== void 0 || options?.k !== void 0) {
|
|
14832
|
+
return options;
|
|
14833
|
+
}
|
|
14834
|
+
if (defaults.sources !== void 0) {
|
|
14835
|
+
return { ...options, sources: defaults.sources.filter((v) => v < nodeCount) };
|
|
14836
|
+
}
|
|
14837
|
+
return { ...options, k: defaults.k !== void 0 && defaults.k < nodeCount ? defaults.k : void 0 };
|
|
14838
|
+
}
|
|
12415
14839
|
function copyAlgorithms(algorithms) {
|
|
12416
14840
|
const copy = { ...algorithms };
|
|
12417
14841
|
if (algorithms.betweenness !== void 0) {
|
|
@@ -12573,17 +14997,95 @@ function createAccelerator(ctx, options) {
|
|
|
12573
14997
|
ctx.assertReady();
|
|
12574
14998
|
return await bellmanFord(ctx, gs, source, o);
|
|
12575
14999
|
},
|
|
15000
|
+
/**
|
|
15001
|
+
* Betweenness centrality on the device (spec 8.4): exact, or sampled through `sources` / `k` (the call's own,
|
|
15002
|
+
* else the accelerator's `algorithms.betweenness` defaults), the unscaled sum over the sources run.
|
|
15003
|
+
* `endpoints: true` is refused.
|
|
15004
|
+
* @param gs - the snapshot
|
|
15005
|
+
* @param o - the seam's `BetweennessAcceleratorOptions`
|
|
15006
|
+
* @returns the f32 scores with `sourcesUsed` and `sigmaOverflow`
|
|
15007
|
+
*/
|
|
15008
|
+
async betweennessCentrality(gs, o) {
|
|
15009
|
+
ctx.assertReady();
|
|
15010
|
+
return await betweennessCentrality(
|
|
15011
|
+
ctx,
|
|
15012
|
+
gs,
|
|
15013
|
+
withBetweennessDefaults(frozen.algorithms?.betweenness, o, gs.nodeCount)
|
|
15014
|
+
);
|
|
15015
|
+
},
|
|
15016
|
+
/**
|
|
15017
|
+
* Edge betweenness on the device (spec 8.4): one score per edge, arcs summed and halved when undirected; sampling
|
|
15018
|
+
* and defaults as `betweennessCentrality`.
|
|
15019
|
+
* @param gs - the snapshot
|
|
15020
|
+
* @param o - the seam's `BetweennessAcceleratorOptions`
|
|
15021
|
+
* @returns the f32 per-edge scores with `sourcesUsed` and `sigmaOverflow`
|
|
15022
|
+
*/
|
|
15023
|
+
async edgeBetweennessCentrality(gs, o) {
|
|
15024
|
+
ctx.assertReady();
|
|
15025
|
+
return await edgeBetweennessCentrality(
|
|
15026
|
+
ctx,
|
|
15027
|
+
gs,
|
|
15028
|
+
withBetweennessDefaults(frozen.algorithms?.betweenness, o, gs.nodeCount)
|
|
15029
|
+
);
|
|
15030
|
+
},
|
|
12576
15031
|
/**
|
|
12577
15032
|
* Closeness centrality on the device (spec 8.4; P8-T13): the bit-parallel multi-source sweep, or one `sssp`
|
|
12578
15033
|
* per source when `weighted`. `maxIterations` / `tolerance` are refused when defined (P8 PD-25).
|
|
12579
15034
|
* @param gs - the snapshot
|
|
12580
|
-
* @param o -
|
|
12581
|
-
* @returns the f32 scores with `precision: "f32"` (spec 9.7)
|
|
15035
|
+
* @param o - `weighted`, and a sampled run's `sources` (undirected snapshots only)
|
|
15036
|
+
* @returns the f32 scores with `precision: "f32"` (spec 9.7) and `sourcesUsed`
|
|
12582
15037
|
*/
|
|
12583
15038
|
async closenessCentrality(gs, o) {
|
|
12584
15039
|
ctx.assertReady();
|
|
12585
15040
|
return await closenessCentrality(ctx, gs, o);
|
|
12586
15041
|
},
|
|
15042
|
+
/**
|
|
15043
|
+
* All-pairs shortest paths on the device (design 8.7): blocked Floyd-Warshall, `E_TOO_LARGE` above the device's
|
|
15044
|
+
* storage-binding ceiling. The seam passes `SsspOptions`; neither of its keys has an all-pairs meaning, so a
|
|
15045
|
+
* defined `cutoff` (it would change what `+Infinity` means) or `weights` (a per-arc override is a different
|
|
15046
|
+
* matrix from the snapshot's resident column) is `E_UNSUPPORTED { option }`, never silently dropped.
|
|
15047
|
+
* @param gs - the snapshot
|
|
15048
|
+
* @param o - the seam's `SsspOptions`; both keys refused when defined
|
|
15049
|
+
* @returns the row-major `n x n` distances and `n` (spec 3.3 line 835)
|
|
15050
|
+
*/
|
|
15051
|
+
async allPairsShortestPath(gs, o) {
|
|
15052
|
+
ctx.assertReady();
|
|
15053
|
+
for (const key of ["cutoff", "weights"]) {
|
|
15054
|
+
if (o?.[key] !== void 0) {
|
|
15055
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `allPairsShortestPath: ${key} is not supported`, {
|
|
15056
|
+
option: key,
|
|
15057
|
+
hint: "all-pairs shortest paths runs over the snapshot's own weights with no cutoff"
|
|
15058
|
+
});
|
|
15059
|
+
}
|
|
15060
|
+
}
|
|
15061
|
+
return await allPairsShortestPath(ctx, gs);
|
|
15062
|
+
},
|
|
15063
|
+
/**
|
|
15064
|
+
* Triangle counting with the clustering coefficient and the transitivity (design 8.5; P11).
|
|
15065
|
+
* @param gs - the snapshot
|
|
15066
|
+
* @returns perNode, total, coefficient and transitivity
|
|
15067
|
+
*/
|
|
15068
|
+
async triangleCount(gs) {
|
|
15069
|
+
ctx.assertReady();
|
|
15070
|
+
return await triangleCount(ctx, gs);
|
|
15071
|
+
},
|
|
15072
|
+
/**
|
|
15073
|
+
* Label propagation (design 8.6; P11): `maxIterations` and `weighted` are honoured, `tolerance` is refused when
|
|
15074
|
+
* defined (a label propagation stops at a fixed point, not below a tolerance).
|
|
15075
|
+
* @param gs - the snapshot
|
|
15076
|
+
* @param o - the seam's placeholder `HitsOptionsLike`
|
|
15077
|
+
* @returns the labels dense in first-seen order, the community count and groups()
|
|
15078
|
+
*/
|
|
15079
|
+
async labelPropagation(gs, o) {
|
|
15080
|
+
ctx.assertReady();
|
|
15081
|
+
if (o?.tolerance !== void 0) {
|
|
15082
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", "labelPropagation: tolerance has no meaning here", {
|
|
15083
|
+
option: "tolerance",
|
|
15084
|
+
hint: "label propagation stops at a fixed point or after maxIterations passes"
|
|
15085
|
+
});
|
|
15086
|
+
}
|
|
15087
|
+
return await labelPropagation(ctx, gs, { maxIterations: o?.maxIterations, weighted: o?.weighted });
|
|
15088
|
+
},
|
|
12587
15089
|
/**
|
|
12588
15090
|
* Destroys every device buffer recorded for the snapshot (spec 4.5); delegates to ctx.release.
|
|
12589
15091
|
* @param s - the snapshot the app is done with
|
|
@@ -12693,7 +15195,7 @@ async function calibrateLayout(ctx, options) {
|
|
|
12693
15195
|
};
|
|
12694
15196
|
}
|
|
12695
15197
|
export {
|
|
12696
|
-
|
|
15198
|
+
a0 as ARC_WINDOW_ALIGN,
|
|
12697
15199
|
EXACT_MAX_NODES,
|
|
12698
15200
|
FA2_DEFAULTS,
|
|
12699
15201
|
FR_DEFAULTS,
|
|
@@ -12701,12 +15203,14 @@ export {
|
|
|
12701
15203
|
LAYOUT_TUNING_DEFAULTS,
|
|
12702
15204
|
MAX_1D_ITEMS,
|
|
12703
15205
|
MAX_WORKGROUPS_PER_DIM,
|
|
12704
|
-
|
|
15206
|
+
a1 as PASSTHROUGH_FORMAT_CODES,
|
|
12705
15207
|
SE_DEFAULTS,
|
|
12706
|
-
|
|
12707
|
-
|
|
15208
|
+
a2 as STORAGE_ALIGN,
|
|
15209
|
+
a3 as WORKGROUP_SIZE,
|
|
12708
15210
|
WebGpuGraphError,
|
|
15211
|
+
allPairsShortestPath,
|
|
12709
15212
|
bellmanFord,
|
|
15213
|
+
betweennessCentrality,
|
|
12710
15214
|
breadthFirstSearch,
|
|
12711
15215
|
calibrateLayout,
|
|
12712
15216
|
closenessCentrality,
|
|
@@ -12716,16 +15220,19 @@ export {
|
|
|
12716
15220
|
createFruchtermanReingold,
|
|
12717
15221
|
createSpringElectrical,
|
|
12718
15222
|
degree,
|
|
15223
|
+
edgeBetweennessCentrality,
|
|
12719
15224
|
eigenvectorCentrality,
|
|
12720
15225
|
hasErrorCode,
|
|
12721
15226
|
hits,
|
|
12722
|
-
|
|
15227
|
+
a4 as isSoftwareAdapter,
|
|
12723
15228
|
isWebGpuGraphError,
|
|
12724
15229
|
katzCentrality,
|
|
15230
|
+
labelPropagation,
|
|
12725
15231
|
pageRank,
|
|
12726
15232
|
personalizedPageRank,
|
|
12727
15233
|
seedPositions,
|
|
12728
15234
|
sssp,
|
|
15235
|
+
triangleCount,
|
|
12729
15236
|
verifyDevice
|
|
12730
15237
|
};
|
|
12731
15238
|
//# sourceMappingURL=webgpu-graph-algorithms.js.map
|