Skip to content

Make training runs self-describing in MLflow - #108

Open
AlexanderFengler wants to merge 5 commits into
mainfrom
feat/mlflow-self-describing-training
Open

Make training runs self-describing in MLflow#108
AlexanderFengler wants to merge 5 commits into
mainfrom
feat/mlflow-self-describing-training

Conversation

@AlexanderFengler

@AlexanderFengler AlexanderFengler commented Aug 6, 2026

Copy link
Copy Markdown
Member

What

PR-0.2 of the ecosystem's "MLflow as database of record" workstream (companion to lnccbrown/ssm-simulators#319). Training runs from both backends now carry the identity a network catalog needs, logged at the CLI level via a shared log_training_run_identity helper:

  • Params (immutable network facts — safe to re-log identically on resume): model, network_type, backend, input_dim, param_space, param_bounds_json + param_bounds_sha256
  • Tags (per-invocation, mutable — a resumed run overwrites them so the run reflects its latest invocation): schema_version=1, phase=train, run_uuid (the uuid in every artifact filename — the MLflow ↔ disk join key), n_training_files_used, training_data_folder, config_sha256, lanfactory_version, SLURM ids
  • The params/tags split is deliberate resume-safety: MLflow rejects re-logging a param with a different value and drops the whole batch, which would silently strand the join key on --mlflow-run-id resumes (regression-tested)
  • network_config.pickle + the source YAML are now logged as run artifacts (previously written to disk but never logged)
  • param_bounds no longer lost: DatasetTorch retains model_config from the training data, so bounds reach data_details.pickle — with values sanitized for stdlib pickle (ssm-simulators pickles are cloudpickle-written and can carry lambda boundary functions; regression-tested)
  • Fixed OPN-mislabeled-as-cpn in the jax trainer: network_type is threaded from network_config instead of being inferred from the output type (inference kept as fallback only)
  • Honest cross-backend train_loss: torch now logs epoch-mean train_loss at step=epoch (matching jax) instead of last-minibatch-at-step-count; legacy loss/val_loss streams unchanged

Ecosystem impact

Together with #319 this makes every producer run in the ecosystem self-describing under a shared schema_version=1 schema (to be documented in HSSMSpine _docs/mlflow-schema.md). Downstream, LAN_pipeline_minimal's publish/registry tooling resolves artifacts via the run_uuid tag. Additive only; no API breaks.

Review provenance

Adversarially reviewed pre-open (multi-agent, findings empirically reproduced). Fixed here: param-collision between identity logging and the trainer's train_config bulk-log (dropped the entire param batch), resume re-log raising and stranding the join key, train_loss semantics divergence, lambda-carrying model_config breaking _save_data_details.

Commands run

uv run pytest tests/   # 206 passed, 8 skipped, 1 xfailed (full suite, incl. stacked ONNX branch)
uv run ruff check src/lanfactory && uv run ruff format --check .

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Training runs tracked with richer MLflow metadata, including configuration, dataset details, environment information, and run identity.
    • Network and YAML configuration files are saved as MLflow artifacts when tracking is enabled.
    • JAX and PyTorch training now report per-epoch training and validation metrics.
    • Saved training outputs include model configuration details for improved traceability.
  • Bug Fixes

    • Training continues safely when MLflow artifact logging encounters an error.
    • Configuration values that cannot be serialized are safely represented when saving metadata.

AlexanderFengler and others added 3 commits August 5, 2026 19:59
Model identity was never logged on training runs: MODEL is deliberately kept
out of train_config (cli/utils.py extra_fields), and train_config is the only
bulk param log — so "which network is this?" was answerable only via
experiment-name conventions or by unpickling artifacts.

- New shared log_training_run_identity() (cli/utils.py), called by both
  jaxtrain and torchtrain: logs model, network_type, backend, run_uuid (the
  uuid1 in every artifact filename — the MLflow<->disk join key), input_dim,
  param_space, param_bounds_json + sha256, training_data_folder,
  n_training_files, config_sha256, lanfactory_version; tags schema_version=1,
  phase=train, slurm ids from env. Best-effort: tracking never kills training.
