Skip to content

Add sidecar GPU/CPU memory+utilization monitor for HF PTQ#2000

Open
Fridah-nv wants to merge 2 commits into
mainfrom
fridah/omniml-4947-mem-monitor
Open

Add sidecar GPU/CPU memory+utilization monitor for HF PTQ#2000
Fridah-nv wants to merge 2 commits into
mainfrom
fridah/omniml-4947-mem-monitor

Conversation

@Fridah-nv

@Fridah-nv Fridah-nv commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: New example

Adds an opt-in sidecar that monitors GPU and CPU memory + utilization during an HF PTQ run, without instrumenting hf_ptq.py itself. It supports the OMNIML-4947 goal of verifying single-GPU layerwise PTQ stays within tight GPU/CPU memory budgets.

  • examples/hf_ptq/scripts/mem_monitor.py — a standalone, cross-process sampler. It reads GPU memory/utilization at the device level via NVML (falling back to nvidia-smi, and degrading to CPU-only if no driver is reachable) and CPU system + process-tree RSS/utilization via psutil, writing a CSV timeseries (overwriting any existing file) plus a peak/mean summary. It binds to a workload two ways: wrap mode (-- <cmd>, launches and tracks the child's process tree and propagates its exit code) or standalone (--pid / --duration / signal).
  • examples/hf_ptq/scripts/huggingface_example.sh — opt-in via MEM_MONITOR=1 (default off → byte-for-byte unchanged behavior); when enabled it wraps the existing hf_ptq.py invocation and writes mem_trace.csv + mem_peak.txt under SAVE_PATH.
  • examples/hf_ptq/requirements.txt — adds psutil (nvidia-ml-py is already a core dependency in pyproject.toml).
  • tests/examples/hf_ptq/test_mem_monitor.py — CPU-only unit tests.

Usage

# Via the example driver — opt in with MEM_MONITOR=1 (writes mem_trace.csv + mem_peak.txt under SAVE_PATH):
#   MEM_MONITOR=1 CUDA_VISIBLE_DEVICES=2,3 bash examples/hf_ptq/scripts/huggingface_example.sh --model <ckpt> --quant fp8
#
# Standalone wrap mode around any workload (propagates the child's exit code):
#   python examples/hf_ptq/scripts/mem_monitor.py --gpus 2,3 --interval 1 \
#       --out mem_trace.csv --summary mem_peak.txt -- \
#       python hf_ptq.py --pyt_ckpt_path <ckpt> --qformat fp8 --export_path <out>

Testing

  • Unit (CPU-only): pytest tests/examples/hf_ptq/test_mem_monitor.py — 8 passing; covers --gpus parsing (incl. UUID/MIG tokens that must not crash), the disabled-GPU path, CPU/process-tree sampling, CSV+summary output, and wrap-mode exit-code propagation. Subprocess tests are bounded by a default timeout.
  • GPU validation: ran the sidecar in wrap mode on 2× idle GPUs — confirmed device-level gpu{i}_used_mb/util columns populate through a workload's allocate → hold → free lifecycle, and mem_peak.txt is written.

Before your PR is "Ready for review"

Make sure you read and follow Contributor guidelines and your commits are signed (git commit -s -S).

Make sure you read and follow the Security Best Practices (e.g. avoiding hardcoded trust_remote_code=True, torch.load(..., weights_only=False), pickle, etc.).

  • Is this change backward compatible?: ✅ — opt-in via MEM_MONITOR=1, default off; no changes to hf_ptq.py or any modelopt/ code.
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: ✅ — no copied code; adds psutil to the example's requirements.txt (nvidia-ml-py was already a core dependency).
  • Did you write any new necessary tests?: ✅ — tests/examples/hf_ptq/test_mem_monitor.py.
  • Did you update Changelog?: N/A — example-only tooling, no library API change or behavior change.
  • Did you get Claude approval on this PR?: ✅

Additional Information

Summary by CodeRabbit

  • New Features

    • Added optional GPU/CPU memory and utilization monitoring for HuggingFace post-training quantization runs (enabled via MEM_MONITOR=1).
    • Monitoring records a time-series CSV plus peak/average summaries, including support for wrapped executions while preserving the wrapped process exit code.
    • Added psutil to enable CPU and process memory tracking.
  • Tests

    • Added automated test coverage for monitoring outputs, GPU index handling, command wrapping behavior, and summary/CSV generation.

Adds examples/hf_ptq/scripts/mem_monitor.py, a standalone cross-process
sidecar that samples device-level GPU (NVML, nvidia-smi fallback) and CPU
(psutil) memory + utilization into a CSV trace and peak/mean summary. It
binds to a workload via wrap mode (-- <cmd>, propagates the child exit code)
or standalone (--pid/--duration/signal), keeping profiling out of hf_ptq.py.

Opt-in from huggingface_example.sh via MEM_MONITOR=1 (default off, no
behavior change). Adds psutil + nvidia-ml-py deps and CPU-only unit tests.

Part of OMNIML-4947 (single-GPU layerwise PTQ memory validation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an optional memory-monitoring sidecar for HuggingFace PTQ runs, collecting CPU/GPU metrics into CSV and summary files. The change adds psutil, shell integration, the monitor implementation, and unit/end-to-end tests.

Changes

PTQ memory monitoring

Layer / File(s) Summary
Sampling and metric aggregation
examples/hf_ptq/scripts/mem_monitor.py
Resolves GPU selections, samples GPU metrics through NVML or nvidia-smi, samples system/process metrics with psutil, and aggregates values for CSV output and summaries.
Monitor orchestration and PTQ wiring
examples/hf_ptq/scripts/mem_monitor.py, examples/hf_ptq/scripts/huggingface_example.sh, examples/hf_ptq/requirements.txt
Parses standalone or wrapped-command options, controls child execution and stop conditions, writes trace and peak files, enables monitoring with MEM_MONITOR=1, and adds psutil.
Monitoring validation
tests/examples/hf_ptq/test_mem_monitor.py
Tests GPU selection, CPU/process sampling, metric aggregation, CSV and summary generation, wrapped-command behavior, and exit-code propagation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant huggingface_example as huggingface_example.sh
  participant mem_monitor as mem_monitor.py
  participant hf_ptq as hf_ptq.py
  participant outputs as CSV and summary files
  huggingface_example->>mem_monitor: invoke when MEM_MONITOR=1
  mem_monitor->>hf_ptq: launch wrapped PTQ command
  mem_monitor->>mem_monitor: sample CPU and GPU metrics
  mem_monitor->>outputs: write trace and peak summary
  mem_monitor-->>huggingface_example: return child exit status
Loading
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed Changed Python files contain no banned patterns; added psutil is BSD-3-Clause, so no restrictive new dependency was introduced.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a sidecar GPU/CPU memory and utilization monitor for HF PTQ.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fridah/omniml-4947-mem-monitor

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.70%. Comparing base (7d5d3f9) to head (77dab2e).
⚠️ Report is 1 commits behind head on main.

❗ There is a different number of reports uploaded between BASE (7d5d3f9) and HEAD (77dab2e). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (7d5d3f9) HEAD (77dab2e)
unit 2 1
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2000       +/-   ##
===========================================
- Coverage   78.38%   66.70%   -11.69%     
===========================================
  Files         518      518               
  Lines       58269    58269               
===========================================
- Hits        45674    38868     -6806     
- Misses      12595    19401     +6806     
Flag Coverage Δ
examples 43.23% <ø> (-0.20%) ⬇️
unit 54.86% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Fridah-nv
Fridah-nv marked this pull request as ready for review July 21, 2026 17:25
@Fridah-nv
Fridah-nv requested review from a team as code owners July 21, 2026 17:25
@Fridah-nv

Copy link
Copy Markdown
Contributor Author

/claude review

Comment thread examples/hf_ptq/scripts/mem_monitor.py Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review passed — no blocking issues found. LGTM

Scope: Full review of all 4 changed files (examples/hf_ptq/scripts/mem_monitor.py, huggingface_example.sh, requirements.txt, tests/examples/hf_ptq/test_mem_monitor.py). This is a self-contained sidecar utility under examples/ — it does not touch modelopt/ core, mode registration, config schema, or export paths, so the Mode/State and Export categories do not apply.

Findings: CRITICAL: 0, IMPORTANT: 0, SUGGESTION: 1

The one suggestion is a doc/behavior mismatch: the module docstring and PR body say the trace "appends a CSV timeseries," but --out is opened with mode "w", which truncates. Non-blocking — reword to "overwrites" (or switch to true append with a header guard).

Things I verified as correct:

  • Exit-code propagation: for a signal-killed child returncode == -signum, so 128 - (-signum) == 128 + signum, matching shell convention; normal codes pass through unchanged.
  • set -e safety in the shell wrapper: when MEM_MONITOR != 1 the MEM_MON_PREFIX array is empty and "${MEM_MON_PREFIX[@]}" expands to nothing, leaving the original python hf_ptq.py … invocation and its behavior fully unchanged (opt-in, default off).
  • nvidia-ml-py is the correct distribution providing the imported pynvml module (not the deprecated pynvml package).
  • Edge cases in _resolve_gpu_indices (UUID/MIG tokens, all/none), _cell (util 0 vs missing None), NVML→smi fallback, and process-tree RSS aggregation are handled and covered by the CPU-only tests.

Risk: Low. New opt-in example tooling with no changes to library code or default behavior; test coverage is adequate for the CPU path.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 4

🧹 Nitpick comments (2)
tests/examples/hf_ptq/test_mem_monitor.py (1)

91-92: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound subprocess test execution.

A regression in monitor termination can hang all three end-to-end tests indefinitely. Set a default timeout while preserving per-test overrides.

Proposed fix
 def _run(args, **kwargs):
+    kwargs.setdefault("timeout", 30)
     return subprocess.run([sys.executable, str(_SCRIPT), *args], **kwargs)

As per coding guidelines, “Respect the per-test timeout.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/examples/hf_ptq/test_mem_monitor.py` around lines 91 - 92, Update the
_run helper to provide a finite default timeout to subprocess.run, while
allowing callers’ per-test timeout values in kwargs to override that default.
Preserve all existing command construction and subprocess options.

Source: Coding guidelines

examples/hf_ptq/scripts/mem_monitor.py (1)

106-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Local import pynvml lacks the required justification comment.

As per coding guidelines, `**/*.py`: Keep imports at the top of Python source and test files; use local imports only for justified circular dependencies, optional dependencies, or unusually heavy imports, with a brief explanatory comment. This import is a justified optional-dependency case (NVML may be absent, falls back to nvidia-smi), but has no comment stating that.

🔧 Proposed fix
     def _init_nvml(self, indices: list[int] | None) -> bool:
         try:
+            # Optional dependency: pynvml (nvidia-ml-py) may not be installed;
+            # fall back to `nvidia-smi` parsing via _init_smi on any failure.
             import pynvml
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/hf_ptq/scripts/mem_monitor.py` around lines 106 - 125, Add a brief
explanatory comment immediately before the local pynvml import in _init_nvml,
documenting that it is intentionally local because NVML/pynvml is an optional
dependency and the monitor falls back to nvidia-smi when unavailable. Keep the
existing import placement and fallback behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/hf_ptq/scripts/mem_monitor.py`:
- Around line 291-293: Guard the psutil.Process initialization in the monitoring
setup around target_pid so stale or already-exited PIDs are handled cleanly
instead of propagating psutil.NoSuchProcess. Preserve the child-process path and
existing behavior for valid PIDs, while emitting the established user-facing
error and exiting through the script’s normal failure flow.
- Around line 268-284: Update _write_summary to ensure the parent directory of
the provided summary path exists before calling Path(path).write_text, while
preserving the existing no-path behavior. This must allow the later exit-status
handling around the wrapped command to run so the child’s return code is
propagated even when args.summary targets a missing directory.
- Around line 270-274: Gate the peak GPU memory output in the metrics summary
using mem_acc.seen, matching the existing util_acc.seen check. Update the loop
over metrics.gpu so peak_gpu{i}_used_mb is emitted only when a valid memory
sample was observed, while preserving the existing formatting and utilization
behavior.
- Around line 352-354: Update the child cleanup block to wait for the terminated
process with a finite timeout, then escalate by killing it if the timeout
expires and wait for termination to complete. Preserve the existing cleanup flow
so _write_summary and exit-code propagation still execute even when the child
ignores SIGTERM.

---

Nitpick comments:
In `@examples/hf_ptq/scripts/mem_monitor.py`:
- Around line 106-125: Add a brief explanatory comment immediately before the
local pynvml import in _init_nvml, documenting that it is intentionally local
because NVML/pynvml is an optional dependency and the monitor falls back to
nvidia-smi when unavailable. Keep the existing import placement and fallback
behavior unchanged.

In `@tests/examples/hf_ptq/test_mem_monitor.py`:
- Around line 91-92: Update the _run helper to provide a finite default timeout
to subprocess.run, while allowing callers’ per-test timeout values in kwargs to
override that default. Preserve all existing command construction and subprocess
options.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8ba3a711-1e7f-4b0e-8809-7d30c4c8195c

📥 Commits

Reviewing files that changed from the base of the PR and between 7d5d3f9 and 44e0d96.

📒 Files selected for processing (4)
  • examples/hf_ptq/requirements.txt
  • examples/hf_ptq/scripts/huggingface_example.sh
  • examples/hf_ptq/scripts/mem_monitor.py
  • tests/examples/hf_ptq/test_mem_monitor.py

Comment thread examples/hf_ptq/scripts/mem_monitor.py
Comment thread examples/hf_ptq/scripts/mem_monitor.py
Comment thread examples/hf_ptq/scripts/mem_monitor.py Outdated
Comment thread examples/hf_ptq/scripts/mem_monitor.py Outdated
Comment thread examples/hf_ptq/requirements.txt Outdated
compressed-tensors
fire
flash-attn>=2.6.0
nvidia-ml-py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvidia-ml-py is already a generic dependency in pyproject.toml so should not be listed here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the catch, removed nvidia-ml-py from examples/hf_ptq/requirements.txt

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (bedrock-claude-opus-4-8) — DM the bot to share feedback.

Adds a standalone, opt-in sidecar GPU/CPU memory+utilization monitor (examples/hf_ptq/scripts/mem_monitor.py) for HF PTQ runs, wired into huggingface_example.sh behind MEM_MONITOR=1 (default off). +542/-1, 4 files. The code is clean and well-documented; license headers match the canonical LICENSE_HEADER; no prompt-injection attempts in the PR content. Correctness spot-checks pass: the shell empty-array expansion "${MEM_MON_PREFIX[@]}" python hf_ptq.py is a genuine no-op when off; the signal exit-code math (128 - rc where rc = -N128 + N) is correct; the NVML→nvidia-smi fallback via or short-circuits properly; wrap-mode child cleanup (terminate()/wait()) on signal is handled. CPU-only unit + subprocess (wrap/standalone) tests are meaningful.

Flagging for a quick owner look because:

  • CHANGELOG not updated for this new feature (the PR checklist leaves it unmarked). Minor for opt-in tooling with no behavior change, but per repo policy new features get a CHANGELOG entry — confirm whether intended.
  • GPU sampling paths (NVML + nvidia-smi) are never exercised in CI — tests only cover the disabled (--gpus none) path, so GpuSampler.sample(), _query_smi() parsing, and the physical-index resolution are unverified by automation. Understandable given CPU-only CI, but worth noting.
  • CUDA_VISIBLE_DEVICES → NVML physical-index mismatch: the shell passes --gpus "${CUDA_VISIBLE_DEVICES-all}", but CUDA_VISIBLE_DEVICES can hold logically-remapped indices that don't match NVML/PCI physical indices unless CUDA_DEVICE_ORDER=PCI_BUS_ID is set. In that case the monitor silently reports the wrong physical GPUs. The docstring documents this caveat, but the shell wiring doesn't set/enforce CUDA_DEVICE_ORDER, so the sidecar could mis-report memory against the wrong device without any warning.

- requirements.txt: drop nvidia-ml-py (already a core dep in pyproject.toml);
  keep psutil, which is not.
- _write_summary: create the summary parent dir before writing so a missing
  directory can't crash the run and swallow the child's exit code; gate
  peak_gpu{i} on .seen so a never-sampled GPU is omitted instead of reported 0.0.
- Guard psutil.Process against a stale/exited --pid (clean error + exit 1).
- Escalate child.terminate() to SIGKILL after a 10s timeout so an unresponsive
  child cannot hang the monitor.
- Docs: 'writes (overwriting any existing file)' instead of 'appends'; add the
  optional-dependency justification comment for the local pynvml import.
- Tests: default 30s subprocess timeout in the _run helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
@shengliangxu

Copy link
Copy Markdown
Collaborator

I think this tool should be put into tools folder, it can be used anywhere else.

@shengliangxu shengliangxu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And could you paste an example output CSV?

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants