vyre_primitives/graph/persistent_bfs/layout.rs
1use crate::graph::program_graph::BINDING_PRIMITIVE_START;
2
3/// Canonical op id.
4pub const OP_ID: &str = "vyre-primitives::graph::persistent_bfs";
5/// Canonical op id for batched persistent BFS over many seed frontiers.
6pub const BATCH_OP_ID: &str = "vyre-primitives::graph::persistent_bfs_batch";
7
8/// Canonical binding index for the input frontier bitset.
9pub const BINDING_FRONTIER_IN: u32 = BINDING_PRIMITIVE_START;
10/// Canonical binding index for the output frontier bitset.
11pub const BINDING_FRONTIER_OUT: u32 = BINDING_PRIMITIVE_START + 1;
12/// Canonical binding index for the global changed flag.
13pub const BINDING_CHANGED: u32 = BINDING_PRIMITIVE_START + 2;
14/// Canonical binding index for the converged flag.
15///
16/// `1` if the frontier reached a fixpoint (a step added nothing) before the
17/// `max_iters` budget was exhausted, `0` if the loop ran all `max_iters` steps
18/// while still growing (an under-approximated closure) or `max_iters == 0`.
19/// This is the device readback that lets a host caller reject a partial closure
20/// loudly instead of silently trusting a frontier the kernel never drove to a
21/// fixpoint. Mirrors `PersistentBfsConvergence::converged` (requires the `cpu-parity` feature).
22pub const BINDING_CONVERGED: u32 = BINDING_PRIMITIVE_START + 3;
23/// Canonical binding index for the optional per-iteration frontier-density array.
24///
25/// Present only in the density-instrumented program variants
26/// ([`super::program::persistent_bfs_with_density`] and
27/// [`super::program::try_persistent_bfs_batch_with_density`]). It is a
28/// `max_iters`-length (single) or `query_count * max_iters` (batch) u32 array
29/// where entry `i` holds the popcount of the frontier after traversal step `i`
30/// (per query for the batch variant). Because reachability growth is monotone, a
31/// host caller reconstructs every `FrontierDensityTelemetry` aggregate (active
32/// total, per-step delta, peak, last) from this array plus the seed popcount,
33/// with no per-iteration device readback loop. The base
34/// [`super::program::persistent_bfs`] programs omit this buffer entirely, so
35/// their ABI is unchanged.
36pub const BINDING_DENSITY_ACTIVE: u32 = BINDING_PRIMITIVE_START + 4;
37/// Canonical name for the per-iteration frontier-density array output buffer.
38pub const DENSITY_ACTIVE_BUFFER: &str = "density_active";
39/// Canonical workgroup size for persistent BFS programs.
40pub const PERSISTENT_BFS_WORKGROUP_SIZE: [u32; 3] = [256, 1, 1];
41/// One-block dispatch grid used by the compact single-workgroup BFS path.
42pub(crate) const PERSISTENT_BFS_SINGLE_DISPATCH_GRID: [u32; 3] = [1, 1, 1];
43
44/// Dispatch grid for a single persistent-BFS query.
45#[must_use]
46pub const fn persistent_bfs_single_dispatch_grid(node_count: u32) -> [u32; 3] {
47 [persistent_bfs_grid_x(node_count), 1, 1]
48}
49
50/// Dispatch grid for a batched persistent-BFS query set.
51#[must_use]
52pub const fn persistent_bfs_batch_dispatch_grid(node_count: u32, query_count: u32) -> [u32; 3] {
53 if query_count == 0 {
54 [1, 1, 1]
55 } else {
56 [persistent_bfs_grid_x(node_count), query_count, 1]
57 }
58}
59
60const fn persistent_bfs_grid_x(node_count: u32) -> u32 {
61 if node_count == 0 {
62 1
63 } else {
64 ((node_count - 1) / PERSISTENT_BFS_WORKGROUP_SIZE[0]) + 1
65 }
66}
67
68/// Validated persistent-BFS graph layout metadata.
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub struct PersistentBfsLayout {
71 /// Number of graph nodes accepted by the primitive.
72 pub node_count: u32,
73 /// Number of logical CSR edges.
74 pub edge_count: u32,
75 /// Number of u32 words in one frontier bitset.
76 pub words: usize,
77 /// Number of u32 words in one frontier bitset, narrowed for cache keys.
78 pub words_u32: u32,
79 /// Number of u32 words required by node-indexed scratch buffers.
80 pub node_words: usize,
81 /// Number of u32 words required by physical edge buffers after padding.
82 pub edge_storage_words: usize,
83}
84
85/// Validated flat-frontier batch metadata for persistent BFS.
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87pub struct PersistentBfsBatchLayout {
88 /// Number of queries in the batch, narrowed for GPU grid dimensions.
89 pub query_count: u32,
90 /// Total number of u32 words in the flat `[query][word]` frontier array.
91 pub total_words: usize,
92}
93
94/// Validated single-frontier metadata for resident persistent BFS.
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub struct PersistentBfsFrontierLayout {
97 /// Number of u32 words in the frontier bitset.
98 pub words: usize,
99 /// Number of u32 words in the frontier bitset, narrowed for primitive metadata.
100 pub words_u32: u32,
101}
102
103/// Primitive program-cache class for persistent-BFS dispatch plans.
104#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
105pub enum PersistentBfsPlanCacheKind {
106 /// One seed frontier for one graph.
107 Single,
108 /// Many seed frontiers batched over one graph.
109 Batch,
110}
111
112/// Primitive-owned persistent-BFS program cache key.
113///
114/// Dispatch wrappers add only backend feature bits; graph identity, frontier
115/// width, query count, masks, iteration budget, and plan class are owned here
116/// so every backend caches the same primitive program shapes.
117#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
118pub struct PersistentBfsPlanCacheKey {
119 /// Stable discriminator for the cached program layout.
120 ///
121 /// Content-addressed graph staging should use [`crate::graph::persistent_bfs::persistent_bfs_layout_hash`].
122 /// Program caches should prefer [`crate::graph::persistent_bfs::persistent_bfs_program_layout_hash`] so
123 /// same-shape CSR contents reuse the same compiled persistent-BFS program.
124 pub layout_hash: u64,
125 /// Number of graph nodes in the primitive program shape.
126 pub node_count: u32,
127 /// Number of logical graph edges in the primitive program shape.
128 pub edge_count: u32,
129 /// Number of frontier words per query.
130 pub words_per_query: u32,
131 /// Number of queries represented by the program.
132 pub query_count: u32,
133 /// Edge-kind allow mask compiled into the primitive program.
134 pub allow_mask: u32,
135 /// Iteration budget compiled into the primitive program.
136 pub max_iters: u32,
137 /// Backend/device feature key supplied by the dispatch wrapper.
138 pub device_features: u64,
139 /// Single-query or batched-query plan kind.
140 pub kind: PersistentBfsPlanCacheKind,
141}
142
143/// Primitive-owned identity for immutable non-resident persistent-BFS inputs.
144///
145/// Dynamic frontier input/output and changed buffers are intentionally omitted:
146/// dispatch wrappers refresh those every call. This key covers graph contents
147/// and shape that decide when static CSR/device inputs must be refreshed.
148#[derive(Clone, Copy, Debug, Eq, PartialEq)]
149pub struct PersistentBfsStaticInputKey {
150 /// Stable graph-content hash from [`crate::graph::persistent_bfs::persistent_bfs_layout_hash`].
151 pub layout_hash: u64,
152 /// Number of graph nodes.
153 pub node_count: u32,
154 /// Number of logical CSR edges.
155 pub edge_count: u32,
156 /// Number of frontier words.
157 pub words: u32,
158}