Instantiate generative processes by capability, not by namespace - #198
Open
ealt wants to merge 5 commits into
Open
Instantiate generative processes by capability, not by namespace#198ealt wants to merge 5 commits into
ealt wants to merge 5 commits into
Conversation
Design proposal for the precondition to deprecating simplexity/generative_processes in favour of the generators repo: teaching managed_run to instantiate a process that lives in the consumer's project. The conflict is narrower than "config-driven instantiation vs a library that refuses to be a dependency" -- Hydra already instantiates consumer-external code (torch.optim, HookedTransformer). It is that generative_process is the one component discovered by a namespace prefix and instantiated behind a nominal isinstance check, and generators' whole point is refusing to be a shared namespace. generators' SPEC licenses the fix: it specifies required results, not how they are organized. Findings are measured, not assumed (spike against the real code paths): - a vendored generators process already runs through generate_data_batch unmodified; no runtime change is needed, only discovery/validation - exactly 2 of 8 ABC members are missing (both log-space), and neither is on the training path -- evidence for a tiered contract - the current failure is silent: filter_instance_keys short-circuits before validation, so a vendored process yields components.generative_processes = None with one INFO line and no warning - a runtime_checkable core Protocol accepts both the vendored adapter and the native HiddenMarkovModel, so the relaxation is back-compatible by construction Recommends a structural Protocol outside the deprecated package, config-declared discovery with the namespace prefix kept as a fast path, loud failure on a declared-but-nonconforming section, and a project-local factory as the config shape (generators' composites take encode/decode callables that YAML cannot express). Deprecation is warn-and-document, not delete: simplexity is shared and has a reproducibility obligation. Open questions for review, including the strategic fork (does simplexity become a runner, or vendor generators internally), who owns the mixed-state-tree gap, and that generators' conformance vectors do not exist yet -- which is the only thing that could keep a modified vendored copy mathematically honest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrVfBeUFhMToafrbgdDGUo
Teaches run management to instantiate a generative process that lives in the consumer's project, which is the precondition for deprecating simplexity/generative_processes in favour of generators. generators distributes process code by copying, so a conforming process shares no namespace or base class with simplexity -- and simplexity identified processes by testing whether _target_ starts with "simplexity.generative_processes.". A namespace test can never pass for vendored code. Replaces that nominal test with a structural one, which is what generators' SPEC licenses: it fixes required results, "not how they are organized or computed" (SPEC.md section 2). This is not a new pattern here -- predictive models already accept torch.nn/equinox.nn/penzai.models targets and are instantiated with no type check at all. - simplexity/run_management/protocols.py: GenerativeProcess as a runtime_checkable Protocol over the operations the runner and training path exercise, plus LogSpaceGenerativeProcess for the two log-space operations that only belief-state analysis calls. The existing ABC satisfies both, so no in-repo process or config changes. - Discovery: a config section may declare `component: generative_process` to claim a process implemented outside simplexity; the namespace prefix stays as a fast path so existing configs and in-flight branches are untouched. filter_instance_keys is reimplemented over a config-aware predicate rather than gaining a parallel variant. - Fixes a silent failure: a section that declares a process and then fails validation now raises instead of being dropped, which previously left components.generative_processes as None -- indistinguishable from a config with no process. A rejected instantiation names the missing operations. The not-found log names what it considered. - generator/torch_generator accept any conforming process. Narrowing tokens at the generate boundary also clears pre-existing pyright looseness that eqx.filter_vmap had been masking. - PendingDeprecationWarning on simplexity.generative_processes. Ignored by default in CPython, so it adds no noise to teammates' runs. Nothing is removed or altered; full deprecation waits for generators to reach feature parity. - tests/vendored_process/: generators modules copied verbatim at b1242ea plus the adapter that is the worked example, and an end-to-end test training that vendored process under @managed_run() -- including that vocabulary resolution reaches the model's d_vocab, the second thing the namespace filter broke. Gates: 1033 passed (978 before), diff coverage 100%, total coverage 94.81% from 94.65%, ruff format/check and pyright clean of anything this branch introduces. Three ruff findings in tests/generative_processes/test_data_prefetcher.py and 20 pyright unresolved-import errors for the uninstalled penzai/aws extras pre-date this branch and are left in unrelated files. Design, tradeoffs and remaining open questions: docs/design/generators_instantiation.md Consumer-facing migration: docs/generators_migration.md Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrVfBeUFhMToafrbgdDGUo
Every reference said Astera-org/generators, which 404s. The repository is ealt/generators. The URL appears in a runtime warning teammates will see, so it needs to resolve. Records the underlying question in the design doc: whether generators is meant to become an Astera-org repository, which should be settled before consumers vendor from it in earnest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrVfBeUFhMToafrbgdDGUo
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
This was referenced Jul 31, 2026
CI's static-analysis job failed on the first push. Cause: generative_processes had no __init__.py, so `pylint simplexity` never walked into it -- 21 modules were silently outside pylint's scope while the score read a clean 10.00/10. Adding __init__.py to carry the pending-deprecation warning brought the whole subpackage into scope and surfaced 11 pre-existing messages. Suppresses those via per-file-ignores rather than refactoring a package that is pending deprecation, and files the underlying scoping gap as #199 -- a missing __init__.py silently shrinking linter coverage, with nothing surfacing it, is the more general bug. Also ignores docstring and naming rules for the verbatim vendored copies, which follow generators' conventions (no docstrings; Ts/T/Vs mirroring the mathematics). Editing them to satisfy this repo's style would defeat diffing against upstream. Fixes this branch's own pylint findings properly rather than suppressing them: docstrings on the remaining tests, HalfProcess built with type() like its neighbours instead of a method-less class, the ABC import hoisted out of a test body, unused predicate parameters underscored, and the NaN check switched from `loss == loss` to math.isfinite. pylint now exits 0 at 10.00/10, matching main. 1033 passed, coverage 94.81%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrVfBeUFhMToafrbgdDGUo
Reusing GenerativeProcess optimized the end state -- the call site reading correctly once the ABC retires -- but that end state is gated on an unresolved question, so the transition window is of unbounded length, and throughout it two classes named GenerativeProcess live in two modules. The defence that the ambiguity was "confined to readers rather than to code" is the argument for renaming, not against it. Readers are who naming serves, and these readers are teammates on 60+ branches reading tracebacks, error strings and review diffs where the bare name does not say which of the two it is. Python's protocols carrying no Protocol suffix (Iterable, Sequence) does not apply here: none of those coexist with a same-named ABC in the same codebase. The collision decides it, not the convention. Also renames LogSpaceGenerativeProcess to LogSpaceGenerativeProcessProtocol. It collides with nothing, but a suffixed and an unsuffixed name side by side in one module tells a reader nothing about which is a protocol. The class docstring now states the split outright rather than referring to the base class in passing. Records both this and the declaration-field name as decisions in the design doc, and renumbers the remaining open questions. pylint 10.00/10 exit 0, pyright and ruff clean of anything this branch introduces, 1033 passed, coverage 94.81% unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XrVfBeUFhMToafrbgdDGUo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Teaches run management to instantiate a generative process that lives in the consumer's project
rather than in
simplexity.generative_processes, and marks that package pending deprecation. Thisis the precondition for moving generative processes out to generators.
Nothing existing changes behaviour: every in-repo process, config and test works unmodified, and the
package emits only a
PendingDeprecationWarning.Motivation
generatorsdistributes process code by copying — "this repo is not a library, do not add it toyour dependencies" — so a conforming process shares no namespace and no base class with simplexity.
But simplexity decided whether a config section was a generative process by testing whether its
_target_starts with"simplexity.generative_processes.". A namespace test can never pass forvendored code, because refusing to be a shared namespace is the whole point of that distribution
model.
The conflict is narrower than "config-driven instantiation versus copy-don't-import". Hydra already
instantiates code from outside simplexity on every run —
torch.optim.Adam,HookedTransformer.The problem was specifically that
generative_processis the one component discovered by anamespace prefix and instantiated behind a nominal
isinstancecheck.So this replaces a nominal test (where does this code live?) with a structural one (what can
this object do?) — which is what generators' own spec licenses:
This is also not a new pattern here:
is_predictive_model_targetalready acceptstorch.nn,equinox.nnandpenzai.modelstargets, and_instantiate_predictive_modeldoes a barehydra.utils.instantiatewith no type check at all. The change brings the most rigid componentfilter into line with how the repo already treats components implemented upstream.
What was measured, not assumed
A spike vendored generators' ghmm module into a throwaway package and ran it through the real code
paths before any design was chosen:
generate_data_batchruns on it unmodified, correct shapes, normalizedobs_dist, correctseq_prob. No runtime change was needed; only discovery and validation.INFOline,no generative process configs found. No warning, no error.build_hidden_markov_model's output, so the relaxation is back-compatible by construction.What changed
run_management/protocols.py(new)GenerativeProcessas aruntime_checkableProtocol over the operations the runner and training path exercise, plusLogSpaceGenerativeProcessfor the two log-space operations only belief-state analysis calls.missing_generative_process_membersreports which members are absent.component: generative_processto claim a process implemented outside simplexity. The namespace prefix stays as a fast path, so existing configs and in-flight branches need no changes.utils/config_utils.pyfilter_instance_keys_bytakes a config-aware predicate and is now the single implementation;filter_instance_keysis a thin adapter over it, unchanged for its callers.components.generative_processesasNone— indistinguishable from a config with no process. A rejected instantiation names the missing operations, and the not-found log names what it considered.generator.py,torch_generator.pygenerative_processes/__init__.py(new)PendingDeprecationWarning. Ignored by default in CPython, so it adds no noise to normal runs while still surfacing in pytest.docs/design/generators_instantiation.md(design, tradeoffs, open questions) anddocs/generators_migration.md(consumer guide).Tiered contract
Today's ABC requires all eight members of every process. Measured usage is asymmetric: the training
and generation path uses seven and never touches log space, while
log_probability/log_observation_probability_distributionare called only bymixed_state_presentation.pyand bycomposite processes forwarding to sub-processes. Log-space is therefore a separate protocol —
generators does not provide those operations, and requiring them would block vendored processes for
a capability their runs never exercise.
Testing
tests/end_to_end/test_vendored_process_training.pyis the acceptance test: generators'modules copied verbatim into
tests/vendored_process/generators_copy/at commitb1242ea,plus a ~50-line adapter, training end-to-end under
@managed_run(). Neither the copies nor theadapter reference
simplexity.generative_processes. It also asserts that vocabulary resolutionreaches the model's
d_vocab, which was the second thing the namespace filter broke — the samefilter routes
vocab_sizeto the predictive-model config.tests/run_management/test_protocols.py— contract pinned; parametrized tests that omitting anycore member breaks conformance and is named in the error; log-space extension boundaries.
tests/run_management/test_generative_process_discovery.py— declared foreign processinstantiates and resolves vocabulary; simplexity processes need no declaration; undeclared foreign
targets are not claimed; a config with no process is still not an error; declared-but-invalid
raises; non-process targets fail naming the absent operations.
test_generative_process_config.pyandtest_config_utils.pyfor the declarationpredicates and the reshaped filter.
1033 passed, 2 skipped (978 before). Diff coverage 100%. Total coverage 94.81%, up from
94.65%.
ruff format,ruff checkandpyrightclean of anything this branch introduces.Narrowing
tokensat thegenerateboundary also cleared pre-existing pyright looseness thateqx.filter_vmaphad been masking by erasing the return type toAny.Breaking changes
None. The existing ABC satisfies both new protocols, the namespace fast path is retained, and no
in-repo process, config or test required modification. Existing tests passing unchanged is the
back-compat evidence.
simplexity/generative_processes/__init__.pyis new — the package was previously an implicitnamespace package.
Suppressions
None. No
# type: ignoreor# noqaadded.Two configuration additions, both deliberate:
pyproject.tomlignoresD(docstring) rules fortests/vendored_process/generators_copy/*.Vendored code arrives with upstream's conventions — generators'
ZEN.mdsays "Eschew excessdocumentation", so its modules have no docstrings. Editing them to comply would defeat the ability
to diff against upstream and re-copy, which is how the vendoring model picks up improvements. Every
consumer vendoring generators will need the same exclusion; it is called out in the migration guide.
[tool.coverage.report] exclude_linesadds^\s*\.\.\.$for Protocol method bodies — thestructural counterpart to the
@abstractmethodentry already there. It excludes 16 statementsrepo-wide, all of which were previously uncovered stub bodies, so nothing executable is hidden.
Out of scope, filed separately
Everything below is deliberately not in this PR. Each is a real decision or defect surfaced by
this work, not a nice-to-have.
Gating the deprecation timeline:
of mixed-state presentation. Deprecation is gated on generators reaching parity, but belief-tree
enumeration is analysis over a process rather than an operation of one, and generators' spec
covers belief updates, not tree enumeration. If it is not going upstream, parity is unreachable by
definition and the gate never opens. Two
eden-experimentsconsumers import it directly.(log-space operations, framing-token augmentation, process-zoo breadth), with a measured inventory
and a note on which of them are scope questions rather than work.
exist.
SPEC.md§7 specifies the encoding; no fixtures ship. This is the weakest link in the wholechain: a structural protocol check verifies that operations exist, never that they compute the
right thing, so the vectors are the only mechanism that would catch a vendored copy whose
modifications broke the mathematics.
Decisions adjacent to this change:
transition_matrices.pydeprecated too? 16 process families vs generators' ~5, and it is datarather than behaviour. Changes what "parity" means.
declaration in the 11 in-repo process configs. Mechanical; deferred purely to avoid churn against
in-flight branches.
Astera-org? It is currently
ealt/generators;Astera-org/generatorsdoes not exist. This PRreferences the URL that resolves, including inside a runtime warning string, so a later move means a
release to change it.
Pre-existing defects:
skipped
generative_processesentirely. The package had no__init__.py, sopylint simplexitynever walked into it: 21 modules, ~3,750 lines, outside linter scope while the score read a clean
10.00/10. Adding
__init__.pyhere (to carry the deprecation warning) brought them into scope andsurfaced 11 pre-existing messages, which this PR suppresses via
per-file-ignoresrather thanrefactoring a package that is being deprecated. The scoping gap — coverage shrinking invisibly, with
nothing to surface it — is the more general bug and is what the issue tracks.
local commands do not match CI's.
CLAUDE.md/CONTRIBUTING.mddocument--extra dev --extra pytorch, while CI uses--extra aws --extra dev --extra penzai --extra pytorch.With the documented commands, pyright reports 20 unresolved imports and a full
pytestrun abortswith 3 collection errors, none of which happen in CI. Plus three ruff findings in
test_data_prefetcher.pythat CI tolerates because itsruff checkstep is|| true. Untouchedhere to keep this diff focused.
Open questions for review
Full list in
docs/design/generators_instantiation.md§9. The two that most affect this PR's shape:GenerativeProcessinsimplexity/run_management/protocols.py,reusing the name so the call site reads correctly once the ABC retires — at the cost of two
same-named classes during the transition. The runner references only the protocol, so the
ambiguity is confined to readers.
GenerativeProcessProtocolis a cheap rename if preferred.component, matching the runner's existing vocabulary(
Components,component_name).component_typewas unavailable — factored-process specs alreadyuse it for
hmm/ghmm. Rejectedkind(less specific) andexternal: true(names the exceptionrather than the role, so it ages badly once declaration becomes the norm).
60+ open branches; eventually every process config declares itself and the prefix path retires.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XrVfBeUFhMToafrbgdDGUo