VORA is a scalable single-GPU engine for subgraph query processing on large, labeled graphs. It combines fused vector-based matching with workload decomposition, so it can evaluate graph-pattern queries whose data and intermediate results exceed GPU memory while still producing either count(*) or columnar match results for downstream query processing.
VORA supports all nine queries (Q1–Q9) in the Labelled Subgraph Query Benchmark (LSQB). Its design and evaluation are presented in Vora: A Vector-Based Engine for Scalable GPU-Accelerated Subgraph Query Processing, accepted for publication at the 2027 IEEE International Conference on Data Engineering (ICDE).
VORA decomposes a query into local tasks, transfers and merges graph shards, and evaluates local tasks using fused vector operators.
- VectorFlux execution. A lightweight CUDA vector library provides composable data-parallel operators and compile-time programmer-controllable operator fusion, reducing intermediate materialization and global-memory traffic.
- GPU graph primitives. Batched neighbor retrieval and batched edge existence checking implement the expand and expand-into operations used by vectorized matching.
- Compact typed graph layout. Edges are organized into relations by
(source-label, edge-label, destination-label)and stored in forward and reverse Compressed Unique Source (CUS) form. - Out-of-core single-GPU execution. Fine-grained physical shards are merged into query-dependent logical shards at runtime; each local task obeys an explicit GPU-memory budget.
- LSQB support. Utilities and query definitions support all nine LSQB benchmark queries, with count-only and materialized-result execution.
- Linux
- An NVIDIA GPU and a CUDA toolkit compatible with the installed RAPIDS stack
- CMake 3.30.4 or newer (required by the tested RAFT 25.10 package)
- A C++17-capable compiler
- RMM and RAFT, installed with CMake package files discoverable through
CMAKE_PREFIX_PATH,rmm_DIR, andraft_DIR liburingdevelopment files when building with the defaultVORA_ENABLE_IO_URING=ONI/O backend
The default CUDA architecture is 86 (for example, RTX 3090). Override it through CMake's standard CMAKE_CUDA_ARCHITECTURES variable when building for another GPU. For example, use -DCMAKE_CUDA_ARCHITECTURES=80 for a single architecture or -DCMAKE_CUDA_ARCHITECTURES="80;86" for multiple architectures.
The end-to-end smoke test below was verified with:
- Ubuntu 22.04, Linux 5.15
- NVIDIA RTX 3090, compute capability 8.6
- NVIDIA driver 535.309.01
- CUDA Toolkit 12.1.105
- GCC 11.4.0
- CMake 4.1.0
- RMM 25.10.0
- RAFT 25.10.0
Other versions and GPU architectures have not been systematically tested.
The pinned Conda environment installs CMake, the CUDA compiler components, RMM, and RAFT used by this configuration. GCC 11.4.0, liburing 2.1 development files (when VORA_ENABLE_IO_URING=ON), and the NVIDIA driver are supplied by the host system.
conda env create -f environment.yml
conda activate voraBuild outside the source directory:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CUDA_ARCHITECTURES=86 \
-DCMAKE_PREFIX_PATH=/path/to/rapids/install
cmake --build build --parallelIf RMM and RAFT were installed independently, pass their package directories instead:
cmake -S . -B build \
-Drmm_DIR=/path/to/rmm/lib/cmake/rmm \
-Draft_DIR=/path/to/raft/lib/cmake/raftOperator fusion is enabled by default. Control it at CMake configuration time with VORA_ENABLE_OPERATOR_FUSION:
# Enable operator fusion (default)
cmake -S . -B build-fusion \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CUDA_ARCHITECTURES=86 \
-DVORA_ENABLE_OPERATOR_FUSION=ON
cmake --build build-fusion --parallel
# Disable operator fusion
cmake -S . -B build-no-fusion \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CUDA_ARCHITECTURES=86 \
-DVORA_ENABLE_OPERATOR_FUSION=OFF
cmake --build build-no-fusion --parallelThe option applies to all targets through the cuda_interface compile definition. Use separate build directories when comparing the two variants so that objects compiled with different settings are not mixed.
The high-performance file-backed reader uses io_uring with O_DIRECT and is enabled by default. This mode requires the liburing development headers and library:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DVORA_ENABLE_IO_URING=ON
cmake --build build --parallelOn a machine without liburing, disable this backend explicitly. VORA then uses synchronous buffered pread while preserving the same file-backed graph functionality:
cmake -S . -B build-buffered \
-DCMAKE_BUILD_TYPE=Release \
-DVORA_ENABLE_IO_URING=OFF
cmake --build build-buffered --parallelThe buffered backend may provide lower shard-loading throughput. --disk_read_block_size controls its maximum read chunk; --disk_read_queue_depth is accepted for command-line compatibility but has no effect. The --use_mmap runtime mode is independent of this build option. Use separate build directories when comparing I/O backends.
The build provides the following VORA executables:
| Executable | Purpose |
|---|---|
preprocess |
Converts raw LSQB input into VORA's preprocessed representation. |
partition |
Creates hash-based physical graph partitions. |
vora_lsqb |
Runs LSQB queries using VORA's hash-partitioned execution. |
It also provides executables for the simple-partition baseline:
| Executable | Purpose |
|---|---|
simple_partition |
Creates edge-based physical graph partitions for the baseline. |
vora_lsqb_simple_partitioned |
Runs LSQB queries using the simple-partition baseline. |
VORA uses the Linked Streams Query Benchmark (LSQB), which defines nine subgraph queries (Q1–Q9) over labeled social-network graphs.
| Dataset | Included | Purpose |
|---|---|---|
| LSQB SF0.003 projected-FK | Yes | Correctness and smoke testing |
| Larger LSQB scale factors | No | Performance evaluation and full experiments |
The included SF0.003 projected-FK dataset is located at data/lsqb/social-network-sf0.003-projected-fk. It is intended only for correctness testing and is too small for performance evaluation. Obtain larger projected-FK datasets from https://ldbcouncil.org/data-sets-surf-repository/lsqb.html.
The usual workflow is:
- Preprocess raw graph files with
build/preprocess. - Partition the preprocessed graph using
build/partition, or usebuild/simple_partitionfor the simple-partition baseline. - Run one of the partitioned execution binaries on the generated partition directory.
From the repository root, run the following commands after building VORA. They preprocess the included fixture, create a single hash partition per vertex label, execute count-only LSQB Q1, and verify its deterministic match count.
Set SMOKE_GPU to the GPU that should run the test.
set -o pipefail
SMOKE_GPU=${SMOKE_GPU:-0}
SMOKE_DIR=${SMOKE_DIR:-build/smoke-test}
mkdir -p "$SMOKE_DIR"
build/preprocess \
--input_dir data/lsqb/social-network-sf0.003-projected-fk \
--output_dir "$SMOKE_DIR/preprocessed" \
--device "$SMOKE_GPU"
build/partition \
--input_dir "$SMOKE_DIR/preprocessed" \
--output_dir "$SMOKE_DIR/partitioned" \
--device "$SMOKE_GPU" \
--load_preprocessed_raw_edge_sets_from_file True \
--partition_threshold_for_vertices 50000 \
--num_physical_partitions_for_vertices 1
build/vora_lsqb \
--data "$SMOKE_DIR/partitioned" \
--query 1 \
--device "$SMOKE_GPU" \
--load_built_relations_from_file True \
--count_only True | tee "$SMOKE_DIR/query.log"
grep -Eq '^NUM_MATCHES[[:space:]]+20608$' "$SMOKE_DIR/query.log"The preprocessing and partitioning stages should each print
[Info] All Finished!. Near the end of query execution, the stable result is:
RUN Q1
NUM_MATCHES 20608
Timing and transfer-statistic lines vary by machine. The final grep exits successfully only when the expected match count is present. This fixture is a correctness smoke test and is too small for meaningful performance evaluation.
For each directed edge type, VORA stores both forward and reverse CUS relations. A CUS relation contains uniqueSrc, deg, and dst vectors: uniqueSrc lists the distinct source vertices, deg gives each source's degree, and dst densely stores all destination vertices. This layout is compact for sparse typed relations and enables traversal in either direction without runtime reordering.
CUS stores each distinct source once, its degree, and a densely packed destination vector.
The default partition workflow uses a two-key hash partitioning scheme over source and destination vertex IDs. VORA first builds fine-grained physical shards offline. At query time it combines selected physical shards into logical shards and processes the resulting local tasks sequentially on one GPU. The simple_partition and vora_lsqb_simple_partitioned executables implement the edge-based simple-partition baseline used for comparison in the paper; they are not VORA's default execution path.
The main execution options include --query, --data, and --device. For example, a hash-partitioned execution has this general form:
build/vora_lsqb \
--query 1-2-3 \
--data /path/to/partitioned-data \
--device 0scripts/run_binary.sh automates LSQB experiment runs. Its arguments are <scale-factor-list> <query-list> <gpu-id> <number-of-runs> <output-suffix>. For example, 1-3 selects scale factors 1 and 3; 1-2-3 selects LSQB Q1–Q3.
The relation-building and experiment scripts do not assume a particular home directory or storage device. Configure external data locations with environment variables:
| Variable | Purpose | Default |
|---|---|---|
VORA_BUILD_DIR |
Directory containing VORA executables | build/ under the repository root |
VORA_LOG_DIR |
Directory for logs and collected results | output/ under the repository root |
VORA_DATA_ROOT |
Preprocessed and partitioned VORA data | Required |
LSQB_DATA_ROOT |
Directory containing social-network-sf*-projected-fk |
Required for CSV input |
COMMAND |
Override the executable selected by a script | Script-specific build target |
VORA_NO_FUSION_BUILD_DIR |
Operator-fusion-disabled build directory | build-no-fusion/ under the repository root |
For repository-local defaults, source the provided environment helper:
source scripts/import-vars.shIt assigns build/, build-no-fusion/, output/, data/vora/, and data/lsqb/ under the repository root. Existing variable values take precedence, so machine-specific storage roots can be exported before sourcing the helper without recording those paths in Git.
For example:
export VORA_BUILD_DIR="$PWD/build"
export VORA_LOG_DIR="$PWD/output"
export LSQB_DATA_ROOT=/path/to/lsqb/data
export VORA_DATA_ROOT=/path/to/vora-data
# Build hash-partitioned binary relations from LSQB CSV input.
OUTPUT_FILE_FORMAT=binary \
scripts/build_relations.sh 1 0 hash csv ""
# Run Q1 once against the generated binary relations.
scripts/run_binary.sh 1 1 0 1 examplescripts/run_binary.sh expects the _binary hash-relation suffix by default. Set HASH_RELATION_SUFFIX when using another relation-directory naming scheme. Users may place the two data roots on any suitable storage volume.
The included fixture also demonstrates the projected-foreign-key CSV layout
expected by preprocess.
VORA is organized into three conceptual operator layers:
- Vector operators perform backend-aware operations over vectors and spans, including selection, sorting, scans, masking, and arithmetic. They are primarily implemented by
VectorOpinutils/array_like/operators.hpp. - Graph primitives provide batched operations over the CUS graph representation, including degree lookup, neighbor retrieval, and edge-existence testing. Examples include
getDegreeBatched,getNeighborsBatched, andcheckEdgeExistenceBatchedindata_graph/cus.cuh. - Subgraph-query operators manipulate intermediate match tables using operations such as
expand_,path_expand_,optional_expand_,expand_into_, andanti_expand_into_. They are implemented byTreeTableinexecution/tree_table.hpp.
Query execution and matching-order orchestration
↓
Subgraph-query operators
↓
Graph primitives
↓
Vector operators
execution/match.hpp and execution/lsqb_match.hpp assemble these operators according to a query's matching order. These layers describe VORA's internal architecture.
The paper uses op and vop as conceptual prefixes for eager and lazy VectorFlux operators. In the implementation, op corresponds to vora::VectorOp, commonly shortened to the vo namespace alias, while vop corresponds to vora::VectorOp_phantom, exposed at selected call sites through aliases such as vo_phantom1.
| Paper notation | Implementation | Behavior |
|---|---|---|
op |
VectorOp / vo |
Eager operator that materializes its result |
vop |
VectorOp_phantom / vo_phantom* |
Lazy operator that returns a fusible iterator-backed view |
When operator fusion is disabled, the vo_phantom* aliases resolve to vo, so the same call sites use materializing operators instead.
./
├── src/
│ ├── vora/ Shared Vora engine
│ │ ├── config.h Build/runtime configuration state and defaults
│ │ ├── config_cli.hpp Command-line configuration setup and reporting
│ │ ├── lsqb_data_pipeline.hpp LSQB preprocessing and relation data pipeline
│ │ ├── data_graph/ Graph loading and representations
│ │ ├── execution/ Matching operators and execution engine
│ │ ├── partition/ Hash-based partitioning and optimization
│ │ ├── query_graph/ Query representation and matching orders
│ │ └── utils/ Memory, vector, and general utilities
│ └── vora_simple_partition/ Simple-partition baseline module
├── third_party/ Vendored header-only dependencies
├── data/lsqb/ Redistributable LSQB fixture and its license
├── docs/assets/ README figures
├── scripts/ Experiment and data-preparation scripts
└── test/ Unit and I/O tests
See CITATION.cff for the preferred citation for VORA and its accepted ICDE 2027 paper.
Vendored dependency notices and licenses are listed in THIRD_PARTY_NOTICES.md, including cereal and rapidcsv.