Skip to content

graph_trainer: auto-size GPU_MAX_HW_QUEUES on ROCm - #4032

Open
rishisinhanj wants to merge 3 commits into
pytorch:mainfrom
rishisinhanj:graphtrainer/rocm-hw-queues-autoset
Open

graph_trainer: auto-size GPU_MAX_HW_QUEUES on ROCm#4032
rishisinhanj wants to merge 3 commits into
pytorch:mainfrom
rishisinhanj:graphtrainer/rocm-hw-queues-autoset

Conversation

@rishisinhanj

@rishisinhanj rishisinhanj commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

On ROCm, HIP maps logical streams to hardware queues via stream_id % Q. When Q is too small, independent streams alias onto the same queue and lose overlap.

Adds hw_queues.maybe_set_gpu_max_hw_queues(): estimates independent GPU stream count from the graph_trainer config, sets GPU_MAX_HW_QUEUES to the next power of two, and runs in GraphTrainer.__init__ before HIP init.

  • ROCm-only; no-op on CUDA
  • Respects an explicit GPU_MAX_HW_QUEUES override

Stream model

lanes = compute + all_reduce
      + (dense AG + RS          if fsdp_ag_rs_overlap else fsdp_comm)
      + expert_comm             if MoE and ep>1
      + tp_all_gather           if tp>1
      + cp_all_gather           if cp>1
      + cudagraph_capture       if cudagraph and not fsdp_ag_rs_overlap
Q = next_pow2(lanes)

EP=2 DeepSeek-V3 with FSDP AG/RS overlap → 5 lanes → Q=8.

Results

EP=2 2-node DeepSeek-V3 (MI355X, gfx950, ROCm 7.2). Throughput (tok/s/GPU):

torchtitan main q4 q8 (auto) q16
fec3e19 (2026-07-14) 9002 9674 (+7.5%) 8357 (-13.6%)
1ac4653 (2026-07-29, current) 9498 9489 (±0.1%) 9137 (-3.8%)

On current main the win is net-neutral, but the setter lands on the best Q and protects against Q=16 overshoot.

Why this is still worth merging

Q >= #streams is a hard ceiling: if Q is too small, colliding streams share a hardware queue and cannot run concurrently, regardless of scheduler quality. Today upstream scheduling already keeps dense FSDP collectives off the compute queue at Q=4, so the lever is not the binding constraint — but that can change as overlap improves or configs add streams.

This hook is cheap (config-only, before HIP init, no profiling run) and ensures the queue count is sized correctly ahead of time. It also prevents accidental overshoot to Q=16, which regressed throughput even on the current build.

Test plan

  • PYTHONPATH=. python3 -m torchtitan.experiments.graph_trainer.tests.test_hw_queues
  • ROCm: unset GPU_MAX_HW_QUEUES, run graph_trainer, confirm auto-set log line
  • Override: GPU_MAX_HW_QUEUES=4 → no auto-set

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jul 30, 2026
@pytorch-bot

pytorch-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

Unknown label ciflow/rocm.
Currently recognized labels are

  • ciflow/8gpu
  • ciflow/h100.8
  • ciflow/rl

@pytorch-bot

pytorch-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

Workflows were awaiting approval. CI has now been triggered for the ciflow labels on this PR.

lanes = ["compute", "all_reduce"] # compute + loss/grad all_reduce (distributed)
if dp_shard_active:
if fsdp_ag_rs_overlap:
lanes += ["dense_all_gather", "reduce_scatter"] # split onto own PGs

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.

why do we need two lanes for "dense_all_gather", "reduce_scatter"?

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.

When enable_fsdp_ag_rs_overlap is on, reassign_collective_pgs_pass puts dense all-gather and reduce-scatter on separate process groups, so each gets its own stream

lanes.append("fsdp_comm") # dense AG+RS unsplit -> share one stream
if is_moe and ep > 1:
lanes.append("expert_all_gather") # eFSDP mesh AG (its own PG, structural)
lanes.append("expert_all_to_all") # expert-parallel dispatch (distinct EP PG)

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.

"expert_all_gather" and "expert_all_to_all" never overlap. so they can share one lane.

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.

Done

Estimate the compiled step's independent GPU stream count from the
training config and set GPU_MAX_HW_QUEUES to the next power of two
before HIP init. Ignore if the env var is already set.
@rishisinhanj
rishisinhanj force-pushed the graphtrainer/rocm-hw-queues-autoset branch from bb4406b to a2fd5c3 Compare August 3, 2026 22:12
Move GPU_MAX_HW_QUEUES setup out of train.py and run it before
super().__init__() so it stays before HIP init while keeping the
logic scoped to graph_trainer.
@sanketpurandare

Copy link
Copy Markdown
Contributor

@rishisinhanj is this only needed for graph trainer or also needed for the main torchtitan trainer?

@rishisinhanj

rishisinhanj commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@rishisinhanj is this only needed for graph trainer or also needed for the main torchtitan trainer?

@sanketpurandare This PR was intended only for Graphtrainer, as we have only validated the improvement for graph trainer and tested with GT workloads specifically so I've added it as a hook in the graph trainer trainer file. I think it is also valid to add to the regular torchtitan, but that is a bigger change. If you think it is worthwhile, I can change where it is being called as the underlying logic would remain identical.

@sanketpurandare

Copy link
Copy Markdown
Contributor

@rishisinhanj can you check the failing CI tests?

@rishisinhanj

rishisinhanj commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@sanketpurandare We have observed that failure in our PR to reenable the ROCm CI in the past. I have linked an issue here with our findings: #4064 -> it enters a deadlock due to an SPMD race

It's unrelated to this PR and any changes it makes so shouldn't affect merge status.

return 1 << (n - 1).bit_length()


def maybe_set_gpu_max_hw_queues(config: JobConfig) -> None:

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.

if it's for rocm only, at least the function name and file name should reflect that? o/w it could be confusing to cuda users what this is doing.

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

We had some internal discussion about this, we understand that the abstraction that rocm has requires that we need to know the number of unique streams so that the hw queues can be set accordingly. The part that we are unhappy is that we are introducing hw dependent if-else style code in torchtitan. I would recommend instead adding a section in README on how to set the env variable GPU_MAX_HW_QUEUES based on parallelism configs. Does that sound okay?

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

we don't have Rocm CI for graph trainer, and ROCM CI is failing for main for a long time...

@yuankaichen-amd

Copy link
Copy Markdown
Contributor

We had some internal discussion about this, we understand that the abstraction that rocm has requires that we need to know the number of unique streams so that the hw queues can be set accordingly. The part that we are unhappy is that we are introducing hw dependent if-else style code in torchtitan. I would recommend instead adding a section in README on how to set the env variable GPU_MAX_HW_QUEUES based on parallelism configs. Does that sound okay?

Thanks for the review! Moving the whole logic to README is messy, as it is not as concise and direct as it is run by the code.

In many places of the pytorch ecosystem, we have hip/rocm specific code. I think it is fine as long as the function names clearly indicate it is for AMD platforms (as Tianyu commented in this PR).

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

Labels

ciflow/rocm ciflow/8gpu CLA Signed This label is managed by the Meta Open Source bot. module: rocm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants