reqm 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. reqm-0.1.0/.gitignore +8 -0
  2. reqm-0.1.0/.pre-commit-config.yaml +16 -0
  3. reqm-0.1.0/.vscode/settings.json +10 -0
  4. reqm-0.1.0/CLAUDE.md +256 -0
  5. reqm-0.1.0/LICENSE +21 -0
  6. reqm-0.1.0/PKG-INFO +256 -0
  7. reqm-0.1.0/README.md +244 -0
  8. reqm-0.1.0/TECH_DEBT.md +9 -0
  9. reqm-0.1.0/docs/config_management.md +227 -0
  10. reqm-0.1.0/docs/torch_integration.md +216 -0
  11. reqm-0.1.0/examples/__init__.py +1 -0
  12. reqm-0.1.0/examples/estimators/__init__.py +25 -0
  13. reqm-0.1.0/examples/estimators/configs/__init__.py +2 -0
  14. reqm-0.1.0/examples/estimators/configs/ensemble/mean_median.yaml +18 -0
  15. reqm-0.1.0/examples/estimators/configs/filters/no_filter.yaml +6 -0
  16. reqm-0.1.0/examples/estimators/configs/filters/outlier.yaml +8 -0
  17. reqm-0.1.0/examples/estimators/configs/filters/top_k.yaml +7 -0
  18. reqm-0.1.0/examples/estimators/configs/mean_outlier.yaml +12 -0
  19. reqm-0.1.0/examples/estimators/configs/mean_simple.yaml +11 -0
  20. reqm-0.1.0/examples/estimators/configs/median_simple.yaml +11 -0
  21. reqm-0.1.0/examples/estimators/configs/trimmed_mean.yaml +12 -0
  22. reqm-0.1.0/examples/estimators/datasets.py +25 -0
  23. reqm-0.1.0/examples/estimators/filters/__init__.py +1 -0
  24. reqm-0.1.0/examples/estimators/filters/api.py +32 -0
  25. reqm-0.1.0/examples/estimators/filters/no_filter.py +22 -0
  26. reqm-0.1.0/examples/estimators/filters/outlier.py +39 -0
  27. reqm-0.1.0/examples/estimators/filters/top_k.py +30 -0
  28. reqm-0.1.0/examples/estimators/quants/__init__.py +1 -0
  29. reqm-0.1.0/examples/estimators/quants/api.py +42 -0
  30. reqm-0.1.0/examples/estimators/quants/ensemble.py +47 -0
  31. reqm-0.1.0/examples/estimators/quants/mean.py +38 -0
  32. reqm-0.1.0/examples/estimators/quants/median.py +37 -0
  33. reqm-0.1.0/examples/estimators/quants/trimmed_mean.py +50 -0
  34. reqm-0.1.0/examples/estimators/scripts/__init__.py +7 -0
  35. reqm-0.1.0/examples/estimators/scripts/compare.py +67 -0
  36. reqm-0.1.0/examples/estimators/scripts/evaluate.py +59 -0
  37. reqm-0.1.0/examples/estimators/scripts/inspect_config.py +42 -0
  38. reqm-0.1.0/examples/estimators/scripts/sweep.py +53 -0
  39. reqm-0.1.0/examples/estimators/scripts/validate_configs.py +45 -0
  40. reqm-0.1.0/examples/torch_models/__init__.py +25 -0
  41. reqm-0.1.0/examples/torch_models/configs/__init__.py +1 -0
  42. reqm-0.1.0/examples/torch_models/configs/linear_simple.yaml +3 -0
  43. reqm-0.1.0/examples/torch_models/configs/mlp_large.yaml +5 -0
  44. reqm-0.1.0/examples/torch_models/configs/mlp_small.yaml +5 -0
  45. reqm-0.1.0/examples/torch_models/models/__init__.py +1 -0
  46. reqm-0.1.0/examples/torch_models/models/api.py +62 -0
  47. reqm-0.1.0/examples/torch_models/models/linear.py +37 -0
  48. reqm-0.1.0/examples/torch_models/models/mlp.py +49 -0
  49. reqm-0.1.0/examples/torch_models/scripts/__init__.py +0 -0
  50. reqm-0.1.0/examples/torch_models/scripts/audit.py +42 -0
  51. reqm-0.1.0/examples/torch_models/scripts/evaluate.py +54 -0
  52. reqm-0.1.0/examples/torch_models/torch_quant.py +96 -0
  53. reqm-0.1.0/llms.txt +309 -0
  54. reqm-0.1.0/pyproject.toml +35 -0
  55. reqm-0.1.0/src/reqm/__init__.py +6 -0
  56. reqm-0.1.0/src/reqm/overrides_ext.py +165 -0
  57. reqm-0.1.0/src/reqm/quant.py +120 -0
  58. reqm-0.1.0/src/reqm/quant_manager.py +264 -0
  59. reqm-0.1.0/tests/__init__.py +0 -0
  60. reqm-0.1.0/tests/dummy_objects.py +30 -0
  61. reqm-0.1.0/tests/test_config_module_one/__init__.py +0 -0
  62. reqm-0.1.0/tests/test_config_module_one/atomic_no_target.yaml +4 -0
  63. reqm-0.1.0/tests/test_config_module_one/atomic_simple.yaml +4 -0
  64. reqm-0.1.0/tests/test_config_module_one/chain_end.yaml +7 -0
  65. reqm-0.1.0/tests/test_config_module_one/chain_middle.yaml +7 -0
  66. reqm-0.1.0/tests/test_config_module_one/depends_on_atomic.yaml +7 -0
  67. reqm-0.1.0/tests/test_config_module_one/depends_on_nested.yaml +7 -0
  68. reqm-0.1.0/tests/test_config_module_one/invalid_no_package_global.yaml +3 -0
  69. reqm-0.1.0/tests/test_config_module_one/multi_dep.yaml +8 -0
  70. reqm-0.1.0/tests/test_config_module_one/sub/deep/deeply_nested.yaml +4 -0
  71. reqm-0.1.0/tests/test_config_module_one/sub/depends_on_root.yaml +7 -0
  72. reqm-0.1.0/tests/test_config_module_one/sub/nested.yaml +4 -0
  73. reqm-0.1.0/tests/test_config_module_one/with_interpolation.yaml +4 -0
  74. reqm-0.1.0/tests/test_config_module_one/with_missing_interpolation.yaml +3 -0
  75. reqm-0.1.0/tests/test_overrides_ext.py +256 -0
  76. reqm-0.1.0/tests/test_quant.py +310 -0
  77. reqm-0.1.0/tests/test_quant_manager.py +345 -0
  78. reqm-0.1.0/tests/test_torch_quant.py +360 -0
  79. reqm-0.1.0/uv.lock +540 -0
