Google Meridian: how Google's open-source MMM works, with a Python tutorial

Meridian is Google's open-source Bayesian marketing mix model and the official successor to LightweightMMM. Its defining idea is that you state what you believe about each channel's ROI before the data arrives, so it's worth understanding what those beliefs say.

Mike Taylor By Mike Taylor · Updated · 12 min read

Google Meridian is Google’s open-source marketing mix modeling framework: a Python library that fits a Bayesian model of your sales or KPI on media spend, controls and time effects, at national level or across regions, and that lets you state your prior beliefs about each channel’s ROI directly. Google made it generally available on 29 January 2025 as the official successor to its earlier LightweightMMM, and in October 2026 it’s on version 2.1.0, with reach and frequency modeling, experiment calibration, a budget optimizer and a scenario planner built in.

I’ve advised Google on marketing mix modeling, and the original Vexpower MMM path taught LightweightMMM as the Python option, so I’ve watched this project go from an “unofficial” library to the thing Google’s measurement teams now point advertisers towards. Meridian is the most opinionated of the big open-source tools, and the opinion is a Bayesian one: Your model starts from priors, and in Meridian the main priors are on ROI, which is the number marketers actually argue about. That makes it unusually easy to bring experiment results and business knowledge into a model, and unusually easy to bake in assumptions without noticing. I’ll explain how it works, what the default priors really believe (with an explorer you can use on your own numbers), and how to run it, using code I’ve tested against the current release.

Where Meridian came from: from LightweightMMM to version 2

Meridian builds on a line of Google research from 2017 onwards, in particular the Jin et al. paper on carryover and shape effects and the geo-level hierarchical model by Sun and colleagues, plus later work on ROI priors and on reach and frequency. The GitHub repository dates from January 2024, and LightweightMMM’s README records that Meridian was released under limited availability for selected advertisers on 7 March 2024. Tagged releases on GitHub started that summer (0.2.0 in July 2024), version 1.0.0 landed on 24 January 2025 and was the first release on PyPI, and LightweightMMM’s README was updated on 29 January 2025 to say Meridian was now available to everyone and that LightweightMMM was no longer supported.

Development has been brisk since. The changelog shows a steady run of 1.x releases through 2025 and 2026, adding automatic knot selection, an exploratory data analysis engine, model-quality checks, serialisation and the scenario planner. Version 2.0.0 on 2 September 2026 made the biggest change: It switched the default backend from TensorFlow to JAX (the TensorFlow backend still exists), added prior calibration from incrementality experiments, including Google’s new open-source geo-testing tool Meridian GeoX, and standardized how dates are passed to the analysis functions, which broke some older code. Version 2.1.0 followed on 17 September 2026 with a more readable way to specify calibration windows and holdouts using dates and channel names rather than index arrays. If you’re reading a tutorial from 2025, expect small API differences.

How the Meridian model works

At its core Meridian is a regression, like every MMM, but each part of it has a prior distribution and the whole thing is estimated by MCMC (the No-U-Turn Sampler, which draws thousands of plausible parameter sets consistent with both the priors and the data). Each media channel is transformed with adstock for carryover, using geometric or binomial decay over a maximum lag of eight periods by default, and a Hill saturation curve for diminishing returns. By default the Hill slope for paid media is fixed at one, which restricts every curve to be concave (no S-shapes), a deliberate choice so the budget optimizer always finds a single best answer. Adstock is applied before saturation by default; you can flip that with hill_before_adstock. Time effects such as trend and seasonality are handled with “knots”, a piecewise-linear baseline whose flexibility you control with the number of knots, and since 2025 there’s an automatic knot selection option (enable_aks=True).

Geo-level data is where Meridian earns its keep. If you have weekly data by region (DMAs, states, countries), each region gets its own media coefficients, but they’re drawn from a shared distribution, so regions with little data borrow strength from the others (partial pooling, which I explain on the hierarchical MMM page). Control variables are hierarchical across regions as well. Fifty regions over two years give you 5,200 observations instead of 104, and that extra variation, spend moving differently in different places, is often what separates a model that can tell channels apart from one that can’t. Meridian works nationally too, but you give up that advantage, as you’ll see in my run below.

Two other inputs are worth knowing about. For video and other channels where you can get reach and frequency, Meridian can model the two separately, with Hill and adstock applied in a fixed order, which lets it recommend an optimal frequency as well as a budget. And for paid search, Google recommends including Google search query volume as a control, to separate organic demand from the effect of the ads.

