Skip to content

fix(deps): declare httpx, unbreaking partial-install (torch) for every PR - #2547

Merged
kevalmorabia97 merged 1 commit into
mainfrom
haoguo/declare-httpx-dep
Sep 25, 2026
Merged

kevalmorabia97 merged 1 commit into
mainfrom
haoguo/declare-httpx-dep

Conversation

@h-guo18

@h-guo18 h-guo18 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

partial-install (torch) has been failing on every PR since 2026-09-24 — including PRs whose branches predate the breakage — and because it is a collection error rather than a test failure, it aborts the entire run:

ImportError while importing test module '.../tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py'
E   ModuleNotFoundError: No module named 'httpx'
collected 2243 items / 1 error / 45 skipped
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!

unit-pr-required-check aggregates it, so nothing currently merges on a fresh run.

What happened

No code changed. modelopt/torch/speculative/plugins/hf_streaming_dataset.py has imported httpx at module scope since #1509 (2026-06-02), and httpx has never appeared in pyproject.toml. It arrived only transitively: dev-test → timm → huggingface_hub → httpx.

huggingface_hub 2.0.0, published 2026-09-24T12:01:21Z, replaced httpx<1,>=0.23.0 with the separate httpx2<3,>=2.0.0 distribution. Different package name, so httpx stopped being installed and the chain disappeared.

The boundary is exact — every run created before that timestamp passes, every one after fails:

PR run created result
#2536 / #2535 09-23 22:02 pass
#2500 09-23 23:45 pass — merged 09-24 20:01 on this stale-green result
hub 1.33.0 (still requires httpx) 09-24 09:49
hub 2.0.0 published 09-24 12:01 ←
#2539 09-24 16:57 fail
#2544 09-24 18:32 fail
#2216 09-25 11:58 fail

#2500 merging afterwards is not a counterexample: GitHub does not re-run checks at merge time, so it merged on a result from ~20 hours earlier. That is also why this went unnoticed.

The changes

1. Declare httpx in the hf extra

httpx is not incidental to streaming — it is the only transport:

  • every fetch is HTTP: POST /v1/completions to the vLLM serve plus GET /meta and /desc against the connector's sidecar, all through httpx.Client;
  • there is no non-HTTP path — the base StreamingDataset._fetch is an abstract seam and EagleVllmStreamingDataset._fetch is its only implementation;
  • no other HTTP library appears in the module (requests / urllib / aiohttp: zero hits, and requests is not declared either);
  • even the retry predicate is built from it: _TRANSIENT_FETCH_ERRORS = (httpx.HTTPError, OSError).

It belongs in hf rather than in the core dependencies: the same module needs transformers.trainer_pt_utils at module scope, so one extra already gates the whole file, and a core install has no use for an HTTP client. The bound matches the 0.x API the code uses — httpx has no 1.0 release, and 2.x is a different distribution.

This is the part that stops it recurring. [hf] currently gets httpx only because datasets happens to require it — the same accident with a different supplier, one release away from repeating.

2. Acquire httpx in the test through the existing skip guard

The test file already intends to skip where the extra is absent — it has pytest.importorskip("transformers") and a comment explaining why, and transformers is absent in this job too. It broke only because import httpx sat five lines above that guard, where a missing module ends collection instead of skipping one file.

Verification

  • With everything installed: 18 passed, no behaviour change.
  • The import-order property is checked with an AST walk over the module's top-level statements: no hf-extra-only import precedes the first importorskip (which is now line 41).
  • A faithful local reproduction was attempted and abandoned honestly: hiding httpx locally also breaks huggingface_hub 1.28, which modelopt.torch.opt.plugins.huggingface imports, so the local failure is not the CI one. CI is the oracle for that half — this PR's own partial-install (torch) run is the check that matters.

Scope

Two files, five lines of declaration and four of test import order. Deliberately not folded into any feature PR: it blocks the whole repo, and burying a repo-wide fix inside unrelated work is how these stay invisible.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Optional Hugging Face installations now include httpx, supporting features that require HTTP communication without requiring it for all installations.
  • Tests
    • Hugging Face streaming dataset tests now skip when httpx is unavailable, allowing the remaining test suite to be collected and run without it.

@h-guo18
h-guo18 requested review from a team as code owners September 25, 2026 14:26
@copy-pr-bot

