Skip to main content

metal-graph

CI

Fast graph analytics for Apple Silicon.

metal-graph is a Python and C library for ranking, searching, and exploring graphs on a Mac. Build a graph once from NumPy arrays, then run PageRank, HITS hub/authority scoring, batched personalized PageRank, breadth-first search, neighborhood extraction, and connected-components analysis through one consistent API.

The library uses the Metal GPU for work that benefits from it and a threaded CPU path for smaller jobs. That choice is automatic by default, so using metal-graph does not require Metal knowledge or device-management code.

It is particularly useful for:

  • knowledge-graph and retrieval pipelines;
  • entity ranking, recommendations, and repo maps;
  • graph-backed agent memory and neighborhood lookup;
  • local analysis of large graphs on Apple Silicon.

Why use metal-graph?

  • A small, practical API. Inputs and outputs are NumPy arrays, and integer or string IDs are supported.
  • Useful graph operations included. PageRank, HITS, batched PPR with top-k, BFS, sparse BFS, bounded k-hop extraction, and weakly connected components are available today.
  • Good performance across different query sizes. Large traversals can use the GPU, while tiny reachable components can stay on the CPU and avoid GPU launch overhead.
  • Transparent execution. mg.last_run_info() reports what ran, where it ran, and how long the engine took.
  • Tested against established libraries. More than 1,000 Python cases cover correctness, properties, and integration behavior. CI also checks the C API, wheels, and GPU-required jobs.

The main interface is Python (metal_graph, with NumPy input and output). A stable C API is available in include/mg.h, but it is not bundled into the Python wheel; install it separately from source with CMake.

Requirements

  • macOS 14 or newer
  • Apple Silicon with an M1 or newer
  • CPython 3.10–3.14

Installing a prebuilt wheel does not require a compiler. Building from source, including installing the C API, additionally requires CMake 3.24 or newer and Xcode with the Metal compiler.

Install

Install the prebuilt wheel from PyPI:

python3 -m pip install metal-graph
python3 -c "import metal_graph as mg; print(mg.__version__, mg.has_gpu())"

No compiler or Metal setup is needed for the wheel. Source builds, development setup, native tests, wheel validation, and C API installation are covered in CONTRIBUTING.md.

Quick start

import numpy as np
import metal_graph as mg

# Build an immutable directed graph.
src = np.array([0, 0, 1, 2], dtype=np.uint32)
dst = np.array([1, 2, 2, 3], dtype=np.uint32)
G = mg.Graph.from_edges(
    src,
    dst,
    directed=True,
    num_vertices=4,
)

# Rank every vertex.
rank = mg.pagerank(G)
hubs, authorities = mg.hits(G)

# Explore from vertex 0.
dist, parent = mg.bfs(G, sources=[0], direction="out")
vertices, sparse_dist, sparse_parent = mg.bfs(
    G,
    sources=[0],
    direction="out",
    output="sparse",
)
neighbors, edge_ids = mg.k_hop(
    G,
    seeds=[0],
    k=2,
    direction="both",
)

# Find weakly connected components.
components = mg.experimental.wcc(G)

For a batch of personalized ranking queries, pack the seeds using offsets:

seeds = np.array([0, 2], dtype=np.uint32)
seed_weights = np.ones(2, dtype=np.float32)
seed_offsets = np.array([0, 1, 2], dtype=np.uint64)

ids, scores = mg.ppr_topk(
    G,
    seeds,
    seed_weights,
    seed_offsets,
    k=4,
)
# ids and scores both have shape (number_of_queries, k)

By default, metal-graph chooses the execution path for each call:

mg.set_execution("auto")       # "auto", "gpu", or "cpu"
mg.last_run_info()             # op, path, iterations, and engine time

The notebooks/ directory contains complete examples for the core API, batched PPR retrieval, and low-latency BFS.

Available operations

  • Graph.from_edges(...) builds an immutable weighted or unweighted graph.
  • pagerank(...) returns a score for every vertex.
  • hits(...) returns L1-normalized hub and authority scores.
  • ppr_topk(...) answers a batch of personalized PageRank queries and returns only the highest-scoring vertices.
  • bfs(...) returns dense distances and parents, or compact results with output="sparse".
  • k_hop(...) finds a bounded neighborhood and its induced input edges.
  • experimental.wcc(...) labels weakly connected components.

Performance

Unless stated otherwise, the results below were measured on an Apple M4 Max running macOS 26.2. Timings cover the Python call through the returned result, so they include normal API overhead. Lower is better.

Most cells come from source commit a4a8bc; the HippoRAG PPR cell marked ² comes from the isolated artifact at source commit 7afb4b2. metal-graph cells are medians of 20 warm calls. External baselines generally use four timed calls after one warm-up, with additional samples for sub-2 ms BFS measurements. The full benchmark report contains p95 values, package versions, and methodology.

metal-graph latency

Dataset PageRank / iteration ppr_topk B=16, k=64 BFS source 0 WCC
RMAT-18 (V=262k, E=4.2M) 0.35 ms 10.2 ms 1.5 ms 3.8 ms
HippoRAG-shape KG (V=100k, E=2M, weighted) 0.21 ms 10.707 ms² 0.012 ms¹ 4.1 ms
RMAT-22 (V=4.2M, E=67M) 3.4 ms 122 ms 13.9 ms 32.4 ms
RMAT-24 (V=16.8M, E=268M) 14.7 ms 556 ms 46.1 ms 119.8 ms
soc-LiveJournal1 (V=4.8M, E=69M) 4.0 ms 174 ms 13.4 ms 24.9 ms
com-orkut (V=3.1M, E=117M) 7.9 ms 257 ms 8.3 ms 55.9 ms

HITS on Apple Metal

The focused HITS run used the same Apple M4 Max on macOS 26.6.2, with an absolute L1 convergence target of 1e-5 and 20 warm calls per implementation. Every baseline shown below passed a score-agreement check against the fp64 reference. The complete HITS benchmark report includes p95 and cold timings, setup costs, package versions, methodology, and source provenance.

Dataset Metal HITS Matching fp64 CPU Prebuilt SciPy recurrence rustworkx public HITS API
RMAT-18 (V=262k, E=4.2M) 2.650 ms 19.443 ms (7.34×) 34.881 ms (13.16×) rustworkx 537.627 ms (202.88×)
HippoRAG-shape KG (V=100k, E=2M) 1.470 ms 10.376 ms (7.06×) 3.927 ms (2.67×) rustworkx 54.275 ms (36.93×)

SciPy's matrix coalesces the KG's 2.0M repeated input links into 305,667 weighted nonzero positions, while metal-graph retains every parallel link. This gives SciPy a substantial workload advantage on that row and makes its 2.67× result the most conservative comparison.

Comparison with CPU graph libraries

Each cell shows metal-graph vs external median (speedup). These are end-to-end API comparisons, not claims that every library performs identical internal work.

The rustworkx BFS adapter returns the same dense dist + parent shape as metal-graph. PageRank uses the faster completed igraph or rustworkx call for each dataset, but solver and iteration behavior are not fully normalized. WCC compares the corresponding operation, although the external libraries return their native component representation. Treat the PageRank and WCC ratios as practical API-level context. The recorded versions were rustworkx 0.18.0 and igraph 1.0.0.

Dataset PageRank full run BFS source 0 WCC
RMAT-18 1.759 vs 306.411 ms, rustworkx (174.2×) 1.538 vs 1,030.735 ms, rustworkx (670.2×) 3.819 vs 125.525 ms, igraph (32.9×)
HippoRAG KG 3.074 vs 35.893 ms, igraph (11.7×) 0.012 vs 0.057 ms, rustworkx (4.8×)¹ 4.090 vs 13.136 ms, igraph (3.2×)
RMAT-22 16.892 vs 6,346.648 ms, rustworkx (375.7×) 13.920 vs 17,600.397 ms, rustworkx (1,264.4×) 32.362 vs 2,173.854 ms, igraph (67.2×)
RMAT-24 73.464 vs 30,765.861 ms, rustworkx (418.8×) 46.096 vs 79,206.021 ms, rustworkx (1,718.3×) 119.823 vs 9,058.173 ms, igraph (75.6×)
LiveJournal 19.773 vs 6,803.438 ms, rustworkx (344.1×) 13.398 vs 9,417.407 ms, rustworkx (702.9×) 24.932 vs 1,652.573 ms, igraph (66.3×)
Orkut 39.501 vs 24,247.799 ms, igraph (613.9×) 8.290 vs 41,158.931 ms, rustworkx (4,965.2×) 55.942 vs 67.772 ms, igraph (1.21×; below the 2× target)

Performance varies with the graph and operation. For example, HippoRAG WCC and source-0 BFS measured 3.2× and 4.8× faster, while Orkut WCC measured 1.21×. Using the benchmark's historical gate definitions, 17 of 18 full-run PageRank, high-degree BFS, and WCC cells exceeded the documented 2× target. The source-0 BFS column above is not that exact gate matrix.

