Search Shortcut cmd + k | ctrl + k

GPU-accelerated aggregates, joins and GROUP BY for DuckDB on NVIDIA CUDA and Apple Silicon Metal — upload columns once, then reductions, joins and HAVING / top-k run on the GPU. First SQL execution engine that targets Apple Silicon GPUs.

Maintainer(s): singhpratech

Installing and Loading

INSTALL gpudb FROM community;
LOAD gpudb;

Example

LOAD gpudb;
-- Drop-in streaming aggregates (BIGINT and DOUBLE overloads)
SELECT gpu_sum(value::BIGINT) FROM range(1000000) AS t(value);

-- Resident columns — upload once, then reductions run on the GPU
-- with zero per-query transfer
SELECT gpu_upload('v', value::BIGINT) FROM range(1000000) AS t(value);
SELECT gpu_sum_resident('v');
SELECT gpu_last_stats();   -- which backend ran + kernel time

-- v0.6.0: GROUP BY / HAVING / top-k on the GPU. Upload a (key, payload)
-- pair once; the GPU sorts it once and every GROUP BY after that is a
-- segmented reduce, with HAVING and ORDER BY … LIMIT k evaluated on the
-- device so only the surviving rows come back.
SELECT gpu_upload_pair('p', (value % 1000)::BIGINT, value::BIGINT)
  FROM range(1000000) AS t(value);                        -- key + payload
SELECT * FROM gpu_groupby_sum_resident_having('p', '>', 499000000);  -- HAVING sum > …
SELECT * FROM gpu_groupby_sum_resident_topk('p', 10, 'desc');        -- top-10 groups by sum
SELECT key, sum, count FROM gpu_groupby_sum_resident('p') LIMIT 5;    -- or every group

-- v0.5.0: joins against a cached sorted build side, fused with the aggregate
SELECT gpu_upload('b', (value * 7)::BIGINT) FROM range(100) AS t(value);  -- build keys
SELECT gpu_join_sum_resident('p.k', 'p.v', 'b');        -- = SUM(p.v) ... JOIN b ON p.k = b.k

About gpudb

gpudb has four SQL surfaces:

Streaming aggregates — drop-in gpu_sum / gpu_min / gpu_max (BIGINT and DOUBLE overloads; smaller integers widen implicitly). They work in plain aggregation, GROUP BY, and window frames, match native DuckDB semantics (SQL NULL for empty/all-NULL input, NaN-aware total order for DOUBLE min/max), and run at parity with native — by design, since per-query GPU round-trips lose through this interface.

Resident columns (v0.4.0) — upload a column to device memory once, then reductions run on-GPU with zero per-query transfer:

  • gpu_upload(name, col) — one-time upload (BIGINT or DOUBLE)
  • gpu_sum_resident / gpu_min_resident / gpu_max_resident (BIGINT), gpu_sum_resident_f64 (DOUBLE)
  • gpu_resident_info, gpu_last_stats, gpu_build_info, gpu_drop_resident

Measured on TPC-H lineitem (DuckDB v1.5.5, 5-run medians, results verified equal to native; full grid with reproduction steps in the project's append-only BENCHMARK.md): SF50 SUM 99 ms native vs 4 ms resident on an RTX 4090 Laptop (25×); SF100 (600M rows) 99 ms vs 10 ms on an Apple M4 Max (9.9×). Whole-column min/max on stored tables stays a native win (zonemap statistics), one-shot cold queries favor native, and the one-time upload breaks even after roughly 100 repeated queries.

Resident joins (v0.5.0) — single-key equi-joins fused with their aggregate, executed in one GPU pass against a device-cached sorted build side (no join output materialised):

  • gpu_upload_pair(name, key BIGINT, payload BIGINT|DOUBLE) — uploads key and payload together (registers name.k and name.v)
  • gpu_join_sum_resident(probe_keys, probe_payload, build_keys), gpu_join_count_resident(probe_keys, build_keys), gpu_join_sum_resident_f64 — INNER; plus gpu_left_join_*, gpu_semi_join_* (EXISTS), gpu_anti_join_* (NOT EXISTS) variants; RIGHT/FULL OUTER as documented compositions
  • gpu_join_rows_resident(probe, build, kind) — table function returning (probe_idx, build_idx) for composing with other SQL

Measured against native DuckDB's hash join on TPC-H SF50 (300M lineitem ⋈ 75M orders, warm, same data and hardware on both sides): inner join + SUM(BIGINT) 998 ms native vs 27–37 ms on an RTX 4090 Laptop (27–37×) and 429 ms vs 37 ms on an Apple M4 Max (11.7×). BIGINT results are bit-equal to native on both backends, DOUBLE within the 1e-9 relative tolerance contract. The row-returning join wins only on unified memory; on a discrete GPU the device→host copy of the index pairs loses to native, which is stated in BENCHMARK.md rather than hidden.

Resident GROUP BY / HAVING / top-k (v0.6.0) — over the same uploaded pairs. The key column is sorted once on the device and cached (the same cache the joins use as a build side); every GROUP BY after that is a segmented reduce over that order, and the _having / _topk forms evaluate HAVING and ORDER BY aggregate LIMIT k on the device so only the surviving rows cross into DuckDB:

  • gpu_groupby_sum_resident(name) / _f64(name)(key, sum, count) per distinct key, sorted by key; gpu_groupby_count_resident(name)(key, count)
  • gpu_groupby_{sum,sum_f64,count}_resident_having(name, cmp, threshold) with cmp one of '>', '>=', '<', '<=' — the groups whose aggregate satisfies it
  • gpu_groupby_{sum,sum_f64,count}_resident_topk(name, k, 'desc'|'asc') — the k groups with the largest / smallest aggregate
  • gpu_topk_resident(name, k, order) / _f64(idx, value), i.e. ORDER BY col LIMIT k over a resident column

Measured statement-for-statement against native GROUP BY in the same process on TPC-H SF50 (300M rows → 75M groups, after the one-time upload and sort), results checked equal to native both ways: TPC-H Q18's inner query (HAVING sum > 300, 3,182 survivors) 1.0 s native vs 42–78 ms on an RTX 4090 Laptop (13–24×) and 462–476 ms vs 78 ms on an Apple M4 Max (6×); the top-10 groups by sum 60–64 ms on CUDA (16–17×) and 114 ms on Metal (4×). Returning every one of the 75M groups is 1.5× (CUDA, bounded by the copy over PCIe) to 2.8× (Metal). Low-cardinality GROUP BY (a handful of groups) stays a native win on Metal, and the first top-k call loses to native's zonemap top-k — both kept in the tables.

The community binary carries the full Metal backend on Apple Silicon. On Linux the community binary is CPU-only (the community build machines have no CUDA toolchain): every function works and returns the same results, with gpu_last_stats() reporting backend=CPU. For the CUDA backend on Linux use the release binary from the project's GitHub releases (static CUDA runtime, needs only a driver) or build from source with nvcc. SELECT gpu_build_info(); reports which backends a binary carries.

Source: https://github.com/singhpratech/duckdbgpumetaldbram

Added Functions

function_name function_type description comment examples
gpu_anti_join_count_resident scalar NULL NULL  
gpu_anti_join_sum_resident scalar NULL NULL  
gpu_anti_join_sum_resident_f64 scalar NULL NULL  
gpu_build_info scalar NULL NULL  
gpu_drop_resident scalar NULL NULL  
gpu_groupby_count_resident table NULL NULL  
gpu_groupby_count_resident_having table NULL NULL  
gpu_groupby_count_resident_topk table NULL NULL  
gpu_groupby_sum_resident table NULL NULL  
gpu_groupby_sum_resident_f64 table NULL NULL  
gpu_groupby_sum_resident_f64_having table NULL NULL  
gpu_groupby_sum_resident_f64_topk table NULL NULL  
gpu_groupby_sum_resident_having table NULL NULL  
gpu_groupby_sum_resident_topk table NULL NULL  
gpu_inner_join table NULL NULL  
gpu_join_count_resident scalar NULL NULL  
gpu_join_rows_resident table NULL NULL  
gpu_join_sum_resident scalar NULL NULL  
gpu_join_sum_resident_f64 scalar NULL NULL  
gpu_last_stats scalar NULL NULL  
gpu_left_join_count_resident scalar NULL NULL  
gpu_left_join_sum_resident scalar NULL NULL  
gpu_left_join_sum_resident_f64 scalar NULL NULL  
gpu_max aggregate NULL NULL  
gpu_max_resident scalar NULL NULL  
gpu_min aggregate NULL NULL  
gpu_min_resident scalar NULL NULL  
gpu_resident_info scalar NULL NULL  
gpu_semi_join_count_resident scalar NULL NULL  
gpu_semi_join_sum_resident scalar NULL NULL  
gpu_semi_join_sum_resident_f64 scalar NULL NULL  
gpu_sum aggregate NULL NULL  
gpu_sum_resident scalar NULL NULL  
gpu_sum_resident_f64 scalar NULL NULL  
gpu_topk_resident table NULL NULL  
gpu_topk_resident_f64 table NULL NULL  
gpu_upload aggregate NULL NULL  
gpu_upload_pair aggregate NULL NULL  

Overloaded Functions

This extension does not add any function overloads.

Added Types

This extension does not add any types.

Added Settings

This extension does not add any settings.