Database::csr() returns the last committed state's relationships in compressed sparse row
form (graphbuff::Csr), the layout GPU kernels and graph algorithms work on:
nodes get dense u32 indexes in id order, and each relationship type has an outgoing and
an incoming CsrAdjacency (offsets, targets, rels), rows sorted by target.
let csr = db.csr()?;
let follows = csr.type_id("FOLLOWS").unwrap();
let out = csr.adjacency(follows, graphbuff::Direction::Outgoing);
let i = csr.node_index(alice).unwrap();
for &j in out.neighbors(i) {
println!("{}", csr.node_id(j).0);
}
The export is built on the first call after a commit, on a snapshot (writers keep
committing), and shared until the next commit. 100,000 nodes and 1.3 million
relationships: 0.26 s in memory, 0.6 s on disk, 32 MiB (cargo run --release -p graphbuff --example csrbench). Csr::build exports any GraphStore.
Kernels
graphbuff::Kernels is the interface an execution backend implements over the export;
CpuKernels is the CPU implementation (threads for independent work) and the fallback
when there is no GPU. They work on dense node indexes and flat arrays:
expand: a batch of source rows to their relationships (ExpandBatch: input row, target, relationship), inCsr::expandorder.propagate: one hop of walk counts per node (a whole-graph multi-hopcount(*); walks may reuse a relationship, Cypher paths may not).bfs: hop distances up to a depth.filter: a numeric propertyColumn(Column::build(store, &csr, "age")) compared with a constant, three-valued like Cypher (Mask, withand/or/not;label_maskfor labels);selectkeeps the rows that pass.aggregate:count(*),count,sum,avg,min,maxover rows, equal to the engine's results;aggregate_countsover walk counts.top_n:ORDER BY … LIMIT n.
let k = graphbuff::CpuKernels::new();
let age = db.read(|s| graphbuff::Column::build(s, &csr, "age"))?;
let all: Vec<u32> = (0..csr.node_count() as u32).collect();
let adults = k.select(&all, &k.filter(&age, graphbuff::CmpOp::Ge, &18.into()));
let stats = k.aggregate(&age, &adults); // stats.avg, stats.max, ...
On the csrbench graph (CPU, 100,000 nodes): a two-hop sum 6 ms (Cypher 15.6 s), a
filtered aggregate 1.5 ms (69 ms), ORDER BY … LIMIT 10 3 ms (68 ms).
Graph algorithms
Procedures run graph algorithms as kernels over the export (cached per committed state; in
a transaction, built from what it sees). Each takes an optional configuration map with
relationshipTypes (a type or a list) and, where it applies, orientation
('NATURAL', 'REVERSE', 'UNDIRECTED'):
| Procedure | Yields | Settings |
|---|---|---|
algo.pageRank(config) |
node, score |
maxIterations (20), dampingFactor (0.85), tolerance (1e-7); Neo4j GDS formula |
algo.wcc(config) |
node, componentId |
weakly connected components; the id of the smallest node in each |
algo.triangleCount(config) |
node, triangleCount |
as an undirected simple graph |
algo.bfs(sourceNode, config) |
node, depth |
from a node or a list; maxDepth (-1 = none); nearest first |
CALL algo.pageRank({relationshipTypes: 'FOLLOWS'}) YIELD node, score
RETURN node.name, score ORDER BY score DESC LIMIT 10
On the csrbench graph (100,000 nodes, 1.3 million relationships, in memory): PageRank
104 ms, components 52 ms, triangles 184 ms.
Kernels in query plans
Read queries use the kernels by themselves when the cost model expects them to be cheaper
(counting what the export and columns would cost to build if they are not cached yet).
EXPLAIN shows the choice:
Select
KernelAggregate count(*), sum(c.id) over (a)-[:FOLLOWS]->()-[:FOLLOWS]->(c) (cpu; cost … vs … by rows)
Start
Two shapes are recognized, each as the whole MATCH … RETURN:
- aggregates (
count(*),count,sum,avg,min,maxof the first or last node) over a pattern of up to two single relationships, with labels, constant property maps, andWHEREtests of one node at a time (comparisons with constants or parameters,IS [NOT] NULL, labels,AND/OR/NOT); ORDER BY n.prop [DESC] [SKIP s] LIMIT lover one node pattern.
Results are identical to the row engine: the row plan stays in the plan as a fallback and
runs whenever the kernels cannot be exact (a property that is not a number; sum, avg,
min, max over non-integers or sums near 2^53, whose results depend on the order of
rows). Queries that write and statements in a Transaction always run on rows.
Database::set_kernel_mode(KernelMode::Off | Auto | Always) switches it;
set_kernels plugs in another backend; kernel_stats() counts runs and fallbacks.
On the csrbench graph, Cypher's two-hop sum drops from 14 s to 8 ms, the filtered
aggregate from 63 to 2.4 ms and ORDER BY … LIMIT 10 from 68 to 3.3 ms (in memory).
GPU kernels
The gpu feature adds WgpuKernels (crate graphbuff-gpu), a backend on
wgpu: Vulkan, Direct3D 12 or Metal, whichever the machine has.
cargo run --release -p graphbuff-cli --features gpu -- --gpu
--gpu (CLI and graphbuff-server) puts every database on the GPU backend; in code,
DatabaseManager::set_kernels(graphbuff::gpu_kernels()?) or Database::set_kernels.
EXPLAIN then shows (wgpu; …). Walk counts (multi-hop aggregates, walk / propagate),
BFS, connected components, triangle counts and, on devices with 64-bit floats, PageRank
run on the GPU, with the CPU's results exactly (PageRank bit for bit). Filters, aggregates,
Top-N and expansions stay on the CPU, as do kernels reading fewer than 262,144
relationships (WgpuKernels::with_min_work) and any the device cannot hold; after a device
error the backend runs everything on the CPU. On a Radeon RX 5700 XT against 4 CPU threads,
13 million relationships (cargo run --release -p graphbuff --features gpu --example gpubench):
three-hop walk counts 180 → 19 ms, BFS 510 → 21 ms, PageRank (20 iterations)
1,293 → 557 ms, Cypher's two-hop count(*) 154 → 31 ms.
On Windows with the GNU toolchain (x86_64-pc-windows-gnu), wgpu's Windows bindings need
an import-library tool and a linker that reads LLVM import libraries: put a copy of
llvm-ar.exe named dlltool.exe first on PATH (it is in rustup's llvm-tools
component), and link with the bundled rust-lld:
RUSTFLAGS="-C linker=rust-lld -C linker-flavor=ld.lld" cargo build --release -p graphbuff-cli --features gpu
The MSVC toolchain and other platforms need nothing extra.
Try it on your machine: one command starts the server with the movies graph. Needs Docker.
$ docker run --rm -p 127.0.0.1:7475:7475 -p 127.0.0.1:7687:7687 ghcr.io/janvencl/graphbuff --demo moviesThen follow Getting started.