Skip to content

Commit 720ffc1

Browse files
authored
Implement core engine entry point and refactor Python inference (#43)
#42 #41 #40
2 parents 6a5af73 + 0e671ec commit 720ffc1

6 files changed

Lines changed: 1313 additions & 0 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Publish GitHub Package
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
tags:
7+
- "v*"
8+
9+
permissions:
10+
contents: read
11+
packages: write
12+
13+
jobs:
14+
publish-github-package:
15+
name: Publish to GitHub Packages
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Check out repository
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Node.js for GitHub Packages
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: "20"
26+
registry-url: "https://npm.pkg.github.com"
27+
scope: "@eamon2009"
28+
cache: "npm"
29+
cache-dependency-path: frontend/package-lock.json
30+
31+
- name: Build frontend assets
32+
run: |
33+
npm --prefix frontend ci
34+
npm --prefix frontend run build
35+
36+
- name: Prepare GitHub Packages metadata
37+
run: |
38+
npm pkg set name="@eamon2009/quadtrix"
39+
npm pkg set publishConfig.registry="https://npm.pkg.github.com"
40+
41+
- name: Publish package
42+
run: npm publish
43+
env:
44+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

benchmark/benchmark_config.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"schema_version": 1,
3+
"purpose": "Reference benchmark dimensions for the real C++ and Python Quadtrix benchmark suites.",
4+
"common": {
5+
"runs": 10,
6+
"warmup": 3,
7+
"quick_runs": 2,
8+
"quick_warmup": 1,
9+
"generate_tokens": 32,
10+
"quick_generate_tokens": 4,
11+
"train_steps": 5,
12+
"quick_train_steps": 1,
13+
"max_data_chars": 1000000,
14+
"quick_max_data_chars": 50000
15+
},
16+
"suites": {
17+
"data": [
18+
"tokenizer_or_char_encode",
19+
"batch_sample_to_device"
20+
],
21+
"primitive": [
22+
"matmul_3d",
23+
"attention_scores_or_softmax3d",
24+
"layer_norm"
25+
],
26+
"forward": [
27+
"batch1_seq8",
28+
"batch1_full_context",
29+
"configured_batch_full_context"
30+
],
31+
"training": [
32+
"adamw_step_forward_backward_update"
33+
],
34+
"generation": [
35+
"empty_prompt",
36+
"short_prompt",
37+
"long_prompt"
38+
]
39+
},
40+
"metrics": [
41+
"avg_ms",
42+
"median_ms",
43+
"min_ms",
44+
"max_ms",
45+
"p90_ms",
46+
"p95_ms",
47+
"std_ms",
48+
"tokens_per_sec",
49+
"loss",
50+
"parameter_mb_fp32",
51+
"memory_mb"
52+
],
53+
"outputs": {
54+
"cpp_json": "benchmark/results/cpp_benchmark.json",
55+
"cpp_csv": "benchmark/results/cpp_benchmark.csv",
56+
"python_json": "benchmark/results/python_benchmark.json",
57+
"python_csv": "benchmark/results/python_benchmark.csv"
58+
}
59+
}

benchmark/benchmark_training.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env python3
2+
"""Compatibility entry point for the real Python benchmark suite."""
3+
4+
from python_benchmark import main
5+
6+
7+
if __name__ == "__main__":
8+
raise SystemExit(main())

benchmark/compare.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python3
2+
"""Compare Quadtrix C++ and Python benchmark JSON files."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import json
8+
from pathlib import Path
9+
from typing import Any
10+
11+
12+
DEFAULT_RESULTS = Path(__file__).resolve().parent / "results"
13+
14+
15+
def load(path: Path) -> dict[str, Any]:
16+
with path.open("r", encoding="utf-8") as f:
17+
return json.load(f)
18+
19+
20+
def index_rows(result: dict[str, Any]) -> dict[tuple[str, str, int, int], dict[str, Any]]:
21+
indexed = {}
22+
for row in result.get("results", []):
23+
key = (
24+
row.get("suite", ""),
25+
row.get("name", ""),
26+
int(row.get("batch_size") or 0),
27+
int(row.get("sequence_length") or 0),
28+
)
29+
indexed[key] = row
30+
return indexed
31+
32+
33+
def pct(new: float, old: float) -> float:
34+
if old == 0:
35+
return 0.0
36+
return (new - old) / old * 100.0
37+
38+
39+
def compare_backends(cpp_path: Path, python_path: Path) -> int:
40+
missing = [str(path) for path in (cpp_path, python_path) if not path.exists()]
41+
if missing:
42+
print("Missing benchmark result file(s):")
43+
for path in missing:
44+
print(f" {path}")
45+
print("Run benchmark/run_all.py first, or pass explicit --cpp/--python paths.")
46+
return 1
47+
48+
cpp = load(cpp_path)
49+
py = load(python_path)
50+
cpp_rows = index_rows(cpp)
51+
py_rows = index_rows(py)
52+
53+
common = sorted(set(cpp_rows) & set(py_rows))
54+
if not common:
55+
print("No matching benchmark rows found.")
56+
return 1
57+
58+
print("Quadtrix C++ vs Python Benchmark Comparison")
59+
print(f"C++: {cpp_path}")
60+
print(f"Python: {python_path}")
61+
print()
62+
print(f"{'suite':<12} {'name':<24} {'shape':<10} {'cpp ms':>10} {'py ms':>10} {'cpp tok/s':>12} {'py tok/s':>12} {'latency':>10}")
63+
print("-" * 110)
64+
65+
for key in common:
66+
suite, name, batch, seq = key
67+
c = cpp_rows[key]
68+
p = py_rows[key]
69+
cpp_ms = float(c.get("avg_ms") or 0.0)
70+
py_ms = float(p.get("avg_ms") or 0.0)
71+
cpp_tps = float(c.get("tokens_per_sec") or 0.0)
72+
py_tps = float(p.get("tokens_per_sec") or 0.0)
73+
shape = f"{batch}x{seq}" if batch or seq else "-"
74+
delta = pct(cpp_ms, py_ms)
75+
print(
76+
f"{suite:<12} {name:<24} {shape:<10} "
77+
f"{cpp_ms:10.3f} {py_ms:10.3f} {cpp_tps:12.1f} {py_tps:12.1f} {delta:+9.1f}%"
78+
)
79+
return 0
80+
81+
82+
def compare_baseline(current_path: Path, baseline_path: Path, threshold_pct: float) -> int:
83+
missing = [str(path) for path in (current_path, baseline_path) if not path.exists()]
84+
if missing:
85+
print("Missing benchmark result file(s):")
86+
for path in missing:
87+
print(f" {path}")
88+
return 1
89+
90+
current = load(current_path)
91+
baseline = load(baseline_path)
92+
current_rows = index_rows(current)
93+
baseline_rows = index_rows(baseline)
94+
common = sorted(set(current_rows) & set(baseline_rows))
95+
96+
print("Quadtrix Benchmark Baseline Comparison")
97+
print(f"Current: {current_path}")
98+
print(f"Baseline: {baseline_path}")
99+
print()
100+
101+
regressions = []
102+
for key in common:
103+
c = current_rows[key]
104+
b = baseline_rows[key]
105+
delta = pct(float(c.get("avg_ms") or 0.0), float(b.get("avg_ms") or 0.0))
106+
if delta > threshold_pct:
107+
regressions.append((key, delta, b, c))
108+
109+
if not regressions:
110+
print(f"No latency regressions over {threshold_pct:.1f}%.")
111+
return 0
112+
113+
print(f"Latency regressions over {threshold_pct:.1f}%:")
114+
for key, delta, b, c in regressions:
115+
suite, name, batch, seq = key
116+
print(
117+
f" {suite}/{name} {batch}x{seq}: "
118+
f"{float(b.get('avg_ms') or 0.0):.3f} ms -> {float(c.get('avg_ms') or 0.0):.3f} ms ({delta:+.1f}%)"
119+
)
120+
return 2
121+
122+
123+
def parse_args() -> argparse.Namespace:
124+
parser = argparse.ArgumentParser(description="Compare Quadtrix benchmark results.")
125+
parser.add_argument("--cpp", type=Path, default=DEFAULT_RESULTS / "cpp_benchmark.json")
126+
parser.add_argument("--python", type=Path, default=DEFAULT_RESULTS / "python_benchmark.json")
127+
parser.add_argument("--current", type=Path, default=None)
128+
parser.add_argument("--baseline", type=Path, default=None)
129+
parser.add_argument("--threshold-pct", type=float, default=10.0)
130+
return parser.parse_args()
131+
132+
133+
def main() -> int:
134+
args = parse_args()
135+
if args.current and args.baseline:
136+
return compare_baseline(args.current, args.baseline, args.threshold_pct)
137+
return compare_backends(args.cpp, args.python)
138+
139+
140+
if __name__ == "__main__":
141+
raise SystemExit(main())

0 commit comments

Comments
 (0)