MLOpsThe MLOps lifecycle · MLflow 3.17 · DVC 3.67 · FastAPI · Docker · GitHub Actions · Evidently · Python 3.11+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
50 small wins to finish your pathNext lesson →

dvc repro and the cache

dvc repro is a command that runs the stages of dvc.yaml in dependency order and skips every stage whose command, dependencies and parameters are unchanged since the last run.

Last updated: 19 Oct, 2026 · MLOps

With the stages written in dvc.yaml stages, deps and outs, one command replaces the training entry point. The first run does the work. What this lesson is about is the second run, and the run after a one-line change, because those are the ones that show what the pipeline file bought.

dvc repro runs the stages, then skips the ones nothing changed · from the Complete End to End Deep Learning Project With MLFLOW,DVC And Deployment · 3:18:38 to 3:24:03

The clip reproduces the deep-learning project's pipeline; this one reproduces the student-performance pipeline, and the run-then-skip behaviour is the same.

Running the pipeline the first time

The project's package lives under src/, so the interpreter is told where to find it before DVC starts the stages.

bash
export PYTHONPATH=src
dvc repro
Captured from a real run
Running stage 'ingest':
> python -m sp.stages.stage_01_ingest
[2026-10-19 11:18:10,990] INFO sp - downloaded 1000 rows to artifacts/data_ingestion/stud.csv
[2026-10-19 11:18:10,994] INFO sp - split into train=800 test=200
Generating lock file 'dvc.lock'
Updating lock file 'dvc.lock'

Running stage 'train':
> python -m sp.stages.stage_02_train
[2026-10-19 11:18:12,430] INFO sp - preprocessor over 5 text and 2 numeric columns
[2026-10-19 11:18:12,447] INFO sp - transformed shapes train=(800, 19) test=(200, 19)
[2026-10-19 11:18:12,595] INFO sp - trained, metrics={'r2': 0.8485, 'mae': 4.6996, 'rmse': 6.0721}
{'r2': 0.8485, 'mae': 4.6996, 'rmse': 6.0721}
Updating lock file 'dvc.lock'

To track the changes with git, run:

	git add dvc.lock

To enable auto staging, run:

	dvc config core.autostage true
Use `dvc push` to send your updates to remote storage.
  • Both stages ran, ingest first, and DVC echoed each command before running it.
  • The stage output is the project's own logging, the same lines the training pipeline prints, with the run's metrics at the end: r2 0.8485, MAE 4.6996, RMSE 6.0721.
  • dvc.lock was written. That file is the record of what this run consisted of.
  • The timestamps belong to this run and are different on yours.

Reading dvc.lock

The lock file is DVC's memory of the last run: for every stage, the command, a hash and size for each dependency and output, and the parameter values themselves rather than hashes of them. Comparing that record with what is on disk is how the next run decides what to skip.

yaml
  train:
    cmd: python -m sp.stages.stage_02_train
    params:
      params.yaml:
        MAX_DEPTH: 12
        N_ESTIMATORS: 100
        RANDOM_STATE: 42
    outs:
    - path: artifacts/model_trainer/metrics.json
      hash: md5
      md5: e9e70eff56e77d88599709deff641649
      size: 59

Running it again with nothing changed

bash
dvc repro
Captured from a real run
Stage 'ingest' didn't change, skipping
Stage 'train' didn't change, skipping
Data and pipelines are up to date.

Nothing was retrained and nothing was downloaded. The video makes the same point by running its pipeline a thousand times over: with the inputs unchanged there is no work to do, and the tool is the thing that knows it.

Changing one parameter

The tracking part registered two versions of this model, one at depth 12 and one at depth 4. Here is the second of them, made in one line of params.yaml: the trees are now allowed to grow to a depth of 4 instead of 12.