ROI priors: the default and what it actually believes

In most Bayesian MMMs you set priors on regression coefficients, which nobody in a marketing meeting can interpret. Meridian’s default is to parameterize paid media by ROI instead: You set a prior on each channel’s return on investment, and the model works out what coefficient that implies given the spend and the transformations. You can also choose priors on marginal ROI or on contribution share, or go back to plain coefficients (paid_media_prior_type in ModelSpec).

The default ROI prior for every paid channel is LogNormal(0.2, 0.9). Google’s documentation gives its median as about 1.22 and its mean as about 1.83. Its quoted intervals (80 percent between 0.5 and 6.0, 95 percent between 0.25 and 9.0) don’t quite match those parameters, though: Computed directly, 80 percent of LogNormal(0.2, 0.9) lies between about 0.39 and 3.9, and 90 percent between 0.28 and 5.4, which is what the explorer below shows. That’s a reasonable “we don’t know much” prior for most advertisers, but notice what it implies: about a 41 percent chance, before seeing any data, that a channel doesn’t pay back. Load the default below and then the experiment preset, and you’ll see how much an informative prior narrows things.

Interactive
What does your ROI prior actually believe?

Set a LogNormal ROI prior the way Meridian lets you, then read off what it says before the model has seen any data: the median, the 90 percent range and the chance the channel loses money.

Median ROI
–
Mean ROI
–
90% of prior mass
–
P(ROI < 1)
–

LogNormal(μ, σ) maths only, no model fitting. The shaded band is the central 90 percent of the prior and the red dashed line is breakeven, ROI = 1. μ and σ are the mean and SD of log ROI. The range mode matches Meridian's lognormal_dist_from_range (median = √(low × high)); the mean and SD mode matches lognormal_dist_from_mean_std. Revenue KPI assumed, so ROI = incremental revenue ÷ spend.

The “from an experiment” preset assumes a test found an ROI of 1.8 with a standard error of 0.3, and widens the SD to 0.5 before using it. That widening matters. Google’s calibration guidance points out that an experiment’s ROI and the model’s ROI often aren’t the same quantity: The model’s counterfactual is zero spend over the whole period, while a test measures the lift from a particular amount of spend over a few weeks, often at a different spend level, in a different season. So the prior should carry extra uncertainty beyond the test’s own standard error, and there’s no single formula for how much. If your experiment only applies to a window, ModelSpec can restrict the prior to that period (roi_calibration, or the older roi_calibration_period array), though Google recommends setting it over the full modeling window where you can. I cover the trade-offs of folding tests into a model on the calibration page, and the general logic of priors on the Bayesian MMM page.

Calibration, GeoX and the partner ecosystem

Calibration in Meridian means turning outside evidence into priors. You can do it by hand, as in the explorer, using two helpers in the prior_distribution module, lognormal_dist_from_range() (a low and high value with a probability mass between them) and lognormal_dist_from_mean_std(). Since version 2.0 there’s also a CalibrationBuilder that collects experiment results and builds calibrated ROI priors for you, and a recommendation feature that suggests which channels are most worth testing. Meridian GeoX, a separate open-source repository (google/meridian-geox), designs and analyzes geo experiments, including multi-cell tests, and converts their results into Meridian priors.

Around the library sits an ecosystem that’s useful to know about even if you never touch it. Google’s Meridian Partners directory listed 52 certified partners in October 2026, filterable by region and by whether they offer MMM, GeoX or both; the list is mostly agencies and measurement consultancies such as Adswerve, Jellyfish, Accenture, KINESSO and Ekimetrics. There’s an MMM Data Platform, available by request, that helps advertisers pull campaign data from Google Ads and Display & Video 360 into the right shape. And the repository now ships “agent skills” that help AI coding assistants configure models, which says something about where Google expects people to be writing their Meridian code.

How to install and run Meridian in Python

Meridian needs Python 3.11 to 3.13 and, for anything beyond a toy model, a GPU; the README says it’s tested on a T4 with 16 GB of RAM, and Google’s getting-started notebook takes about 25 minutes to sample on Colab’s free T4. On Linux with an NVIDIA GPU you install the CUDA extra; on a Mac or CPU-only machine, the plain package (there’s no official GPU support on macOS):

