Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
165 commits
Select commit Hold shift + click to select a range
5e5c35b
plan: define Panel Tier-1 Stage B diagnostics contracts
TheHiddenObserver Aug 8, 2026
a23533e
plan: tighten Panel Stage B diagnostic contracts
TheHiddenObserver Aug 8, 2026
fc38ff9
feat: add shared Panel Stage B diagnostic primitives
TheHiddenObserver Aug 8, 2026
d69325a
test: add analytic Panel Stage B diagnostic contracts
TheHiddenObserver Aug 8, 2026
189ed68
plan: separate legacy FE df from Stage B diagnostic df
TheHiddenObserver Aug 8, 2026
8e94912
plan: define FE adjusted R-squared total df
TheHiddenObserver Aug 8, 2026
9525031
feat: add Panel Stage B estimator diagnostic context helpers
TheHiddenObserver Aug 8, 2026
34c1ed6
feat: retain small panel covariance for diagnostics
TheHiddenObserver Aug 8, 2026
fde3b4a
feat: add pooled panel diagnostics and fit statistics
TheHiddenObserver Aug 8, 2026
57fdac5
feat: add fixed-effects diagnostics and fit statistics
TheHiddenObserver Aug 8, 2026
bf5208e
feat: add random-effects fit statistics and Hausman context
TheHiddenObserver Aug 8, 2026
79fd98c
test: cover Stage B fitted panel diagnostic integration
TheHiddenObserver Aug 8, 2026
852d27b
feat: add BetweenOLS standardized panel fit statistics
TheHiddenObserver Aug 8, 2026
1c72862
feat: add FirstDifferenceOLS standardized panel fit statistics
TheHiddenObserver Aug 8, 2026
a8e5301
fix: use common panel metadata conversion in diagnostics
TheHiddenObserver Aug 8, 2026
350c8cf
feat: export Panel Stage B diagnostic API
TheHiddenObserver Aug 8, 2026
a627589
test: relax tail p-value relative tolerance for pooling F
TheHiddenObserver Aug 8, 2026
8cbcd19
feat: add FamaMacBeth parameter-based panel R-squared metrics
TheHiddenObserver Aug 8, 2026
eea794b
feat: export Panel Stage B diagnostics at top level
TheHiddenObserver Aug 8, 2026
e48fa2c
test: add maintained Torch CPU coverage for Panel Stage B
TheHiddenObserver Aug 8, 2026
905548c
test: cover Panel Stage B formula and missing-row alignment
TheHiddenObserver Aug 8, 2026
cbba6f0
test: cover standardized fit statistics across panel estimators
TheHiddenObserver Aug 8, 2026
9a237b5
ci: add maintained Panel Stage B Torch CPU gate
TheHiddenObserver Aug 8, 2026
5287bf6
fix: standardize FE covariance scale for Hausman diagnostics
TheHiddenObserver Aug 8, 2026
d1659c5
test: lock Hausman standard FE covariance scaling
TheHiddenObserver Aug 8, 2026
e7a0421
ci: fold Panel Stage B into maintained Torch CPU gate
TheHiddenObserver Aug 8, 2026
0b4e325
ci: remove redundant Panel Stage B Torch workflow
TheHiddenObserver Aug 8, 2026
73cc914
test: add linearmodels 7.0 alignment gate for Panel Stage B
TheHiddenObserver Aug 8, 2026
f69476b
ci: add linearmodels 7.0 Panel Stage B alignment gate
TheHiddenObserver Aug 8, 2026
4502f81
test: add physical GPU acceptance runner for Panel Stage B
TheHiddenObserver Aug 8, 2026
e27e406
ci: install pytest in Panel Stage B external alignment job
TheHiddenObserver Aug 8, 2026
797482f
test: isolate optional linearmodels dependency to external gate
TheHiddenObserver Aug 8, 2026
30c4f61
docs: document Panel Stage B diagnostics and fit statistics
TheHiddenObserver Aug 8, 2026
086a1a5
test: isolate FirstDifference external alignment from internal panel …
TheHiddenObserver Aug 8, 2026
67a8223
test: strengthen Panel Stage B physical inference gate
TheHiddenObserver Aug 8, 2026
e72be28
docs: document Panel Stage B diagnostics in Chinese
TheHiddenObserver Aug 8, 2026
4a18cc2
docs: add Panel Stage B changelog summary
TheHiddenObserver Aug 8, 2026
83c96a2
docs: add Panel Stage B English changelog
TheHiddenObserver Aug 8, 2026
6369887
docs: add Panel Stage B Chinese changelog
TheHiddenObserver Aug 8, 2026
90c7de5
docs: record PR122 physical GPU validation
TheHiddenObserver Aug 8, 2026
b619172
docs: record PR122 physical GPU acceptance
TheHiddenObserver Aug 8, 2026
7c7b10e
docs: record PR122 physical GPU acceptance
TheHiddenObserver Aug 8, 2026
1b15782
test: register PR122 physical validation for benchmark frontend
TheHiddenObserver Aug 8, 2026
ea13991
test: update benchmark frontend contracts for PR122 source
TheHiddenObserver Aug 8, 2026
d10f444
docs: link PR122 physical evidence to canonical frontend source
TheHiddenObserver Aug 8, 2026
3f222f7
docs: document PR122 validation-only frontend source
TheHiddenObserver Aug 8, 2026
ad70900
docs: refresh benchmark dashboard source snapshot
TheHiddenObserver Aug 8, 2026
ac4fe63
docs: refresh benchmark frontend data for PR122
TheHiddenObserver Aug 8, 2026
1760602
fix: stabilize CV frontend std serialization
TheHiddenObserver Aug 8, 2026
a8f0cbd
test: cover cross-Python CV frontend determinism
TheHiddenObserver Aug 8, 2026
5d86bf8
ci: enforce CV frontend determinism on maintained Python
TheHiddenObserver Aug 8, 2026
65169e0
test: lock real CV std determinism across Python
TheHiddenObserver Aug 8, 2026
038ee3d
docs: regenerate benchmark frontend after stable CV std
TheHiddenObserver Aug 8, 2026
aa74e43
fix: address panel diagnostics review findings
TheHiddenObserver Aug 8, 2026
7dcc7bf
fix: use observed two-way incidence rank
TheHiddenObserver Aug 8, 2026
f8b1071
fix: harden Hausman identity and exact-fit model F
TheHiddenObserver Aug 8, 2026
aeded4f
fix: detect explicit constants in RandomEffects diagnostics
TheHiddenObserver Aug 8, 2026
a596137
fix: preserve Torch constant-column detection
TheHiddenObserver Aug 8, 2026
2eac10d
test: cover panel Stage B review regressions
TheHiddenObserver Aug 8, 2026
671be50
test: cover Torch explicit-constant diagnostics
TheHiddenObserver Aug 8, 2026
6ca83ee
docs: align panel diagnostics contracts with review fixes
TheHiddenObserver Aug 8, 2026
df8f5c5
docs: sync Chinese panel diagnostic contracts
TheHiddenObserver Aug 8, 2026
8385a9e
test: align RandomEffects constant F structure externally
TheHiddenObserver Aug 8, 2026
1d31d19
docs: correct normative panel diagnostic rank specification
TheHiddenObserver Aug 8, 2026
9d5ee4c
docs: make post-review panel contracts normative
TheHiddenObserver Aug 8, 2026
3f92a0a
fix: make panel diagnostic tolerances scale invariant
TheHiddenObserver Aug 8, 2026
3dc25f7
fix: make explicit constant detection scale invariant
TheHiddenObserver Aug 8, 2026
b89968a
test: cover scale-invariant panel diagnostics
TheHiddenObserver Aug 8, 2026
8ead69d
test: cover explicit-constant RE on physical GPU
TheHiddenObserver Aug 8, 2026
40053e1
bench: measure Panel Hausman identity overhead
TheHiddenObserver Aug 8, 2026
73d1bcc
test: cover tiny explicit constants on Torch CPU
TheHiddenObserver Aug 8, 2026
0864c3c
perf: skip Hausman digest for inapplicable FE fits
TheHiddenObserver Aug 8, 2026
3ac2eb1
test: freeze Hausman digest eligibility
TheHiddenObserver Aug 8, 2026
a78b9cd
docs: record final panel numerical boundary contracts
TheHiddenObserver Aug 8, 2026
360bcae
test: lock Stage B physical runner coverage
TheHiddenObserver Aug 8, 2026
285a9f6
fix: canonicalize Hausman identity to common slopes
TheHiddenObserver Aug 8, 2026
37804bb
test: cover absorbed-intercept Hausman identity
TheHiddenObserver Aug 8, 2026
7cf0774
test: cover absorbed-intercept Hausman on Torch CPU
TheHiddenObserver Aug 8, 2026
658964f
test: validate absorbed-intercept Hausman on physical GPU
TheHiddenObserver Aug 8, 2026
3f38fe2
test: lock absorbed-intercept physical diagnostics
TheHiddenObserver Aug 8, 2026
85dc6cf
test: align absorbed-intercept Hausman structure with linearmodels
TheHiddenObserver Aug 8, 2026
1cdef77
docs: define canonical-slope Hausman identity
TheHiddenObserver Aug 8, 2026
8a7ff31
docs: document final Stage B numerical boundaries
TheHiddenObserver Aug 8, 2026
9c78bf6
docs: 同步 Stage B 最终数值边界
TheHiddenObserver Aug 8, 2026
faa95ce
fix: stabilize explicit-constant random effects across backends
TheHiddenObserver Aug 8, 2026
806789a
benchmark: record PR122 P100 Stage B validation faa95ce7
TheHiddenObserver Aug 8, 2026
e9e9cbd
fix: distinguish PR122 Hausman physical variants
TheHiddenObserver Aug 8, 2026
ae37fe1
benchmark: refresh PR122 canonical P100 evidence
TheHiddenObserver Aug 8, 2026
2fedbd0
style: canonicalize PR122 physical evidence JSON
TheHiddenObserver Aug 8, 2026
eadd5f4
benchmark: register exact-head PR122 physical evidence
TheHiddenObserver Aug 8, 2026
14fa642
test: lock PR122 physical evidence provenance
TheHiddenObserver Aug 8, 2026
5b21731
docs: record final PR122 P100 evidence
TheHiddenObserver Aug 8, 2026
7626f24
benchmark: point Panel coverage at final PR122 evidence
TheHiddenObserver Aug 8, 2026
41452f7
test: update final PR122 frontend coverage contract
TheHiddenObserver Aug 8, 2026
6a0e635
test: sync PR122 final evidence source ID
TheHiddenObserver Aug 8, 2026
4593071
benchmark: classify PR122 raw physical artifact
TheHiddenObserver Aug 8, 2026
cfac44e
ci: refresh PR122 generated benchmark assets once
TheHiddenObserver Aug 8, 2026
fb68bc5
chore: refresh PR122 benchmark frontend assets
github-actions[bot] Aug 8, 2026
9ac1257
ci: restore read-only benchmark frontend workflow
TheHiddenObserver Aug 8, 2026
9113ac3
docs: publish final PR122 physical evidence
TheHiddenObserver Aug 8, 2026
f7da4d5
docs: 同步 PR122 最终物理验证证据
TheHiddenObserver Aug 8, 2026
dd459fd
docs: close PR122 physical evidence record
TheHiddenObserver Aug 8, 2026
ace884a
docs: restore BetweenOLS estimator contract documentation
TheHiddenObserver Aug 9, 2026
ac2f04a
fix: use component-aware FE rank for fit feasibility
TheHiddenObserver Aug 9, 2026
d3e942d
fix: propagate physical validation aggregate failures
TheHiddenObserver Aug 9, 2026
2e920c5
test: cover ready-review panel regressions
TheHiddenObserver Aug 9, 2026
62ac38a
test: cover disconnected FE rank on Torch CPU
TheHiddenObserver Aug 9, 2026
a491a42
docs: mark PR122 physical evidence superseded by FE fix
TheHiddenObserver Aug 9, 2026
594b1af
test: add disconnected FE physical GPU gate
TheHiddenObserver Aug 9, 2026
f005b77
fix: use exact Cauchy boundary for panel df1 inference
TheHiddenObserver Aug 9, 2026
a57efce
test: lock Torch df1 panel confidence intervals
TheHiddenObserver Aug 9, 2026
72b3279
benchmark: record PR122 P100 Stage B + disconnected FE validation a57…
TheHiddenObserver Aug 9, 2026
1b0af41
benchmark: promote PR122 a57efcea physical evidence
TheHiddenObserver Aug 9, 2026
5bd4680
benchmark: normalize PR122 canonical evidence
TheHiddenObserver Aug 9, 2026
8386ae9
benchmark: register PR122 current physical evidence
TheHiddenObserver Aug 9, 2026
e4cf630
benchmark: refresh PR122 panel coverage source
TheHiddenObserver Aug 9, 2026
6babb27
benchmark: retire superseded PR122 canonical source
TheHiddenObserver Aug 9, 2026
725f4c2
docs: record final PR122 physical GPU acceptance
TheHiddenObserver Aug 9, 2026
af42c47
test: refresh PR122 canonical physical source contract
TheHiddenObserver Aug 9, 2026
52a9a86
test: refresh PR122 panel coverage identity
TheHiddenObserver Aug 9, 2026
659abb8
test: publish current PR122 physical source identity
TheHiddenObserver Aug 9, 2026
fd68a6b
test: point PR122 review regressions at current evidence
TheHiddenObserver Aug 9, 2026
9640bdd
docs: clarify PR122 physical evidence applicability
TheHiddenObserver Aug 9, 2026
ca09cb5
ci: temporarily sync PR122 generated assets
TheHiddenObserver Aug 9, 2026
646866f
benchmark: refresh PR122 frontend evidence assets
github-actions[bot] Aug 9, 2026
fd1ab5a
ci: temporarily sync PR122 benchmark assets
TheHiddenObserver Aug 9, 2026
ba5b2b2
ci: remove temporary PR122 asset sync
TheHiddenObserver Aug 9, 2026
fb29114
ci: temporarily stage PR122 Hausman physical gate fix
TheHiddenObserver Aug 9, 2026
883cba1
ci: repair PR122 Hausman autofix carrier
TheHiddenObserver Aug 9, 2026
d509018
ci: refine PR122 applicable Hausman fixture
TheHiddenObserver Aug 9, 2026
e188641
ci: scan applicable Hausman fixture regimes
TheHiddenObserver Aug 9, 2026
a9206a6
ci: scan nonzero-effect Hausman fixtures
TheHiddenObserver Aug 9, 2026
9466efa
ci: validate final PR122 Hausman physical gate fix
TheHiddenObserver Aug 9, 2026
f05da45
ci: retry PR122 Hausman autofix after test typo
TheHiddenObserver Aug 9, 2026
82d837f
test: require applicable Hausman GPU coverage
github-actions[bot] Aug 9, 2026
0b9c9b2
docs: reopen PR122 physical gate for applicable Hausman coverage
TheHiddenObserver Aug 9, 2026
e80e8ed
ci: stage PR122 Hausman frontend parser fix
TheHiddenObserver Aug 9, 2026
4eb501d
ci: repair PR122 Hausman parser autofix carrier
TheHiddenObserver Aug 9, 2026
72c5b6f
ci: retry PR122 Hausman parser autofix
TheHiddenObserver Aug 9, 2026
7cfdf8d
fix: distinguish applicable Hausman frontend evidence
TheHiddenObserver Aug 9, 2026
c7abeda
test: lock applicable Hausman frontend identity
TheHiddenObserver Aug 9, 2026
3b67ca5
ci: remove temporary PR122 parser carrier
TheHiddenObserver Aug 9, 2026
2612d78
ci: remove temporary PR122 parser carrier
TheHiddenObserver Aug 9, 2026
a8c22d3
fix: require numeric applicable Hausman evidence
TheHiddenObserver Aug 9, 2026
190f403
test: require numeric applicable Hausman evidence
TheHiddenObserver Aug 9, 2026
16ed93d
ci: cover applicable Hausman parser contract
TheHiddenObserver Aug 9, 2026
179c51f
ci: stage PR122 pending physical changelog sync
TheHiddenObserver Aug 9, 2026
344d6ba
ci: retry PR122 pending changelog sync
TheHiddenObserver Aug 9, 2026
bb49cb7
ci: finalize PR122 pending changelog sync
TheHiddenObserver Aug 9, 2026
48184a2
docs: mark PR122 applicable Hausman physical gate pending
github-actions[bot] Aug 9, 2026
0e6da46
docs: record PR122 local review-fix closure
TheHiddenObserver Aug 9, 2026
773beec
docs: clarify physical evidence invalidation rule
TheHiddenObserver Aug 9, 2026
2701aa9
test: lock applicable Hausman case identity
TheHiddenObserver Aug 9, 2026
0d0d654
benchmark: record PR122 P100 Stage B validation 2701aa9f
TheHiddenObserver Aug 9, 2026
ad4a660
ci: finalize PR122 physical evidence once
TheHiddenObserver Aug 9, 2026
f45759b
test: update PR122 canonical source contract
TheHiddenObserver Aug 9, 2026
624c7d1
ci: retry PR122 evidence finalization
TheHiddenObserver Aug 9, 2026
7b54cfb
ci: repair PR122 evidence retry carrier
TheHiddenObserver Aug 9, 2026
8eff35c
ci: isolate PR122 retry from workflow writes
TheHiddenObserver Aug 9, 2026
6b681dc
test: align PR122 final frontend coverage
github-actions[bot] Aug 9, 2026
99eea75
ci: run PR122 final evidence promotion
TheHiddenObserver Aug 9, 2026
764f4a9
benchmark: finalize PR122 Stage B physical evidence
github-actions[bot] Aug 9, 2026
09d1d67
ci: remove PR122 evidence promotion carrier
TheHiddenObserver Aug 9, 2026
d47575d
ci: remove PR122 evidence retry carrier
TheHiddenObserver Aug 9, 2026
cdff571
ci: remove PR122 final evidence carrier
TheHiddenObserver Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/benchmark-frontend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ on:
- 'dev/tests/test_benchmark_cv_source.py'
- 'dev/tests/test_cv_runner_instrumentation.py'
- 'dev/tests/test_frontend_contracts.py'
- 'dev/tests/test_frontend_cv_determinism.py'
- 'dev/tests/test_frontend_domain_coverage.py'
- 'dev/tests/test_panel_stage_b_frontend_source.py'
- 'dev/tests/test_panel_stage_b_applicable_hausman_parser.py'
- 'dev/tests/fixtures/benchmark_frontend/**'
- 'frontend/**'
- 'docs/assets/benchmarks/**'
Expand All @@ -28,7 +31,10 @@ on:
- 'dev/tests/test_benchmark_cv_source.py'
- 'dev/tests/test_cv_runner_instrumentation.py'
- 'dev/tests/test_frontend_contracts.py'
- 'dev/tests/test_frontend_cv_determinism.py'
- 'dev/tests/test_frontend_domain_coverage.py'
- 'dev/tests/test_panel_stage_b_frontend_source.py'
- 'dev/tests/test_panel_stage_b_applicable_hausman_parser.py'
- 'dev/tests/fixtures/benchmark_frontend/**'
- 'frontend/**'
- 'docs/assets/benchmarks/**'
Expand Down Expand Up @@ -61,7 +67,10 @@ jobs:
dev/tests/test_benchmark_cv_source.py \
dev/tests/test_cv_runner_instrumentation.py \
dev/tests/test_frontend_contracts.py \
dev/tests/test_frontend_domain_coverage.py -v
dev/tests/test_frontend_cv_determinism.py \
dev/tests/test_frontend_domain_coverage.py \
dev/tests/test_panel_stage_b_frontend_source.py \
dev/tests/test_panel_stage_b_applicable_hausman_parser.py -v
- name: Validate generator output
run: python dev/benchmarks/generate_benchmark_data.py --check --strict-sources

Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,31 @@ jobs:
python -m pytest \
dev/tests/test_logistic_cv_torch_dtype.py \
dev/tests/test_panel_stage_a_torch_cpu.py \
dev/tests/test_panel_stage_b_torch_cpu.py \
-q --tb=short

