Skip to content

[hardware, perf, training_utils] feat: add a profiler_markers() platform hook and share the attention padding helpers - #7917

Merged
wuxibin89 merged 14 commits into
verl-project:mainfrom
kahlun:intel-xpu-plugin-e2e-vllm
Oct 8, 2026
Merged

wuxibin89 merged 14 commits into
verl-project:mainfrom
kahlun:intel-xpu-plugin-e2e-vllm

Conversation

@kahlun

@kahlun kahlun commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Two independent changes, both making verl's device handling less CUDA-specific.

1. A profiler_markers() hook on PlatformBase. 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 is None, so existing platforms are unaffected.

This also fixes marker resolution. verl/utils/profiler/__init__.py checked is_nvtx_available() before consulting the platform, and nvtx ships in the verl-core extra, 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.py converts tool_config for any configured profiler.tool rather than an allowlist of the five built-in names. omega_conf_to_dataclass returns None for a missing or empty config, so a tool with no tool_config entry 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.py was already a pure torch/einops port of flash-attn's bert_padding, with nothing NPU-specific in it, so it is renamed attention_padding_utils.py and used as the fallback for any install where flash_attn will 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 in attention_utils.py are deleted, and torch_functional.py's two remaining hardcoded flash_attn / get_device_name() == "cuda"/"npu" branches now go through the same dispatcher.

No public API changes.

Consumed by verl-hardware-plugin's PlatformXPU (#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 PlatformBase hook -- an optional torch.profiler activity list, i.e. what the built-in torch profiler records, not which marker functions the trainer calls. It overlaps only in touching platform_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 into attention_utils.py and deletes the module, where this PR keeps it as attention_padding_utils.py; #7098 also normalizes indices to int64, 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

python -m pytest -q tests/utils/test_profiler_markers.py
python -m pytest -q tests/utils/test_attention_utils.py
python -m pytest -q tests/plugin/test_platform_abstraction.py
Target Result
tests/utils/test_profiler_markers.py 2 passed, 2 skipped
tests/utils/test_attention_utils.py 5 passed
tests/plugin/test_platform_abstraction.py 14 passed

The 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 with is_nvtx_available() forced to True -- the regression the fix targets. profiler.tool resolution 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_input imports through verl.utils.attention_utils -- no logic change:

  • tests/models/test_transformer.py
  • tests/models/test_transformers_ulysses.py
  • tests/utils/test_activation_offload.py
  • tests/special_distributed/test_fsdp_ckpt.py

They 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_input unbound). Results on 4x Intel Arc Pro B60 24GB:

  • test_transformer.py -- 4/4 passed
  • test_transformers_ulysses.py (torchrun --nproc_per_node=4 -m pytest) -- all sp_size<=4 configs pass; sp_size=8 needs 8 GPUs
  • test_activation_offload.py -- fsdp2 cases pass; FSDP1 cases hit a Level Zero runtime error, an XPU-side issue unrelated to this change
  • test_fsdp_ckpt.py -- the default config exceeds 24GB per device; with a reduced batch, fsdp2 save/load round-trips bit-exactly

Design & Code Changes

File Change
verl/plugin/platform/platform_base.py profiler_markers(), None by default
verl/utils/profiler/__init__.py resolution order and the nvtx-shadowing fix
verl/workers/engine_workers.py allowlist replaced with an any-tool check
verl/utils/npu_flash_attn_utils.py -> verl/utils/attention_padding_utils.py rename only
verl/utils/attention_utils.py falls back to the padding utils whenever flash_attn will not import; duplicate _fallback_* helpers deleted
verl/utils/torch_functional.py two hardcoded device branches routed through the dispatcher
tests/utils/test_profiler_markers.py, tests/utils/test_attention_utils.py new

Checklist Before Submitting

  • Read the Contribute Guide.
  • Apply pre-commit checks.
  • Add / Update the documentation.
  • Add unit or end-to-end test(s) to the CI workflow. The hardware that exposes the original collision is not in CI; the in-tree platforms are covered by unit tests and the table above documents that their behavior is unchanged.
  • Once your PR is ready for CI, send a message in the ci-request channel.
  • If your PR is related to the recipe submodule — not applicable.

