操作指南#
本指南描述 Observable Library M2 已实现的行为,面向包用户编写;未来路线图能力会被明确标出。
完成快速开始后再使用这些示例。每个示例都保持在 0.1.0 公共边界内。
generate() 会生成什么#
generate(model, reductions=..., transforms=...) 扫描 model.named_parameters() 返回的每个条目,为每个参数和请求的 reduction 生成一个可观测量,并为每个可观测量附加相同的有序 transform 链。
M2 不公开 sources= 参数,也不生成 activation.*、grad.* 或 loss 可观测量。过滤返回列表可以减少 Runtime 工作,不过参数元数据已经完成枚举:
generated = ol.generate(model, reductions=["l2_norm"])
wanted = {"param.0.weight", "param.2.weight"}
selected = [item for item in generated if item.spec.source in wanted]
参数、gradient 和 activation 的生成阶段 template filter 属于 M3;其精确的 source 选择 API 尚未成为公共契约。
Transform 顺序与验证#
Transform 从左到右运行。例如,transforms=["center", "normalize"] 执行:
normalize(center(tensor)) -> reduction
系统不会生成子集、反向顺序或排列。每个 transform 必须在调用 generate() 前注册,重复名称会被拒绝。Registry 验证名称而非张量契约;不兼容的 rank、shape、dtype 或 device 假设会在可观测量运行时失败。
生成的可观测量会捕获已解析的 transform 和 reduction callable,因此后续 registry 变更不能静默改变已有可观测量的计算。
在线 source 生命周期#
HookSource.get("param.*", step) 直接读取当前模型参数。仅含参数的生成可观测量不需要 hooks,因此这种情况下 attach() 是可选的。
activation.* 和 grad.* 值在 forward 和 backward 期间产生。请在这些操作前调用 source.attach(),完成后调用 source.detach()。应在 loss.backward() 之后、optimizer.step() 之前观测 gradients。
当前 attach() 行为范围较广:它在所有顶层子模块上安装 activation hooks,在所有可训练参数上安装 gradient hooks。它存储最近捕获的张量,不跟踪每个 step 的新鲜度。每次调用 observe() 前都要运行匹配的 forward/backward,否则旧值可能仍在缓存中。Activation id 使用 activation.<top-level-child-name>,gradient id 使用 grad.<parameter-name>。
Loss 不会被自动发现。要使用 source="loss" 可观测量,请先调用 source.record_loss(loss, step)。
一次 observe() 调用内,Runtime 会缓存每个请求的 source,因此同一张量上的多个 reduction 只执行一次 source lookup。
混合生成与自定义可观测量#
generate() 返回 list[Observable]。构造 Pack 前可以加入高级自定义可观测量:
generated = ol.generate(model, reductions=["l2_norm"])
gradient_spec = ol.ObservableSpec(
source="grad.0.weight",
selector="all",
reduction="l2_norm",
budget_hint={"compute_ms": 0.01},
)
gradient = ol.Observable(
spec=gradient_spec,
compute=lambda tensors, _context: tensors[gradient_spec.source].norm(),
)
runtime = ol.Runtime(ol.Pack([*generated, gradient]), source=source)
Runtime 要求每个可观测量 id 唯一。Spec 与生成项相同的自定义可观测量会被拒绝。启用 Budget 时,自定义可观测量应提供现实的 budget_hint;缺失估算按零计算。
用户自定义 Filters#
M2 提供 Filter 基类以及 & / | 组合,但没有内置 BySource 或 ByReduction filter。用户自定义 filter 在生成后生效:
class ByReduction(ol.Filter):
def __init__(self, name: str) -> None:
self.name = name
def apply(self, observables):
return [
item for item in observables if item.spec.reduction == self.name
]
keep = ByReduction("l2_norm") | ByReduction("mean")
selected = keep(observables)
这会阻止未选中的可观测量运行,但不会阻止它们最初被生成。真正的生成阶段 template filter 计划属于 M3。
Identity、结果与查询#
系统没有单独的用户可见 name 字段。observable.spec.id 是从完整 spec 确定性派生的 16 字符哈希,覆盖 source、selector、有序 transforms、reduction、temporal 设置、frequency 和 budget hint。修改任何 identity 字段都可能改变 id。
Runtime.observe() 返回 dict[observable_id, value]。LocalStorage 在 SQLite 中存储元数据,在 NumPy NPZ 文件中存储数组 payload;它与 query() 使用相同的精确 id 和 step:
observable = observables[0]
values = runtime.observe(step=7)
value = values[observable.spec.id]
stored = ol.query(storage, observable.spec.id, step=7)
用于日志或用户界面时,可以派生显示标签,但不要把它当作存储键:
spec = observable.spec
chain = " -> ".join((*spec.transforms, spec.reduction))
label = f"{spec.source} | {chain}"
M2 只支持按精确 id/step 读回。按 source、reduction、tag 或显示标签查询属于后续分析工具。