Module neuralops.analysis
Analysis tools: plotting, metrics, layerwise analysis, model comparison, reporting.
Sub-modules
neuralops.analysis.comparison-
Comparison utilities for analysis output.
neuralops.analysis.generate_all_modules_analysis-
Comprehensive 217-Module Analysis Generator. Extracts and validates all 25 required columns for paper/_ALL_MODULES_ANALYSIS.csv and …
neuralops.analysis.generate_sweep_publication_artifacts-
Publication-Grade Sweep Analysis, Reporting, and Visualization Engine …
neuralops.analysis.layerwise_analyzer-
Streaming Layer-wise Analysis Engine for Foundation Models & Transformers …
neuralops.analysis.metrics-
Metrics computation, aggregation, and tracking.
neuralops.analysis.model_comparison-
Multi‑run model comparison and plotting.
neuralops.analysis.plotting-
Comprehensive plotting functions for model analysis.
neuralops.analysis.report_generator-
HTML, LaTeX, interactive report generators.
neuralops.analysis.utils-
Utility functions for analysis.
Functions
def aggregate_results(results_list: list[dict[str, typing.Any]]) ‑> dict[str, typing.Any]-
Expand source code
def aggregate_results(results_list: list[dict[str, Any]]) -> dict[str, Any]: """Aggregate results.""" if not results_list: return {} aggregated = {} for key in results_list[0].keys(): values = [r[key] for r in results_list if key in r and r[key] is not None] if values: aggregated[f"{key}_mean"] = np.mean(values) aggregated[f"{key}_std"] = np.std(values) aggregated[f"{key}_values"] = values return aggregatedAggregate results.
def compute_metrics(predictions: numpy.ndarray, labels: numpy.ndarray, task: str = 'classification') ‑> dict[str, float]-
Expand source code
def compute_metrics( predictions: np.ndarray, labels: np.ndarray, task: str = "classification" ) -> dict[str, float]: """Compute and return metrics.""" from scipy.stats import pearsonr, spearmanr from sklearn.metrics import accuracy_score, f1_score, matthews_corrcoef metrics = {} if task in ("regression", "stsb"): pred_flat = np.squeeze(predictions) labels_flat = np.squeeze(labels) metrics["pearson"] = pearsonr(pred_flat, labels_flat)[0] metrics["spearman"] = spearmanr(pred_flat, labels_flat)[0] metrics["pearson_spearman"] = (metrics["pearson"] + metrics["spearman"]) / 2 else: preds = np.argmax(predictions, axis=-1) if predictions.ndim > 1 else predictions metrics["accuracy"] = accuracy_score(labels, preds) metrics["f1_macro"] = f1_score(labels, preds, average="macro", zero_division=0) metrics["f1_weighted"] = f1_score(labels, preds, average="weighted", zero_division=0) if len(np.unique(labels)) == 2: metrics["matthews_corrcoef"] = matthews_corrcoef(labels, preds) return metricsCompute and return metrics.
def correlation_matrix(df: pandas.DataFrame, columns: list[str]) ‑> pandas.DataFrame-
Expand source code
def correlation_matrix(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame: """Correlation matrix.""" return df[columns].corr()Correlation matrix.
def create_comprehensive_plots(df: pandas.DataFrame, output_dir: str, figsize: tuple[int, int] = (18, 16)) ‑> None-
Expand source code
def create_comprehensive_plots( df: pd.DataFrame, output_dir: str, figsize: tuple[int, int] = (18, 16) ) -> None: """Build and return comprehensive plots.""" os.makedirs(output_dir, exist_ok=True) set_style() fig, axes = plt.subplots(3, 3, figsize=figsize) fig.suptitle( "SVD Compression Analysis: Comprehensive Results", fontsize=16, fontweight="bold", ) # 1. Accuracy vs Rank by model ax = axes[0, 0] for model in df["model"].unique(): sub = df[df["model"] == model].groupby("rank")["accuracy"].mean().reset_index() ax.plot(sub["rank"], sub["accuracy"], marker="o", label=model, linewidth=2) ax.set_title("Accuracy vs Rank (by Model)") ax.legend() ax.grid(True, alpha=0.3) # 2. Accuracy vs Rank by task ax = axes[0, 1] for task in df["task"].unique(): sub = df[df["task"] == task].groupby("rank")["accuracy"].mean().reset_index() ax.plot(sub["rank"], sub["accuracy"], marker="s", label=task, linewidth=2) ax.set_title("Accuracy vs Rank (by Task)") ax.legend() ax.grid(True, alpha=0.3) # 3. Speedup factor ax = axes[0, 2] if "speedup_factor" in df.columns: for model in df["model"].unique(): sub = df[df["model"] == model].groupby("rank")["speedup_factor"].mean() ax.plot(sub.index, sub.values, marker="^", label=model, linewidth=2) ax.set_title("Speedup vs Rank") ax.legend() ax.grid(True, alpha=0.3) else: ax.text( 0.5, 0.5, "No speedup data", ha="center", va="center", transform=ax.transAxes, ) # 4. Parameter reduction ax = axes[1, 0] if "param_reduction" in df.columns: pd_data = df.groupby(["model", "rank"])["param_reduction"].mean().reset_index() models = pd_data["model"].unique() width = 0.8 / len(models) for i, m in enumerate(models): sub = pd_data[pd_data["model"] == m] x = sub["rank"] + (i - len(models) / 2) * width ax.bar(x, sub["param_reduction"], width=width, label=m) ax.set_title("Parameter Reduction") ax.legend() ax.grid(True, axis="y", alpha=0.3) else: ax.text( 0.5, 0.5, "No param reduction", ha="center", va="center", transform=ax.transAxes, ) # 5. Accuracy drop distribution ax = axes[1, 1] if "accuracy_drop" in df.columns: drops = df["accuracy_drop"].dropna() ax.hist(drops, bins=20, color="lightcoral", edgecolor="darkred", alpha=0.7) ax.axvline(drops.mean(), color="blue", linestyle="-", label=f"Mean: {drops.mean():.3f}") ax.set_title("Accuracy Drop Distribution") ax.legend() ax.grid(True, axis="y", alpha=0.3) else: ax.text( 0.5, 0.5, "No accuracy drop", ha="center", va="center", transform=ax.transAxes, ) # 6. Memory reduction ax = axes[1, 2] if "memory_reduction" in df.columns: mem = df.groupby("rank")["memory_reduction"].mean().reset_index() ax.bar( mem["rank"], mem["memory_reduction"], color="lightgreen", edgecolor="darkgreen", ) ax.set_title("Avg Memory Reduction") ax.grid(True, axis="y", alpha=0.3) else: ax.text( 0.5, 0.5, "No memory reduction", ha="center", va="center", transform=ax.transAxes, ) # 7. Accuracy by task type ax = axes[2, 0] if "task_type" in df.columns: for tt in df["task_type"].unique(): sub = df[df["task_type"] == tt].groupby("rank")["accuracy"].mean().reset_index() ax.plot(sub["rank"], sub["accuracy"], marker="D", label=tt, linewidth=2) ax.set_title("Accuracy by Task Type") ax.legend() ax.grid(True, alpha=0.3) else: ax.text(0.5, 0.5, "No task type", ha="center", va="center", transform=ax.transAxes) # 8. Compression ratio heatmap ax = axes[2, 1] if "compression_ratio" in df.columns: pivot = df.pivot_table( index="model", columns="rank", values="compression_ratio", aggfunc="mean" ) im = ax.imshow(pivot.values, cmap="YlOrRd", aspect="auto") ax.set_xticks(range(len(pivot.columns))) ax.set_xticklabels(pivot.columns) ax.set_yticks(range(len(pivot.index))) ax.set_yticklabels(pivot.index) plt.colorbar(im, ax=ax, label="Compression Ratio") ax.set_title("Compression Ratio Heatmap") else: ax.text(0.5, 0.5, "No comp ratio", ha="center", va="center", transform=ax.transAxes) # 9. Best config per dataset ax = axes[2, 2] best = [] for task in df["task"].unique(): sub = df[df["task"] == task] if "accuracy" in sub.columns: idx = sub["accuracy"].idxmax() best.append(sub.loc[idx]) if best: best_df = pd.DataFrame(best) ax.barh(best_df["task"], best_df["accuracy"], color="skyblue", edgecolor="navy") ax.set_title("Best Configuration per Dataset") for i, (acc, model, rank) in enumerate( zip(best_df["accuracy"], best_df["model"], best_df["rank"]) ): ax.text(acc + 0.005, i, f"{model} r={int(rank)}", va="center", fontsize=8) else: ax.text(0.5, 0.5, "No best config", ha="center", va="center", transform=ax.transAxes) plt.tight_layout() plt.savefig(Path(output_dir) / "comprehensive_analysis.png", dpi=300, bbox_inches="tight") plt.close()Build and return comprehensive plots.
def detect_least_compressible_layers(model, tokenizer, calibration_texts: list[str], k: int = 5) ‑> list[int]-
Expand source code
def detect_least_compressible_layers( model, tokenizer, calibration_texts: list[str], k: int = 5 ) -> list[int]: """Detect least compressible layers.""" from .layerwise_analyzer import LayerwiseAnalyzer analyzer = LayerwiseAnalyzer(model, tokenizer) result = analyzer.analyze(calibration_texts) changes = result["cosine_similarities"] most_change_scores = [1 - sim for sim in changes] top_indices = np.argsort(most_change_scores)[-k:] return sorted(top_indices)Detect least compressible layers.
def estimate_intrinsic_dimension(hidden_states: np.ndarray, k: int = 5) ‑> float-
Expand source code
def estimate_intrinsic_dimension(hidden_states: np.ndarray, k: int = 5) -> float: """ Estimates the intrinsic dimension of a representation manifold using k-NN distances. Args: hidden_states: Sample representation array of shape [N, D]. k: Neighbor rank for distance estimation. Returns: float: Estimated intrinsic dimension between 1.0 and D. """ hidden_states = np.asarray(hidden_states, dtype=np.float64) if hidden_states.ndim == 1: return 1.0 N, D = hidden_states.shape if k + 1 > N: return float(min(N, D)) from scipy.spatial.distance import pdist, squareform try: dists = squareform(pdist(hidden_states)) np.fill_diagonal(dists, np.inf) kth_dists = np.sort(dists, axis=1)[:, k - 1] valid = kth_dists > 1e-10 if not valid.any(): return float(D) # Maximum likelihood estimation of intrinsic dimension log_ratios = np.log(kth_dists[valid] / (kth_dists[valid][:, None] + 1e-12)) id_estimate = -1.0 / (np.mean(log_ratios) + 1e-12) return float(max(1.0, min(float(D), abs(id_estimate)))) except Exception: return float(min(N, D))Estimates the intrinsic dimension of a representation manifold using k-NN distances.
- Args
- -----=
hidden_states- Sample representation array of shape [N, D].
k- Neighbor rank for distance estimation.
- Returns
- -----=
float- Estimated intrinsic dimension between 1.0 and D.
def filter_results(df: pandas.DataFrame,
models: list[str] | None = None,
tasks: list[str] | None = None,
ranks: list[int] | None = None,
min_accuracy: float | None = None) ‑> pandas.DataFrame-
Expand source code
def filter_results( df: pd.DataFrame, models: list[str] | None = None, tasks: list[str] | None = None, ranks: list[int] | None = None, min_accuracy: float | None = None, ) -> pd.DataFrame: """Filter results.""" result = df.copy() if models: result = result[result["model"].isin(models)] if tasks: result = result[result["task"].isin(tasks)] if ranks: result = result[result["rank"].isin(ranks)] if min_accuracy is not None and "accuracy" in result.columns: result = result[result["accuracy"] >= min_accuracy] return resultFilter results.
def generate_full_report(df: pandas.DataFrame, output_dir: str, experiment_name: str = 'experiment') ‑> str-
Expand source code
def generate_full_report( df: pd.DataFrame, output_dir: str, experiment_name: str = "experiment" ) -> str: """Generate full report.""" os.makedirs(output_dir, exist_ok=True) plots_dir = os.path.join(output_dir, "plots") from .plotting import create_comprehensive_plots, plot_tradeoff create_comprehensive_plots(df, plots_dir) plot_tradeoff(df, plots_dir) generate_html_report(df, output_dir, title=f"{experiment_name} Full Report") csv_path = os.path.join(output_dir, "summary.csv") df.to_csv(csv_path, index=False) generate_latex_table(df, os.path.join(output_dir, "results.tex")) return os.path.join(output_dir, "report.html")Generate full report.
def generate_html_report(df: pandas.DataFrame, output_dir: str, title: str = 'Analysis Report') ‑> str-
Expand source code
def generate_html_report(df: pd.DataFrame, output_dir: str, title: str = "Analysis Report") -> str: """Generate html report.""" os.makedirs(output_dir, exist_ok=True) html = f"""<!DOCTYPE html> <html><head><meta charset='utf-8'><title>{title}</title> <style>body{{font-family:Arial;margin:20px}} table{{border-collapse:collapse;width:100%}} th,td{{border:1px solid #ddd;padding:8px}} th{{background:#f2f2f2}}</style> </head><body> <h1>{title}</h1><p>Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p> <h2>Summary Table</h2>{df.to_html(index=False)} </body></html>""" out = os.path.join(output_dir, "report.html") with open(out, "w") as f: f.write(html) return outGenerate html report.
def generate_interactive_report(df: pandas.DataFrame, output_dir: str) ‑> str-
Expand source code
def generate_interactive_report(df: pd.DataFrame, output_dir: str) -> str: """Generate interactive report.""" os.makedirs(output_dir, exist_ok=True) import plotly.graph_objects as go fig1 = go.Figure() for task in df["task"].unique(): sub = df[df["task"] == task] fig1.add_trace( go.Scatter(x=sub["rank"], y=sub["accuracy"], mode="lines+markers", name=task) ) fig1.update_layout(title="Accuracy vs Rank", xaxis_title="Rank", yaxis_title="Accuracy") fig2 = go.Figure() for model in df["model"].unique(): sub = df[df["model"] == model] fig2.add_trace( go.Scatter(x=sub["rank"], y=sub["accuracy"], mode="lines+markers", name=model) ) fig2.update_layout(title="Model Comparison", xaxis_title="Rank", yaxis_title="Accuracy") html = f"""<!DOCTYPE html> <html><head><script src="https://cdn.plot.ly/plotly-latest.min.js"></script></head> <body><h1>Interactive Report</h1> <div id="rankPlot"></div><div id="modelPlot"></div> <script> Plotly.newPlot('rankPlot', {json.dumps(fig1.to_dict())}); Plotly.newPlot('modelPlot', {json.dumps(fig2.to_dict())}); </script></body></html>""" path = os.path.join(output_dir, "interactive_report.html") with open(path, "w") as f: f.write(html) return pathGenerate interactive report.
def generate_latex_table(df: pandas.DataFrame, output_path: str, caption: str = 'Results') ‑> str-
Expand source code
def generate_latex_table(df: pd.DataFrame, output_path: str, caption: str = "Results") -> str: """Generate latex table.""" lines = [ r"\begin{table}[htbp]\centering", r"\caption{" + caption + "}", r"\begin{tabular}{lcccr}", r"\toprule", ] lines.append("Model & Task & Rank & Accuracy & Drop \\\\") lines.append(r"\midrule") for _, row in df.iterrows(): acc = row.get("accuracy", 0) drop = row.get("accuracy_drop", "-") drop_str = f"{drop:.4f}" if isinstance(drop, (int, float)) else "-" lines.append( f"{row['model']} & {row['task']} & {row['rank']} & {acc:.4f} & {drop_str} \\\\" ) lines.extend([r"\bottomrule", r"\end{tabular}", r"\end{table}"]) with open(output_path, "w") as f: f.write("\n".join(lines)) return output_pathGenerate latex table.
def get_best_configurations(df: pandas.DataFrame, metric: str = 'accuracy') ‑> pandas.DataFrame-
Expand source code
def get_best_configurations(df: pd.DataFrame, metric: str = "accuracy") -> pd.DataFrame: """Return the best configurations.""" best_rows = [] for (task, model), group in df.groupby(["task", "model"]): if metric in group.columns: idx = group[metric].idxmax() best_rows.append(group.loc[idx]) return pd.DataFrame(best_rows)Return the best configurations.
def plot_accuracy_curve(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_accuracy_curve(history, metrics, config, output_dir, opts) -> Any: """Plot accuracy curve.""" steps = [h.get("step", i) for i, h in enumerate(history) if "eval_accuracy" in h] acc = [h["eval_accuracy"] for h in history if "eval_accuracy" in h] if not steps: return [] fig, ax = plt.subplots(figsize=opts.get("figsize", [10, 6])) ax.plot(steps, acc, "o-", color="#5DC963", markersize=4) ax.set_xlabel("Step") ax.set_ylabel("Accuracy") ax.set_title("Validation Accuracy") ax.grid(True, alpha=0.3) plt.tight_layout() path = Path(output_dir) / "accuracy_curve.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot accuracy curve.
def plot_attention_heatmap(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_attention_heatmap(history, metrics, config, output_dir, opts) -> Any: """Plot attention heatmap.""" attn_path = Path(output_dir).parent / "artifacts" / "attention_sample.json" if not attn_path.exists(): return [] with open(attn_path) as f: attn_data = json.load(f) matrix = np.array(attn_data.get("matrix", [])) tokens = attn_data.get("tokens", None) layer = attn_data.get("layer", 0) head = attn_data.get("head", 0) # Simple heatmap fig, ax = plt.subplots(figsize=(10, 8)) sns.heatmap(matrix, cmap="viridis", xticklabels=tokens, yticklabels=tokens, ax=ax) ax.set_title(f"Attention Heatmap – Layer {layer} Head {head}") path = Path(output_dir) / "attention_heatmap.png" fig.savefig(path, dpi=150, bbox_inches="tight") plt.close() return [str(path)]Plot attention heatmap.
def plot_confusion_matrix(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_confusion_matrix(history, metrics, config, output_dir, opts) -> Any: """Plot confusion matrix.""" preds_path = Path(output_dir).parent / "artifacts" / "eval_predictions.json" if not preds_path.exists(): return [] with open(preds_path) as f: data = json.load(f) labels = data.get("labels", []) preds = data.get("predictions", []) if not labels: return [] from sklearn.metrics import confusion_matrix cm = confusion_matrix(labels, preds) class_names = opts.get("class_names", None) fig, ax = plt.subplots(figsize=opts.get("figsize", [10, 8])) sns.heatmap( cm, annot=True, fmt="d", cmap="Blues", xticklabels=class_names, yticklabels=class_names, ax=ax, ) ax.set_xlabel("Predicted") ax.set_ylabel("True") ax.set_title("Confusion Matrix") path = Path(output_dir) / "confusion_matrix.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot confusion matrix.
def plot_gpu_memory(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_gpu_memory(history, metrics, config, output_dir, opts) -> Any: """Plot gpu memory.""" gpu_steps = [h.get("step", i) for i, h in enumerate(history) if "gpu_mem" in h] gpu = [h["gpu_mem"] for h in history if "gpu_mem" in h] cpu_steps = [h.get("step", i) for i, h in enumerate(history) if "cpu_mem" in h] cpu = [h["cpu_mem"] for h in history if "cpu_mem" in h] if not gpu_steps and not cpu_steps: return [] fig, ax = plt.subplots(figsize=opts.get("figsize", [10, 4])) if gpu_steps: ax.plot(gpu_steps, gpu, label="GPU Mem (MB)", color="#3B528B") if cpu_steps: ax.plot(cpu_steps, cpu, label="CPU Mem (MB)", color="#5DC963") ax.set_xlabel("Step") ax.set_ylabel("Memory (MB)") ax.set_title("Memory Usage") ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() path = Path(output_dir) / "gpu_memory.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot gpu memory.
def plot_gradient_flow(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_training_time(history, metrics, config, output_dir, opts) -> Any: """Plot training time.""" times = [ h.get("elapsed", i * 0.01) for i, h in enumerate(history) if "elapsed" in h or "step" in h ] if not times: return [] fig, ax = plt.subplots(figsize=opts.get("figsize", [10, 4])) ax.plot(range(len(times)), times, color="#440154") ax.set_xlabel("Step") ax.set_ylabel("Time (s)") ax.set_title("Training Time vs Steps") ax.grid(True, alpha=0.3) plt.tight_layout() path = Path(output_dir) / "training_time.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot training time.
def plot_loss_curve(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_loss_curve(history, metrics, config, output_dir, opts) -> Any: """Plot loss curve.""" steps = [h.get("step", i) for i, h in enumerate(history) if "loss" in h] train = [h["loss"] for h in history if "loss" in h] eval_steps = [h.get("step", i) for i, h in enumerate(history) if "eval_loss" in h] eval_l = [h["eval_loss"] for h in history if "eval_loss" in h] smooth = opts.get("loss_smooth", 0.6) if smooth > 0 and len(train) > 5: window = max(1, int(len(train) * smooth / 10)) train = uniform_filter1d(np.array(train), size=window) fig, ax = plt.subplots(figsize=opts.get("figsize", [10, 6])) ax.plot(steps, train, label="Train Loss", color="#3B528B") if eval_steps: ax.plot(eval_steps, eval_l, "o-", label="Eval Loss", color="#21908C", markersize=4) ax.set_xlabel("Step") ax.set_ylabel("Loss") ax.set_title("Training & Validation Loss") ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() path = Path(output_dir) / "loss_curve.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot loss curve.
def plot_lr_schedule(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_lr_schedule(history, metrics, config, output_dir, opts) -> Any: """Plot lr schedule.""" steps = [h.get("step", i) for i, h in enumerate(history) if "learning_rate" in h] lr = [h["learning_rate"] for h in history if "learning_rate" in h] if not steps: return [] fig, ax = plt.subplots(figsize=opts.get("figsize", [10, 4])) ax.plot(steps, lr, color="#FDE725") ax.set_xlabel("Step") ax.set_ylabel("LR") ax.set_title("Learning Rate Schedule") ax.grid(True, alpha=0.3) plt.tight_layout() path = Path(output_dir) / "lr_schedule.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot lr schedule.
def plot_parameter_distribution(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_parameter_distribution(history, metrics, config, output_dir, opts) -> Any: """Plot parameter distribution.""" param_path = Path(output_dir).parent / "artifacts" / "param_counts.json" if not param_path.exists(): return [] with open(param_path) as f: param_data = json.load(f) layers = list(param_data.keys()) counts = list(param_data.values()) fig, ax = plt.subplots(figsize=opts.get("figsize", [8, 6])) ax.barh(layers, counts, color="#21908C") ax.set_xlabel("Parameter Count") ax.set_title("Parameter Distribution per Layer") plt.tight_layout() path = Path(output_dir) / "parameter_count.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot parameter distribution.
def plot_pareto_front(df, output_dir, x_metric='speedup_factor', y_metric='accuracy') ‑> None-
Expand source code
def plot_pareto_front(df, output_dir, x_metric="speedup_factor", y_metric="accuracy") -> None: """Plot pareto front.""" set_style() points = df[[x_metric, y_metric, "model", "rank"]].dropna().values pareto = [] for i, p in enumerate(points): dominated = False for q in points: if q[0] >= p[0] and q[1] >= p[1] and (q[0] > p[0] or q[1] > p[1]): dominated = True break if not dominated: pareto.append(p) pareto_df = pd.DataFrame(pareto, columns=[x_metric, y_metric, "model", "rank"]) fig, ax = plt.subplots(figsize=(10, 8)) for model in df["model"].unique(): sub = df[df["model"] == model] ax.scatter(sub[x_metric], sub[y_metric], label=model, alpha=0.5) ax.scatter( pareto_df[x_metric], pareto_df[y_metric], color="red", s=150, marker="*", label="Pareto front", ) ax.set_xlabel(x_metric.replace("_", " ").capitalize()) ax.set_ylabel(y_metric.capitalize()) ax.set_title("Pareto Frontier of Configurations") ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.savefig(Path(output_dir) / "pareto_front.png", dpi=150) plt.close()Plot pareto front.
def plot_radar_chart(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_radar_chart(history, metrics, config, output_dir, opts) -> Any: """Plot radar chart.""" if not metrics: return [] numeric = {k: v for k, v in metrics.items() if isinstance(v, (int, float))} if len(numeric) < 3: return [] categories = list(numeric.keys()) values = list(numeric.values()) angles = np.linspace(0, 2 * np.pi, len(categories), endpoint=False).tolist() values = values + values[:1] angles += angles[:1] fig, ax = plt.subplots(figsize=opts.get("figsize", [8, 8]), subplot_kw=dict(polar=True)) ax.fill(angles, values, alpha=0.25, color="#3B528B") ax.plot(angles, values, color="#3B528B", linewidth=2) ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) ax.set_title("Performance Radar Chart") plt.tight_layout() path = Path(output_dir) / "radar_chart.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot radar chart.
def plot_rank_allocation(recommended_ranks, output_dir) ‑> None-
Expand source code
def plot_rank_allocation(recommended_ranks, output_dir) -> None: """Plot rank allocation.""" set_style() fig, ax = plt.subplots(figsize=(12, 6)) layers = list(range(len(recommended_ranks))) ax.bar(layers, recommended_ranks, color="steelblue", edgecolor="navy") ax.set_xlabel("Layer Index") ax.set_ylabel("Recommended Rank") ax.set_title("Layer‑wise Rank Allocation Recommendation") ax.grid(True, axis="y", alpha=0.3) plt.tight_layout() plt.savefig(Path(output_dir) / "rank_allocation.png", dpi=150) plt.close()Plot rank allocation.
def plot_tradeoff(df, output_dir, metric='accuracy') ‑> None-
Expand source code
def plot_tradeoff(df, output_dir, metric="accuracy") -> None: """Plot tradeoff.""" set_style() fig, ax = plt.subplots(figsize=(10, 8)) for model in df["model"].unique(): sub = df[df["model"] == model].dropna(subset=["speedup_factor", metric]) ax.scatter(sub["speedup_factor"], sub[metric], s=100, label=model, alpha=0.7) ax.set_xlabel("Speedup Factor") ax.set_ylabel(metric.capitalize()) ax.set_title("Accuracy vs Speedup Tradeoff") ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.savefig(Path(output_dir) / "tradeoff.png", dpi=150, bbox_inches="tight") plt.close()Plot tradeoff.
def plot_training_history(history: dict, save_path: str) ‑> None-
Expand source code
def plot_training_history(history: dict, save_path: str) -> None: """Plot training history.""" fig, ax = plt.subplots(figsize=(8, 5)) if history.get("train_steps") and history.get("train_loss"): ax.plot( history["train_steps"], history["train_loss"], label="Train Loss", color="blue", ) if history.get("eval_steps") and history.get("eval_loss"): ax.plot( history["eval_steps"], history["eval_loss"], label="Eval Loss", color="red", marker="o", ) ax.set_xlabel("Steps") ax.set_ylabel("Loss") ax.set_title("Training History") ax.legend() ax.grid(True, alpha=0.3) plt.savefig(save_path, dpi=150, bbox_inches="tight") plt.close()Plot training history.
def plot_training_time(history, metrics, config, output_dir, opts) ‑> Any-
Expand source code
def plot_training_time(history, metrics, config, output_dir, opts) -> Any: """Plot training time.""" times = [ h.get("elapsed", i * 0.01) for i, h in enumerate(history) if "elapsed" in h or "step" in h ] if not times: return [] fig, ax = plt.subplots(figsize=opts.get("figsize", [10, 4])) ax.plot(range(len(times)), times, color="#440154") ax.set_xlabel("Step") ax.set_ylabel("Time (s)") ax.set_title("Training Time vs Steps") ax.grid(True, alpha=0.3) plt.tight_layout() path = Path(output_dir) / "training_time.png" fig.savefig(path, dpi=opts.get("dpi", 150), bbox_inches="tight") plt.close() return [str(path)]Plot training time.
def quick_compare(results_dirs: list[str], output_dir: str = './results/comparison') ‑> None-
Expand source code
def quick_compare(results_dirs: list[str], output_dir: str = "./results/comparison") -> None: """Quick compare.""" comparator = ModelComparison(results_dirs, output_dir) comparator.load_all_results() comparator.generate_all_plots()Quick compare.
def set_style() ‑> None-
Expand source code
def set_style() -> None: """Set the style.""" plt.style.use("seaborn-v0_8-darkgrid") sns.set_palette("husl") sns.set_context("paper", font_scale=1.2)Set the style.
def statistical_tests(df: pandas.DataFrame, group_col: str, value_col: str) ‑> dict[str, typing.Any]-
Expand source code
def statistical_tests(df: pd.DataFrame, group_col: str, value_col: str) -> dict[str, Any]: """Statistical tests.""" from scipy import stats groups = [g[1][value_col].dropna() for g in df.groupby(group_col)] if len(groups) < 2: return {"error": "Need at least two groups"} f_stat, p_anova = stats.f_oneway(*groups) pairwise = {} for i in range(len(groups) - 1): t, p = stats.ttest_ind(groups[i], groups[i + 1]) pairwise[f"{i}_vs_{i + 1}"] = {"t": t, "p": p} return {"anova_p": p_anova, "pairwise": pairwise}Statistical tests.
Classes
class LayerwiseAnalyzer (model: nn.Module, tokenizer: Any, device: str | torch.device | None = None)-
Expand source code
class LayerwiseAnalyzer: """ Streaming Layerwise Analyzer that captures inter-layer representations, outlier distribution spikes, and effective rank without accumulating VRAM memory. """ def __init__( self, model: nn.Module, tokenizer: Any, device: str | torch.device | None = None, ) -> None: self.model = model self.tokenizer = tokenizer self.device = str(device) if device is not None else self._get_device() self.model.to(self.device) self.model.eval() self.layer_accumulators: list[StreamingLayerAccumulator] = [] self.hooks: list[torch.utils.hooks.RemovableHandle] = [] def _get_device(self) -> str: """Detect best available compute device.""" if torch.cuda.is_available(): return "cuda" if hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): return "mps" return "cpu" def _find_transformer_layers(self) -> list[tuple[str, nn.Module]]: """Identify candidate Transformer / Encoder / Decoder block modules.""" target_layers = [] for name, module in self.model.named_modules(): name_lower = name.lower() # Match standard transformer layer patterns if any( pattern in name_lower for pattern in [ "encoder.layer.", "transformer.layer.", "transformer.h.", "model.layers.", "layers.", "h.", "decoder.layer.", "transformer.blocks.", ] ): # Filter to only the top-level block (e.g. layers.0, not layers.0.attention) parts = name.split(".") if parts[-1].isdigit(): target_layers.append((name, module)) # Fallback: if no digit-indexed layers found, look for any layer module if not target_layers: for name, module in self.model.named_modules(): if "layer" in name.lower() and isinstance(module, (nn.ModuleList, nn.Sequential)): for sub_idx, sub_mod in enumerate(module): target_layers.append((f"{name}.{sub_idx}", sub_mod)) break return target_layers def register_streaming_hooks(self) -> None: """Registers streaming forward hooks on all model transformer layers.""" self.remove_hooks() layers = self._find_transformer_layers() num_layers = len(layers) self.layer_accumulators = [StreamingLayerAccumulator() for _ in range(num_layers)] for layer_idx, (layer_name, module) in enumerate(layers): def make_hook(idx: int): def hook_fn(mod: nn.Module, inputs: Any, output: Any) -> None: # Extract tensor tensor = output[0] if isinstance(output, tuple) else output if isinstance(tensor, torch.Tensor): # FIX: Update streaming accumulator dynamically and release activation immediately self.layer_accumulators[idx].update(tensor) # Explicitly release references del tensor return hook_fn hook_handle = module.register_forward_hook(make_hook(layer_idx)) self.hooks.append(hook_handle) def remove_hooks(self) -> None: """Remove all active hooks and free memory.""" for hook in self.hooks: hook.remove() self.hooks.clear() if torch.cuda.is_available(): torch.cuda.empty_cache() gc.collect() def analyze(self, texts: list[str], batch_size: int = 8) -> dict[str, Any]: """ Executes streaming layerwise analysis on input text corpus. Args: texts: List of input string prompts/sentences. batch_size: Batch size for tokenization and model inference. Returns: Dict containing cosine similarities, effective ranks, outlier statistics, and least-compressible layer rankings. """ self.register_streaming_hooks() try: for i in range(0, len(texts), batch_size): batch_texts = texts[i : i + batch_size] encoded = self.tokenizer( batch_texts, return_tensors="pt", truncation=True, max_length=128, padding=True, ) inputs = {k: v.to(self.device) for k, v in encoded.items()} with torch.no_grad(): self.model(**inputs) # Cleanup batch inputs del inputs, encoded if torch.cuda.is_available(): torch.cuda.empty_cache() finally: self.remove_hooks() num_layers = len(self.layer_accumulators) if num_layers == 0: return { "num_layers": 0, "cosine_similarities": [], "effective_ranks": [], "max_outlier_magnitudes": [], "outlier_ratios": [], "kurtosis_scores": [], "intrinsic_dimensions": [], } # Extract per-layer mean vectors mean_vectors = [ ( acc.running_mean_vector if acc.running_mean_vector is not None else np.zeros(128, dtype=np.float32) ) for acc in self.layer_accumulators ] # Compute cosine similarity between adjacent layers similarities = [] for i in range(num_layers - 1): v1, v2 = mean_vectors[i], mean_vectors[i + 1] norm1, norm2 = np.linalg.norm(v1), np.linalg.norm(v2) if norm1 > 1e-8 and norm2 > 1e-8: sim = 1.0 - float(cosine(v1, v2)) sim_clamped = max(0.0, min(1.0, sim)) else: sim_clamped = 1.0 similarities.append(sim_clamped) effective_ranks = [acc.compute_effective_rank() for acc in self.layer_accumulators] max_outliers = [acc.max_activation for acc in self.layer_accumulators] outlier_ratios = [acc.compute_outlier_ratio() for acc in self.layer_accumulators] kurtosis_scores = [acc.compute_kurtosis() for acc in self.layer_accumulators] # Estimate intrinsic dimension using mean vectors np.stack(mean_vectors) if num_layers > 1 else np.zeros((1, 128)) intrinsic_dims = [ estimate_intrinsic_dimension( acc.cov_matrix if acc.cov_matrix is not None else np.eye(10), k=3 ) for acc in self.layer_accumulators ] return { "num_layers": num_layers, "cosine_similarities": similarities, "effective_ranks": effective_ranks, "max_outlier_magnitudes": max_outliers, "outlier_ratios": outlier_ratios, "kurtosis_scores": kurtosis_scores, "intrinsic_dimensions": intrinsic_dims, }Streaming Layerwise Analyzer that captures inter-layer representations, outlier distribution spikes, and effective rank without accumulating VRAM memory.
Methods
def analyze(self, texts: list[str], batch_size: int = 8) ‑> dict[str, typing.Any]-
Expand source code
def analyze(self, texts: list[str], batch_size: int = 8) -> dict[str, Any]: """ Executes streaming layerwise analysis on input text corpus. Args: texts: List of input string prompts/sentences. batch_size: Batch size for tokenization and model inference. Returns: Dict containing cosine similarities, effective ranks, outlier statistics, and least-compressible layer rankings. """ self.register_streaming_hooks() try: for i in range(0, len(texts), batch_size): batch_texts = texts[i : i + batch_size] encoded = self.tokenizer( batch_texts, return_tensors="pt", truncation=True, max_length=128, padding=True, ) inputs = {k: v.to(self.device) for k, v in encoded.items()} with torch.no_grad(): self.model(**inputs) # Cleanup batch inputs del inputs, encoded if torch.cuda.is_available(): torch.cuda.empty_cache() finally: self.remove_hooks() num_layers = len(self.layer_accumulators) if num_layers == 0: return { "num_layers": 0, "cosine_similarities": [], "effective_ranks": [], "max_outlier_magnitudes": [], "outlier_ratios": [], "kurtosis_scores": [], "intrinsic_dimensions": [], } # Extract per-layer mean vectors mean_vectors = [ ( acc.running_mean_vector if acc.running_mean_vector is not None else np.zeros(128, dtype=np.float32) ) for acc in self.layer_accumulators ] # Compute cosine similarity between adjacent layers similarities = [] for i in range(num_layers - 1): v1, v2 = mean_vectors[i], mean_vectors[i + 1] norm1, norm2 = np.linalg.norm(v1), np.linalg.norm(v2) if norm1 > 1e-8 and norm2 > 1e-8: sim = 1.0 - float(cosine(v1, v2)) sim_clamped = max(0.0, min(1.0, sim)) else: sim_clamped = 1.0 similarities.append(sim_clamped) effective_ranks = [acc.compute_effective_rank() for acc in self.layer_accumulators] max_outliers = [acc.max_activation for acc in self.layer_accumulators] outlier_ratios = [acc.compute_outlier_ratio() for acc in self.layer_accumulators] kurtosis_scores = [acc.compute_kurtosis() for acc in self.layer_accumulators] # Estimate intrinsic dimension using mean vectors np.stack(mean_vectors) if num_layers > 1 else np.zeros((1, 128)) intrinsic_dims = [ estimate_intrinsic_dimension( acc.cov_matrix if acc.cov_matrix is not None else np.eye(10), k=3 ) for acc in self.layer_accumulators ] return { "num_layers": num_layers, "cosine_similarities": similarities, "effective_ranks": effective_ranks, "max_outlier_magnitudes": max_outliers, "outlier_ratios": outlier_ratios, "kurtosis_scores": kurtosis_scores, "intrinsic_dimensions": intrinsic_dims, }Executes streaming layerwise analysis on input text corpus.
- Args
- -----=
texts- List of input string prompts/sentences.
batch_size- Batch size for tokenization and model inference.
Returns -----= Dict containing cosine similarities, effective ranks, outlier statistics, and least-compressible layer rankings.
def register_streaming_hooks(self) ‑> None-
Expand source code
def register_streaming_hooks(self) -> None: """Registers streaming forward hooks on all model transformer layers.""" self.remove_hooks() layers = self._find_transformer_layers() num_layers = len(layers) self.layer_accumulators = [StreamingLayerAccumulator() for _ in range(num_layers)] for layer_idx, (layer_name, module) in enumerate(layers): def make_hook(idx: int): def hook_fn(mod: nn.Module, inputs: Any, output: Any) -> None: # Extract tensor tensor = output[0] if isinstance(output, tuple) else output if isinstance(tensor, torch.Tensor): # FIX: Update streaming accumulator dynamically and release activation immediately self.layer_accumulators[idx].update(tensor) # Explicitly release references del tensor return hook_fn hook_handle = module.register_forward_hook(make_hook(layer_idx)) self.hooks.append(hook_handle)Registers streaming forward hooks on all model transformer layers.
def remove_hooks(self) ‑> None-
Expand source code
def remove_hooks(self) -> None: """Remove all active hooks and free memory.""" for hook in self.hooks: hook.remove() self.hooks.clear() if torch.cuda.is_available(): torch.cuda.empty_cache() gc.collect()Remove all active hooks and free memory.
class MetricsTracker (filepath: str)-
Expand source code
class MetricsTracker: """Tracks per‑step metrics and saves as JSON lines.""" def __init__(self, filepath: str) -> None: self.filepath = filepath self.records: list[dict[str, Any]] = [] os.makedirs(os.path.dirname(filepath), exist_ok=True) def log(self, metrics: dict[str, Any], step: int | None = None, flush: bool = False) -> None: """Log.""" entry = {"step": step} if step is not None else {} entry.update(metrics) self.records.append(entry) if flush: self._flush() def _flush(self) -> None: """flush.""" if self.filepath: with open(self.filepath, "a") as f: for rec in self.records: f.write(json.dumps(rec) + "\n") self.records.clear() def save(self) -> None: """Save.""" self._flush() def to_dataframe(self) -> pd.DataFrame: """To dataframe.""" all_records = [] if os.path.exists(self.filepath): with open(self.filepath) as f: for line in f: try: all_records.append(json.loads(line)) except json.JSONDecodeError: pass all_records.extend(self.records) return pd.DataFrame(all_records)Tracks per‑step metrics and saves as JSON lines.
Methods
def log(self,
metrics: dict[str, typing.Any],
step: int | None = None,
flush: bool = False) ‑> None-
Expand source code
def log(self, metrics: dict[str, Any], step: int | None = None, flush: bool = False) -> None: """Log.""" entry = {"step": step} if step is not None else {} entry.update(metrics) self.records.append(entry) if flush: self._flush()Log.
def save(self) ‑> None-
Expand source code
def save(self) -> None: """Save.""" self._flush()Save.
def to_dataframe(self) ‑> pandas.DataFrame-
Expand source code
def to_dataframe(self) -> pd.DataFrame: """To dataframe.""" all_records = [] if os.path.exists(self.filepath): with open(self.filepath) as f: for line in f: try: all_records.append(json.loads(line)) except json.JSONDecodeError: pass all_records.extend(self.records) return pd.DataFrame(all_records)To dataframe.
class ModelComparison (results_dirs: list[str], output_dir: str = './results/comparison')-
Expand source code
class ModelComparison: def __init__(self, results_dirs: list[str], output_dir: str = "./results/comparison") -> None: self.results_dirs = results_dirs self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.df_all = None def load_all_results(self) -> pd.DataFrame: """Load all results from disk or config.""" dfs = [] for d in self.results_dirs: csv_path = os.path.join(d, "performance.csv") if os.path.exists(csv_path): df = pd.read_csv(csv_path) df["run"] = os.path.basename(d) dfs.append(df) if dfs: self.df_all = pd.concat(dfs, ignore_index=True) return self.df_all def generate_all_plots(self) -> None: """Generate all plots.""" if self.df_all is None: self.load_all_results() plots_dir = self.output_dir / "plots" create_comprehensive_plots(self.df_all, str(plots_dir)) plot_tradeoff(self.df_all, str(plots_dir)) print(f"Plots saved to {plots_dir}")Methods
def generate_all_plots(self) ‑> None-
Expand source code
def generate_all_plots(self) -> None: """Generate all plots.""" if self.df_all is None: self.load_all_results() plots_dir = self.output_dir / "plots" create_comprehensive_plots(self.df_all, str(plots_dir)) plot_tradeoff(self.df_all, str(plots_dir)) print(f"Plots saved to {plots_dir}")Generate all plots.
def load_all_results(self) ‑> pandas.DataFrame-
Expand source code
def load_all_results(self) -> pd.DataFrame: """Load all results from disk or config.""" dfs = [] for d in self.results_dirs: csv_path = os.path.join(d, "performance.csv") if os.path.exists(csv_path): df = pd.read_csv(csv_path) df["run"] = os.path.basename(d) dfs.append(df) if dfs: self.df_all = pd.concat(dfs, ignore_index=True) return self.df_allLoad all results from disk or config.