Skip to main content

Ridiculously Easy Quant Manager — config-based aliased object factory with enforced interfaces

Project description

reqm

Ridiculously Easy Quant Manager

Directory-based config management and object factory built on Hydra.


The problem

Hydra is excellent for config-driven instantiation. But using it as a general object factory requires ceremony:

# You have to do all of this just to instantiate one object
with hydra.initialize(config_path="conf"):
    cfg = hydra.compose(config_name="my_model")
    model = hydra.utils.instantiate(cfg.model)

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.

reqm gives you Hydra's power — config-driven instantiation, composable overrides, recursive object graphs — with none of the ceremony:

from reqm import QuantManager
import my_configs

QM = QuantManager(my_configs)
model = QM.build("summarizer_prod")

Same call in a notebook, a FastAPI endpoint, a test, or a batch job.


Core concept: the Quant

A Quant is the unit reqm builds and manages. It is:

  • Callable — invoked directly with its inputs
  • Config-driven — constructor arguments defined in YAML, no hardcoding
  • Auditable — implements dummy_inputs(), example inputs the factory uses to verify it actually runs
from reqm import Quant
from reqm.overrides_ext import override

class Summarizer(Quant):
    def __init__(self, model_name: str, max_tokens: int):
        self.model = load_model(model_name)
        self.max_tokens = max_tokens

    @override
    def dummy_inputs(self) -> list[dict]:
        return [{"text": "The quick brown fox jumps over the lazy dog."}]

    @override
    def __call__(self, text: str) -> str:
        return self.model.summarize(text, max_tokens=self.max_tokens)

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.


Config modules and QuantManager

A config module is any importable Python package containing YAML files:

my_configs/
├── __init__.py
├── summarizer_prod.yaml
├── summarizer_fast.yaml
└── serving/
    └── prod.yaml

Each YAML config declares what to build:

# @package _global_
_target_: myproject.models.Summarizer
model_name: gpt-4o
max_tokens: 512

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:

# myproject/__init__.py (next to configs/)
import myproject.configs as configs
from reqm import QuantManager

QM = QuantManager(configs)
# Any call site — notebook, script, service, test
from myproject import QM

QM.list_configs()          # ["serving/prod", "summarizer_fast", "summarizer_prod"]
QM.validate()              # check all configs have # @package _global_
cfg = QM.get_config("summarizer_prod")   # resolved OmegaConf DictConfig
obj = QM.build("summarizer_prod")        # instantiated object

Configs can compose other configs via Hydra defaults lists:

# @package _global_
defaults:
  - /base_model@child
  - _self_
_target_: myproject.models.Ensemble
weight: 0.6

The uniform call site

The core value proposition: ONE script, swap the config name, get different experimental results. No code changes, no if/else chains, no factory functions.

Construct QM once in the __init__.py right next to your configs directory:

# myproject/__init__.py (lives next to configs/)
import myproject.configs as configs
from reqm import QuantManager

QM = QuantManager(configs)

Then every script just imports it:

import sys
from myproject import QM

model = QM.build(sys.argv[1])       # <-- only this string changes
result = model(text="Hello world")
python evaluate.py summarizer_prod
python evaluate.py summarizer_fast
python evaluate.py summarizer_experiment_v3

Runnable example

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:

# Evaluate a single estimator config
uv run python -m examples.estimators.scripts.evaluate mean_simple

# Inspect the fully resolved config YAML
uv run python -m examples.estimators.scripts.inspect_config ensemble/mean_median

# Compare multiple configs side by side
uv run python -m examples.estimators.scripts.compare mean_simple mean_outlier median_simple

# Validate all configs
uv run python -m examples.estimators.scripts.validate_configs

# Sweep all configs and rank by performance
uv run python -m examples.estimators.scripts.sweep

PyTorch integration

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:

Don't subclass TorchQuant directly for every model — create a domain base class that locks the forward signature for your use case:

from myproject.torch_quant import TorchQuant  # copy from examples/

# Domain base — locks forward(self, x: Tensor) -> Tensor for all regressors
class Regressor(TorchQuant):
    in_features: int

    @override
    @abc.abstractmethod
    def forward(self, x: torch.Tensor) -> torch.Tensor: ...

    @override
    def dummy_inputs(self) -> list[dict]:
        return [{"x": torch.randn(4, self.in_features)}]


# Concrete model — must match the Regressor signature
class LinearRegressor(Regressor):
    def __init__(self, in_features: int):
        super().__init__()
        self.in_features = in_features
        self.linear = nn.Linear(in_features, 1)

    @override
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.linear(x)

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.

See docs/torch_integration.md for the full explanation, and examples/torch_models/ for a runnable example:

uv run python -m examples.torch_models.scripts.evaluate linear_simple
uv run python -m examples.torch_models.scripts.evaluate mlp_small
uv run python -m examples.torch_models.scripts.audit

Why not just Hydra?

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.

Hydra reqm
Object instantiation yes yes
Config composition yes yes (via Hydra)
Auditability (dummy_inputs) no yes
Works in notebooks limited yes
CLI ceremony required yes no

Name

reqm is also a nod to 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.


Status

Core API implemented: Quant, QuantManager, config composition, and override support all work. See examples/estimators/ for a complete working project.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reqm-0.1.0.tar.gz (60.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reqm-0.1.0-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

Details for the file reqm-0.1.0.tar.gz.

File metadata

  • Download URL: reqm-0.1.0.tar.gz
  • Upload date:
  • Size: 60.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for reqm-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b24d7521c39711891999a9525522200edf6c1ca042e3c2a43c309d06d7b0cd4f
MD5 b7c0c317932c8fb9826d68aa11a2749d
BLAKE2b-256 1f64e5c2fc5d346cc96a3f1b14b5867f3646b2c6efc743cc68d955e187a3ef4f

See more details on using hashes here.

File details

Details for the file reqm-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: reqm-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 11.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for reqm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 67ac4945564e6140d8c45649ae120a510fe8e7d8e51c8de7209029f95218c352
MD5 9bb63e0846112fd1d385a4226c72b790
BLAKE2b-256 34196ac4eccaeb03847e1c1e8885d84f51b975f68abfa0d22e7845297ed3c82b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page