Repository navigation
[hardware, perf, training_utils] feat: add a profiler_markers() platform hook and share the attention padding helpers - #7917
Conversation
… fallback mechanisms
…reduce_avg support checks
… function imports
…and platform detection
Builds this repo (verl-hardware-plugin) as the container's build context, cloning verl-core in at a pinned ref (VERL_GIT_REPO/VERL_REF) rather than requiring a local verl checkout — the intended user already has this plugin checked out and wants a known-good verl paired with it. VERL_GIT_REPO/VERL_REF default to kahlun/verl:intel-xpu-plugin-e2e-vllm until verl-project/verl#7917 (the PlatformBase hooks this plugin's platform_xpu.py/profilers/ override) merges upstream. No vendor has previously shipped a Dockerfile in this repo; verl-core's docker/ascend and docker/rocm use verl-core as build context instead, which doesn't fit a plugin that isn't built into verl-core.
install_guidance.md said nothing about the fact that is_reduce_avg_supported/attention_utils_module/profiler_markers/ dist_profiler_cls only take effect on a verl-core build that includes the still-unmerged verl-project/verl#7917 - against stock main they silently no-op instead of erroring. Also add the missing VTune row to quick_start.md's feature matrix.
PlatformXPU already implements is_reduce_avg_supported() / attention_utils_module() / profiler_markers() / dist_profiler_cls(), but against an unpatched verl core nothing calls them -- those call sites only exist as part of verl-project/verl#7917, which hasn't merged. This shim teaches verl core to consult get_platform() for each of the four, without touching verl core, so the plugin no longer has to wait on that PR. Each patch is feature-detected against PlatformBase and becomes a no-op once the real hook lands upstream. Verified against real PyPI verl (0.9.1, confirmed to genuinely lack all four hooks) in an isolated venv: attention_utils_module dispatch, profiler_markers rebinding, dist_profiler_cls (both a registered tool and an unrecognized one falling back to NoOp), the all_reduce(op=AVG) fallback, and the apply_fsdp2 tail logic against a real fully_shard()-wrapped model (forward+backward completed cleanly). Known gap: the vLLM/SGLang/TRT-LLM rollout servers null out profiler_config before this shim's DistProfiler patch ever runs, so a plugin profiler tool used for *rollout*-side profiling specifically still needs those three files touched directly, or #7917 itself.
| ``set_force_sum_reduction_for_comms`` should also be enabled when this | ||
| is ``False``, if the platform supports FSDP2. | ||
| """ | ||
| return True |
There was a problem hiding this comment.
If XPU doesn't support ReduceOp.AVG, then how do you do gradient reduce_scatter and all_reduce in HSDP?
https://github.com/pytorch/pytorch/blob/v2.13.0/torch/distributed/fsdp/_fully_shard/_fsdp_collectives.py#L846
There was a problem hiding this comment.
On coverage — the fix is validated on FSDP2 with a single-mesh setup (fsdp_size=-1): 6 runs with it and 6 without, back-to-back on the same node, clean pass every time with it and the same failure every time without. What that doesn't cover is the 2D mesh, so the all-reduce leg is from reading the source rather than measured. I'd rather keep this PR to the platform hooks themselves, since they're device-neutral and every default preserves current CUDA behavior. The XPU-side validation is a separate change, and I'll send it as its own PR: a 2×2 mesh run, plus a CPU/gloo test asserting SUM on both a 1D and a 2D mesh so it's covered in CI without needing XPU hardware.
There was a problem hiding this comment.
From intel/torch-xpu-ops#3020, I think XCCL do support ReduceOp.AVG but seems to have double-division bug? If this bug still exists, it may also affect FSDP gradient reduce_scatter and all_reduce in 1/2D mesh, so disable it in verl side alone is not enough. Please check whether this bug has been fixed.
There was a problem hiding this comment.
Hi, pytorch xpu ops 3020 fixed. It is fixed, when using pytorch2.13 will not encounter average divison bug.
the Average that i mention not support is more of the AVG Reduction in Reduce (Scale-up & Scale-out)
this bug will be fix in oneccl2022.2 = pytorch2.15.
but for the FSDP gradient reduce_scatter and all_reduce in 1/2D mesh question or concern, i will put in the comment
On HSDP coverage: you're right that it wasn't covered, and running it surfaced a real bug — but it's not a Ray bug, and I want to be precise about what's proven versus what isn't.
Root cause: worker.py:273 only pins a device with set_device() when a RAY_EXPERIMENTAL_NOSET_* env var happens to be set. In the default config it never pins at all — it silently assumes Ray's visibility mask has already relabeled devices so index 0 is correct. That assumption doesn't hold on XPU (a separate torch-xpu issue discards the mask after lazy init), so every rank lands on the same physical GPU. Ray itself isn't at fault here — I checked ray-project/ray#64440, it's correct.
I have a fix on a branch (not yet in this PR): pin unconditionally, and resolve the index against what the process can actually see rather than trusting the mask blindly. Verified on real 4x B60 hardware against verl's own create_device_mesh() / collective path — 1 of 4 distinct GPUs before the fix, 4 of 4 after, 0 AVG failures across both 1-D and 2-D meshes.
What I haven't yet gotten to complete: a full real GRPO training run under this PR's exact colocated-RM + FSDP2 + transfer_queue configuration hit a separate, unrelated stall — verl's shared-memory weight-transfer fallback (BucketedWeightSender, used because XPU doesn't support CUDA-style IPC) deterministically stalls partway through sending weights into the rollout engine. Reproduced twice, same byte offset both times, independent of oneCCL env settings, so it isn't a flaky resource issue. Separate bug, still open, will report on its own.
So: the device-pinning fix is verified correct at the mechanism level — real hardware, real verl code path, not a mock. A full end-to-end HSDP training run under this specific config is still blocked by that second, unrelated issue.
I will change the PR description:
narrow the claim from "xccl does NOT support ReduceOp.AVG" to the accurate version — oneCCL implements AVG on its SYCL-kernel path but not its scheduler path, path selection is internal and not capability-aware, so a framework can't guarantee an AVG request will be serviceable at runtime. Forcing SUM is the only way to guarantee it for pre oneccl 2022.2
|
the full description for the question on, quite lengthy or detail Checked #3020 — it's real, and it's fixed on the stack I'm running. It's AVG double-division on So I measured your case instead of reasoning from the source. 4 ranks under
fp32; a surviving double division would show as There is a second, distinct failure, and it isn't a wrong number — it's a hard abort: On "disabling it in verl alone is not enough" — agreed on the principle, and that's why the change What I'll change in the PR: narrow the claim from "xccl does NOT support ReduceOp.AVG" to the On size coverage, since #3020 was size-dependent: separately from FSDP I ran raw if want the run log, i can put in gist if requested |
… hooks Per verl-hardware-plugin#26, the maintainer's ruling (2026-09-23) is to default new hardware backends to plugin-side monkeypatching, reserving new PlatformBase hooks for genuine hardware-agnostic blockers. XPU's reduce_avg workaround doesn't need one: - FSDP2's per-layer set_force_sum_reduction_for_comms() can be applied from the plugin's own EngineRegistry-registered engine, after super().initialize() returns -- ordinary method override, no core hook. - The remaining raw dist.all_reduce(op=AVG) call sites can be monkeypatched from the plugin (torch.distributed.all_reduce is looked up by attribute at call time, so patch order doesn't matter). See verl-hardware-plugin's experiment/xpu-monkeypatch-reduce-avg-attention branch for the plugin-side implementation. Keeps attention_utils_module()/profiler_markers()/dist_profiler_cls() -- those don't have a plugin-only alternative.
…odule fix Per verl-hardware-plugin#26, the maintainer's ruling (2026-09-23) is to default new hardware backends to plugin-side monkeypatching, reserving new verl-core PlatformBase hooks for genuine hardware-agnostic blockers. The xccl reduce_avg workaround doesn't need one -- it's already applied entirely from this plugin's own fsdp_xpu.py engine (self.module. set_force_sum_reduction_for_comms(True) after super().initialize()), with no PlatformBase hook involved. Dropping is_reduce_avg_supported() here so this PR only asks verl-project/verl#7917 for the two hooks XPU genuinely can't provide on its own: attention_utils_module() and the profiler hooks. Keeps the self.model -> self.module fix in fsdp_xpu.py -- that's an independent, hardware-confirmed bug (FSDPEngineWithLMHead/WithValueHead expose the wrapped model as self.module, never self.model) that has nothing to do with the hook-vs-monkeypatch question. Follow-up plugin-side monkeypatch work for the remaining raw dist.all_reduce(op=AVG) call sites lives on experiment/xpu-monkeypatch-reduce-avg-attention.
…atch attention_utils_module() is staying a PlatformBase hook (see PR #22, and verl-project/verl#7917's updated description) -- it passed the maintainer's bar on verl-hardware-plugin#26 for a genuinely hardware-agnostic capability that couldn't be done cleanly from the plugin alone. Carrying a monkeypatch alternative for it here too just duplicates an already-accepted, working solution with no benefit. is_reduce_avg_supported() didn't pass that bar and is this branch's actual job: the xccl AVG gap can be fully handled from the plugin (FSDP2 via an engine-level method override, the 3 remaining raw dist.all_reduce(op=AVG) sites via a scoped, documented monkeypatch). Renamed the design doc to match (xpu-monkeypatch-reduce-avg.md) and folded in a note that this branch is now the sole owner of the fsdp_xpu.py self.model->self.module fix -- it's been dropped from #22's smaller, root-only version of the same fix now that #22 no longer touches this file for any other reason.
The lazy-resolving wrappers for mark_start_range/mark_end_range/ mark_annotate/marked_timer had no docstrings, failing the validate_imported_docs.py sanity check.
…cher test_fsdp_ckpt.py and test_activation_offload.py still had the old if cuda/elif npu branch for unpad_input that test_transformer.py and test_transformers_ulysses.py were already updated to route through attention_utils.py's dispatcher. On any other platform (e.g. XPU), neither branch matched and unpad_input was never bound, raising a NameError.
|
@wuxibin89 following up on our thread above — since your last comment I've This PR is now down to two hooks: attention_utils_module() and |
## Open question for reviewers, up front `reduce_avg_allreduce_patch.py` patches `torch.distributed.all_reduce` when `op=AVG` — the only mechanism available for verl-core's 3 raw `dist.all_reduce(op=ReduceOp.AVG)` call sites, since there's no per-site wrapper to hook into and this plugin doesn't own those call sites. It's scoped to `dist.get_backend(group) == "xccl"`, so a gloo/nccl group in the same process (e.g. a CPU-only Ray actor's coordination group alongside a GPU worker's xccl group) is untouched and keeps native `AVG` — this was originally unscoped (any group, any backend) and has been fixed to check the backend per call. What's left: anyone reading verl-core's source still can't see, from that source alone, that an xccl group's `ReduceOp.AVG` silently becomes SUM+divide, for as long as the process runs (there's no narrower call-boundary available — see Changes below for why). That's a real design smell, not a clean win, and is the one piece here still worth a narrow core-hook ask if that's judged unacceptable — happy to split it into its own PR against #7917 if so. ## Summary Plugin-side replacement for `is_reduce_avg_supported()` — implemented with **zero `verl-core` changes**: no new `PlatformBase` hook, no dependency on [verl-project/verl#7917](verl-project/verl#7917). ## Motivation [verl-hardware-plugin#26](#26) asked maintainers whether Intel XPU should extend `PlatformBase` in `verl-core` (the TPU precedent) or stay entirely inside this plugin via monkeypatch (the MLU precedent). The maintainer's answer (2026-09-23): default to the plugin, keep #7917 narrowed to only what truly can't be done from here. `is_reduce_avg_supported`/`all_reduce_avg` has just been dropped from #7917 and from [#22](#22) on that basis -- the underlying reduce_avg problem doesn't need a core hook to work around. (Correction from an earlier version of this PR: the reason isn't "xccl doesn't implement `ReduceOp.AVG`" -- per [this review-thread investigation](verl-project/verl#7917 (comment)) on real 4xB60 hardware, oneCCL implements `AVG` on its SYCL-kernel path but not its scheduler path, and path selection is internal and not capability-aware, so an `AVG` request can abort mid-collective rather than reliably succeed or fail. Fix tracked for oneCCL 2022.2 / torch 2.15. Same mechanism below, corrected reasoning.) This PR is the plugin-side replacement. `attention_utils_module()` and the profiler hooks passed the maintainer's bar and stay as `PlatformBase` hooks — implemented for XPU in #22, which this PR does not touch or duplicate. (An earlier draft of this branch also monkeypatched attention as a "how far can pure monkeypatch go" comparison; dropped once it was clear the hook was staying and a plugin-only alternative for it served no purpose.) ## Changes - `verl_hardware_plugin/engines/fsdp_xpu.py`: extend the existing (non-hook, non-patch) `initialize()` override to call `set_force_sum_reduction_for_comms(True)` on the FSDP2 root **and every individually-wrapped transformer layer**, not just the root — each `fully_shard()`'d module has independent FSDP state. This is ordinary method-override behavior on the plugin's own `EngineRegistry`-registered engine, not a monkeypatch, so it's robust regardless of import order. Also fixes an unrelated `self.model`→`self.module` attribute bug in the same function (`FSDPEngineWithLMHead`/`WithValueHead` expose the wrapped model as `self.module`, never `self.model`) — #22 had picked up the same fix independently since it lives in the same lines; dropped from #22 in favor of this PR's superset version (root + per-layer, vs. root-only). - `verl_hardware_plugin/patches/reduce_avg_allreduce_patch` (new): monkeypatches `torch.distributed.all_reduce(op=AVG)` → `SUM` + manual divide, scoped to `xccl` process groups (`dist.get_backend(group) == "xccl"`), for the three remaining raw `dist.all_reduce(op=ReduceOp.AVG)` call sites in verl-core (`sft_trainer.py`, `engine_workers.py`, `profiler/performance.py::reduce_timing`). See the open question above. `async_op=True` call sites are intentionally left unpatched (falls through to original behavior) rather than risk returning a wrong result before the collective completes. - `verl_hardware_plugin/patches/__init__.py` (new): plain package docstring, no code — holds `reduce_avg_allreduce_patch` as the one patch module. No generic `apply_all()` aggregator: it had exactly one caller and one module, so it wasn't earning its keep (see the platform_xpu.py bullet below). - `verl_hardware_plugin/platforms/platform_xpu.py`: `PlatformXPU.__init__` calls `reduce_avg_allreduce_patch.apply()` directly. **Deliberately not called from the shared `verl_hardware_plugin/__init__.py`** (that file is common to every vendor this plugin supports) — `PlatformXPU()` is only constructed once `verl.plugin.platform.platform_manager._create_platform()` has actually selected `"intel"` as the platform for this process. Calling it from the shared top-level `__init__.py` instead would fire on any host where `torch.xpu` merely happens to be importable, even if a different platform ends up selected for the run — e.g. a mixed host with `VERL_PLATFORM=nvidia` explicitly set would still get its process-wide `all_reduce` semantics changed for no reason. (Two earlier drafts of this PR had this wired the wrong way, then routed through an unnecessary same-package `apply_all()` wrapper — both fixed.) ## Testing - [x] `pytest tests/ -v` passes (76 passed), including new coverage: - `PlatformXPU()` instantiation triggers `reduce_avg_allreduce_patch.apply()`; the shared plugin `__init__.py` no longer binds any patch call directly (regression guard against re-introducing the wrong wiring). - `reduce_avg_allreduce_patch`: `op=AVG` on an `xccl` group → `SUM` + divide by world size; `op=AVG` on a non-`xccl` group (e.g. `gloo`) passes through untouched; other ops pass through unchanged; `async_op=True` + `AVG` falls through unpatched; no-op when XPU unavailable; idempotent (calling `apply()` twice doesn't double-wrap). - [x] Manually verified on target hardware (B60): What's new *here* is the method-override framing - `reduce_avg_allreduce_patch.py`'s actual collective behavior is unit-tested ## Acceptance Baseline (for new hardware adaptation PRs) - [ ] Ran `scripts/baseline_grpo_gsm8k.sh` on target hardware (8 devices) - [ ] Training completed all epochs without error - [ ] `critic/rewards/mean` shows clear upward trend in first 100 steps - [ ] Curve is consistent with NVIDIA reference ## Checklist - [x] Code follows the project's style and passes `pre-commit` checks - [x] Documentation updated — folded into this description rather than a `docs/design/` file; this repo has no precedent for keeping RFC/experiment-verdict docs in the merged tree - [x] No secrets or credentials included --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
| from verl.utils.npu_flash_attn_utils import index_first_axis, pad_input, rearrange, unpad_input | ||
| elif plugin_module is not None: | ||
| mod = importlib.import_module(plugin_module) | ||
| index_first_axis, pad_input, rearrange, unpad_input = ( |
There was a problem hiding this comment.
verl.utils.npu_flash_attn_utils copy attention utils from Dao-AILab/flash-attention, can XPU reuse it?
| omega_profiler_config = config.ref.get("profiler", {}) | ||
|
|
||
| profiler_config = omega_conf_to_dataclass(omega_profiler_config, dataclass_type=ProfilerConfig) | ||
| if omega_profiler_config.get("tool", None) in ["npu", "nsys", "torch", "torch_memory", "precision_debugger"]: |
There was a problem hiding this comment.
Can we unify this as below? Then we can avoid add a new dist_profiler_cls to PlatformBase:
if omega_profiler_config.get("tool", None) is not None:
tool_config = omega_conf_to_dataclass(
omega_profiler_config.get("tool_config", {}).get(omega_profiler_config.get("tool"))
)
|
Thanks for narrowing this down. After looking at how verl-hardware-plugin#22 actually implements these hooks, I think both 1.
Please keep the call-site changes ( 2.
Side note on That would reduce this PR to |
… hooks to profiler_markers() Applies wuxibin89's review of verl-project#7917. Three changes, in the order they were raised: 1. Drop `PlatformBase.attention_utils_module()`. `npu_flash_attn_utils` is already verl-core and imports nothing but torch/F/einops, so a core->core indirection through the plugin bought nothing. `_get_attention_functions()` now uses it whenever `flash_attn` will not import, the duplicated `_fallback_*` helpers are deleted, and the module is renamed to the device-neutral `attention_padding_utils`. The `torch_functional.py` and test call-site cleanups are kept. 2. Drop `PlatformBase.dist_profiler_cls()`. `engine_workers.py` now converts `tool_config` for any configured tool instead of an allowlist -- `omega_conf_to_dataclass` returns None for a missing/empty config, so a tool with no `tool_config` entry still works and a plugin tool can name its own dataclass via `_target_`. The three rollout-server edits are reverted: `profiler_controller` there only gates, the real profiling goes through `engine.start_profile()`, so a plugin profiler's start/stop never ran. 3. Keep `profiler_markers()`, and fix a bug in it. Resolution checked `is_nvtx_available()` first, but nvtx ships in the `verl-core` extra, so on any box where it imports the hook was dead. The platform is now asked first, falling back to nvtx, then mstx, then the generic markers.
…rface Tracks the rework of verl-project/verl#7917, which cuts the PlatformBase additions down to `profiler_markers()` alone. - Drop `attention_utils_module()`. Nothing is needed from the plugin here: verl core now routes every backend without a `flash_attn` build through its own pure-PyTorch `attention_padding_utils` (the former `npu_flash_attn_utils`, renamed device-neutral), which is the same bert_padding port this hook was pointing at. - Drop `dist_profiler_cls()`. `VtuneProfiler` is now registered for `profiler.tool: vtune` by a monkeypatch over `DistProfiler.__init__` (profilers/register_vtune.py) instead of a core hook. verl core has no profiler registry, so the hook was adding a second dispatch mechanism alongside its hardcoded if/elif; per the ruling on #26 a plugin-side monkeypatch is the default for this kind of thing. The patch is double-gated: applied from `PlatformXPU.__init__` rather than at import time, and behind a live `torch.xpu.is_available()` check, because verl's platform auto-detection instantiates every registered platform to probe it -- construction alone does not mean XPU was selected. - Keep `profiler_markers()`. Core's module-level markers are imported by the trainer at module scope, so there is no plugin-side seam for them. Docs updated: only one hook now depends on an unmerged verl-core PR, and the `tool_config` allowlist lift in engine_workers.py is called out as the other thing #7917 is needed for.
… hooks to profiler_markers() Applies wuxibin89's review of verl-project#7917. Three changes, in the order they were raised: 1. Drop `PlatformBase.attention_utils_module()`. `npu_flash_attn_utils` is already verl-core and imports nothing but torch/F/einops, so a core->core indirection through the plugin bought nothing. `_get_attention_functions()` now uses it whenever `flash_attn` will not import, the duplicated `_fallback_*` helpers are deleted, and the module is renamed to the device-neutral `attention_padding_utils`. The `torch_functional.py` and test call-site cleanups are kept. 2. Drop `PlatformBase.dist_profiler_cls()`. `engine_workers.py` now converts `tool_config` for any configured tool instead of an allowlist -- `omega_conf_to_dataclass` returns None for a missing/empty config, so a tool with no `tool_config` entry still works and a plugin tool can name its own dataclass via `_target_`. The three rollout-server edits are reverted: `profiler_controller` there only gates, the real profiling goes through `engine.start_profile()`, so a plugin profiler's start/stop never ran. 3. Keep `profiler_markers()`, and fix a bug in it. Resolution checked `is_nvtx_available()` first, but nvtx ships in the `verl-core` extra, so on any box where it imports the hook was dead. The platform is now asked first, falling back to nvtx, then mstx, then the generic markers.
[hardware, perf, training_utils] move_attn to device agnostic and handle profiler_markers
…rface Tracks the rework of verl-project/verl#7917, which cuts the PlatformBase additions down to `profiler_markers()` alone. - Drop `attention_utils_module()`. Nothing is needed from the plugin here: verl core now routes every backend without a `flash_attn` build through its own pure-PyTorch `attention_padding_utils` (the former `npu_flash_attn_utils`, renamed device-neutral), which is the same bert_padding port this hook was pointing at. - Drop `dist_profiler_cls()`. `VtuneProfiler` is now registered for `profiler.tool: vtune` by a monkeypatch over `DistProfiler.__init__` (profilers/register_vtune.py) instead of a core hook. verl core has no profiler registry, so the hook was adding a second dispatch mechanism alongside its hardcoded if/elif; per the ruling on #26 a plugin-side monkeypatch is the default for this kind of thing. The patch is double-gated: applied from `PlatformXPU.__init__` rather than at import time, and behind a live `torch.xpu.is_available()` check, because verl's platform auto-detection instantiates every registered platform to probe it -- construction alone does not mean XPU was selected. - Keep `profiler_markers()`. Core's module-level markers are imported by the trainer at module scope, so there is no plugin-side seam for them. Docs updated: only one hook now depends on an unmerged verl-core PR, and the `tool_config` allowlist lift in engine_workers.py is called out as the other thing #7917 is needed for.
|
@wuxibin89 thanks — all three points are done in 72e089d. The PR is now 13 files, +369/−112, and 1. 2. 3.
TestsCPU-only container, The 2 skips are the nvtx and mstx fallback branches — I also checked the cross-repo behaviour against verl-hardware-plugin#22, including the case your side note is about: edited <4.20pm, for update the commit ID> |
…n fork verl-project/verl#7917 (the PlatformBase profiler_markers() hook and shared attention padding helpers this plugin builds on) merged to verl-core main on 2026-10-08, which was the condition this Dockerfile's comment set for dropping the kahlun/verl:intel-xpu-plugin-e2e-vllm fork default. No release tag contains the merge yet (v0.9.1 predates it), so pin to main rather than a tag until one ships it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
## Summary Adds Intel VTune (ITT) profiling support for Intel XPU, plus the XPU user guide that fills the stub `docs/accelerators/xpu/README.md`. After the rework described below, this PR implements exactly **one** `PlatformBase` hook — `profiler_markers()` — and does everything else from the plugin side: - `PlatformXPU.profiler_markers()` → ITT range markers, so verl core can emit trace ranges without knowing about ITT or XPU. - `VtuneProfiler(DistProfiler)`, selected by `profiler.tool: vtune`, registered by a plugin-side monkeypatch over `DistProfiler.__init__` (`verl_hardware_plugin/accelerators/xpu/profilers/register_vtune.py`) — no core hook. ## What changed since this PR was opened Two rounds of narrowing, both driven by the ruling on [#26](#26) (2026-09-23) that new `PlatformBase` hooks are reserved for capabilities that genuinely can't be done from the plugin: 1. **`is_reduce_avg_supported()` dropped** — the xccl `ReduceOp.AVG` gap doesn't meet that bar. Its plugin-only replacement landed as [#32](#32) (merged): a monkeypatch over `torch.distributed.all_reduce`, scoped to xccl groups. 2. **`attention_utils_module()` and `dist_profiler_cls()` dropped** (2026-10-08, commit `439503c`), following the narrowed hook surface in the rework of [verl-project/verl#7917](verl-project/verl#7917): - `attention_utils_module()`: nothing is needed from the plugin. verl core routes every backend without a `flash_attn` build through its own pure-PyTorch `attention_padding_utils` (the former `npu_flash_attn_utils`, renamed device-neutral) — the same `bert_padding` port this hook pointed at. - `dist_profiler_cls()`: it added a second dispatch mechanism alongside core's hardcoded if/elif, so `VtuneProfiler` is now claimed by the `register_vtune.py` monkeypatch instead. The patch is double-gated — applied from `PlatformXPU.__init__`, not at import time, and behind a live `torch.xpu.is_available()` check, because verl's platform auto-detection instantiates every registered platform to probe it. - `profiler_markers()` is kept: core's module-level markers are imported by the trainer at module scope, so there is no plugin-side seam for them. `verl_hardware_plugin/accelerators/xpu/engines/fsdp_xpu.py` is untouched by this PR. ## Motivation Add in profiling of step annotation for intel gpu ## Changes - `verl_hardware_plugin/accelerators/xpu/platform_xpu.py`: add `profiler_markers()`; apply the VTune registration patch from `__init__` behind a live-device check. - `verl_hardware_plugin/accelerators/xpu/profilers/itt_profile_xpu.py` (new): ITT range markers (`mark_start_range`/`mark_end_range`/`mark_annotate`/`marked_timer`) and `VtuneProfiler(DistProfiler)`. - `verl_hardware_plugin/accelerators/xpu/profilers/register_vtune.py` (new): claims the `vtune` tool name by wrapping `DistProfiler.__init__`. - `verl_hardware_plugin/accelerators/xpu/profilers/__init__.py` (new): docstring note on registration. - `tests/test_plugin_registration.py` (new): hook + registration tests that run on a CPU-only host. - `docs/accelerators/xpu/{install_guidance,quick_start,profiling}.md` (new) and an updated `README.md`. ## verl-core dependency `profiler_markers()` only exists on verl-core builds that include #7917. Against stock `verl-project/verl:main` the plugin still installs and runs, but the hook has no base method to override, so ITT ranges aren't emitted; #7917 also lifts the `tool_config` allowlist in `engine_workers.py`, without which a `tool_config.vtune` entry is dropped. `VtuneProfiler` registration itself is pure monkeypatch and works either way. Use the pinned ref in `docker/intel_gpu/` for a known-good combination until #7917 merges. ## Testing - [x] `pytest tests/` — 199 passed on a CPU-only host (`torch 2.13.0+xpu`), including the new registration tests. Re-run after merging the accelerator reorganization (#41), which moved these modules to `accelerators/xpu/`. - [x] Smoke-checked on 1x Arc Pro B60 against a verl-core build *without* #7917, i.e. the degraded path described above: `PlatformXPU` selected (`xpu`/`intel`/`xccl`); ITT markers execute; `profiler.tool: vtune` gives `DistProfiler._impl = VtuneProfiler` with `start`/`step`/`stop` running, while `tool: torch` still resolves to core's own profiler; and with no #7917 the hook is absent, markers stay generic and the `tool_config` allowlist stays gated — a silent fallback, not an error. - [x] ITT emission is real on this stack, not a stub: `torch.profiler.itt.is_available()` is `True` and `torch._C._itt` exposes `rangePush`/`rangePop`/`mark`. **Not covered:** viewing the ranges in a VTune result. VTune attaches as an external collector, so `profiler_start`/`profiler_stop` are no-ops by design and there is no code path here that only a capture would exercise. It is also not runnable in the Intel GPU image: its oneAPI 2026.1 ships no `vtune` component and no `libittnotify`. Installing the collector is an image change, independent of this PR — written up in `docs/accelerators/xpu/profiling.md` (Prerequisites + Troubleshooting). ## Acceptance Baseline (for new hardware adaptation PRs) - [x] GRPO / PPO / GRPO+colocated-RM / SFT (`tests/special_intel_gpu/*.sh`) completed full training steps on B60 with this branch's platform changes live in the tree - [ ] `critic/rewards/mean` upward trend / NVIDIA-reference curve from `scripts/baseline_grpo_gsm8k.sh` specifically — **TODO: paste the SwanLab/log link, or re-run that script.** **SwanLab or training log link**: TODO ## Checklist - [x] Code follows the project's style and passes `pre-commit` checks - [x] Documentation updated - [x] No secrets or credentials included --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
## Summary Adds Intel VTune (ITT) profiling support for Intel XPU, plus the XPU user guide that fills the stub `docs/accelerators/xpu/README.md`. After the rework described below, this PR implements exactly **one** `PlatformBase` hook — `profiler_markers()` — and does everything else from the plugin side: - `PlatformXPU.profiler_markers()` → ITT range markers, so verl core can emit trace ranges without knowing about ITT or XPU. - `VtuneProfiler(DistProfiler)`, selected by `profiler.tool: vtune`, registered by a plugin-side monkeypatch over `DistProfiler.__init__` (`verl_hardware_plugin/accelerators/xpu/profilers/register_vtune.py`) — no core hook. ## What changed since this PR was opened Two rounds of narrowing, both driven by the ruling on [verl-project#26](verl-project#26) (2026-09-23) that new `PlatformBase` hooks are reserved for capabilities that genuinely can't be done from the plugin: 1. **`is_reduce_avg_supported()` dropped** — the xccl `ReduceOp.AVG` gap doesn't meet that bar. Its plugin-only replacement landed as [verl-project#32](verl-project#32) (merged): a monkeypatch over `torch.distributed.all_reduce`, scoped to xccl groups. 2. **`attention_utils_module()` and `dist_profiler_cls()` dropped** (2026-10-08, commit `439503c`), following the narrowed hook surface in the rework of [verl-project/verl#7917](verl-project/verl#7917): - `attention_utils_module()`: nothing is needed from the plugin. verl core routes every backend without a `flash_attn` build through its own pure-PyTorch `attention_padding_utils` (the former `npu_flash_attn_utils`, renamed device-neutral) — the same `bert_padding` port this hook pointed at. - `dist_profiler_cls()`: it added a second dispatch mechanism alongside core's hardcoded if/elif, so `VtuneProfiler` is now claimed by the `register_vtune.py` monkeypatch instead. The patch is double-gated — applied from `PlatformXPU.__init__`, not at import time, and behind a live `torch.xpu.is_available()` check, because verl's platform auto-detection instantiates every registered platform to probe it. - `profiler_markers()` is kept: core's module-level markers are imported by the trainer at module scope, so there is no plugin-side seam for them. `verl_hardware_plugin/accelerators/xpu/engines/fsdp_xpu.py` is untouched by this PR. ## Motivation Add in profiling of step annotation for intel gpu ## Changes - `verl_hardware_plugin/accelerators/xpu/platform_xpu.py`: add `profiler_markers()`; apply the VTune registration patch from `__init__` behind a live-device check. - `verl_hardware_plugin/accelerators/xpu/profilers/itt_profile_xpu.py` (new): ITT range markers (`mark_start_range`/`mark_end_range`/`mark_annotate`/`marked_timer`) and `VtuneProfiler(DistProfiler)`. - `verl_hardware_plugin/accelerators/xpu/profilers/register_vtune.py` (new): claims the `vtune` tool name by wrapping `DistProfiler.__init__`. - `verl_hardware_plugin/accelerators/xpu/profilers/__init__.py` (new): docstring note on registration. - `tests/test_plugin_registration.py` (new): hook + registration tests that run on a CPU-only host. - `docs/accelerators/xpu/{install_guidance,quick_start,profiling}.md` (new) and an updated `README.md`. ## verl-core dependency `profiler_markers()` only exists on verl-core builds that include #7917. Against stock `verl-project/verl:main` the plugin still installs and runs, but the hook has no base method to override, so ITT ranges aren't emitted; #7917 also lifts the `tool_config` allowlist in `engine_workers.py`, without which a `tool_config.vtune` entry is dropped. `VtuneProfiler` registration itself is pure monkeypatch and works either way. Use the pinned ref in `docker/intel_gpu/` for a known-good combination until #7917 merges. ## Testing - [x] `pytest tests/` — 199 passed on a CPU-only host (`torch 2.13.0+xpu`), including the new registration tests. Re-run after merging the accelerator reorganization (verl-project#41), which moved these modules to `accelerators/xpu/`. - [x] Smoke-checked on 1x Arc Pro B60 against a verl-core build *without* #7917, i.e. the degraded path described above: `PlatformXPU` selected (`xpu`/`intel`/`xccl`); ITT markers execute; `profiler.tool: vtune` gives `DistProfiler._impl = VtuneProfiler` with `start`/`step`/`stop` running, while `tool: torch` still resolves to core's own profiler; and with no #7917 the hook is absent, markers stay generic and the `tool_config` allowlist stays gated — a silent fallback, not an error. - [x] ITT emission is real on this stack, not a stub: `torch.profiler.itt.is_available()` is `True` and `torch._C._itt` exposes `rangePush`/`rangePop`/`mark`. **Not covered:** viewing the ranges in a VTune result. VTune attaches as an external collector, so `profiler_start`/`profiler_stop` are no-ops by design and there is no code path here that only a capture would exercise. It is also not runnable in the Intel GPU image: its oneAPI 2026.1 ships no `vtune` component and no `libittnotify`. Installing the collector is an image change, independent of this PR — written up in `docs/accelerators/xpu/profiling.md` (Prerequisites + Troubleshooting). ## Acceptance Baseline (for new hardware adaptation PRs) - [x] GRPO / PPO / GRPO+colocated-RM / SFT (`tests/special_intel_gpu/*.sh`) completed full training steps on B60 with this branch's platform changes live in the tree - [ ] `critic/rewards/mean` upward trend / NVIDIA-reference curve from `scripts/baseline_grpo_gsm8k.sh` specifically — **TODO: paste the SwanLab/log link, or re-run that script.** **SwanLab or training log link**: TODO ## Checklist - [x] Code follows the project's style and passes `pre-commit` checks - [x] Documentation updated - [x] No secrets or credentials included --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
What does this PR do?
Two independent changes, both making verl's device handling less CUDA-specific.
1. A
profiler_markers()hook onPlatformBase. Lets a hardware backend supply its own tracing markers --(mark_start_range, mark_end_range, mark_annotate, marked_timer)-- instead of nvtx or mstx. Default isNone, so existing platforms are unaffected.This also fixes marker resolution.
verl/utils/profiler/__init__.pycheckedis_nvtx_available()before consulting the platform, and nvtx ships in theverl-coreextra, so on any install where nvtx imports the platform was never asked. Order is now platform, nvtx, mstx, generic, and stays lazy so the platform need not exist at import time.Relatedly,
engine_workers.pyconvertstool_configfor any configuredprofiler.toolrather than an allowlist of the five built-in names.omega_conf_to_dataclassreturnsNonefor a missing or empty config, so a tool with notool_configentry still works, and an out-of-tree tool can name its own dataclass through_target_.2. The attention padding helpers become device-neutral.
npu_flash_attn_utils.pywas already a pure torch/einops port of flash-attn'sbert_padding, with nothing NPU-specific in it, so it is renamedattention_padding_utils.pyand used as the fallback for any install whereflash_attnwill not import -- not just NPU. That covers non-CUDA accelerators and CPU-only installs such as unit tests and dev boxes. The duplicate_fallback_*helpers inattention_utils.pyare deleted, andtorch_functional.py's two remaining hardcodedflash_attn/get_device_name() == "cuda"/"npu"branches now go through the same dispatcher.No public API changes.
Consumed by
verl-hardware-plugin'sPlatformXPU(#22).Why this is not duplicating an existing PR
Searched open and closed PRs for
profiler_markers,PlatformBase hook,marked_timer,mark_start_range,attention_utils,npu_flash_attn_utils,index_first_axis.For the profiler hook, nothing else lets a platform override the markers. #7988 (also mine) adds a different
PlatformBasehook -- an optionaltorch.profileractivity list, i.e. what the built-in torch profiler records, not which marker functions the trainer calls. It overlaps only in touchingplatform_base.py; I will rebase whichever lands second. #6086 (merged) created the platform abstraction both extend.For the attention change, #7098 overlaps substantially and predates this work. It reaches the same conclusion from the same reasoning -- that the NPU copy only uses torch and einops, so dispatching on hardware was unnecessary -- and both PRs delete the dispatch and touch
tests/utils/test_attention_utils.py, so they conflict. The differences are small: #7098 inlines the helpers intoattention_utils.pyand deletes the module, where this PR keeps it asattention_padding_utils.py; #7098 also normalizes indices toint64, which this PR does not. #7098 has the stronger test evidence of the two. I did not see it before implementing, and I am happy to drop part 2 here and review #7098 instead if you would rather land that -- part 1 is independent and does not depend on it.Test
tests/utils/test_profiler_markers.pytests/utils/test_attention_utils.pytests/plugin/test_platform_abstraction.pyThe 2 skips are the nvtx and mstx paths; neither package is installed in this image. Both new unit test files inject a fake platform, so no accelerator is needed.
Against a real out-of-tree platform supplying Intel ITT markers,
_resolve_markers()selects them even withis_nvtx_available()forced toTrue-- the regression the fix targets.profiler.toolresolution was checked for three cases: an out-of-tree tool name resolves to its implementation, a built-in name still reaches core's dispatch, and an unknown name still falls through to_NoOpProfiler.Existing tests touched
Four existing test files change only in routing their
unpad_input/pad_inputimports throughverl.utils.attention_utils-- no logic change:tests/models/test_transformer.pytests/models/test_transformers_ulysses.pytests/utils/test_activation_offload.pytests/special_distributed/test_fsdp_ckpt.pyThey are covered by the existing CUDA/NPU CI (
model.yml,model_ascend.yml). Before this change they failed at import on a device without a flash-attn build (unpad_inputunbound). Results on 4x Intel Arc Pro B60 24GB:test_transformer.py-- 4/4 passedtest_transformers_ulysses.py(torchrun --nproc_per_node=4 -m pytest) -- allsp_size<=4configs pass;sp_size=8needs 8 GPUstest_activation_offload.py-- fsdp2 cases pass; FSDP1 cases hit a Level Zero runtime error, an XPU-side issue unrelated to this changetest_fsdp_ckpt.py-- the default config exceeds 24GB per device; with a reduced batch, fsdp2 save/load round-trips bit-exactlyDesign & Code Changes
verl/plugin/platform/platform_base.pyprofiler_markers(),Noneby defaultverl/utils/profiler/__init__.pyverl/workers/engine_workers.pyverl/utils/npu_flash_attn_utils.py->verl/utils/attention_padding_utils.pyverl/utils/attention_utils.pyflash_attnwill not import; duplicate_fallback_*helpers deletedverl/utils/torch_functional.pytests/utils/test_profiler_markers.py,tests/utils/test_attention_utils.pyChecklist Before Submitting
ci-requestchannel.recipesubmodule — not applicable.