Skip to main content

How to Log a Matplotlib Figure to MLflow

· 6 min read
Jagdish Kumawat
Founder @ Dewiride

Charts explain a run better than numbers: a loss curve, predicted against actual values, a confusion matrix. mlflow.log_figure() saves a Matplotlib figure straight into the run, with no temporary file to create or clean up. This guide logs a chart as PNG and SVG, logs one from pyplot without leaking memory, and finds them in the MLflow UI.

Syntax​

Syntax
mlflow.log_figure(<figure>, "<folder>/<file name>.<extension>")
PartWhat it means
<figure>The chart to save: a Matplotlib Figure. MLflow also accepts Plotly figures
"<folder>/<file name>.<extension>"Where to save it inside the run, such as "plots/loss.png". MLflow creates the folder, and the extension picks the format

The figure goes into the active run's artifacts, the files a run keeps.

Prerequisites​

Step 1: Log a Figure​

Create figure_demo.py:

The figure_demo.py script in a macOS-style code window: it builds a 6 by 4 Matplotlib Figure with a validation loss line over five epochs, starts an MLflow run, logs the figure twice as plots/loss.png and plots/loss.svg, then lists the run's plots folder.

figure_demo.py
import mlflow
from matplotlib.figure import Figure

mlflow.set_tracking_uri("http://127.0.0.1:8080")
mlflow.set_experiment("figure-demo")

figure = Figure(figsize=(6, 4), layout="constrained")
axes = figure.subplots()
axes.plot([1, 2, 3, 4, 5], [0.92, 0.61, 0.47, 0.41, 0.38], marker="o")
axes.set(title="Validation loss by epoch", xlabel="Epoch", ylabel="Loss")

with mlflow.start_run(run_name="figure-demo") as run:
mlflow.log_figure(figure, "plots/loss.png")
mlflow.log_figure(figure, "plots/loss.svg")

client = mlflow.MlflowClient()
print([artifact.path for artifact in client.list_artifacts(run.info.run_id, "plots")])
  • mlflow.log_figure(figure, "plots/loss.png") saves the figure inside the run at that path. The folder, plots, is created for you.
  • The file name's extension picks the format: loss.png is an image, loss.svg a vector drawing that stays sharp at any size.
  • list_artifacts(run_id, "plots") lists what's in the run's plots folder.

The chart's numbers are made up to keep the example short; in real training you'd plot your own values.

Terminal
uv run python figure_demo.py
Output
2026/11/13 17:08:55 INFO mlflow.tracking.fluent: Experiment with name 'figure-demo' does not exist. Creating a new experiment.
🏃 View run figure-demo at: http://127.0.0.1:8080/#/experiments/8/runs/e7ebcaa08b684d4199fcd56dc4322ce3
🧪 View experiment at: http://127.0.0.1:8080/#/experiments/8
['plots/loss.png', 'plots/loss.svg']

Step 2: See It in the UI​

Open the run, click Artifacts, open plots and click loss.png:

The Artifacts tab of the figure-demo run in MLflow, with the plots folder holding loss.png and loss.svg, loss.png selected, its mlflow-artifacts path, and a preview of the validation loss chart falling over five epochs.

The preview shows the chart, its size and its path. The download icon saves the file.

Step 3: Log a Figure from pyplot​

If your code uses matplotlib.pyplot, the call is the same, but close the figure afterwards: pyplot keeps every figure in memory until you do.

figure_pyplot.py
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import mlflow

mlflow.set_tracking_uri("http://127.0.0.1:8080")
mlflow.set_experiment("figure-demo")

fig, ax = plt.subplots(figsize=(6, 4))
ax.bar(["mae", "rmse"], [0.328, 0.506])
ax.set_title("Error metrics")

with mlflow.start_run(run_name="figure-pyplot"):
mlflow.log_figure(fig, "plots/errors.png")
plt.close(fig)
print("Open figures after closing:", plt.get_fignums())
Terminal
uv run python figure_pyplot.py
Output
🏃 View run figure-pyplot at: http://127.0.0.1:8080/#/experiments/8/runs/ecc73482af0e4379bc86ff51b81006ee
🧪 View experiment at: http://127.0.0.1:8080/#/experiments/8
Open figures after closing: []

matplotlib.use("Agg") draws without a screen, which training jobs and servers usually don't have. plt.get_fignums() lists the figures pyplot still holds; after plt.close(fig) there are none. Building a Figure directly, as in Step 1, avoids the question altogether: How to Plot Predicted vs Actual Values with Matplotlib.

Common Mistakes​

MistakeWhat happensDo this instead
Saving the chart to a file, then logging the fileExtra code, and temporary files to clean upmlflow.log_figure() saves it straight into the run
Creating pyplot figures in a loop without closing themMemory grows with every figureplt.close(fig) after logging, or build a Figure directly
Logging every chart at the top levelThe Artifacts tab gets crowdedUse folders, such as plots/

Troubleshooting​

SymptomLikely causeFix
The chart is blankThe figure was logged before anything was drawn on itDraw first, then call log_figure()
plt.get_fignums() keeps growing in a long jobpyplot figures are logged but never closedplt.close(fig) after each log_figure()

FAQ​

Does the Artifacts tab preview SVG files?​

Yes. Both PNG and SVG files show a preview, with their size and path.

Do I need to call plt.savefig() first?​

No. log_figure() saves the figure for you, in the format the file name says.

Can I log Plotly charts too?​

Yes. log_figure() also accepts Plotly figures; MLflow's documentation lists the formats.

Where are the files stored?​

On the tracking server, in its artifact store: How to See Where MLflow Stores Runs and Artifacts.

Conclusion​

mlflow.log_figure() saved one Matplotlib figure into the run twice, as plots/loss.png and plots/loss.svg, with no temporary files. The Artifacts tab previewed the chart, and the pyplot version worked the same once plt.close() released the figure.

Additional Resources​