Opens a larger view. Escape closes it.

hardware-counters

holes.py

"""
Diagnose the specific weaknesses of the MLP setup, beyond "not enough data".

Candidate holes:
  H1  target scale     - is log10(eta) well-conditioned for a NN?
  H2  feature skew     - heavy-tailed ratios hurt gradient descent badly
  H3  imputation       - median-fill on structurally-missing counters injects
                         values that are wrong in a systematic, per-app way
  H4  fold imbalance   - each LOAO fold has only 32 train samples
  H5  no early stopping / no validation split inside the fold
"""
import sys, warnings
import numpy as np, pandas as pd
warnings.filterwarnings("ignore")

D = "/work/project/project/user"
sys.path.insert(0, f"{D}/analysis")
from train_model import build_features
F_PEAK = 2.25e9

df = pd.read_csv(f"{D}/data/runs.csv")
df = df[(df.app != "cp2k") & df.runtime_s.notna()].reset_index(drop=True)
wc = [c for c in ["wall_B","wall_C","wall_D","wall_E"] if c in df]
y = df[wc].median(axis=1).fillna(df.runtime_s).values
cyc = pd.to_numeric(df.PAPI_TOT_CYC, errors="coerce").values
eta = (cyc / F_PEAK) / y
X = build_features(df)

print("H1  TARGET  log10(eta)")
t = np.log10(eta)
print(f"    range {t.min():+.3f} to {t.max():+.3f}, sd {t.std():.3f}")
print(f"    -> spread is only {t.max()-t.min():.2f} decades; a NN initialised")
print("       near zero output already sits close to the mean, so gradients")
print("       are small and it takes heavy training to beat a constant.\n")

print("H2  FEATURE SKEW (|skew| > 2 is problematic for gradient descent)")
sk = X.skew(numeric_only=True).abs().sort_values(ascending=False)
for k, v in sk.head(8).items():
    rng = X[k].max() / max(X[k].min(), 1e-12)
    print(f"    {k:22s} skew {v:6.2f}   max/min = {rng:.3g}")
print(f"    {(sk>2).sum()} of {len(sk)} features are heavily skewed.")
print("    -> StandardScaler centres these but does NOT fix the tail; a few")
print("       configs dominate the loss. log1p first, then scale.\n")

print("H3  MISSINGNESS is structural, not random")
m = X.isna().sum()
print(f"    {int(m.sum())} missing cells over {X.shape[0]}x{X.shape[1]}")
per_app = X.assign(app=df.app).groupby("app").apply(lambda g: g.isna().sum().sum())
print("    missing cells per application:")
for k, v in per_app.items():
    print(f"      {k:10s} {v}")
print("    -> median imputation fills with the OTHER apps' median, which is")
print("       systematically wrong for the held-out app. Add missingness")
print("       indicator columns so the model can tell 'absent' from 'typical'.\n")

print("H4  FOLD SIZE")
for a in sorted(df.app.unique()):
    print(f"    hold out {a:10s}: train n={int((df.app!=a).sum())}, test n={int((df.app==a).sum())}")
print("    -> 32 training rows. Any net with >150 params is over-parameterised.\n")

print("H5  TRAINING PROTOCOL")
print("    current: solver=adam, max_iter=20000, early_stopping=False")
print("    -> no validation split, so training runs to convergence on 32")
print("       samples every time. early_stopping needs a val split that is")
print("       itself tiny; better to use lbfgs (deterministic, suits small n)")
print("       and rely on L2 + ensembling over seeds.")