Skip to content

Feat/qwen35 tp phase1#627

Merged
CAICAIIs merged 32 commits into
openinfer-project:mainfrom
Mrtroll486:feat/qwen35-tp-phase1
Jul 16, 2026
Merged

Feat/qwen35 tp phase1#627
CAICAIIs merged 32 commits into
openinfer-project:mainfrom
Mrtroll486:feat/qwen35-tp-phase1

Conversation

@Mrtroll486

Copy link
Copy Markdown
Contributor

Related

Background

This PR lands Qwen3.5-4B TP Phase 1.

Phase 1 is a correctness-first eager dense-TP milestone: it brings up TP2 dense full-attention / MLP sharding, worker/scheduler lifecycle, and the real serving path. This PR does not change the design contract doc docs/models/qwen35/tp-design.md; if review finds that additional contract wording is needed, I will update the design-doc branch / PR #450 rather than patching the design contract directly in this implementation PR.

Phase 2 design work, including TP unified steps, TP CUDA Graph, and sharded linear-attention/GDR state, should be decided in follow-up RFC issue discussion. Any unsettled design points mentioned below are follow-ups, not Phase 1 design commitments.

What Changed

Qwen3.5 TP runtime

  • Added Qwen3.5 tensor-parallel config:
    • rank/world_size
    • dense-dimension divisibility checks
    • fail-closed startup for TP > 1 && CUDA Graph
    • unchanged TP1 default behavior
  • Implemented dense TP weight loading:
    • sharded full-attention q/k/v/o
    • rank-local full-attention KV-head layout
    • sharded MLP gate/up/down
    • replicated embedding / tied lm_head
    • replicated linear-attention / GDR weights, conv state, and recurrent state for Phase 1
  • Added a Qwen3.5 TP worker executor:
    • each rank worker owns its CUDA context, cuBLAS, NCCL comm, model shard, KV state, and recurrent/conv state
    • supports eager prefill, chunked prefill, and eager decode
    • DropRequest clears rank-local request state
  • Routed the scheduler through a TP backend:
    • scheduler keeps logical request/page accounting
    • workers own physical rank-local KV/recurrent/conv state
    • TP scheduler currently selects eager prefill-only or eager decode-only steps
    • TP unified step is not enabled in Phase 1

Qwen3.5 test framework

  • Added shared TP2 test-device parsing through OPENINFER_TEST_TP_DEVICES.
    • Defaults to 0,1.
    • Accepts explicit two-device pairs such as 1,2 or 2,3.
    • Validates exactly two distinct CUDA ordinals.
    • Used by TP2 HF golden gates, TP2 scheduler e2e, and TP2 HTTP serving smoke.
  • Extended Qwen3.5 test path configuration:
    • OPENINFER_TEST_MODEL_PATH selects the real Qwen3.5 weights path.
    • OPENINFER_TEST_FRONTEND_MODEL_PATH optionally selects frontend tokenizer/config metadata for HTTP serving tests.
    • OPENINFER_TEST_FRONTEND_MODEL_PATH falls back to OPENINFER_TEST_MODEL_PATH when unset.
  • Refactored Qwen3.5 scheduler e2e so the same request-flow body can run against both:
    • TP1 single-GPU scheduler
    • TP2 public multi-device engine path
  • Added TP2 HF golden-gate coverage:
    • short fixture replay through Qwen35TpExecutor
    • sequential and batched eager replay
    • long fixture replay through TP2 eager path
  • Added TP scheduler/backend tests:
    • eager-only plan selection under TP
    • fail-closed TP > 1 && CUDA Graph
    • chunked prefill followed by eager decode
    • rank-local cleanup through request lifecycle paths
  • Added a real TP2 HTTP serving integration test:
    • starts a Qwen3.5 TP2 engine
    • serves it through openinfer_vllm_frontend::serve
    • exercises OpenAI-compatible /v1/models and /v1/completions

Server / frontend launch path

  • Added Qwen3.5 launch options so the server path can pass:
    • tp_size
    • device_ordinal
    • cuda_graph
    • max_prefill_tokens
  • Updated openinfer-server Qwen3.5 startup to use the new launch options.
    • tp_size > 1 maps to devices 0..tp_size in the server path.
    • CUDA Graph must be disabled for TP in Phase 1.
  • Extended bench_serving with --max-prefill-tokens for Qwen3/Qwen3.5 chunked-prefill control.
  • Added HTTP TP2 smoke coverage through the frontend path.
    • This PR does not directly change the openinfer_vllm_frontend crate.
    • It wires Qwen3.5 TP startup into the server/bench paths and validates frontend behavior end-to-end.

Docs

  • Added docs/models/qwen35/tp-implementation.md.
    • Records Phase 1 outcome.
    • Records validation evidence.
    • Records stable TP2 test knobs.
    • Records implementation constraints and follow-ups.
    • Does not replace the stable design contract in tp-design.md.

Testing

TP2 correctness

  • TP2 short HF logits gate passes:
    • sequential eager: 108 positions, mean 0.0258, p99 0.0801, max 0.1298
    • batched eager: 72 positions, mean 0.0257, p99 0.0809, max 0.1298
  • TP2 long HF logits gate passes:
    • prompts 4097 / 8192
    • sequential eager: 18 positions, mean 0.0232, p99 0.0792, max 0.1035

Scheduler / serving

  • TP2 scheduler e2e passes and covers:
    • context-window rejection
    • greedy / logprobs paths
    • sequential requests
    • repeated request reuse
    • concurrent mixed greedy/sampling requests
    • consumer drop
    • post-drop scheduler health
  • TP2 HTTP serving smoke passes and covers:
    • /v1/models
    • non-streaming /v1/completions
    • streaming /v1/completions
    • concurrent completions
    • finite logprobs
    • chunked prefill forced with max_prefill_tokens=1
    • fail-closed startup for TP2 + CUDA Graph

TP1 regression

  • TP1 short/long HF logits gates pass
  • TP1 scheduler e2e passes

Compile / focused tests

  • Qwen3.5 lib tests pass with real CUDA access.
  • Qwen3.5 TP executor focused tests pass.
  • Qwen3.5 scheduler focused tests pass.
  • serving_tp2 test target compiles.
  • openinfer-server --features qwen35-4b test target compiles.

Out Of Scope

  • TP CUDA Graph capture/replay
  • TP RunUnifiedStep mixed prefill+decode execution
  • sharded linear-attention/GDR weights, kernels, conv state, or recurrent state
  • vocab-parallel embedding / lm_head
  • prefix cache / recurrent-state snapshots
  • performance claims

Reviewer Notes / Open Design Points

1. TP unified step

The Phase 1 TP scheduler currently selects eager prefill-only or eager decode-only steps. It does not select a mixed prefill+decode unified step under TP.

This PR’s position is that Phase 1 should first prove dense TP correctness, worker-owned state, and rank-local cleanup/reuse. The semantics, scheduling policy, acceptance gates, and sequencing for TP RunUnifiedStep should be decided in a follow-up RFC issue.

If reviewers want tp-design.md to explicitly say that Phase 1 does not cover RunUnifiedStep, I will update the design-doc branch / PR #450 rather than changing the design contract directly in this PR.

2. Slot / slot-compaction wording

There are two concepts that are easy to conflate:

  • single-GPU CUDA Graph decode slots: stable-address recurrent/conv slots in BatchDecodeGraphState
  • PagedAttention KV pages/blocks: physical/logical KV cache pages

TP2 Phase 1 is eager-only and does not use TP CUDA Graph recurrent slots. It validates consistent rank-local request cleanup across finish/drop/cancel/reuse, which is not the same thing as TP CUDA Graph slot compaction.

If this distinction should become part of the design contract, I will update PR #450 / the design-doc branch.

3. Gated q_proj physical layout

The implementation confirmed that Qwen3.5 full-attention q_proj uses a head-interleaved physical layout: [head q][head gate]. TP sharding must preserve that layout. Rebuilding local shards as [all local q][all local gate] breaks numerics.

This is recorded in the implementation doc for now. If reviewers consider it part of the long-term partition contract, I will add the generic formula and TP2 example to PR #450 / the design-doc branch.

Docs

Added:

  • docs/models/qwen35/tp-implementation.md

This doc records the Phase 1 implementation and validation evidence. The stable design contract remains in docs/models/qwen35/tp-design.md / PR #450.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

  • New feature (non-breaking change which adds functionality)

  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

  • Documentation update

Checklist

  • My code follows the style guidelines of this project (see docs/conventions/coding-style.md).

  • I have performed a self-review of my own code.

  • I have formatted my commits according to Commitizen conventions.

  • I have run the local test suite and all tests pass (see CLAUDE.md).

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b380a1baec

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openinfer-qwen35-4b/src/tp_executor.rs Outdated
Comment thread openinfer-qwen35-4b/src/tp_executor.rs Outdated
@Mrtroll486
Mrtroll486 marked this pull request as draft July 9, 2026 12:31
@Mrtroll486

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e4f50a0f01

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openinfer-kernels/build.rs Outdated
Comment on lines +1041 to +1045
&format!("CUmodule {module_name}[16] = {{0}};"),
)
.replace(
&format!("CUfunction {function_name} = NULL;"),
&format!("CUfunction {function_name}[16] = {{0}};"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Size Triton handle tables for the selected CUDA device

When Qwen3.5 is launched on CUDA ordinal 16 or higher, the generated stubs index these fixed 16-entry arrays with the dev returned by cuCtxGetDevice in the patched load/launch paths, so module_name[dev] or function_name[dev] writes past the table and can corrupt memory. Please size the tables from the actual CUDA device count or fail closed when dev is outside the allocated range.

Useful? React with 👍 / 👎.

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.

This issue was not caused by switching from a single handle to a per-device list. The real fix was to support multiple CUDA devices with separate handles. We kept the fixed 16-entry table, and added a check that rejects any device ordinal greater than 15 before indexing it. If reviewers find this approach suboptimal, please let me know, and I will update it accordingly.

@Mrtroll486
Mrtroll486 marked this pull request as ready for review July 9, 2026 14:33

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e4f50a0f01

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openinfer-kernels/build.rs Outdated
@CAICAIIs
CAICAIIs self-requested a review July 12, 2026 16:15
@CAICAIIs

CAICAIIs commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

hi thanks your contribution, please fix the conflicts

@Mrtroll486
Mrtroll486 force-pushed the feat/qwen35-tp-phase1 branch from a624e29 to 920ec4a Compare July 13, 2026 08:59
@Mrtroll486

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 920ec4a9f3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openinfer-server/src/main.rs
@Mrtroll486

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a33275755f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openinfer-qwen35-4b/src/batch_decode.rs
@Mrtroll486

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07dc15be4f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openinfer-qwen35-4b/src/scheduler.rs Outdated
})
.expect("failed to spawn Qwen3.5 TP scheduler thread");

