Release notes¶
Changes in each published mllm-shap version. Newest first.
Unreleased¶
Fixed¶
Wav2Vec2Ctcno longer prints the Transformers load report for the training-onlymasked_spec_embedweight; any other missing weight is logged as a warning.
0.6.0 (2026-10-12)¶
Added¶
The
looestimator gives leave-one-out values \(v(N) - v(N \setminus \{i\})\) from \(n + 2\) generations, the baseline any attribution method should beat.The
reference_wervalue function scores1 - min(1, WER)against a reference such as a transcript, or against the full answer.deletion_curve()removes the top-kplayers at once for eachkand rescores, to test faithfulness with a second value function.copy()returns an independent copy of a registry.
0.5.0 (2026-10-12)¶
Added¶
Snapshot JSON carries audio: a message may give a
"content"list of text and audio parts ("path"relative to the snapshot file, or base64"data"), somllm-shap explainruns on recordings (Command line).sgpa_segmentsoptiontranscript: align_onlyuses the transcript only to build the players and sends every coalition’s audio alone, so the values reflect what the model hears, not what it reads.
0.4.1 (2026-10-11)¶
Added¶
The
transformersbackend hears audio: a checkpointAutoModelForCausalLMdoes not know (or anybackend.options.model_class) loads with its processor and takes audio parts, for example Granite Speech (Audio input).backend.options.attn_implementationis passed tofrom_pretrained.The
apibackend sends audio parts as OpenAIinput_audioparts (base64 WAV) instead of refusing them.The
lmstudiooptionaudio: transcriptsends the text parts of a message with audio; by default audio is still refused, since the LM Studio SDK takes none.
Changed¶
The
transformersextra addstorchaudio(Granite Speech’s feature extractor needs it), and the backend reads the model config before loading.
0.4.0 (2026-10-11)¶
Added¶
The
mock_audiobackend (MockAudioBackend) echoes message words and answers one token per voiced run of each audio part, so audio players get attributions in tests and demos without a model. It reads 16-bit WAV without theaudioextra.
0.3.1 (2026-10-11)¶
Fixed¶
The
apibackend gives an empty reply the logprobs()instead ofNone(LM Studio sendslogprobs: nullfor it), soreanalyzeaccepts archives of runs that contain empty generations.
0.3.0 (2026-10-11)¶
Added¶
players: tokenstakes the plugin optionroles(likemessages): only the words of the listed roles become players, other messages stay present.plot_conversation()andrender_conversation_html()draw a conversation message by message under role and speaker headers, with token or message players shaded in place and text that is no player in grey. Messages with audio players get a waveform strip shaded on the same colour scale.The
sgpa_segmentsoptiontext_rolesalso makes the words of typed messages of those roles players, so typed and spoken words share one run.
Changed¶
The project is renamed to
mllm-shap: installmllm-shapfrom PyPI, importmllm_shap, and find the repository atPawlo77/MLLM-SHAP. Environment variables start withMLLM_SHAP_, entry-point groups withmllm_shap., and the base error isMllmShapError. The oldnlp-shappackage is archived.plot_audio_segments()withvalueslightens every second segment and keeps fills translucent, so neighbouring words stay distinct (equal values no longer merge into one block) and the waveform shows through.
0.2.1 (2026-10-10)¶
Added¶
explanation.players: sgpa_segments(SgpaSegmentPartitioner) makes each spoken word of an audio message’s transcript a player. ItsPlayerSpancarries the newsamplesrange, anddeletecuts absent audio whilepadandneutralsilence it. See Audio alignment.mllm_shap.Message.with_audio()replaces a message’s first audio part;AudioSegmentrecordstoken_indexand the rough CTC edgesctc_start_timeandctc_end_time.plot_audio_segments()draws aligned segments over the waveform, the shift of each boundary from its CTC edge and the RMS energy;valuescolours segments by attribution.
0.2.0 (2026-10-10)¶
Added¶
mllm_shap.alignment(audioextra):SpectrogramGuidedAligneraligns a transcript to its speech in anAudioPartand returns oneAudioSegmentper token. Each boundary moves to a quiet point between words.Wav2Vec2Ctcruns a Hugging Face Wav2Vec2 model (transformersextra); anyCtcModelcan replace it. See Audio alignment.
0.1.19 (2026-10-09)¶
Fixed¶
The transformers backend warns once when
explanation.kv_cacheruns withfloat16orbfloat16weights: cached prefixes round differently from one forward pass, so greedy outputs can differ from the cache off. The backends guide no longer promises equality;backend.options.dtype: float32is identical.The LM Studio backend accepts a model key that LM Studio lists once per downloaded quantisation; earlier versions refused it as ambiguous.
0.1.18 (2026-10-09)¶
Fixed¶
Without model embeddings (mock, LM Studio and API backends),
embedding_mode: contextualnow hashes the generated text alone, soexplainandreanalyzeagree andreanalyze --strictaccepts the archive (embedding_source: hash). The values of such runs change.The
token_textandtoken_barrenderers no longer log matplotlib’sFailed to find font weight semiboldwarning.Renderer labels no longer overlap:
token_textwraps long text onto new lines, andtoken_barputs value labels beyond the bar ends and the sign legend outside the axes.Complementary and Neyman samplers now evaluate all
nsingleton pairs({i}, N - {i})after the grand and empty pair. On an additive 8-player game atbudget.fraction: 0.5the attribution sum was on average 1.7 abovegrand_value - base_value; it is now within 0.03.Neyman phase one walks its grid size by size, round-robin over players, so a short budget leaves the same sizes empty for every player; its small-budget error now matches complementary sampling (Approximate estimation).
Complementary and Neyman budgets never drop below
2n + 2masks (2 ** nwhen smaller), so all singleton pairs fit. With the earlier2nfloor, the gamev(S) = |S|with three players gave[1, 1, 1.333]instead of[1, 1, 1].Monte Carlo and other partial-sample estimates log a
WARNINGnaming the players with no observed marginal (their value is 0), typically at small budgets where only the grand and empty coalitions were sampled.The transformers backend reuses cached prompt-prefix KV state (every prompt token but the last). A miss computes the prefix once and stores it. A hit copies the longest cached prefix, crops it to the shared tokens and extends it. No prompt token is processed twice; earlier versions ran one forward pass per prefix length.
kv_cache.max_entries(default 8) bounds the prefix states, and model calls (tokenization included) are serialized.metrics.kv_cache_reused_tokensreports the prompt tokens served from the cache next tometrics.kv_cache_hits. Both count the current run only, also whenExplainRunner(backend=...)reuses the backend. Infloat32greedy outputs are identical with the cache on and off.The transformers backend runs on Apple MPS when available. The new
backend.options.dtype(auto,float32,float16,bfloat16) defaults toauto: bfloat16 on CUDA and MPS when supported, float16 on other accelerators and float32 on CPU. Earlier versions loaded float16 on every accelerator, which overflows to NaN on many bfloat16-trained models.backend.options.revisionpins the Hub revision of the model and tokenizer.The transformers backend computes static and contextual embeddings only when the value function needs them (see
RequiresEmbeddings), andexplainthen scores the model’s embeddings. Earlier versions ran two extra forward passes per coalition and discarded the result.reanalyze()still scores hash embeddings of the archived text, because archives store text only. Embedding-based values of a transformers run therefore differ betweenexplainandreanalyze.The manifest records which embeddings the run scored:
embedding_source(none,hashorbackend) andembedding_model. Reanalyzing abackendrun with an embedding value function logs aWARNING;ExplainRunner.reanalyze(..., strict=True)(mllm-shap reanalyze --strict) raisesReanalyzeMismatchErrorinstead.The HTTP API backend retries status 408, 429, 500, 502, 503 and 504 and transport errors up to
backend.max_retriestimes. The backoff is exponential and honoursRetry-Afterup tobackend.timeout_s. Non-JSON responses raiseBackendUnavailableError, and at most 1024 responses are cached.The HTTP API backend sends
MLLM_SHAP_API_KEYas the bearer key to any host. It sendsOPENAI_API_KEYonly when that is unset andbackend.api_hostisapi.openai.com; earlier versions sent it to every host, local servers included.The LM Studio backend retries only connection failures and re-raises
ValueErrorandTypeErrorat once. It matches downloaded models exactly first, then by the prefix of the last path segment, then by substring; an ambiguous id raises with the list of candidates.With
auto_download, the LM Studio backend refuses (BackendUnavailableError, listing the hits) when no repository hit is flagged exact or matches the name. Earlier versions downloaded the first hit.explain_sync()called inside a running event loop raises aRuntimeErrorthat points toawait runner.explain(...). With an injected backend, repeatedexplain_synccalls run on one event loop owned by the runner, so loop-bound clients and locks keep working.reanalyze()refuses withArchiveIncompleteErrorthe archive of a run that failed part-way, even before its first coalition. It also refuses archives without the grand or empty coalition, full-budget archives (budget.fraction: 1.0) missing rows, and archives not written byExplainRunner.Re-running explain replaces the archive of a run that did not finish without needing
on_exists: overwrite.load()opens the database read-only and writes nothing, so archives on read-only storage can be reanalyzed. It raisesArchiveNotFoundError(aFileNotFoundError) whenmanifest.jsonorarchive.sqliteis missing, instead of creating an empty database.load()raisesArchiveCorruptErrorwhen either file cannot be read or the manifest’s fields are invalid, so the command line exits with 2 on a damaged archive.Monte Carlo sampling counts the grand and empty coalitions toward the budget; earlier versions could exceed it by two calls.
Progress callbacks report one growing plan and a monotonic finished count across the two Neyman phases.
Partial-sample marginal aggregation renormalizes over observed strata with the semivalue’s stratum probabilities, for any estimand.
explanation.neymanandexplanation.embedding_modenow reach the Neyman estimator and the embedding value functions; earlier versions ignored them.neyman.initial_fractionmust lie in(0, 1]andneyman.initial_num_samplesbe at least 1.run_idhashes the model id of an injected backend, not the one inbackend.model_id.
Changed¶
The
apibackend requiresbackend.api_host; theOPENAI_BASE_URLenvironment fallback and thehttp://127.0.0.1:1234/v1default are gone, so a configuration without a host fails at load time withConfigErrorinstead of silently targeting a local server.The coalition scheduler runs jobs in an
asyncio.TaskGroup. The first failing job still cancels the others and its exception is raised unwrapped, not as anExceptionGroup, also when it sits next to a nested group of cancellations.estimator: complementaryandneyman_ccwithestimand: banzhafare refused withConfigErrorwhen the config is validated instead of failing after every coalition was generated.The
context_idargument ofgeneration_record_from_text()is ignored and raises aDeprecationWarning; it will be removed in 0.2.0. Custom backends callgeneration_record_from_text(text).Plugin factories receive a
PluginContext(the run’s config, the plugin’s options and the registry), and every built-in plugin class takes it as its first constructor argument.resolve()takes the context as its third argument. Third-party plugins must acceptcontext.LM Studio settings
hub_search,quantization,auto_downloadandserialize_generatemoved frombackendtobackend.optionsand are validated by the LM Studio backend; unknown keys raiseConfigError.Backends take a
PluginContextinstead of aBackendConfig, andgenerate(snapshot, generation)receives the wholeGenerationConfig.Messageholdscontentparts (TextPart,AudioPart);Message(role=..., text=...)still works andMessage.textjoins the text parts. Snapshot ids of text-only conversations are unchanged; audio is detected by part type, no longer by anaudio:text prefix.Partitioners return a
PlayerSetwith onePlayerSpanper player. Absence policies and labels read the spans instead of re-tokenising, so custom partitioners work inExplainRunner, and text outside every span is kept. Rendered messages keep their original whitespace instead of collapsing it to single spaces.GenerationConfig,DedupConfigandKvCacheConfiglive inmllm_shap.domain.config(still importable frommllm_shap.pipeline.config); the runtime no longer imports the pipeline.import mllm_shapno longer reads[tool.logging]from the nearestpyproject.toml. SetMLLM_SHAP_LOGGING_CONFIGor callmllm_shap.configure_logging().The orchestrator logs run start, base generation, scheduled coalitions and run duration at
INFO.kv_cache.enableddefaults tofalse; newkv_cache.max_entries.New
backend.timeout_s(default 120) andbackend.max_retries(default 3); the HTTP client caps connections atexplanation.max_inflight.Run archives store generation text in the SQLite row (no
blobs/directory), use WAL journaling and carryuser_version3 and manifest schema version 3. Archives written by 0.1.17 raiseManifestVersionError; re-run explain to rebuild them.Coalition jobs are rendered lazily while the scheduler consumes them.
explain_config_from_yaml()raisesConfigError(not pydantic’sValidationError) for settings that contradict each other, and for an empty document or a YAML root that is not a mapping (earlier aTypeError).The
transformersextra requirestransformers>=4.56; the build backend isuv_build0.12.
Added¶
Message(speaker=...)names who wrote a message when people share a role. It is part of the snapshot id and of snapshot JSON ("speaker"), backends send"<speaker>: <text>", and labels andto_dictreport it.explanation.players: messages(MessagePartitioner) makes each message one player labelled"<role>[/<speaker>] <n>: <text>"; plugin optionroleskeeps other roles always present.value_fn: logprobscores the mean log probability of each coalition’s own generated tokens. The transformers andapibackends return logprobs (SupportsLogprobs); other backends raiseConfigError. Archives store them (user_versionand manifest schema 4), so older archives need a newexplain.mllm-shapcommand line (Command line):explain(JSON result, optional PNG chart),reanalyze(--strictrefuses runs scored with backend embeddings),pluginsandconfig --example/--check.The command line uses
argparseonly. It exits with 2 for invalid input (arguments,--log-level, configuration, snapshot, unreadable input files or archives) and with 3 when the backend is unavailable or the connection fails. Any other exception propagates with its traceback.mllm_shap.ConversationSnapshot.from_json()andmllm_shap.ConversationSnapshot.to_json()read and write snapshots as{"turns": [{"messages": [{"role": ..., "text": ...}]}]}, with an optional"snapshot_id"when the id is not the content-derived one, so the id (and therun_id) survives a round trip. Messages with several content parts are refused, like audio.mllm_shap.ExplainRunOutput.to_dict()returns a JSON-compatible result with a stable key order, including one label per player (text and character span);ExplainRunOutput.player_setholds the explained players as aPlayerSet.configure_logging()acceptslevel=andjson=Trueto log the pipeline’s stage events to stderr without a TOML file. Events are plain lines or one JSON object per line, and the package logger then stops propagating to the root logger.json=Truewith aconfig_pathraisesValueError.config/logging.example.tomlis a starting point forMLLM_SHAP_LOGGING_CONFIG.explanation.budget.evaluationssets an absolute budget instead ofbudget.fraction: that many coalition evaluations in total, the empty and grand coalitions included, at any number of players.Each estimator applies its floor to
budget.evaluationsand the ceiling2 ** n: 2 formc,2n + 2forcomplementaryandneyman_cc(which also round an odd count down to whole pairs).exactaccepts onlyfraction: 1.0, and setting both keys raisesConfigError.max_playersrefusesbudget.evaluationsabove2 ** max_players. Configurations withoutevaluationsserialise as before, so their run ids do not change.explanation.plugin_options: per-plugin options keyed by<group>/<name>(for exampleabsence_policies/pad: {placeholder: "<unk>"}) or by the bare plugin name when only one plugin of the run has it. Built-in plugins declare their options in aPluginOptionsmodel; unknown keys, wrong types and keys that name no plugin of the run raiseConfigErrorbefore the run starts, naming the group, plugin and key.Error hierarchy rooted at
MllmShapError:PluginNotFoundError,PluginConflictError,PluginLoadError,ConfigError,ArchiveNotFoundErrorandArchiveCorruptErrorjoin the existing errors.Config cross-validation:
estimator: exactrequiresbudget.fraction: 1.0andneyman_ccrejectsinclude_minimal_masks: true.Capability protocols
SupportsKvCache,SupportsAclose,FittableValueFunctionandRequiresEmbeddings; any value function withfitis fitted on the run corpus, not only TF-IDF.GenerationConfig.compute_embeddingsasks a backend for model embeddings.default_registry()registers all eight plugin groups (estimands and renderers included) and loads third-party entry points; registering a name twice raisesPluginConflictErrorunlessreplace=True.registrycan be passed torender_attribution()to reuse custom renderers.Top-level exports for every built-in plugin class, the absence policies,
TokenPartitioner, all errors, the observability and progress protocols anddisplay_attribution_html.ExplainRunner(backend=...)reuses one backend acrossexplaincalls;close(),aclose()andwith/async withblocks close it.New
explanation.max_players(default 24): a run whose budget plans more than2 ** max_playerscoalitions (budget.fraction * 2 ** nfornplayers, whatever the estimator) raisesConfigError.
Removed¶
generation.precompute_base(the base generation always runs; remove the key from YAML configs),CoalitionJob.utility,CoalitionRecord.utility,SystemRolesSetup,CooperativeGame.empty_valueandmasking.prefix_tree.PrefixTreeStub.
Documentation¶
New “How a run works” page (stages, scheduler counters, archives,
reanalyze, progress and telemetry,explainvsexplain_sync), “Choosing an estimator”, and “Coming from MLLM-Shap-legacy” guides; citing and contributing pages.Configuration reference and the public-exports table are generated from the installed package; every runnable guide snippet is executed on each docs build from the page source.
Theory pages state the efficiency convention
sum(values) = grand_value - base_value(exact on the full coalition table only), the stratified partial-sample estimand, and corrected DOIs (Štrumbelj & Kononenko 2014, Young 1985).make docsbuilds with-W --keep-going -n;make -C docs linkcheckchecks external links.
Tooling¶
Upgrades and releases:
make upgrade-checkandmake upgrade(scripts/upgrade.py) list and apply new package versions, prek hook revisions, workflow action pins, the uv version and Obsidian plugins, except the pins inpublish.yaml.scripts/pr_policy.py --bumpderives the next version from the commits since the last tag.Benchmarks use
pytest-benchmark:make benchwrites a JSON report andmake bench-regressionfails when a median is more than 25% slower than the committedtests/benchmarks/baseline.json. New benchmarks cover exact, stratified and complementary aggregation, the scheduler, TF-IDF scoring and an end-to-end mock run.make mutaterunsmutmuton the estimation package.Tests that build a wheel or reach the network are marked
slowand run inmake tests-allonly.Practical tests:
make practical FEATURE=<id|glob|all>runs about 30 scenarios (markerpractical) onQwen/Qwen2.5-1.5B-InstructandSmolLM2-360M-Instructon a GPU, with rubric checks and a logit judge;make checkexcludes them.Deep practical tier:
make practical TIER=deepruns ninedeep.*scenarios onQwen/Qwen2.5-3B-Instructunder an 8 GB VRAM cap. They cover error versus budget against a 10-player exact reference, estimator stability, Banzhaf, a counterfactual pair per value function, a 10-turn three-speaker thread, the KV cache, archive reanalysis and CLI parity.
0.1.17 (2026-10-07)¶
Fixed¶
Exact, Monte Carlo, complementary and Neyman attributions now satisfy efficiency on the full coalition table (
budget.fraction: 1.0); approximate budgets converge to it as coverage grows. Earlier versions omitted the grand coalition (exact, Monte Carlo) and the \((N, \emptyset)\) pair (complementary, Neyman), which under-reported every player by its \(S = N\setminus\{i\}\) marginal. Re-run explanations produced by 0.1.x.Coalition payoffs stay aligned with their masks when
kv_cache.enabledreorders jobs for prefix reuse; earlier versions could score a coalition with another coalition’s generation.Monte Carlo partial samples use a stratified mean of observed marginals instead of an un-normalised weighted sum.
Complementary and Neyman aggregation pair masks by complement, independent of evaluation order.
Neyman phase one no longer stalls on cells that cannot be filled, and phase two now runs inside
ExplainRunnerand spends the remaining budget. The allocation uses the complement column \(n - j\).InferenceSchedulersurfaces the first coalition failure and cancels the remaining jobs.
Changed¶
run_idincludes a digest of the configuration and model id. Re-running into an existing archive raisesArchiveExistsErrorunlessexplanation.archive.on_existsisoverwrite.Manifest schema version 2 records the estimator, value function, normalizer, absence policy, partition, backend, model, player ids and the full config. Archives also carry SQLite
user_version2; older archives raiseManifestVersionErroron load.reanalyze()re-aggregates with the archived estimator, refuses a mismatched estimator, absence policy or partition withReanalyzeMismatchError, and reproducesexplainvalues for the same value function.Value functions score records built from the generated text in both
explainandreanalyze(generation_record_from_text()).hash_mask()is stable across processes.Complementary and Neyman estimators raise
ValueErrorfor thebanzhafestimand; earlier versions returned complementary Shapley values under a Banzhaf label.
Added¶
base_valueandgrand_value.TwoPhaseEstimatorprotocol; every estimator exposesestimate_attributions(masks, payoffs, aggregator).
Removed¶
InferenceScheduler.run_stream(userun_iter()) and themllm_shap.backends.mock.generationmodule.
Earlier releases¶
0.1.16 (2026-07-19): coalition progress callbacks on the scheduler and runner;
BackendConfig.serialize_generatefor LM Studio;DeletePolicyrenders the empty coalition; a Neyman infinite loop at small player counts is fixed.0.1.15 (2026-07-15): runtime micro-benchmarks and the
make benchandmake bench-regressiontargets.0.1.14 (2026-07-15): Extending mllm-shap guide; renderers load lazily, so
import mllm_shapworks without[viz].0.1.13 (2026-07-15):
mllm_shap.vizwith thetoken_textandtoken_barrenderers and the[viz]extra (on PyPI; nov0.1.13git tag).0.1.12 (2026-07-15):
ApiBackendfor OpenAI-compatible HTTP APIs and the[api]extra.0.1.11 (2026-07-15):
TransformersTextBackend, prefix KV caching and the[transformers]extra.0.1.10 (2026-07-15):
LmStudioBackendand the[lmstudio]extra.0.1.9 (2026-07-15):
reanalyze(), stage telemetry andPerfSummary.0.1.8 (2026-07-14):
ExplainRunner, the orchestrator and themockbackend.0.1.7 (2026-07-14): TF-IDF, embedding and logprob value functions, presentation normalizers and
GenerationRecord.0.1.6 (2026-07-14): the
mc,complementaryandneyman_ccestimators.0.1.5 (2026-07-13):
ExactEstimatorandrun_iter().
0.1.4 (2026-07-13)¶
Added¶
RunArchivewith SQLite index, blob storage, and lazyhistory_lazy()iteration.CoalitionRecordrows with mask bytes, utility, timing, and cache-hit metadata.CoalitionDedupRegistryandbuild_coalition_key()SHA256 coalition keys.HotResultStoreLRU cache for recent generations.InferenceSchedulerwith boundedmax_inflightconcurrency andSchedulerMetrics.
Documentation¶
Theory and usage guides for the runtime archive, dedup, and scheduler.
API reference page for
mllm_shap.runtime.Example notebook
examples/runtime_core.ipynb.
0.1.3 (2026-07-12)¶
Added¶
Masking codec:
PackedMask,MaskCodecwith legacy-compatible packed hashing.MaskSpacefor projecting feature splits to full masks.View-based
MaskedSnapshotandMaskBuilderwith structural sharing.Absence policies:
DeletePolicy,PadPolicy,NeutralPolicy.TokenPartitionerand built-in plugin registration fortokens,delete,pad, andneutral.numpycore dependency for mask packing.
Documentation¶
Theory and usage guides for coalition masking and absence policies.
API reference page for
mllm_shap.masking.Example notebook
examples/masking_views.ipynb.
0.1.2 (2026-07-12)¶
Added¶
Domain types:
ConversationSnapshot,Turn,Message,PlayerSet,CoalitionMask,CooperativeGame, and conversation enums.Protocols for backends, estimators, value functions, partitions, absence policies, and embedding providers.
PluginRegistrywith packaging entry-point discovery.ExplainConfig(Pydantic v2) with YAML load/save helpers.Configuration guide and expanded API reference pages.
Documentation¶
Sphinx theory pages for cooperative games and estimands (Shapley vs Banzhaf).
Usage guide for estimand aggregators, results, and manifests.
Expanded API reference for public modules.
Example notebook
examples/estimands_toy_game.ipynb.Furo theme, getting-started page, embedded notebook rendering, and API module layout.
Tooling¶
make notebookstarget to execute example notebooks in place before commit.
0.1.1 (2026-07-12)¶
Added¶
ShapleyAggregatorandBanzhafAggregatorfor coalition-weighted attribution from masks and payoffs.Estimandenum and wire types for honest estimand labelling.ExplainResultwith requiredestimandfield.RunManifest,RunManifestPayload, andparse_manifest()for run-archive metadata.EstimandAggregatorprotocol.
0.1.0 (2026-07-12)¶
Added¶
Initial PyPI package layout and
mllm_shapimport surface.Logging bootstrap from
[tool.logging]inpyproject.tomlvialogging518.