nemo_rl.telemetry.host_memory#
Host memory per node, scraped from Ray and exported through lens.
Same source as the W&B path: every raylet publishes ray_node_mem_used and
ray_node_mem_total on its own Prometheus endpoint, and
RayGpuMonitorLogger in :mod:nemo_rl.utils.logger already reads them.
Nothing else in NeMo-RL reports host memory – utils/memory_tracker.py
samples the driver’s own RSS, prints it, and is wired only into PPO – so
without this a run’s OTel stream has GPU-side numbers and no host-side ones.
Scraped here rather than teed out of the GPU monitor because that path flattens
the node’s identity into the metric name (node.3.mem_gb) and discards the
hostname, and because it is gated on logger.monitor_gpus: reading it would
make an OTel series disappear when someone turns off a W&B feature. The cost is
a second HTTP GET per node per interval, against a local endpoint, which is not
measurable next to a training step.
What the number is, and is not: ray_node_mem_used is whole-host memory. It
covers the raylet, the Ray object store (often tens of GB, and frequently the
thing that actually ran the node out of memory), every worker resident on the
host, and – on the head node – the driver. It is not a per-process or
per-worker-group figure, and there is no breakdown inside it; see
- data:
RL_WORKER_GROUPS_ATTRfor what the worker-group attribute can and cannot be used for.
Module Contents#
Classes#
One live Ray node: who it is, and where to scrape it. |
|
Samples every Ray node’s host memory on a timer, for lens to export. |
Functions#
One observation per host in the latest scrape, for registry key. |
|
Record that worker group name_prefix occupies node_ids. |
|
Resolve bundles to nodes and register them. Never raises. |
|
Drop name_prefix’s placement, for a group that has been shut down. |
|
Worker groups with at least one worker on node_id, unformatted. |
|
Render names as :data: |
|
Value of :data: |
|
Live Ray nodes that publish a metrics endpoint. |
|
Extract |
|
Start this process’s monitor, if it has none. Never raises. |
|
Stop this process’s monitor. Never raises; a no-op if none is running. |
Data#
API#
- nemo_rl.telemetry.host_memory.logger#
‘getLogger(…)’
- nemo_rl.telemetry.host_memory.RL_HOST_MEMORY_USED_METRIC#
‘rl.host.memory.used’
- nemo_rl.telemetry.host_memory.RL_HOST_MEMORY_TOTAL_METRIC#
‘rl.host.memory.total’
- nemo_rl.telemetry.host_memory.HOST_NAME_ATTR#
‘host.name’
- nemo_rl.telemetry.host_memory.RL_WORKER_GROUPS_ATTR#
‘rl.worker_groups’
- nemo_rl.telemetry.host_memory.NO_WORKER_GROUPS#
‘none’
- nemo_rl.telemetry.host_memory.RAY_NODE_MEM_USED#
‘ray_node_mem_used’
- nemo_rl.telemetry.host_memory.RAY_NODE_MEM_TOTAL#
‘ray_node_mem_total’
- nemo_rl.telemetry.host_memory._SERIES_BY_RAY_NAME#
None
- nemo_rl.telemetry.host_memory._USED_KEY#
‘registry_key(…)’
- nemo_rl.telemetry.host_memory._TOTAL_KEY#
‘registry_key(…)’
- nemo_rl.telemetry.host_memory._SNAPSHOT: dict[tuple[str, str], dict[str, float]]#
None
- nemo_rl.telemetry.host_memory._SNAPSHOT_LOCK#
‘Lock(…)’
- nemo_rl.telemetry.host_memory._observations(key: str) list[opentelemetry.metrics.Observation]#
One observation per host in the latest scrape, for registry key.
- nemo_rl.telemetry.host_memory._observe_used(
- options: opentelemetry.metrics.CallbackOptions,
- nemo_rl.telemetry.host_memory._observe_total(
- options: opentelemetry.metrics.CallbackOptions,
- nemo_rl.telemetry.host_memory.HOST_MEMORY_RECORDED_METRICS#
()
- nemo_rl.telemetry.host_memory.FALLBACK_COLLECTION_INTERVAL#
10.0
- nemo_rl.telemetry.host_memory._SCRAPE_TIMEOUT_S#
5.0
- nemo_rl.telemetry.host_memory._SHUTDOWN_GRACE_S#
None
- nemo_rl.telemetry.host_memory._WORKER_GROUP_NODES: dict[str, frozenset[str]]#
None
- nemo_rl.telemetry.host_memory._WORKER_GROUP_LOCK#
‘Lock(…)’
- nemo_rl.telemetry.host_memory.register_worker_group_nodes(
- name_prefix: str,
- node_ids: Iterable[str],
Record that worker group name_prefix occupies node_ids.
- Parameters:
name_prefix – The group’s
RayWorkerGroup.name_prefix– the same value its workers carry as therl.worker_groupresource attribute, so a reader can join the two.node_ids – Ray
NodeIDhex strings the group’s workers are placed on.
Replaces any previous entry for the group, so a group rebuilt onto different nodes after a worker restart is not reported on both sets.
- nemo_rl.telemetry.host_memory.register_worker_group_placement(
- name_prefix: str,
- bundles: Iterable[tuple[Any, int]],
Resolve bundles to nodes and register them. Never raises.
The form :class:
~nemo_rl.distributed.worker_groups.RayWorkerGroupcan supply directly: it knows each worker’s(placement group, bundle index)but not the node underneath, andplacement_group_tableanswers that from the GCS’s own scheduling record – driver-side, with no worker asked anything. Each placement group is looked up once however many bundles it contributes, since the table covers all of them.- Parameters:
name_prefix – See :func:
register_worker_group_nodes.bundles –
(placement_group, bundle_index)pairs the group occupies.
- nemo_rl.telemetry.host_memory.forget_worker_group_nodes(name_prefix: str) None#
Drop name_prefix’s placement, for a group that has been shut down.
- nemo_rl.telemetry.host_memory.worker_group_names_on_node(node_id: str) set[str]#
Worker groups with at least one worker on node_id, unformatted.
- nemo_rl.telemetry.host_memory.format_worker_groups(names: Iterable[str]) str#
Render names as :data:
RL_WORKER_GROUPS_ATTRcarries them.
- nemo_rl.telemetry.host_memory.worker_groups_on_node(node_id: str) str#
Value of :data:
RL_WORKER_GROUPS_ATTRfor node_id.
- class nemo_rl.telemetry.host_memory.RayNode#
One live Ray node: who it is, and where to scrape it.
- node_id: str#
None
- host: str#
None
- metrics_url: str#
None
- nemo_rl.telemetry.host_memory.ray_nodes() list[nemo_rl.telemetry.host_memory.RayNode]#
Live Ray nodes that publish a metrics endpoint.
Returns an empty list when Ray is not up, which makes the collection loop idle rather than fail: the driver starts telemetry before
init_ray().
- nemo_rl.telemetry.host_memory.parse_memory_samples(metrics_text: str) dict[str, float]#
Extract
{registry key: bytes}from one node’s Prometheus payload.Pure function (no OTel side effects) so it is trivially unit-testable. Unknown series are ignored, which is nearly all of them: a raylet publishes hundreds, and only the two in :data:
_SERIES_BY_RAY_NAMEare wanted.
- class nemo_rl.telemetry.host_memory.HostMemoryMonitor(collection_interval: float)#
Samples every Ray node’s host memory on a timer, for lens to export.
Driver-side and read-only: it scrapes endpoints the cluster already serves and touches no worker. Start it once the cluster is up; it tolerates being started earlier and simply finds no nodes until there are some.
It publishes into :data:
_SNAPSHOTrather than recording: the series are observable, so the exporter pulls the latest sample on its own schedule. The two intervals are independent, which means a sample can be up to one collection interval old when it is exported, and that samples taken between two export cycles are never seen. Both are properties of a gauge and neither is worth coupling the threads to avoid.Initialization
Initialise the monitor.
- Parameters:
collection_interval – Seconds between samples of every node. Must be positive; :func:
start_host_memory_monitoringis what enforces that, since it is the only caller a run goes through.
- start() None#
Begin sampling on a background thread. Idempotent.
- stop() None#
Stop sampling and wait for the thread to finish. Idempotent.
- collect_once() None#
Sample every node and publish the result for export. Never raises.
The guarantee lives here rather than in the loop so that the loop cannot be killed by a single bad sample, and so a caller driving the monitor by hand gets the same protection.
- _collect_once() None#
Body of :meth:
collect_once, inside its exception guard.
- _scrape( ) dict[str, float]#
Fetch and parse one node’s memory series, or
{}if unusable.A node that cannot be scraped is skipped rather than retried: the next interval is the retry, and a node that is down or overloaded is exactly the one that must not be allowed to stall the loop for the others.
- _collection_loop() None#
Sample until stopped, on :attr:
collection_interval.
- nemo_rl.telemetry.host_memory._MONITOR: Optional[nemo_rl.telemetry.host_memory.HostMemoryMonitor]#
None
- nemo_rl.telemetry.host_memory.start_host_memory_monitoring(collection_interval: float) None#
Start this process’s monitor, if it has none. Never raises.
Called from
init_telemetry_driver, which is what puts the monitor in the driver process on every entry point. That is not a detail: the placement map above is an ordinary module global, so it is only visible to the process that wrote it, andRayWorkerGroupwrites it from the driver. Owning the monitor anywhere else reports the right memory under the wrong label – the single-controller entry points build their worker groups on the driver (examples/run_grpo_single_controller.py,examples/run_sft_v2.py) but theirLoggerinside the controller actor, whose copy of the map would never be anything but empty.- Parameters:
collection_interval – Seconds between samples of every node.
- nemo_rl.telemetry.host_memory.stop_host_memory_monitoring() None#
Stop this process’s monitor. Never raises; a no-op if none is running.
Called from
shutdown_telemetry, which every worker also calls – hence the no-op rather than an assertion that one is running.