copy-pr-bot Bot commented Sep 25, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: NVIDIA/Model-Optimizer/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 221ecd8e-92e6-48bb-84f4-7d6bbd8f98c6

📥 Commits

Reviewing files that changed from the base of the PR and between 1396ba1 and 23072e0.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


📝 Walkthrough

Walkthrough

The hf extra now includes httpx>=0.23.0,<1. The streaming dataset test module skips collection if httpx is unavailable. Its existing transformers skip remains unchanged.

Changes

HF optional dependencies

Layer / File(s) Summary
Declare the dependency and guard test imports
pyproject.toml, tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py
The hf extra adds httpx>=0.23.0,<1. The test module uses pytest.importorskip for httpx, so collection skips if it is unavailable. The existing transformers skip is unchanged.

Priority: ⬇️ Low

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 23072

The dependency declaration and collection guard address the reported partial-install failure. No outstanding merge-blocking issue is evident.

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the dependency declaration fix and its purpose of preventing partial-install failures.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
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 PASS. The PR adds only httpx>=0.23.0,<1 to the hf extra and updates the test import guard. httpx 0.28.1 is BSD-3-Clause, a permissive license. No changed Python file adds `torch.load(..., weight…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

@h-guo18
h-guo18 force-pushed the haoguo/declare-httpx-dep branch from 71db41c to a5354ca Compare September 25, 2026 14:32
@codecov

codecov Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.64%. Comparing base (ed7e879) to head (23072e0).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2547      +/-   ##
==========================================
+ Coverage   71.56%   78.64%   +7.07%     
==========================================
  Files         607      607              
  Lines       67609    67609              
==========================================
+ Hits        48387    53169    +4782     
+ Misses      19222    14440    -4782     
Flag Coverage Δ
examples-diffusers 21.39% <ø> (ø)
examples-gpt-oss 13.55% <ø> (ø)
examples-hf_ptq 23.02% <ø> (-0.04%) ⬇️
examples-llm_distill 13.61% <ø> (-0.01%) ⬇️
examples-llm_eval 17.55% <ø> (ø)
examples-llm_qat 17.76% <ø> (-0.01%) ⬇️
examples-llm_sparsity 16.02% <ø> (ø)
examples-megatron_bridge 26.54% <ø> (-0.12%) ⬇️
examples-specdec_bench 13.31% <ø> (ø)
examples-speculative_decoding 17.91% <ø> (-0.07%) ⬇️
examples-torch_onnx 21.94% <ø> (ø)
examples-torch_trt 15.37% <ø> (ø)
examples-vllm_serve 13.73% <ø> (ø)
gpu 58.53% <ø> (+25.25%) ⬆️
regression 15.25% <ø> (-0.01%) ⬇️
unit 58.79% <ø> (ø)

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.

@h-guo18
h-guo18 force-pushed the haoguo/declare-httpx-dep branch from a5354ca to 1396ba1 Compare September 25, 2026 15:02
@h-guo18

h-guo18 commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

/claude review

Comment thread pyproject.toml

@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 — CRITICAL: 0, IMPORTANT: 1, SUGGESTION: 0

Full review; both changed files (pyproject.toml, tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py) reviewed, plus the surrounding install machinery (noxfile.py, uv.lock, .pre-commit-config.yaml, modelopt/torch/speculative/plugins/).

The one blocking item

uv.lock was not regenerated. The committed resolution's hf extra (uv.lock:2742-2754) and requires-dist (uv.lock:2786+) still omit httpx, so in lock-driven installs — uv sync --extra hf, and the uv run --frozen --extra dev pre-commit hooks — httpx continues to arrive only as a transitive requirement of datasets (uv.lock:945). That is the exact accident the PR description sets out to remove. It also means the next uv lock rewrites the lock in an unrelated branch, or bump_uv_lock.yml absorbs this change into a dependency-bump PR where nobody sees it. Run uv lock and commit it.

This does not affect the CI job being fixed: noxfile.py:87 installs .[dev-test] from the extras directly, so partial_unit(subset='torch') is unblocked by the importorskip alone.

What I checked and found correct

  • The importorskip fix is right and complete. partial_unit(subset='torch') installs .[dev-test] + megatron-core, so transformers is absent too and the pre-existing guard would have skipped the file anyway — the only defect was import httpx sitting above it, turning a skip into a collection abort. httpx = pytest.importorskip("httpx") binds the module, and every use in the file (lines 103, 277-283, 302-324, 463-465) is an attribute access on it, including the monkeypatch.setattr(hf_streaming_dataset.httpx, "Client", ...) indirection. No other file under tests/ imports httpx or transitively reaches hf_streaming_dataset, so this was the only collection-time exposure.
  • hf is the correct extra, not core dependencies. hf_streaming_dataset.py:48 imports httpx at module scope alongside transformers.trainer_pt_utils, so one extra already gates the whole module. Plugin laziness is intact: plugins/__init__.py never imports hf_streaming_dataset (only megatron_* and the hf_* modules under import_plugin), so a core install cannot reach the import.
  • The example scripts are covered. scripts/send_conversation_vllm.py:23 and collect_hidden_states/send_conversations_for_hiddens.py:23 also import httpx at module scope and their requirements.txt doesn't list it — but examples/speculative_decoding/README.md:43 instructs pip install -U nvidia-modelopt[hf] before the requirements file, so declaring it in hf fixes those too.
  • The bound matches the API in use. httpx.Client, httpx.Timeout(..., connect=...), httpx.HTTPError, httpx.MockTransport are all well within >=0.23.0, and the upper cap is consistent with the extra's existing style (transformers>=4.57,<5.15).

Risk: Low. Behaviour-neutral dependency plumbing with no source changes; the lock omission is the only thing that leaves the stated goal half-applied.

🤖 Generated with Claude Code

@h-guo18

h-guo18 commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

/claude review

…absent

`partial-install (torch)` has been red on every PR since 2026-09-24, with a
collection error rather than a test failure, so it takes the whole run down:

    ImportError while importing test module '.../test_hf_streaming_dataset.py'
    E   ModuleNotFoundError: No module named 'httpx'

Nothing in the repo changed. `hf_streaming_dataset.py` has imported httpx at
module scope since #1509 (2026-06-02) and httpx has never been declared anywhere
in pyproject.toml -- it arrived only as a transitive dependency, via
dev-test -> timm -> huggingface_hub. huggingface_hub 2.0.0, published
2026-09-24T12:01Z, replaced its `httpx<1` requirement with the separate `httpx2`
distribution, and the chain went away. Every CI run created before that timestamp
passes and every one after it fails; PRs merged later merged on a green result
from a run that predated it.

Two changes, for two different reasons.

`httpx` is now declared in the `hf` extra. It is not incidental to streaming: the
whole fetch path is HTTP (POST /v1/completions to the vLLM serve, GET /meta and
/desc against the connector's sidecar) through `httpx.Client`, there is no
non-HTTP transport -- the base `StreamingDataset._fetch` is an abstract seam and
the only implementation is that one -- and even the retry predicate is built from
`httpx.HTTPError`. It belongs in `hf` rather than the core dependencies because
that same module needs `transformers.trainer_pt_utils` at module scope, so one
extra already gates the file; core installs have no use for an HTTP client. The
bound matches the 0.x API the code uses; httpx has no 1.0 release.

The test acquires httpx through `pytest.importorskip` instead of importing it
five lines above the guard. That file already intends to skip on an install
without the extra -- it has `importorskip("transformers")` and a comment saying
why -- and transformers is absent in this job too. It only broke because one
import sat on the wrong side of the guard, where a missing module ends collection
instead of skipping one file.

Declaring the dependency is what keeps this from recurring: `[hf]` currently gets
httpx only because `datasets` happens to require it, which is the same accident
with a different supplier.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: h-guo18 <67671475+h-guo18@users.noreply.github.com>
@h-guo18
h-guo18 force-pushed the haoguo/declare-httpx-dep branch from 1396ba1 to 23072e0 Compare September 25, 2026 16:29

@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 (re-run on 1396ba1) — CRITICAL: 0, IMPORTANT: 0, SUGGESTION: 2

Full review of both changed files (pyproject.toml, tests/unit/torch/speculative/plugins/test_hf_streaming_dataset.py), plus the install machinery needed to judge them: noxfile.py, uv.lock, .pre-commit-config.yaml, .github/workflows/bump_uv_lock.yml, .github/CODEOWNERS, and the httpx call sites in modelopt/ and examples/. No inline comments this round — nothing rose to blocking, and the one prior finding is re-graded below rather than re-posted.

Revising the previous review's one blocking item

The earlier run flagged the stale uv.lock as IMPORTANT. Having verified it against the repo, I'm downgrading that to non-blocking and approving. The factual claim holds — requires-dist (uv.lock:2786+) has no httpx edge under extra == 'hf', so the lock is out of sync with pyproject.toml:85 — but the impact assessment was wrong on three counts:

  1. No gate validates lock/pyproject sync. The only uv invocations in the repo are uv run --frozen --extra dev in .pre-commit-config.yaml (lines 60, 66, 81, 168). --frozen deliberately neither re-resolves nor validates against pyproject.toml — that is --locked / uv lock --check, which appear nowhere in .github/workflows/ or noxfile.py. Nothing fails.
  2. Regenerating it would change zero installed packages. httpx is already a locked package (uv.lock:1351) and a hard requirement of both datasets 5.0.1 (uv.lock:941) and diffusers 0.40.0 (uv.lock:1014) — both members of the hf extra. uv lock would add a declared edge, not a package, to any uv sync --extra hf / --extra dev environment.
  3. The lock is separately owned and self-healing. .github/CODEOWNERS:16 assigns uv.lock to @NVIDIA/modelopt-setup-codeowners, and bump_uv_lock.yml re-resolves it from pyproject.toml every Monday — so the next bump picks up the new direct edge on its own. Touching it here would pull an extra codeowner review onto a CI-unblocking hotfix and risk conflicting with that automation, for no functional delta.

So leaving uv.lock alone in this PR is defensible, arguably preferable. Worth knowing, not worth blocking on.

What I verified as correct

  • The importorskip fix is right, and it does not silently disable the test. This was the real bug: import httpx sat above the guard, so a missing module aborts collection instead of skipping one file. httpx = pytest.importorskip("httpx") binds the module, and all uses in the file are attribute accesses on it (lines 103, 277-283, 302-324, 463-465), including the monkeypatch.setattr(hf_streaming_dataset.httpx, "Client", ...) indirection — so the module-object binding is sufficient. Critically, the test still runs in the main unit session: noxfile.py:72 installs .[all,dev-test], and pyproject.toml:143 makes all include hf, which now declares httpx. The skip is confined to partial_unit(subset='torch') (noxfile.py:87, .[dev-test] + megatron-core), where transformers is absent too and the file was always going to skip.
  • hf is the right extra, and plugin laziness is intact. hf_streaming_dataset.py:48 imports httpx at module scope beside transformers.trainer_pt_utils, so one extra already gates the module. speculative/plugins/__init__.py never imports hf_streaming_dataset, so a core install cannot reach the import.
  • httpx has exactly one runtime home. Across the repo only four files touch it: the module (:48, :85 _TRANSIENT_FETCH_ERRORS, :441-442 Client/Timeout), this test, and two examples/speculative_decoding/ scripts — the latter covered because that README directs pip install -U nvidia-modelopt[hf]. No other test imports httpx or transitively reaches the module, so this was the only collection-time exposure.
  • No constraint conflict. httpx appears nowhere else in pyproject.toml, and no constraint-dependencies is active.

Suggestions (non-blocking)

  1. Stale uv.lock edge — as above. If the setup codeowners would rather the lock track pyproject.toml commit-for-commit, uv lock is the fix; otherwise the Monday bump handles it.
  2. The <1 cap is a small resolution risk for a library. It matches house style (transformers>=4.57,<5.15) and the 0.x API in use, so it is fine as written. But nvidia-modelopt is installed alongside datasets / diffusers, which require httpx unpinned; if either later requires httpx>=1, the cap turns into an unsatisfiable resolution or a silent datasets downgrade — the same class of upstream-shift breakage this PR is fixing, from the other direction. A bare httpx>=0.23.0 would carry less of it.

Assessment

Risk: low. Two lines of dependency plumbing with no source changes; the diagnosis in the description checks out against the code, the fix lands on the actual failure mechanism (import above the guard, not the guard itself), and the test remains covered where the extra is installed. Approving.

🤖 Generated with Claude Code

@kevalmorabia97
kevalmorabia97 merged commit 23355ed into main Sep 25, 2026
57 checks passed
@kevalmorabia97
kevalmorabia97 deleted the haoguo/declare-httpx-dep branch September 25, 2026 22:57
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-25 22:57 UTC

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.

2 participants