@graphty/webgpu-graph-algorithms 0.6.15 → 0.6.17
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 +5 -5
- package/dist/browser.js +1 -1
- package/dist/chunks/{context-oXphO3yj.js → context-VIvatQOo.js} +61 -34
- package/dist/chunks/context-VIvatQOo.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/src/accelerator.d.ts +2 -1
- package/dist/src/accelerator.d.ts.map +1 -1
- package/dist/src/accelerator.js +53 -1
- 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/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 +45 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +45 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +8 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/prelude.d.ts.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 +16 -4
- package/dist/src/kernels.d.ts.map +1 -1
- package/dist/src/kernels.js +223 -3
- 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/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 +10 -2
- package/dist/src/types/accelerator.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/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/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 +1819 -181
- package/dist/webgpu-graph-algorithms.js.map +1 -1
- package/package.json +4 -4
- package/src/accelerator.ts +56 -1
- package/src/algorithms/all-pairs.ts +228 -0
- 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 +45 -0
- package/src/index.ts +12 -1
- package/src/kernel/prelude.ts +6 -0
- package/src/kernels.ts +248 -6
- package/src/memory/residency.ts +15 -4
- package/src/primitives/coo-to-csr.ts +251 -0
- package/src/primitives/group-by-key.ts +209 -0
- package/src/types/accelerator.ts +10 -2
- package/src/types/all-pairs.ts +37 -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/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-oXphO3yj.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
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, m as
|
|
2
|
-
import {
|
|
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
3
|
import { renumberPartition, INVALID_INDEX, foldArcs, makeMask, maskTest, expandEdges, fromEdgeArrays } from "@graphty/graph-format";
|
|
4
4
|
class UniformRing {
|
|
5
5
|
/**
|
|
@@ -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,116 @@ 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
|
+
);
|
|
608
722
|
const bcBackwardWgsl = (
|
|
609
723
|
/* wgsl */
|
|
610
724
|
`
|
|
@@ -1249,6 +1363,54 @@ fn compact_scatter(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
|
|
|
1249
1363
|
}
|
|
1250
1364
|
`
|
|
1251
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
|
+
);
|
|
1252
1414
|
const countingScatterWgsl = (
|
|
1253
1415
|
/* wgsl */
|
|
1254
1416
|
`
|
|
@@ -2188,6 +2350,130 @@ fn grid_near_field(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocat
|
|
|
2188
2350
|
}
|
|
2189
2351
|
`
|
|
2190
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
|
+
);
|
|
2191
2477
|
const histogramWgsl = (
|
|
2192
2478
|
/* wgsl */
|
|
2193
2479
|
`
|
|
@@ -2222,6 +2508,51 @@ fn indirect_finalize(@builtin(local_invocation_id) lid: vec3<u32>) {
|
|
|
2222
2508
|
}
|
|
2223
2509
|
`
|
|
2224
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
|
+
);
|
|
2225
2556
|
const prFinalizeWgsl = (
|
|
2226
2557
|
/* wgsl */
|
|
2227
2558
|
`
|
|
@@ -2407,6 +2738,20 @@ fn reduce(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_id) l
|
|
|
2407
2738
|
}
|
|
2408
2739
|
`
|
|
2409
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
|
+
);
|
|
2410
2755
|
const scanAddWgsl = (
|
|
2411
2756
|
/* wgsl */
|
|
2412
2757
|
`
|
|
@@ -2727,6 +3072,63 @@ fn sssp_relax(@builtin(workgroup_id) wid: vec3<u32>, @builtin(local_invocation_i
|
|
|
2727
3072
|
}
|
|
2728
3073
|
`
|
|
2729
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
|
+
);
|
|
2730
3132
|
const wccCompressWgsl = (
|
|
2731
3133
|
/* wgsl */
|
|
2732
3134
|
`
|
|
@@ -3116,6 +3518,30 @@ const BF_FLAGS = UniformBlock.define(
|
|
|
3116
3518
|
],
|
|
3117
3519
|
{ layout: "storage" }
|
|
3118
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
|
+
]);
|
|
3119
3545
|
function decl(group, binding, name, kind, wgslType) {
|
|
3120
3546
|
return { group, binding, name, kind, wgslType };
|
|
3121
3547
|
}
|
|
@@ -3996,6 +4422,165 @@ const BC_FORWARD_EDGE = {
|
|
|
3996
4422
|
snippetSlots: [],
|
|
3997
4423
|
phase: "P9"
|
|
3998
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
|
+
};
|
|
3999
4584
|
const REGISTRY = Object.freeze({
|
|
4000
4585
|
degree: DEGREE,
|
|
4001
4586
|
reduce: REDUCE,
|
|
@@ -4048,7 +4633,16 @@ const REGISTRY = Object.freeze({
|
|
|
4048
4633
|
"bc-backward": BC_BACKWARD,
|
|
4049
4634
|
"bc-gather": BC_GATHER,
|
|
4050
4635
|
"bc-edge-gather": BC_EDGE_GATHER,
|
|
4051
|
-
"bc-forward-edge": BC_FORWARD_EDGE
|
|
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
|
|
4052
4646
|
});
|
|
4053
4647
|
const bodyOverrides = /* @__PURE__ */ new Map();
|
|
4054
4648
|
function entryOf(id) {
|
|
@@ -4207,7 +4801,7 @@ class ScanPlannerImpl {
|
|
|
4207
4801
|
}
|
|
4208
4802
|
const POISON = 3735928559;
|
|
4209
4803
|
const CHECK_BLOCKS = 32;
|
|
4210
|
-
const RING_SLOTS$
|
|
4804
|
+
const RING_SLOTS$9 = 8;
|
|
4211
4805
|
const checked = /* @__PURE__ */ new WeakMap();
|
|
4212
4806
|
function inputAt(i) {
|
|
4213
4807
|
return i + 1;
|
|
@@ -4235,7 +4829,7 @@ async function runCheck(ctx) {
|
|
|
4235
4829
|
const count = CHECK_BLOCKS * wg + 1;
|
|
4236
4830
|
const bytes = 4 * count;
|
|
4237
4831
|
const lease = ctx.pool.lease();
|
|
4238
|
-
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");
|
|
4239
4833
|
try {
|
|
4240
4834
|
const scope = {
|
|
4241
4835
|
device: ctx.device,
|
|
@@ -4452,7 +5046,7 @@ function coreOfView(v, arcCount) {
|
|
|
4452
5046
|
hasWeights: weights !== null
|
|
4453
5047
|
});
|
|
4454
5048
|
}
|
|
4455
|
-
function checkDest$
|
|
5049
|
+
function checkDest$7(dest, n) {
|
|
4456
5050
|
if (dest === void 0) {
|
|
4457
5051
|
return null;
|
|
4458
5052
|
}
|
|
@@ -4481,7 +5075,7 @@ async function degree(ctx, s, options) {
|
|
|
4481
5075
|
ctx.assertReady();
|
|
4482
5076
|
await assertDeviceComputes(ctx);
|
|
4483
5077
|
const n = s.nodeCount;
|
|
4484
|
-
const dest = checkDest$
|
|
5078
|
+
const dest = checkDest$7(options?.dest, n);
|
|
4485
5079
|
if (options?.signal?.aborted) {
|
|
4486
5080
|
throw new WebGpuGraphError("E_ABORTED", "degree: the signal was aborted before any work started", {});
|
|
4487
5081
|
}
|
|
@@ -4902,13 +5496,13 @@ function algorithmScope(ctx, label, slots) {
|
|
|
4902
5496
|
ringOverruns: () => ring.overruns
|
|
4903
5497
|
};
|
|
4904
5498
|
}
|
|
4905
|
-
const ALGORITHM$
|
|
5499
|
+
const ALGORITHM$8 = "connectedComponents";
|
|
4906
5500
|
const ROUNDS_PER_BATCH$1 = 4;
|
|
4907
5501
|
const MAX_WCC_ROUNDS = 64;
|
|
4908
5502
|
const SAMPLE_SIZE = 1024;
|
|
4909
5503
|
const MAX_STEPS = 1024;
|
|
4910
|
-
const RING_SLOTS$
|
|
4911
|
-
function checkDest$
|
|
5504
|
+
const RING_SLOTS$8 = 2 * ROUNDS_PER_BATCH$1;
|
|
5505
|
+
function checkDest$6(dest, n) {
|
|
4912
5506
|
if (dest === void 0) {
|
|
4913
5507
|
return null;
|
|
4914
5508
|
}
|
|
@@ -4917,7 +5511,7 @@ function checkDest$4(dest, n) {
|
|
|
4917
5511
|
}
|
|
4918
5512
|
throw new WebGpuGraphError(
|
|
4919
5513
|
"E_INVALID_ARGUMENT",
|
|
4920
|
-
`${ALGORITHM$
|
|
5514
|
+
`${ALGORITHM$8}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
4921
5515
|
{
|
|
4922
5516
|
argument: "dest",
|
|
4923
5517
|
value: `${dest.constructor.name}(${dest.length})`,
|
|
@@ -4927,7 +5521,7 @@ function checkDest$4(dest, n) {
|
|
|
4927
5521
|
}
|
|
4928
5522
|
function coreOf$2(ctx, s) {
|
|
4929
5523
|
const core = ctx.residency.core(s);
|
|
4930
|
-
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$
|
|
5524
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$8);
|
|
4931
5525
|
return core;
|
|
4932
5526
|
}
|
|
4933
5527
|
function bindingOf$3(buffer, size) {
|
|
@@ -4986,8 +5580,8 @@ function checkLabels(raw) {
|
|
|
4986
5580
|
for (let v = 0; v < n; v++) {
|
|
4987
5581
|
const label = raw[v];
|
|
4988
5582
|
if (label >= n) {
|
|
4989
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
4990
|
-
label: `${ALGORITHM$
|
|
5583
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$8}: labels[${v}] = ${label} is not a node index`, {
|
|
5584
|
+
label: `${ALGORITHM$8}/labels`,
|
|
4991
5585
|
message: `the device produced a label outside [0, ${n})`
|
|
4992
5586
|
});
|
|
4993
5587
|
}
|
|
@@ -5003,9 +5597,9 @@ async function connectedComponents(ctx, s, options) {
|
|
|
5003
5597
|
await assertDeviceComputes(ctx);
|
|
5004
5598
|
const n = s.nodeCount;
|
|
5005
5599
|
const renumber = options?.renumber !== false;
|
|
5006
|
-
const dest = checkDest$
|
|
5600
|
+
const dest = checkDest$6(options?.dest, n);
|
|
5007
5601
|
if (options?.signal?.aborted) {
|
|
5008
|
-
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$
|
|
5602
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$8}: the signal was aborted before any work started`, {});
|
|
5009
5603
|
}
|
|
5010
5604
|
if (n === 0) {
|
|
5011
5605
|
options?.onProgress?.(1, 1);
|
|
@@ -5022,7 +5616,7 @@ async function connectedComponents(ctx, s, options) {
|
|
|
5022
5616
|
}
|
|
5023
5617
|
const edges = ctx.residency.view(s, "edgeList");
|
|
5024
5618
|
const edgeCount = edges.scalars.edgeCount[0];
|
|
5025
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
5619
|
+
const scope = algorithmScope(ctx, ALGORITHM$8, RING_SLOTS$8);
|
|
5026
5620
|
try {
|
|
5027
5621
|
const compBytes = 4 * (n + 1);
|
|
5028
5622
|
const comp = scope.scratch(compBytes, "comp");
|
|
@@ -5051,7 +5645,7 @@ async function connectedComponents(ctx, s, options) {
|
|
|
5051
5645
|
return batch.submit();
|
|
5052
5646
|
};
|
|
5053
5647
|
queue.writeBuffer(comp, 4 * flagIndex, zero);
|
|
5054
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
5648
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$8}/setup`);
|
|
5055
5649
|
let pass = setup.pass("sample-rounds");
|
|
5056
5650
|
const fillParams = scope.params(FILL_PARAMS, { count: n, value: 0, mode: 1, pad0: 0 });
|
|
5057
5651
|
fill.dispatch(
|
|
@@ -5070,7 +5664,7 @@ async function connectedComponents(ctx, s, options) {
|
|
|
5070
5664
|
setup.endPass();
|
|
5071
5665
|
await submit2(setup).readback;
|
|
5072
5666
|
ctx.assertReady();
|
|
5073
|
-
const sampler = new CommandBatch(ctx, `${ALGORITHM$
|
|
5667
|
+
const sampler = new CommandBatch(ctx, `${ALGORITHM$8}/sample`);
|
|
5074
5668
|
pass = sampler.pass("sample");
|
|
5075
5669
|
const sampleParams = wccParams({ items, stride: 0, r: 0, giant: U32_MAX$2 });
|
|
5076
5670
|
sample.dispatch(
|
|
@@ -5088,7 +5682,7 @@ async function connectedComponents(ctx, s, options) {
|
|
|
5088
5682
|
let rounds = 0;
|
|
5089
5683
|
for (; ; ) {
|
|
5090
5684
|
queue.writeBuffer(comp, 4 * flagIndex, zero);
|
|
5091
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
5685
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$8}/rounds`);
|
|
5092
5686
|
pass = batch.pass("edge-rounds");
|
|
5093
5687
|
for (let i = 0; i < ROUNDS_PER_BATCH$1; i++) {
|
|
5094
5688
|
const params = wccParams({ items: edgeCount, stride: edgePlan.stride ?? edgeCount, r: 0, giant });
|
|
@@ -5103,7 +5697,7 @@ async function connectedComponents(ctx, s, options) {
|
|
|
5103
5697
|
rounds += ROUNDS_PER_BATCH$1;
|
|
5104
5698
|
ctx.assertReady();
|
|
5105
5699
|
if (options?.signal?.aborted) {
|
|
5106
|
-
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$
|
|
5700
|
+
throw new WebGpuGraphError("E_ABORTED", `${ALGORITHM$8}: the signal was aborted`, {
|
|
5107
5701
|
batchId: submitted.id
|
|
5108
5702
|
});
|
|
5109
5703
|
}
|
|
@@ -5113,12 +5707,12 @@ async function connectedComponents(ctx, s, options) {
|
|
|
5113
5707
|
if (rounds >= MAX_WCC_ROUNDS) {
|
|
5114
5708
|
throw new WebGpuGraphError(
|
|
5115
5709
|
"E_VALIDATION",
|
|
5116
|
-
`${ALGORITHM$
|
|
5117
|
-
{ 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` }
|
|
5118
5712
|
);
|
|
5119
5713
|
}
|
|
5120
5714
|
}
|
|
5121
|
-
const final = new CommandBatch(ctx, `${ALGORITHM$
|
|
5715
|
+
const final = new CommandBatch(ctx, `${ALGORITHM$8}/final`);
|
|
5122
5716
|
recordCompress(final.pass("compress"));
|
|
5123
5717
|
final.endPass();
|
|
5124
5718
|
await submit2(final).readback;
|
|
@@ -5360,8 +5954,20 @@ class TieredPlanner {
|
|
|
5360
5954
|
async function prepareSegmentedReduce(scope, core, options) {
|
|
5361
5955
|
const op = opCode(options.op);
|
|
5362
5956
|
validateValueSnippet(options.valueSnippet);
|
|
5363
|
-
const
|
|
5364
|
-
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
|
+
];
|
|
5365
5971
|
const compiled = [];
|
|
5366
5972
|
for (const range of ranges) {
|
|
5367
5973
|
if (range.tier !== 0 && range.end <= range.start) {
|
|
@@ -5484,8 +6090,8 @@ async function prepareSpmvPull(scope, rev, options) {
|
|
|
5484
6090
|
return new SpmvPullPlannerImpl(scope, compiled, perm, options.weights);
|
|
5485
6091
|
}
|
|
5486
6092
|
const PR_BATCH = 8;
|
|
5487
|
-
const RING_SLOTS$
|
|
5488
|
-
function checkDest$
|
|
6093
|
+
const RING_SLOTS$7 = 2 * PR_BATCH + 2;
|
|
6094
|
+
function checkDest$5(dest, n, algorithm) {
|
|
5489
6095
|
if (dest === void 0) {
|
|
5490
6096
|
return null;
|
|
5491
6097
|
}
|
|
@@ -5532,7 +6138,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
5532
6138
|
expected: "a positive integer"
|
|
5533
6139
|
});
|
|
5534
6140
|
}
|
|
5535
|
-
const dest = checkDest$
|
|
6141
|
+
const dest = checkDest$5(options?.dest, n, algorithm);
|
|
5536
6142
|
if (options?.signal?.aborted) {
|
|
5537
6143
|
throw aborted$3(algorithm);
|
|
5538
6144
|
}
|
|
@@ -5562,7 +6168,7 @@ async function run(ctx, s, personalization, options, algorithm) {
|
|
|
5562
6168
|
const weights = useWeights ? void 0 : null;
|
|
5563
6169
|
const weightedCore = useWeights ? core : { ...core, weights: null, hasWeights: false };
|
|
5564
6170
|
const weightedRev = useWeights ? rev : { ...rev, weights: null, hasWeights: false };
|
|
5565
|
-
const scope = algorithmScope(ctx, algorithm, RING_SLOTS$
|
|
6171
|
+
const scope = algorithmScope(ctx, algorithm, RING_SLOTS$7);
|
|
5566
6172
|
let uploaded = null;
|
|
5567
6173
|
try {
|
|
5568
6174
|
const bytes = 4 * n;
|
|
@@ -5725,8 +6331,8 @@ async function personalizedPageRank(ctx, s, personalization, options) {
|
|
|
5725
6331
|
return run(ctx, s, normalised2, options, "personalizedPageRank");
|
|
5726
6332
|
}
|
|
5727
6333
|
const BATCH = 8;
|
|
5728
|
-
const RING_SLOTS$
|
|
5729
|
-
function checkDest$
|
|
6334
|
+
const RING_SLOTS$6 = 4 * BATCH + 8;
|
|
6335
|
+
function checkDest$4(dest, n, algorithm) {
|
|
5730
6336
|
if (dest === void 0) {
|
|
5731
6337
|
return null;
|
|
5732
6338
|
}
|
|
@@ -5764,7 +6370,7 @@ function whole(buffer, size) {
|
|
|
5764
6370
|
}
|
|
5765
6371
|
async function runPowerIteration(ctx, n, config) {
|
|
5766
6372
|
await assertDeviceComputes(ctx);
|
|
5767
|
-
const scope = algorithmScope(ctx, config.label, RING_SLOTS$
|
|
6373
|
+
const scope = algorithmScope(ctx, config.label, RING_SLOTS$6);
|
|
5768
6374
|
try {
|
|
5769
6375
|
const bytes = 4 * n;
|
|
5770
6376
|
const ring = (config.alternate === null ? ["rankA", "rankB"] : ["rankA", "rankB", "rankC"]).map(
|
|
@@ -5876,7 +6482,7 @@ function resolve(ctx, s, options, algorithm) {
|
|
|
5876
6482
|
expected: "a positive integer"
|
|
5877
6483
|
});
|
|
5878
6484
|
}
|
|
5879
|
-
const dest = checkDest$
|
|
6485
|
+
const dest = checkDest$4(options?.dest, n, algorithm);
|
|
5880
6486
|
if (options?.signal?.aborted) {
|
|
5881
6487
|
throw aborted$2(algorithm);
|
|
5882
6488
|
}
|
|
@@ -6032,7 +6638,7 @@ function checkCount(what, count) {
|
|
|
6032
6638
|
});
|
|
6033
6639
|
}
|
|
6034
6640
|
}
|
|
6035
|
-
function checkWords$
|
|
6641
|
+
function checkWords$3(what, name, binding, words) {
|
|
6036
6642
|
if (binding.size < 4 * words) {
|
|
6037
6643
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${what}: ${name} is smaller than 4 x ${words} bytes`, {
|
|
6038
6644
|
argument: name,
|
|
@@ -6052,15 +6658,15 @@ function checkWordIndex(what, name, index, block) {
|
|
|
6052
6658
|
}
|
|
6053
6659
|
function checkCompactArguments(r) {
|
|
6054
6660
|
checkCount("compact", r.count);
|
|
6055
|
-
checkWords$
|
|
6056
|
-
checkWords$
|
|
6057
|
-
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);
|
|
6058
6664
|
checkWordIndex("compact", "outIndex", r.outIndex, r.outCount);
|
|
6059
6665
|
}
|
|
6060
6666
|
function checkDedupeArguments(r) {
|
|
6061
6667
|
checkCount("dedupe", r.count);
|
|
6062
|
-
checkWords$
|
|
6063
|
-
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)));
|
|
6064
6670
|
checkWordIndex("dedupe", "outIndex", r.outIndex, r.outCount);
|
|
6065
6671
|
if (r.countIndex === U32_MAX$2) {
|
|
6066
6672
|
return;
|
|
@@ -6544,7 +7150,7 @@ class RadixSortPlannerImpl {
|
|
|
6544
7150
|
return src;
|
|
6545
7151
|
}
|
|
6546
7152
|
}
|
|
6547
|
-
const ALGORITHM$
|
|
7153
|
+
const ALGORITHM$7 = "breadthFirstSearch";
|
|
6548
7154
|
const NEXT_DEGREE_MAX_GROUPS = 128;
|
|
6549
7155
|
function bfsRingSlots(windows, levelsPerSubmit) {
|
|
6550
7156
|
return Math.max((5 + 4 * windows) * levelsPerSubmit + 16, RESULT_BATCH_SLOTS + windows);
|
|
@@ -6556,7 +7162,7 @@ function normaliseMaxDepth(maxDepth) {
|
|
|
6556
7162
|
}
|
|
6557
7163
|
return Math.max(0, Math.ceil(maxDepth));
|
|
6558
7164
|
}
|
|
6559
|
-
function checkDest$
|
|
7165
|
+
function checkDest$3(dest, n) {
|
|
6560
7166
|
if (dest === void 0) {
|
|
6561
7167
|
return null;
|
|
6562
7168
|
}
|
|
@@ -6565,7 +7171,7 @@ function checkDest$1(dest, n) {
|
|
|
6565
7171
|
}
|
|
6566
7172
|
throw new WebGpuGraphError(
|
|
6567
7173
|
"E_INVALID_ARGUMENT",
|
|
6568
|
-
`${ALGORITHM$
|
|
7174
|
+
`${ALGORITHM$7}: dest must be a Uint32Array of length ${n} over an ArrayBuffer`,
|
|
6569
7175
|
{
|
|
6570
7176
|
argument: "dest",
|
|
6571
7177
|
value: `${dest.constructor.name}(${dest.length})`,
|
|
@@ -6580,8 +7186,8 @@ function degreeView(ctx, s, name) {
|
|
|
6580
7186
|
const { bindings } = ctx.residency.view(s, name);
|
|
6581
7187
|
const { [name]: binding } = bindings;
|
|
6582
7188
|
if (binding === void 0) {
|
|
6583
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
6584
|
-
label: `${ALGORITHM$
|
|
7189
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$7}: the ${name} view has no ${name} binding`, {
|
|
7190
|
+
label: `${ALGORITHM$7}/${name}`,
|
|
6585
7191
|
message: `the ${name} view has no ${name} binding`
|
|
6586
7192
|
});
|
|
6587
7193
|
}
|
|
@@ -6590,15 +7196,15 @@ function degreeView(ctx, s, name) {
|
|
|
6590
7196
|
function aborted$1(batchId) {
|
|
6591
7197
|
return new WebGpuGraphError(
|
|
6592
7198
|
"E_ABORTED",
|
|
6593
|
-
`${ALGORITHM$
|
|
7199
|
+
`${ALGORITHM$7}: the signal was aborted`,
|
|
6594
7200
|
batchId === void 0 ? {} : { batchId }
|
|
6595
7201
|
);
|
|
6596
7202
|
}
|
|
6597
7203
|
function wordOf$1(block, name) {
|
|
6598
7204
|
const value = block[name];
|
|
6599
7205
|
if (typeof value !== "number") {
|
|
6600
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
6601
|
-
label: `${ALGORITHM$
|
|
7206
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$7}: counters.${name} did not decode to a number`, {
|
|
7207
|
+
label: `${ALGORITHM$7}/counters`,
|
|
6602
7208
|
message: `the field ${name} did not decode to a number`
|
|
6603
7209
|
});
|
|
6604
7210
|
}
|
|
@@ -6609,7 +7215,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6609
7215
|
await assertDeviceComputes(ctx);
|
|
6610
7216
|
const n = s.nodeCount;
|
|
6611
7217
|
if (!Number.isInteger(source) || source < 0 || source >= n) {
|
|
6612
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$
|
|
7218
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$7}: source ${source} is outside [0, ${n})`, {
|
|
6613
7219
|
argument: "source",
|
|
6614
7220
|
value: source,
|
|
6615
7221
|
expected: `an integer in [0, ${n})`
|
|
@@ -6619,7 +7225,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6619
7225
|
if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
6620
7226
|
throw new WebGpuGraphError(
|
|
6621
7227
|
"E_INVALID_ARGUMENT",
|
|
6622
|
-
`${ALGORITHM$
|
|
7228
|
+
`${ALGORITHM$7}: levelsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
6623
7229
|
{
|
|
6624
7230
|
argument: "levelsPerSubmit",
|
|
6625
7231
|
value: levelsPerSubmit,
|
|
@@ -6627,7 +7233,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6627
7233
|
}
|
|
6628
7234
|
);
|
|
6629
7235
|
}
|
|
6630
|
-
const dest = checkDest$
|
|
7236
|
+
const dest = checkDest$3(options?.dest, n);
|
|
6631
7237
|
const maxDepth = normaliseMaxDepth(options?.maxDepth);
|
|
6632
7238
|
const predKind = tuning.predKind ?? 1;
|
|
6633
7239
|
if (options?.signal?.aborted) {
|
|
@@ -6638,12 +7244,12 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6638
7244
|
if (s.directed && 4 * s.arcCount > ctx.caps.limits.maxStorageBufferBindingSize) {
|
|
6639
7245
|
throw new WebGpuGraphError(
|
|
6640
7246
|
"E_TOO_LARGE",
|
|
6641
|
-
`${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`,
|
|
6642
7248
|
{
|
|
6643
7249
|
needed: 4 * s.arcCount,
|
|
6644
7250
|
limit: ctx.caps.limits.maxStorageBufferBindingSize,
|
|
6645
7251
|
path: "windowed",
|
|
6646
|
-
algorithm: ALGORITHM$
|
|
7252
|
+
algorithm: ALGORITHM$7
|
|
6647
7253
|
}
|
|
6648
7254
|
);
|
|
6649
7255
|
}
|
|
@@ -6651,7 +7257,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6651
7257
|
const backward = coreWindows(reverse);
|
|
6652
7258
|
const outDegree = degreeView(ctx, s, "outDegree");
|
|
6653
7259
|
const inDegree = degreeView(ctx, s, "inDegree");
|
|
6654
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
7260
|
+
const scope = algorithmScope(ctx, ALGORITHM$7, bfsRingSlots(forward.length, levelsPerSubmit));
|
|
6655
7261
|
tuning.onScope?.(scope);
|
|
6656
7262
|
try {
|
|
6657
7263
|
const bytes = 4 * n;
|
|
@@ -6715,7 +7321,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6715
7321
|
scope.flush();
|
|
6716
7322
|
return batch.submit();
|
|
6717
7323
|
};
|
|
6718
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
7324
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$7}/setup`);
|
|
6719
7325
|
const setupPass = setup.pass("fill");
|
|
6720
7326
|
recordFill2(setupPass, depth, INVALID_INDEX, 0);
|
|
6721
7327
|
recordFill2(setupPass, iota, 0, 1);
|
|
@@ -6735,7 +7341,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6735
7341
|
let submits = 0;
|
|
6736
7342
|
for (; ; ) {
|
|
6737
7343
|
queue.writeBuffer(counters.buffer, counters.offset + 4 * W.unvisitedCount, new Uint32Array(3));
|
|
6738
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
7344
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$7}/levels`);
|
|
6739
7345
|
const pass2 = batch.pass("bfs");
|
|
6740
7346
|
recordRebuild(pass2);
|
|
6741
7347
|
const bitsParams = scope.params(FILL_PARAMS, { count: bitsWords, value: 0, mode: 0, pad0: 0 });
|
|
@@ -6845,8 +7451,8 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6845
7451
|
if (submits > n + 1) {
|
|
6846
7452
|
throw new WebGpuGraphError(
|
|
6847
7453
|
"E_VALIDATION",
|
|
6848
|
-
`${ALGORITHM$
|
|
6849
|
-
{ 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` }
|
|
6850
7456
|
);
|
|
6851
7457
|
}
|
|
6852
7458
|
}
|
|
@@ -6860,7 +7466,7 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6860
7466
|
offsets: bindingOf$1(scope.scratch(histBytes, "order/offsets"), histBytes)
|
|
6861
7467
|
};
|
|
6862
7468
|
const parent = bindingOf$1(scope.scratch(bytes, "parent"), bytes);
|
|
6863
|
-
const result = new CommandBatch(ctx, `${ALGORITHM$
|
|
7469
|
+
const result = new CommandBatch(ctx, `${ALGORITHM$7}/result`);
|
|
6864
7470
|
result.copy(depth, keys, bytes);
|
|
6865
7471
|
const pass = result.pass("result");
|
|
6866
7472
|
recordFill2(pass, vals, 0, 1);
|
|
@@ -6897,9 +7503,9 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6897
7503
|
if (visitedCount > n) {
|
|
6898
7504
|
throw new WebGpuGraphError(
|
|
6899
7505
|
"E_VALIDATION",
|
|
6900
|
-
`${ALGORITHM$
|
|
7506
|
+
`${ALGORITHM$7}: visitedCount ${visitedCount} exceeds the ${n} vertices (a duplicate claim)`,
|
|
6901
7507
|
{
|
|
6902
|
-
label: `${ALGORITHM$
|
|
7508
|
+
label: `${ALGORITHM$7}/visitedCount`,
|
|
6903
7509
|
message: `the device counted ${visitedCount} visits of ${n} vertices`
|
|
6904
7510
|
}
|
|
6905
7511
|
);
|
|
@@ -6923,9 +7529,9 @@ async function bfsWithTuning(ctx, s, source, options, tuning) {
|
|
|
6923
7529
|
function breadthFirstSearch(ctx, s, source, options) {
|
|
6924
7530
|
return bfsWithTuning(ctx, s, source, options, {});
|
|
6925
7531
|
}
|
|
6926
|
-
const ALGORITHM$
|
|
7532
|
+
const ALGORITHM$6 = "sssp";
|
|
6927
7533
|
const HALF_ALIGN = 64;
|
|
6928
|
-
const RING_SLOTS$
|
|
7534
|
+
const RING_SLOTS$5 = 4 * MAX_LEVELS_PER_SUBMIT + 40;
|
|
6929
7535
|
function bitsOf(value) {
|
|
6930
7536
|
return new Uint32Array(Float32Array.of(value).buffer)[0];
|
|
6931
7537
|
}
|
|
@@ -6951,14 +7557,14 @@ function assertSource(algorithm, source, n) {
|
|
|
6951
7557
|
function wordOf(block, name) {
|
|
6952
7558
|
const value = block[name];
|
|
6953
7559
|
if (typeof value !== "number") {
|
|
6954
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
6955
|
-
label: `${ALGORITHM$
|
|
7560
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$6}: counters.${name} did not decode to a number`, {
|
|
7561
|
+
label: `${ALGORITHM$6}/counters`,
|
|
6956
7562
|
message: `the field ${name} did not decode to a number`
|
|
6957
7563
|
});
|
|
6958
7564
|
}
|
|
6959
7565
|
return value;
|
|
6960
7566
|
}
|
|
6961
|
-
function checkDest(algorithm, dest, n) {
|
|
7567
|
+
function checkDest$2(algorithm, dest, n) {
|
|
6962
7568
|
if (dest === void 0) {
|
|
6963
7569
|
return null;
|
|
6964
7570
|
}
|
|
@@ -7119,12 +7725,12 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7119
7725
|
ctx.assertReady();
|
|
7120
7726
|
await assertDeviceComputes(ctx);
|
|
7121
7727
|
const n = s.nodeCount;
|
|
7122
|
-
assertSource(ALGORITHM$
|
|
7728
|
+
assertSource(ALGORITHM$6, source, n);
|
|
7123
7729
|
const roundsPerSubmit = tuning.roundsPerSubmit ?? MAX_LEVELS_PER_SUBMIT;
|
|
7124
7730
|
if (!Number.isInteger(roundsPerSubmit) || roundsPerSubmit < 1 || roundsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
7125
7731
|
throw new WebGpuGraphError(
|
|
7126
7732
|
"E_INVALID_ARGUMENT",
|
|
7127
|
-
`${ALGORITHM$
|
|
7733
|
+
`${ALGORITHM$6}: roundsPerSubmit must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
7128
7734
|
{
|
|
7129
7735
|
argument: "roundsPerSubmit",
|
|
7130
7736
|
value: roundsPerSubmit,
|
|
@@ -7133,42 +7739,42 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7133
7739
|
);
|
|
7134
7740
|
}
|
|
7135
7741
|
if (tuning.delta !== void 0 && !(Number.isFinite(tuning.delta) && tuning.delta > 0)) {
|
|
7136
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$
|
|
7742
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$6}: delta must be a finite positive number`, {
|
|
7137
7743
|
argument: "delta",
|
|
7138
7744
|
value: tuning.delta,
|
|
7139
7745
|
expected: "a finite positive number"
|
|
7140
7746
|
});
|
|
7141
7747
|
}
|
|
7142
|
-
const dest = checkDest(ALGORITHM$
|
|
7143
|
-
const vector2 = resolveWeights$1(ALGORITHM$
|
|
7144
|
-
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);
|
|
7145
7751
|
if (options?.signal?.aborted) {
|
|
7146
|
-
throw aborted(ALGORITHM$
|
|
7752
|
+
throw aborted(ALGORITHM$6);
|
|
7147
7753
|
}
|
|
7148
7754
|
if (vector2 === null || vector2.allOne) {
|
|
7149
7755
|
return unitWeightRoute(ctx, s, source, cutoff, dest, options);
|
|
7150
7756
|
}
|
|
7151
7757
|
if (!vector2.nonNegative) {
|
|
7152
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$
|
|
7758
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$6}: a negative weight has no shortest path here`, {
|
|
7153
7759
|
feature: "sssp.negativeWeights",
|
|
7154
7760
|
hint: "use bellmanFord"
|
|
7155
7761
|
});
|
|
7156
7762
|
}
|
|
7157
7763
|
if (!vector2.finite) {
|
|
7158
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$
|
|
7764
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$6}: a NaN or infinite weight has no bit-pattern order`, {
|
|
7159
7765
|
feature: "sssp.nonFiniteWeights"
|
|
7160
7766
|
});
|
|
7161
7767
|
}
|
|
7162
7768
|
const { arcCount } = s;
|
|
7163
7769
|
const core = ctx.residency.core(s);
|
|
7164
7770
|
const limit = ctx.caps.limits.maxStorageBufferBindingSize;
|
|
7165
|
-
assertWholeCore(core, arcCount, limit, ALGORITHM$
|
|
7771
|
+
assertWholeCore(core, arcCount, limit, ALGORITHM$6);
|
|
7166
7772
|
const cap = Math.ceil(Math.max(1, arcCount) / HALF_ALIGN) * HALF_ALIGN;
|
|
7167
7773
|
if (8 * cap > limit) {
|
|
7168
7774
|
throw new WebGpuGraphError(
|
|
7169
7775
|
"E_TOO_LARGE",
|
|
7170
|
-
`${ALGORITHM$
|
|
7171
|
-
{ 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 }
|
|
7172
7778
|
);
|
|
7173
7779
|
}
|
|
7174
7780
|
const delta = Math.fround(
|
|
@@ -7177,7 +7783,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7177
7783
|
const deltaBits = bitsOf(delta);
|
|
7178
7784
|
const maxRounds = n + Math.ceil(vector2.sum / delta) + 1;
|
|
7179
7785
|
const maxSubmits = Math.ceil((maxRounds + 1) / roundsPerSubmit) + 1;
|
|
7180
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
7786
|
+
const scope = algorithmScope(ctx, ALGORITHM$6, RING_SLOTS$5);
|
|
7181
7787
|
try {
|
|
7182
7788
|
const wg = ctx.workgroupSize;
|
|
7183
7789
|
const bytes = 4 * n;
|
|
@@ -7215,7 +7821,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7215
7821
|
scope.flush();
|
|
7216
7822
|
return batch.submit();
|
|
7217
7823
|
};
|
|
7218
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
7824
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$6}/setup`);
|
|
7219
7825
|
const setupPass = setup.pass("fill");
|
|
7220
7826
|
recordFill2(setupPass, dist, n, F32_INF_BITS);
|
|
7221
7827
|
recordFill2(setupPass, pred, predWords, INVALID_INDEX);
|
|
@@ -7261,7 +7867,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7261
7867
|
let roundsRecorded = 0;
|
|
7262
7868
|
let submits = 0;
|
|
7263
7869
|
for (; ; ) {
|
|
7264
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
7870
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$6}/rounds`);
|
|
7265
7871
|
const pass = batch.pass("sssp");
|
|
7266
7872
|
const near = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 0 });
|
|
7267
7873
|
const far = scope.params(FRONTIER_PARAMS, { ...relaxFields, role: 1 });
|
|
@@ -7284,7 +7890,7 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7284
7890
|
submits += 1;
|
|
7285
7891
|
ctx.assertReady();
|
|
7286
7892
|
if (options?.signal?.aborted) {
|
|
7287
|
-
throw aborted(ALGORITHM$
|
|
7893
|
+
throw aborted(ALGORITHM$6, submitted.id);
|
|
7288
7894
|
}
|
|
7289
7895
|
options?.onProgress?.(Math.min(roundsRecorded, maxRounds), maxRounds);
|
|
7290
7896
|
if (inspect !== null && tuning.onRound !== void 0) {
|
|
@@ -7309,26 +7915,26 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7309
7915
|
const needed = Math.max(wordOf(block, "nextFrontierCount"), wordOf(block, "nextFarCount"));
|
|
7310
7916
|
throw new WebGpuGraphError(
|
|
7311
7917
|
"E_TOO_LARGE",
|
|
7312
|
-
`${ALGORITHM$
|
|
7313
|
-
{ 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 }
|
|
7314
7920
|
);
|
|
7315
7921
|
}
|
|
7316
7922
|
throw new WebGpuGraphError(
|
|
7317
7923
|
"E_UNSUPPORTED",
|
|
7318
|
-
`${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`,
|
|
7319
7925
|
{ feature: "sssp.thresholdAbsorbed", hint: "the distances outgrew the delta's f32 precision" }
|
|
7320
7926
|
);
|
|
7321
7927
|
}
|
|
7322
7928
|
if (submits > maxSubmits) {
|
|
7323
7929
|
throw new WebGpuGraphError(
|
|
7324
7930
|
"E_VALIDATION",
|
|
7325
|
-
`${ALGORITHM$
|
|
7326
|
-
{ 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` }
|
|
7327
7933
|
);
|
|
7328
7934
|
}
|
|
7329
7935
|
}
|
|
7330
7936
|
const passed = await predecessorPass({
|
|
7331
|
-
algorithm: ALGORITHM$
|
|
7937
|
+
algorithm: ALGORITHM$6,
|
|
7332
7938
|
ctx,
|
|
7333
7939
|
scope,
|
|
7334
7940
|
predKernel,
|
|
@@ -7344,8 +7950,8 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7344
7950
|
if (passed.orphans !== 0) {
|
|
7345
7951
|
throw new WebGpuGraphError(
|
|
7346
7952
|
"E_VALIDATION",
|
|
7347
|
-
`${ALGORITHM$
|
|
7348
|
-
{ 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` }
|
|
7349
7955
|
);
|
|
7350
7956
|
}
|
|
7351
7957
|
const distOut = dest ?? new Float32Array(n);
|
|
@@ -7364,10 +7970,10 @@ async function ssspWithTuning(ctx, s, source, options, tuning) {
|
|
|
7364
7970
|
function sssp(ctx, s, source, options) {
|
|
7365
7971
|
return ssspWithTuning(ctx, s, source, options, {});
|
|
7366
7972
|
}
|
|
7367
|
-
const ALGORITHM$
|
|
7973
|
+
const ALGORITHM$5 = "bellmanFord";
|
|
7368
7974
|
const ROUNDS_PER_BATCH = 8;
|
|
7369
7975
|
const MAX_RETRIES = 16;
|
|
7370
|
-
const RING_SLOTS$
|
|
7976
|
+
const RING_SLOTS$4 = MAX_LEVELS_PER_SUBMIT + 16;
|
|
7371
7977
|
function assertSymmetric(s, vector2) {
|
|
7372
7978
|
const { arcToEdge, edgeToArc } = s;
|
|
7373
7979
|
for (let a = 0; a < s.arcCount; a++) {
|
|
@@ -7375,7 +7981,7 @@ function assertSymmetric(s, vector2) {
|
|
|
7375
7981
|
if (vector2[a] !== vector2[forward]) {
|
|
7376
7982
|
throw new WebGpuGraphError(
|
|
7377
7983
|
"E_UNSUPPORTED",
|
|
7378
|
-
`${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`,
|
|
7379
7985
|
{ feature: "bellmanFord.asymmetricUndirectedWeights", hint: "use a directed snapshot" }
|
|
7380
7986
|
);
|
|
7381
7987
|
}
|
|
@@ -7385,10 +7991,10 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7385
7991
|
ctx.assertReady();
|
|
7386
7992
|
await assertDeviceComputes(ctx);
|
|
7387
7993
|
const n = s.nodeCount;
|
|
7388
|
-
assertSource(ALGORITHM$
|
|
7994
|
+
assertSource(ALGORITHM$5, source, n);
|
|
7389
7995
|
const maxRetries = tuning.maxRetries ?? MAX_RETRIES;
|
|
7390
7996
|
if (!Number.isInteger(maxRetries) || maxRetries < 1) {
|
|
7391
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$
|
|
7997
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$5}: maxRetries must be an integer >= 1`, {
|
|
7392
7998
|
argument: "maxRetries",
|
|
7393
7999
|
value: maxRetries,
|
|
7394
8000
|
expected: "an integer >= 1"
|
|
@@ -7398,7 +8004,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7398
8004
|
if (!Number.isInteger(roundsPerBatch) || roundsPerBatch < 1 || roundsPerBatch > MAX_LEVELS_PER_SUBMIT) {
|
|
7399
8005
|
throw new WebGpuGraphError(
|
|
7400
8006
|
"E_INVALID_ARGUMENT",
|
|
7401
|
-
`${ALGORITHM$
|
|
8007
|
+
`${ALGORITHM$5}: roundsPerBatch must be an integer in [1, ${MAX_LEVELS_PER_SUBMIT}]`,
|
|
7402
8008
|
{
|
|
7403
8009
|
argument: "roundsPerBatch",
|
|
7404
8010
|
value: roundsPerBatch,
|
|
@@ -7406,18 +8012,18 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7406
8012
|
}
|
|
7407
8013
|
);
|
|
7408
8014
|
}
|
|
7409
|
-
const dest = checkDest(ALGORITHM$
|
|
7410
|
-
const vector2 = resolveWeights$1(ALGORITHM$
|
|
7411
|
-
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);
|
|
7412
8018
|
if (options?.signal?.aborted) {
|
|
7413
|
-
throw aborted(ALGORITHM$
|
|
8019
|
+
throw aborted(ALGORITHM$5);
|
|
7414
8020
|
}
|
|
7415
8021
|
if (vector2 === null || vector2.allOne) {
|
|
7416
8022
|
const unit = await unitWeightRoute(ctx, s, source, cutoff, dest, options);
|
|
7417
8023
|
return { result: { ...unit, hasNegativeCycle: false }, rounds: 0, retryExhaustedRounds: 0 };
|
|
7418
8024
|
}
|
|
7419
8025
|
if (!vector2.finite) {
|
|
7420
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$
|
|
8026
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$5}: a NaN or infinite weight has no shortest path`, {
|
|
7421
8027
|
feature: "bellmanFord.nonFiniteWeights"
|
|
7422
8028
|
});
|
|
7423
8029
|
}
|
|
@@ -7426,10 +8032,10 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7426
8032
|
}
|
|
7427
8033
|
const { arcCount } = s;
|
|
7428
8034
|
const core = ctx.residency.core(s, ["rowPtr", "colIdx", "weights", "edgeToArc"]);
|
|
7429
|
-
assertWholeCore(core, arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$
|
|
8035
|
+
assertWholeCore(core, arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$5);
|
|
7430
8036
|
const edges = ctx.residency.view(s, "edgeList");
|
|
7431
8037
|
const edgeCount = edges.scalars.edgeCount[0];
|
|
7432
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
8038
|
+
const scope = algorithmScope(ctx, ALGORITHM$5, RING_SLOTS$4);
|
|
7433
8039
|
try {
|
|
7434
8040
|
const wg = ctx.workgroupSize;
|
|
7435
8041
|
const bytes = 4 * n;
|
|
@@ -7464,7 +8070,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7464
8070
|
scope.flush();
|
|
7465
8071
|
return batch.submit();
|
|
7466
8072
|
};
|
|
7467
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
8073
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$5}/setup`);
|
|
7468
8074
|
const setupPass = setup.pass("fill");
|
|
7469
8075
|
recordFill2(setupPass, dist, n, F32_INF_BITS);
|
|
7470
8076
|
recordFill2(setupPass, pred, predWords, INVALID_INDEX);
|
|
@@ -7493,7 +8099,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7493
8099
|
const zero = new Uint32Array(BF_FLAGS.byteLength / 4);
|
|
7494
8100
|
const runRounds = async (count, label) => {
|
|
7495
8101
|
queue.writeBuffer(flags.buffer, flags.offset, zero);
|
|
7496
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
8102
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$5}/${label}`);
|
|
7497
8103
|
const pass = batch.pass("relax");
|
|
7498
8104
|
const params = scope.params(BF_PARAMS, relaxFields);
|
|
7499
8105
|
const bound = relax.bind({ ...relaxBindings, P: params.binding });
|
|
@@ -7518,9 +8124,9 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7518
8124
|
if (decision.retryExhausted !== 0) {
|
|
7519
8125
|
throw new WebGpuGraphError(
|
|
7520
8126
|
"E_VALIDATION",
|
|
7521
|
-
`${ALGORITHM$
|
|
8127
|
+
`${ALGORITHM$5}: a lane exhausted the ${maxRetries}-retry compare-exchange bound in the decision round, so its change is not a verdict`,
|
|
7522
8128
|
{
|
|
7523
|
-
label: `${ALGORITHM$
|
|
8129
|
+
label: `${ALGORITHM$5}/retry`,
|
|
7524
8130
|
message: "retryExhausted in the decision round",
|
|
7525
8131
|
batchId: decision.id
|
|
7526
8132
|
}
|
|
@@ -7536,7 +8142,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7536
8142
|
retryExhaustedRounds += 1;
|
|
7537
8143
|
}
|
|
7538
8144
|
if (options?.signal?.aborted) {
|
|
7539
|
-
throw aborted(ALGORITHM$
|
|
8145
|
+
throw aborted(ALGORITHM$5, batch.id);
|
|
7540
8146
|
}
|
|
7541
8147
|
options?.onProgress?.(rounds, n);
|
|
7542
8148
|
if (batch.changed === 0 && batch.retryExhausted === 0) {
|
|
@@ -7544,7 +8150,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7544
8150
|
}
|
|
7545
8151
|
}
|
|
7546
8152
|
const passed = await predecessorPass({
|
|
7547
|
-
algorithm: ALGORITHM$
|
|
8153
|
+
algorithm: ALGORITHM$5,
|
|
7548
8154
|
ctx,
|
|
7549
8155
|
scope,
|
|
7550
8156
|
predKernel,
|
|
@@ -7560,7 +8166,7 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7560
8166
|
if (passed.orphans !== 0 && !hasNegativeCycle) {
|
|
7561
8167
|
throw new WebGpuGraphError(
|
|
7562
8168
|
"E_UNSUPPORTED",
|
|
7563
|
-
`${ALGORITHM$
|
|
8169
|
+
`${ALGORITHM$5}: ${passed.orphans} reached node(s) the tight subgraph never reaches (a cycle of weights below one f32 ulp relaxed once)`,
|
|
7564
8170
|
{
|
|
7565
8171
|
feature: "bellmanFord.roundedCycle",
|
|
7566
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"
|
|
@@ -7587,17 +8193,17 @@ async function bellmanFordWithTuning(ctx, s, source, options, tuning) {
|
|
|
7587
8193
|
async function bellmanFord(ctx, s, source, options) {
|
|
7588
8194
|
return (await bellmanFordWithTuning(ctx, s, source, options, {})).result;
|
|
7589
8195
|
}
|
|
7590
|
-
const ALGORITHM$
|
|
8196
|
+
const ALGORITHM$4 = "betweennessCentrality";
|
|
7591
8197
|
const BYTES_PER_NODE_SOURCE = 16;
|
|
7592
8198
|
const SAMPLE_SEED = 2654435769;
|
|
7593
|
-
const RING_SLOTS$
|
|
8199
|
+
const RING_SLOTS$3 = BC_BACKWARD_LEVELS_PER_SUBMIT + 16;
|
|
7594
8200
|
function planBatchSize(n, remaining, limits) {
|
|
7595
8201
|
const needed = 4 * (n + 2);
|
|
7596
8202
|
if (needed > limits.maxStorageBufferBindingSize) {
|
|
7597
8203
|
throw new WebGpuGraphError(
|
|
7598
8204
|
"E_TOO_LARGE",
|
|
7599
|
-
`${ALGORITHM$
|
|
7600
|
-
{ needed, limit: limits.maxStorageBufferBindingSize, path: "binding", algorithm: ALGORITHM$
|
|
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 }
|
|
7601
8207
|
);
|
|
7602
8208
|
}
|
|
7603
8209
|
const kByBinding = Math.floor(limits.maxStorageBufferBindingSize / (4 * n));
|
|
@@ -7618,7 +8224,7 @@ function drawSources(n, k) {
|
|
|
7618
8224
|
return pool.slice(0, k);
|
|
7619
8225
|
}
|
|
7620
8226
|
function badArgument(argument, value, expected) {
|
|
7621
|
-
return new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$
|
|
8227
|
+
return new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$4}: ${argument} must be ${expected}`, {
|
|
7622
8228
|
argument,
|
|
7623
8229
|
value,
|
|
7624
8230
|
expected
|
|
@@ -7670,7 +8276,7 @@ async function submit(state, batch, signal) {
|
|
|
7670
8276
|
const back = await submitted.readback;
|
|
7671
8277
|
state.ctx.assertReady();
|
|
7672
8278
|
if (signal?.aborted) {
|
|
7673
|
-
throw aborted(ALGORITHM$
|
|
8279
|
+
throw aborted(ALGORITHM$4, submitted.id);
|
|
7674
8280
|
}
|
|
7675
8281
|
return back;
|
|
7676
8282
|
}
|
|
@@ -7687,7 +8293,7 @@ async function runBatch(state, sources, form, levelsPerSubmit, tuning, signal) {
|
|
|
7687
8293
|
let overflow = false;
|
|
7688
8294
|
let endsWords = null;
|
|
7689
8295
|
for (let first = true; endsWords === null; first = false) {
|
|
7690
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
8296
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$4}/forward`);
|
|
7691
8297
|
const pass = batch.pass("forward");
|
|
7692
8298
|
if (first) {
|
|
7693
8299
|
recordFill(state, pass, depthK, words, 4294967295);
|
|
@@ -7729,8 +8335,8 @@ async function runBatch(state, sources, form, levelsPerSubmit, tuning, signal) {
|
|
|
7729
8335
|
overflow = words32[W.sigmaOverflow] !== 0;
|
|
7730
8336
|
endsWords = new Uint32Array(back, endsRequest.offset, endsCount).slice(0, levels + 1);
|
|
7731
8337
|
} else if (recorded > n + 2) {
|
|
7732
|
-
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$
|
|
7733
|
-
label: ALGORITHM$
|
|
8338
|
+
throw new WebGpuGraphError("E_VALIDATION", `${ALGORITHM$4}: the done flag never rose in ${recorded} levels`, {
|
|
8339
|
+
label: ALGORITHM$4,
|
|
7734
8340
|
message: `the done flag never rose in ${recorded} levels`
|
|
7735
8341
|
});
|
|
7736
8342
|
}
|
|
@@ -7743,7 +8349,7 @@ async function runBatch(state, sources, form, levelsPerSubmit, tuning, signal) {
|
|
|
7743
8349
|
for (let i = 0; ; i += BC_BACKWARD_LEVELS_PER_SUBMIT) {
|
|
7744
8350
|
const chunk = backwardLevels.slice(i, i + BC_BACKWARD_LEVELS_PER_SUBMIT);
|
|
7745
8351
|
const last = i + BC_BACKWARD_LEVELS_PER_SUBMIT >= backwardLevels.length;
|
|
7746
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM$
|
|
8352
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$4}/backward`);
|
|
7747
8353
|
const pass = batch.pass("backward");
|
|
7748
8354
|
for (const level of chunk) {
|
|
7749
8355
|
const start = endsWords[level];
|
|
@@ -7807,11 +8413,11 @@ async function runRaw(ctx, s, sources, withEdges, tuning, options) {
|
|
|
7807
8413
|
const limits = tuning.limits ?? ctx.caps.limits;
|
|
7808
8414
|
const kMax = planBatchSize(n, sources.length, limits);
|
|
7809
8415
|
const core = ctx.residency.core(s);
|
|
7810
|
-
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$
|
|
8416
|
+
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM$4);
|
|
7811
8417
|
const { edgeCount } = s;
|
|
7812
8418
|
const mayRunEdge = pinned === "edge" || pinned === "auto" && sources.length > kMax;
|
|
7813
8419
|
const edgeView = mayRunEdge && edgeCount > 0 ? ctx.residency.view(s, "edgeList") : null;
|
|
7814
|
-
const scope = algorithmScope(ctx, ALGORITHM$
|
|
8420
|
+
const scope = algorithmScope(ctx, ALGORITHM$4, RING_SLOTS$3);
|
|
7815
8421
|
try {
|
|
7816
8422
|
const arrayBytes = 4 * n * kMax;
|
|
7817
8423
|
const lease = (bytes, label) => bindingOf(scope.scratch(bytes, label), bytes);
|
|
@@ -7851,7 +8457,7 @@ async function runRaw(ctx, s, sources, withEdges, tuning, options) {
|
|
|
7851
8457
|
bound: /* @__PURE__ */ new Map()
|
|
7852
8458
|
};
|
|
7853
8459
|
await ctx.allocator.check();
|
|
7854
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM$
|
|
8460
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$4}/setup`);
|
|
7855
8461
|
const setupPass = setup.pass("setup");
|
|
7856
8462
|
recordFill(state, setupPass, state.bc, n, 0);
|
|
7857
8463
|
if (state.arcScores !== null) {
|
|
@@ -7879,7 +8485,7 @@ async function runRaw(ctx, s, sources, withEdges, tuning, options) {
|
|
|
7879
8485
|
batches += 1;
|
|
7880
8486
|
options?.onProgress?.(start, sources.length);
|
|
7881
8487
|
}
|
|
7882
|
-
const result = new CommandBatch(ctx, `${ALGORITHM$
|
|
8488
|
+
const result = new CommandBatch(ctx, `${ALGORITHM$4}/result`);
|
|
7883
8489
|
const vertexRequest = result.readback(state.bc.buffer, state.bc.offset, 4 * n);
|
|
7884
8490
|
const arcRequest = state.arcScores === null ? null : result.readback(state.arcScores.buffer, state.arcScores.offset, 4 * s.arcCount);
|
|
7885
8491
|
const back = await submit(state, result, options?.signal);
|
|
@@ -7903,7 +8509,7 @@ async function precheck(ctx, options) {
|
|
|
7903
8509
|
ctx.assertReady();
|
|
7904
8510
|
await assertDeviceComputes(ctx);
|
|
7905
8511
|
if (options?.endpoints === true) {
|
|
7906
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$
|
|
8512
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$4}: endpoints: true is not supported`, {
|
|
7907
8513
|
feature: "betweenness.endpoints",
|
|
7908
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"
|
|
7909
8515
|
});
|
|
@@ -7912,10 +8518,10 @@ async function precheck(ctx, options) {
|
|
|
7912
8518
|
async function betweennessWithTuning(ctx, s, options, tuning) {
|
|
7913
8519
|
await precheck(ctx, options);
|
|
7914
8520
|
const n = s.nodeCount;
|
|
7915
|
-
const scores = checkDest(ALGORITHM$
|
|
8521
|
+
const scores = checkDest$2(ALGORITHM$4, options?.dest, n) ?? new Float32Array(n);
|
|
7916
8522
|
const sources = resolveSources(options, n);
|
|
7917
8523
|
if (options?.signal?.aborted) {
|
|
7918
|
-
throw aborted(ALGORITHM$
|
|
8524
|
+
throw aborted(ALGORITHM$4);
|
|
7919
8525
|
}
|
|
7920
8526
|
if (n === 0 || sources.length === 0) {
|
|
7921
8527
|
scores.fill(0);
|
|
@@ -7938,10 +8544,10 @@ async function betweennessWithTuning(ctx, s, options, tuning) {
|
|
|
7938
8544
|
async function edgeBetweennessWithTuning(ctx, s, options, tuning, onArcs) {
|
|
7939
8545
|
await precheck(ctx, options);
|
|
7940
8546
|
const n = s.nodeCount;
|
|
7941
|
-
const scores = checkDest("edgeBetweennessCentrality", options?.dest, s.edgeCount) ?? new Float32Array(s.edgeCount);
|
|
8547
|
+
const scores = checkDest$2("edgeBetweennessCentrality", options?.dest, s.edgeCount) ?? new Float32Array(s.edgeCount);
|
|
7942
8548
|
const sources = resolveSources(options, n);
|
|
7943
8549
|
if (options?.signal?.aborted) {
|
|
7944
|
-
throw aborted(ALGORITHM$
|
|
8550
|
+
throw aborted(ALGORITHM$4);
|
|
7945
8551
|
}
|
|
7946
8552
|
if (n === 0 || sources.length === 0 || s.arcCount === 0) {
|
|
7947
8553
|
scores.fill(0);
|
|
@@ -7962,10 +8568,10 @@ function betweennessCentrality(ctx, s, options) {
|
|
|
7962
8568
|
function edgeBetweennessCentrality(ctx, s, options) {
|
|
7963
8569
|
return edgeBetweennessWithTuning(ctx, s, options, {});
|
|
7964
8570
|
}
|
|
7965
|
-
const ALGORITHM = "closenessCentrality";
|
|
8571
|
+
const ALGORITHM$3 = "closenessCentrality";
|
|
7966
8572
|
const SOURCES_PER_BATCH = 32;
|
|
7967
8573
|
const PER_SOURCE_WORDS = 4 * SOURCES_PER_BATCH;
|
|
7968
|
-
const RING_SLOTS = 8 * MAX_LEVELS_PER_SUBMIT + 16;
|
|
8574
|
+
const RING_SLOTS$2 = 8 * MAX_LEVELS_PER_SUBMIT + 16;
|
|
7969
8575
|
function reusingScratch(scope) {
|
|
7970
8576
|
const held = /* @__PURE__ */ new Map();
|
|
7971
8577
|
return {
|
|
@@ -7987,7 +8593,7 @@ async function weightedRoute(ctx, s, scores, sources, options) {
|
|
|
7987
8593
|
const totals = sources === null ? null : new Float64Array(n);
|
|
7988
8594
|
for (let i = 0; i < count; i++) {
|
|
7989
8595
|
if (options?.signal?.aborted) {
|
|
7990
|
-
throw aborted(ALGORITHM);
|
|
8596
|
+
throw aborted(ALGORITHM$3);
|
|
7991
8597
|
}
|
|
7992
8598
|
const source = sources === null ? i : sources[i];
|
|
7993
8599
|
const { dist } = await sssp(ctx, s, source, { signal: options?.signal });
|
|
@@ -8021,8 +8627,8 @@ async function sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tun
|
|
|
8021
8627
|
return { scores, iterations: 0, converged: true, precision: "f32", sourcesUsed: 0 };
|
|
8022
8628
|
}
|
|
8023
8629
|
const core = ctx.residency.core(s);
|
|
8024
|
-
assertWholeCore(core, s.arcCount, ctx.caps.limits.maxStorageBufferBindingSize, ALGORITHM);
|
|
8025
|
-
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);
|
|
8026
8632
|
try {
|
|
8027
8633
|
const wg = ctx.workgroupSize;
|
|
8028
8634
|
const bytes = 4 * n;
|
|
@@ -8071,7 +8677,7 @@ async function sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tun
|
|
|
8071
8677
|
scope.flush();
|
|
8072
8678
|
return batch.submit();
|
|
8073
8679
|
};
|
|
8074
|
-
const setup = new CommandBatch(ctx, `${ALGORITHM}/setup`);
|
|
8680
|
+
const setup = new CommandBatch(ctx, `${ALGORITHM$3}/setup`);
|
|
8075
8681
|
recordFill2(setup.pass("fill"), iota, n, 1);
|
|
8076
8682
|
setup.endPass();
|
|
8077
8683
|
await submit2(setup).readback;
|
|
@@ -8080,7 +8686,7 @@ async function sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tun
|
|
|
8080
8686
|
for (let batchStart = 0; batchStart < seedCount; batchStart += SOURCES_PER_BATCH) {
|
|
8081
8687
|
let level = 0;
|
|
8082
8688
|
for (let first = true; ; first = false) {
|
|
8083
|
-
const batch = new CommandBatch(ctx, `${ALGORITHM}/levels`);
|
|
8689
|
+
const batch = new CommandBatch(ctx, `${ALGORITHM$3}/levels`);
|
|
8084
8690
|
const pass = batch.pass("closeness");
|
|
8085
8691
|
if (first) {
|
|
8086
8692
|
recordFill2(pass, bits, 4 * bitsBase, 0);
|
|
@@ -8139,7 +8745,7 @@ async function sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tun
|
|
|
8139
8745
|
const back = await submitted.readback;
|
|
8140
8746
|
ctx.assertReady();
|
|
8141
8747
|
if (options?.signal?.aborted) {
|
|
8142
|
-
throw aborted(ALGORITHM, submitted.id);
|
|
8748
|
+
throw aborted(ALGORITHM$3, submitted.id);
|
|
8143
8749
|
}
|
|
8144
8750
|
if (new Uint32Array(back, doneRequest.offset, 1)[0] !== 0) {
|
|
8145
8751
|
const block = new Uint32Array(back, blockRequest.offset, PER_SOURCE_WORDS);
|
|
@@ -8161,8 +8767,8 @@ async function sweepRoute(ctx, s, scores, sources, levelsPerSubmit, options, tun
|
|
|
8161
8767
|
if (level > n + 3) {
|
|
8162
8768
|
throw new WebGpuGraphError(
|
|
8163
8769
|
"E_VALIDATION",
|
|
8164
|
-
`${ALGORITHM}: the done flag never rose in ${level} levels of the batch at ${batchStart}`,
|
|
8165
|
-
{ 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` }
|
|
8166
8772
|
);
|
|
8167
8773
|
}
|
|
8168
8774
|
}
|
|
@@ -8182,14 +8788,14 @@ function checkSources(s, sources) {
|
|
|
8182
8788
|
return null;
|
|
8183
8789
|
}
|
|
8184
8790
|
if (s.directed) {
|
|
8185
|
-
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM}: sampled sources need an undirected snapshot`, {
|
|
8791
|
+
throw new WebGpuGraphError("E_UNSUPPORTED", `${ALGORITHM$3}: sampled sources need an undirected snapshot`, {
|
|
8186
8792
|
feature: "closenessCentrality.directedSources",
|
|
8187
8793
|
hint: "run the CPU port, which searches the in-arcs"
|
|
8188
8794
|
});
|
|
8189
8795
|
}
|
|
8190
8796
|
for (const v of sources) {
|
|
8191
8797
|
if (!Number.isInteger(v) || v < 0 || v >= s.nodeCount) {
|
|
8192
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM}: a source is not a node index`, {
|
|
8798
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `${ALGORITHM$3}: a source is not a node index`, {
|
|
8193
8799
|
argument: "sources",
|
|
8194
8800
|
value: v,
|
|
8195
8801
|
expected: `an integer in [0, ${s.nodeCount})`
|
|
@@ -8203,7 +8809,7 @@ async function closenessWithTuning(ctx, s, options, tuning) {
|
|
|
8203
8809
|
await assertDeviceComputes(ctx);
|
|
8204
8810
|
for (const key of ["maxIterations", "tolerance"]) {
|
|
8205
8811
|
if (options?.[key] !== void 0) {
|
|
8206
|
-
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`, {
|
|
8207
8813
|
option: key,
|
|
8208
8814
|
hint: "closeness is an exact traversal; the option has no meaning here"
|
|
8209
8815
|
});
|
|
@@ -8215,7 +8821,7 @@ async function closenessWithTuning(ctx, s, options, tuning) {
|
|
|
8215
8821
|
if (!Number.isInteger(levelsPerSubmit) || levelsPerSubmit < 1 || levelsPerSubmit > MAX_LEVELS_PER_SUBMIT) {
|
|
8216
8822
|
throw new WebGpuGraphError(
|
|
8217
8823
|
"E_INVALID_ARGUMENT",
|
|
8218
|
-
`${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}]`,
|
|
8219
8825
|
{
|
|
8220
8826
|
argument: "levelsPerSubmit",
|
|
8221
8827
|
value: levelsPerSubmit,
|
|
@@ -8223,16 +8829,16 @@ async function closenessWithTuning(ctx, s, options, tuning) {
|
|
|
8223
8829
|
}
|
|
8224
8830
|
);
|
|
8225
8831
|
}
|
|
8226
|
-
const scores = checkDest(ALGORITHM, options?.dest, n) ?? new Float32Array(n);
|
|
8832
|
+
const scores = checkDest$2(ALGORITHM$3, options?.dest, n) ?? new Float32Array(n);
|
|
8227
8833
|
const weighted = options?.weighted ?? s.flags.weighted;
|
|
8228
8834
|
if (options?.signal?.aborted) {
|
|
8229
|
-
throw aborted(ALGORITHM);
|
|
8835
|
+
throw aborted(ALGORITHM$3);
|
|
8230
8836
|
}
|
|
8231
8837
|
if (weighted && s.weights !== null && !s.flags.allWeightsOne) {
|
|
8232
8838
|
if (!s.flags.nonNegativeWeights) {
|
|
8233
8839
|
throw new WebGpuGraphError(
|
|
8234
8840
|
"E_UNSUPPORTED",
|
|
8235
|
-
`${ALGORITHM}: a negative weight has no shortest-path distance to sum`,
|
|
8841
|
+
`${ALGORITHM$3}: a negative weight has no shortest-path distance to sum`,
|
|
8236
8842
|
{
|
|
8237
8843
|
feature: "closenessCentrality.negativeWeights",
|
|
8238
8844
|
hint: "pass weighted: false to ignore the column"
|
|
@@ -8240,7 +8846,7 @@ async function closenessWithTuning(ctx, s, options, tuning) {
|
|
|
8240
8846
|
);
|
|
8241
8847
|
}
|
|
8242
8848
|
if (!s.flags.finiteWeights) {
|
|
8243
|
-
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`, {
|
|
8244
8850
|
feature: "closenessCentrality.nonFiniteWeights"
|
|
8245
8851
|
});
|
|
8246
8852
|
}
|
|
@@ -8251,32 +8857,269 @@ async function closenessWithTuning(ctx, s, options, tuning) {
|
|
|
8251
8857
|
function closenessCentrality(ctx, s, options) {
|
|
8252
8858
|
return closenessWithTuning(ctx, s, options, {});
|
|
8253
8859
|
}
|
|
8254
|
-
const
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
const
|
|
8260
|
-
const
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
return new CountingSortPlannerImpl(scope, histogram, scan, scatter);
|
|
8268
|
-
}
|
|
8269
|
-
function checkWords(name, binding, words) {
|
|
8270
|
-
if (binding.size < 4 * words) {
|
|
8271
|
-
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `histogram: ${name} is smaller than 4 x ${words} bytes`, {
|
|
8272
|
-
argument: name,
|
|
8273
|
-
value: binding.size,
|
|
8274
|
-
expected: 4 * words
|
|
8275
|
-
});
|
|
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;
|
|
8276
8873
|
}
|
|
8874
|
+
return { maxNodes, limit, limitName };
|
|
8277
8875
|
}
|
|
8278
|
-
function
|
|
8279
|
-
|
|
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"));
|
|
9104
|
+
return new HistogramPlannerImpl(scope, histogram, fill);
|
|
9105
|
+
}
|
|
9106
|
+
async function prepareCountingSort(scope) {
|
|
9107
|
+
const histogram = await prepareHistogramImpl(scope);
|
|
9108
|
+
const scan = await prepareScan(scope);
|
|
9109
|
+
const scatter = await scope.pipelines.kernel(kernelSpec("counting-scatter"));
|
|
9110
|
+
return new CountingSortPlannerImpl(scope, histogram, scan, scatter);
|
|
9111
|
+
}
|
|
9112
|
+
function checkWords$1(name, binding, words) {
|
|
9113
|
+
if (binding.size < 4 * words) {
|
|
9114
|
+
throw new WebGpuGraphError("E_INVALID_ARGUMENT", `histogram: ${name} is smaller than 4 x ${words} bytes`, {
|
|
9115
|
+
argument: name,
|
|
9116
|
+
value: binding.size,
|
|
9117
|
+
expected: 4 * words
|
|
9118
|
+
});
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
9121
|
+
function checkHistogramArguments(keys, count, bins, hist) {
|
|
9122
|
+
if (!Number.isSafeInteger(count) || count < 0 || count > U32_MAX) {
|
|
8280
9123
|
throw new WebGpuGraphError("E_INVALID_ARGUMENT", "histogram: count must be a non-negative integer below 2^32", {
|
|
8281
9124
|
argument: "count",
|
|
8282
9125
|
value: count
|
|
@@ -8288,8 +9131,8 @@ function checkHistogramArguments(keys, count, bins, hist) {
|
|
|
8288
9131
|
value: bins
|
|
8289
9132
|
});
|
|
8290
9133
|
}
|
|
8291
|
-
checkWords("keys", keys, count);
|
|
8292
|
-
checkWords("hist", hist, bins);
|
|
9134
|
+
checkWords$1("keys", keys, count);
|
|
9135
|
+
checkWords$1("hist", hist, bins);
|
|
8293
9136
|
}
|
|
8294
9137
|
class HistogramPlannerImpl {
|
|
8295
9138
|
/**
|
|
@@ -8377,9 +9220,9 @@ class CountingSortPlannerImpl {
|
|
|
8377
9220
|
*/
|
|
8378
9221
|
record(pass, keys, count, bins, scratch, outIndex, outStart) {
|
|
8379
9222
|
checkHistogramArguments(keys, count, bins, scratch.hist);
|
|
8380
|
-
checkWords("cursor", scratch.cursor, bins);
|
|
8381
|
-
checkWords("outIndex", outIndex, count);
|
|
8382
|
-
checkWords("outStart", outStart, bins);
|
|
9223
|
+
checkWords$1("cursor", scratch.cursor, bins);
|
|
9224
|
+
checkWords$1("outIndex", outIndex, count);
|
|
9225
|
+
checkWords$1("outStart", outStart, bins);
|
|
8383
9226
|
this.histogram.record(pass, keys, count, bins, scratch.hist);
|
|
8384
9227
|
this.scan.record(pass, scratch.hist, bins, outStart);
|
|
8385
9228
|
this.histogram.recordZero(pass, scratch.cursor, bins);
|
|
@@ -8393,6 +9236,751 @@ class CountingSortPlannerImpl {
|
|
|
8393
9236
|
this.dispatches += 1;
|
|
8394
9237
|
}
|
|
8395
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;
|
|
9668
|
+
}
|
|
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];
|
|
9673
|
+
}
|
|
9674
|
+
return bound;
|
|
9675
|
+
}
|
|
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
|
+
}
|
|
9685
|
+
}
|
|
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;
|
|
9693
|
+
}
|
|
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);
|
|
9723
|
+
}
|
|
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();
|
|
9822
|
+
}
|
|
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
|
+
}
|
|
9857
|
+
}
|
|
9858
|
+
return { perNode, total: sum / 3, coefficient, transitivity: triples === 0 ? 0 : sum / triples };
|
|
9859
|
+
}
|
|
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`, {});
|
|
9870
|
+
}
|
|
9871
|
+
if (n === 0) {
|
|
9872
|
+
options?.onProgress?.(1, 1);
|
|
9873
|
+
return epilogue(dest ?? new Uint32Array(0), new Uint32Array(1));
|
|
9874
|
+
}
|
|
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
|
+
);
|
|
9952
|
+
}
|
|
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();
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
8396
9984
|
function nextPow2(x) {
|
|
8397
9985
|
let p = 1;
|
|
8398
9986
|
while (p < x) {
|
|
@@ -13451,6 +15039,53 @@ function createAccelerator(ctx, options) {
|
|
|
13451
15039
|
ctx.assertReady();
|
|
13452
15040
|
return await closenessCentrality(ctx, gs, o);
|
|
13453
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
|
+
},
|
|
13454
15089
|
/**
|
|
13455
15090
|
* Destroys every device buffer recorded for the snapshot (spec 4.5); delegates to ctx.release.
|
|
13456
15091
|
* @param s - the snapshot the app is done with
|
|
@@ -13560,7 +15195,7 @@ async function calibrateLayout(ctx, options) {
|
|
|
13560
15195
|
};
|
|
13561
15196
|
}
|
|
13562
15197
|
export {
|
|
13563
|
-
|
|
15198
|
+
a0 as ARC_WINDOW_ALIGN,
|
|
13564
15199
|
EXACT_MAX_NODES,
|
|
13565
15200
|
FA2_DEFAULTS,
|
|
13566
15201
|
FR_DEFAULTS,
|
|
@@ -13568,11 +15203,12 @@ export {
|
|
|
13568
15203
|
LAYOUT_TUNING_DEFAULTS,
|
|
13569
15204
|
MAX_1D_ITEMS,
|
|
13570
15205
|
MAX_WORKGROUPS_PER_DIM,
|
|
13571
|
-
|
|
15206
|
+
a1 as PASSTHROUGH_FORMAT_CODES,
|
|
13572
15207
|
SE_DEFAULTS,
|
|
13573
|
-
|
|
13574
|
-
|
|
15208
|
+
a2 as STORAGE_ALIGN,
|
|
15209
|
+
a3 as WORKGROUP_SIZE,
|
|
13575
15210
|
WebGpuGraphError,
|
|
15211
|
+
allPairsShortestPath,
|
|
13576
15212
|
bellmanFord,
|
|
13577
15213
|
betweennessCentrality,
|
|
13578
15214
|
breadthFirstSearch,
|
|
@@ -13588,13 +15224,15 @@ export {
|
|
|
13588
15224
|
eigenvectorCentrality,
|
|
13589
15225
|
hasErrorCode,
|
|
13590
15226
|
hits,
|
|
13591
|
-
|
|
15227
|
+
a4 as isSoftwareAdapter,
|
|
13592
15228
|
isWebGpuGraphError,
|
|
13593
15229
|
katzCentrality,
|
|
15230
|
+
labelPropagation,
|
|
13594
15231
|
pageRank,
|
|
13595
15232
|
personalizedPageRank,
|
|
13596
15233
|
seedPositions,
|
|
13597
15234
|
sssp,
|
|
15235
|
+
triangleCount,
|
|
13598
15236
|
verifyDevice
|
|
13599
15237
|
};
|
|
13600
15238
|
//# sourceMappingURL=webgpu-graph-algorithms.js.map
|