Repository navigation
Conversation
Change map
Requirement: each prediction run must appear in the experiment tracker that holds the training runs, pointing to the model that produced it (final MLOps design, section 3). Existing: the #67 manifest has the facts, the #60/#68 adapters log fits, #69's |
|
Warning Review limit reachedEnable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Next included review available in 24 minutes. View limit details
Walkthrough
Priority: Merge Risk:
|
6fb1ddc to
2306723
Compare
6fed618 to
25b4328
Compare
2306723 to
5d9e5c7
Compare
25b4328 to
948e1f0
Compare
5d9e5c7 to
08b464b
Compare
948e1f0 to
f9fe479
Compare
08b464b to
8aa54c7
Compare
f9fe479 to
dbe1fd9
Compare
dbe1fd9 to
fa42a89
Compare
8aa54c7 to
aaa1137
Compare
fa42a89 to
66425c9
Compare
0a4907c to
35f3177
Compare
66425c9 to
5dcf2ef
Compare
66425c9 to
1c8c3e5
Compare
61399bf to
9426af9
Compare
26aadcb to
ee29b2d
Compare
9426af9 to
7c140cb
Compare
ee29b2d to
3402edf
Compare
7c140cb to
332472d
Compare
2cf524f to
9c4dee3
Compare
332472d to
2e57b10
Compare
9c4dee3 to
08e9b4a
Compare
2e57b10 to
1b25f5f
Compare
08e9b4a to
2b34fa8
Compare
1b25f5f to
f60e43d
Compare
5ed7432 to
e954689
Compare
There was a problem hiding this comment.
Actionable comments posted: 2

Review info
-
Run configuration-
- Source mode: Auto
- Repository: NVIDIA/kumo-engine
- Branch: vedaant/mlops-monitoring
- File: .coderabbit.yaml
- Commit: 0c39510
- Layers: Repository YAML
- Review profile: QUIET
- Plan: Enterprise
- Run ID:
14c0939a-0a91-42de-abb5-0e04c2cd0201
Files selected for processing: 9-
packages/kumo-engine/docs/mlops.mdpackages/kumo-engine/kumo/engine/_tracking.pypackages/kumo-engine/kumo/engine/integrations/mlflow.pypackages/kumo-engine/kumo/engine/integrations/snowflake.pypackages/kumo-engine/kumo/engine/trainer/_run_manifest.pypackages/kumo-engine/test/test_mlflow_tracking.pypackages/kumo-engine/test/test_prediction_manifest.pypackages/kumo-engine/test/test_snowflake_tracking.pypackages/kumo-engine/test/test_tracking_contract.py
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 1 remain after this review.
-
| logger.warning('Experiment tracking failed; the prediction run is ' | ||
| 'not tracked', exc_info=True) | ||
| return | ||
| log_run(tracking, 'log_prediction_run', record) |
There was a problem hiding this comment.
Prevent overlapping runs on one tracker.
If a prediction’s tracking call exceeds the 30-second wait, its worker continues. A later predict call can start another worker on the same tracker. Both adapters reset and update run state on that instance, so the workers can assign records to the wrong run or leave a run unfinished. Serialize calls per tracker or give each prediction an independent run session while preserving the timeout.
| self.start(record) | ||
| self.finish(record, record_failure(record)) |
There was a problem hiding this comment.
Finish a run when start fails after creating it.
If MLflow creates the run but _log_params, _log_artifacts, or _log_inputs raises, this hook never calls finish. The tracking worker suppresses the exception, leaving the run open and the HTTP-settings guard unclosed. SnowflakeTracking.log_prediction_run has the same failure path; its active run can also block later tracking attempts. Clean up a partially started run in both hooks, then let the tracking worker report the failure.
A prediction run could be traced from its manifest but not found next to the fit run in the team's experiment tracker. With kumo.MLOps(tracking=...), predict builds a RunRecord from the run's manifest (prediction_record: scoring options as params, row counts and score percentiles as metrics, the scoring snapshot's source tables as inputs, the tags kumo.kind/run_id/bundle_id, kumo.error when the export failed, and _provenance.json as an artifact) and logs it, after the output_config export and the artifact-root copy, through the tracker's optional log_prediction_run hook on a tracking thread with the fit's time limit. Tracking errors never change the run. The MLflow and Snowflake adapters implement the hook as one run named by the prediction id; a failed export ends it as failed. Verified: test_tracking_contract, test_mlflow_tracking, test_snowflake_tracking, test_prediction_manifest, test_monitoring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Folded into #64 (prediction runs & monitoring), which now contains this PR's final code: the consolidated stack shows only the end state. Review continues there. |
Series: kumo-engine MLOps series: every snapshot, training table, model and prediction run carries a self-contained _provenance.json;
with kumo.mlops(...)stores each artifact under one artifact root, tracks fits and prediction runs in MLflow or Snowflake and registers models; volumes and stages can hold files; prediction runs are monitored for drift and realized performance.Stack: Prediction runs and monitoring: #64 → #67 → #69 → #73
Merge order: one linear stack; merge bottom to top (#53 first)
This PR: Adds prediction-run tracking: with
kumo.MLOps(tracking=...), each prediction run is recorded in MLflow or Snowflake under the samekumo.*tags as the fit run, built from its manifest.Summary
This PR adds prediction-run tracking. With
kumo.MLOps(tracking=...), everyTrainer.predictis recorded as a run in MLflow or Snowflake Experiment Tracking, in the same experiment as the training runs and through the sameTrackingadapters as fits. It closes the prediction side of the MLOps series: #67 writes a manifest for every prediction run, #60 and #68 record fits, and this PR sends the prediction manifest to those trackers.Problem. Teams that score on a schedule need to answer questions like these:
Since #67 the answers exist, but only in each run's
_provenance.json. Today none of them can be answered from the experiment tracker where the training runs already live.Cause. The
Trackingprotocol (#60, #68) records fits:start(record),on_trial(record, event)andfinish(record, error), fed by aTrackingSessionon a background thread. It listslog_prediction_run(record)as an optional hook, but nothing calls it and nothing maps a prediction manifest into aRunRecord.Change.
Core mapping (
_tracking.prediction_record(manifest, error=None) -> RunRecord, no platform imports), the only place prediction-run names are defined:scoringoptions (binary_classification_threshold,threshold_source,num_classes_to_return,count_source,num_workers) plusanchors;Noneoptions are left outstats, flattened with the same helper as fit params:prediction_table.rows;<artifact>.rows; per score column (True_PROB,TARGET_PREDortop_score)count,mean,min,maxand seven percentile levelsq01,q05,q25,q50,q75,q95,q99(the manifest keeps all 99);predictions.predicted_positive_rate(binary)fit_inputshelper fits use (now also for prediction manifests); MLflow logs them as run inputskumo.kind=prediction,kumo.run_id(the prediction id),kumo.bundle_id, andkumo.errorwhen the export failed_provenance.json, the full manifestWhen. Inside
predict, after theoutput_configexport, the manifest write and the artifact-root copy (Record a manifest and output score statistics for every prediction run #67),_run_manifest.finishcallstrack_prediction(manifest, settings.tracking, error). It goes through Evaluate prediction runs and report score drift #69'slog_run: the tracker's optionallog_prediction_runhook runs on a tracking thread, only on global rank 0, for up to 30 s, so a hung tracker cannot holdpredict, and its errors are logged, never raised. If the export raises, the run is still recorded as failed (kumo.error=<Type>: <message>, the same formatting as fit runs) andpredictraises the export's error. Settings are resolved on the caller's thread (Record provenance for every artifact under one MLOps artifact root #50).Adapters. Each implements the hook as its own
start(record)thenfinish(record, record_failure(record)), so params, metrics, tags, inputs and artifacts go through the same code as a fit run.MlflowTracking: one run named by the prediction id in the configured experiment, nested under the caller's active run if there is one (the same rule as fits). Params MLflow rejects are counted inkumo.dropped_params.SnowflakeTracking: one run named by the (quoted) prediction id;kumo.*tags become params, as for fits. An existing run is never resumed (a taken name gets a suffix); if the process'sExperimentTrackinginstance is in use, the run is not recorded there, with a warning.run_nameandtagsapply to fit runs only. The adapter'srun_idattribute is now the most recent tracked run, fit or prediction.Docs.
docs/mlops.md(Experiment tracking) describes prediction runs and addsprediction/monitoringto the tag table.Real example. Synthetic graph: 500 users, 4,000 orders, query
PREDICT COUNT(orders.*, 0, 7, days) > 0 FOR EACH users.ID. A short fit and apredictinside onekumo.mlopsblock withMlflowTracking, read back withMlflowClient.search_runsfrom a local SQLite store (ids shortened, metrics rounded):{ "run_name": "972e73fe…-predict-550b108f…", "status": "FINISHED", "tags": {"kumo.kind": "prediction", "kumo.run_id": "972e73fe…-predict-550b108f…", "kumo.bundle_id": "fa2d033d…"}, "params": {"anchors": "[\"2024-04-29T00:00:00+00:00\"]", "binary_classification_threshold": "0.5", "threshold_source": "argument", "count_source": "none", "num_workers": "1"}, "metrics": {"prediction_table.rows": 500.0, "predictions.rows": 500.0, "predictions.predicted_positive_rate": 0.0, "predictions.True_PROB.count": 500.0, "predictions.True_PROB.mean": 0.3485, "predictions.True_PROB.min": 0.3176, "predictions.True_PROB.max": 0.3663, "predictions.True_PROB.q01": 0.324, "predictions.True_PROB.q05": 0.329, "predictions.True_PROB.q25": 0.3418, "predictions.True_PROB.q50": 0.3504, "predictions.True_PROB.q75": 0.356, "predictions.True_PROB.q95": 0.3615, "predictions.True_PROB.q99": 0.3643}, "inputs": ["orders", "users"], "artifacts": ["_provenance.json"] }The fit run in the same experiment carries the same
kumo.bundle_id(#65), so filtering on it returns the training run and every prediction run of that model;kumo.kind = predictionseparates scoring runs from training runs. Every daily run has the same metric names, sopredictions.True_PROB.q50orpredictions.predicted_positive_ratecan be charted across runs. For PSI, KS and Wasserstein against the holdout or another run,Trainer.score_drift(#69) reads the full curves from the manifests and logs a monitoring run that MLflow links back withkumo.monitored.Back-compat
This PR only records and exposes information. Tracking is opt-in, and a tracking failure never changes or fails a prediction.
predict()with MLOps off, or on withouttrackingpredict()with MLOps tracking onlog_prediction_run, after the export and the artifact-root copy. A test compares the frames of tracked and untracked runs.output_configexport raiseskumo.error; the export's error is raised as before.Trackingimplementations (fit hooks only)predictwaits at most 30 s.Known limitations
Testing Done
test_tracking_contract.py(13 passed):num_classes_to_return, atop_scorecolumn without percentiles, embeddings rows, a failure tag);prediction_record;test_prediction_manifest.py: withMLOps(tracking=recorder)the recorder getsprediction_record(result.manifest)and the frame equals an untracked run's; a raising tracker only warns; an export that raises in a trackedpredictre-raises, records a failed run withkumo.error=OSError: disk fulland the manifest's id, and stores nothing in the root.test_mlflow_tracking.py: a prediction run nests under an active user run, which stays active, and otherwise lands top-level in the named experiment under the prediction id.test_snowflake_tracking.py: with the instance in use, the prediction run is not recorded and the instance is untouched; logging the same record twice creates a suffixed run instead of resuming.test_monitoring,test_score_stats,test_bundle_provenance,test_holdout_df,test_scoring_defaults,test_prediction_completion,test_job_templates,test_query_from_snapshot,test_mlflow_model: 125 passed, 8 skipped (checks against the installed snowflake-ml, which is not installed);test_predictive_query+test_optional_imports: 118 passed.Checklist
🤖 Generated with Claude Code