pip install --upgrade "google-meridian[and-cuda]"   # Linux + NVIDIA GPU
pip install --upgrade google-meridian                # macOS or CPU only

To check the current API end to end, I fitted Meridian 2.1.0 to the 104-week practice dataset used across this library, on a laptop CPU. The data has revenue and spend for search, social and video plus a promotion flag, and no regions, so this is a national model. The data goes in through a builder that maps your DataFrame columns to roles (spend doubles as the media metric because the file has no impressions):

import numpy as np
import pandas as pd
import tensorflow_probability.substrates.jax as tfp_jax
from meridian import constants
from meridian.analysis import analyzer, optimizer, visualizer
from meridian.data import data_frame_input_data_builder
from meridian.model import model, prior_distribution, spec

df = pd.read_csv("mmm-practice-synthetic.csv").rename(columns={"week": "time"})
channels = ["search", "social", "video"]

builder = data_frame_input_data_builder.DataFrameInputDataBuilder(
    kpi_type="revenue", default_kpi_column="revenue"
)
data = (
    builder.with_kpi(df)
    .with_controls(df, control_cols=["promotion"])
    .with_media(
        df,
        media_cols=[f"{c}_spend" for c in channels],
        media_spend_cols=[f"{c}_spend" for c in channels],
        media_channels=channels,
    )
    .build()
)

Then the priors and the model. Here search gets an informative prior, as if a test had put its ROI between 1.2 and 2.0 with 90 percent probability, and the other two keep the default LogNormal(0.2, 0.9). Passing arrays to LogNormal gives each channel its own prior, in channel order:

search = prior_distribution.lognormal_dist_from_range(1.2, 2.0, mass_percent=0.9)
prior = prior_distribution.PriorDistribution(
    roi_m=tfp_jax.distributions.LogNormal(
        np.array([float(search.loc), 0.2, 0.2]),
        np.array([float(search.scale), 0.9, 0.9]),
        name=constants.ROI_M,
    )
)
mmm = model.Meridian(input_data=data, model_spec=spec.ModelSpec(prior=prior))

mmm.sample_prior(200)
mmm.sample_posterior(n_chains=4, n_adapt=500, n_burnin=500, n_keep=500, seed=0)

Google’s own demo uses 10 chains with 1,000 adaptation steps, 500 burn-in and 1,000 kept draws, which is the better default if you have a GPU. After sampling, check convergence and look at the results. ModelReviewer(mmm).run() produces a health card of automated checks, the r-hat summary tells you whether the chains agree (values near 1.0 are good; Google treats under 1.2 as approximate convergence), and MediaSummary gives you the table of spend, contribution, ROI, marginal ROI and cost per incremental KPI with credible intervals:

mmm_analyzer = analyzer.Analyzer(model_context=mmm.model_context, inference_data=mmm.inference_data)
print(mmm_analyzer.rhat_summary())

print(visualizer.MediaSummary(mmm).summary_table())
visualizer.ModelFit(mmm).plot_model_fit()
visualizer.MediaEffects(mmm).plot_response_curves()

results = optimizer.BudgetOptimizer(mmm).optimize()   # fixed budget, ±30% per channel
results.output_optimization_summary("optimization_output.html", ".")

On my run the whole script took under a minute on a laptop CPU, every r-hat was under 1.01, and the posterior ROIs were 1.5 for search (90 percent credible interval 1.1 to 1.9), 1.2 for social (0.3 to 2.6) and 0.7 for video (0.2 to 1.7). Because the practice data is synthetic, you can check those against the truth, and it’s worth knowing exactly what that truth is: The file was generated from a plain linear equation with no noise, revenue = 20,000 + 1.4 × search + 0.8 × social + 0.6 × video + 3,000 × sin(πt/26) + 1,800 × promotion, with no adstock and no saturation at all. So the true ROIs are 1.4, 0.8 and 0.6. Search’s interval is tight mostly because I gave it a tight prior, and the posterior barely moved from it, which is exactly the “baked-in assumption” risk (here the prior happened to be close to right). Social’s estimate is too high and its interval is wide, because 104 national weeks with three correlated channels don’t contain much information once Meridian also has to estimate decay, half-saturation and a time baseline, so the default prior is doing a lot of the work. The fitted decay rates (posterior means between 0.3 and 0.5, with intervals spanning almost 0 to 0.9) aren’t a finding either: The true carryover is zero, and the model simply can’t rule out other values. The default optimization then moved about a third of video’s budget to search and social, which happens to be the right direction, but with intervals that wide I’d want a test on video before acting on it. (These are Vexpower’s synthetic numbers, not a benchmark.)