kahlun added a commit to kahlun/verl-hardware-plugin-fork that referenced this pull request Sep 21, 2026
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.
kahlun added a commit to kahlun/verl-hardware-plugin-fork that referenced this pull request Sep 22, 2026
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.
@kahlun kahlun changed the title [draft] enable intel XPU to Verl with plugin mechanism with extra General API abstraction [hardware, util, fsdp, profile] enable intel XPU to Verl with plugin mechanism with extra General API abstraction Sep 22, 2026
@kahlun
kahlun marked this pull request as ready for review September 22, 2026 07:52
kahlun added a commit to kahlun/verl-hardware-plugin that referenced this pull request Sep 23, 2026
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.
Comment thread verl/plugin/platform/platform_base.py Outdated
``set_force_sum_reduction_for_comms`` should also be enabled when this
is ``False``, if the platform supports FSDP2.
"""
return True

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.

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

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.

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.

@wuxibin89 wuxibin89 Sep 23, 2026 •

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.

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.

@kahlun kahlun Sep 23, 2026 •

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.

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

@kahlun

kahlun commented Sep 23, 2026

Copy link
Copy Markdown
Contributor Author

the full description for the question on, quite lengthy or detail

From https://github.com/intel/torch-xpu-ops/issues/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.

Checked #3020 — it's real, and it's fixed on the stack I'm running. It's AVG double-division on
XCCL's small-message protocol (the non-monotonic size pattern in that issue). The fix shipped in
oneAPI DLE 2026.0 rather than in a torch release; torch.version.xpu reports 20260000 on my
image, which is how I can tell the measurements below are on the fixed build. Stack: 4× Arc Pro
B60, torch 2.13.0+xpu, oneCCL 2022.1.

So I measured your case instead of reasoning from the source. 4 ranks under torchrun, one rank
per physical GPU (verified by device uuid), real fully_shard. I wrapped
_get_gradient_divide_factors to record what FSDP2 actually requested, and checked gradients
against a reference that uses no AVG anywhere — each rank's local gradient, ReduceOp.SUM over
gloo on CPU, divided by world size by hand:

mesh ops FSDP2 requested (pre, post, RS, AR) grad ÷ reference
1-D (4,), default None, None, AVG, AVG 1.000000
1-D (4,), force_sum None, 4.0, SUM, SUM 1.000000
2-D (2,2), default None, None, AVG, AVG 1.000000
2-D (2,2), force_sum None, 4.0, SUM, SUM 1.000000

fp32; a surviving double division would show as 1/4 = 0.250000. The AVG and force-SUM arms are
bitwise identical (max|diff| = 0.000e+00) — expected, since world size 4 is a power of two, so
oneCCL's AVG and an explicit post_factor=4.0 are the same arithmetic. Raw
all_reduce/reduce_scatter AVG on the 2-D mesh's own size-2 subgroups is also correct (1.5
against a 3.0 SUM control). So no double division on either leg on either mesh — your reading was
right and my PR description is too broad.

There is a second, distinct failure, and it isn't a wrong number — it's a hard abort:
coll_param.cpp:491 validate: EXCEPTION: average operation is not supported for the scheduler path. oneCCL has two execution paths, and AVG is implemented on the SYCL-kernel path but not on
the scheduler path (confirmed with Intel's oneCCL team). ccl_coll_param::validate() throws
unconditionally when the collective is allreduce/reduce_scatter/reduce and the reduction is avg —
no size threshold and no partial-correctness window, which is how I know it isn't #3020. The
caller doesn't pick the path: you request a reduction and oneCCL selects internally, and that
selection isn't capability-aware, so an AVG request the API accepted can be dispatched to a path
that cannot service it — surfacing mid-collective inside a training step. I'm not claiming the
path choice is wrong; it may be right for the topology. What's missing is capability-awareness in
the dispatch, and I'm following up with Intel to ask for that, or for AVG emulated as
SUM-then-scale on that path.

On "disabling it in verl alone is not enough" — agreed on the principle, and that's why the change
isn't scoped to verl's own AVG call sites. is_reduce_avg_supported() also gates the FSDP2 side,
at fsdp_utils.py:615 in this PR: when it's False, apply_fsdp2 calls
set_force_sum_reduction_for_comms(True) on the root and every fully_shard()'d layer, since
that API doesn't recurse into children. That's the same knob FSDP2 consults for its internal
reduce_scatter/all_reduce, so one flag covers both verl's all_reduce_avg() helper and FSDP's own
collectives — the SUM, SUM rows above are that block firing. Worth being explicit about what the
flag can and can't express: it's static, while oneCCL's path selection happens at runtime, so
False is the only safe answer a capability query can give when the path isn't predictable. It's
also a backend property rather than a device one, and it lives in the out-of-tree XPU platform
class — so when oneCCL's dispatch becomes capability-aware, flipping it back is a one-line change
there with nothing to revert in verl. The test is written and passing, not a follow-up:
tests/utils/test_fsdp2_force_sum_reduction_on_cpu.py, 4 gloo ranks on CPU, parametrised over a
1-D (4,) mesh and a 2-D (2,2) ("ddp", "fsdp") mesh, calling the real apply_fsdp2; it
asserts both legs come back ReduceOp.SUM and that gradients match the default AVG path at
rtol=atol=1e-6. Both parametrisations pass.

What I'll change in the PR: 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, and per the table it's
numerically exact.

On size coverage, since #3020 was size-dependent: separately from FSDP I ran raw all_reduce and
reduce_scatter AVG at numel 0, 1, 8 and 1024 — the 0-dim scalar and size-1 cases that issue
lists as broken — and all return 1.5 against a 3.0 SUM control on two ranks. Double division
would give 0.75. So the small-message region the issue identifies as the risky one is correct on
this stack, and the FSDP runs above cover the gradient path itself up to 65536 elements including
a padded head.


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.
kahlun added a commit to kahlun/verl-hardware-plugin-fork that referenced this pull request Sep 24, 2026
…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.
@kahlun
kahlun marked this pull request as draft September 24, 2026 03:02
@kahlun kahlun changed the title [hardware, util, fsdp, profile] enable intel XPU to Verl with plugin mechanism with extra General API abstraction [hardware, util, profile] enable intel XPU to Verl with plugin mechanism with extra General API abstraction Sep 24, 2026
kahlun added a commit to kahlun/verl-hardware-plugin-fork that referenced this pull request Sep 24, 2026
…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.
@kahlun kahlun changed the title [hardware, util, profile] enable intel XPU to Verl with plugin mechanism with extra General API abstraction [hardware, perf] feat: add PlatformBase hooks for attention utils and profiler markers Sep 24, 2026
…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.
@kahlun
kahlun marked this pull request as ready for review September 25, 2026 07:04
@kahlun

kahlun commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

@wuxibin89 following up on our thread above — since your last comment I've
removed the is_reduce_avg_supported() hook entirely (AVG's reduce_avg
workaround now runs as a plugin-only patch, verl-hardware-plugin#32, per
the direction confirmed in verl-hardware-plugin#26), and
intel/torch-xpu-ops#3020 being fixed covers the double-division concern you
raised — the remaining AVG gap I was describing is specifically the
Reduce (scale-up/scale-out) path, not the FSDP gradient reduce_scatter/
all_reduce path you asked about.

This PR is now down to two hooks: attention_utils_module() and
profiler_markers() (+ dist_profiler_cls() for the rollout-server profiler
allowlist, which can't be done from the plugin). Would appreciate a look
when you have a chance —if something's still blocking, just let me know what it is.
So I can fix it quickly.

@kahlun
kahlun requested a review from wuxibin89 September 30, 2026 07:21
physics31415926 pushed a commit to verl-project/verl-hardware-plugin that referenced this pull request Sep 30, 2026
## 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>
Comment thread verl/utils/attention_utils.py Outdated
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 = (

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.

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"]:

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.

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"))
      )

@wuxibin89

Copy link
Copy Markdown
Collaborator

Thanks for narrowing this down. After looking at how verl-hardware-plugin#22 actually implements these hooks, I think both attention_utils_module() and dist_profiler_cls() can be dropped from PlatformBase.

1. attention_utils_module()

PlatformXPU.attention_utils_module() returns "verl.utils.npu_flash_attn_utils", which is a verl-core module. That file is a pure torch + einops copy of flash-attn's bert_padding.py and has no torch_npu dependency, so nothing in it is NPU-specific. A hook that only points verl-core at another verl-core module isn't needed. Instead, attention_utils._get_attention_functions() can use npu_flash_attn_utils whenever flash_attn can't be imported, replacing the _fallback_* duplicates. Renaming the module to something device-neutral would make that clearer. XPU (and any future backend without flash-attn) then gets the same validated code path with no platform hook.

Please keep the call-site changes (torch_functional.py and the 4 test files routing through verl.utils.attention_utils). Those are real fixes.

2. dist_profiler_cls()

  • engine_workers.py: convert tool_config for any non-None tool, as in my inline comment. omega_conf_to_dataclass returns None for an empty config, so tools without a tool_config entry still work, and a plugin tool can provide its own dataclass through _target_. The hardcoded allowlist goes away entirely.
  • Rollout servers (vllm_async_server.py / async_sglang_server.py / trtllm_async_server.py): I'd revert these. There, profiler_controller is only used for gating (check_enable / check_this_rank / is_discrete_mode). Actual profiling goes through the engine's own profiler (engine.start_profile(), configured through build_vllm_profiler_args() from tool_config.contents). A plugin profiler instantiated there never has its start()/stop() called, so allowing vtune through doesn't enable VTune for rollout.
  • DistProfiler dispatch: VtuneProfiler is essentially NsightSystemsProfiler with ITT markers instead of nvtx. start/stop already go through get_platform().profiler_start()/profiler_stop(), which are no-ops on XPU. If XPU really needs its own profiler.tool name, it can be registered from the plugin side, in line with the direction in verl-hardware-plugin#26. It doesn't need a new PlatformBase method.

Side note on profiler_markers() (the remaining hook): _resolve_markers() checks is_nvtx_available() first, and nvtx is a dependency of the verl-core extra. is_nvtx_available() only checks whether the package is importable, not the device. So on an XPU box with verl[verl-core] installed, the platform markers are never used. Asking the platform first and falling back to nvtx/mstx when it returns None keeps CUDA and NPU behavior unchanged.

That would reduce this PR to profiler_markers() plus the attention call-site cleanup.

kahlun added a commit to kahlun/verl that referenced this pull request Oct 8, 2026
… 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.
kahlun added a commit to kahlun/verl-hardware-plugin-fork that referenced this pull request Oct 8, 2026
…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.
kahlun added 2 commits October 8, 2026 04:59
… 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
kahlun added a commit to kahlun/verl-hardware-plugin-fork that referenced this pull request Oct 8, 2026
…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.
@kahlun kahlun changed the title [hardware, perf] feat: add PlatformBase hooks for attention utils and profiler markers [hardware, perf] feat: add a profiler_markers() plugin hook to PlatformBase Oct 8, 2026
@kahlun kahlun changed the title [hardware, perf] feat: add a profiler_markers() plugin hook to PlatformBase [hardware, perf] feat: add a profiler_markers() plugin hook to PlatformBase and device-agnostic for non megatron for attention util Oct 8, 2026
@kahlun kahlun changed the title [hardware, perf] feat: add a profiler_markers() plugin hook to PlatformBase and device-agnostic for non megatron for attention util [hardware, perf, training_utils] feat: add a profiler_markers() platform hook and share the attention padding helpers Oct 8, 2026
@kahlun

kahlun commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

@wuxibin89 thanks — all three points are done in 72e089d. The PR is now 13 files, +369/−112, and platform_base.py is a single +14 hook.

1. attention_utils_module() — dropped. The five _fallback_* duplicates are deleted and verl/utils/attention_utils.py falls back to that module whenever flash_attn cannot be imported. Renamed it verl/utils/attention_padding_utils.py per your suggestion. Call-site changes in torch_functional.py and the tests kept.

2. dist_profiler_cls() — dropped. engine_workers.py:492 unified exactly as in your inline comment; the allowlist is gone. Rollout-server edits reverted. The profiler class is registered from the plugin side instead.

3. profiler_markers() — platform first, then nvtx → mstx → generic. Resolved lazily on first marker use, so importing the package does not probe hardware.

is_reduce_avg_supported() is no longer in this PR at all, so that thread is moot on the core side.

Tests

CPU-only container, PYTHONPATH pointed at this branch:

$ python -m pytest tests/utils/test_attention_utils.py
5 passed, 10 warnings in 6.70s

$ python -m pytest tests/utils/test_profiler_markers.py
2 passed, 2 skipped, 10 warnings in 7.36s

$ python -m pytest tests/plugin/test_platform_abstraction.py
14 passed, 12 warnings in 6.72s

The 2 skips are the nvtx and mstx fallback branches — importorskip on nvtx / torch_npu, neither of which is in that image. They should run in CI, since nvtx comes with the verl-core extra.

I also checked the cross-repo behaviour against verl-hardware-plugin#22, including the case your side note is about:

[PASS] before patch, tool='vtune' -> core no-op -> _NoOpProfiler
[PASS] after patch, tool='vtune' -> VtuneProfiler
[PASS] builtin tool='torch' untouched -> Profiler
[PASS] unknown tool -> core no-op -> _NoOpProfiler
[PASS] tool with no tool_config entry -> None
[PASS] tool_config with _target_ -> dataclass -> NsightToolConfig
[PASS] ITT markers win over an available nvtx

edited <4.20pm, for update the commit ID>

@wuxibin89
wuxibin89 merged commit 5daceb5 into verl-project:main Oct 8, 2026
60 of 71 checks passed
kahlun added a commit to kahlun/verl-hardware-plugin-fork that referenced this pull request Oct 9, 2026
…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>
physics31415926 pushed a commit to verl-project/verl-hardware-plugin that referenced this pull request Oct 13, 2026
## 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>
askhat-g pushed a commit to askhat-g/verl-hardware-plugin that referenced this pull request Nov 1, 2026
## 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>
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