- Log network_config.pickle and the source YAML as run artifacts. Previously
  the pickle was written to disk but never logged, so the MLflow artifact set
  alone could not reconstruct a network.
- Carry model_config from training data into DatasetTorch and on into
  data_details.pickle (train/valid_data_model_config). The training pickles
  embed param_bounds; the trained-network folder previously dropped them —
  the one field HSSM-facing consumers most need.
- Unify per-epoch metrics across backends: jax now logs train_loss/val_loss
  per epoch (torch adds train_loss alongside its existing loss/val_loss).
  The jax per-100-step `loss` metric is unchanged.
- Fix OPN mislabeling: jax train_and_evaluate() gains a network_type
  parameter passed from the CLI. The old inference from train_output_type
  maps logits -> "cpn" unconditionally, so OPN artifacts were labeled cpn;
  inference is kept only as a fallback for direct trainer use.

Schema documented in HSSMSpine _docs/mlflow-schema.md (forthcoming).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three findings from adversarial review, each reproduced against a live store:

- n_training_files identity param (effective count) collided with the
  trainer's later log_params(train_config) (configured cap): MLflow rejects
  changed param values, silently dropping the ENTIRE train_config batch.
  The effective count is now the tag n_training_files_used.
- Resumed runs (--mlflow-run-id) regenerate RUN_ID, so re-logging run_uuid
  as a param raised and dropped the whole identity batch, and the recorded
  join key went stale. Split: immutable network facts (model, network_type,
  backend, input_dim, param_space, bounds) stay params — identical re-log is
  permitted; per-invocation values (run_uuid, training_data_folder,
  n_training_files_used, config_sha256, lanfactory_version) are now tags,
  which a resume overwrites so the run always reflects its latest artifacts.
  Regression test logs identity twice in one run.
- torch train_loss logged the LAST minibatch loss at the cumulative batch
  step while jax logs the EPOCH-MEAN at step=epoch — same metric name,
  different semantics, contradicting the comment claiming they match. torch
  now logs train_loss as the epoch mean at step=epoch; legacy loss/val_loss
  series unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ssm-simulators training pickles are written with cloudpickle and can carry
lambda boundary functions inside model_config (e.g. race_no_bias_angle_2).
Retaining that dict on DatasetTorch made _save_data_details' stdlib
pickle.dump raise. Unpicklable values are now repr'd at capture; the
catalog-relevant fields (params, param_bounds, choices) pass through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 6, 2026 01:15
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@AlexanderFengler, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: da677910-9ef2-4c67-88a8-5b480ea85f60

📥 Commits

Reviewing files that changed from the base of the PR and between dba61a1 and 9efc052.

📒 Files selected for processing (3)
  • src/lanfactory/cli/utils.py
  • src/lanfactory/trainers/jax_mlp.py
  • tests/test_run_identity.py
📝 Walkthrough

Walkthrough

The training CLIs now log MLflow run identity and configuration artifacts. JAX and Torch trainers persist model configuration metadata and epoch metrics. JAX accepts an explicit network type. Ruff now has a bounded development dependency range.

Changes

Training metadata

Layer / File(s) Summary
Run identity logging
src/lanfactory/cli/utils.py, tests/test_run_identity.py
Adds best-effort MLflow identity logging for configuration, dataset, invocation, package, and SLURM metadata. Tests cover active, resumed, incomplete, and inactive runs.
CLI tracking and artifacts
src/lanfactory/cli/jax_train.py, src/lanfactory/cli/torch_train.py
Both CLIs log run identity and upload network and YAML configuration files under training_output. Artifact failures do not stop training.
Trainer metadata and metrics
src/lanfactory/trainers/jax_mlp.py, src/lanfactory/trainers/torch_mlp.py, tests/test_run_identity.py
Trainers log epoch metrics, preserve network types, retain model configuration metadata, sanitize callable values, and persist training and validation metadata. Tests cover these behaviors.

Development tooling

