架构概述
本文档介绍 Hyperparameter 的内部架构,包括 Rust 后端和 Python 前端如何协同工作。
整体架构
┌─────────────────────────────────────────────────────────────┐
│ Python 用户代码 │
│ @hp.param, scope, hp.config() 等 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Python API 层 │
│ hyperparameter/api.py, hyperparameter/cli.py │
│ - 装饰器 (@hp.param) │
│ - 上下文管理器 (scope) │
│ - CLI 参数解析 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 存储抽象层 │
│ hyperparameter/storage.py │
│ - TLSKVStorage (线程本地存储) │
│ - 自动后端选择 │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────────┐
│ Rust 后端 │ │ Python 回退后端 │
│ (librbackend.so) │ │ (纯 Python 字典) │
│ - xxhash 键哈希 │ │ - Rust 不可用时使用 │
│ - 线程本地存储 │ │ - 相同的 API 契约 │
│ - 无锁读取 │ │ │
└─────────────────────────┘ └─────────────────────────────┘
组件详解
1. Python API 层 (hyperparameter/api.py)
这是用户直接交互的部分。
核心类:
-
scope: 创建新参数作用域的上下文管理器。 -
_ParamAccessor: 处理hp.scope.x.y.z | default语法。 -
param装饰器: 检查函数签名并注入值。
2. 存储层 (hyperparameter/storage.py)
存储层抽象了底层的键值存储。
核心特性:
- 线程本地存储 (TLS): 每个线程有自己的参数栈。
- 作用域更新: 更改仅限当前作用域,退出时回滚。
- 后端选择: 如果可用则自动使用 Rust 后端。
class TLSKVStorage:
"""带作用域栈的线程本地键值存储"""
def enter(self):
"""将新作用域压入栈"""
def exit(self):
"""弹出当前作用域,回滚更改"""
def get(self, key: str) -> Any:
"""在当前作用域查找键,然后查找父作用域"""
def put(self, key: str, value: Any):
"""仅在当前作用域设置键"""
3. Rust 后端 (src/core/, src/py/)
Rust 后端提供高性能的参数访问。
为什么用 Rust?
-
编译时键哈希: 像
"model.layers.size"这样的键在编译时使用xxhash哈希,消除了运行时字符串哈希开销。 -
无锁读取: 线程本地存储意味着读取时没有互斥锁竞争。
-
零拷贝字符串处理: Rust 的字符串处理避免了 Python 字符串驻留的开销。
核心 Rust 组件:
// src/core/src/storage.rs
pub struct ThreadLocalStorage {
stack: Vec<HashMap<u64, Value>>, // 作用域栈
}
// src/core/src/xxh.rs
pub const fn xxhash(s: &str) -> u64 {
// 编译时 xxhash64
}
// src/core/src/api.rs
pub fn get_param<T>(key_hash: u64, default: T) -> T {
// 通过预计算哈希快速查找
}
Python 绑定 (src/py/):
使用 PyO3 将 Rust 函数暴露给 Python:
4. 配置加载器 (hyperparameter/loader.py)
加载器处理配置文件解析和处理。
处理流水线:
- 解析: 支持 TOML、JSON、YAML
- 合并: 深度合并多个配置(后者覆盖前者)
- 插值: 解析
${variable}引用 - 校验: 可选的基于类类型提示的 Schema 校验
def load(path, schema=None):
config = _load_and_merge(path)
config = _resolve_interpolations(config)
if schema:
return validate(config, schema)
return config
数据流示例
让我们追踪运行以下代码时发生了什么:
import hyperparameter as hp
@hp.param("model")
def train(lr=0.001):
print(lr)
with hp.scope(**{"model.lr": 0.01}):
train()
逐步分析:
scope(**{"model.lr": 0.01}):- 创建新的
TLSKVStorage作用域 - 计算哈希:
xxhash("model.lr")→0x1234... -
存储:
{0x1234...: 0.01}到当前线程的作用域栈 -
train()被调用: @hp.param包装器运行- 对每个有默认值的参数 (
lr=0.001):- 计算哈希:
xxhash("model.lr") - 调用
storage.get_entry(0x1234...) - Rust 后端返回
0.01
- 计算哈希:
-
调用
train(lr=0.01) -
作用域退出:
hp.scope.__exit__()被调用- 从栈中弹出作用域
model.lr不再可访问
性能特征
为什么 Hyperparameter 快
| 操作 | Hydra/OmegaConf | Hyperparameter |
|---|---|---|
| 键查找 | 运行时字符串哈希 | 预计算 xxhash |
| 类型检查 | 每次访问都检查 | 可选,加载时检查 |
| 线程安全 | 全局锁 | 线程本地(无锁) |
| 内存 | Python 字典 + 包装器 | Rust HashMap |
何时使用哪种访问模式
| 模式 | 速度 | 使用场景 |
|---|---|---|
@hp.param 注入 |
🚀🚀🚀 最快 | 热循环,性能关键 |
with hp.scope() as ps: ps.x |
🚀🚀 快 | 大多数代码 |
hp.scope.x (全局) |
🚀 中等 | 便捷访问,一次性访问 |
线程安全模型
线程 1 线程 2
────── ──────
hp.scope(a=1)
│ scope(a=2)
│ a = 1 │ a = 2
│ │
└── 退出 └── 退出
a = 未定义 a = 未定义
每个线程有独立的作用域栈。一个线程的更改永远不会影响另一个线程。
frozen() 用于跨线程默认值:
扩展 Hyperparameter
自定义存储后端
from hyperparameter.storage import TLSKVStorage
class RedisBackedStorage(TLSKVStorage):
"""示例: 用于分布式系统的 Redis 后端存储"""
def get(self, key):
# 先尝试本地
value = super().get(key)
if value is None:
# 回退到 Redis
value = self.redis.get(key)
return value
自定义类型转换
from hyperparameter.loader import _coerce_type
def _coerce_type(value, target_type):
# 添加自定义类型处理
if target_type is MyCustomType:
return MyCustomType.from_string(value)
# ... 现有逻辑
文件结构
hyperparameter/
├── __init__.py # 公共 API 导出
├── api.py # 核心 Python API (scope, param)
├── cli.py # CLI 支持 (launch, launch)
├── loader.py # 配置加载、插值、校验
├── storage.py # 存储抽象、TLS
└── tune.py # 超参调优工具
src/
├── core/ # Rust 核心库
│ └── src/
│ ├── api.rs # 公共 Rust API
│ ├── storage.rs # 线程本地存储
│ ├── value.rs # 值类型处理
│ └── xxh.rs # 编译时 xxhash
├── macros/ # Rust 过程宏
└── py/ # PyO3 Python 绑定