Troubleshooting

A runbook for the moments most likely to stop a first run: a failed install, a slow or diverging sampler, a rejected dataset, a model that won't reload, or a web app that won't start. Each entry names the symptom, the cause, and the fix. For "what does a negative coefficient mean?"-style questions, see the conceptual FAQ instead.

First, confirm the install is intact

A surprising share of "it's broken" reports are a half-finished environment. Verify:

import mmm_framework
print(mmm_framework.__version__)   # expect 0.2.0

from mmm_framework import load_example, BayesianMMM
panel = load_example("national")   # bundled data — no files needed
print(panel.n_obs, "observations,", panel.n_channels, "channels")

If that prints without error, the library is installed correctly and the problem is in your data, config, or environment — read on.

Installation & environment

ImportError / ModuleNotFoundError on import mmm_framework

Dependencies aren't installed, or you're in the wrong environment. From a source checkout, install the library (and the dev/app groups if you need tests or the web app):

# library only
$ uv sync
# or, with pip
$ pip install -e .

# add dev + app tooling (tests, API backend, Streamlit)
$ uv sync --group dev --group app

If you installed into a global Python but run inside uv, prefix commands with uv run so they use the project's virtual environment.

JAX / NumPyro won't import or crashes on Apple Silicon

NumPyro is the fast default sampler and rides on JAX. On macOS (Intel or Apple Silicon) install the CPU JAX build — it is fast enough for MMM-scale models and avoids the experimental Metal backend, which is not supported here:

$ pip install --upgrade "jax[cpu]"

If a fit still fails inside JAX, fall back to the pure-PyMC sampler (ModelConfigBuilder().bayesian_pymc()) or an approximate fit to isolate whether the problem is JAX-specific.

PyTensor / C-compiler ("clang") link errors on macOS

PyTensor compiles the model graph with a C++ compiler. A conda base environment often puts its own clang first on PATH, which then fails to link against the system libraries. Pin the system compiler in ~/.pytensorrc:

[global]
cxx = /usr/bin/clang++

Then reactivate your shell and re-run. (Deactivating the conda base environment entirely also works.)

Frontend won't start (npm run dev errors)

Install the React app's packages once before starting the dev server:

$ cd frontend && npm install
$ npm run dev

Sampling & fitting

"Fitting is slow" — a national model takes minutes

A 104-week national model should fit in seconds, not minutes. Two levers:

Approximate ≠ trustworthy intervals

MAP / Laplace / ADVI / Pathfinder are for model checking, not decisions. Their R-hat and ESS are undefined and their uncertainty is not calibrated — re-fit with full NUTS before trusting any ROI, credible interval, or budget recommendation.

"Chains have not converged" — R-hat > 1.01 or low ESS

The framework annotates every fit with convergence diagnostics and warns when they fail — do not interpret an unconverged posterior. Usual remedies, in order:

The full escalation ladder — which diagnostic points to which fix, in cost order — is formalized in the research post When Sampling Fails.

High R-hat but each chain looks fine — suspected multimodality

When chains individually mix well yet disagree with each other (high split R-hat with clean per-chain traces), each chain has usually found a different posterior mode — label switching in latent-class models, a reflected latent factor, or an adstock↔carryover ridge. More draws will not fix this. Confirm it with Sequential Monte Carlo, an exact sampler whose independent tempered runs do not get mode-locked:

# exact sampler — approximate stays False; R-hat compares independent runs
results = model.fit(method="smc", draws=2000, chains=4)
print(results.diagnostics["log_marginal_likelihood"])  # model evidence, for Bayes factors

If SMC confirms real modes, the fix is identification — sign-anchor the latent factor, constrain or re-center the offending component — not a different sampler. SMC also estimates the log marginal likelihood, so competing fixes (adstock families, mediation structures) can be compared by Bayes factor. Note SMC is slower than NUTS on well-behaved posteriors and degrades on very high-dimensional models — it is a diagnosis and evidence tool, not a speedup.

Divergences won't clear

Divergences signal the sampler can't explore part of the posterior — usually a funnel in a hierarchical or tightly-constrained parameter. Raise target_accept (0.9 → 0.95 → 0.99), add data or a lift-test calibration to pin the offending parameter, or simplify the structure (fewer per-geo effects) until they clear. A handful of divergences on an otherwise-converged fit with good ESS is usually tolerable; dozens are not.

Out-of-memory during sampling

Reduce the sampler footprint — draws, chains, or both:

results = model.fit(draws=1000, tune=500, chains=2)

Data & MFF

MFF validation errors when loading data

The loader expects Master Flat File (MFF) long format: a Period column, the dimension columns (Geography, Product, …), and a VariableName / VariableValue pair — one row per variable per period. A raised MFFValidationError lists exactly which columns or variables are missing. Common causes: a KPI or channel named in the config that doesn't appear in VariableName, or a wide (one-column-per-variable) file that hasn't been melted to long form (mff_from_wide_format does that). The MFF format section shows the exact shape.

"KPI has N missing period(s) … non-contiguous" warning

Adstock carryover and trend estimation assume an evenly-spaced series, so a gap in the KPI weeks is flagged. Fill the missing periods (carry-forward, interpolation, or a zero where a zero is real) before fitting. The loader anchors the expected weekly grid to your data's own weekday, so genuinely-contiguous Monday- or Sunday-dated weekly data will not trip this warning — if it fires, there is a real gap.

Negative or implausible spend values

Media variables must be non-negative — a negative spend or impression value is almost always a join or sign error upstream. Screen for it before modeling with the data-quality checks or the Data Studio EDA tab, which flags negatives, outliers, and missing weeks and offers one-click treatments.

DAG validation fails when building an extension model

The DAG builder rejects graphs it can't compile into a valid model (a cycle, a mediator with no path to the outcome, an edge referencing an unknown node). The DAGValidationError message names the offending node or edge; fix the graph and rebuild. See the causal-inference page for what makes a well-formed measurement DAG.

Saving & loading models

A saved model won't unpickle after an upgrade

Model artifacts are written with cloudpickle, which is version-sensitive: a large mismatch in cloudpickle, PyMC, or ArviZ between the environment that saved the model and the one loading it can break the unpickle. (Models saved under 0.1.0 reload cleanly under 0.2.0 despite the PyMC 5→6 jump — see the changelog.)

The robust recovery: re-fit from the config

A fitted model is fully determined by its spec + data, both of which are saved alongside the trace. If an artifact won't load, rebuild it from those rather than fighting the pickle — the fit is cheap (seconds for a national model) and reproducible given the recorded random_seed. In the agent/API path this is the build_and_fit(spec, dataset_path) flow; from the library, reload the panel and re-run BayesianMMM(...).fit() with the saved settings. Pinning the exact version the model was saved with (pip install mmm-framework==<version>) to load-then-re-save is the fallback when a re-fit isn't possible.

"cloudpickle version" errors across machines

Keep the modeling environment consistent — pin mmm-framework to an exact release (==0.2.0) and use the same lockfile everywhere you save and load models, so cloudpickle and the core stack match.

Web app & agent

Which backend am I running?

The modern React app talks to the agent API, which runs fits in-kernel — no Redis, no worker. Only the legacy Streamlit app needs Redis + an ARQ worker. If you're on the React path, none of the Redis fixes below apply. See Running the Web Application.

Redis connection refused (legacy Streamlit path)

Start a local Redis server before the legacy REST API and worker:

$ redis-server
Jobs submitted but never finish (legacy Streamlit path)

The ARQ worker that processes fitting jobs isn't running. Start it:

$ cd api && arq worker.WorkerSettings
Agent uses the wrong LLM, or auth fails

The agent's model is chosen by config/model_config.yaml (or MMM_LLM_* environment variables), not hard-coded. On Google Cloud, Vertex uses Application Default Credentials — grant the VM service account roles/aiplatform.user and run gcloud auth application-default login. See the platform overview and the project's docs/model-configuration.md for provider setup (Anthropic, OpenAI, Google, Vertex, or a local LM Studio model).

Still stuck?

If none of the above fixes it: