NVIDIA-NeMo / NVIDIA-NeMo/DataDesigner

Decouple HTTP connection pool limits from max_parallel_requests at high concurrency

Open
#946 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
2.2k
Forks
211
Avg merge
2d 6h
Merged PRs (30d)
40

Description

Priority Level

High (Major functionality broken)

Describe the bug

HttpModelClient derives HTTP transport limits directly from max_parallel_requests:

max_connections = max(32, 2 * max_parallel_requests)
max_keepalive_connections = max(16, max_parallel_requests)

At high request-admission ceilings this creates and retains a very large shared httpcore connection pool, even when the async scheduler can expose far fewer concurrent model requests. httpcore.AsyncConnectionPool._assign_requests_to_connections() and connection is_idle() checks scan Python connection state on the event-loop thread, so an oversized pool can become a dominant GIL cost.

The model request-admission ceiling and HTTP transport-pool capacity represent different concerns and should not be coupled linearly without a bound or independent control.

Steps/Code to reproduce bug
from data_designer.engine.models.clients.adapters.http_model_client import (
    ClientConcurrencyMode,
    HttpModelClient,
)


class ExampleClient(HttpModelClient):
    def _build_headers(self, extra_headers: dict[str, str]) -> dict[str, str]:
        return extra_headers


client = ExampleClient(
    provider_name="example",
    endpoint="http://127.0.0.1:8000",
    max_parallel_requests=8_192,
    concurrency_mode=ClientConcurrencyMode.ASYNC,
)

print(client.limits.max_connections)
print(client.limits.max_keepalive_connections)

This configures 16,384 total connections and 8,192 keepalive connections before any workload-specific client concurrency is considered.

For the runtime symptom, run a large asynchronous workload with a high model request ceiling and a lower scheduler task limit, then capture a GIL-only profile. Connection assignment and idle checks consume substantial event-loop CPU even though the useful concurrent request population is much smaller than the configured pool.

Expected behavior

Data Designer should size the HTTP pool from expected transport concurrency rather than directly from the model request-admission ceiling. A supported solution could provide independently configurable, validated transport limits with bounded defaults.

The implementation should:

  • Preserve enough connections to sustain configured useful concurrency.
  • Avoid retaining thousands of connections when scheduler demand is materially lower.
  • Expose the effective transport limits for diagnostics.
  • Preserve the correction made for #459, where configured limits previously did not reach the underlying transport.
Agent Diagnostic / Prior Investigation

No existing issue was found for the high-concurrency inverse of #459. That issue correctly ensured that max_parallel_requests affected the real transport pool, but the current direct 2x/1x sizing policy becomes expensive at much larger values.

In a controlled GIL-only profile, HTTP connection and transport work accounted for 52.45% of samples. After removing an independent request-admission queue bottleneck, it accounted for 75.39% and became the dominant remaining client cost. The largest leaves were _assign_requests_to_connections() and connection is_idle() variants.

The profile showed one event-loop thread consuming the client CPU while other host CPU capacity remained available. Request completion was balanced and successful, which points to client-side transport bookkeeping rather than endpoint routing or error handling.

Additional context

Related: #459. This issue is not requesting that the pool return to the old fixed limit. It requests a transport-capacity model that remains correct at both moderate and very high request-admission ceilings.

Checklist
  • I reproduced this issue or provided a minimal example
  • I searched the docs/issues myself, or had my agent do so
  • If I used an agent, I included its diagnostics above

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in data_designer/engine/models/clients/adapters/http_model_client.py by tracing HttpModelClient limits and the correction from #459. Run the minimal ExampleClient reproduction with max_parallel_requests=8,192, then inspect how the limits reach httpcore; done means independently configurable, validated transport limits remain sufficient for useful concurrency, avoid oversized pools, and are exposed for diagnostics.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.