Architecture
NeMo Lens is intentionally small. Its architecture prioritizes three properties:
- Low overhead when disabled: instrumented code should pay a trivial gating cost (a
frozensetlookup) when a span group is off, and never construct span objects. - Optional dependency: consumer libraries (such as Megatron-LM, NeMo RL, and NeMo Gym) can ship without bundling NeMo Lens.
- Clean layering: each module has one responsibility and stable boundaries.
Layer Diagram
Each layer depends only on layers above it. No circular imports.
Module Responsibilities
Call Flow for setup_telemetry
Call Flow for managed_span
The hot path when disabled is three Python statements: lookup, compare, yield None.
Why This Factoring
State separated from logic: state.py is just a frozenset and a lock. managed_span in helpers.py imports is_span_group_enabled, but does not know how groups are stored. Swapping the state implementation (e.g., to add per-thread overrides) does not touch helpers.py.
Config separated from providers: config.py has no dependency on OTel SDK. A consumer can construct and validate NemoLensConfig in a process that does not have the SDK installed, then decide whether to initialize telemetry.
Lazy SDK imports: providers.py is the primary home for opentelemetry.sdk.* construction (a few other modules, such as sampling.py and logging_bridge.py, import the SDK lazily inside function bodies). Non-exporting ranks never execute build_providers, so they never incur the SDK import cost. On a large-rank job where most ranks do not export, avoiding that import on every non-exporting rank adds up.
Public API minimal: __init__.py exports only the documented public surface (the entries in __all__). Everything else is internal. Consumers can rely on the __all__ list; internals can be refactored freely.
Thread Safety
The NeMo Lens global state is protected where it matters:
state._ENABLED_GROUPSis written under a lock, read lock-free (a frozenset is immutable so readers see a consistent snapshot).state._PP_TRACE_CARRIERis written duringsetup_telemetry(single-threaded) and read from multiple threads in the pipeline schedule.- Instrument caches use
WeakKeyDictionary, which is thread-safe under CPython’s GIL for the operations NeMo Lens performs.
setup_telemetry itself is not thread-safe; it is documented as “call once per process.” The double-init guard surfaces violations.
What Is Out of Scope
NeMo Lens is deliberately narrow. Log shipping beyond the OTel logging bridge remains with your existing log stack. Profiling CPU, memory, or GPU activity is the responsibility of the PyTorch profiler or nsys, not a tracing library. APM-style features such as service maps and error tracking are handled by whichever backend consumes the OTLP stream (such as Datadog, Sentry, or other compliant platforms).
The scope is also restricted to the NeMo ecosystem’s training and inference workloads. The core primitives are domain-agnostic, but the opinionated components (span groups, metric instruments, and resource attributes) lean toward machine learning workloads. NeMo Lens does not serve as a general-purpose instrumentation library for unrelated domains.