[Tunix] Adopt OpenTelemetry behind MetricsLogger#1686
Open
erain wants to merge 1 commit into
Open
Conversation
erain
requested review from
abheesht17,
hgao327,
jiangyangmu,
lc5211,
s-noghabi,
sizhit2,
tianshub and
wang2yn84
as code owners
July 15, 2026 19:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #1685
Summary
This change keeps
MetricsLoggeras the public Tunix telemetry facade while replacing the Metrax/JAX-monitoring backend implementation with OpenTelemetry.The facade now:
log(),metric_exists(),get_metric(),get_metric_history(), andclose()call patterns;MeterProvider;span()for correlated tracing andevent()for structured Python log records that an OpenTelemetry logging bridge can export;opentelemetry-apito the core package. The SDK remains a test/application dependency.Motivation
The previous implementation coupled each logger to Metrax backends through process-global JAX monitoring listeners. That caused three architectural problems:
close()could clear process-global listeners and close resources shared by other trainers.OpenTelemetry gives Tunix a vendor-neutral instrumentation layer. Applications can choose OTLP, a collector, or a vendor distribution without changing Tunix training call sites.
Design
1. Compatibility facade
MetricsLoggerremains the interface used by SFT, RL, performance metrics, and progress bars. Existing metric logging and local history behavior is preserved, including geometric-mean aggregation for perplexity.MetricsLoggerOptionsremains copy-safe and continues to accept these legacy fields so existing Python and YAML configuration still parses:log_dirproject_namerun_nameflush_every_n_stepsbackend_kwargsThose fields no longer configure telemetry backends.
backend_kwargsemits a deprecation warning because readers, processors, and exporters now belong on OpenTelemetry providers. The legacylog_dirremains temporarily meaningful to agentic trajectory CSV logging; that artifact path is explicitly documented as separate from telemetry.Two signal-level options are added:
enabled: disables signal emission while retaining local metric history.emit_on_all_processes: emits on every JAX process and addstunix.jax.process.index; otherwise process zero emits.attributessupplies common low-cardinality signal attributes. Service, deployment, and run metadata are documented as OpenTelemetryResourceattributes instead.2. Provider and lifecycle ownership
The core package depends on
opentelemetry-api, not the SDK. A process without an SDK receives the standard no-op meter/tracer behavior and training continues normally.Applications may configure global providers or inject
meter_providerandtracer_providerwhen constructing a logger. Injection is keyword-only so the existing positional constructor remains unchanged.MetricsLogger.close()deliberately does not flush or shut down providers. Provider lifecycle is owned by the application that configured the readers, processors, and exporters.Trainer ownership now follows the same rule:
PeftTrainercloses a logger only when it created that logger.RLCluster, which creates and shares the RL logger, closes that facade once after its trainers close.This prevents one trainer from invalidating telemetry used by another trainer or by the host application.
3. Metric data model
Synchronous gauges represent the latest training values because loss, perplexity, learning rate, and gradient norm are non-monotonic current measurements.
losstunix.training.lossperplexitytunix.training.perplexitylearning_ratetunix.training.learning_rategrad_normtunix.training.gradient.normUnknown user metric names remain supported. They are normalized into the
tunix.*namespace, so a key such asrewards/score meanbecomestunix.rewards.score.mean.The legacy prefix and mode become attributes:
tunix.metrics.prefixtunix.training.modeThe logical step is emitted as a separate
tunix.training.stepgauge. It is intentionally not a metric attribute, because a distinct attribute value per step would create an unbounded time-series cardinality.Only numeric scalar values are emitted. Non-scalar values remain in local history and produce a warning rather than interrupting training. Telemetry conversion/export setup failures are best effort and cannot prevent history updates.
The instrumentation scope is the stable name
tunix, with the installedgoogle-tunixpackage version when available.4. Traces and structured events
The same facade adds:
span(name, attributes=...), implemented withstart_as_current_span;event(name, body=..., attributes=..., severity=...), implemented as a structured standard-library log record.Using standard Python logging avoids depending on private/SDK logging APIs in the core library. Applications that install an OpenTelemetry logging handler can export these records and correlate them with the current span.
This change instruments two high-value operations without replacing Tunix's existing performance tracing:
tunix.model.trainaround a PEFT training step;tunix.rolloutaround RL generation.The existing Perfetto-oriented tracers continue to run alongside these OpenTelemetry spans. Injected legacy-compatible logger objects that do not implement
span()continue to work through a no-op context fallback.5. Concurrency and process behavior
Metric history and instrument creation use locks so shared logger instances are safe against concurrent logging. Process-zero emission remains the default for all three signal types to avoid duplicate data. All-process emission is explicit and identifies the source JAX process.
Migration impact
This is source-compatible for normal trainer and logger call sites, but intentionally changes backend ownership:
Resource, readers/processors, and exporters before creating trainers.The documentation includes an OTLP metric/trace setup example and explains the no-SDK behavior, multi-process policy, structured logging bridge, lifecycle boundary, and legacy trajectory path.
Non-goals
MetricsLoggerOptions; that is called out as a follow-up.Validation
Installed the package normally with the test extra:
python -m pip install -e '.[test]'Focused package integration:
python -m pytest tests/sft/metrics_logger_test.py tests/sft/progress_bar_test.py tests/sft/peft_trainer_test.py::PeftTrainerTest::test_close_does_not_close_injected_metrics_logger -q # 21 passed, 3 subtests passedComplete SFT trainer integration:
python -m pytest tests/sft/peft_trainer_test.py -q # 23 passedApplicable RL cluster integration:
The deselected cases require optional
vllmorsgl_jaxinstallations that are not included in.[test]. An unfiltered run passed the other 26 cases and failed only while importing those optional engines.Additional checks:
git diff --checkpassed for the modified sources. Pylint reports only pre-existing non-blocking warnings in trainer/cluster modules.mdformat1.0.0 is pinned withmdformat-gfm0.3.5, which requiresmdformat<0.8; Python hooks were run individually.Reference
Colab Notebook
Not applicable. The existing
MetricsLoggerand trainer entry points remain the usage surface; SDK/exporter configuration and the additive span/event methods are documented and covered with in-memory SDK tests.Checklist