Ok(SchedulerHandle::new_with_join_handle(submit_tx, join_handle).with_servable_len(servable))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Advertise Qwen3.5 TP KV capacity

When Qwen3.5 starts with tp_size > 1, this returns an EngineHandle without with_kv_capacity, even though the TP backend already knows capacity_pages_for_requests() and page_size(). The vLLM bridge treats handle.kv_capacity() == None as num_gpu_blocks = 0 and omits cache size/concurrency in its ready response (openinfer-vllm-frontend/src/bridge.rs:65-80), so TP HTTP serving advertises zero KV blocks to the frontend/tooling despite having a real cache. Mirror the single-GPU path by attaching a KvCapacity built from the TP backend's page count and block size.

Useful? React with 👍 / 👎.

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.

sloved.

@Mrtroll486

Copy link
Copy Markdown
Contributor Author

All conflicts fixed.

Comment thread openinfer-qwen35-4b/src/tp_executor.rs Outdated
let mut result = None;
for (rank, recv) in pending.into_iter().enumerate() {
match recv
.recv()

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.

A worker can return a rank-local CUDA/OOM error before its peers complete the corresponding NCCL collective. The controller then waits indefinitely on these unbounded recv() calls, and Drop can also block forever while joining a worker stuck in NCCL. Please add a runtime failure contract that aborts the communicator or process, marks the executor as poisoned, and prevents unbounded cleanup.

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.

Solved. The runtime fix adds a shared poison state, propagates worker errors and panics, uses bounded response and shutdown waits, and aborts the process if NCCL workers cannot exit, preventing indefinite runtime and cleanup hangs.

request_id,
phase: TpRequestPhase::Prefilling,
kv: self.model.alloc_kv(),
recurrent: RecurrentState::new(self.model.device_ctx(), self.model.config())?,

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.

Each new TP request allocates a full replicated recurrent and convolution state on every rank. For Qwen3.5-4B this is about 49.125 MiB per request per rank, so the default max_batch=64 requires about 3.07 GiB per rank after the KV pool has already consumed 85% of the available memory. Please reserve or preallocate this state before sizing the KV pool, or derive and advertise a lower capacity from the minimum available memory across ranks.

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.

Solved. The capacity fix calculates per-request recurrent-state memory, reserves GPU runtime headroom, derives a safe max_batch for each rank, uses the minimum across all ranks, and rejects startup if even one request cannot fit.

.mem_get_info()
.map_err(|err| anyhow::anyhow!("failed to query TP rank {rank} memory: {err}"))?;
let recurrent_bytes = RecurrentState::allocation_bytes(model.config());
let max_batch = effective_recurrent_capacity(

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.

This preserves only 512 MiB, but a legal 20k-token TP2 prefill needs about 2.66 GiB of scratch. Recurrent states can consume that headroom under high occupancy and cause OOM. Please subtract the configured prefill scratch before calculating capacity, or cap max_prefill_tokens.

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.

Solved. Before calculating recurrent-state capacity, we subtract both the runtime reserve and the estimated prefill scratch allocation from the available memory. The scratch allocation is estimated using min(max_prefill_tokens, PREFILL_CHUNK_LEN), where PREFILL_CHUNK_LEN is currently 20,000 tokens. Thus, we reserve scratch for the smaller of the configured maximum prefill budget and the hard cap of 20k tokens.

Note that the subtracted value is estimated value calculated by GdrChunkwiseScratch35::estimate_bytes(...), this will be slightly bigger than 2.66 GiB.

@Mrtroll486
Mrtroll486 force-pushed the feat/qwen35-tp-phase1 branch from 5d84063 to 879044a Compare July 16, 2026 15:00
@CAICAIIs
CAICAIIs merged commit 9abca8a into openinfer-project:main Jul 16, 2026
12 checks passed
CAICAIIs pushed a commit that referenced this pull request Jul 16, 2026
Squashed PR #627 from Mrtroll486/feat/qwen35-tp-phase1.
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