yaml
MAX_DEPTH: 4
bash
dvc repro
Captured from a real run
Stage 'ingest' didn't change, skipping
Running stage 'train':
> python -m sp.stages.stage_02_train
[2026-10-19 11:19:33,185] INFO sp - preprocessor over 5 text and 2 numeric columns
[2026-10-19 11:19:33,207] INFO sp - transformed shapes train=(800, 19) test=(200, 19)
[2026-10-19 11:19:33,289] INFO sp - trained, metrics={'r2': 0.8374, 'mae': 4.8467, 'rmse': 6.2904}
{'r2': 0.8374, 'mae': 4.8467, 'rmse': 6.2904}
Updating lock file 'dvc.lock'
  • ingest was skipped. Its dependencies and its two parameters are untouched, and MAX_DEPTH is not one of them, so DVC had no reason to download the data again.
  • train ran. MAX_DEPTH is in its parameter list, so its record no longer matched.
  • The model got worse. r2 fell from 0.8485 to 0.8374, MAE rose from 4.6996 to 4.8467 and RMSE from 6.0721 to 6.2904. Trees of depth 4 are too shallow for this data.
  • That is the result worth having. One line changed, one stage reran, and the number moved. The pipeline knew exactly what the change touched.

Seeing the change land in dvc.lock

bash
git diff --unified=1 dvc.lock
Captured from a real run
@@ -51,3 +51,3 @@ stages:
       params.yaml:
-        MAX_DEPTH: 12
+        MAX_DEPTH: 4
         N_ESTIMATORS: 100
@@ -57,3 +57,3 @@ stages:
       hash: md5
-      md5: e9e70eff56e77d88599709deff641649
+      md5: a6ba8e2fbeeb0607449b5e77a6fa952f
       size: 59
@@ -61,3 +61,3 @@ stages:
       hash: md5
-      md5: 7fd106ab6e8533ae9b2668201b5f3e1f
-      size: 5810561
+      md5: c30956fc1d16dad69e89942c2d9521ff
+      size: 255457
  • Three places changed, all inside the train stage: the recorded parameter, the hash of the metrics file and the hash of the model.
  • The ingest section is identical, which is the same fact the skip message stated, written down.
  • The model shrank from 5810561 bytes to 255457. A forest of depth 4 holds far fewer nodes, and the lock file records the size as well as the hash.

Putting the parameter back, and the run cache

Change MAX_DEPTH back to 12 and the pipeline does not retrain. DVC has run this exact combination of inputs before and kept the outputs, so it restores them instead.

bash
dvc repro
Captured from a real run
Stage 'ingest' didn't change, skipping
Stage 'train' is cached - skipping run, checking out outputs
Updating lock file 'dvc.lock'

is cached means the outputs came out of .dvc/cache, not out of a fresh fit, and dvc metrics show is back to 4.6996, 0.8485 and 6.0721. The cache is doing two jobs at once: keeping versions of data files, and keeping the results of stage runs it has seen before.

Sending the pipeline's outputs to the remote

Stage outputs are cached data like any other, so one push sends them to the remote the dataset already went to.

bash
dvc push
Captured from a real run
4 files pushed

Four contents: the raw CSV, the train and test splits, and the model file. The metrics file is not among them, because cache: false left it to git.

dvc repro vs running the training script

python train_pipeline.pydvc repro
With nothing changedRuns everything againRuns nothing
After a parameter editRuns everything againRuns the stages that read it
Record of the runWhatever you loggeddvc.lock, committed with the code
Repeating an old resultRetrain and hopeRestored from the cache

Where repro changes how you work

  • Tuning. Edit one parameter, rerun the pipeline, read the metric. The steps that cannot have changed cost nothing.
  • Review. A pull request carries dvc.lock, so a reviewer can see which stage's inputs moved.
  • Scheduled retraining. The same command on a build machine does the work only when the data or the code has moved.
Watch out. A stage is skipped on the strength of its recorded hashes, so a dependency you forgot to list makes the skip wrong rather than fast. If a run surprises you by skipping, read the stage's deps before you doubt the result, and use dvc repro -f to force the whole pipeline through once.
Try it yourself
  • Add a comment line to src/sp/components/model_trainer.py and run dvc repro. The file's hash changed, so train reruns even though the result is the same.
  • Delete artifacts/model_trainer/model.joblib and rerun. DVC restores it from the cache rather than retraining.
  • Run dvc repro -f and watch both stages run with nothing changed at all.

You understood something today that you didn't yesterday.