feat: expand vLLM observability/telemetry - #526
Conversation
|
PR changed again? Review this PR in Change Stack to compare snapshots and stay oriented. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds GenerationObservability schema and telemetry probes (NVML, loadavg, vLLM metrics, engine runtime config), a WandB logging Protocol/helper, integrates emission into VllmBackend.generate() as a finally-bracketed event, and adds unit tests for contracts and integration. ChangesvLLM generation observability
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
f7f177e to
aa91b42
Compare
Greptile SummaryAdds a self-contained
Confidence Score: 5/5Safe to merge — observability failures are fully insulated from generation results and all previous review findings are resolved. All three previously-flagged issues (NVML resource leak, wrong device index, logger-in-except propagation in vllm_backend.py) are correctly fixed. The generate() refactor is structurally sound: _run_generation always succeeds or raises, _emit_generation_observability is always called via finally and swallows every failure including a contextlib.suppress guard on the inner logger. Degraded-mode coverage is thorough and the 28 contract tests include the shutdown-on-handle-failure path. The two remaining notes are non-blocking style observations. No files require special attention — the only open item is the unprotected logger.warning in wandb_setup.py's except branch, which is guarded by the generation path's outer try/except today and is a preventative concern for a future training wiring. Important Files Changed
|
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: d2e5e7a9-cf3f-4117-ae86-b280f679957c
📒 Files selected for processing (5)
src/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pytests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{md,markdown,py}
📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)
**/*.{md,markdown,py}: Avoid decorative bold (**text**) in list items, body text, and docstrings; use structural cues (headers, list markers, colons, backticks) for emphasis instead
Use backticks for code identifiers, paths, and CLI commands in markdown and docstrings
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
**/*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
**/*.py: Use American English spelling: 'initialize' not 'initialise', 'recognize' not 'recognise', 'color' not 'colour'.
Useobservability.get_logger(__name__)for logging, neverlogging.getLogger()orstructlog.get_logger()directly.
Use category loggers:.runtimefor internals,.userfor progress/results,.systemfor system events.
Never useprint()for operational output. Useclick.echo()for CLI output orsys.stdout.write()for raw output in tools.
Useextra={}in logging for structured data that downstream tools should query or aggregate; use f-strings for human-readable context.
Raise from the custom error hierarchy with dual inheritance:SafeSynthesizerError(base),UserError,DataError,ParameterError,GenerationError,InternalError.
UseNSSBaseModelfor config/parameter models inconfig/which define user-facing configuration. Use rawBaseModelor module-specific bases for data transfer objects and internal structures.
UseBaseSettingsfor env/CLI settings. PreferAliasChoiceson individual fields when a field needs to respond to both its Python name and an env var name.
IncludeField(description=...)for Pydantic model fields as the canonical field docstring for API documentation and CLI help text.
Use assignment-styletype = Field(default=..., description="...")as the default for Pydantic model fields because type checkers understanddefault,default_factory, andaliasin assignment style.
UseAnnotatedonly when the field carries additional metadata beyondField()--ValueValidator,AutoParam,DependsOnValidator, reusable constrained type aliases, nested-type constraints, or discriminated unions.
Put defaults as bare assignment (= value), not insideField(default=...), when usingAnnotated. Exception: use assignment-styleField(default_factory=...)for defaults that cannot be expressed as bare assignments.
Use@dataclass(frozen=True)for immutable value objects and validators; mu...
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
src/**/*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
src/**/*.py: Use relative imports insrc/(e.g.,from ..observability import get_logger).
Do not useprint()statements in library code. Useget_logger(__name__)fromobservability.pyorclick.echo()for CLI.
Do not useassertfor validation in library code. Useif/raisefor input validation.assertstatements can be stripped by-Oand must never guard correctness.
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
⚙️ CodeRabbit configuration file
Review library code against STYLE_GUIDE.md. Focus on behavior, API contracts, error handling, resource cleanup, typing, logging, and user-facing failures. Public APIs and nontrivial functions need Google-style docstrings.
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
**/*.{py,sh,yaml,yml}
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
Include SPDX copyright header at the top:
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.and# SPDX-License-Identifier: Apache-2.0. Themake formatcommand handles this automatically.
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
**/*
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
**/*: Include a newline at the end of all files, never trailing whitespace. This is enforced bypre-commit.
Use line length of 120 characters for code, comments, and docstrings (configured inruff.toml).
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
⚙️ CodeRabbit configuration file
**/*: Review as a senior maintainer for NeMo Safe Synthesizer. Prioritize issues that can change behavior, break user workflows, weaken privacy guarantees, hide failures, make tests unreliable, or create maintenance risk. Avoid generic style commentary unless it points to a concrete project convention that automated tools will not catch.
Comment only when the finding is actionable and tied to changed code. For each finding, state the impact, the condition that triggers it, and the smallest practical fix. Prefer one precise comment over broad advice. Do not ask for refactors outside the PR scope unless the changed code creates the problem.
Review type guidance: - Potential issue: use for correctness bugs, data loss, privacy leaks,
security risks, broken public APIs, invalid config behavior, missing
validation, hidden failures, nondeterministic tests, or CI breakage.
- Refactor suggestion: use for local maintainability problems introduced
by the diff when they have clear future cost, such as duplicated setup,
unclear boundaries, over-mocking, avoidable complexity, or opaque test
helpers.- Nitpick: avoid in chill mode. Do not emit formatting, import-order,
wording, or style-only comments unless automated tools cannot catch the
issue and it affects maintainability.Severity guidance: - Critical: security/privacy leaks, data loss, training/test/holdout
contamination, or broken release/package/core pipeline execution.
- Major: incorrect generation/training/evaluation behavior, broken
CLI/SDK public API, invalid config defaults or validators, or GPU/vLLM
cleanup and process-isolation bugs likely to fail CI or production
runs.- Minor: localized bugs, missing focused tests for changed behavior, or
bad test patterns that weaken regression coverage.- Trivial: small cleanup with no behavior impact. Usually suppress in
chill mode.- Info: context only. Avoid unless it helps reviewers understand risk.
Safe-Synthesizer-specific review focus: - Data ...
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
tests/**
📄 CodeRabbit inference engine (.cursor/rules/repo-navigation.mdc)
Tests should mirror the
src/directory structure intests/
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
tests/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/repo-navigation.mdc)
Tests in
tests/e2e/should be auto-marked withe2emarker, tests intests/smoke/withsmokemarker, others withunitmarker
tests/**/*.py: Use absolute imports intests/(e.g.,from nemo_safe_synthesizer.observability import get_logger).
Usefixture_prefix convention for fixtures for grep-ability and to separate fixtures from test functions. Add a one-line docstring describing the fixture's purpose and data.
Use function-scoped fixtures by default. Session scope only when empirically justified by test runtime.
Use bareassertas the primary assertion style;pytest.raises()withmatch=for exceptions;pytest.approx()for floating-point comparisons.
Mark CUDA-dependent tests with@pytest.mark.e2e,@pytest.mark.smoke, or@pytest.mark.requires_gpu.
Mock only external boundaries, not internal implementation details.
Ensure test isolation: no shared mutable state or execution-order dependencies between tests. If something must be run first before executing a test, include it in the test or a fixture.
Use@pytest.mark.parametrizefor testing multiple input combinations rather than copy-pasting similar tests.Organize tests using pytest following the structure in
tests/TESTING.mdwith support for unit tests, smoke tests, and end-to-end tests
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
⚙️ CodeRabbit configuration file
Review tests against tests/TESTING.md. Check marker usage, fixture naming, tmp_path usage, determinism, and GPU/vLLM process-isolation requirements. Flag slop tests that only check that code runs, assert result is not None when stronger invariants exist, over-mock internal implementation details, patch around the bug instead of reproducing it, or add broad snapshot/golden churn without a clear contract. Flag change detector tests that fail on harmless refactors, formatting, record ordering, incidental wording, or private implementation details without demonstrating a behavior regression. Prefer existing fixtures or focused new fixtures for repeated setup; keep tests DRY when reasonable without making the behavior under test opaque. print() is allowed in tests.
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
**/*test*.py
📄 CodeRabbit inference engine (AGENTS.md)
The
unit_testmarker is deprecated; useunitinstead
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
src/nemo_safe_synthesizer/generation/**/*.py
⚙️ CodeRabbit configuration file
Review generation changes for retry loops, stopping conditions, invalid record handling, regex/structured output contracts, backend teardown, memory cleanup, and vLLM assumptions.
Files:
src/nemo_safe_synthesizer/generation/vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
🧠 Learnings (4)
📚 Learning: 2026-05-14T21:47:20.140Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: tests/TESTING.md:0-0
Timestamp: 2026-05-14T21:47:20.140Z
Learning: Applies to tests/**/tests/**/test_*.py : Tests using vLLM generation backend must use the `vllm` marker and each file should have a dedicated `test-smoke-gpu-*` Make target to ensure separate process execution for GPU memory isolation.
Applied to files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_backend.py
📚 Learning: 2026-05-27T22:20:37.354Z
Learnt from: kendrickb-nvidia
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 520
File: tests/generation/test_vllm_backend.py:556-587
Timestamp: 2026-05-27T22:20:37.354Z
Learning: In NVIDIA-NeMo/Safe-Synthesizer, `tests/conftest.py`’s `pytest_collection_modifyitems` hook applies pytest category markers automatically based on each test file’s path: tests under `/e2e/` get `pytest.mark.e2e`, tests under `/smoke/` get `pytest.mark.smoke`, and all other tests get `pytest.mark.unit`. Therefore, when reviewing pytest tests outside `tests/e2e/` and `tests/smoke/`, do not flag missing explicit `pytest.mark.unit` decorators on test classes/functions as an issue (the hook will add them during collection). If a new test directory/category is introduced, ensure the hook is updated so it’s categorized correctly.
Applied to files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
📚 Learning: 2026-05-14T17:03:10.291Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-05-14T17:03:10.291Z
Learning: Applies to **/*.py : Use bare `except Exception: pass` only in `__del__` methods where suppression is intentional.
Applied to files:
src/nemo_safe_synthesizer/generation/vllm_observability.py
📚 Learning: 2026-05-14T17:03:10.291Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-05-14T17:03:10.291Z
Learning: Applies to tests/**/*.py : Use absolute imports in `tests/` (e.g., `from nemo_safe_synthesizer.observability import get_logger`).
Applied to files:
tests/generation/test_vllm_observability.py
🪛 Ruff (0.15.15)
src/nemo_safe_synthesizer/generation/vllm_observability.py
[error] 423-424: try-except-continue detected, consider logging the exception
(S112)
🔇 Additional comments (2)
src/nemo_safe_synthesizer/cli/wandb_setup.py (1)
24-25: LGTM!Also applies to: 271-289
src/nemo_safe_synthesizer/generation/vllm_backend.py (1)
27-46: LGTM!Also applies to: 227-230, 307-313, 669-680, 683-803
mckornfield
left a comment
There was a problem hiding this comment.
main q about adding this observability other than my stupid questions: do we know what the effect is on runtime?
mckornfield
left a comment
There was a problem hiding this comment.
would appreciate comments addressed, but we can move this through so I can review the other one a bit more cleanly
Respond to the PR #526 review round on the vLLM observability series. Rename the shared event primitive cell -> generation: GenerationObservability, vllm.generation.complete (+ .observability.emit_failed), _emit_generation_observability, log_generation_observability, and the vllm_gen wandb prefix. The benchmark harness's grid "cell" vocabulary is intentionally left unchanged. Review fixes: - guard the warning call in _emit_generation_observability so a faulty logger handler cannot turn a swallowed observability failure into a generation failure - resolve the NvmlPeakSampler device index from CUDA_VISIBLE_DEVICES instead of hardcoding physical GPU 0 on multi-GPU hosts - log degraded paths at debug (exc_info=True) in probe_engine_runtime_config and NvmlPeakSampler.__exit__ rather than swallowing silently - move _LOADAVG_HORIZON_LABELS above the model so it precedes its first reference - drop None values from engine_runtime_config in to_wandb_payload, symmetric with the scalar-field handling - add design reference URLs to the module docstring - assert the vllm.generation.complete structured-log emission in the finalizer test Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/generation/test_vllm_backend.py (1)
498-512:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd a test for logger-handler failure suppression in the emission fallback.
This test checks probe-failure swallowing, but not the new guard where
logger.runtime.warning(...)itself raises. A regression there would reintroduce masking risk without failing this test.Smallest practical test extension
def test_emit_swallows_failures(self, base_params, mock_model_metadata, mock_schema, mock_workdir): """A failure inside emission must not propagate — observability is best-effort.""" backend = create_backend(base_params, mock_model_metadata, mock_schema, mock_workdir) backend.llm = None sampler = MagicMock() sampler.peak_gb = 1.0 - with patch( - "nemo_safe_synthesizer.generation.vllm_backend.read_vllm_runtime_metrics", - side_effect=RuntimeError("probe blew up"), - ): + with ( + patch( + "nemo_safe_synthesizer.generation.vllm_backend.read_vllm_runtime_metrics", + side_effect=RuntimeError("probe blew up"), + ), + patch( + "nemo_safe_synthesizer.generation.vllm_backend.logger.runtime.warning", + side_effect=RuntimeError("logger blew up"), + ), + ): # Must not raise. backend._emit_generation_observability(sampler, None)As per coding guidelines: "New behavior needs focused tests near the affected subsystem."
🧹 Nitpick comments (2)
src/nemo_safe_synthesizer/generation/vllm_observability.py (1)
306-307: ⚡ Quick winUse runtime-category logging for degraded observability paths.
These are runtime-internal telemetry failures; uncategorized debug logs reduce consistency for downstream routing/filtering.
Suggested patch
- logger.debug("nvml-sampler: nvmlShutdown failed", exc_info=True) + logger.runtime.debug("nvml-sampler: nvmlShutdown failed", exc_info=True) @@ - logger.debug("engine-probe: vllm_config unreachable; returning empty probe", exc_info=True) + logger.runtime.debug("engine-probe: vllm_config unreachable; returning empty probe", exc_info=True) @@ - logger.debug("engine-probe: field %r failed; skipping", spec.out_key, exc_info=True) + logger.runtime.debug("engine-probe: field %r failed; skipping", spec.out_key, exc_info=True)As per coding guidelines,
Use category loggers: .runtime for internals, .user for progress/results, .system for system events.Also applies to: 445-460
src/nemo_safe_synthesizer/cli/wandb_setup.py (1)
286-290: ⚡ Quick winLog swallowed WandB failures with traceback on the runtime logger.
This is a non-fatal observability boundary; keeping stack traces here improves debuggability without changing behavior.
Suggested patch
- except Exception as exc: # noqa: BLE001 — degraded mode - logger.warning(f"failed to log generation observability to wandb: {exc}") + except Exception: # noqa: BLE001 — degraded mode + logger.runtime.debug("failed to log generation observability to wandb", exc_info=True)As per coding guidelines,
Use except Exception: + logger.debug(..., exc_info=True) for non-fatal cleanup at teardown boundariesandUse category loggers: .runtime for internals.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 987443c6-8dbf-411b-9a2a-cd88fc4f7529
📒 Files selected for processing (5)
src/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pytests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Unit Tests (3.12)
- GitHub Check: Unit Tests (3.13)
- GitHub Check: Unit Tests (3.11)
- GitHub Check: Smoke Tests
- GitHub Check: Analyze (Python)
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{md,markdown,py}
📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)
**/*.{md,markdown,py}: Avoid decorative bold (**text**) in list items, body text, and docstrings; use structural cues (headers, list markers, colons, backticks) for emphasis instead
Use backticks for code identifiers, paths, and CLI commands in markdown and docstrings
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Place durable implementation guidance in function and class docstrings for public contracts and source comments for local invariants
Target Python 3.11–3.13 with modern syntax (X | Y,list[str],Self). Python 3.14+ is not supported
**/*.py: Use American English spelling: 'initialize' not 'initialise', 'recognize' not 'recognise', 'color' not 'colour'.
For Pydantic models inconfig/, useNSSBaseModelfor config/parameter models. Use rawBaseModelor module-specific bases (e.g.,ReportBaseModel) for data transfer objects and internal structures.
UseBaseSettingsfor env/CLI settings. PreferAliasChoiceson individual fields when you need a field to respond to both its Python name and an env var name (e.g.,validation_alias=AliasChoices("config_path", "NSS_CONFIG")).env_prefixis acceptable for simple settings classes.
UseField(description=...)as the canonical field docstring for Pydantic models. Always include it.
Use assignment style (type = Field(default=..., description="...")) as the default for Pydantic model fields. Prefer it because type checkers understanddefault,default_factory, andaliasin assignment-styleField()and synthesize correct__init__signatures.
UseAnnotatedonly when the field carries additional metadata beyondField()--ValueValidator,AutoParam,DependsOnValidator, reusable constrained type aliases, nested-type constraints, or discriminated unions.
For defaults withAnnotated: put the default as a bare assignment (= value), not insideField(default=...). Exception:default_factoryhas no bare-assignment equivalent, so use assignment-styleField(default_factory=...)even when the type isAnnotated[...].
Use@dataclass(frozen=True)preferred for immutable value objects and validators. Mutable@dataclassacceptable for builders, accumulators, and pipeline state.
Usefield(default_factory=list)for mutable defaults in dataclasses, never= [].
UseStrEnumfor ...
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
src/**/*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
src/**/*.py: Use relative imports insrc/(e.g.,from ..observability import get_logger).
Never useprint()statements in library code. Useget_logger(__name__)fromobservability.pyorclick.echo()for CLI.
Never useassertfor validation in library code. Useif/raisefor input validation.assertis fine in tests.
Every directory undersrc/that contains Python files must include an__init__.pyfile, even if empty.Write Google-style docstrings in source code for API reference auto-generation via mkdocstrings
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
⚙️ CodeRabbit configuration file
Review library code against STYLE_GUIDE.md. Focus on behavior, API contracts, error handling, resource cleanup, typing, logging, and user-facing failures. Public APIs and nontrivial functions need Google-style docstrings.
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/*
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
Include a newline at end of file, with no trailing whitespace.
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
⚙️ CodeRabbit configuration file
**/*: Review as a senior maintainer for NeMo Safe Synthesizer. Prioritize issues that can change behavior, break user workflows, weaken privacy guarantees, hide failures, make tests unreliable, or create maintenance risk. Avoid generic style commentary unless it points to a concrete project convention that automated tools will not catch.
Comment only when the finding is actionable and tied to changed code. For each finding, state the impact, the condition that triggers it, and the smallest practical fix. Prefer one precise comment over broad advice. Do not ask for refactors outside the PR scope unless the changed code creates the problem.
Review type guidance: - Potential issue: use for correctness bugs, data loss, privacy leaks,
security risks, broken public APIs, invalid config behavior, missing
validation, hidden failures, nondeterministic tests, or CI breakage.
- Refactor suggestion: use for local maintainability problems introduced
by the diff when they have clear future cost, such as duplicated setup,
unclear boundaries, over-mocking, avoidable complexity, or opaque test
helpers.- Nitpick: avoid in chill mode. Do not emit formatting, import-order,
wording, or style-only comments unless automated tools cannot catch the
issue and it affects maintainability.Severity guidance: - Critical: security/privacy leaks, data loss, training/test/holdout
contamination, or broken release/package/core pipeline execution.
- Major: incorrect generation/training/evaluation behavior, broken
CLI/SDK public API, invalid config defaults or validators, or GPU/vLLM
cleanup and process-isolation bugs likely to fail CI or production
runs.- Minor: localized bugs, missing focused tests for changed behavior, or
bad test patterns that weaken regression coverage.- Trivial: small cleanup with no behavior impact. Usually suppress in
chill mode.- Info: context only. Avoid unless it helps reviewers understand risk.
Safe-Synthesizer-specific review focus: - Data ...
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/*.{py,sh,yaml,yml,md}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
All source files (.py, .sh, .yaml, .yml, .md) must include SPDX copyright headers
Files:
src/nemo_safe_synthesizer/cli/wandb_setup.pytests/generation/test_vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pytests/generation/test_vllm_observability.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/test_*.py
📄 CodeRabbit inference engine (AGENTS.md)
Use the
unitmarker instead of the deprecatedunit_testmarker for test identification
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
tests/**/*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
tests/**/*.py: Use absolute imports intests/(e.g.,from nemo_safe_synthesizer.observability import get_logger).
Use fixtures withfixture_prefix convention for grep-ability. Add a one-line docstring describing the fixture's purpose and data.
Use function-scoped fixtures by default. Session scope only when empirically justified by test runtime.
Use bareassertas the primary assertion style;pytest.raises()withmatch=for exceptions;pytest.approx()for floating-point comparisons.
Markers are auto-assigned by path viapytest_collection_modifyitems(/e2e/->e2e,/smoke/->smoke, default ->unit). Explicit markers:@pytest.mark.slow,@pytest.mark.requires_gpu,@pytest.mark.timeout().
Usetmp_pathfixture for file operations, never write to the repo tree.
Mark CUDA-dependent tests with@pytest.mark.e2e,@pytest.mark.smoke, or@pytest.mark.requires_gpu.
Mock only external boundaries, not internal implementation details.
Ensure test isolation: no shared mutable state or execution-order dependencies between tests. Include necessary setup in the test or a fixture.
Use@pytest.mark.parametrizefor testing multiple input combinations rather than copy-pasting similar tests.
tests/**/*.py: Run mise run test to execute unit tests (excludes slow unit tests, smoke and e2e)
New features must include tests; bug fixes must include regression tests
tests/**/*.py: Define pytest markers inpytest.iniwith--strict-markersenabled. Use exactly one category marker (unit,smoke,e2e) per test. Category modifiers includeslowfor long-running tests,requires_gpufor CUDA-dependent tests,vllmfor vLLM backend tests,smollm2for SmolLM2 Hub download tests, andnoautouseto skip autouse fixtures
ForParsedResponsemocking, usevalid_records=[...],invalid_records=[...],errors=[...], andprompt_number=int. Usefixture_mock_processororfixture_mock_processor_without_valid_recordshelpers
Use `pytest.impo...
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
⚙️ CodeRabbit configuration file
Review tests against tests/TESTING.md. Check marker usage, fixture naming, tmp_path usage, determinism, and GPU/vLLM process-isolation requirements. Flag slop tests that only check that code runs, assert result is not None when stronger invariants exist, over-mock internal implementation details, patch around the bug instead of reproducing it, or add broad snapshot/golden churn without a clear contract. Flag change detector tests that fail on harmless refactors, formatting, record ordering, incidental wording, or private implementation details without demonstrating a behavior regression. Prefer existing fixtures or focused new fixtures for repeated setup; keep tests DRY when reasonable without making the behavior under test opaque. print() is allowed in tests.
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
tests/**
📄 CodeRabbit inference engine (.cursor/rules/repo-navigation.mdc)
tests/**: Mirrorsrc/directory structure intests/directory for test organization
Auto-mark tests by directory:tests/e2e/→e2e,tests/smoke/→smoke, otherwise default tounit
Files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
src/nemo_safe_synthesizer/generation/**/*.py
⚙️ CodeRabbit configuration file
Review generation changes for retry loops, stopping conditions, invalid record handling, regex/structured output contracts, backend teardown, memory cleanup, and vLLM assumptions.
Files:
src/nemo_safe_synthesizer/generation/vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
🧠 Learnings (14)
📚 Learning: 2026-06-04T16:14:09.868Z
Learnt from: binaryaaron
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 526
File: tests/generation/test_vllm_backend.py:399-509
Timestamp: 2026-06-04T16:14:09.868Z
Learning: In this repo, only apply `pytest.mark.vllm` to smoke tests under `tests/smoke/` that actually run real vLLM GPU generation and therefore require per-file process isolation (e.g., `test-smoke-gpu-*` Makefile targets). Do not apply `pytest.mark.vllm` to unit-style tests under `tests/generation/` that merely import `vllm_backend` but never instantiate a real vLLM engine and never call `.generate()` (GPU not required). Note that `tests/conftest.py` auto-marks these as `unit` via `pytest_collection_modifyitems`, and `vllm` is not among the auto-mark categories—so if a test in `tests/generation/` has `vllm`, it should be treated as a review issue unless it meets the real GPU generation criteria above.
Applied to files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
📚 Learning: 2026-06-03T23:08:50.142Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: tests/TESTING.md:0-0
Timestamp: 2026-06-03T23:08:50.142Z
Learning: Applies to tests/**/*.py : For vLLM tests that call `.generate()`, mark with `pytest.mark.vllm`, use per-file process isolation (`-n 0`), and create dedicated `test:smoke:gpu:*` mise tasks. vLLM pre-allocates all GPU memory and never releases it within a process, causing OOM in later tests if not isolated
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-03T23:08:50.142Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: tests/TESTING.md:0-0
Timestamp: 2026-06-03T23:08:50.142Z
Learning: Applies to tests/**/*.py : Define pytest markers in `pytest.ini` with `--strict-markers` enabled. Use exactly one category marker (`unit`, `smoke`, `e2e`) per test. Category modifiers include `slow` for long-running tests, `requires_gpu` for CUDA-dependent tests, `vllm` for vLLM backend tests, `smollm2` for SmolLM2 Hub download tests, and `noautouse` to skip autouse fixtures
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-05-27T22:20:37.354Z
Learnt from: kendrickb-nvidia
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 520
File: tests/generation/test_vllm_backend.py:556-587
Timestamp: 2026-05-27T22:20:37.354Z
Learning: In NVIDIA-NeMo/Safe-Synthesizer, `tests/conftest.py`’s `pytest_collection_modifyitems` hook applies pytest category markers automatically based on each test file’s path: tests under `/e2e/` get `pytest.mark.e2e`, tests under `/smoke/` get `pytest.mark.smoke`, and all other tests get `pytest.mark.unit`. Therefore, when reviewing pytest tests outside `tests/e2e/` and `tests/smoke/`, do not flag missing explicit `pytest.mark.unit` decorators on test classes/functions as an issue (the hook will add them during collection). If a new test directory/category is introduced, ensure the hook is updated so it’s categorized correctly.
Applied to files:
tests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to tests/**/*.py : Mark CUDA-dependent tests with `pytest.mark.e2e`, `pytest.mark.smoke`, or `pytest.mark.requires_gpu`.
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to tests/**/*.py : Markers are auto-assigned by path via `pytest_collection_modifyitems` (`/e2e/` -> `e2e`, `/smoke/` -> `smoke`, default -> `unit`). Explicit markers: `pytest.mark.slow`, `pytest.mark.requires_gpu`, `pytest.mark.timeout()`.
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-03T23:06:56.798Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-06-03T23:06:56.798Z
Learning: Applies to **/test_*.py : Use the `unit` marker instead of the deprecated `unit_test` marker for test identification
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to tests/**/*.py : Use absolute imports in `tests/` (e.g., `from nemo_safe_synthesizer.observability import get_logger`).
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-03T23:08:50.142Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: tests/TESTING.md:0-0
Timestamp: 2026-06-03T23:08:50.142Z
Learning: Read and understand key conftest.py files in order: `tests/conftest.py` (auto-marking, test helpers), `pytest.ini` (markers, asyncio, timeout), `tests/evaluation/conftest.py` (Faker-based data generation), and `tests/generation/conftest.py` (JSONL/schema fixtures) before writing tests
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-03T23:09:02.641Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: .cursor/rules/repo-navigation.mdc:0-0
Timestamp: 2026-06-03T23:09:02.641Z
Learning: Applies to tests/** : Auto-mark tests by directory: `tests/e2e/` → `e2e`, `tests/smoke/` → `smoke`, otherwise default to `unit`
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-03T23:09:02.641Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: .cursor/rules/repo-navigation.mdc:0-0
Timestamp: 2026-06-03T23:09:02.641Z
Learning: Applies to pytest.ini : Define test markers in `pytest.ini`: `unit`, `slow`, `smoke`, `e2e`, `requires_gpu`, `noautouse`
Applied to files:
tests/generation/test_vllm_backend.py
📚 Learning: 2026-06-04T16:14:09.868Z
Learnt from: binaryaaron
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 526
File: tests/generation/test_vllm_backend.py:399-509
Timestamp: 2026-06-04T16:14:09.868Z
Learning: In NVIDIA-NeMo/Safe-Synthesizer, the `pytest.mark.vllm` marker is scoped exclusively to tests under `tests/smoke/` that invoke real vLLM GPU generation and need per-file process isolation (via `test-smoke-gpu-*` Makefile targets). Mocked unit tests in `tests/generation/` that import from `vllm_backend` but never instantiate a real engine (no GPU required, no `.generate()` call) should NOT carry the `vllm` marker. `tests/conftest.py` auto-marks these files as `unit` via `pytest_collection_modifyitems`, and `vllm` is not part of the auto-mark categories. Sibling files like `test_vllm_shutdown.py` and `test_timeseries_backend.py` follow this convention.
Applied to files:
src/nemo_safe_synthesizer/generation/vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to **/*.py : Add `from __future__ import annotations` to every module. This makes all annotations strings consistently, preventing accidental runtime evaluation and aligning with type-checker expectations.
Applied to files:
src/nemo_safe_synthesizer/generation/vllm_backend.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to **/*.py : Use `except Exception:` + `logger.debug(..., exc_info=True)` for non-fatal cleanup at teardown boundaries. Bare `except Exception: pass` only in `__del__` methods.
Applied to files:
src/nemo_safe_synthesizer/generation/vllm_observability.py
🔇 Additional comments (2)
src/nemo_safe_synthesizer/generation/vllm_backend.py (1)
8-680: LGTM!Also applies to: 766-798
tests/generation/test_vllm_backend.py (1)
22-22: LGTM!Also applies to: 399-497
Adds ``src/nemo_safe_synthesizer/generation/vllm_observability.py`` — a
self-contained module housing four reusable measurement primitives plus
the schema for the ``vllm.cell.complete`` structured event that
``VllmBackend.generate()`` will emit (next commit).
This is a **pure-additive** commit: no existing code paths call the new
module yet. Subsequent commits wire production (``VllmBackend.generate``)
and benchmark (``tools/vllm_benchmark.py``) consumers.
## Primitives
- ``NvmlPeakSampler`` — context-manager + daemon thread polling
``pynvml.nvmlDeviceGetMemoryInfo`` at 250 ms cadence, tracking peak
device-wide VRAM. Reads at the driver layer so it sees vLLM
worker-subprocess allocations regardless of which process holds the
torch handle — sidesteps the ``VLLM_ENABLE_V1_MULTIPROCESSING=1``
blind spot where ``torch.cuda.max_memory_allocated()`` in the parent
process reads 0.
- ``read_loadavg() -> tuple[float, float, float] | None`` — host
``/proc/loadavg`` snapshot (1m, 5m, 15m). Linux-only; ``None`` on
read failure.
- ``probe_engine_runtime_config(llm) -> dict[str, Any]`` — best-effort
introspection of ``llm.llm_engine.vllm_config`` for the
scheduler/cache/speculative settings. Spans vLLM v0/v1 attribute
naming (``llm_engine`` vs ``engine``). Empty dict on any failure.
- ``read_vllm_runtime_metrics(llm) -> dict[str, float | None]`` —
one-shot snapshot of ``llm.get_metrics()`` for
``vllm:kv_cache_usage_perc`` (gauge), ``prefix_cache_hit_rate``
(derived from ``hits/queries`` counters), and ``spec_accept_rate``
(derived from ``spec_decode_num_{accepted,draft}_tokens`` counters).
``None`` for any metric the engine didn't expose — distinguishes
"not measured" from "measured zero".
Plus ``flag_engagement_mismatches(intended, actual) -> list[str]`` — a
helper that cross-checks an intended-config dict against a probed
runtime-config dict, used by the caller to set the
``flag_did_not_engage`` bit. Dict-vs-dict shape (not pydantic) so it
works whether the caller has a typed config model or just raw vLLM
kwargs.
## Schema
``CellObservability`` pydantic model defines the
``vllm.cell.complete`` event payload: peak_vram_gb, kv_cache_usage_perc,
prefix_cache_hit_rate, spec_accept_rate, loadavg_pre/post,
engine_runtime_config (dict), flag_did_not_engage (bool). Every
measurement field is optional with a sensible default — producers
populate what they can capture and consumers (logs, wandb, benchmark
aggregator) silently drop ``None`` values.
``model_config = ConfigDict(extra='forbid')`` so producers are forced
to update the schema when they add new fields, preventing silent drift.
## Dependencies
Only ``pydantic``, ``pynvml`` (already in the project's ``cu129`` extra
via ``nvidia-ml-py``), and the existing ``observability.get_logger``.
No PR-1-introduced modules (``vllm_engine_factory``, ``vllm_trace``,
etc.) are imported — this module stands alone on top of ``main``.
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
…lmBackend.generate
Wires the ``vllm_observability`` primitives into ``VllmBackend`` so
every production generation invocation emits a ``vllm.cell.complete``
structured event carrying peak device VRAM, host loadavg pre/post,
vLLM's kv_cache_usage_perc / prefix_cache_hit_rate / spec_accept_rate,
and the engine's effective runtime config.
## What changes
- ``VllmBackend.initialize`` (after ``self.llm = vLLM(...)``) caches
``self._engine_runtime_config = probe_engine_runtime_config(self.llm)``.
Probed once at engine-init time; consumed by every subsequent
``generate()`` call.
- ``VllmBackend.generate``:
- Captures ``loadavg_pre`` at the top.
- Enters an :class:`NvmlPeakSampler` context wrapping the entire body
via manual ``__enter__`` / ``__exit__`` calls inside a try/finally,
so the sampler thread always shuts down and the observability event
always emits — even when generation raises mid-batch.
- At end of body (finally branch): reads
``read_vllm_runtime_metrics(self.llm)``, captures ``loadavg_post``,
builds a ``CellObservability`` event, emits via
``logger.runtime.info("vllm.cell.complete", extra={"ctx": ...})``.
## Degraded-mode behavior
Every primitive returns ``None`` / empty-dict when its data source is
unavailable, and the emission still fires. Specifically:
- No GPU / pynvml missing → ``peak_vram_gb=None``.
- Non-Linux → ``loadavg_pre/post=None``.
- vLLM doesn't expose a given metric → that field is ``None``.
- ``llm.get_metrics()`` raises → log a warning and emit with all metric
fields ``None``.
- Engine config probe fails → ``engine_runtime_config={}``.
The event itself is never optional — every ``generate()`` call emits
exactly one ``vllm.cell.complete`` to the structured-log surface that
PR-1's telemetry work established (``logger.runtime.*`` aliases on the
``CategoryLogger`` from ``observability.py``).
## What ``flag_did_not_engage`` does (and doesn't) do here
The bit is set to ``False`` unconditionally in production because
``VllmBackend`` doesn't carry an intended-overrides dict to compare
against the probed runtime config — production passes whatever
``SafeSynthesizerParameters`` specifies, and the engine accepts or
defaults each field independently. Benchmark callers (next PR's
harness) carry their own intended-overrides dict per candidate and
compute the mismatch themselves; they construct the ``CellObservability``
event with the bit populated correctly. The schema field is here so
benchmark consumers don't have to extend it.
## Wandb wiring is a separate commit
This commit only emits the structured-log event. The next commit adds
the wandb-side emission via ``wandb_setup.py`` so production runs
with ``WANDB_MODE!=disabled`` automatically log the same payload to
the active wandb run.
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
``generate()`` reads ``self._engine_runtime_config`` when building
the ``CellObservability`` event, but the attribute was only set
inside ``initialize()`` (right after ``self.llm = vLLM(...)``).
Existing test_vllm_backend.py tests construct VllmBackend without
calling ``initialize()`` (they mock the engine separately), so
``generate()`` raised ``AttributeError`` on those tests.
Fix: declare ``self._engine_runtime_config: dict[str, Any] = {}`` in
``__init__`` so the attribute always exists. ``initialize()``
overwrites it with the actual probe result post-engine-build; tests
that skip ``initialize()`` see an empty dict, which flows through
to ``CellObservability.engine_runtime_config`` as the documented
'probe unavailable' value.
Closes 5 failures in test_vllm_backend.py:
- test_native_eos_stopping_for_grouped_processor
- test_no_stop_kwargs_for_tabular_processor
- test_large_context_grouped_generation_has_eos_stop
- test_uses_generation_max_tokens_for_with_cached_prompt_len
- test_passes_cached_prompt_token_count_when_engine_initialized
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
…ntracts Review-driven cleanup of the vllm.cell.complete observability path: - generate(): wrap NvmlPeakSampler with `with`, extract _run_generation and _emit_cell_observability so generate() reads as a thin observability bracket; drop the manual __enter__/__exit__ calls. - read_vllm_runtime_metrics: widen the degraded-mode guard over the whole body and return a VllmRuntimeMetrics TypedDict (stable keys, float|None values); remove the caller's redundant try/except and duplicated literal. - probe_engine_runtime_config: replace nested if-ladders with a declarative _ProbeField table plus a single loop; degrade per-field instead of whole-probe; derive ENGINE_CONFIG_CHECKED_FIELDS from the table so the two cannot drift. - Narrow NvmlPeakSampler guards to pynvml.NVMLError; type the probe input as object and log_cell_observability's event as CellObservability. - Add integration tests for the generate() emission path and engine-config caching, plus probe-table and checked-fields coverage. Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Collapse the per-metric if/elif dispatch into a set-driven collector (_collect_raw_metrics) feeding a functional VllmRuntimeMetrics construction, replacing the mutable out-dict with named, intention- revealing helpers: - _COLLECTED_METRICS: the raw counters pulled from llm.get_metrics() - _safe_ratio: factors out the duplicated numerator/denominator guard (prefix-cache hit rate, spec-accept rate) - _empty_runtime_metrics: the single degraded-mode snapshot Narrows the exception surface to wrap only the engine call + numeric coercion; the ratio derivation is pure dict arithmetic and stays outside the guard. Behavior and the stable three-key contract are unchanged (covered by existing test_vllm_observability tests). Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Respond to the PR #526 review round on the vLLM observability series. Rename the shared event primitive cell -> generation: GenerationObservability, vllm.generation.complete (+ .observability.emit_failed), _emit_generation_observability, log_generation_observability, and the vllm_gen wandb prefix. The benchmark harness's grid "cell" vocabulary is intentionally left unchanged. Review fixes: - guard the warning call in _emit_generation_observability so a faulty logger handler cannot turn a swallowed observability failure into a generation failure - resolve the NvmlPeakSampler device index from CUDA_VISIBLE_DEVICES instead of hardcoding physical GPU 0 on multi-GPU hosts - log degraded paths at debug (exc_info=True) in probe_engine_runtime_config and NvmlPeakSampler.__exit__ rather than swallowing silently - move _LOADAVG_HORIZON_LABELS above the model so it precedes its first reference - drop None values from engine_runtime_config in to_wandb_payload, symmetric with the scalar-field handling - add design reference URLs to the module docstring - assert the vllm.generation.complete structured-log emission in the finalizer test Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
5fc70a0 to
23c69c5
Compare
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 5d01eadb-1d2e-4044-9003-13a7b1a698ee
📒 Files selected for processing (7)
src/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_backend.pysrc/nemo_safe_synthesizer/generation/vllm_observability.pysrc/nemo_safe_synthesizer/observability.pytests/generation/test_vllm_backend.pytests/generation/test_vllm_observability.pytests/test_observability.py
🚧 Files skipped from review as they are similar to previous changes (3)
- src/nemo_safe_synthesizer/generation/vllm_backend.py
- tests/generation/test_vllm_observability.py
- tests/generation/test_vllm_backend.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Unit Tests (3.12)
- GitHub Check: Unit Tests (3.11)
- GitHub Check: Unit Tests (3.13)
- GitHub Check: Smoke Tests
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{md,markdown,py}
📄 CodeRabbit inference engine (.cursor/rules/agent-markdown-style.mdc)
**/*.{md,markdown,py}: Avoid decorative bold (**text**) in list items, body text, and docstrings; use structural cues (headers, list markers, colons, backticks) for emphasis instead
Use backticks for code identifiers, paths, and CLI commands in markdown and docstrings
Files:
src/nemo_safe_synthesizer/observability.pytests/test_observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Place durable implementation guidance in function and class docstrings for public contracts and source comments for local invariants
Target Python 3.11–3.13 with modern syntax (X | Y,list[str],Self). Python 3.14+ is not supported
**/*.py: Use American English spelling: 'initialize' not 'initialise', 'recognize' not 'recognise', 'color' not 'colour'.
For Pydantic models inconfig/, useNSSBaseModelfor config/parameter models. Use rawBaseModelor module-specific bases (e.g.,ReportBaseModel) for data transfer objects and internal structures.
UseBaseSettingsfor env/CLI settings. PreferAliasChoiceson individual fields when you need a field to respond to both its Python name and an env var name (e.g.,validation_alias=AliasChoices("config_path", "NSS_CONFIG")).env_prefixis acceptable for simple settings classes.
UseField(description=...)as the canonical field docstring for Pydantic models. Always include it.
Use assignment style (type = Field(default=..., description="...")) as the default for Pydantic model fields. Prefer it because type checkers understanddefault,default_factory, andaliasin assignment-styleField()and synthesize correct__init__signatures.
UseAnnotatedonly when the field carries additional metadata beyondField()--ValueValidator,AutoParam,DependsOnValidator, reusable constrained type aliases, nested-type constraints, or discriminated unions.
For defaults withAnnotated: put the default as a bare assignment (= value), not insideField(default=...). Exception:default_factoryhas no bare-assignment equivalent, so use assignment-styleField(default_factory=...)even when the type isAnnotated[...].
Use@dataclass(frozen=True)preferred for immutable value objects and validators. Mutable@dataclassacceptable for builders, accumulators, and pipeline state.
Usefield(default_factory=list)for mutable defaults in dataclasses, never= [].
UseStrEnumfor ...
Files:
src/nemo_safe_synthesizer/observability.pytests/test_observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
src/**/*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
src/**/*.py: Use relative imports insrc/(e.g.,from ..observability import get_logger).
Never useprint()statements in library code. Useget_logger(__name__)fromobservability.pyorclick.echo()for CLI.
Never useassertfor validation in library code. Useif/raisefor input validation.assertis fine in tests.
Every directory undersrc/that contains Python files must include an__init__.pyfile, even if empty.
Files:
src/nemo_safe_synthesizer/observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
⚙️ CodeRabbit configuration file
Review library code against STYLE_GUIDE.md. Focus on behavior, API contracts, error handling, resource cleanup, typing, logging, and user-facing failures. Public APIs and nontrivial functions need Google-style docstrings.
Files:
src/nemo_safe_synthesizer/observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/*
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
Include a newline at end of file, with no trailing whitespace.
Files:
src/nemo_safe_synthesizer/observability.pytests/test_observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
⚙️ CodeRabbit configuration file
**/*: Review as a senior maintainer for NeMo Safe Synthesizer. Prioritize issues that can change behavior, break user workflows, weaken privacy guarantees, hide failures, make tests unreliable, or create maintenance risk. Avoid generic style commentary unless it points to a concrete project convention that automated tools will not catch.
Comment only when the finding is actionable and tied to changed code. For each finding, state the impact, the condition that triggers it, and the smallest practical fix. Prefer one precise comment over broad advice. Do not ask for refactors outside the PR scope unless the changed code creates the problem.
Review type guidance: - Potential issue: use for correctness bugs, data loss, privacy leaks,
security risks, broken public APIs, invalid config behavior, missing
validation, hidden failures, nondeterministic tests, or CI breakage.
- Refactor suggestion: use for local maintainability problems introduced
by the diff when they have clear future cost, such as duplicated setup,
unclear boundaries, over-mocking, avoidable complexity, or opaque test
helpers.- Nitpick: avoid in chill mode. Do not emit formatting, import-order,
wording, or style-only comments unless automated tools cannot catch the
issue and it affects maintainability.Severity guidance: - Critical: security/privacy leaks, data loss, training/test/holdout
contamination, or broken release/package/core pipeline execution.
- Major: incorrect generation/training/evaluation behavior, broken
CLI/SDK public API, invalid config defaults or validators, or GPU/vLLM
cleanup and process-isolation bugs likely to fail CI or production
runs.- Minor: localized bugs, missing focused tests for changed behavior, or
bad test patterns that weaken regression coverage.- Trivial: small cleanup with no behavior impact. Usually suppress in
chill mode.- Info: context only. Avoid unless it helps reviewers understand risk.
Safe-Synthesizer-specific review focus: - Data ...
Files:
src/nemo_safe_synthesizer/observability.pytests/test_observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/*.{py,sh,yaml,yml,md}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
All source files (.py, .sh, .yaml, .yml, .md) require SPDX copyright headers. Use
mise run formatto add them automatically
Files:
src/nemo_safe_synthesizer/observability.pytests/test_observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
src/nemo_safe_synthesizer/**/*.py
📄 CodeRabbit inference engine (CONTRIBUTING.md)
All Python source code must follow Google-style docstring format for auto-generation in API reference
Files:
src/nemo_safe_synthesizer/observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
{src/**/*,test/**/*}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
All src and test files are owned by
@NVIDIA-NeMo/safe-synthesizer-reviewersand require their code review
Files:
src/nemo_safe_synthesizer/observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**
⚙️ CodeRabbit configuration file
**:AGENTS.md
Guide for AI agents (Cursor, Windsurf, Claude Code, etc.) working in the Safe-Synthesizer repo.
This project loads local developer preferences from
@AGENTS.local.md. You MUST read this file if it exists and give its instructions top priority.Skills
Repo-specific skills live in
.agents/skills/; see.agents/README.mdfor the catalog. Read a skill when the task matches its scope instead of copying workflow details into this file.Durable implementation guidance belongs with the code it describes: function and class docstrings for public contracts and source comments for local invariants. Test-suite guidance belongs in
tests/TESTING.md.Repo Conventions
See STYLE_GUIDE.md for detailed code style conventions (Python, markdown, Dockerfiles, shell scripts, testing, config files, docstrings).
Use
uvfor everything -- neverpipor rawpython. Python 3.11–3.13 with modern syntax (X | Y,list[str],Self). Python 3.14+ is not supported.Common commands:
mise run test(unit tests),mise run format(auto-fix formatting + lint + copyright),mise run check(read-only local quality checks),mise run validate(pre-PR quality, lock, and CI unit checks),mise run typecheck(ty only). Always use mise tasks or the wrapper scripts intools/instead of runningruffortydirectly. Useuv runfor Python execution. When in doubt, inspectmise tasksandpytest --markers.The canonical
uv synccommand for a full GPU/dev environment is:uv sync --frozen --extra cu129 --extra engine --group devBare
uv sync --frozen(without extras) installs an incomplete environment --ty, import checks, and GPU tests will fail.Feature branches off
main. Branch names often include an issue number prefix (e.g.,<author>/123-short-name).Do ...
Files:
src/nemo_safe_synthesizer/observability.pytests/test_observability.pysrc/nemo_safe_synthesizer/cli/wandb_setup.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
**/test_*.py
📄 CodeRabbit inference engine (AGENTS.md)
Use the
unitmarker instead of the deprecatedunit_testmarker for test identification
Files:
tests/test_observability.py
tests/**/*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
tests/**/*.py: Use absolute imports intests/(e.g.,from nemo_safe_synthesizer.observability import get_logger).
Use fixtures withfixture_prefix convention for grep-ability. Add a one-line docstring describing the fixture's purpose and data.
Use function-scoped fixtures by default. Session scope only when empirically justified by test runtime.
Use bareassertas the primary assertion style;pytest.raises()withmatch=for exceptions;pytest.approx()for floating-point comparisons.
Markers are auto-assigned by path viapytest_collection_modifyitems(/e2e/->e2e,/smoke/->smoke, default ->unit). Explicit markers:@pytest.mark.slow,@pytest.mark.requires_gpu,@pytest.mark.timeout().
Usetmp_pathfixture for file operations, never write to the repo tree.
Mark CUDA-dependent tests with@pytest.mark.e2e,@pytest.mark.smoke, or@pytest.mark.requires_gpu.
Mock only external boundaries, not internal implementation details.
Ensure test isolation: no shared mutable state or execution-order dependencies between tests. Include necessary setup in the test or a fixture.
Use@pytest.mark.parametrizefor testing multiple input combinations rather than copy-pasting similar tests.
tests/**/*.py: Define pytest markers inpytest.iniwith--strict-markersenabled. Use exactly one category marker (unit,smoke,e2e) per test. Category modifiers includeslowfor long-running tests,requires_gpufor CUDA-dependent tests,vllmfor vLLM backend tests,smollm2for SmolLM2 Hub download tests, andnoautouseto skip autouse fixtures
ForParsedResponsemocking, usevalid_records=[...],invalid_records=[...],errors=[...], andprompt_number=int. Usefixture_mock_processororfixture_mock_processor_without_valid_recordshelpers
Usepytest.importorskipto gate tests on optional dependencies that require specific extras (e.g.,sentence_transformersandvllmrequirecu129extra)
For vLLM tests that call `.gen...
Files:
tests/test_observability.py
⚙️ CodeRabbit configuration file
Review tests against tests/TESTING.md. Check marker usage, fixture naming, tmp_path usage, determinism, and GPU/vLLM process-isolation requirements. Flag slop tests that only check that code runs, assert result is not None when stronger invariants exist, over-mock internal implementation details, patch around the bug instead of reproducing it, or add broad snapshot/golden churn without a clear contract. Flag change detector tests that fail on harmless refactors, formatting, record ordering, incidental wording, or private implementation details without demonstrating a behavior regression. Prefer existing fixtures or focused new fixtures for repeated setup; keep tests DRY when reasonable without making the behavior under test opaque. print() is allowed in tests.
Files:
tests/test_observability.py
tests/test_*.py
📄 CodeRabbit inference engine (STYLE_GUIDE.md)
File naming:
test_*.py; class naming:Test*; function naming:test_<module>_<expected_behavior>.
Files:
tests/test_observability.py
tests/**
📄 CodeRabbit inference engine (.cursor/rules/repo-navigation.mdc)
tests/**: Mirrorsrc/directory structure intests/directory for test organization
Auto-mark tests by directory:tests/e2e/→e2e,tests/smoke/→smoke, otherwise default tounit
Files:
tests/test_observability.py
⚙️ CodeRabbit configuration file
tests/**:Testing Guide
Comprehensive testing reference for Safe-Synthesizer developers. Covers commands, markers, test data, fixtures, and gotchas.
Read First
tests/conftest.py-- auto-marking,load_test_dataset/load_test_dataframe,fixture_mock_processorpatternpytest.ini-- markers, asyncio, timeouttests/evaluation/conftest.py-- most complex: Faker-basedmake_df, nullable dtype conversiontests/generation/conftest.py-- JSONL/schema fixtures,fixture_valid_iris_dataset_jsonl_and_schemaRunning Tests
All mise test tasks, grouped by scope:
mise run test # Unit (excludes slow, e2e, and smoke) mise run test:unit-slow # Unit tests including slow (excludes e2e and smoke) mise run test:smoke # CPU smoke tests (~few min, no GPU required) mise run test:smoke:gpu # All staged GPU smoke tests (requires CUDA) mise run test:smoke:gpu:train-only mise run test:smoke:gpu:generation mise run test:smoke:gpu:resume mise run test:smoke:gpu:structured-generation mise run test:smoke:gpu:timeseries mise run test:smoke:gpu:smollm2 mise run test:e2e # All e2e (requires CUDA) -- runs default + dp mise run test:e2e:default # e2e default (no-DP) tests only mise run test:e2e:dp # e2e DP tests only mise run test:ci # CI unit tests with coverage (excludes slow, e2e, gpu, smoke) mise run test:ci-slow # CI slow tests with coverage mise run test:ci-container # CI tests in a Linux container (Docker/Podman)Run a single test:
uv run --frozen pytest tests/path/test_file.py::test_name -vvs -n0Test runner:
uv run --frozen pytest -n auto --dist loadscope -vv...
Files:
tests/test_observability.py
src/nemo_safe_synthesizer/generation/**/*.py
⚙️ CodeRabbit configuration file
Review generation changes for retry loops, stopping conditions, invalid record handling, regex/structured output contracts, backend teardown, memory cleanup, and vLLM assumptions.
Files:
src/nemo_safe_synthesizer/generation/vllm_observability.py
🧠 Learnings (6)
📚 Learning: 2026-06-04T16:14:16.006Z
Learnt from: binaryaaron
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 526
File: tests/generation/test_vllm_backend.py:399-509
Timestamp: 2026-06-04T16:14:16.006Z
Learning: In NVIDIA-NeMo/Safe-Synthesizer, the `pytest.mark.vllm` marker is scoped exclusively to tests under `tests/smoke/` that invoke real vLLM GPU generation and need per-file process isolation (via `test-smoke-gpu-*` Makefile targets). Mocked unit tests in `tests/generation/` that import from `vllm_backend` but never instantiate a real engine (no GPU required, no `.generate()` call) should NOT carry the `vllm` marker. `tests/conftest.py` auto-marks these files as `unit` via `pytest_collection_modifyitems`, and `vllm` is not part of the auto-mark categories. Sibling files like `test_vllm_shutdown.py` and `test_timeseries_backend.py` follow this convention.
Applied to files:
tests/test_observability.pysrc/nemo_safe_synthesizer/generation/vllm_observability.py
📚 Learning: 2026-06-03T23:08:50.142Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: tests/TESTING.md:0-0
Timestamp: 2026-06-03T23:08:50.142Z
Learning: Applies to tests/**/*.py : For vLLM tests that call `.generate()`, mark with `pytest.mark.vllm`, use per-file process isolation (`-n 0`), and create dedicated `test:smoke:gpu:*` mise tasks. vLLM pre-allocates all GPU memory and never releases it within a process, causing OOM in later tests if not isolated
Applied to files:
tests/test_observability.py
📚 Learning: 2026-05-27T22:20:37.354Z
Learnt from: kendrickb-nvidia
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 520
File: tests/generation/test_vllm_backend.py:556-587
Timestamp: 2026-05-27T22:20:37.354Z
Learning: In NVIDIA-NeMo/Safe-Synthesizer, `tests/conftest.py`’s `pytest_collection_modifyitems` hook applies pytest category markers automatically based on each test file’s path: tests under `/e2e/` get `pytest.mark.e2e`, tests under `/smoke/` get `pytest.mark.smoke`, and all other tests get `pytest.mark.unit`. Therefore, when reviewing pytest tests outside `tests/e2e/` and `tests/smoke/`, do not flag missing explicit `pytest.mark.unit` decorators on test classes/functions as an issue (the hook will add them during collection). If a new test directory/category is introduced, ensure the hook is updated so it’s categorized correctly.
Applied to files:
tests/test_observability.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to **/*.py : Use `Protocol` for structural subtyping when you need duck-typing boundaries.
Applied to files:
src/nemo_safe_synthesizer/cli/wandb_setup.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to **/*.py : Use `except Exception:` + `logger.debug(..., exc_info=True)` for non-fatal cleanup at teardown boundaries. Bare `except Exception: pass` only in `__del__` methods.
Applied to files:
src/nemo_safe_synthesizer/generation/vllm_observability.py
📚 Learning: 2026-06-03T23:08:14.151Z
Learnt from: CR
Repo: NVIDIA-NeMo/Safe-Synthesizer PR: 0
File: STYLE_GUIDE.md:0-0
Timestamp: 2026-06-03T23:08:14.151Z
Learning: Applies to **/*.py : Avoid defensive `try/except Exception` on trusted internal paths where exceptions shouldn't occur.
Applied to files:
src/nemo_safe_synthesizer/generation/vllm_observability.py
🔇 Additional comments (14)
src/nemo_safe_synthesizer/generation/vllm_observability.py (5)
1-91: LGTM!
98-239: LGTM!
246-349: LGTM!
352-379: LGTM!
425-488: LGTM!src/nemo_safe_synthesizer/cli/wandb_setup.py (2)
14-14: LGTM!
268-301: LGTM!src/nemo_safe_synthesizer/observability.py (2)
84-86: LGTM!
1012-1145: LGTM!tests/test_observability.py (5)
1-13: LGTM!
15-21: LGTM!
24-43: LGTM!
53-77: LGTM!
46-50: EnsureNvmlPeakSampler.__enter__handles the same failure mode your test injects forpynvml.
Withmonkeypatch.setitem(sys.modules, "pynvml", None),import pynvmlwill succeed and the firstpynvml.*access (e.g.,pynvml.nvmlInit()) will raiseAttributeError; ifNvmlPeakSampler.__enter__only catchesImportError, the test won’t exercise the intended “no pynvml” fallback—either catchAttributeErrorinNvmlPeakSampler.__enter__or mock the import to raiseImportError(e.g., remove the module fromsys.modulessoimport pynvmlfails).
- wandb_setup: use concrete "" default for WandbLoggable.to_wandb_payload prefix - vllm_observability: log read_vllm_runtime_metrics failure with exc_info=True for consistency with sibling degraded paths Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Replace the dot-namespaced generation-observability event names
("vllm.generation.complete", "vllm.generation.observability.emit_failed")
with plain human-readable log messages that match the repo's existing
logger.runtime + extra={"ctx": ...} convention; the dotted form read like
a module path. Give WandbLoggable.to_wandb_payload a docstring body instead
of "..." so it is no longer flagged as a no-effect statement while staying
idiomatic for a Protocol.
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
Summary
Adds a self-contained
vllm_observabilitymodule that lifts four measurement primitives + the structured event schema out of any specific consumer (productionVllmBackendor benchmark harness) and into a shared, well-tested surface. Wires the primitives intoVllmBackend.generate()so every production generation invocation now emits avllm.cell.completestructured event carrying peak device VRAM, host loadavg pre/post, vLLM's KV-cache fraction / prefix-cache hit rate / speculative-decoding acceptance, and the engine's effective runtime config.The primitives are deliberately generic — they're observability infrastructure, not benchmark-specific. The companion PR (
binaryaaron/vllm-benchmark-harness-work) imports them and composes the schema rather than re-defining its fields, which fixes the architectural smell where observability fields had been locked insideCandidateMetrics.What this changes for production
Every
VllmBackend.generate()call now writes:logger.runtime.info("vllm.cell.complete", extra={"ctx": event.model_dump()})wandb.log(event.to_wandb_payload())— keys namespaced undervllm_cell/Operators get flag-engagement drift detection (cross-checks intended config against probed
vllm_config), KV-cache pressure visibility, out-of-process peak VRAM via NVML (sidesteps theVLLM_ENABLE_V1_MULTIPROCESSING=1blind spot wheretorch.cuda.max_memory_allocated()in the harness reads 0), and host load bracketing per cell.Primitives + schema
NvmlPeakSamplerpynvmlat 250ms cadenceread_loadavg()/proc/loadavgsnapshot as(1m, 5m, 15m)tupleprobe_engine_runtime_config(llm)vllm_config.{scheduler,cache,speculative}_configread_vllm_runtime_metrics(llm)LLM.get_metrics()reader →{kv_cache_usage_perc, prefix_cache_hit_rate, spec_accept_rate}flag_engagement_mismatches(intended, actual)CellObservabilitypydantic modelextra="forbid"so producers must update on schema changelog_cell_observability(event)Every primitive is degraded-mode by design: missing pynvml / non-Linux / unavailable metric / failed call →
Noneor empty dict, never raises.Architectural choices worth flagging
phase=GENERATE+job_type="benchmark"instead of a newWandbPhase.BENCHMARK— a benchmark cell is structurally aGENERATEphase invocation that's measured rather than consumed;job_typeis the right discriminator. Keeps production-generate and benchmark cells in the same wandb workspace section for cross-comparison.observability: CellObservabilityrather than re-declaring fields. Adding a new measurement primitive in the future requires touching this module only.WANDB_MODE=disabled/wandb.initraises /wandb.lograises → warning, generation continues. Production reliability isn't gated on observability working.pynvml+/proc/loadavg+ vLLM's publicllm.get_metrics()/llm.llm_engine.vllm_configsurface. No imports fromvllm_engine_factory/vllm_traceetc. This PR lands independently of PR-1's review timing.Test coverage
tests/generation/test_vllm_observability.py— 28 contract tests, ~7s wall:extra='forbid'enforced.to_wandb_payload: namespacing, None-dropping, tuple unpacking, dict flattening.flag_engagement_mismatches: parametrized matrix (clean match / disagreement / unset-intended / missing-actual / both-empty).read_loadavg: shape on Linux, None on read failure.probe_engine_runtime_config: empty dict on any failure (parametrized), extracts known fields when vllm_config is real-shaped.read_vllm_runtime_metrics: stable dict keys, correct derivation, exception → degraded mode, zero-denominator → None.NvmlPeakSampler: context-manager protocol,peak_gbtypedfloat | None, pynvml ImportError → None.log_cell_observability: no-op when no active run, callswandb.logwith the flattened payload when active, swallowswandb.logexceptions.Tests deliberately focus on contracts, NOT implementation details (field counts, log wording, etc.) — that's documented inline so reviewers know the scope choice was intentional.
Test plan
tests/generation/test_vllm_observability.py) pass — 28 testssafe-synthesizer run generate ...against a small dataset, confirm avllm.cell.completeevent lands in the structured logWANDB_MODE=online, confirm the wandb run page shows thevllm_cell/*metricspeak_vram_gb: Noneand no exceptionWhat's NOT in this PR
compilation_config+kv_cache_metricsfields on a futureBenchmarkEngineConfig— those are benchmark-side knobs, deferred to the companion PR.generate()call; future improvement via the existingNvmlPeakSamplerthread.VllmBackend.generate()end-to-end integration test — requires actual vLLM spin-up; covered by production usage + the companion PR's benchmark integration.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests