nemo_rl.telemetry.setup#
Process-global nemo-lens telemetry lifecycle for NeMo-RL.
Two entry points, mirroring Megatron’s global_vars._set_telemetry /
get_telemetry_handle pattern but adapted to NeMo-RL’s Ray driver + worker process
model:
- func:
init_telemetry_driver— called once on the driver, beforeinit_ray(). It reads thetelemetry:config block, exports the settings asNEMO_RL_OTEL_*env vars so every Ray worker inherits them, and sets up the driver’s own telemetry (the training loop and the metrics logger run here, so the driver always exports).
- func:
init_telemetry_worker— called once inside each Ray actor process, from the worker’s__init__(policy, value and vLLM generation workers). It reads the propagated env and sets up that worker’s telemetry, then- func:
shutdown_telemetryflushes it from the worker’sshutdown.__init__rather thanpost_init, because somepost_initfan-outs run on only one rank per parallel group, while OTel providers have to be set up in every actor process.
nemo-lens is a base dependency, so telemetry.enabled is the only switch:
when it is false the init functions return None and every instrumentation
site is a ~0-cost no-op. Lens imports stay function-local to keep the cost off
the import path of modules that never emit anything.
Module Contents#
Functions#
Lifecycle stage for algorithm; :data: |
|
Return True if env var |
|
Whether |
|
Whether |
|
Translate a |
|
Best-effort nested lookup that works for both dicts and objects. |
|
Build process-lifetime resource attributes (Jaeger “Process” tags). |
|
Resource attributes identifying this process within its group. |
|
Initialise driver-side telemetry (call once, before |
|
Build resource attributes identifying this worker process. |
|
Initialise telemetry inside a Ray actor (call once per worker process). |
|
Make outgoing |
|
Decorate a worker |
|
Return the process-global telemetry handle ( |
|
Flush and shut down telemetry providers, returning within timeout_ms. |
Data#
API#
- nemo_rl.telemetry.setup.logger#
‘getLogger(…)’
- nemo_rl.telemetry.setup._F#
‘TypeVar(…)’
- nemo_rl.telemetry.setup._TELEMETRY_HANDLE: Optional[nemo.lens.TelemetryHandle]#
None
- nemo_rl.telemetry.setup._TELEMETRY_INITIALISED#
False
- nemo_rl.telemetry.setup._OTEL_PREFIX#
‘NEMO_RL_OTEL’
- nemo_rl.telemetry.setup._OTEL_FALLBACK_PREFIX#
‘NEMO_LENS’
- nemo_rl.telemetry.setup._RUN_ID_ENV#
None
- nemo_rl.telemetry.setup._WORKER_GROUP_ENV#
‘NRL_WORKER_GROUP’
- nemo_rl.telemetry.setup._CAMPAIGN_STAGE_ATTR#
‘nv.dl.campaign.stage’
- nemo_rl.telemetry.setup._DEFAULT_CAMPAIGN_STAGE#
‘RL’
- nemo_rl.telemetry.setup._CAMPAIGN_STAGE_BY_ALGORITHM#
None
- nemo_rl.telemetry.setup._CAMPAIGN_STAGE_ENV#
None
- nemo_rl.telemetry.setup._campaign_stage(algorithm: str) str#
Lifecycle stage for algorithm; :data:
_DEFAULT_CAMPAIGN_STAGEif unknown.
- nemo_rl.telemetry.setup._ENV_FIELD_MAP#
None
- nemo_rl.telemetry.setup._SERVICE_NAME_ENV#
‘OTEL_SERVICE_NAME’
- nemo_rl.telemetry.setup._is_env_truthy(name: str) bool#
Return True if env var
nameis set to a truthy value.
- nemo_rl.telemetry.setup.telemetry_enabled_in_env() bool#
Whether
telemetry.enabledreached this process as truthy.Exists for instrumentation that never asks for a handle: vLLM’s native tracing runs on vLLM’s own exporter, so :func:
get_telemetry_handlewould not gate it and the master switch would not reach it. Reads the environment rather than a config object because that is the only channel a worker process has.
- nemo_rl.telemetry.setup.vllm_native_tracing_requested() bool#
Whether
telemetry.vllm_native_tracingreached this process as truthy.Callers must also honour :func:
telemetry_enabled_in_env; this reports only the one field.
- nemo_rl.telemetry.setup._config_to_env(tel: Any) None#
Translate a
TelemetryConfigintoNEMO_RL_OTEL_*env vars.Uses
os.environ.setdefaultso raw env vars always win over YAML. Runs on the driver beforeinit_ray(), so the resulting environment is snapshotted into the Rayruntime_envand inherited by every worker process.
- nemo_rl.telemetry.setup._dig(obj: Any, *path: str) Any#
Best-effort nested lookup that works for both dicts and objects.
Returns
Noneas soon as any level is missing. Used to pull resource attributes out of aMasterConfigwhose nested nodes may be pydantic models (attribute access) or TypedDict-derived dicts (key access).
- nemo_rl.telemetry.setup._build_resource_attributes(
- master_config: Any,
- algorithm: str,
Build process-lifetime resource attributes (Jaeger “Process” tags).
Only stable-for-the-run values belong here (algorithm, model, precision, parallelism). Per-step values are span tags; time-series values are metrics. Best-effort: a missing key simply omits that attribute — never raises. Rank identity comes from :func:
_rank_attributes, which the callers merge in.
- nemo_rl.telemetry.setup._rank_attributes(rank: int, world_size: int) dict[str, Any]#
Resource attributes identifying this process within its group.
Lens has no notion of rank: it neither filters nor samples on one, so every process that sets up telemetry exports. Rank is recorded as a resource attribute instead, which is what makes a single rank’s spans selectable afterwards – filter on
nv.dl.rankin the collector. There is no per-rank switch to reach for instead:telemetry.enabledis one job-wide value every worker inherits through the Rayruntime_env.Passing rank at all is what keeps that filter available; lens warns when
nv.dl.rankis missing, because without it a process cannot be told apart from its peers downstream.
- nemo_rl.telemetry.setup.init_telemetry_driver(
- master_config: Any,
- algorithm: str,
Initialise driver-side telemetry (call once, before
init_ray()).Reads
master_config.telemetry, exports the resolved settings asNEMO_RL_OTEL_*env vars (so workers inherit them), and sets up the driver’s OTel providers. The driver always exports (it hosts the training loop and the metrics logger).Returns the :class:
TelemetryHandle, orNoneif telemetry is disabled. Idempotent.
- nemo_rl.telemetry.setup._WORKER_GROUP_ATTR#
‘rl.worker_group’
- nemo_rl.telemetry.setup._worker_resource_attributes(
- worker_group: Optional[str],
- extra: Optional[dict[str, Any]],
Build resource attributes identifying this worker process.
RANKis group-local — the policy group and the generation group each number their workers from zero — sonv.dl.rankalone cannot tell their spans apart. :data:_WORKER_GROUP_ATTRcarries the group’sname_prefix(lm_policy,vllm_policy, …), whichRayWorkerGroupexports asNRL_WORKER_GROUP. An explicit worker_group wins over the environment, for the actors built outsideRayWorkerGroup, which nothing sets it for. Explicitextraattributes win over both.
- nemo_rl.telemetry.setup.init_telemetry_worker(
- rank: Optional[int] = None,
- world_size: Optional[int] = None,
- worker_group: Optional[str] = None,
- resource_attributes: Optional[dict[str, Any]] = None,
Initialise telemetry inside a Ray actor (call once per worker process).
Reads the
NEMO_RL_OTEL_*env propagated from the driver via the Rayruntime_env.rank/world_sizedefault to theRANK/WORLD_SIZEenv vars the worker was launched with, and are recorded asnv.dl.rank/nv.dl.world_sizeresource attributes.Every worker that gets here exports, and
telemetry.enabledis job-wide, so it cannot keep some ranks quiet while others export. Narrowing down is a downstream decision: filter onnv.dl.rankin the collector.- Parameters:
rank – This process’s rank. Defaults to the
RANKenv var.world_size – Size of this process’s group. Defaults to
WORLD_SIZE.resource_attributes – Extra resource attributes for this process.
Never raises: a worker must not fail a training run over optional observability. The driver resolves the same config before any worker starts, so a misconfiguration is already reported once, from the one process whose log the user is reading — rather than once per worker.
Returns the :class:
TelemetryHandle, orNoneif telemetry is disabled or setup failed. Idempotent per process.
- nemo_rl.telemetry.setup._AIOHTTP_INSTRUMENTED#
False
- nemo_rl.telemetry.setup.instrument_aiohttp_client() bool#
Make outgoing
aiohttprequests carry the caller’s trace context.The NeMo-Gym rollout path leaves this process over HTTP from inside Gym’s own client, so there is no request NeMo-RL can add a header to. Patching the client library instead means every request Gym makes is stamped with a W3C
traceparenttaken from the ambient context, which is what lets a Gym server running :func:nemo.lens.contrib.fastapi.instrument_fastapicontinue the trace rather than start one.Call after worker telemetry is up and before any
ClientSessionexists – the instrumentor patches the class, so sessions built earlier keep the unpatched behaviour.- Returns:
Whether the client is instrumented.
Falsewhen telemetry is off, or when the optional dependency is missing – neither is fatal, because losing the Gym half of a trace is not a reason to fail a rollout.
- nemo_rl.telemetry.setup.traced_worker_init(
- span_name: str,
- **attributes: Any,
Decorate a worker
__init__so its model load lands in a span.Training workers build and shard the model inside
__init__, which on a large checkpoint is the longest single phase of a run’s startup. Without this the time is real but unattributed: the driver’srl.setup.workersspan covers it as one block, and nothing says what the worker was doing.Initialises worker telemetry before the body runs. A worker normally calls
- Func:
init_telemetry_workeritself a few lines into__init__, but a span cannot open before its provider exists, so the decorator hoists that call ahead of the constructor; the body’s own call then returns the handle already made, being idempotent per process.
A
Nonehandle – telemetry off, or its setup failed – runs the constructor untouched. That is also what keeps this safe whennemo.lensis absent: the span imports below are reached only once a handle exists, which cannot happen without lens, so an install without it neither imports nor pays for any of this.The span belongs to the
model_initumbrella and so carries norl.bucket: it is startup rather than a phase of a training step, and the goodput rollup measures steps.
- nemo_rl.telemetry.setup.get_telemetry_handle() Optional[nemo.lens.TelemetryHandle]#
Return the process-global telemetry handle (
Noneif uninitialised).Named for the handle rather than the telemetry because callers reach through it –
.tracer,.meter,.is_exporting– rather than using the return value as a value.
- nemo_rl.telemetry.setup.shutdown_telemetry(timeout_ms: int = 5000) None#
Flush and shut down telemetry providers, returning within timeout_ms.
Call on the driver at job end, and in each Ray actor’s
shutdown: span and metric processors buffer in the background, so an actor that exits without flushing silently drops whatever it had not exported yet. A no-op when this process never initialised telemetry.The budget is enforced here rather than passed down, because nothing below honours it.
TelemetryHandle.shutdownforwardstimeout_mstoforce_flush(timeout_millis=...), and the SDK’sBatchProcessoraccepts that argument and then drains the whole queue regardless; theprovider.shutdown()calls that follow take no timeout at all and fall back to the SDK’s own 30s. Four unbounded calls in total. Against an unreachable collector each buffered batch is retried with exponential backoff, so the cost scales with the queue: 3k spans took 36s under a 5s budget when measured.That mattered because callers size their own timeouts on this one –
_flush_collector_telemetryin async GRPO allows 15s for a 3s quiesce plus “its 5s export”, thenray.kills the collector when the budget blows, dropping the last rollout spans the flush exists to save.So: run the flush on a daemon thread and stop waiting at the deadline. A flush that overruns is abandoned rather than joined, which loses the same spans an unreachable collector was going to lose anyway, and being a daemon it never holds up process exit.