Contextual personalized PageRank comparison

The igraph query loop uses PRPACK and cannot be pinned to metal-graph's iteration count. This table is useful end-to-end context, not an identical-work comparison.

Dataset metal-graph B=16, k=64 igraph 16-query loop Contextual speedup
RMAT-18 10.202 ms 5,972.022 ms 585.4×
HippoRAG KG 10.707 ms² 611.357 ms 57.1×
RMAT-22 121.993 ms 106,395.181 ms 872.1×
RMAT-24 555.634 ms 588,510.973 ms 1,059.2×
LiveJournal 173.635 ms did not complete after more than six hours not comparable
Orkut 257.417 ms skipped after the LiveJournal overrun not comparable
Notes for the marked benchmark cells

¹ Tiny-component BFS. Small reachable components use a bounded serial CPU path and are assessed against a 50 µs absolute-latency objective rather than a ratio alone. Dense result initialization dominates at this scale. output="sparse" avoids that cost when the traversal fits the configured caps. Overflow or forced-GPU execution remains exact but materializes dense state before compaction. The bounded collector still reserves a lazily zeroed V-byte visited map, so sparse output is not an unconditional O(|reached|)-memory guarantee. The benchmark records whether the objective passed but does not fail the command when it is missed.

For the HippoRAG high-degree source, which exercises the GPU, metal-graph measured 1.905 ms versus rustworkx at 50.058 ms (26.3×).

² Isolated HippoRAG PPR measurement. The displayed values come from a clean-process, 20-run artifact: JSON · rendered report. It measured 10.707 ms per batch (p95 10.922), or 0.669 ms/query. That meets the 0.7 ms/query objective and misses the 10 ms batch objective by 7%.

This measurement supersedes the interrupted full-suite run's anomalous 15.116 ms reading, which carried a 3.3 ms python_boundary residue unique to that collection window. The same artifact measured high-degree BFS at 1.857 ms versus igraph at 2.321 ms (1.25×) and rustworkx at 49.334 ms (26.6×), using the corrected equivalent-output adapter.

Benchmark provenance and comparison caveats

The run's original igraph BFS rows predate the corrected dense-output adapter and are excluded as non-equivalent. The current harness contains the corrected adapter but needs a new physical rerun. SciPy and NetworkX comparisons remain in the full artifacts and benchmark report.

The Orkut results have a strict checked-in JSON and rendered report pair. Completed rows for RMAT-18 through LiveJournal were reconstructed from the preserved run log and remain provisional. The original run was interrupted before its final JSON was written; LiveJournal's contextual PPR comparison did not complete.

These are workload-specific measurements from one machine, not a blanket GPU-speedup claim. Orkut's rustworkx PageRank call failed on the undirected graph, leaving igraph as its completed comparator.

How execution is chosen

mode="auto" is intended for normal use. The planner starts with the GPU when a Metal device is available and the graph has at least one million stored edges; otherwise it starts with the CPU. Individual algorithms can override that candidate when another path is more appropriate.

BFS, for example, first tries a bounded serial traversal. If the reachable component fits within its vertex and scanned-edge caps, the CPU answers the query directly. If it does not fit, the partial attempt is discarded and the regular CPU or GPU path runs instead.

You can override the planner when testing or profiling:

mg.set_execution("gpu")   # require the GPU path
mg.set_execution("cpu")   # require the CPU path
mg.set_execution("auto")  # restore automatic selection

mg.last_run_info() reports the operation, chosen path, iteration count, and engine time. Forced GPU mode raises an error if no device is available.

Advanced environment settings

GPU BFS batches increasing numbers of levels (8, 8, 16, 32, then 64) to keep shallow traversals responsive while reducing host round-trips on deep graphs.