reqm-0.1.0/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ .venv/
5
+ dist/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
@@ -0,0 +1,16 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.4
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: local
10
+ hooks:
11
+ - id: pytest
12
+ name: pytest
13
+ entry: uv run pytest
14
+ language: system
15
+ pass_filenames: false
16
+ always_run: true
@@ -0,0 +1,10 @@
1
+ {
2
+ "[python]": {
3
+ "editor.defaultFormatter": "charliermarsh.ruff",
4
+ "editor.formatOnSave": true,
5
+ "editor.codeActionsOnSave": {
6
+ "source.fixAll.ruff": "explicit",
7
+ "source.organizeImports.ruff": "explicit"
8
+ }
9
+ }
10
+ }
reqm-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,256 @@
1
+ # CLAUDE.md — reqm development guide
2
+
3
+ This file is for AI coding agents working **on** the reqm library itself.
4
+ For guidance on using reqm in your own project, see `llms.txt` or `README.md`.
5
+
6
+ ---
7
+
8
+ ## Tech debt policy
9
+
10
+ Any consciously taken shortcut or design caveat **must** be recorded in `TECH_DEBT.md`
11
+ before the code is committed. Each entry must include:
12
+ - What was skipped or compromised
13
+ - Why (the constraint that forced it)
14
+ - The concrete action needed to repay it
15
+
16
+ Tech debt that is not recorded does not exist — and will bite us later.
17
+ Reviewing and repaying `TECH_DEBT.md` items is a normal part of the workflow,
18
+ not an afterthought.
19
+
20
+ ---
21
+
22
+ ## What reqm is
23
+
24
+ `reqm` (Ridiculously Easy Quant Manager) is a directory-based config management
25
+ and object factory built on Hydra's `instantiate`. It eliminates "Hydra ceremony"
26
+ (context managers, `@hydra.main`, etc.) while keeping Hydra's config composition
27
+ power. A `QuantManager` takes an importable Python config module (a directory with
28
+ `__init__.py` and YAML files) and provides a uniform API to list, validate, load,
29
+ and build objects from those configs.
30
+
31
+ ---
32
+
33
+ ## Core abstractions
34
+
35
+ ### Quant
36
+ The unit reqm builds and manages. An abstract base class that users subclass.
37
+
38
+ Requirements:
39
+ - Must be callable (`__call__`)
40
+ - Must implement `dummy_inputs() -> dict` returning example inputs
41
+ - Constructor args are defined in YAML config, not hardcoded
42
+
43
+ The factory calls each Quant with its own `dummy_inputs()` at build time.
44
+ This makes Quants **auditable** — not just interface-compliant, but provably runnable.
45
+
46
+ ### QuantManager
47
+ Directory-based config manager. Takes an importable Python module whose
48
+ directory contains YAML config files and treats it as the Hydra config root.
49
+
50
+ Key methods:
51
+ - `list_configs()` — list all YAML configs in the module
52
+ - `validate()` — check that all configs have `# @package _global_`
53
+ - `get_config(name)` — load and resolve a config as OmegaConf DictConfig
54
+ - `get_raw_config(name)` — load and resolve a config as a YAML string
55
+ - `build(name)` — instantiate the `_target_` object from a config
56
+
57
+ All methods accept optional `config_overrides` (dict) and `param_overrides`
58
+ (Hydra CLI-style strings) for runtime customization.
59
+
60
+ See `docs/config_management.md` for detailed design rationale.
61
+
62
+ ---
63
+
64
+ ## Architecture decisions (do not violate)
65
+
66
+ 1. **No framework ownership.** reqm must never require `@hydra.main`,
67
+ `hydra.initialize()`, or any context manager at the user's call site.
68
+ reqm handles all Hydra plumbing internally.
69
+
70
+ 2. **Fail fast.** `dummy_inputs` is called at build/get time, not at inference.
71
+ Errors surface early and loudly, not silently in production.
72
+
73
+ 3. **Minimal public API.** The surface users touch is:
74
+ - `Quant` (base class to subclass)
75
+ - `QuantManager(config_module)` (config management and object building)
76
+ Keep everything else internal.
77
+
78
+ 4. **Declarative over imperative.** Config files express what to build.
79
+ Python code expresses the interface contract. Never mix them.
80
+
81
+ 5. **Directory-based, not registry-based.** Config modules are importable
82
+ Python packages containing YAMLs. No global registry, no eager imports.
83
+ Every YAML must declare `# @package _global_` for explicit composition.
84
+
85
+ 6. **Generic instantiation.** `QuantManager.build()` instantiates whatever
86
+ `_target_` points to — it does not enforce that the result is a Quant.
87
+
88
+ ---
89
+
90
+ ## Code conventions
91
+
92
+ ### Docstrings
93
+ Every public class, method, and function must have a Google-style docstring
94
+ with an `Examples:` section containing runnable code. Small LLMs helping users
95
+ will rely on these.
96
+
97
+ ```python
98
+ def build(self, config_name: str, *, ...) -> object:
99
+ """Build an object from a config via hydra.utils.instantiate.
100
+
101
+ Loads the config, applies overrides, resolves interpolations, and
102
+ passes the result to Hydra's recursive instantiation.
103
+
104
+ Args:
105
+ config_name: Config name (relative path, no .yaml extension).
106
+
107
+ Returns:
108
+ The instantiated object.
109
+
110
+ Raises:
111
+ FileNotFoundError: If config_name does not exist.
112
+ hydra.errors.InstantiationException: If instantiation fails.
113
+
114
+ Examples:
115
+ >>> import my_configs
116
+ >>> from reqm import QuantManager
117
+ >>> QM = QuantManager(my_configs)
118
+ >>> model = QM.build("summarizer/prod")
119
+ >>> result = model(text="Hello world")
120
+ """
121
+ ```
122
+
123
+ ### Type annotations
124
+ All public APIs must be fully type-annotated. Internal helpers should be
125
+ annotated where it aids clarity.
126
+
127
+ ### Error messages
128
+ Errors must be actionable. Always tell the user what alias, config path, or
129
+ interface was involved and what to do next.
130
+
131
+ ```python
132
+ # Good
133
+ raise FileNotFoundError(
134
+ f"Config '{config_name}' not found in config module at {self._config_dir}. "
135
+ f"Available configs: {self.list_configs()}"
136
+ )
137
+
138
+ # Bad
139
+ raise FileNotFoundError(f"Config not found: {config_name}")
140
+ ```
141
+
142
+ ### No magic
143
+ Avoid metaclass magic, import hooks, or decorators that alter behavior invisibly.
144
+ If something happens, it should be traceable by reading the call stack.
145
+
146
+ ---
147
+
148
+ ## File layout
149
+
150
+ ```
151
+ src/reqm/
152
+ ├── __init__.py # public API exports: Quant, QuantManager
153
+ ├── quant.py # Quant ABC definition
154
+ ├── quant_manager.py # QuantManager, ConfigValidationError
155
+ └── overrides_ext.py # @override / @allow_any_override support
156
+
157
+ examples/
158
+ └── estimators/ # end-to-end example project
159
+ ├── __init__.py # QM = QuantManager(configs) — single construction point
160
+ ├── filters/ # non-Quant configurable dependencies
161
+ │ ├── api.py # Filter base class
162
+ │ ├── no_filter.py, outlier.py, top_k.py
163
+ ├── quants/ # Estimator Quant subclasses
164
+ │ ├── api.py # Estimator(Quant) base class
165
+ │ ├── mean.py, median.py, trimmed_mean.py, ensemble.py
166
+ ├── scripts/ # uniform call site demos (import QM from parent)
167
+ │ ├── evaluate.py, inspect_config.py, compare.py
168
+ │ ├── validate_configs.py, sweep.py
169
+ └── configs/ # config module for QuantManager
170
+ ├── filters/ # filter configs (non-Quant)
171
+ ├── *.yaml # estimator configs (compose filters via defaults)
172
+ └── ensemble/ # ensemble configs (compose estimators via defaults)
173
+ └── torch_models/ # PyTorch integration example (requires torch)
174
+ ├── __init__.py # QM = QuantManager(configs)
175
+ ├── torch_quant.py # TorchQuant bridge (copy into your project)
176
+ ├── models/ # Regressor(TorchQuant) subclasses
177
+ ├── configs/ # model configs
178
+ └── scripts/ # evaluate.py, audit.py
179
+ ```
180
+
181
+ ---
182
+
183
+ ## What to build next
184
+
185
+ Completed:
186
+ 1. ~~`quant.py` — `Quant` ABC with `dummy_inputs` abstract method~~
187
+ 2. ~~`overrides_ext.py` — `@override` / `@allow_any_override` support~~
188
+ 3. ~~`quant_manager.py` — `QuantManager` class + `ConfigValidationError`~~
189
+
190
+ 4. ~~`__init__.py` — wire up public API exports (`Quant`, `QuantManager`)~~
191
+ 5. ~~`examples/` — estimators example project with eval script~~
192
+
193
+ Remaining:
194
+ 6. Integration tests with Quant + QuantManager together
195
+
196
+ ---
197
+
198
+ ## Testing
199
+
200
+ Run tests with:
201
+ ```bash
202
+ uv run pytest
203
+ ```
204
+
205
+ ### Write tests abundantly
206
+
207
+ Tests are first-class documentation here. Write as many as are appropriate —
208
+ they double as concrete, runnable usage examples that LLMs and humans can learn from.
209
+
210
+ Rules:
211
+ - Every public API must have multiple tests covering: happy path, edge cases, error cases
212
+ - Test names must be descriptive sentences: `test_get_returns_correct_quant_instance`,
213
+ not `test_get`
214
+ - Each test should be self-contained and readable in isolation — no shared mutable state
215
+ - Tests that demonstrate usage patterns are as valuable as tests that catch bugs
216
+ - When a new feature is added, write tests before or alongside (not after) the implementation
217
+
218
+ ### Quant ABC signature override
219
+
220
+ `Quant.__call__` is defined as `**kwargs` and decorated with `@allow_any_override`.
221
+ This is intentional. Subclasses narrow the signature to their specific API.
222
+ The `@allow_any_override` marker tells type checkers and linters that the
223
+ signature change is deliberate, not a mistake.
224
+
225
+ ```python
226
+ # Base — accepts anything
227
+ class Quant(EnforceOverrides):
228
+ @abstractmethod
229
+ @allow_any_override
230
+ def __call__(self, **kwargs) -> Any: ...
231
+
232
+ # Subclass — narrows to specific API (correct and intended)
233
+ class Summarizer(Quant):
234
+ @override
235
+ def __call__(self, text: str, max_tokens: int = 512) -> str: ...
236
+ ```
237
+
238
+ ### Two-level signature narrowing (TorchQuant pattern)
239
+
240
+ `TorchQuant.forward` has `@allow_any_override` so domain base classes can
241
+ narrow it. But the domain base class must **not** use `@allow_any_override`
242
+ on its own `forward` — this locks the signature for all concrete models:
243
+
244
+ ```
245
+ TorchQuant → forward(**kwargs) @allow_any_override (open)
246
+ Regressor → forward(x: Tensor) NO @allow_any_override (locked)
247
+ LinearModel → forward(x: Tensor) must match Regressor exactly
248
+ BadModel → forward(a, b) REJECTED at class definition time
249
+ ```
250
+
251
+ This pattern is critical for uniform call sites. Without the domain base
252
+ locking the signature, each concrete model could use a different `forward`
253
+ signature, breaking evaluation scripts at runtime instead of at definition time.
254
+
255
+ Always create a domain base class between TorchQuant and concrete models.
256
+ See `docs/torch_integration.md` for the full explanation.
reqm-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jkvc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
reqm-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,256 @@
1
+ Metadata-Version: 2.4
2
+ Name: reqm
3
+ Version: 0.1.0
4
+ Summary: Ridiculously Easy Quant Manager — config-based aliased object factory with enforced interfaces
5
+ Project-URL: Homepage, https://github.com/jkvc/reqm
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.9
9
+ Requires-Dist: hydra-core>=1.3
10
+ Requires-Dist: overrides>=7.7.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # reqm
14
+
15
+ **R**idiculously **E**asy **Q**uant **M**anager
16
+
17
+ Directory-based config management and object factory built on [Hydra](https://hydra.cc).
18
+
19
+ ---
20
+
21
+ ## The problem
22
+
23
+ Hydra is excellent for config-driven instantiation. But using it as a general object factory requires ceremony:
24
+
25
+ ```python
26
+ # You have to do all of this just to instantiate one object
27
+ with hydra.initialize(config_path="conf"):
28
+ cfg = hydra.compose(config_name="my_model")
29
+ model = hydra.utils.instantiate(cfg.model)
30
+ ```
31
+
32
+ This ceremony means Hydra stays in the lab. It's awkward in a notebook, verbose in a service, and doesn't belong in production call sites.
33
+
34
+ `reqm` gives you Hydra's power — config-driven instantiation, composable overrides, recursive object graphs — with none of the ceremony:
35
+
36
+ ```python
37
+ from reqm import QuantManager
38
+ import my_configs
39
+
40
+ QM = QuantManager(my_configs)
41
+ model = QM.build("summarizer_prod")
42
+ ```
43
+
44
+ Same call in a notebook, a FastAPI endpoint, a test, or a batch job.
45
+
46
+ ---
47
+
48
+ ## Core concept: the Quant
49
+
50
+ A **Quant** is the unit reqm builds and manages. It is:
51
+
52
+ - **Callable** — invoked directly with its inputs
53
+ - **Config-driven** — constructor arguments defined in YAML, no hardcoding
54
+ - **Auditable** — implements `dummy_inputs()`, example inputs the factory uses to verify it actually runs
55
+
56
+ ```python
57
+ from reqm import Quant
58
+ from reqm.overrides_ext import override
59
+
60
+ class Summarizer(Quant):
61
+ def __init__(self, model_name: str, max_tokens: int):
62
+ self.model = load_model(model_name)
63
+ self.max_tokens = max_tokens
64
+
65
+ @override
66
+ def dummy_inputs(self) -> list[dict]:
67
+ return [{"text": "The quick brown fox jumps over the lazy dog."}]
68
+
69
+ @override
70
+ def __call__(self, text: str) -> str:
71
+ return self.model.summarize(text, max_tokens=self.max_tokens)
72
+ ```
73
+
74
+ The `dummy_inputs` contract is what separates a Quant from a plain ABC. reqm can call each Quant with its own dummy inputs at build time — if it fails, it fails early and loudly, not silently in production.
75
+
76
+ ---
77
+
78
+ ## Config modules and QuantManager
79
+
80
+ A **config module** is any importable Python package containing YAML files:
81
+
82
+ ```
83
+ my_configs/
84
+ ├── __init__.py
85
+ ├── summarizer_prod.yaml
86
+ ├── summarizer_fast.yaml
87
+ └── serving/
88
+ └── prod.yaml
89
+ ```
90
+
91
+ Each YAML config declares what to build:
92
+
93
+ ```yaml
94
+ # @package _global_
95
+ _target_: myproject.models.Summarizer
96
+ model_name: gpt-4o
97
+ max_tokens: 512
98
+ ```
99
+
100
+ `QuantManager` takes the config module and gives you a uniform API. Construct it once in the `__init__.py` next to your configs directory, then import `QM` everywhere:
101
+
102
+ ```python
103
+ # myproject/__init__.py (next to configs/)
104
+ import myproject.configs as configs
105
+ from reqm import QuantManager
106
+
107
+ QM = QuantManager(configs)
108
+ ```
109
+
110
+ ```python
111
+ # Any call site — notebook, script, service, test
112
+ from myproject import QM
113
+
114
+ QM.list_configs() # ["serving/prod", "summarizer_fast", "summarizer_prod"]
115
+ QM.validate() # check all configs have # @package _global_
116
+ cfg = QM.get_config("summarizer_prod") # resolved OmegaConf DictConfig
117
+ obj = QM.build("summarizer_prod") # instantiated object
118
+ ```
119
+
120
+ Configs can compose other configs via Hydra defaults lists:
121
+
122
+ ```yaml
123
+ # @package _global_
124
+ defaults:
125
+ - /base_model@child
126
+ - _self_
127
+ _target_: myproject.models.Ensemble
128
+ weight: 0.6
129
+ ```
130
+
131
+ ---
132
+
133
+ ## The uniform call site
134
+
135
+ The core value proposition: ONE script, swap the config name, get different experimental results. No code changes, no if/else chains, no factory functions.
136
+
137
+ Construct `QM` once in the `__init__.py` right next to your configs directory:
138
+
139
+ ```python
140
+ # myproject/__init__.py (lives next to configs/)
141
+ import myproject.configs as configs
142
+ from reqm import QuantManager
143
+
144
+ QM = QuantManager(configs)
145
+ ```
146
+
147
+ Then every script just imports it:
148
+
149
+ ```python
150
+ import sys
151
+ from myproject import QM
152
+
153
+ model = QM.build(sys.argv[1]) # <-- only this string changes
154
+ result = model(text="Hello world")
155
+ ```
156
+
157
+ ```bash
158
+ python evaluate.py summarizer_prod
159
+ python evaluate.py summarizer_fast
160
+ python evaluate.py summarizer_experiment_v3
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Runnable example
166
+
167
+ The repo includes a complete example project at `examples/estimators/` that demonstrates Quant subclasses, non-Quant configurable dependencies (Filters), Hydra config composition, and multiple scripts sharing a single `QM` instance defined in `examples/estimators/__init__.py`:
168
+
169
+ ```bash
170
+ # Evaluate a single estimator config
171
+ uv run python -m examples.estimators.scripts.evaluate mean_simple
172
+
173
+ # Inspect the fully resolved config YAML
174
+ uv run python -m examples.estimators.scripts.inspect_config ensemble/mean_median
175
+
176
+ # Compare multiple configs side by side
177
+ uv run python -m examples.estimators.scripts.compare mean_simple mean_outlier median_simple
178
+
179
+ # Validate all configs
180
+ uv run python -m examples.estimators.scripts.validate_configs
181
+
182
+ # Sweep all configs and rank by performance
183
+ uv run python -m examples.estimators.scripts.sweep
184
+ ```
185
+
186
+ ---
187
+
188
+ ## PyTorch integration
189
+
190
+ reqm does not depend on PyTorch, but its primary use case is config-driven model experimentation with `nn.Module`. The repo includes a `TorchQuant` bridge class that resolves the `__call__` vs `forward()` conflict — subclass it instead of plain `Quant` when your model is an `nn.Module`:
191
+
192
+ Don't subclass TorchQuant directly for every model — create a **domain base class** that locks the `forward` signature for your use case:
193
+
194
+ ```python
195
+ from myproject.torch_quant import TorchQuant # copy from examples/
196
+
197
+ # Domain base — locks forward(self, x: Tensor) -> Tensor for all regressors
198
+ class Regressor(TorchQuant):
199
+ in_features: int
200
+
201
+ @override
202
+ @abc.abstractmethod
203
+ def forward(self, x: torch.Tensor) -> torch.Tensor: ...
204
+
205
+ @override
206
+ def dummy_inputs(self) -> list[dict]:
207
+ return [{"x": torch.randn(4, self.in_features)}]
208
+
209
+
210
+ # Concrete model — must match the Regressor signature
211
+ class LinearRegressor(Regressor):
212
+ def __init__(self, in_features: int):
213
+ super().__init__()
214
+ self.in_features = in_features
215
+ self.linear = nn.Linear(in_features, 1)
216
+
217
+ @override
218
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
219
+ return self.linear(x)
220
+ ```
221
+
222
+ `TorchQuant.forward` uses `@allow_any_override` so domain bases can narrow freely. Once the domain base locks the signature (without `@allow_any_override`), all concrete models must match — enforced at class definition time, not runtime.
223
+
224
+ See `docs/torch_integration.md` for the full explanation, and `examples/torch_models/` for a runnable example:
225
+
226
+ ```bash
227
+ uv run python -m examples.torch_models.scripts.evaluate linear_simple
228
+ uv run python -m examples.torch_models.scripts.evaluate mlp_small
229
+ uv run python -m examples.torch_models.scripts.audit
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Why not just Hydra?
235
+
236
+ Hydra is framework-first. It expects to own your program's entry point. `reqm` is library-first — it has no opinion about your application structure and works wherever Python runs.
237
+
238
+ | | Hydra | reqm |
239
+ |---|---|---|
240
+ | Object instantiation | yes | yes |
241
+ | Config composition | yes | yes (via Hydra) |
242
+ | Auditability (`dummy_inputs`) | no | yes |
243
+ | Works in notebooks | limited | yes |
244
+ | CLI ceremony required | yes | no |
245
+
246
+ ---
247
+
248
+ ## Name
249
+
250
+ `reqm` is also a nod to [Rue Esquermoise](https://en.wikipedia.org/wiki/Rue_Esquermoise), one of the oldest streets in Lille, France, dating to the 13th century. Its etymology traces to the Flemish *eskelm* — "frontier." A fitting name for a library that sits at the frontier between research and production.
251
+
252
+ ---
253
+
254
+ ## Status
255
+
256
+ Core API implemented: `Quant`, `QuantManager`, config composition, and override support all work. See `examples/estimators/` for a complete working project.