mxframe 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mxframe-0.1.2/CHANGELOG.md +64 -0
- mxframe-0.1.2/MANIFEST.in +21 -0
- mxframe-0.1.2/PKG-INFO +437 -0
- mxframe-0.1.2/README.md +404 -0
- mxframe-0.1.2/__init__.py +136 -0
- mxframe-0.1.2/_modidx.py +180 -0
- mxframe-0.1.2/aot_kernels.py +633 -0
- mxframe-0.1.2/compiler.py +409 -0
- mxframe-0.1.2/custom_ops.py +2785 -0
- mxframe-0.1.2/docs/vision-and-architecture.md +304 -0
- mxframe-0.1.2/kernels.mojopkg +0 -0
- mxframe-0.1.2/kernels_aot/__init__.py +1 -0
- mxframe-0.1.2/kernels_aot/libmxkernels_aot.so +0 -0
- mxframe-0.1.2/kernels_aot/libmxkernels_aot_gpu.so +0 -0
- mxframe-0.1.2/lazy_expr.py +179 -0
- mxframe-0.1.2/lazy_frame.py +583 -0
- mxframe-0.1.2/mxframe.egg-info/PKG-INFO +437 -0
- mxframe-0.1.2/mxframe.egg-info/SOURCES.txt +40 -0
- mxframe-0.1.2/mxframe.egg-info/dependency_links.txt +1 -0
- mxframe-0.1.2/mxframe.egg-info/requires.txt +18 -0
- mxframe-0.1.2/mxframe.egg-info/top_level.txt +1 -0
- mxframe-0.1.2/optimizer.py +229 -0
- mxframe-0.1.2/plan_validation.py +134 -0
- mxframe-0.1.2/pyproject.toml +63 -0
- mxframe-0.1.2/setup.cfg +4 -0
- mxframe-0.1.2/setup.py +27 -0
- mxframe-0.1.2/sql_frontend.py +406 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ๐ Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to MXFrame are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## [0.1.2] โ 2026-04-22
|
|
9
|
+
|
|
10
|
+
### ๐ Fixed
|
|
11
|
+
- `pip install "mxframe[runtime]"` now gives a working install. The `[runtime]` extra pulls `modular>=25.5`, which provides the `max.engine` / `max.driver` modules that `mxframe.compiler` imports at module load. (Pixi users get `modular` from the Modular conda channel and should install `mxframe` without the extra.)
|
|
12
|
+
- `sqlglot` import in `mxframe.sql_frontend` is now lazy: `import mxframe` no longer requires the `[sql]` extra. Calling `mxframe.sql(โฆ)` without `sqlglot` installed raises a clear `ImportError` with the install hint.
|
|
13
|
+
- `__init__.py` `__version__` now matches `pyproject.toml`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [0.1.1] โ 2026-04-22
|
|
18
|
+
|
|
19
|
+
### ๐ Fixed
|
|
20
|
+
- Packaging: `readme` now points to `README.md` so PyPI shows the full project page with benchmarks and quickstart (was accidentally showing the internal vision/architecture doc)
|
|
21
|
+
- CI: `AOTKernelsGPU` init no longer hard-fails on CPU-only runners (`CUDA driver unavailable` is caught, `self._aot_gpu` falls back to `None`)
|
|
22
|
+
- CI: `_find_kernels_path()` uses `Path(__file__).resolve().parent` directly, removing the stale `/home/ablearn/mxdf/โฆ` dev fallback that surfaced in installed wheels
|
|
23
|
+
- Bug: `NameError: cache_key_count` in `_hash_join_mojo_cpu` MAX Graph fallback
|
|
24
|
+
- Tests: `scripts/_test_phase0_custom_ops.py` and `_test_phase1.py` no longer hard-code a developer's kernels path; they use the auto-detected default
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## [0.1.0] โ 2026-04-20
|
|
29
|
+
|
|
30
|
+
### โจ Added
|
|
31
|
+
- **All 22 TPC-H queries** on CPU and GPU AOT paths
|
|
32
|
+
- **GPU LEFT JOIN** โ `join_count_left` + `join_scatter_left` Mojo kernels wired to Python (Q13)
|
|
33
|
+
- `bench_simple.py` โ clean 4-column benchmark (Pandas | Polars | MX CPU | MX GPU)
|
|
34
|
+
- `--queries` and `--runs` flags for targeted benchmarking
|
|
35
|
+
|
|
36
|
+
### โก Performance
|
|
37
|
+
- **Q22** phone prefix anti-join: vectorized via `pc.utf8_slice_codeunits` + `pc.is_in` + `np.isin` โ 3.6ร faster than Polars CPU (was 10ร **slower**)
|
|
38
|
+
- **Q20** semi-join chain: eliminated `.to_pandas()` Pandas detour โ 8.7ร faster than Polars (was comparable)
|
|
39
|
+
- **Q21** EXISTS+NOT EXISTS: replaced `.to_pandas().groupby.nunique()` with NumPy composite key dedup
|
|
40
|
+
- **Q18** large volume customers: replaced `.to_pylist()` semi-join with Mojo join โ semi-join now uses AOT kernel path
|
|
41
|
+
- **Q15** argmax supplier: replaced `0.9999 * max_rev` tolerance hack with exact `pc.equal()` on float32
|
|
42
|
+
- **Q13** LEFT JOIN: removed `.to_pandas()` groupby detour โ now fully on GPU AOT path (19ms CPU, 29ms GPU vs 25ms Polars)
|
|
43
|
+
|
|
44
|
+
### ๐ Fixed
|
|
45
|
+
- `not use_gpu_filter` gate bug in `custom_ops.py` section 4.6 that blocked GPU filter path
|
|
46
|
+
- Q7 nation pre-join cache key now uses `id()` preventing cache misses on repeated hot calls
|
|
47
|
+
|
|
48
|
+
### ๐๏ธ Infrastructure
|
|
49
|
+
- `pyproject.toml` updated with full metadata, dependencies, optional extras, package-data for `.so` files
|
|
50
|
+
- `CONTRIBUTING.md` โ developer guide with kernel writing tutorial
|
|
51
|
+
- `PUBLISHING.md` โ step-by-step pip release guide
|
|
52
|
+
- GitHub Actions: `test.yml` (CI), `publish.yml` (PyPI via OIDC)
|
|
53
|
+
|
|
54
|
+
### ๐ Benchmark Summary (1M and 10M rows, all 22 queries, warm median of 3)
|
|
55
|
+
|
|
56
|
+
See the full table in [README.md ยง TPC-H Benchmark](README.md#-tpc-h-benchmark--all-22-queries),
|
|
57
|
+
sourced from `scripts/bench_results_1M.csv` and `scripts/bench_results_10M.csv`.
|
|
58
|
+
|
|
59
|
+
- **Correctness:** 22/22 queries pass on CPU and GPU paths
|
|
60
|
+
- **MX CPU wins vs Polars:** **21/22** at 1 M, **18/22** at 10 M
|
|
61
|
+
โ headline: **Q9 128ร ยท Q12 89ร ยท Q7 42ร ยท Q8 31ร ยท Q17 24ร ยท Q5 22ร** (at 10 M)
|
|
62
|
+
- **MX GPU wins vs Polars:** **16/22** at 1 M, **15/22** at 10 M
|
|
63
|
+
โ headline: **Q12 26.5ร ยท Q9 12ร ยท Q8 10.8ร ยท Q17 9.7ร ยท Q7 4.7ร** (at 10 M)
|
|
64
|
+
- **Remaining losses (Q4, Q6, Q13, Q21):** ops that still route through PyArrow fallback โ kernel replacements tracked in `roadmap.md`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
include pyproject.toml
|
|
2
|
+
include setup.py
|
|
3
|
+
include README.md
|
|
4
|
+
include CHANGELOG.md
|
|
5
|
+
include docs/vision-and-architecture.md
|
|
6
|
+
include __init__.py
|
|
7
|
+
include _modidx.py
|
|
8
|
+
include aot_kernels.py
|
|
9
|
+
include compiler.py
|
|
10
|
+
include custom_ops.py
|
|
11
|
+
include lazy_expr.py
|
|
12
|
+
include lazy_frame.py
|
|
13
|
+
include optimizer.py
|
|
14
|
+
include plan_validation.py
|
|
15
|
+
include sql_frontend.py
|
|
16
|
+
include kernels.mojopkg
|
|
17
|
+
recursive-include kernels_aot *.py *.so
|
|
18
|
+
prune llm-ctx
|
|
19
|
+
prune scripts
|
|
20
|
+
prune kernels_v261
|
|
21
|
+
global-exclude __pycache__ *.py[cod]
|
mxframe-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mxframe
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: GPU-accelerated DataFrame engine: Python ergonomics + Mojo AOT kernels, cross-vendor (NVIDIA/AMD/Apple).
|
|
5
|
+
Author-email: Abhishek Sreesaila <abhisheksreesaila@users.noreply.github.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/abhisheksreesaila/mxframe
|
|
8
|
+
Project-URL: Repository, https://github.com/abhisheksreesaila/mxframe
|
|
9
|
+
Keywords: dataframe,gpu,mojo,arrow,analytics,tpch
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: pyarrow>=14.0
|
|
20
|
+
Requires-Dist: numpy>=1.24
|
|
21
|
+
Requires-Dist: pandas>=2.0
|
|
22
|
+
Provides-Extra: runtime
|
|
23
|
+
Requires-Dist: modular>=25.5; extra == "runtime"
|
|
24
|
+
Provides-Extra: polars
|
|
25
|
+
Requires-Dist: polars>=0.20; extra == "polars"
|
|
26
|
+
Provides-Extra: sql
|
|
27
|
+
Requires-Dist: sqlglot>=25.0; extra == "sql"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: polars>=0.20; extra == "dev"
|
|
31
|
+
Requires-Dist: sqlglot>=25.0; extra == "dev"
|
|
32
|
+
Requires-Dist: duckdb>=0.9; extra == "dev"
|
|
33
|
+
|
|
34
|
+
# ๐ MXFrame
|
|
35
|
+
|
|
36
|
+
> **GPU-accelerated DataFrames โ Python ergonomics, Mojo speed, every GPU.**
|
|
37
|
+
|
|
38
|
+
MXFrame is a DataFrame query engine that pairs a Polars-style Python API with
|
|
39
|
+
pre-compiled Mojo AOT kernels. The same code runs on **NVIDIA, AMD, and Apple Silicon** โ
|
|
40
|
+
no CUDA required, no JIT compilation at query time.
|
|
41
|
+
|
|
42
|
+
[](docs/benchmarks.md)
|
|
43
|
+
[](pyproject.toml)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## โจ Why MXFrame?
|
|
49
|
+
|
|
50
|
+
| | pandas | Polars | cuDF (Rapids) | **MXFrame** |
|
|
51
|
+
|---|---|---|---|---|
|
|
52
|
+
| GPU support | โ | โ | โ
NVIDIA only | โ
**Any GPU** |
|
|
53
|
+
| Compiled kernels | โ | โ
Rust | โ
CUDA | โ
**Mojo AOT** |
|
|
54
|
+
| Install complexity | pip | pip | CUDA + Rapids stack | **pixi install** |
|
|
55
|
+
| TPC-H competitive | โ | โ
| โ
| โ
|
|
|
56
|
+
| Cross-vendor | โ | โ | โ | โ
NVIDIA/AMD/Apple |
|
|
57
|
+
|
|
58
|
+
MXFrame is the **cuDF architecture without the CUDA lock-in**.
|
|
59
|
+
The kernels are compiled **once** to a `.so` at build time โ loaded in ~1 ms at process start,
|
|
60
|
+
then pure dispatch on every query. No per-query JIT tax.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## โก Quick Start
|
|
65
|
+
|
|
66
|
+
### Install (Development)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 1. Install pixi (Modular's package manager)
|
|
70
|
+
curl -fsSL https://pixi.sh/install.sh | bash
|
|
71
|
+
|
|
72
|
+
# 2. Clone and set up
|
|
73
|
+
git clone https://github.com/abhisheksreesaila/mxframe
|
|
74
|
+
cd mxframe
|
|
75
|
+
pixi install
|
|
76
|
+
|
|
77
|
+
# 3. Verify GPU is working
|
|
78
|
+
pixi run python3 scripts/_check_gpu.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Your First Query
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import pyarrow as pa
|
|
85
|
+
from mxframe import LazyFrame, Scan, col, lit
|
|
86
|
+
|
|
87
|
+
# Create an Arrow table (or load from Parquet/CSV)
|
|
88
|
+
data = pa.table({
|
|
89
|
+
"dept": pa.array(["eng", "eng", "mkt", "mkt", "eng"]),
|
|
90
|
+
"salary": pa.array([120.0, 95.0, 80.0, 110.0, 130.0], pa.float32()),
|
|
91
|
+
"age": pa.array([32, 28, 35, 29, 40], pa.int32()),
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
# Build a lazy query plan โ nothing executes yet
|
|
95
|
+
result = (
|
|
96
|
+
LazyFrame(Scan(data))
|
|
97
|
+
.filter(col("age") > lit(28))
|
|
98
|
+
.groupby("dept")
|
|
99
|
+
.agg(
|
|
100
|
+
col("salary").sum().alias("total_salary"),
|
|
101
|
+
col("salary").mean().alias("avg_salary"),
|
|
102
|
+
col("age").count().alias("headcount"),
|
|
103
|
+
)
|
|
104
|
+
.sort(col("total_salary"), descending=True)
|
|
105
|
+
.compute(device="gpu") # or "cpu"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
print(result.to_pandas())
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Output:
|
|
112
|
+
```
|
|
113
|
+
dept total_salary avg_salary headcount
|
|
114
|
+
0 eng 345.0 115.0 3
|
|
115
|
+
1 mkt 110.0 110.0 1
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## ๐ TPC-H Benchmark โ all 22 queries
|
|
121
|
+
|
|
122
|
+
> **Hardware:** NVIDIA RTX 3090 (sm_86) ยท AMD 12-core CPU ยท Mojo 0.26.2 AOT kernels
|
|
123
|
+
> **Baselines:** Polars 1.29+ ยท Pandas 3.0 ยท MXFrame CPU path ยท MXFrame GPU path
|
|
124
|
+
> **Data:** TPC-H schema synthetic data (numpy RNG, fixed seed)
|
|
125
|
+
> **Methodology:** 1 warmup run + **median of 3 timed runs**, per engine. Warmup primes every cache an app would have primed on query #2 in production โ so these numbers reflect steady-state dispatch, not first-call JIT cost.
|
|
126
|
+
> **Source of truth:** [`scripts/bench_results_1M.csv`](scripts/bench_results_1M.csv) and
|
|
127
|
+
> [`scripts/bench_results_10M.csv`](scripts/bench_results_10M.csv) โ committed in repo, reproducible via
|
|
128
|
+
> [`scripts/benchmark_all_22.py`](scripts/benchmark_all_22.py)
|
|
129
|
+
|
|
130
|
+
### How the kernels dispatch
|
|
131
|
+
|
|
132
|
+
| Device | Path | Coverage |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| **CPU** | 100% ctypes into pre-compiled `libmxkernels_aot.so` | All 22 queries โ group aggs, masked aggs, inner + left joins, gather, filter, sort, unique |
|
|
135
|
+
| **GPU** | ctypes into pre-compiled `libmxkernels_aot_gpu.so` | All grouped aggs (sum/min/max/count) + masked global aggs |
|
|
136
|
+
| **GPU** | MAX Graph, shape-cached model | Hash joins only โ compiled once per `(n_left, n_right)` shape, cached for the session |
|
|
137
|
+
|
|
138
|
+
No per-query JIT on CPU. GPU aggregations skip MAX Graph entirely. GPU joins compile a model once per shape and reuse it โ the warmup run primes that cache.
|
|
139
|
+
|
|
140
|
+
### 1 M rows โ warm median of 3 runs
|
|
141
|
+
|
|
142
|
+
All times in **milliseconds ยท lower is better**. Speedup columns = `Polars / MXFrame`; **bold** = MXFrame wins.
|
|
143
|
+
|
|
144
|
+
| Query | Description | MX CPU | MX GPU | Polars | Pandas | CPU vs Polars | GPU vs Polars |
|
|
145
|
+
|---|---|---:|---:|---:|---:|---:|---:|
|
|
146
|
+
| Q1 | Filter + 8 aggregations | **11.2** | 94.4 | 35.6 | 117.4 | **3.2ร** | 0.4ร |
|
|
147
|
+
| Q2 | Min-cost supplier | **6.6** | 15.6 | 16.3 | 9.5 | **2.5ร** | **1.0ร** |
|
|
148
|
+
| Q3 | 3-table join + agg | **5.6** | **15.9** | 19.2 | 21.8 | **3.4ร** | **1.2ร** |
|
|
149
|
+
| Q4 | Order priority | 15.0 | 192.5 | 15.0 | 29.9 | 1.0ร | 0.1ร |
|
|
150
|
+
| Q5 | Multi-join + groupby | **0.6** | **4.1** | 22.6 | 22.0 | **37.7ร** | **5.5ร** |
|
|
151
|
+
| Q6 | Masked global agg | **7.9** | 13.2 | 10.4 | 7.7 | **1.3ร** | 0.8ร |
|
|
152
|
+
| Q7 | Shipping volume | **0.6** | **7.3** | 29.9 | 19.1 | **49.8ร** | **4.1ร** |
|
|
153
|
+
| Q8 | Market share | **0.9** | **4.7** | 20.2 | 10.3 | **22.4ร** | **4.3ร** |
|
|
154
|
+
| Q9 | Product profit (6-table join) | **0.6** | **6.6** | 39.9 | 17.8 | **66.5ร** | **6.0ร** |
|
|
155
|
+
| Q10 | Customer revenue | **3.6** | **11.0** | 32.6 | 19.3 | **9.1ร** | **3.0ร** |
|
|
156
|
+
| Q11 | Important stock | **0.5** | **2.7** | 7.4 | 3.0 | **14.8ร** | **2.7ร** |
|
|
157
|
+
| Q12 | 2-table join + agg | **0.8** | **3.5** | 24.5 | 586.2 | **30.6ร** | **7.0ร** |
|
|
158
|
+
| Q13 | Customer distribution | **16.2** | 30.2 | 27.5 | 33.5 | **1.7ร** | 0.9ร |
|
|
159
|
+
| Q14 | Promo revenue | **1.4** | **1.2** | 6.9 | 244.0 | **4.9ร** | **5.8ร** |
|
|
160
|
+
| Q15 | Top-supplier revenue | **1.3** | 11.8 | 9.9 | 6.4 | **7.6ร** | 0.8ร |
|
|
161
|
+
| Q16 | Part/supplier relationships | **2.1** | **6.3** | 16.9 | 6.8 | **8.0ร** | **2.7ร** |
|
|
162
|
+
| Q17 | Small-qty order | **0.3** | **2.7** | 7.9 | 4.7 | **26.3ร** | **2.9ร** |
|
|
163
|
+
| Q18 | Large-volume customers | **4.2** | **22.3** | 33.4 | 16.8 | **8.0ร** | **1.5ร** |
|
|
164
|
+
| Q19 | Discounted revenue | **10.9** | **11.2** | 19.6 | 22.4 | **1.8ร** | **1.8ร** |
|
|
165
|
+
| Q20 | Potential part promo | **4.3** | **5.6** | 30.0 | 9.6 | **7.0ร** | **5.4ร** |
|
|
166
|
+
| Q21 | Suppliers who kept (EXISTS) | **26.0** | 64.1 | 31.3 | 28.6 | **1.2ร** | 0.5ร |
|
|
167
|
+
| Q22 | Global sales opportunity | **7.6** | **16.5** | 25.4 | 56.7 | **3.3ร** | **1.5ร** |
|
|
168
|
+
|
|
169
|
+
**1 M summary:** MX CPU beats Polars on **21/22** queries (Q4 tied); MX GPU beats Polars on **16/22** queries. Headline CPU wins: **Q9 66ร**, **Q7 50ร**, **Q5 38ร**, **Q12 31ร**, **Q17 26ร**. Headline GPU wins: **Q12 7ร**, **Q9 6ร**, **Q14 5.8ร**, **Q5 5.5ร**, **Q20 5.4ร**.
|
|
170
|
+
|
|
171
|
+
### 10 M rows โ warm median of 3 runs
|
|
172
|
+
|
|
173
|
+
| Query | Description | MX CPU | MX GPU | Polars | Pandas | CPU vs Polars | GPU vs Polars |
|
|
174
|
+
|---|---|---:|---:|---:|---:|---:|---:|
|
|
175
|
+
| Q1 | Filter + 8 aggregations | **361.0** | 1190.7 | 946.5 | 1771.7 | **2.6ร** | 0.8ร |
|
|
176
|
+
| Q2 | Min-cost supplier | **5.7** | **11.9** | 15.4 | 7.6 | **2.7ร** | **1.3ร** |
|
|
177
|
+
| Q3 | 3-table join + agg | **57.7** | **67.2** | 72.2 | 581.8 | **1.3ร** | **1.1ร** |
|
|
178
|
+
| Q4 | Order priority | 301.5 | 492.2 | 113.8 | 807.6 | 0.4ร | 0.2ร |
|
|
179
|
+
| Q5 | Multi-join + groupby | **2.8** | **13.8** | 60.7 | 332.9 | **21.7ร** | **4.4ร** |
|
|
180
|
+
| Q6 | Masked global agg | 399.6 | 523.2 | 92.0 | 246.4 | 0.2ร | 0.2ร |
|
|
181
|
+
| Q7 | Shipping volume | **1.8** | **16.4** | 76.4 | 392.5 | **42.4ร** | **4.7ร** |
|
|
182
|
+
| Q8 | Market share | **1.3** | **3.8** | 40.9 | 55.1 | **31.5ร** | **10.8ร** |
|
|
183
|
+
| Q9 | Product profit | **0.7** | **7.4** | 89.7 | 431.3 | **128.1ร** | **12.1ร** |
|
|
184
|
+
| Q10 | Customer revenue | **39.2** | **45.0** | 131.1 | 216.2 | **3.3ร** | **2.9ร** |
|
|
185
|
+
| Q11 | Important stock | **0.4** | **3.1** | 6.6 | 2.5 | **16.5ร** | **2.1ร** |
|
|
186
|
+
| Q12 | 2-table join + agg | **1.3** | **4.4** | 116.4 | 6853.6 | **89.5ร** | **26.5ร** |
|
|
187
|
+
| Q13 | Customer distribution | 385.1 | 396.1 | 285.8 | 463.9 | 0.7ร | 0.7ร |
|
|
188
|
+
| Q14 | Promo revenue | **14.4** | **16.9** | 29.7 | 2719.2 | **2.1ร** | **1.8ร** |
|
|
189
|
+
| Q15 | Top-supplier revenue | **2.7** | 57.4 | 16.1 | 30.0 | **6.0ร** | 0.3ร |
|
|
190
|
+
| Q16 | Part/supplier relationships | **2.7** | **6.5** | 16.3 | 6.8 | **6.0ร** | **2.5ร** |
|
|
191
|
+
| Q17 | Small-qty order | **0.6** | **1.5** | 14.6 | 17.0 | **24.3ร** | **9.7ร** |
|
|
192
|
+
| Q18 | Large-volume customers | **46.8** | 69.4 | 63.4 | 242.8 | **1.4ร** | 0.9ร |
|
|
193
|
+
| Q19 | Discounted revenue | **100.4** | **97.9** | 112.9 | 234.8 | **1.1ร** | **1.2ร** |
|
|
194
|
+
| Q20 | Potential part promo | **32.3** | **37.1** | 39.9 | 52.9 | **1.2ร** | **1.1ร** |
|
|
195
|
+
| Q21 | Suppliers who kept | 756.0 | 705.3 | 85.9 | 216.6 | 0.1ร | 0.1ร |
|
|
196
|
+
| Q22 | Global sales opportunity | **54.1** | **59.2** | 132.8 | 1292.1 | **2.5ร** | **2.2ร** |
|
|
197
|
+
|
|
198
|
+
**10 M summary:** MX CPU beats Polars on **18/22** queries; MX GPU beats Polars on **15/22**. Headline CPU wins scale beautifully: **Q9 128ร**, **Q12 89ร**, **Q7 42ร**, **Q8 32ร**, **Q17 24ร**, **Q5 22ร**. Headline GPU wins: **Q12 26.5ร**, **Q9 12ร**, **Q8 10.8ร**, **Q17 9.7ร**, **Q7 4.7ร**.
|
|
199
|
+
|
|
200
|
+
### Where MXFrame loses (same at both scales)
|
|
201
|
+
|
|
202
|
+
- **Q4, Q6, Q13, Q21** โ operations where our kernel path falls back to PyArrow compute or does extra passes. These are the focus of the next milestone (see [`roadmap.md`](roadmap.md)).
|
|
203
|
+
|
|
204
|
+
### What the numbers mean
|
|
205
|
+
|
|
206
|
+
- **Correctness โ
** โ all 22 queries return results that round-trip through Pandas and match Polars output.
|
|
207
|
+
- **Coverage โ
** โ every TPC-H query has a CPU AOT path; all group aggs/masked aggs have GPU AOT paths; GPU joins use shape-cached MAX Graph models.
|
|
208
|
+
- **No JIT tax in steady state** โ after the first query of each shape warms the GPU join model cache, every subsequent call is pure dispatch. The CPU path has no JIT at all.
|
|
209
|
+
- **Why GPU doesn't always win** โ GPU wins scale with workload size and kernel coverage. At 10 M, GPU crushes Polars on the join-heavy queries (Q8/Q9/Q12) where Mojo's shape-cached kernels pay off. Where GPU loses, it's either PCIe overhead on tiny outputs (Q1, Q6) or ops that still route through PyArrow fallback (Q4, Q13, Q21).
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## ๐ Reproducing the Benchmark
|
|
216
|
+
|
|
217
|
+
To run the benchmark with **official TPC-H data** (generated by DuckDB's
|
|
218
|
+
faithful port of the TPC-H `dbgen` tool):
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Step 1 โ generate TPC-H data (requires: pip install duckdb)
|
|
222
|
+
# SF=1 โ ~6M lineitem rows, ~200 MB Parquet
|
|
223
|
+
# SF=0.1 โ ~600K rows, quick sanity check
|
|
224
|
+
pixi run python3 scripts/gen_tpch_parquet.py --sf 1
|
|
225
|
+
|
|
226
|
+
# Step 2 โ run the 22-query benchmark against real data
|
|
227
|
+
pixi run python3 scripts/bench_real_tpch.py --data-dir data/tpch_sf1 --runs 3
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The generated `data/tpch_sf1/` directory contains 8 Parquet files (one per
|
|
231
|
+
TPC-H table) that you can inspect, share, or version-control.
|
|
232
|
+
|
|
233
|
+
### Scale factor guide
|
|
234
|
+
|
|
235
|
+
| `--sf` | lineitem rows | approx size | use case |
|
|
236
|
+
|---|---|---|---|
|
|
237
|
+
| 0.01 | ~60K | 2 MB | smoke test / CI |
|
|
238
|
+
| 0.1 | ~600K | 20 MB | local dev |
|
|
239
|
+
| 1 | ~6M | 200 MB | standard published benchmark |
|
|
240
|
+
| 10 | ~60M | 2 GB | stress test |
|
|
241
|
+
|
|
242
|
+
### Data lineage & legal note
|
|
243
|
+
|
|
244
|
+
- Data is generated by **DuckDB's TPC-H extension** โ a faithful port of the
|
|
245
|
+
official TPC-H `dbgen` v3.0.1 with the same value distributions (uniform,
|
|
246
|
+
Zipfian, pseudo-random vocab).
|
|
247
|
+
- **TPC-Hยฎ is a trademark of the Transaction Processing Performance Council.**
|
|
248
|
+
These results are an _independent, non-certified_ benchmark. They may _not_
|
|
249
|
+
be reported as "TPC-H results" without formal TPC certification.
|
|
250
|
+
- Reference: <https://www.tpc.org/tpch/>
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## ๐ค API Reference
|
|
255
|
+
|
|
256
|
+
### LazyFrame
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from mxframe import LazyFrame, Scan, col, lit, when
|
|
260
|
+
|
|
261
|
+
lf = LazyFrame(Scan(arrow_table))
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
| Method | Description | Example |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `.filter(expr)` | Row filter | `.filter(col("x") > lit(10))` |
|
|
267
|
+
| `.select(*cols)` | Column projection | `.select("a", "b", col("c").alias("d"))` |
|
|
268
|
+
| `.with_columns(*exprs)` | Add/replace columns | `.with_columns((col("a") * lit(2)).alias("a2"))` |
|
|
269
|
+
| `.groupby(*keys)` | Start grouped agg | `.groupby("dept", "region")` |
|
|
270
|
+
| `.join(other, left_on, right_on, how)` | Hash join | `.join(lf2, "id", "fk_id", how="inner")` |
|
|
271
|
+
| `.sort(expr, descending)` | Sort rows | `.sort(col("revenue"), descending=True)` |
|
|
272
|
+
| `.limit(n)` | Take first N rows | `.limit(100)` |
|
|
273
|
+
| `.distinct()` | Deduplicate rows | `.distinct()` |
|
|
274
|
+
| `.compute(device)` | Execute the plan | `.compute(device="gpu")` |
|
|
275
|
+
|
|
276
|
+
### Expressions (`col`, `lit`, `when`)
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
# Arithmetic
|
|
280
|
+
col("price") * (lit(1.0) - col("discount"))
|
|
281
|
+
|
|
282
|
+
# Comparison
|
|
283
|
+
col("date") >= lit(19940101)
|
|
284
|
+
|
|
285
|
+
# Boolean combine
|
|
286
|
+
(col("x") > lit(0)) & (col("y") < lit(100))
|
|
287
|
+
|
|
288
|
+
# Conditional
|
|
289
|
+
when(col("nation") == lit("BRAZIL"), col("revenue"), lit(0.0))
|
|
290
|
+
|
|
291
|
+
# String
|
|
292
|
+
col("phone").startswith("13")
|
|
293
|
+
|
|
294
|
+
# Date parts
|
|
295
|
+
col("orderdate").year() # extract year as int32
|
|
296
|
+
|
|
297
|
+
# Aggregations (inside .agg())
|
|
298
|
+
col("salary").sum()
|
|
299
|
+
col("salary").mean()
|
|
300
|
+
col("salary").min()
|
|
301
|
+
col("salary").max()
|
|
302
|
+
col("id").count()
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### SQL Frontend
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from mxframe.sql_frontend import sql
|
|
309
|
+
|
|
310
|
+
result = sql("""
|
|
311
|
+
SELECT dept, SUM(salary) AS total, COUNT(*) AS n
|
|
312
|
+
FROM employees
|
|
313
|
+
WHERE age > 30
|
|
314
|
+
GROUP BY dept
|
|
315
|
+
ORDER BY total DESC
|
|
316
|
+
""", employees=arrow_table)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## ๐ง Supported Operations
|
|
322
|
+
|
|
323
|
+
| Category | Operations |
|
|
324
|
+
|---|---|
|
|
325
|
+
| **Filter** | `>`, `>=`, `<`, `<=`, `==`, `!=`, `&`, `\|`, `~`, `isin`, `startswith`, `contains` |
|
|
326
|
+
| **Aggregation** | `sum`, `mean`, `min`, `max`, `count` |
|
|
327
|
+
| **Groupby** | Single key, multi-key, composite key |
|
|
328
|
+
| **Join** | Inner, Left outer |
|
|
329
|
+
| **Sort** | Single/multi column, ascending/descending |
|
|
330
|
+
| **Window** | `year()` date part extraction |
|
|
331
|
+
| **Projection** | `select`, `with_columns`, `alias`, arithmetic expressions |
|
|
332
|
+
| **Semi-join** | Via unique-key inner join |
|
|
333
|
+
| **Anti-join** | Via `pc.is_in` + `pc.invert` |
|
|
334
|
+
| **Distinct** | Full row deduplication |
|
|
335
|
+
| **SQL** | `SELECT`, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY`, `LIMIT`, `JOIN` |
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## ๐ Project Structure
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
mxframe/
|
|
343
|
+
โโโ __init__.py โ Public API (LazyFrame, Scan, col, lit, when, sql)
|
|
344
|
+
โโโ lazy_frame.py โ LazyFrame, LazyGroupBy, Scan
|
|
345
|
+
โโโ lazy_expr.py โ Expr, col(), lit(), when()
|
|
346
|
+
โโโ compiler.py โ LogicalPlan โ MAX Graph compiler
|
|
347
|
+
โโโ custom_ops.py โ Dispatch: AOT kernels / MAX Graph / PyArrow fallback
|
|
348
|
+
โโโ optimizer.py โ Plan rewrites (filter pushdown, join reordering)
|
|
349
|
+
โโโ plan_validation.py โ Pre-execution plan checks
|
|
350
|
+
โโโ sql_frontend.py โ SQL โ LogicalPlan via sqlglot
|
|
351
|
+
โ
|
|
352
|
+
โโโ kernels_aot/ โ Pre-compiled AOT shared libraries
|
|
353
|
+
โ โโโ libmxkernels_aot.so โ CPU kernels (ctypes-callable)
|
|
354
|
+
โ โโโ libmxkernels_aot_gpu.so โ GPU kernels (CUDA/ROCm/Metal)
|
|
355
|
+
โ
|
|
356
|
+
โโโ kernels_v261/ โ Mojo kernel source (build time only)
|
|
357
|
+
โ โโโ group_sum.mojo, group_min.mojo, group_max.mojo ...
|
|
358
|
+
โ โโโ join_scatter.mojo, join_count.mojo
|
|
359
|
+
โ โโโ join_scatter_left.mojo, join_count_left.mojo
|
|
360
|
+
โ โโโ filter_gather.mojo, gather_rows.mojo, unique_mask.mojo ...
|
|
361
|
+
โ
|
|
362
|
+
โโโ kernels_aot/
|
|
363
|
+
โ โโโ kernels_aot.mojo โ CPU AOT entry points
|
|
364
|
+
โ โโโ kernels_aot_gpu.mojo โ GPU AOT entry points
|
|
365
|
+
โ
|
|
366
|
+
โโโ scripts/
|
|
367
|
+
โ โโโ bench_simple.py โ Clean 4-col benchmark (Pandas|Polars|MX CPU|MX GPU)
|
|
368
|
+
โ โโโ benchmark_tpch.py โ All 22 TPC-H query implementations
|
|
369
|
+
โ โโโ _test_aot_smoke.py โ AOT kernel smoke tests
|
|
370
|
+
โ โโโ quickstart.py โ Minimal hello-world example
|
|
371
|
+
โ
|
|
372
|
+
โโโ docs/
|
|
373
|
+
โโโ vision-and-architecture.md
|
|
374
|
+
โโโ CONTRIBUTING.md โ Developer guide
|
|
375
|
+
โโโ PUBLISHING.md โ pip release steps
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## ๐ฅ๏ธ Device Selection
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
# CPU (default, works everywhere)
|
|
384
|
+
result = lf.compute(device="cpu")
|
|
385
|
+
|
|
386
|
+
# GPU (requires NVIDIA/AMD/Apple Silicon with MAX runtime)
|
|
387
|
+
result = lf.compute(device="gpu")
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The GPU path uses Mojo's `DeviceContext` โ the same source compiles to:
|
|
391
|
+
- **PTX** on NVIDIA (CUDA-compatible)
|
|
392
|
+
- **HSA/ROCm** on AMD
|
|
393
|
+
- **Metal** on Apple Silicon
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## ๐งช Running Tests
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
# Smoke tests โ AOT kernels
|
|
401
|
+
pixi run python3 scripts/_test_aot_smoke.py
|
|
402
|
+
|
|
403
|
+
# All TPC-H correctness checks
|
|
404
|
+
pixi run python3 scripts/_test_phase6_tpch_tier2.py
|
|
405
|
+
|
|
406
|
+
# GPU check
|
|
407
|
+
pixi run python3 scripts/_check_gpu.py
|
|
408
|
+
|
|
409
|
+
# Full 22-query benchmark
|
|
410
|
+
pixi run python3 scripts/bench_simple.py --rows 1000000 --runs 3
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## ๐ฆ Dependencies
|
|
416
|
+
|
|
417
|
+
| Package | Required | Purpose |
|
|
418
|
+
|---|---|---|
|
|
419
|
+
| `pyarrow >= 14` | โ
| Column storage, zero-copy NumPy bridge |
|
|
420
|
+
| `numpy >= 1.24` | โ
| Vectorized pre/post processing |
|
|
421
|
+
| `pandas >= 2.0` | โ
| Reference implementations, Pandas bridge |
|
|
422
|
+
| `modular >= 26.2` | GPU only | MAX Engine runtime, Mojo GPU dispatch |
|
|
423
|
+
| `polars >= 0.20` | optional | Polars bridge + benchmark comparison |
|
|
424
|
+
| `sqlglot >= 25` | optional | SQL frontend parsing |
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## ๐ค Contributing
|
|
429
|
+
|
|
430
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the developer guide, kernel writing guidelines,
|
|
431
|
+
and how to add new TPC-H queries.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## ๐ License
|
|
436
|
+
|
|
437
|
+
Apache 2.0 โ see [LICENSE](LICENSE).
|