Layer / File(s) Summary
Ruff version constraint
pyproject.toml
Bounds the Ruff development dependency to >=0.15.1,<0.16 and documents the migration requirement.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TrainingCLI
  participant TrainingTrainer
  participant MLflow
  TrainingCLI->>MLflow: log run identity and configuration artifacts
  TrainingCLI->>TrainingTrainer: start training with network type
  TrainingTrainer->>MLflow: log epoch train_loss and val_loss
Loading

Possibly related PRs

Suggested reviewers: cpaniaguam

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding self-describing metadata and artifacts to MLflow training runs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mlflow-self-describing-training

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a shared, CLI-level MLflow identity logging schema so training runs from both torch and jax backends become self-describing for downstream catalog/registry tooling, while also ensuring training-data model_config (incl. param bounds) is preserved through to data_details artifacts.

Changes:

  • Introduces log_training_run_identity helper to log immutable identity as MLflow params and per-invocation identity as tags (resume-safe), and logs key config artifacts to MLflow.
  • Propagates model_config from training data into datasets and data_details artifacts, including sanitization for stdlib-pickle compatibility.
  • Fixes JAX output filename labeling by threading network_type, and aligns cross-backend train_loss metric semantics to per-epoch values.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_run_identity.py Adds regression tests for MLflow identity logging, model_config retention/sanitization, and JAX network_type passthrough.
