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.
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.
export PYTHONPATH=src
dvc reproRunning 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.
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: 59Running it again with nothing changed
dvc reproStage '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.
MAX_DEPTH: 4dvc reproStage '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_DEPTHis not one of them, so DVC had no reason to download the data again. - train ran.
MAX_DEPTHis 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
git diff --unified=1 dvc.lock@@ -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.
dvc reproStage '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.
dvc push4 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.py | dvc repro | |
|---|---|---|
| With nothing changed | Runs everything again | Runs nothing |
| After a parameter edit | Runs everything again | Runs the stages that read it |
| Record of the run | Whatever you logged | dvc.lock, committed with the code |
| Repeating an old result | Retrain and hope | Restored 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.
deps before you doubt the result, and use dvc repro -f to force the whole pipeline through once.Related
- Previous: dvc.yaml stages, deps and outs
- Next: DVC metrics and plots
- Reference: dvc repro
- See also: The model registry, which compared these two runs
- Add a comment line to
src/sp/components/model_trainer.pyand rundvc repro. The file's hash changed, so train reruns even though the result is the same. - Delete
artifacts/model_trainer/model.jobliband rerun. DVC restores it from the cache rather than retraining. - Run
dvc repro -fand watch both stages run with nothing changed at all.
You understood something today that you didn't yesterday.