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.
- reqm-0.1.0/.gitignore +8 -0
- reqm-0.1.0/.pre-commit-config.yaml +16 -0
- reqm-0.1.0/.vscode/settings.json +10 -0
- reqm-0.1.0/CLAUDE.md +256 -0
- reqm-0.1.0/LICENSE +21 -0
- reqm-0.1.0/PKG-INFO +256 -0
- reqm-0.1.0/README.md +244 -0
- reqm-0.1.0/TECH_DEBT.md +9 -0
- reqm-0.1.0/docs/config_management.md +227 -0
- reqm-0.1.0/docs/torch_integration.md +216 -0
- reqm-0.1.0/examples/__init__.py +1 -0
- reqm-0.1.0/examples/estimators/__init__.py +25 -0
- reqm-0.1.0/examples/estimators/configs/__init__.py +2 -0
- reqm-0.1.0/examples/estimators/configs/ensemble/mean_median.yaml +18 -0
- reqm-0.1.0/examples/estimators/configs/filters/no_filter.yaml +6 -0
- reqm-0.1.0/examples/estimators/configs/filters/outlier.yaml +8 -0
- reqm-0.1.0/examples/estimators/configs/filters/top_k.yaml +7 -0
- reqm-0.1.0/examples/estimators/configs/mean_outlier.yaml +12 -0
- reqm-0.1.0/examples/estimators/configs/mean_simple.yaml +11 -0
- reqm-0.1.0/examples/estimators/configs/median_simple.yaml +11 -0
- reqm-0.1.0/examples/estimators/configs/trimmed_mean.yaml +12 -0
- reqm-0.1.0/examples/estimators/datasets.py +25 -0
- reqm-0.1.0/examples/estimators/filters/__init__.py +1 -0
- reqm-0.1.0/examples/estimators/filters/api.py +32 -0
- reqm-0.1.0/examples/estimators/filters/no_filter.py +22 -0
- reqm-0.1.0/examples/estimators/filters/outlier.py +39 -0
- reqm-0.1.0/examples/estimators/filters/top_k.py +30 -0
- reqm-0.1.0/examples/estimators/quants/__init__.py +1 -0
- reqm-0.1.0/examples/estimators/quants/api.py +42 -0
- reqm-0.1.0/examples/estimators/quants/ensemble.py +47 -0
- reqm-0.1.0/examples/estimators/quants/mean.py +38 -0
- reqm-0.1.0/examples/estimators/quants/median.py +37 -0
- reqm-0.1.0/examples/estimators/quants/trimmed_mean.py +50 -0
- reqm-0.1.0/examples/estimators/scripts/__init__.py +7 -0
- reqm-0.1.0/examples/estimators/scripts/compare.py +67 -0
- reqm-0.1.0/examples/estimators/scripts/evaluate.py +59 -0
- reqm-0.1.0/examples/estimators/scripts/inspect_config.py +42 -0
- reqm-0.1.0/examples/estimators/scripts/sweep.py +53 -0
- reqm-0.1.0/examples/estimators/scripts/validate_configs.py +45 -0
- reqm-0.1.0/examples/torch_models/__init__.py +25 -0
- reqm-0.1.0/examples/torch_models/configs/__init__.py +1 -0
- reqm-0.1.0/examples/torch_models/configs/linear_simple.yaml +3 -0
- reqm-0.1.0/examples/torch_models/configs/mlp_large.yaml +5 -0
- reqm-0.1.0/examples/torch_models/configs/mlp_small.yaml +5 -0
- reqm-0.1.0/examples/torch_models/models/__init__.py +1 -0
- reqm-0.1.0/examples/torch_models/models/api.py +62 -0
- reqm-0.1.0/examples/torch_models/models/linear.py +37 -0
- reqm-0.1.0/examples/torch_models/models/mlp.py +49 -0
- reqm-0.1.0/examples/torch_models/scripts/__init__.py +0 -0
- reqm-0.1.0/examples/torch_models/scripts/audit.py +42 -0
- reqm-0.1.0/examples/torch_models/scripts/evaluate.py +54 -0
- reqm-0.1.0/examples/torch_models/torch_quant.py +96 -0
- reqm-0.1.0/llms.txt +309 -0
- reqm-0.1.0/pyproject.toml +35 -0
- reqm-0.1.0/src/reqm/__init__.py +6 -0
- reqm-0.1.0/src/reqm/overrides_ext.py +165 -0
- reqm-0.1.0/src/reqm/quant.py +120 -0
- reqm-0.1.0/src/reqm/quant_manager.py +264 -0
- reqm-0.1.0/tests/__init__.py +0 -0
- reqm-0.1.0/tests/dummy_objects.py +30 -0
- reqm-0.1.0/tests/test_config_module_one/__init__.py +0 -0
- reqm-0.1.0/tests/test_config_module_one/atomic_no_target.yaml +4 -0
- reqm-0.1.0/tests/test_config_module_one/atomic_simple.yaml +4 -0
- reqm-0.1.0/tests/test_config_module_one/chain_end.yaml +7 -0
- reqm-0.1.0/tests/test_config_module_one/chain_middle.yaml +7 -0
- reqm-0.1.0/tests/test_config_module_one/depends_on_atomic.yaml +7 -0
- reqm-0.1.0/tests/test_config_module_one/depends_on_nested.yaml +7 -0
- reqm-0.1.0/tests/test_config_module_one/invalid_no_package_global.yaml +3 -0
- reqm-0.1.0/tests/test_config_module_one/multi_dep.yaml +8 -0
- reqm-0.1.0/tests/test_config_module_one/sub/deep/deeply_nested.yaml +4 -0
- reqm-0.1.0/tests/test_config_module_one/sub/depends_on_root.yaml +7 -0
- reqm-0.1.0/tests/test_config_module_one/sub/nested.yaml +4 -0
- reqm-0.1.0/tests/test_config_module_one/with_interpolation.yaml +4 -0
- reqm-0.1.0/tests/test_config_module_one/with_missing_interpolation.yaml +3 -0
- reqm-0.1.0/tests/test_overrides_ext.py +256 -0
- reqm-0.1.0/tests/test_quant.py +310 -0
- reqm-0.1.0/tests/test_quant_manager.py +345 -0
- reqm-0.1.0/tests/test_torch_quant.py +360 -0
- reqm-0.1.0/uv.lock +540 -0
reqm-0.1.0/.gitignore
ADDED
|
@@ -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
|
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.
|