Skip to content

fix(ple): add rocm disk wait-sync support - #776

Merged
jason-fxz merged 3 commits into
FlashML-org:mainfrom
LZ-QWQ:fix/rocm-ple-wait-sync-review-20261020
Oct 23, 2026
Merged

jason-fxz merged 3 commits into
FlashML-org:mainfrom
LZ-QWQ:fix/rocm-ple-wait-sync-review-20261020

Conversation

@LZ-QWQ

@LZ-QWQ LZ-QWQ commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Related: #697

ROCm disk PLE currently uses gate fallback. This PR adds HIP wait-sync and prevents deadlocks when submitting large graphs with wait-sync.

It adds HIP signal memory, stream memops and capture probing (auto probes; wait requires support). Use HIP signal memory for Engram's wait-sync flag.

#697 uses early cuGraphUpload to avoid the first-launch deadlock on CUDA. In our ROCm 10.1.0 large-graph checks, hipGraphUpload followed by stream synchronization did not prevent the deadlock. This may be related to the current HIP upload implementation: ihipGraphUpload returns after handle validation and stream resolution, while hipGraphUpload still marks preparatory work as TODO. To avoid this deadlock, this PR starts a HIP-only CPU worker before replay to fill PLE rows and signal the flag independently of graph submission.

CUDA's existing allocation, execution and synchronization logic is unchanged.

Validation

The baseline uses main's unchanged production code, with the new 16,384-kernel test included in the test suite.

Test files:

  • tests/kernels/test_row_store.py
  • tests/models/qwen4_exp/test_ple_disk.py
  • tests/models/deepseek_v41/test_engram.py
GPU Runtime Main + test PR
RTX4090 CUDA 13.0 18 passed 18 passed
W7900D ROCm 10.1.0 15 passed, 3 skipped 18 passed, 0 skipped
R9700_x8 ROCm 10.1.0 15 passed, 3 skipped 18 passed, 0 skipped

The new regression and two existing Engram worker cases now run on ROCm.

Performance reference

Qwen/Qwen3.8-Flash-Next-FP8, 256 input / 128 output tokens, single GPU/request, MoE offload cache 1024, graph batch 1, ROCm 10.1.0. Compare main/gate, PR/gate and PR/wait under identical settings.

GPU Metric Main/gate PR/gate PR/wait Wait vs PR gate
R9700_x8 Decode tok/s 17.190 17.172 19.365 +12.77%
R9700_x8 Request (s) 10.497 10.465 9.562 -8.63%
W7900D Decode tok/s 12.676 12.665 12.780 +0.91%
W7900D Request (s) 14.529 14.542 14.445 -0.67%

Each configuration was tested in two independent runs, with three measurements after warmup per run. The table reports the average of the two run medians.

(void)hipStreamDestroy(stream);
}
(void)hipGetLastError();
TORCH_CHECK(rc == hipSuccess || rc == hipErrorNotSupported,

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.

In auto mode a failed probe should fall back to launch-gating, as the CUDA branch of probe_wait_sync and GpuSync::probe in cpu_moe/gpu_sync_hip.cpp do. This check only accepts hipErrorNotSupported, but clr reports most capability failures as hipErrorInvalidValue (ihipStreamOperation: memory that is not signal memory, pointer lookup failure). So on such a runtime the engine fails to start, where gate would have worked. wait mode already raises when the probe returns false, so the probe itself can return false on any error.

GpuSync::probe also does two more checks that would help here:

  • it reads hipDeviceAttributeCanUseStreamWaitValue first;
  • a host thread releases the WAIT after the launch. Here the graph writes 7 and then waits for >= 7, so the WAIT is always satisfied by the graph itself. The probe never checks that a host store into signal memory releases a device WAIT, which is the path the fill relies on.

self._worker.shutdown(wait=True)
raise

def close(self) -> None:

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.

close() and _closed have no caller, in the engine or in the tests, so _closed is never true. Can they be dropped?

Same for the return value at L244: the HIP branch of forward_host_ctx doesn't use deferred (it awaits in its finally), so _start_fill could return None.

histories = [self._ple_context(r.input_ids, r.device_len - 1) for r in reqs]

@torch.inference_mode()
def _complete() -> None:

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 _complete is the same as the CUDA one at L315, except that histories is computed before the submit. Could both share one helper, e.g. _fill_decode(histories, bs)? Then the readback wait, the fill and the signal-on-error sequence live in one place.

Comment thread python/freetoken/models/qwen4_exp/ple_disk.py Outdated
PleStore = _row_store.PleStore


def alloc_sync_flag() -> torch.Tensor:

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.

Nit: the module docstring (L6-7) still says the captured lookup WAITs on a pinned flag. With this function it is pinned on CUDA and HIP signal memory on ROCm.

@LZ-QWQ

LZ-QWQ commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Addressed all five review comments in two commits. All 18 tests across the three test files pass on RTX4090, W7900D, and R9700, including the 16,384-kernel regression.

Performance rechecks on W7900D and R9700 were broadly consistent with the previous PR revision.

@jason-fxz jason-fxz 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

@jason-fxz
jason-fxz merged commit 32056cf into FlashML-org:main Oct 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants