Module neuralops.experiment_utils
Django integration layer – background experiment runner, WebSocket broadcaster, AI assistant, and multi‑run comparison.
Sub-modules
neuralops.experiment_utils.comparison-
Experiment Comparison Engine – side-by-side diff, parallel coordinates, metric delta tables, and run diff for training runs.
neuralops.experiment_utils.configneuralops.experiment_utils.dataneuralops.experiment_utils.main-
Efficient Attention Framework – GLUE Fine‑Tuning & Utility Functions
neuralops.experiment_utils.model-
Module providing model implementations for NeuralOps.
neuralops.experiment_utils.orchestrator-
MegaOrchestrator – single entry point that executes the full experiment pipeline. Uses the core framework for all ML logic.
neuralops.experiment_utils.runner-
Deprecated shim for neuralops.experiment_utils.main …
neuralops.experiment_utils.tasks-
Background training task (Django integration) with WebSocket broadcasting and AI assistant.
neuralops.experiment_utils.train
Functions
def broadcast_terminal(msg: str) ‑> None-
Expand source code
def broadcast_terminal(msg: str) -> None: """Send a log message to the terminal WebSocket group.""" if not HAS_CHANNELS: return try: channel_layer = get_channel_layer() if channel_layer and async_to_sync: async_to_sync(channel_layer.group_send)( "terminal", {"type": "terminal_message", "data": msg} ) except Exception as e: print(f"WS broadcast error: {e}")Send a log message to the terminal WebSocket group.
def compare_runs_api(run_ids: list[str], baseline_id: str | None = None) ‑> dict-
Expand source code
def compare_runs_api(run_ids: list[str], baseline_id: str | None = None) -> dict: """API-friendly wrapper.""" comparator = ExperimentComparator(run_ids, baseline_id) result = comparator.compare() return { "run_ids": result.run_ids, "metrics_table": result.metrics_table, "config_diff": result.config_diff, "hyperparameter_table": result.hyperparameter_table, "metric_deltas": result.metric_deltas, "timeline_data": result.timeline_data, "parallel_coords": result.parallel_coords, "best_run_id": result.best_run_id, "summary": result.summary, }API-friendly wrapper.
def run_compression(config, output_dir) ‑> Any-
Expand source code
def run_compression(config, output_dir) -> Any: """Run compression.""" if isinstance(config, dict): cfg = data_ExperimentConfig() for k, v in config.items(): setattr(cfg, k, v) else: cfg = config compressor_name = cfg.compressor or "svd" if compressor_name not in COMPRESSOR_REGISTRY: raise ValueError(f"Unknown compressor: {compressor_name}") set_seed(cfg.seed) device = get_device() model = AutoModelForSequenceClassification.from_pretrained(cfg.model_name) model.to(device) compressor_cls = COMPRESSOR_REGISTRY[compressor_name] compressor = compressor_cls(model, rank=cfg.compression_rank) compressed_model = compressor.compress() os.makedirs(output_dir, exist_ok=True) compressed_model.save_pretrained(output_dir) tokenizer = AutoTokenizer.from_pretrained(cfg.model_name) tokenizer.save_pretrained(output_dir) original_bytes = sum(p.numel() * p.element_size() for p in model.parameters()) compressed_bytes = sum(p.numel() * p.element_size() for p in compressed_model.parameters()) reduction_ratio = original_bytes / compressed_bytes if compressed_bytes > 0 else 1 stats = { "compressor": compressor_name, "original_size_mb": round(original_bytes / (1024**2), 2), "compressed_size_mb": round(compressed_bytes / (1024**2), 2), "reduction_ratio": round(reduction_ratio, 2), "compression_rank": cfg.compression_rank, } logger.info(f"Compression complete: {stats}") return statsRun compression.
def run_evaluation(config, model_path) ‑> Any-
Expand source code
def run_evaluation(config, model_path) -> Any: """Run evaluation.""" if isinstance(config, dict): cfg = data_ExperimentConfig() for k, v in config.items(): setattr(cfg, k, v) else: cfg = config set_seed(cfg.seed) device = get_device() # Reload data (simplified) raw_datasets = load_glue_local(cfg.task, cfg.glue_base_path) num_labels, is_regression = get_num_labels_and_is_regression(cfg.task) sent1_key, sent2_key = GLUE_TASK_KEYS[cfg.task] tokenizer = AutoTokenizer.from_pretrained(cfg.model_name, use_fast=True) preprocess_fn = build_preprocess( tokenizer, cfg.task, cfg.max_len, sent1_key, sent2_key, is_regression ) dataset = raw_datasets.map(preprocess_fn, batched=True) cols = ["input_ids", "attention_mask", "labels"] if "token_type_ids" in dataset["validation"].column_names: cols.append("token_type_ids") dataset.set_format(type="torch", columns=cols) try: model = AutoModelForSequenceClassification.from_pretrained(model_path) except Exception: logger.warning(f"Could not load from {model_path}, using base model") model = AutoModelForSequenceClassification.from_pretrained( cfg.model_name, num_labels=num_labels ) model.to(device) training_args = TrainingArguments( output_dir="./tmp_eval", per_device_eval_batch_size=cfg.batch_size * 2, remove_unused_columns=False, report_to="none", ) eval_trainer = Trainer( model=model, args=training_args, eval_dataset=dataset.get("validation", dataset.get("validation_matched")), data_collator=DataCollatorWithPadding(tokenizer), ) eval_result = eval_trainer.evaluate() return {k.replace("eval_", ""): v for k, v in eval_result.items()}Run evaluation.
def run_training(config,
stop_event=None,
progress_callback=None,
log_callback=None,
final_callback=None) ‑> Any-
Expand source code
def run_training( config, stop_event=None, progress_callback=None, log_callback=None, final_callback=None, ) -> Any: """Run training.""" if isinstance(config, dict): cfg = data_ExperimentConfig() for k, v in config.items(): setattr(cfg, k, v) else: cfg = config set_seed(cfg.seed) device = get_device() logger.info(f"Using device: {device}") # Data raw_datasets = load_glue_local(cfg.task, cfg.glue_base_path) num_labels, is_regression = get_num_labels_and_is_regression(cfg.task) sent1_key, sent2_key = GLUE_TASK_KEYS[cfg.task] tokenizer = AutoTokenizer.from_pretrained(cfg.model_name, use_fast=True) preprocess_fn = build_preprocess( tokenizer, cfg.task, cfg.max_len, sent1_key, sent2_key, is_regression ) dataset = raw_datasets.map( preprocess_fn, batched=True, remove_columns=raw_datasets["train"].column_names ) cols = ["input_ids", "attention_mask", "labels"] if "token_type_ids" in dataset["train"].column_names: cols.append("token_type_ids") dataset.set_format(type="torch", columns=cols) # Model model = AutoModelForSequenceClassification.from_pretrained( cfg.model_name, num_labels=num_labels ) fix_layernorm_naming(model) model.to(device) # Trainer trainer_type = getattr(cfg, "trainer_type", "hf") trainer_cls = get_trainer(trainer_type) callbacks = [ DjangoCallback(progress_callback, log_callback, final_callback, stop_event), TrainerLoggingCallback(None, logger), MemoryCallback(), TimeCallback(), ] if getattr(cfg, "compressor", None): callbacks.append(SVDAttentionCallback()) trainer = trainer_cls.create( model=model, dataset=dataset, config=cfg, tokenizer=tokenizer, logger=logger, callbacks=callbacks, ) logger.info("Starting training...") trainer.train() eval_results = trainer.evaluate() logger.info("Training completed.") # Save output_dir = Path(cfg.output_dir) / (cfg.exp_name or "training") output_dir.mkdir(parents=True, exist_ok=True) trainer.save_model(str(output_dir)) tokenizer.save_pretrained(str(output_dir)) try: create_comprehensive_plots( metrics=trainer.state.log_history, output_dir=str(output_dir / "plots"), prefix=cfg.exp_name or "training", ) except Exception as e: logger.warning(f"Plot generation failed: {e}") if final_callback: final_callback(eval_results, trainer.state.log_history) return eval_resultsRun training.
def run_training_async(experiment, stop_event: threading.Event) ‑> None-
Expand source code
def run_training_async(experiment, stop_event: threading.Event) -> None: """ Main entry point called by Django in a background thread. Args: experiment: Django model instance with fields: id, config (JSON), status, logs, current_step, total_steps, etc. stop_event: threading.Event to signal cancellation. """ config = experiment.config run_id = str(experiment.id) base_dir = _get_media_root() / "experiments" def log(msg: str) -> None: """Log.""" experiment.logs = (experiment.logs or "") + msg + "\n" experiment.save(update_fields=["logs"]) WSMessageQueue.send(run_id, {"type": "log", "data": msg}) broadcast_terminal(msg) def update_status(status: str) -> None: """Update status.""" experiment.status = status experiment.save(update_fields=["status"]) WSMessageQueue.send(run_id, {"type": "status", "status": status}) def progress_callback(current: int, total: int, pct: float) -> None: """Progress callback.""" experiment.current_step = current experiment.total_steps = total experiment.estimated_remaining_seconds = (total - current) * 0.1 # crude experiment.cpu_usage = psutil.cpu_percent() experiment.ram_usage_mb = psutil.virtual_memory().used / 1024**2 experiment.save( update_fields=[ "current_step", "total_steps", "estimated_remaining_seconds", "cpu_usage", "ram_usage_mb", ] ) WSMessageQueue.send( run_id, { "type": "progress", "progress": pct, "current_step": current, "total_steps": total, }, ) def log_callback(msg: str): # Called by the orchestrator for log messages """Log callback.""" log(msg) def final_callback(metrics: dict) -> None: """Final callback.""" experiment.config["final_metrics"] = metrics experiment.save(update_fields=["config"]) WSMessageQueue.send(run_id, {"type": "final", "data": metrics}) log(f"Training finished. Final metrics: {json.dumps(metrics)}") save_artifacts(experiment, metrics) try: update_status("running") orchestrator = MegaOrchestrator( config=config, run_id=run_id, base_dir=base_dir, progress_callback=progress_callback, log_callback=log_callback, final_callback=final_callback, stop_event=stop_event, ) orchestrator.run() if stop_event.is_set(): update_status("stopped") log("Training cancelled by user.") else: update_status("completed") except Exception as e: error_msg = traceback.format_exc() log(f"ERROR: {str(e)}\n{error_msg}") update_status("failed")Main entry point called by Django in a background thread.
- Args
- -----=
experiment- Django model instance with fields: id, config (JSON), status, logs, current_step, total_steps, etc.
stop_event- threading.Event to signal cancellation.
Classes
class AIAssistant (llm_function)-
Expand source code
class AIAssistant: """ Monitors training metrics and suggests actions (e.g., early stop). Uses a callable LLM function for decision making. """ def __init__(self, llm_function) -> None: self.llm = llm_function def monitor(self, exp_id: str, metrics_history: list) -> None: """Monitor.""" last_losses = [m.get("loss") for m in metrics_history[-5:] if "loss" in m] if len(last_losses) >= 5 and all( abs(last_losses[i] - last_losses[i - 1]) < 0.01 for i in range(1, 5) ): suggestion = "early_stop" reason = "Loss plateau detected" WSMessageQueue.send( exp_id, {"type": "ai_suggestion", "suggestion": suggestion, "reason": reason}, ) # Optionally trigger early stop # stop_event.set()Monitors training metrics and suggests actions (e.g., early stop). Uses a callable LLM function for decision making.
Methods
def monitor(self, exp_id: str, metrics_history: list) ‑> None-
Expand source code
def monitor(self, exp_id: str, metrics_history: list) -> None: """Monitor.""" last_losses = [m.get("loss") for m in metrics_history[-5:] if "loss" in m] if len(last_losses) >= 5 and all( abs(last_losses[i] - last_losses[i - 1]) < 0.01 for i in range(1, 5) ): suggestion = "early_stop" reason = "Loss plateau detected" WSMessageQueue.send( exp_id, {"type": "ai_suggestion", "suggestion": suggestion, "reason": reason}, ) # Optionally trigger early stop # stop_event.set()Monitor.
class ExperimentComparator (run_ids: list[str], baseline_id: str | None = None)-
Expand source code
class ExperimentComparator: """ Compare multiple TrainingRuns with rich diff output. Inspired by ZenML's comparison tool, MLflow compare, and RagaAI diff. """ def __init__(self, run_ids: list[str], baseline_id: str | None = None) -> None: self.run_ids = run_ids self.baseline_id = baseline_id or (run_ids[0] if run_ids else None) self.runs: list[TrainingRun] = [] def load_runs(self) -> None: """Load runs from disk or config.""" self.runs = list(TrainingRun.objects.filter(id__in=self.run_ids).order_by("created_at")) def compare(self) -> ComparisonResult: """Compare.""" self.load_runs() if len(self.runs) < 2: raise ValueError("Need at least 2 runs to compare") baseline = next((r for r in self.runs if str(r.id) == self.baseline_id), self.runs[0]) result = ComparisonResult( run_ids=[str(r.id) for r in self.runs], metrics_table=self._build_metrics_table(), config_diff=self._build_config_diff(), hyperparameter_table=self._build_hyperparameter_table(), metric_deltas=self._build_metric_deltas(baseline), timeline_data=self._build_timeline_overlay(), parallel_coords=self._build_parallel_coords(), best_run_id=self._find_best_run(), summary="", ) result.summary = self._generate_summary(result) return result def _build_metrics_table(self) -> list[dict[str, Any]]: """build metrics table.""" rows = [] for run in self.runs: metrics = run.final_metrics or {} config = run.config or {} rows.append( { "run_id": str(run.id), "name": run.name or str(run.id)[:8], "status": run.status, "created_at": run.created_at.isoformat(), "epochs": config.get("epochs", "N/A"), "batch_size": config.get("batch_size", "N/A"), "learning_rate": config.get("learning_rate", "N/A"), "model_name": config.get("model_name", "N/A"), "eval_accuracy": metrics.get("eval_accuracy"), "eval_loss": metrics.get("eval_loss"), "eval_f1": metrics.get("eval_f1"), "duration_seconds": run.actual_duration, } ) return rows def _build_config_diff(self) -> dict[str, Any]: """build config diff.""" configs = {str(r.id): r.config for r in self.runs} first_id = str(self.runs[0].id) diffs = {} for rid, cfg in configs.items(): if rid != first_id: diff = DeepDiff(configs[first_id], cfg, ignore_order=True, verbose_level=2) diffs[rid] = diff return { "baseline_id": first_id, "diffs": diffs, "all_config_keys": list(set().union(*[set(c.keys()) for c in configs.values()])), } def _build_hyperparameter_table(self) -> list[dict[str, Any]]: """build hyperparameter table.""" hp_keys = [ "learning_rate", "batch_size", "epochs", "max_seq_length", "compression_rank", "lora_r", "lora_alpha", "weight_decay", "warmup_steps", "grad_accum", "max_grad_norm", ] rows = [] for run in self.runs: config = run.config or {} row = {"run_id": str(run.id), "name": run.name or str(run.id)[:8]} for k in hp_keys: row[k] = config.get(k) rows.append(row) return rows def _build_metric_deltas(self, baseline: TrainingRun) -> list[dict[str, Any]]: """build metric deltas.""" base_metrics = baseline.final_metrics or {} deltas = [] metric_keys = [ "eval_accuracy", "eval_loss", "eval_f1", "eval_precision", "eval_recall", ] for run in self.runs: if str(run.id) == str(baseline.id): continue run_metrics = run.final_metrics or {} delta_row = {"run_id": str(run.id), "name": run.name or str(run.id)[:8]} for k in metric_keys: base_v = base_metrics.get(k) run_v = run_metrics.get(k) if base_v is not None and run_v is not None: delta_row[k] = round(run_v - base_v, 6) delta_row[f"{k}_pct"] = ( round(((run_v - base_v) / abs(base_v)) * 100, 2) if base_v != 0 else None ) else: delta_row[k] = None deltas.append(delta_row) return deltas def _build_timeline_overlay(self) -> dict[str, list[float]]: """Extract training history for overlaid loss curves.""" timeline = {"steps": [], "series": {}} for run in self.runs: history = run.training_history or {} steps = history.get("steps", []) losses = history.get("losses", []) if steps and losses: if len(steps) > len(timeline["steps"]): timeline["steps"] = steps timeline["series"][str(run.id)] = losses return timeline def _build_parallel_coords(self) -> dict[str, Any]: """Build data for a parallel coordinates plot (hyperparams + metrics).""" dimensions = [ "learning_rate", "batch_size", "epochs", "max_seq_length", "compression_rank", "eval_accuracy", "eval_loss", "eval_f1", ] data = [] for run in self.runs: config = run.config or {} metrics = run.final_metrics or {} point = {"run_id": str(run.id), "name": run.name or str(run.id)[:8]} for d in dimensions: point[d] = config.get(d) or metrics.get(d) data.append(point) return {"dimensions": dimensions, "data": data} def _find_best_run(self) -> str | None: """find best run.""" best = None best_acc = -1 for run in self.runs: acc = (run.final_metrics or {}).get("eval_accuracy", -1) if acc is not None and acc > best_acc: best_acc = acc best = str(run.id) return best def _generate_summary(self, result: ComparisonResult) -> str: """generate summary.""" lines = [f"Comparison of {len(self.runs)} runs."] if result.best_run_id: best_run = next((r for r in self.runs if str(r.id) == result.best_run_id), None) if best_run: lines.append(f"Best run: {best_run.name or result.best_run_id[:8]}") # Count improvements improved = sum( 1 for d in result.metric_deltas if d.get("eval_accuracy") is not None and d["eval_accuracy"] > 0 ) lines.append(f"{improved} run(s) improved over baseline in accuracy.") return " ".join(lines)Compare multiple TrainingRuns with rich diff output. Inspired by ZenML's comparison tool, MLflow compare, and RagaAI diff.
Methods
def compare(self) ‑> ComparisonResult-
Expand source code
def compare(self) -> ComparisonResult: """Compare.""" self.load_runs() if len(self.runs) < 2: raise ValueError("Need at least 2 runs to compare") baseline = next((r for r in self.runs if str(r.id) == self.baseline_id), self.runs[0]) result = ComparisonResult( run_ids=[str(r.id) for r in self.runs], metrics_table=self._build_metrics_table(), config_diff=self._build_config_diff(), hyperparameter_table=self._build_hyperparameter_table(), metric_deltas=self._build_metric_deltas(baseline), timeline_data=self._build_timeline_overlay(), parallel_coords=self._build_parallel_coords(), best_run_id=self._find_best_run(), summary="", ) result.summary = self._generate_summary(result) return resultCompare.
def load_runs(self) ‑> None-
Expand source code
def load_runs(self) -> None: """Load runs from disk or config.""" self.runs = list(TrainingRun.objects.filter(id__in=self.run_ids).order_by("created_at"))Load runs from disk or config.
class MegaOrchestrator (config: dict[str, typing.Any],
run_id: str,
base_dir: pathlib.Path,
progress_callback:| None = None,
log_callback:| None = None,
final_callback:| None = None,
stop_event: threading.Event | None = None)-
Expand source code
class MegaOrchestrator: """ Central orchestrator that connects all framework modules. Usage (from Django runner): orchestrator = MegaOrchestrator( config=request.data, run_id=str(uuid.uuid4()), base_dir=Path(settings.MEDIA_ROOT), progress_callback=progress_cb, log_callback=log_cb, final_callback=final_cb, stop_event=stop_event, ) orchestrator.run() """ def __init__( self, config: dict[str, Any], run_id: str, base_dir: Path, progress_callback: Callable | None = None, log_callback: Callable | None = None, final_callback: Callable | None = None, stop_event: threading.Event | None = None, ) -> None: self.raw_config = config self.run_id = run_id self.base_dir = Path(base_dir) self.run_dir = self.base_dir / run_id self.run_dir.mkdir(parents=True, exist_ok=True) self.progress_cb = progress_callback self.log_cb = log_callback self.final_cb = final_callback self.stop_event = stop_event or threading.Event() # Convert dict to unified Config self.cfg = load_config(None) # start with defaults if config: self.cfg = Config( **{k: v for k, v in config.items() if k in Config.__dataclass_fields__} ) self.model = None self.tokenizer = None self.trainer_state = None self.final_metrics: dict[str, float] = {} logger.info("MegaOrchestrator created: run_id=%s", run_id) def run(self) -> dict[str, Any]: """Execute the full experiment pipeline.""" try: self._log("Starting experiment…") # 1. Load model & tokenizer self._log("Loading model…") self.model, self.tokenizer = load_model( self.cfg.model.name, num_labels=self.cfg.model.num_labels, attention_type=self.cfg.compression.attn_type, attention_rank=self.cfg.compression.rank, ) # 2. Apply training method (if any) if self.cfg.method.method != "baseline": from neuralops.trainer.methods import get_method method_fn = get_method(self.cfg.method.method) if method_fn: method_fn(self.model, self.cfg.compression) # 3. Load data self._log("Loading data…") exp_cfg = self._build_experiment_config() dataset = load_dataset(exp_cfg) tokenized = self._preprocess_dataset(dataset) # 4. Build trainer self._log("Building trainer…") results = ResultsManager(self.run_id, self.base_dir, config=self.raw_config) trainer = HuggingFaceTrainer( model=self.model, train_dataset=tokenized["train"], eval_dataset=tokenized.get("validation"), config=self.cfg.training.__dict__, results_manager=results, compute_metrics=get_task_metrics(self.cfg.training.task), ) # Add basic callbacks from neuralops.trainer.callbacks import MemoryCallback, TimeCallback trainer.add_callback(MemoryCallback()) trainer.add_callback(TimeCallback()) # 5. Train self._log("Training started…") trainer.train() eval_metrics = trainer.evaluate() self.final_metrics = eval_metrics self.trainer_state = trainer.state.log_history if hasattr(trainer, "state") else [] # 6. Save everything self._log("Saving results…") saver = ResultsSaver( run_id=self.run_id, base_dir=self.base_dir, config=SaveConfig(save_model=True, save_plots=True), ) saver.save_all( self.model, self.cfg.__dict__, self.trainer_state, eval_metrics, self.tokenizer, ) saver.push_all() self._log("Experiment completed.") return eval_metrics except Exception as e: logger.exception("Orchestrator failed: %s", e) self._log(f"Experiment failed: {traceback.format_exc()}") raise def _build_experiment_config(self) -> Any: """build experiment config.""" from neuralops.data.dataset_loader import ExperimentConfig return ExperimentConfig(dataset_name=self.cfg.training.task) def _preprocess_dataset(self, dataset) -> Any: """preprocess dataset.""" task = self.cfg.training.task s1, s2 = None, None from neuralops.data.glue_loader import GLUE_TASK_KEYS if task in GLUE_TASK_KEYS: s1, s2 = GLUE_TASK_KEYS[task] data_cfg = DataConfig( task_type=( TaskType.SINGLE_CLASSIFICATION if s2 is None else TaskType.PAIR_CLASSIFICATION ), max_length=self.cfg.data.max_seq_length, batch_size=self.cfg.training.batch_size, text_column_1=s1, text_column_2=s2, label_column="label", ) def preprocess(examples) -> Any: """Preprocess.""" return preprocess_function(examples, self.tokenizer, data_cfg) tokenized = dataset.map( preprocess, batched=True, remove_columns=dataset["train"].column_names ) tokenized.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"]) return tokenized def _log(self, msg: str) -> None: """log.""" logger.info("[%s] %s", self.run_id[:8], msg) if self.log_cb: self.log_cb(msg)Central orchestrator that connects all framework modules.
Usage (from Django runner): orchestrator = MegaOrchestrator( config=request.data, run_id=str(uuid.uuid4()), base_dir=Path(settings.MEDIA_ROOT), progress_callback=progress_cb, log_callback=log_cb, final_callback=final_cb, stop_event=stop_event, ) orchestrator.run()
Methods
def run(self) ‑> dict[str, typing.Any]-
Expand source code
def run(self) -> dict[str, Any]: """Execute the full experiment pipeline.""" try: self._log("Starting experiment…") # 1. Load model & tokenizer self._log("Loading model…") self.model, self.tokenizer = load_model( self.cfg.model.name, num_labels=self.cfg.model.num_labels, attention_type=self.cfg.compression.attn_type, attention_rank=self.cfg.compression.rank, ) # 2. Apply training method (if any) if self.cfg.method.method != "baseline": from neuralops.trainer.methods import get_method method_fn = get_method(self.cfg.method.method) if method_fn: method_fn(self.model, self.cfg.compression) # 3. Load data self._log("Loading data…") exp_cfg = self._build_experiment_config() dataset = load_dataset(exp_cfg) tokenized = self._preprocess_dataset(dataset) # 4. Build trainer self._log("Building trainer…") results = ResultsManager(self.run_id, self.base_dir, config=self.raw_config) trainer = HuggingFaceTrainer( model=self.model, train_dataset=tokenized["train"], eval_dataset=tokenized.get("validation"), config=self.cfg.training.__dict__, results_manager=results, compute_metrics=get_task_metrics(self.cfg.training.task), ) # Add basic callbacks from neuralops.trainer.callbacks import MemoryCallback, TimeCallback trainer.add_callback(MemoryCallback()) trainer.add_callback(TimeCallback()) # 5. Train self._log("Training started…") trainer.train() eval_metrics = trainer.evaluate() self.final_metrics = eval_metrics self.trainer_state = trainer.state.log_history if hasattr(trainer, "state") else [] # 6. Save everything self._log("Saving results…") saver = ResultsSaver( run_id=self.run_id, base_dir=self.base_dir, config=SaveConfig(save_model=True, save_plots=True), ) saver.save_all( self.model, self.cfg.__dict__, self.trainer_state, eval_metrics, self.tokenizer, ) saver.push_all() self._log("Experiment completed.") return eval_metrics except Exception as e: logger.exception("Orchestrator failed: %s", e) self._log(f"Experiment failed: {traceback.format_exc()}") raiseExecute the full experiment pipeline.