import os, warnings, sys, itertools
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import Ridge
from sklearn.neural_network import MLPRegressor
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, FunctionTransformer
from sklearn.pipeline import Pipeline
from scipy.stats import wilcoxon
warnings.filterwarnings("ignore")
D = os.environ.get("DISS_ROOT", "/work/project/project/user")
OUT = Path(f"{D}/analysis/out"); OUT.mkdir(parents=True, exist_ok=True)
F_PEAK = {"archer2": 2.25e9, "cirrus": 3.71e9}
COUNTERS = ["PAPI_TOT_CYC", "PAPI_TOT_INS", "PAPI_FP_OPS", "PAPI_FP_INS",
"PAPI_L1_DCA", "PAPI_L2_DCH", "PAPI_L2_DCM", "PAPI_L2_DCR",
"PAPI_TLB_DM", "PAPI_BR_INS", "PAPI_BR_MSP",
"PAPI_L1_DCM", "PAPI_L2_TCM", "PAPI_VEC_INS", "PAPI_FMA_INS",
"UNC_L3_CACHE_MISSES", "UNC_L3_MISS_LATENCY",
"PACKAGE_ENERGY", "PP0_ENERGY",
"L2_PREFETCH_HIT_L2", "L2_PREFETCH_HIT_L3",
"REQUESTS_TO_L2_GROUP1:L2_HW_PF", "REQUESTS_TO_L2_GROUP1:RD_BLK_X",
"DISPATCH_RESOURCE_STALL_CYCLES_1:LOAD_QUEUE_RSRC_STALL",
"DISPATCH_RESOURCE_STALL_CYCLES_1:STORE_QUEUE_RSRC_STALL",
"DISPATCH_RESOURCE_STALL_CYCLES_1:FP_REG_FILE_RSRC_STALL",
"DISPATCH_RESOURCE_STALL_CYCLES_1:FP_SCHEDULER_RSRC_STALL"]
NOTE
INTERSECTION = ["log_ncore", "log_nrank", "log_nthread", "log_t_analytic",
"log_instr_per_rank", "ipc", "flops_per_instr",
"flops_per_cycle", "l2_hit_rate", "l2_miss_per_instr",
"l1_access_per_instr", "tlb_miss_per_instr",
"stall_load_frac", "stall_store_frac"]
def load(path, platform):
df = pd.read_csv(path)
if "platform" not in df.columns:
df["platform"] = platform
df = df[df.runtime_s.notna() & (df.runtime_s > 0)]
keys = ["platform", "app", "size", "ncore", "nthread", "nrank"]
agg = {c: "median" for c in COUNTERS if c in df.columns}
agg["runtime_s"] = "median"
m = df.groupby(keys, as_index=False).agg(agg)
return m[m.PAPI_TOT_CYC.notna()].reset_index(drop=True)
def features(df):
X = pd.DataFrame(index=df.index)
g = lambda c: pd.to_numeric(df[c], errors="coerce") if c in df.columns \
else pd.Series(np.nan, index=df.index)
cyc, ins = g("PAPI_TOT_CYC"), g("PAPI_TOT_INS")
fp = g("PAPI_FP_OPS")
l1a = g("PAPI_L1_DCA")
l2h, l2m = g("PAPI_L2_DCH"), g("PAPI_L2_DCM")
tlb = g("PAPI_TLB_DM")
br, brm = g("PAPI_BR_INS"), g("PAPI_BR_MSP")
s_ld = g("DISPATCH_RESOURCE_STALL_CYCLES_1:LOAD_QUEUE_RSRC_STALL")
s_st = g("DISPATCH_RESOURCE_STALL_CYCLES_1:STORE_QUEUE_RSRC_STALL")
fpk = df.platform.map(F_PEAK).values
X["log_ncore"] = np.log2(df.ncore.clip(lower=1))
X["log_nrank"] = np.log2(df.nrank.clip(lower=1))
X["log_nthread"] = np.log2(df.nthread.clip(lower=1))
X["log_t_analytic"] = np.log10((cyc / fpk).clip(lower=1e-6))
X["log_instr_per_rank"] = np.log10((ins / df.nrank.clip(lower=1)).clip(lower=1))
X["ipc"] = ins / cyc
X["flops_per_instr"] = fp / ins
X["flops_per_cycle"] = fp / cyc
X["l2_hit_rate"] = l2h / (l2h + l2m)
X["l2_miss_per_instr"] = l2m / ins
X["l1_access_per_instr"] = l1a / ins
X["tlb_miss_per_instr"] = tlb / ins
X["branch_miss_rate"] = brm / br
X["stall_load_frac"] = s_ld / cyc
X["stall_store_frac"] = s_st / cyc
X["l3_miss_per_instr"] = g("UNC_L3_CACHE_MISSES") / ins
X["l3_lat_per_miss"] = g("UNC_L3_MISS_LATENCY") / g("UNC_L3_CACHE_MISSES")
X["energy_per_instr"] = g("PACKAGE_ENERGY") / ins
X["core_energy_frac"] = g("PP0_ENERGY") / g("PACKAGE_ENERGY")
X["l1_miss_per_instr"] = g("PAPI_L1_DCM") / ins
X["vec_per_instr"] = g("PAPI_VEC_INS") / ins
X["fma_per_instr"] = g("PAPI_FMA_INS") / ins
return X.replace([np.inf, -np.inf], np.nan)
def slog(A):
return np.sign(A) * np.log1p(np.abs(A))
def mk(kind, seed=0):
pre = [("i", SimpleImputer(strategy="median")),
("l", FunctionTransformer(slog, validate=False)),
("s", StandardScaler())]
est = {"rf": RandomForestRegressor(n_estimators=400, min_samples_leaf=2,
random_state=seed, n_jobs=-1),
"gbq": GradientBoostingRegressor(loss="quantile", alpha=0.5,
n_estimators=300, max_depth=3,
learning_rate=0.05, random_state=seed),
"ridge": Ridge(alpha=10.0),
"mlp": MLPRegressor(hidden_layer_sizes=(32, 16), alpha=1.0,
solver="lbfgs", max_iter=5000, random_state=seed),
}[kind]
return Pipeline(pre + [("e", est)])
def fac(p, a):
p = np.clip(p, 1e-9, None)
return np.maximum(p / a, a / p)
def run(Xtr, ytr_eta, Xte, t_an_te, y_te, kind, seeds=(0,)):
ps = []
for s in seeds:
m = mk(kind, s); m.fit(Xtr, np.log10(ytr_eta))
ps.append(m.predict(Xte))
return fac(t_an_te / (10 ** np.mean(ps, axis=0)), y_te)
def main():
a2 = load(f"{D}/data/runs_expanded.csv", "archer2")
cir = load(f"{D}/data/cirrus_persets.csv", "cirrus")
both = pd.concat([a2, cir], ignore_index=True)
X = features(both)
y = both.runtime_s.values
fpk = both.platform.map(F_PEAK).values
t_an = pd.to_numeric(both.PAPI_TOT_CYC, errors="coerce").values / fpk
eta = t_an / y
ok = (eta > 0) & (eta <= 1.5)
both, X, y, t_an, eta = (both[ok].reset_index(drop=True), X[ok].reset_index(drop=True),
y[ok], t_an[ok], eta[ok])
isA2 = (both.platform == "archer2").values
L = []
say = lambda s="": (print(s, flush=True), L.append(s))
say("Cross-platform performance prediction: ARCHER2 (Zen 2) vs Cirrus (Zen 5)")
say("=" * 78)
say(f"ARCHER2 {isA2.sum()} configs Cirrus {(~isA2).sum()} configs "
f"total {len(both)}")
say(f"apps: {sorted(both.app.unique())}")
say("")
say("feature availability (% non-null):")
for c in X.columns:
pa = 100 * X.loc[isA2, c].notna().mean()
pc = 100 * X.loc[~isA2, c].notna().mean()
mark = " <-- intersection" if c in INTERSECTION else ""
say(f" {c:22s} ARCHER2 {pa:5.1f}% Cirrus {pc:5.1f}%{mark}")
say("")
FEATSETS = {"intersection": [c for c in INTERSECTION if c in X.columns],
"full": list(X.columns)}
rows = []
def report(setting, featset, kind, err, n):
med, p90 = np.nanmedian(err), np.nanpercentile(err, 90)
rows.append({"setting": setting, "features": featset, "model": kind,
"n_test": n, "median": med, "p90": p90})
say(f" {setting:16s} {featset:13s} {kind:6s} n={n:4d} "
f"median {med:.4f} p90 {p90:.4f}")
for fs, cols in FEATSETS.items():
Xf = X[cols].values
say(f"--- feature set: {fs} ({len(cols)} features) ---")
for plat, mask in [("within-A2", isA2), ("within-CIR", ~isA2)]:
for kind in ("rf", "gbq", "mlp"):
e = np.full(len(y), np.nan)
sub = np.where(mask)[0]
for a in sorted(both.app[mask].unique()):
te = sub[(both.app.values[sub] == a)]
tr = sub[(both.app.values[sub] != a)]
if len(te) == 0 or len(tr) < 10:
continue
e[te] = run(Xf[tr], eta[tr], Xf[te], t_an[te], y[te], kind)
report(plat, fs, kind, e[mask], int(mask.sum()))
e = np.full(len(y), np.nan); sub = np.where(mask)[0]
for a in sorted(both.app[mask].unique()):
te = sub[both.app.values[sub] == a]; tr = sub[both.app.values[sub] != a]
if len(te) == 0 or len(tr) < 10: continue
e[te] = fac(t_an[te] / (10 ** np.median(np.log10(eta[tr]))), y[te])
report(plat, fs, "const", e[mask], int(mask.sum()))
for name, tr_m, te_m in [("A2->CIR", isA2, ~isA2), ("CIR->A2", ~isA2, isA2)]:
for kind in ("rf", "gbq", "mlp", "ridge"):
e = run(Xf[tr_m], eta[tr_m], Xf[te_m], t_an[te_m], y[te_m], kind)
report(name, fs, kind, e, int(te_m.sum()))
e = fac(t_an[te_m] / (10 ** np.median(np.log10(eta[tr_m]))), y[te_m])
report(name, fs, "const", e, int(te_m.sum()))
for name, tr_m, te_m in [("A2->CIR LOAO", isA2, ~isA2),
("CIR->A2 LOAO", ~isA2, isA2)]:
for kind in ("rf", "mlp"):
e = np.full(len(y), np.nan)
for a in sorted(both.app.unique()):
te = np.where(te_m & (both.app == a).values)[0]
tr = np.where(tr_m & (both.app != a).values)[0]
if len(te) == 0 or len(tr) < 10:
continue
e[te] = run(Xf[tr], eta[tr], Xf[te], t_an[te], y[te], kind)
report(name, fs, kind, e[te_m], int(np.isfinite(e[te_m]).sum()))
say("")
res = pd.DataFrame(rows)
res.to_csv(OUT / "crossplatform_results.csv", index=False)
say("=== summary: best model per setting/featureset ===")
best = (res[res.model != "const"].sort_values("median")
.groupby(["setting", "features"]).first().reset_index())
con = res[res.model == "const"].set_index(["setting", "features"])["median"]
for _, r in best.sort_values(["setting", "features"]).iterrows():
c = con.get((r.setting, r.features), np.nan)
say(f" {r.setting:16s} {r.features:13s} best={r.model:5s} "
f"{r['median']:.4f} const={c:.4f} "
f"{'BEATS' if r['median'] < c else 'loses to'} constant")
(OUT / "crossplatform_summary.txt").write_text("\n".join(L) + "\n")
if __name__ == "__main__":
main()