src/lanfactory/trainers/torch_mlp.py Captures/sanitizes model_config from training pickles; logs epoch-mean train_loss; includes model_config in data_details.
src/lanfactory/trainers/jax_mlp.py Adds explicit network_type passthrough for output naming; logs per-epoch schema metrics; includes model_config in data_details.
src/lanfactory/cli/utils.py Adds the shared log_training_run_identity MLflow helper implementing the params/tags schema.
src/lanfactory/cli/torch_train.py Calls identity logger and logs network config + YAML config as MLflow artifacts.
src/lanfactory/cli/jax_train.py Calls identity logger, logs config artifacts, and passes network_type into the JAX trainer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# the provenance HSSM-facing consumers need. Populated from the first
# data file when present; training pickles embed it alongside
# generator_config (ssm-simulators lan_mlp.py).
self.data_model_config: str = "None"
Unpinned ruff resolved to 0.16 in CI (no tracked lockfile) and flags 111
pre-existing errors on main alone — unrelated to any PR content. Pin
matches ssm-simulators' identical fix (>=0.15.1,<0.16); the 0.16 rule
migration should land as its own deliberate PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 9, 2026 00:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyproject.toml`:
- Around line 85-87: Update the Ruff revision in the pre-commit configuration to
a release within the supported range >=0.15.1 and <0.16, aligning it with the
dependency constraint in pyproject.toml; do not retain v0.14.13 unless an
explicit rationale is documented.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 38e9ac58-660d-4baa-97fa-2d8317920ccf

📥 Commits

Reviewing files that changed from the base of the PR and between d725ae3 and dba61a1.

📒 Files selected for processing (7)
  • pyproject.toml
  • src/lanfactory/cli/jax_train.py
  • src/lanfactory/cli/torch_train.py
  • src/lanfactory/cli/utils.py
  • src/lanfactory/trainers/jax_mlp.py
  • src/lanfactory/trainers/torch_mlp.py
  • tests/test_run_identity.py

Comment thread pyproject.toml
Comment on lines +85 to +87
# <0.16: ruff 0.16 flags ~111 pre-existing errors repo-wide (same drift that
# broke ssm-simulators CI, pinned identically there); migrate deliberately.
"ruff>=0.15.1,<0.16",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Project Ruff constraint:"
rg -n 'ruff' pyproject.toml

echo "Pre-commit Ruff revision:"
rg -n -A3 -B2 'ruff-pre-commit|rev:' .pre-commit-config.yaml

Repository: lnccbrown/LANfactory

Length of output: 853


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "tool.ruff lint/format config in pyproject.toml:"
sed -n '110,145p' pyproject.toml

echo
echo "uv-related Ruff usage:"
rg -n 'uv .*ruff|ruff|pre-commit|tox|.ci|workflow|github/workflows' -S pyproject.toml .pre-commit-config.yaml .github || true

Repository: lnccbrown/LANfactory

Length of output: 2970


🌐 Web query:

ruff-pre-commit v0.14.13 Ruff version bundled

💡 Result:

The ruff-pre-commit repository follows the versioning of the Ruff tool itself [1]. Therefore, ruff-pre-commit v0.14.13 bundles Ruff version 0.14.13 [2][3][4].

Citations:


Align the pre-commit Ruff revision with the supported range.

pyproject.toml requires Ruff >=0.15.1,<0.16, while .pre-commit-config.yaml uses ruff-pre-commit v0.14.13, which bundles Ruff 0.14.13. CI runs uv run ruff from the project dependency, so local pre-commit hooks can lint/format differently. Update the pre-commit revision to a Ruff release in >=0.15.1,<0.16, or document why the lower tooling version remains intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 85 - 87, Update the Ruff revision in the
pre-commit configuration to a release within the supported range >=0.15.1 and
<0.16, aligning it with the dependency constraint in pyproject.toml; do not
retain v0.14.13 unless an explicit rationale is documented.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/lanfactory/trainers/torch_mlp.py:93

  • data_model_config is annotated as str but is assigned a dict when model_config is present in the training data. This makes the attribute’s type inaccurate and can break static analysis and downstream assumptions about its shape.
        self.data_generator_config: str = "None"
        # model_config from the training data (param_bounds, params, choices):
        # the provenance HSSM-facing consumers need. Populated from the first
        # data file when present; training pickles embed it alongside
        # generator_config (ssm-simulators lan_mlp.py).
        self.data_model_config: str = "None"

src/lanfactory/trainers/torch_mlp.py:723

  • epoch_loss_sum += float(loss) runs on every minibatch even when mlflow_on is false. On GPU, converting a tensor to float can force a device sync each step, slowing training. Since epoch_loss_sum is only used for the MLflow train_loss metric, guard the accumulation behind mlflow_on.
                # Log training progress
                self._log_training_progress(epoch, cnt, loss, verbose)

                epoch_loss_sum += float(loss)
                cnt += 1
                step_cnt += 1

pyproject.toml:89

  • tests/test_run_identity.py imports and uses cloudpickle, but cloudpickle is not listed as a direct dependency in the dev/test dependency group. Relying on transitive installation can make CI fail if upstream dependencies change; declare it explicitly.
    "ruff>=0.15.1,<0.16",
    "types-PyYAML",
    "mlflow>=3.14.0",

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.22807% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/lanfactory/cli/utils.py 85.29% 0 Missing and 5 partials ⚠️
Files with missing lines Coverage Δ
src/lanfactory/trainers/jax_mlp.py 96.05% <100.00%> (+3.02%) ⬆️
src/lanfactory/trainers/torch_mlp.py 94.46% <100.00%> (+0.28%) ⬆️
src/lanfactory/cli/utils.py 93.33% <85.29%> (-6.67%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Also mark the two genuinely unreachable defensive branches no-cover:
the jax network_type 'unknown' fallback (JaxMLP.setup() raises on any
train_output_type outside network_type_dict before a trainer can exist)
and the PackageNotFoundError guard (lanfactory is always installed under
uv run).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 9, 2026 00:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/lanfactory/trainers/torch_mlp.py:92

  • data_model_config is annotated as str but is later assigned a dict (via _picklable_copy(init_file["model_config"])). This makes the attribute type incorrect and can break type checking / IDE expectations for code that consumes DatasetTorch.data_model_config.
        # model_config from the training data (param_bounds, params, choices):
        # the provenance HSSM-facing consumers need. Populated from the first
        # data file when present; training pickles embed it alongside
        # generator_config (ssm-simulators lan_mlp.py).
        self.data_model_config: str = "None"

src/lanfactory/trainers/jax_mlp.py:653

  • pickle.dump(..., open(...)) leaves the file handle unclosed if an exception occurs (and can leak descriptors). Use a context manager when writing data_details.pickle, matching the torch trainer’s _save_data_details implementation.
                    "valid_data_model_config": self.valid_dl.dataset.data_model_config,
                    "valid_data_file_ids": self.valid_dl.dataset.file_ids,
                },
                open(data_details_path, "wb"),
            )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants