Hyperparameter
Make configurable AI applications. Build for Python/Rust hackers.
Hyperparameter is a versatile library designed to streamline the management and control of hyperparameters in machine learning algorithms and system development. Tailored for AI researchers and Machine Learning Systems (MLSYS) developers, Hyperparameter offers a unified solution with a focus on ease of use in Python, high-performance access in Rust and C++, and a set of macros for seamless hyperparameter management.
5-Minute Try
pip install hyperparameter
# Run a ready-to-use demo
python -m hyperparameter.examples.quickstart
# Try the @hp.param CLI: override defaults from the command line
python -m hyperparameter.examples.quickstart --define greet.name=Alice --enthusiasm=3
# Inspect params and defaults
python -m hyperparameter.examples.quickstart -lps
python -m hyperparameter.examples.quickstart -ep greet.name
# Running from source? Use module mode or install editable
# python -m hyperparameter.examples.quickstart
# or: pip install -e .
Why Hyperparameter?
🚀 Unmatched Performance (vs Hydra)
Hyperparameter is built on a high-performance Rust backend, making it significantly faster than pure Python alternatives like Hydra, especially in inner-loop parameter access.
| Method | Time (1M iters) | Speedup (vs Hydra) |
|---|---|---|
| HP: Injected (Native Speed) | 0.0184s | 856.73x 🚀 |
| HP: Dynamic (Optimized) | 2.4255s | 6.50x ⚡️ |
| Hydra (Baseline) | 15.7638s | 1.00x |
Benchmark scenario: Accessing a nested parameter
model.layers.0.size1,000,000 times in a loop. Seebenchmark/folder for reproduction scripts.
✨ Zero-Dependency Schema Validation
Hyperparameter supports structural validation using standard Python type hints without introducing heavy dependencies (like Pydantic or OmegaConf).
from dataclasses import dataclass
import hyperparameter as hp
@dataclass
class AppConfig:
host: str
port: int
debug: bool = False
# Validates types and converts automatically: "8080" -> 8080 (int)
cfg = hp.config("config.toml", schema=AppConfig)
Key Features
For Python Users
- Pythonic Syntax: Define hyperparameters using keyword argument syntax;
- Intuitive Scoping: Control parameter scope through
withstatement; - Configuration File: Easy to load parameters from config files (JSON/TOML/YAML) with composition and interpolation support;
- Zero-Overhead Validation: Optional schema validation using standard Python type hints;
For Rust and C++ Users
-
High-Performance Backend: Hyperparameter is implemented in Rust, providing a robust and high-performance backend for hyperparameter management. Access hyperparameters in Rust and C++ with minimal overhead, making it ideal for ML and system developers who prioritize performance.
-
Macro-Based Parameter Management: Hyperparameter provides a set of macros for both Rust and C++ users. These macros mimic Python's
withstatements and adhere to language-specific scoping rules. -
Compile-Time Hashing: Both Rust and C++ interfaces utilize compile-time hashing of hyperparameter names, reducing runtime hash computation overhead.
Quick Start
Installation
Python
import hyperparameter as hp
@hp.param("foo")
def foo(x=1, y="a"):
return f"x={x}, y={y}"
foo() # x=1, y='a'
with hp.scope(**{"foo.x": 2}):
foo() # x=2, y='a'
Rust
fn foo() -> i32 {
with_params! {
@get x = foo.x or 1i32; // Read hyperparameter with default value
println!("x={}", x);
}
}
fn main() {
foo(); // x=1
with_params! {
@set foo.x = 2i32; // Set hyperparameter
foo(); // x=2
}
foo(); // x=1
}
C++
ASSERT(1 == GET_PARAM(a.b, 1), "get undefined param");
{
auto guard = WITH_PARAMS(a, 1, //
a.b, 2.0, //
a.b.c, true, //
a.b.c.d, "str");
ASSERT(1 == GET_PARAM(a, 0), "get int value");
ASSERT(1 == GET_PARAM(a, 0), "get int value");
}
Detailed Usage Examples
Support for Default Values
Python
Rust
Scope Control of Parameter Values
Python
with hp.scope() as ps: # 1st scope start
ps.foo.x=1
with hp.scope() as ps2: # 2nd scope start
ps.foo.y=2
# 2nd scope end
# 1st scope end
Rust
with_params!{ // 1st scope start
@set foo.x=1;
with_params!{ //2nd scope start
@set foo.y=2
...
} // 2nd scope end
} // 1st scope end
Thread Isolation/Thread Safety
Python
@hp.param("foo")
def foo(x=1): # Print hyperparameter foo.x
print(f"foo.x={x}")
with hp.scope() as ps:
ps.foo.x=2 # Modify foo.x in the current thread
foo() # foo.x=2
threading.Thread(target=foo).start() # foo.x=1, new thread's hyperparameter value is not affected by the main thread
Rust
fn foo() { // Print hyperparameter foo.x
with_params!{
@get x = foo.x or 1;
println!("foo.x={}", x);
}
}
fn main() {
with_params!{
@set foo.x = 2; // Modify foo.x in the current thread
foo(); // foo.x=2
thread::spawn(foo); // foo.x=1, new thread's hyperparameter value is not affected by the main thread
}
}
Command Line Application
In command line applications, it's common to define hyperparameters using command line arguments (e.g., -D, --define) and control hyperparameters on the command line. Here's an example in Python and Rust:
Python
# example.py
import hyperparameter as hp
@hp.param("example")
def main(a=0, b=1):
print(f"example.a={a}, example.b={b}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-D", "--define", nargs="*", default=[], action="extend")
args = parser.parse_args()
with hp.scope(*args.define):
main()
Rust
// example.rs
use hyperparameter::*;
use hyperparameter_derive::Parser;
fn main() {
#[derive(Parser, Debug)]
struct DeriveArgs {
#[arg(short = 'D', long)]
define: Vec<String>,
}
let args = DeriveArgs::parse();
with_params! {
params ParamScope::from(&args.define);
foo()
}
}
fn foo() {
with_params! {
@get a = example.a or 0;
@get b = example.b or 1;
println!("example.a={}, example.b={}",a ,b);
}
}
More Examples
parameter tuning for researchers
This example demonstrates how to use hyperparameter in research projects, and make experiments reproducible.
experiment tracing for data scientists
This example showcases experiment management with hyperparameter and result tracing with mlflow.tracing.
Behavior Guarantees (Semantic Contract)
- Keys & hashing: keys use
.for nesting, case is preserved, and hashing uses the same UTF-8 input and seed across Python/Rust/C++; invalid characters are an error. - Read precedence: current thread’s innermost scope > parent scopes outward > frozen global snapshot > user default. Writes only affect the current scope and rollback on exit.
- Defaults vs. missing: only missing keys fall back to defaults; explicit
None/False/0are treated as existing values. Type conversion rules (bool/int/float/str) are consistent across languages; invalid values use a best-effort conversion and otherwise fall back to the provided default (no silent random values). - Threads &
frozen(): each thread starts from the frozen global snapshot; mutations stay in-thread unlessfrozen()is called, which atomically updates the global snapshot. Global mutations are lock-protected in the Python backend, matching Rust semantics. - Error model: reading an undefined key without a default raises a key error; backend load failure falls back to the Python backend without noisy tracebacks; no silent failure on type errors.
- Multiprocess notice: cross-process consistency requires a shared backend (e.g., Rust backend or user-provided storage adapter); the built-in Python backend only guards threads, not processes.