User Guide¶
This guide provides comprehensive information on using hya in your projects.
Basic Usage¶
Importing and Automatic Registration¶
When you import hya, all default resolvers are automatically registered with OmegaConf:
import hya
from omegaconf import OmegaConf
# Resolvers are now available
conf = OmegaConf.create({"result": "${hya.add:2,3}"})
print(conf.result) # Output: 5
Using Resolvers in YAML Configurations¶
You can use resolvers directly in YAML configuration files:
# config.yaml
model:
layers: 4
hidden_size: 256
# NOTE: This is a simple demonstration of the mul resolver
# Real neural network parameter counts are much more complex
approx_params: ${hya.mul:${model.layers},${model.hidden_size}}
training:
total_samples: 10000
batch_size: 32
num_batches: ${hya.ceildiv:${training.total_samples},${training.batch_size}}
learning_rate: ${hya.pow:10,-3} # 10^-3 = 0.001
Load and use the configuration:
import hya
from omegaconf import OmegaConf
conf = OmegaConf.load("config.yaml")
print(conf.model.approx_params) # Output: 1024
print(conf.training.num_batches) # Output: 313
print(conf.training.learning_rate) # Output: 0.001
Integration with Hydra¶
hya is designed to work seamlessly with Hydra.
Basic Hydra Example¶
config.yaml:
dataset:
name: mnist
train_samples: 60000
val_samples: 10000
total_samples: ${hya.add:${dataset.train_samples},${dataset.val_samples}}
model:
type: cnn
channels: [32, 64, 128]
num_layers: ${hya.len:${model.channels}}
training:
batch_size: 64
epochs: 10
steps_per_epoch: ${hya.ceildiv:${dataset.train_samples},${training.batch_size}}
paths:
root: /data
train: ${hya.iter_join:[${paths.root},${dataset.name},train],/}
val: ${hya.iter_join:[${paths.root},${dataset.name},val],/}
experiment:
name: ${dataset.name}_${model.type}
version: ${hya.sha256:${experiment.name}}
train.py:
import hya
import hydra
from omegaconf import DictConfig
@hydra.main(version_base=None, config_path=".", config_name="config")
def train(cfg: DictConfig):
print(f"Dataset: {cfg.dataset.name}")
print(f"Total samples: {cfg.dataset.total_samples}")
print(f"Model layers: {cfg.model.num_layers}")
print(f"Steps per epoch: {cfg.training.steps_per_epoch}")
print(f"Train path: {cfg.paths.train}")
print(f"Experiment version: {cfg.experiment.version}")
if __name__ == "__main__":
train()
Multi-Config with Hydra¶
You can use hya resolvers across multiple configuration files:
config.yaml:
defaults:
- model: resnet
- optimizer: adam
experiment:
name: ${model.name}_${optimizer.name}
seed: 42
version: ${hya.sha256:${experiment.name}_${experiment.seed}}
model/resnet.yaml:
name: resnet
depth: 50
width_multiplier: 1.0
effective_depth: ${hya.mul:${model.depth},${model.width_multiplier}}
optimizer/adam.yaml:
name: adam
lr: ${hya.pow:10,-4} # 0.0001
betas: [0.9, 0.999]
Custom Resolvers¶
Creating and Registering Custom Resolvers¶
You can extend hya with your own custom resolvers:
from hya import get_default_registry
# Get the default registry
registry = get_default_registry()
# Register a simple custom resolver
@registry.register("double")
def double_resolver(x):
"""Double the input value."""
return x * 2
# Register a more complex resolver
@registry.register("clip")
def clip_resolver(value, min_val, max_val):
"""Clip a value between min and max."""
return max(min_val, min(value, max_val))
# Register custom resolvers with OmegaConf
# Note: Default hya resolvers are already registered on import
# This only registers your custom resolvers added to the default registry
registry.register_resolvers()
Now use them in your configuration:
hyperparameters:
base_lr: 0.001
scaled_lr: ${double:${hyperparameters.base_lr}}
dropout: 0.7
clipped_dropout: ${clip:${hyperparameters.dropout},0.0,0.5}
Overriding Existing Resolvers¶
You can override existing resolvers using the exist_ok parameter:
from hya import get_default_registry
registry = get_default_registry()
@registry.register("hya.add", exist_ok=True)
def custom_add(*args):
"""Custom add that also prints the result."""
result = sum(args)
print(f"Adding {args} = {result}")
return result
registry.register_resolvers()
Creating Isolated Registries¶
For advanced use cases, you can create independent registries:
from hya.registry import ResolverRegistry
# Create a new, isolated registry
custom_registry = ResolverRegistry()
@custom_registry.register("custom.multiply")
def multiply(x, y):
return x * y
# Register only these custom resolvers
custom_registry.register_resolvers()
Common Use Cases¶
Path Construction¶
Build file paths dynamically:
paths:
root: /data
project: myproject
version: v1
# Build paths using iter_join
data_dir: ${hya.iter_join:[${paths.root},${paths.project},${paths.version}],/}
train_file: ${hya.iter_join:[${paths.data_dir},train.csv],/}
# Use path resolver for proper Path objects
model_checkpoint: ${hya.path:${paths.data_dir}/models/checkpoint.pth}
Mathematical Computations¶
Perform calculations in your configuration:
model:
embedding_dim: 512
num_heads: 8
head_dim: ${hya.floordiv:${model.embedding_dim},${model.num_heads}} # 64
training:
total_steps: 100000
warmup_ratio: 0.1
warmup_steps: ${hya.mul:${training.total_steps},${training.warmup_ratio}} # 10000
# Calculate batch size for distributed training
per_device_batch_size: 16
num_devices: 4
global_batch_size: ${hya.mul:${training.per_device_batch_size},${training.num_devices}}
Data Type Specifications (PyTorch)¶
Specify tensor data types in configuration:
model:
dtype: ${hya.torch.dtype:float32}
use_half_precision: false
compute_dtype: ${hya.torch.dtype:float16}
data:
input_shape: [3, 224, 224]
dummy_input: ${hya.torch.tensor:${data.input_shape}}
Versioning and Hashing¶
Generate consistent identifiers:
experiment:
model_name: resnet50
dataset: imagenet
augmentation: heavy
# Create a unique experiment ID
full_name: ${experiment.model_name}_${experiment.dataset}_${experiment.augmentation}
experiment_id: ${hya.sha256:${experiment.full_name}}
# Use for reproducible paths
output_dir: ${hya.iter_join:[outputs,${experiment.experiment_id}],/}
List Operations¶
Work with lists and sequences:
datasets:
train: [mnist, cifar10, imagenet]
num_datasets: ${hya.len:${datasets.train}}
# Join dataset names
combined_name: ${hya.iter_join:${datasets.train},_} # mnist_cifar10_imagenet
Brace Expansion (requires braceexpand)¶
Generate multiple values from patterns:
files:
# Expands to iterator: file_1.txt, file_2.txt, ..., file_5.txt
pattern: ${hya.braceexpand:file_{1..5}.txt}
# Expands to: train_a.json, train_b.json, train_c.json
train_configs: ${hya.braceexpand:train_{a,b,c}.json}
Registry API¶
Checking for Resolvers¶
Check if a resolver is registered:
from hya import get_default_registry
registry = get_default_registry()
# Check if a resolver exists
if registry.has_resolver("hya.add"):
print("add resolver is available")
# List all registered resolvers
for key in registry.state.keys():
print(f"Registered: {key}")
Accessing Registry State¶
from hya import get_default_registry
registry = get_default_registry()
# Get all registered resolvers
resolvers_dict = registry.state
# Count resolvers
print(f"Total resolvers: {len(resolvers_dict)}")
# Check specific resolvers
core_resolvers = [k for k in resolvers_dict.keys() if k.startswith("hya.")]
print(f"Core hya resolvers: {len(core_resolvers)}")
Best Practices¶
Configuration Organization¶
Organize your configurations for maintainability:
# Good: Use resolvers to avoid duplication
model:
hidden_size: 768
num_layers: 12
total_hidden: ${hya.mul:${model.hidden_size},${model.num_layers}}
# Avoid: Hardcoding computed values
# total_hidden: 9216 # If you change hidden_size, this becomes incorrect
Error Handling¶
When using optional resolvers, provide helpful error messages:
import hya
from omegaconf import OmegaConf, errors
try:
conf = OmegaConf.create({"tensor": "${hya.torch.tensor:[1,2,3]}"})
print(conf.tensor)
except errors.InterpolationResolutionError as e:
print(f"Error: PyTorch is required. Install with: pip install torch")
Type Safety¶
Use appropriate resolvers for type safety:
# Good: Use correct division operators
samples: 1000
batch_size: 32
num_batches: ${hya.ceildiv:${samples},${batch_size}} # Integer result: 32
# Avoid: Using wrong division type
# num_batches: ${hya.truediv:${samples},${batch_size}} # Float result: 31.25
Performance Considerations¶
Resolvers are evaluated lazily when accessed:
conf = OmegaConf.create(
{"slow_computation": "${hya.pow:2,1000}", "fast_value": 42} # Not computed yet
)
# Only computed when accessed
result = conf.slow_computation # Computed here
Cache expensive computations:
# Good: Compute once, reuse
model:
vocab_size: 50000
embedding_dim: 512
total_embeddings: ${hya.mul:${model.vocab_size},${model.embedding_dim}}
layer1:
input_size: ${model.total_embeddings} # Reuses cached computation
layer2:
input_size: ${model.total_embeddings} # Reuses cached computation
Troubleshooting¶
Resolver Not Found¶
Error: omegaconf.errors.UnsupportedInterpolationType: Unsupported interpolation type hya.xxx
Solution: Make sure you have imported hya:
import hya # This line is required!
from omegaconf import OmegaConf
Optional Dependency Missing¶
Error: ImportError: braceexpand is required to use hya.braceexpand resolver
Solution: Install the required package:
pip install braceexpand # or numpy, or torch
Resolver Already Registered¶
Error: RuntimeError: A resolver is already registered for 'my_key'
Solution: Use exist_ok=True to override:
@registry.register("my_key", exist_ok=True)
def my_resolver(x):
return x
Type Errors in Resolvers¶
Error: TypeError: unsupported operand type(s) for +: 'int' and 'str'
Solution: Ensure your configuration passes correct types:
# Wrong: Mixing types
result: ${hya.add:5,abc}
# Correct: Same types
result: ${hya.add:5,10}
Circular References¶
Error: omegaconf.errors.InterpolationResolutionError: Circular reference detected
Solution: Avoid self-referential configurations:
# Wrong: Circular reference
x: ${hya.add:${y},1}
y: ${hya.add:${x},1}
# Correct: No circular dependency
x: 5
y: ${hya.add:${x},1}
Advanced Topics¶
Working with Complex Data Types¶
Create NumPy arrays and PyTorch tensors:
import hya
from omegaconf import OmegaConf
conf = OmegaConf.create(
{
"numpy_array": "${hya.np.array:[[1,2,3],[4,5,6]]}",
"torch_tensor": "${hya.torch.tensor:[1.0,2.0,3.0]}",
"torch_dtype": "${hya.torch.dtype:float32}",
}
)
print(type(conf.numpy_array)) # <class 'numpy.ndarray'>
print(type(conf.torch_tensor)) # <class 'torch.Tensor'>
print(conf.torch_dtype) # torch.float32
Combining Multiple Resolvers¶
Chain resolvers for complex operations:
model:
base_size: 64
multiplier: 4
layers: 3
# Compute total size: (64 * 4) * 3 = 768
intermediate_size: ${hya.mul:${model.base_size},${model.multiplier}}
total_size: ${hya.mul:${model.intermediate_size},${model.layers}}
Using Resolvers with OmegaConf Features¶
Combine resolvers with OmegaConf's native features:
defaults:
- base_config
# Use resolvers with variable interpolation
paths:
root: /data
project: ${oc.env:PROJECT_NAME,default_project}
output: ${hya.iter_join:[${paths.root},${paths.project}],/}
# Use resolvers with optional values
model:
size: ${size:512} # Can be overridden via command line
doubled_size: ${hya.mul:${model.size},2}