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.
@@ -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
+ [![TPC-H](https://img.shields.io/badge/TPC--H-22%2F22%20queries-brightgreen)](docs/benchmarks.md)
43
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)
44
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](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).