panel-stage-b-linearmodels:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install current external panel reference
run: |
python -m pip install --upgrade pip
python -m pip install -e . "linearmodels==7.0" pytest
- name: Confirm linearmodels reference version
run: |
python - <<'PY'
import linearmodels
assert linearmodels.__version__ == "7.0"
print("linearmodels", linearmodels.__version__)
PY
- name: Run Panel Stage B external alignment
run: python -m pytest dev/tests/test_panel_stage_b_linearmodels.py -q --tb=short

static-contracts:
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to statgpu are documented here, organized by release and dat

## 2026-08-08

### PR #122 — Panel Tier-1 diagnostics Stage B

- Added structured Panel `fit_statistics_` with parameter-based within/between/overall R², adjusted R², and classical model F statistics while preserving Stage-A inference and legacy df/R² attributes.
- Added classical pooling F, one-way entity Breusch-Pagan LM (including Baltagi-Li unbalanced panels), and one-way classical FE-vs-RE Hausman with explicit applicability diagnostics.
- Added NumPy/CuPy/Torch coverage, formula-row alignment, maintained Torch 2.0 CPU tests, executable linearmodels 7.0 definition alignment, and an exact-head physical GPU acceptance runner that rechecks coefficient inference as well as Stage-B diagnostics.

### PR #121 — CuPy inverse-quantile LUT correctness

- Fixed CuPy `betaincinv` and `gammaincinv` LUT cache tuple ordering so inverse quantiles no longer collapse to boundary values and downstream confidence intervals retain their correct width.
Expand Down
9 changes: 9 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ contracts, documentation contracts, and the Python 3.9–3.12 regression matrix.
For changes affecting CuPy, Torch, inference, device routing, or performance,
record physical-GPU acceptance on the exact release source commit.

Physical evidence is tied to both the numerical implementation and the validator
that defines the acceptance matrix. If a physical-validation runner changes after
an artifact has been accepted—for example, because review adds a previously
uncovered inference branch—the old artifact remains useful historical evidence
but no longer proves the new acceptance contract. Return the affected pull
request to a pending/draft state, rerun the changed validator on an exact clean
candidate head, and only then promote new canonical evidence or restore a
Ready/merge-ready conclusion.

### Package validation

The `Release package validation` workflow automatically:
Expand Down
12 changes: 8 additions & 4 deletions dev/benchmarks/benchmark_coverage_matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,22 @@
},
{
"capability_id": "panel-estimation",
"label": "Panel estimation coverage",
"label": "Panel estimation and diagnostics coverage",
"status": "partial_canonical",
"source_ids": [
"new-modules-20260624-bcbdb676223b"
"new-modules-20260624-bcbdb676223b",
"panel-stage-b-pr122-20260809-2056f836bfe2"
],
"representative_dimensions": [
"estimator",
"backend",
"aligned_scale"
"aligned_scale",
"physical_validation",
"diagnostics",
"inference_regression"
],
"issue": "#108",
"disposition": "Aligned PanelOLS and RandomEffects evidence exists; additional estimators and covariance variants remain open."
"disposition": "June timing rows cover aligned PanelOLS and RandomEffects. PR #122 adds canonical validation-only CuPy/Torch evidence for the 17-case Stage-B estimator matrix, five Hausman diagnostics per backend (including a physically applicable nonzero-effect statistic/p-value/df path), backend provenance, Stage-A inference regression, and the disconnected two-way FE df=1 physical boundary; broader performance/covariance timing remains open."
},
{
"capability_id": "gam-nonparametric",
Expand Down
246 changes: 246 additions & 0 deletions dev/benchmarks/benchmark_panel_stage_b_identity_overhead.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
#!/usr/bin/env python3
"""Measure Stage-B full-content identity overhead on physical GPU backends.

This benchmark isolates the cost of the collision-resistant X/y SHA-256 used by
Hausman sample identity. It compares ordinary PanelOLS/RandomEffects fit time
against the same fit with only ``_full_content_digest`` replaced by a constant
stub. Numerical work, low-order audit reductions, estimator setup, and all other
Stage-B code remain unchanged in the baseline.

The script is intentionally separate from ``validate_panel_stage_b_gpu.py``:
that runner remains correctness/provenance-only and its frontend source must not
acquire inferred timing or speedup fields.
"""

from __future__ import annotations

import argparse
import importlib.metadata
import json
import platform
import subprocess
import time
from pathlib import Path

import numpy as np

import statgpu.panel._diagnostics as diagnostics
from statgpu.panel import PanelOLS, RandomEffects


def _git_sha() -> str:
return subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()


def _git_status_porcelain() -> str:
return subprocess.check_output(["git", "status", "--porcelain"], text=True)


def _version(name: str):
try:
return importlib.metadata.version(name)
except importlib.metadata.PackageNotFoundError:
return None


def _parse_scales(value: str):
out = []
for item in value.split(","):
n_text, k_text = item.strip().lower().split("x", 1)
n, k = int(n_text), int(k_text)
if n <= 0 or k <= 0:
raise ValueError("benchmark scales must be positive NxK pairs")
out.append((n, k))
return out


def _sync(backend: str):
if backend == "cupy":
import cupy as cp

cp.cuda.Stream.null.synchronize()
elif backend == "torch":
import torch

torch.cuda.synchronize()


def _to_backend(X, y, entity, backend: str):
if backend == "cupy":
import cupy as cp

return (
cp.asarray(X),
cp.asarray(y),
cp.asarray(entity, dtype=cp.int64),
)
if backend == "torch":
import torch

return (
torch.as_tensor(X, dtype=torch.float64, device="cuda"),
torch.as_tensor(y, dtype=torch.float64, device="cuda"),
torch.as_tensor(entity, dtype=torch.int64, device="cuda"),
)
raise ValueError(backend)


def _device_arg(backend: str):
return {"cupy": "cuda", "torch": "torch"}[backend]


def _dataset(n: int, k: int, seed: int):
rng = np.random.default_rng(seed)
X = rng.normal(size=(n, k)).astype(np.float64)
beta = np.linspace(0.2, 0.8, k, dtype=np.float64)
entity = np.arange(n, dtype=np.int64) // 20
n_entities = int(entity.max()) + 1
alpha = np.linspace(-0.5, 0.5, n_entities, dtype=np.float64)[entity]
y = X @ beta + alpha + rng.normal(scale=0.2, size=n)
return X, y.astype(np.float64), entity


def _fit(model_name: str, X, y, entity, backend: str):
device = _device_arg(backend)
if model_name == "PanelOLS":
model = PanelOLS(entity_effects=True, cov_type="nonrobust", device=device)
elif model_name == "RandomEffects":
model = RandomEffects(device=device)
else:
raise ValueError(model_name)
model.fit(X, y, entity_ids=entity)
return model


def _timed_fit(model_name, X, y, entity, backend, *, disable_digest: bool):
original = diagnostics._full_content_digest
if disable_digest:
diagnostics._full_content_digest = lambda _X, _y: "0" * 64
try:
_sync(backend)
start = time.perf_counter()
_fit(model_name, X, y, entity, backend)
_sync(backend)
return time.perf_counter() - start
finally:
diagnostics._full_content_digest = original


def _median(values):
return float(np.median(np.asarray(values, dtype=np.float64)))


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--expected-sha", required=True)
parser.add_argument("--backends", default="cupy,torch")
parser.add_argument(
"--scales",
default="10000x2,100000x2,100000x10,500000x2",
help="comma-separated NxK pairs",
)
parser.add_argument("--repeats", type=int, default=3)
args = parser.parse_args()

sha = _git_sha()
if sha != args.expected_sha:
raise RuntimeError(f"wrong source head: {sha} != {args.expected_sha}")
dirty = _git_status_porcelain()
if dirty.strip():
raise RuntimeError(
"identity benchmark requires a clean working tree; uncommitted changes:\n"
+ dirty
)
if args.repeats < 1:
raise ValueError("--repeats must be positive")

backends = [x.strip() for x in args.backends.split(",") if x.strip()]
if not backends or any(x not in {"cupy", "torch"} for x in backends):
raise ValueError("--backends must contain cupy and/or torch")
scales = _parse_scales(args.scales)

rows = []
for scale_index, (n, k) in enumerate(scales):
X_np, y_np, entity_np = _dataset(n, k, seed=20260808 + scale_index)
for backend in backends:
X, y, entity = _to_backend(X_np, y_np, entity_np, backend)
for model_name in ("PanelOLS", "RandomEffects"):
# Warm both paths before measurement to avoid one-time import/
# allocator effects being attributed to the digest.
_timed_fit(
model_name, X, y, entity, backend, disable_digest=False
)
_timed_fit(
model_name, X, y, entity, backend, disable_digest=True
)

with_digest = []
without_digest = []
for _ in range(args.repeats):
with_digest.append(
_timed_fit(
model_name,
X,
y,
entity,
backend,
disable_digest=False,
)
)
without_digest.append(
_timed_fit(
model_name,
X,
y,
entity,
backend,
disable_digest=True,
)
)

normal = _median(with_digest)
baseline = _median(without_digest)
overhead = normal - baseline
ratio = normal / baseline if baseline > 0.0 else None
rows.append(
{
"backend": backend,
"model": model_name,
"n_samples": n,
"n_features": k,
"repeats": args.repeats,
"with_digest_seconds": normal,
"without_digest_seconds": baseline,
"digest_overhead_seconds": overhead,
"with_over_without_ratio": ratio,
"with_digest_samples": with_digest,
"without_digest_samples": without_digest,
}
)

payload = {
"schema_version": 1,
"git_sha": sha,
"working_tree_clean": True,
"benchmark": "panel_stage_b_full_content_identity_overhead",
"timing_scope": "end-to-end estimator fit with vs without only the SHA-256 full-content digest",
"target_scale_source": "PR122 fresh-review performance finding",
"environment": {
"python": platform.python_version(),
"platform": platform.platform(),
"packages": {
name: _version(name)
for name in ("statgpu", "numpy", "cupy", "torch")
},
},
"rows": rows,
}
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
print(json.dumps(payload, indent=2))
print(f"PASS — identity-overhead benchmark recorded: {args.out}")


if __name__ == "__main__":
main()
Loading
Loading