Repository navigation
feat(rl): stream live training metrics over motrix.metrics/v1 - #121
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
It is a large cross-cutting feature touching training lifecycle, a background daemon HTTP sink with concurrency/retry semantics, and async process orchestration across all three providers, which warrants human review despite thorough tests and no defects found.
0 open findings
What changed in this PR
This PR adds opt-in live streaming of training metrics over the motrix.metrics/v1 protocol. When MOTRIX_METRICS_URL is set, each training run writes every record to run_dir/metrics.jsonl and pushes the same records in gzip batches to an ingest service from a background daemon thread; a slow/unreachable/erroring service never blocks or fails training. With the env var unset the logger is a pure no-op (no file, no thread). The feature spans the motrix_rl control plane plus all three providers (FastSAC sync/async, SKRL torch/jax, RSLRL), each mapping its native TensorBoard tags onto shared canonical keys aligned on axes.env_steps. TensorBoard output and the terminal panel are unchanged.
Changes:
- New stdlib-only
motrix_rl/metrics.py(MetricsLogger,JsonlSink,HttpSink) with bounded queue, scalar-drop policy, retry/backoff and heartbeats;TrainerHandle.train()emits the run lifecycle (running/finished/killed/failed) andAgentProvider.total_env_steps()feeds progress. - Per-provider live emission: FastSAC
live_metrics.system_scalars+ sync/async scalar/event wiring, SKRLlive_metrics.pytag mapping, RSLRLlive_logger.LiveLogger; plus incidental correctness fixes (SKRL per-instance reward totals, RSLRLextras["log"]forwarding, FastSAC sync TB writer closed infinally). - Extensive new/updated tests and golden protocol fixtures; design + plan docs under
wiki/.
| File | Description |
|---|---|
| motrix_rl/src/motrix_rl/metrics.py | New core protocol logger and two sinks |
| motrix_rl/src/motrix_rl/contracts.py | TrainerHandle.train() lifecycle, _failure_reason, metrics/run_info fields, total_env_steps hook |
| motrix_rl/src/motrix_rl/runner.py | Build logger from env, annotate run, assemble running fields |
| motrix_rl/src/motrix_rl/frameworks.py | Thread metrics through trainer context/handle |
| motrix_rl/src/motrix_rl/runs.py | Extract task_config_snapshot reused by the running record |
| motrix_rl/tests/test_metrics.py | New comprehensive sink/logger tests |
| motrix_rl/tests/test_rl_contracts.py | TrainerHandle finished/killed/failed coverage |
| motrix_rl/tests/test_runner.py | Live-metrics-on-train vs disabled-on-play coverage |
| motrix_rl/tests/fixtures/metrics_v1/*.json | Golden protocol fixtures (pipeline + motrixlab) |
| motrix_rl_skrl/src/.../live_metrics.py | New SKRL tag→canonical-key mapping |
| motrix_rl_skrl/src/.../ppo.py | Attach live metrics, emit run_started/final checkpoint |
| motrix_rl_skrl/src/.../torch/train/ppo.py | Per-instance reward totals; keyword-only write_tracking_data/write_checkpoint overrides (match skrl 2.1) |
| motrix_rl_skrl/src/.../jax/train/ppo.py | Same overrides for JAX PPO |
| motrix_rl_skrl/src/.../framework.py | total_env_steps |
| motrix_rl_skrl/tests/test_skrl_*.py | Mapping, isolation, lifecycle tests |
| motrix_rl_rslrl/src/.../torch/train/live_logger.py | New RSL-RL LiveLogger subclass |
| motrix_rl_rslrl/src/.../torch/wrap_torch.py, wrap_np.py | Forward reward terms/metrics via extras["log"] as 0-dim tensors |
| motrix_rl_rslrl/src/.../torch/train/ppo.py | Attach LiveLogger when enabled |
| motrix_rl_rslrl/src/.../framework.py | total_env_steps |
| motrix_rl_rslrl/tests/test_rslrl_*.py | Logger mapping, RND, rank, real-runner tests |
| motrix_rl_builtin/src/.../fastsac/live_metrics.py | New system_scalars sys/* keys |
| motrix_rl_builtin/src/.../fastsac/sync/train.py | Scalars/events + TB writer close in finally |
| motrix_rl_builtin/src/.../fastsac/async_impl/train.py | Parent-only scalars/events, checkpoint notices, worker-crash reporting |
| motrix_rl_builtin/src/.../fastsac/async_impl/worker.py | Per-save checkpoint notice via panel queue |
| motrix_rl_builtin/src/.../fastsac/framework.py | total_env_steps |
| motrix_rl_builtin/tests/*.py | Sync/async live-metrics tests, thread-backed spawn mocks |
| wiki/design/live-metrics.md, wiki/plan/live-metrics.md, *index.md | Design/plan docs and index entries |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
静态检查了一下 live metrics 对训练 hot path 的额外成本,建议重点关注以下几点(未跑 benchmark,暂不量化吞吐影响):
另需区分「开启 live 的增量」和「本 PR 相对基线的增量」:RSLRL wrapper 新增的每步 reward/metrics 归约和标量分配不受 live 开关影响,关闭 live 也执行;训练时原生日志会消费,但 play/headless 视频路径返回的 infos 未被使用,建议关闭该场景的诊断采集。这些 Torch reward 均值保留在设备上,不应误判为新增每步 GPU→CPU 同步。 FastSAC sync 正常日志路径复用已有 host scalars,没有为 live 再做一次 loss 设备归约;async 常规上报在父进程,未进入 collector/learner 每步计算路径。不过 checkpoint 通知的 put(timeout=5.0) 在队列满时会等待,且 live 关闭时也存在,属于低频背压风险。 建议优先消除 RSLRL 的重复聚合、无消费者路径的逐步诊断,以及训练线程上的同步 JSONL IO。 |
2bb971e to
b4fbc90
Compare
|
结论:开启 live metrics(只开 jsonl、只开 http 或两者都开)后,训练吞吐的变化都在两次运行之间的正常波动范围内。即使输出出了故障也是如此,包括:
每次记录时,live 让训练线程多花约 10–20 µs;作为对比,同一次调用里 TensorBoard 本身就要约 400–550 µs。 为什么没有额外开销
测试方法
两种负载:
python bench/bench_live_metrics.py --repeats 7 # numpy,每次迭代都记录
python bench/bench_live_metrics.py --workload python --repeats 5 # 持有 GIL,每次迭代都记录
python bench/bench_live_metrics.py --log-every 50 --repeats 15 # numpy,每 50 次迭代记录一次测试环境:AMD Ryzen 7 9700X(8 核 16 线程),Python 3.10.19,torch 2.7.0+cu128,机器空闲。 结果开销 = 循环吞吐中位数相对
所有场景都在约 ±2.6% 以内,正负方向是随机的:正常输出并不比故障输出更快。这说明这些差异是测量噪声,而不是 live 的开销。 训练线程上每次
p99 的变化幅度和 p50 相同。 送达情况:
说明与限制
|
|
另: 去掉了rslrl/skrl的logger-metrics实现等后续在整理 |
|
rebase一下 |
Training runs can now push their metrics to an ingest service while they run, instead of only leaving TensorBoard files behind for later. It is off unless MOTRIX_METRICS_URL is set, and then every record goes both to run_dir/metrics.jsonl and, in batches, to the service. A slow or unreachable service never blocks or fails training. - motrix_rl: MetricsLogger with jsonl and http sinks (stdlib only), and the run lifecycle (running, then finished / failed / killed) reported from TrainerHandle.train(); play never builds a logger. - FastSAC (sync and async), SKRL and RSLRL emit their scalars under shared keys; TensorBoard tags are unchanged. - Fixes found on the way: RSLRL now forwards reward terms and env metrics via extras["log"], SKRL reward totals are per instance, and FastSAC sync closes its TensorBoard writer when training fails. - Design and plan in wiki/design/live-metrics.md and wiki/plan/live-metrics.md.
complete |
feat(rl): stream live training metrics over
motrix.metrics/v1Why
Until now, training metrics were only available after the fact as TensorBoard event files. An external
dashboard couldn't show progress, loss or reward during a run, or tell that a run had died. With this
PR, a training process pushes its metrics to any ingest service compatible with
motrix.metrics/v1while it runs.
It is off by default and turns on only when
MOTRIX_METRICS_URLis set. Once enabled, every recordgoes both to
run_dir/metrics.jsonland, in batches, to the service. A slow, unreachable or erroringservice never blocks training and never makes it fail. The terminal panel and TensorBoard are
unchanged.
Design:
wiki/design/live-metrics.md. Plan:wiki/plan/live-metrics.mdUsage
Without
MOTRIX_METRICS_URLthe logger is disabled: it creates no file, starts no thread, and everycall is a no-op.
playnever builds a logger.Changes
Control plane (
motrix_rl)motrix_rl/metrics.py, which uses only the stdlib, so there are no new dependencies:MetricsLoggerhasrun(),scalars(),event()andclose().seqand goes to both sinks, so the jsonl can later backfill the servicerecord for record, deduplicated by
(writer, seq).seqstarts at the wall clock rather than 0. A restarted container can reuse the same hostnameand pid, and the service would otherwise drop the new records as duplicates.
JsonlSinkappends tometrics.jsonland warns once on IO errors.HttpSinkuses a daemon thread and a bounded queue. It sends gzip batches toPOST {URL}/api/v1/ingest, up to 500 records per batch or every 2s.honours
Retry-After. Any other 4xx drops the batch with a single warning.torch.compileisn't marked as lost.null, and the first non-finite value for each key also emits anonfiniteevent. numpy and torch values are converted recursively.TrainerHandle.train()reports the run lifecycle:running, then one offinished,failed, orkilled(on Ctrl+C).failedrecord carries areason:Type: message, capped at 2KB, plus the paths of anyasync_errors/*.logfiles.runningrecord carries themetadata.jsonfields plusfamily(rl.offpolicy/rl.onpolicy),total_steps,num_envs, the resolvedconfigandmotrixlab_version.AgentProvider.total_env_steps(config, num_envs)hook, used for progress reporting.TrainerContext.metricspasses the logger to providers. When live metrics are enabled,metadata.jsonrecordslive.run_id.Providers
All providers emit scalars under shared canonical keys, aligned on
axes.env_steps. TensorBoard tagsare unchanged.
worker_crashedwhen a workerexits abnormally.
run_started,warmup_done,checkpoint_savedandresumedevents, plussys/*resource metrics.live_metrics.pymaps native tags to canonical keys. Unmappedtags are kept as
skrl/<slug>.live_logger.pydoes the same mapping. Unmapped tags are kept asrslrl/<slug>.The full key mapping and the step /
env_stepsconventions are in §3 of the design doc.Fixes found along the way
wrap_np/wrap_torch) now forward reward terms and env metrics viaextras["log"]. Previously they never reached TensorBoard.Tests
motrix_rl/tests/test_metrics.pycovers the logger and both sinks: the disabled state,seq, NaNhandling, the full-queue drop policy, retry and backoff, and
closetimeouts.motrix_rl/tests/fixtures/metrics_v1/*.jsonare golden fixtures that lock the protocol format,including the pipeline-side
pending/failedrecords.test_rl_contracts.pycovers theTrainerHandlelifecycle: finished, failed, killed andSystemExit(0).test_fastsac_live_metrics.py,test_skrl_live_metrics.pyandtest_rslrl_live_logger.pycovereach provider's key mapping, step axes and events.
test_fastsac_sync_trainer.py,test_skrl_ppo.pyandtest_rslrl_sim_backend.py.Compatibility
task_configschema is unchanged, and existing runs'task_config.yamlandmetadata.jsonstill load.
logging.backendvalue; live metrics are an additional output.Non-goals
This PR does not add a wandb sink, media upload, eval record production, or the server itself. The
protocol reserves the
evalandmediakinds for later.