Variable Default Meaning
MG_E_GPU_MIN 1000000 auto-planner GPU threshold (stored edges)
MG_PR_AUDIT_INTERVAL 5 iterations per GPU command batch / fp64 audit
MG_HITS_AUDIT_INTERVAL 5 HITS iterations per GPU command batch / fp64 audit
MG_BFS_LEVELS_PER_BATCH adaptive 8,8,16,32,64… BFS levels per command buffer; an integer pins a fixed size (clamped 1–256)
MG_BFS_SPARSE_MAX_VERTICES 1024 auto/CPU BFS latency-path vertex cap (0 disables)
MG_BFS_SPARSE_MAX_EDGES 8192 auto/CPU BFS latency-path scanned-edge cap (0 disables)
MG_WCC_ROUNDS_PER_BATCH 4 WCC hook+jump rounds per command buffer
MG_GPU_TOPK 1 GPU radix-select for ppr_topk (0 = CPU oracle)
MG_BFS_BOTTOMUP 1 direction-optimizing bottom-up switch
MG_REQUIRE_GPU 0 hard-fail when no Metal device
MG_FORCE_CPU 0 disable the GPU entirely
MG_THREADS all cores CPU-path thread count
MG_METALLIB embedded override path to a .metallib (dev loop)

Graph IDs and result behavior

External integer or string IDs are mapped to dense user indices in np.unique order. Algorithm inputs and outputs use those user indices. Use G.index_of(ids) to map external IDs to indices and G.external_ids[index] to map back. G.external_ids returns a detached, read-only NumPy snapshot, so changing a caller-owned copy cannot alter the graph's ID mapping.

Graphs are immutable after construction. Duplicate edges and self-loops are kept, and PageRank counts them. Non-finite or negative weights are rejected. Undirected graphs are symmetrized internally, while G.num_edges continues to report the number of input edges.

The most important result conventions are:

  • PageRank follows NetworkX-compatible weighted normalization and dangling mass handling. max_iter is a budget: metal-graph returns the current result when it is reached, while NetworkX raises.
  • HITS returns (hubs, authorities) with separate L1-normalized float32 vectors. It ignores edge weights, counts parallel edges separately, and returns zeros for edgeless graphs. max_iter is a budget.
  • ppr_topk normalizes seed weights, combines duplicate seeds, returns deterministic top-k ordering, and pads with id=-1, score=0 when k > V.
  • BFS supports direction="out", "in", or "both". Unreachable distances and missing parents are -1. Parent choice can vary when several same-depth parents are valid on a parallel path.
  • Sparse BFS returns aligned (vertices, dist, parent) arrays in ascending user-index order.
  • k_hop returns ascending vertex indices and original input edge IDs. max_vertices and max_edges bound result size, not worst-case runtime, scanned edges, or scratch memory. Use as_graph=True to materialize the returned neighborhood as a new graph.
  • WCC treats directed edges as undirected and numbers components by their first appearance in user-index order.
Exact PageRank convergence behavior

Convergence is L1(r_new - r_old) < V * tol, audited in fp64 every MG_PR_AUDIT_INTERVAL iterations. Iteration counts therefore land on audit boundaries. Batched PPR queries converge independently and freeze at those boundaries. Top-k ties are resolved by ascending user index.

Exact HITS convergence behavior

Starting from uniform hubs, HITS alternates authority = A.T @ hubs and hubs = A @ authority, L1-normalizing after each multiplication. Convergence is L1(hubs_new - hubs_old) < V * tol, audited every MG_HITS_AUDIT_INTERVAL iterations. Iteration counts therefore land on audit boundaries.

Current limits

  • The library currently supports V, E < 2^31; graph values are fp32 and graph IDs are int32 internally.
  • Graphs are immutable snapshots rather than streaming or mutable graphs.
  • HITS materializes and caches the reverse CSR for directed graphs, so its first call adds a second adjacency-sized structure to the graph snapshot.
  • direction="both" BFS on directed graphs and capped k_hop run on the deterministic CPU path.
  • Batched GPU PPR keeps the CSR plus an eight-query tile in memory. The dominant tile state uses roughly 100 bytes per vertex. Oversized workloads raise a descriptive working-set error; mode="cpu" avoids that GPU limit.
  • GPU top-k selection uses its bit-identical CPU fallback for tiny graphs (V < 4096), k >= V, k > 4096, and degenerate tie floods.
  • Typed and temporal k_hop filters are reserved for a future release; edge_types= and time_range= currently raise NotImplementedError.

Benchmarks and reproducibility

bench/run.py --suite v01 runs the full matrix against igraph, rustworkx, SciPy, and NetworkX. It uses synthetic RMAT and knowledge-graph-shaped data plus SHA-pinned SNAP datasets, which are downloaded only with --fetch. See the benchmark guide for commands and artifact format.

New published performance claims require a matched, checked-in JSON and rendered-report pair from a physical run. The disclosed exception is the reconstructed RMAT-18 through LiveJournal data described above; only Orkut has a matched pair for the later full-suite values.

Project resources