py
Meridian quickstart on the practice data (Python)The full script: data builder, custom ROI priors, sampling, r-hat check, media summary and default budget optimisation. Tested with google-meridian 2.1.0 on Python 3.13, CPU.
Download

For a reusable model, save it with meridian_serde.save_meridian(mmm, "model.binpb") from meridian.schema.serde (it needs the schema extra, pip install "google-meridian[schema]"), and load it later with load_meridian, so you don’t resample every time you want a chart. The Summarizer class writes a two-page HTML results report, and optimize() accepts a budget, a fixed or flexible budget scenario (flexible lets total spend change to hit a target ROI or marginal ROI), spend constraints per channel, and hypothetical future data for planning.

The budget optimizer and Scenario Planner

BudgetOptimizer.optimize() defaults to a fixed-budget scenario over the historical period: Keep total spend the same, and let each channel move up to 30 percent either way from its historical share to maximize incremental outcome. Because the default media curves are concave, there’s a single optimum. For planning ahead, you pass new data (spend patterns, costs and revenue per KPI for the future period) and the optimizer uses those instead of the history. The output includes a two-page HTML summary with the optimized allocation, the change in ROI and the response curves. I’d read that alongside my notes on budget allocation and scenario planning, because an optimizer will happily push spend into the part of a curve your data never observed.

The Meridian Scenario Planner, in open beta, takes a saved model and generates an interactive dashboard in Data Studio from a Colab notebook (Google’s notebooks also call it Looker Studio). Viewers can check model fit, see contribution and ROI by channel, compare ROI with marginal ROI, and rerun fixed or flexible budget optimizations with their own date ranges and channel bounds, without touching Python. For teams where the analyst isn’t the person making budget calls, that’s a meaningful improvement on emailing HTML files around, though the usual rule applies: Give people viewer rather than editor access, as Google’s own page recommends.

Strengths, limits and who Meridian is for

Meridian’s strengths follow from its design. ROI priors make Bayesian MMM legible to marketers and give experiments a natural way in, geo hierarchy extracts far more signal from regional data than any national model can, reach and frequency support is unique among the major open-source tools, and the surrounding pieces (health checks, two-pagers, the optimizer, the scenario planner, GeoX) mean you’re not building your own reporting layer. It’s also under heavier active development than any of its peers, with releases most months in 2026.

The limits are mostly about cost and control. Sampling is slow without a GPU once you have many regions, and the TensorFlow-to-JAX switch in 2.0 shows the API is still moving. The defaults are opinionated, concave curves only, a common ROI prior for all channels, and if you accept them unexamined, they shape your answer more than you might think on thin data, as my run shows. Google says it answers GitHub questions in weekly batches and rarely merges outside pull requests because the code is tied to internal systems, so you’re dependent on its roadmap. And as a Google product, it’s reasonable for a CFO to ask whether Google-built measurement flatters Google channels; the model is open source and nothing in it favors any channel, but it’s a fair question to be ready for, and an experiment-calibrated prior is the best answer to it.

I’d choose Meridian if you work in Python, have regional data or reach and frequency data, and have (or plan to run) experiments you want the model to respect; for a Google-heavy media mix it’s also the tool your Google account team will understand. If you want more freedom over the model’s structure, PyMC-Marketing is the more flexible Bayesian option, and if your team is in R with national data, Robyn is still a sound choice; the open-source library comparison puts all of them side by side. If you’re coming from LightweightMMM, my migration map lists where each function went. And if you’re still building intuition for what any of these models are doing, the Vexpower MMM course and the overview of what marketing mix modeling is are the place to start, because Meridian rewards people who know which assumptions they’re making.

Downloads

py
Meridian quickstart on the practice data (Python)National Meridian model with custom ROI priors, r-hat check, media summary and budget optimization. Tested with google-meridian 2.1.0 on CPU.
Download
csv
MMM practice dataset (104 weeks, CSV)Synthetic weekly revenue, search, social and video spend and a promotion flag.
Download

Frequently asked questions

What is Google Meridian?

Meridian is Google's open-source marketing mix modeling framework, written in Python and released under the Apache 2.0 license. It fits a Bayesian model, sampled with the No-U-Turn Sampler, that can run nationally or as a hierarchical geo-level model, and it lets you set priors directly on each channel's ROI. It also handles reach and frequency data, experiment calibration and budget optimization. Google made it generally available on 29 January 2025 as the successor to LightweightMMM.

Is Google Meridian free?

Yes. The library is free and open source on GitHub and PyPI. Your costs are compute, since Google recommends at least one GPU, and the analyst time to prepare data and review the model. Google also lists certified Meridian partners, mostly agencies and consultancies, who will run it for you for a fee.

What is the default ROI prior in Meridian?

For paid media channels the default ROI prior is LogNormal(0.2, 0.9), applied to every channel. Google's documentation gives its median as about 1.22 and its mean as about 1.83, and computing from those parameters, 90 percent of its mass sits between about 0.28 and 5.4. That implies roughly a 41 percent prior probability that any given channel's ROI is below 1. When the KPI isn't revenue and there's no revenue-per-KPI data, Meridian uses a prior on total media contribution instead.

Does Meridian need a GPU?

It doesn't strictly need one, but Google recommends at least one GPU and tests on a T4 with 16 GB of RAM. Its getting-started demo takes about 25 minutes to sample on a free Colab T4. A small national model will run on a laptop CPU in a few minutes with light sampler settings, but geo models with many regions are slow without a GPU.

What is the difference between Meridian and LightweightMMM?

Meridian is the official evolution of LightweightMMM, which Google stopped supporting and has archived. Meridian adds ROI-parameterized priors, reach and frequency modeling, built-in experiment calibration, automatic input scaling, knot-based time effects instead of a trend plus sinusoids, and a fuller geo hierarchy. It was built on TensorFlow Probability rather than NumPyro, and since version 2.0 it runs on JAX by default.

Can Meridian use experiment results?

Yes, through the priors. You can turn a lift test into a custom ROI prior for that channel, optionally restricted to the test period with the roi_calibration setting, and the CalibrationBuilder and Meridian GeoX tools automate parts of that translation. Google stresses that an experiment's ROI and the model's ROI are often defined differently, so the prior should be wider than the experiment's standard error.

References

  1. Meridian on GitHub (google/meridian): README Install commands, Python 3.11-3.13, GPU recommendation, T4 testing, support policy.
  2. Meridian CHANGELOG 1.0.0 on 24 January 2025; 2.0.0 (JAX default, experiment calibration) 2 September 2026; 2.1.0 17 September 2026.
  3. Meridian getting-started notebook (demo/Meridian_Getting_Started.ipynb) Current API: DataFrameInputDataBuilder, ModelSpec, sample_posterior, ModelReviewer, BudgetOptimizer, meridian_serde; 25-minute T4 sampling.
  4. Meridian source: prior_distribution.py Default priors and the lognormal_dist_from_range and lognormal_dist_from_mean_std helpers.
  5. Meridian docs: Default prior distributions LogNormal(0.2, 0.9) ROI prior with its stated median, mean and intervals; other defaults.
  6. Meridian docs: ROI priors and calibration Turning experiments into priors, estimand mismatch, roi_calibration.
  7. Meridian docs: Migrate from LightweightMMM Feature comparison and model differences.
  8. Meridian docs: Scenario Planner (open beta) Colab to Data Studio dashboard.
  9. Meridian Partners directory 52 certified partners listed in October 2026, filterable by region and MMM or GeoX.
  10. Meridian GeoX Open-source geo experiments that convert results into Meridian priors.
  11. LightweightMMM README (google/lightweight_mmm) Meridian limited availability from 7 March 2024; general release 29 January 2025.
  12. Google Research: Media Mix Model Calibration With Bayesian Priors The ROI-prior calibration approach Meridian is built on.
Mike Taylor
Mike Taylor

I've built marketing mix models for Monzo Bank, Booking.com, Time Out, The AA and others, advised Google, Appsflyer and Recast on MMM, and teach marketing mix modeling on LinkedIn Learning. Before that I co-founded a 50-person growth marketing agency. I write the Vexpower MMM course.

Keep reading

Learn MMM by doing itThe Vexpower course walks through thirty lessons on one invented brand, with simulators, Python labs and a decision case you can defend.
See the course