Skip to content

Allow Multiple Sampling Configs Per Node - #201

Merged
NSagan271 merged 13 commits into
mainfrom
fix-cuda-graphable-multi-sampler
Aug 8, 2026
Merged

Allow Multiple Sampling Configs Per Node#201
NSagan271 merged 13 commits into
mainfrom
fix-cuda-graphable-multi-sampler

Conversation

@NSagan271

@NSagan271 NSagan271 commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Closes #199. Replaces the broken sample_with_config workaround for the Qwen3-Omni code predictor with the ability for a model to define one or more "auxiliary" samplers, which are instantiated as full Sampler or CudaGraphableSampler classes, and have their own (derived) seed and config.

Migrated Qwen3-Omni and Qwen3-TTS over to the new sampling logic.

How was it tested?

Added new unit test.

Integration test with Qwen3-Omni, Qwen3-TTS, and Orpheus, with and without TP where applicable: outputs are correct, and spot-checked Qwen3-Omni benchmarks.

Checklist

  • ruff check . passes
  • Added or updated tests / docs where relevant

@NSagan271 NSagan271 changed the title [WIP] Allow Multiple Sampling Configs Per Node Allow Multiple Sampling Configs Per Node Aug 2, 2026
Comment thread mstar/model/base.py

@stephen-dwq stephen-dwq 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.

I think we can remove a lot of self.main is not None checks in handling MultiSamplingConfig if we add guard against that in the construction site in ./mstar/model/base.py, rather than permitting a propagation of None downstream from there, and adding spot-handling everywhere.

Otherwise, lgtm. Generally, I think we will have to abstract a lot of the structure as we bring in more models; some stuff (like the Sampling that was changed here) seems a little bit tight to a single model/architecture.

@NSagan271
NSagan271 force-pushed the fix-cuda-graphable-multi-sampler branch from cd480e2 to 5d629ac Compare August 2, 2026 20:42
@NSagan271
NSagan271 requested a review from stephen-dwq August 2, 2026 20:58
@NSagan271

Copy link
Copy Markdown
Collaborator Author

The Qwen3-TTS PR also got merged, so I migrated Qwen3-TTS over to the new sampler logic (and removed the custom fallback sampling logic)

@stephen-dwq stephen-dwq 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.

LGTM.

In the future, especially with Rust migration, we may consider a rewrite to a general NSampler with separate gather and view, rather than combining the two in gather_for_request_ids which seems to be doing both. This would really screw with the API, so it would be a very low priority if we consider this improvement.

@merceod merceod 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.

I had an observation from serving legs (not this PR's fault): qwen3-tts with an explicit seed kwarg is not reproducible across repeats even within one serverinstance. On this branch AND on main identically (different byte lengths per run). Since "seed" implies determinism to users, probably worth its own issue.

Comment thread mstar/engine/cuda_graph_runner.py Outdated
Comment on lines 2846 to 2847
device, autocast_dtype, tp_world_size, sampler_buffers=sampler_buffers,
)

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.

BLOCKING/CRITICAL: this call now always passes sampler_buffers=, but VJepa2ACRolloutPredictorSubmodule.get_piecewise_cuda_graph_configs (mstar/model/vjepa2/submodules.py lines 919) still has the old signature and this call sits outside the per-label try/except below, so nothing swallows it. Serving configs/vjepa2_ac.yaml on this branch kills the worker at engine warmup with "TypeError: got an unexpected keyword argument 'sampler_buffers'". The identical serve on main starts and answers requests. The base NodeSubmodule and qwen3_tts overrides were updated while vjepa2's was missed.

Fix should be pretty easy (accept and ignore the kwarg).

Also another note: no modular test builds vjepa2 piecewise runners, so the full suite is green on a branch that can't serve the model. Inthink a cheap signature-conformance test over get_piecewise_cuda_graph_configs overrides would close that gap.

@NSagan271 NSagan271 Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This (and the "This call is for its side effect " comment below) revealed a design flaw in incorporating the sampler into the piecewise cuda graph runner. get_piecewise_cuda_graph_configs is not the natural place to inject the sampler; instead, I added a flag to the piecewise cuda graph config for whether sampling is used, and if so, the cuda graph runner gathers the sampler and passes it into the captured function directly. The Qwen3-TTS submodules.py now doesn't have to handle sampler buffer/gathering logic; the captured function just gets a sampler it can use.

I still added the test for future reference.

Comment thread mstar/model/base.py
Comment on lines +371 to +374
def get_aux_sampling_configs(
self, node_name: str,
model_kwargs: dict | None = None,
) -> dict[str, SamplingConfig]:

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.

The aux SamplerBuffers are allocated once at engine build from the default config, and MultiSampler.set_config /MultiSamplerBuffers.update_request_config silently intersect/skip unknown labels. So a model whose aux label SET depended on model_kwargs would have late labels silently ignored (sample_aux does assert at use, but only then). The declare-at-startup contract is fine but we should be stating it here in the docstring (something like"the label set must not depend on model_kwargs, only the values may") as it would prevent a future issue.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, added the comment.

Comment thread mstar/model/qwen3_tts/submodules.py Outdated
Comment on lines +502 to +504
# off, and those rows always hold a well-formed (stale) config.
self._cp_sampler_buffers.aux["code_predictor"].gather_for_request_ids(
request_ids=engine_inputs.request_ids,

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 call is for its side effect (staging params into thebuffers the captured samplers read) and discards the returned sampler. Itt almost reads like someone forgot to use the result. This is correct currently I think (the per-bucket capture samplers alias the same gather buffers, and the padded tail is stale-but-sliced-off as the comment says), but a stage_-named wrapper or a one-line comment on the buffer aliasing would make the intent clear.

Also subtle: padded_bs=batch_size stages only the real rows while the bucket samples its full captured width which is fine per the slicing argument, just worth the note.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same fix as #201 (comment)

Comment thread mstar/utils/sampling.py Outdated
Comment on lines 1234 to 1235
) -> MultiCudaGraphableSampler:
"""Compatibility shim. Prefer ``bufs.gather_for_request_ids`` directly.

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.

About the doctrine (nitpick): the signature changed to MultiSamplerBuffers -> MultiCudaGraphableSampler, so this is no longer compatible with old callers. Either drop the "shim" framing or delete the function if nothing in-tree still uses it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed, just removed the function because it was only used in one place.

@Gaurav-Shah05 Gaurav-Shah05 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.

LGTM pending atindra's comments; the existing reviews cover everything I'd raise.

@NSagan271
NSagan271 requested a review from merceod August 5, 2026 05:58

@merceod merceod 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.

LGTM!

Two comments:

(1) needs a rebase since there is one trivial conflict in mstar/cli/main.py's DEFAULT_CONFIGS (wan22 vs sampling_test entries)

(2) that same CLI map registers "sampling_test": "sampling_test.yaml" but no such config file exists in the tree (at least I could not find it though I might be wrong lol) so mstar serve sampling_test would fail

@NSagan271
NSagan271 merged commit ea5e5a4 into main Aug 8, 2026
2 checks passed
@NSagan271
NSagan271 deleted the fix-cuda-graphable-multi-sampler branch August 8, 2026 20:25
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.

CudaGraphableSampler.sample_with_config is not CUDA-graphable: aux sampling params are frozen at capture

4 participants