Roadmap

Possible future additions include label propagation, core number, similarity top-k, typed and temporal neighborhood filters, MLX device-resident results, and a NetworkX backend preview. Louvain and Leiden community detection are longer-term candidates.

License

Apache-2.0 (LICENSE). The vendored metal-cpp library is Apache-2.0, © Apple Inc.; see THIRD_PARTY_NOTICES.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

metal_graph-0.1.1-cp314-cp314-macosx_14_0_arm64.whl (248.6 kB view details)

Uploaded CPython 3.14macOS 14.0+ ARM64

metal_graph-0.1.1-cp313-cp313-macosx_14_0_arm64.whl (248.4 kB view details)

Uploaded CPython 3.13macOS 14.0+ ARM64

metal_graph-0.1.1-cp312-cp312-macosx_14_0_arm64.whl (248.5 kB view details)

Uploaded CPython 3.12macOS 14.0+ ARM64

metal_graph-0.1.1-cp311-cp311-macosx_14_0_arm64.whl (249.3 kB view details)

Uploaded CPython 3.11macOS 14.0+ ARM64

metal_graph-0.1.1-cp310-cp310-macosx_14_0_arm64.whl (249.6 kB view details)

Uploaded CPython 3.10macOS 14.0+ ARM64

File details

Details for the file metal_graph-0.1.1-cp314-cp314-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for metal_graph-0.1.1-cp314-cp314-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 8a4024d3aa038f89289b70b4331aa062128ea8ca854e913bea18516e011e3887
MD5 7c8448a6c6fe0128324de77627bd903c
BLAKE2b-256 3b7f45c0a2beb56ebb4b61ccf74edec705b85cccade52cb84ae6cd06262d6140

See more details on using hashes here.

Provenance

The following attestation bundles were made for metal_graph-0.1.1-cp314-cp314-macosx_14_0_arm64.whl:

Publisher: wheels.yml on tabulai/metal-graph

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file metal_graph-0.1.1-cp313-cp313-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for metal_graph-0.1.1-cp313-cp313-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 b6eb483aa337e252636b96b03cae31ef1aeb8c4e82a1fe6be3f6e8cd6d25deff
MD5 344686ae681b4d7cf4a85b3cf43e6a03
BLAKE2b-256 127d85c07779453289848671e173b6a3f25ae6be6bebf18927d2fe591276b4df

See more details on using hashes here.

Provenance

The following attestation bundles were made for metal_graph-0.1.1-cp313-cp313-macosx_14_0_arm64.whl:

Publisher: wheels.yml on tabulai/metal-graph

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file metal_graph-0.1.1-cp312-cp312-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for metal_graph-0.1.1-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 d9977f49015bec1ead2b99b87346b0a27ee783ab65d57a40d80a1426f35660a3
MD5 a4d334818d4854be7e1f1a6652be7ab9
BLAKE2b-256 bc0f939c62c2b015df8a3f250d748436d17c30b91b0ae379681190cbadec3614

See more details on using hashes here.

Provenance

The following attestation bundles were made for metal_graph-0.1.1-cp312-cp312-macosx_14_0_arm64.whl:

Publisher: wheels.yml on tabulai/metal-graph

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file metal_graph-0.1.1-cp311-cp311-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for metal_graph-0.1.1-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 a0359523d18d320e1b66f2ea3d2fc547b5b7c9ffd9d2a9ae0bf8579456516ee4
MD5 1c1c4c4a62cc774ea3c76b4837b3274a
BLAKE2b-256 e7568eb49ffec0a4bb7e4c7fb62ed1bdd299501ff4dc1e6bf4b49c6c5aa06ef8

See more details on using hashes here.

Provenance

The following attestation bundles were made for metal_graph-0.1.1-cp311-cp311-macosx_14_0_arm64.whl:

Publisher: wheels.yml on tabulai/metal-graph

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file metal_graph-0.1.1-cp310-cp310-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for metal_graph-0.1.1-cp310-cp310-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 2de35ec5db3f51346829d4d6182b0a3058d955310285b85a9dd08ea70d2b47a8
MD5 d5040680924d18dfab56e8a924ffc532
BLAKE2b-256 35501f4fe01614e82476599767c5f5a3a5518cdbcdf195dd0cf2d7482c1030e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for metal_graph-0.1.1-cp310-cp310-macosx_14_0_arm64.whl:

Publisher: wheels.yml on tabulai/metal-graph

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.1 This release

5 files

0.1.0

5 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page