Skip to content

[XPU] feat(xpu): add VTune (ITT) profiler backend - #22

Merged
physics31415926 merged 23 commits into
verl-project:mainfrom
kahlun:feature/xpu-vtune-avg-attention
Oct 13, 2026
Merged

physics31415926 merged 23 commits into
verl-project:mainfrom
kahlun:feature/xpu-vtune-avg-attention

Conversation

@kahlun

@kahlun kahlun commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

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
(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 (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:
    • 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

  • 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 (refactor: organize hardware plugin by accelerator #41), which moved these modules to
    accelerators/xpu/.
  • 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.
  • 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)

  • 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

  • Code follows the project's style and passes pre-commit checks
  • Documentation updated
  • No secrets or credentials included

…uler.avg override behavior, attention utils point to existing npu file
docs/user_guide_xpu/README.md already listed these three files in its
"Directory Structure" section but none of them existed. Adapted from
docs/intel_gpu_tutorial/ (verl-project/verl, fork/verl working tree,
uncommitted) with corrections: verl_xpu -> verl_hardware_plugin (this
repo's actual package name), VERL_USE_EXTERNAL_MODULES -> entry_points
auto-discovery (this plugin declares verl.plugins entry_points in
pyproject.toml, no env var needed), and Dockerfile references pointed at
docker/intel_gpu/ in this repo instead of verl-core's.
FSDPEngineWithLMHead/FSDPEngineWithValueHead (verl-core,
verl/workers/engine/fsdp/transformer_impl.py) expose the wrapped model as
self.module, never self.model. Both XPU engine subclasses called
self.model in initialize(), which isn't caught by the hasattr() guard --
hasattr() only suppresses AttributeError from its own internal getattr,
not from evaluating self.model as an argument, so this raised immediately
and crashed ref-model init before training could start.

Confirmed on real B60 hardware, single GPU, both FSDP1 and FSDP2
(run_grpo_intel_gpu.sh / run_grpo_fsdp2_intel_gpu.sh): identical
'FSDPXPUEngineWithLMHead' object has no attribute 'model' crash in
WorkerDict.actor_rollout_ref_init_model() -> engine.initialize(), for
both strategies -- this predates the vLLM v0.29.0 bump and the
VtuneProfiler/hook work in this PR, it's a pre-existing bug in the base
engine file.
Not ready to ship yet -- pulling it out rather than leaving stale
mentions in quick_start.md and the pre-existing README.md stub. Also
removes the RAY_memory_monitor_refresh_ms/RAY_NUM_PRESTART_PYTHON_WORKERS
example from quick_start.md's launch snippet for the same reason as the
8-GPU script: only validated at 2 GPUs, disables a real Ray safety net.
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.
Restructure README/install_guidance/quick_start to match the other
vendor guides' level of detail: source-tree listing in the README,
a full Environment Variables table, prerequisites-before-install
ordering, numbered install/quick-start steps, a platform-resolution
sanity check before the full training run, the plugin test-suite
command, and Next Steps/Related Documentation sections. No new
technical claims - same facts as before, just organized and filled
out to match user_guide_tpu's structure.
kahlun and others added 4 commits September 22, 2026 08:54
Fills the last missing user_guide_xpu doc relative to user_guide_mlu's
four-file structure. Documents the actual dist_profiler_cls/tool_config
wiring verified against a live Hydra compose (there is no generated
tool_config.vtune schema entry, unlike nsys/npu/torch -- it must be
added via +tool_config.vtune._target_=NsightToolConfig), and clarifies
that VTune attaches externally as a collector rather than writing a
self-contained trace file the way nsys/torch/npu do.
Two mypy errors failed the lint job:

- marked_timer's `color: str = None` is an implicit Optional (PEP 484);
  annotate it Optional[str] to match the sibling mark_* helpers.
- `tool_config.discrete` is read unconditionally even though tool_config
  is Optional. Beyond the union-attr error this is a live AttributeError:
  when the profiler is disabled, tool_config is None and the preceding
  assert passes, so construction crashes. Default discrete to False in
  that case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`profiler.tool=vtune` crashed every worker unless the user hand-added a
`+tool_config.vtune.*` Hydra override, including when `profiler.enable`
was False.

verl core has no `tool_config.vtune` schema entry, so the caller's
`tool_config.get("vtune")` resolves to None. DistProfiler.__init__ then
substitutes the *whole* tool_config mapping for that None:

    if tool_config is None:
        tool_config = config.tool_config

That mapping is non-empty, so the old `if not tool_config` guard did not
fire and the `else False` fallback never engaged — but it is a plain
dict/DictConfig with no `discrete` key, so `tool_config.discrete` raised
`AttributeError: 'dict' object has no attribute 'discrete'`.

Read it with `getattr(..., "discrete", False)` instead, mirroring what
core itself does at verl/utils/profiler/profile.py (`self._discrete =
getattr(tool_config, "discrete", False) if tool_config else False`).
False is always the correct default on XPU: `discrete` only gates
`PlatformXPU.profiler_start`/`profiler_stop`, which are no-ops here, so
both values behave identically.

The `assert not config.enable` is dropped as dead code — tool_config is
never falsy on the real resolution path, so the branch was unreachable
in the enable=True case it was meant to catch.

Explicit `+tool_config.vtune.discrete=True` overrides are still honoured.

Test: tests/test_plugin_registration.py::TestVtuneProfilerToolConfig
covers tool_config of None / {} / the full mapping core actually passes,
at both enable values, plus an explicit NsightToolConfig(discrete=True).
4 of the 6 default-path cases fail before this change (2 AssertionError,
2 AttributeError) and all pass after.
The VTune guide told users that omitting `+tool_config.vtune.*` raises
`AssertionError: tool_config must be provided when profiler is enabled`
and that this was "intentional, not a bug to work around". Both halves
were wrong: the real failure was an AttributeError from a different
repo's code path, and it was a bug (fixed in the preceding commit).

Lead with the minimal recipe — `profiler.tool=vtune` plus the usual
enable/ranks keys — and keep the `+tool_config.vtune.*` form as an
optional explicit entry, noting it changes nothing functionally on XPU
because `discrete` only gates the no-op profiler_start/profiler_stop
hooks. Drop the now-unreachable AssertionError troubleshooting bullet.
…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.
…erl-project#32

Redundant scope: this PR no longer touches fsdp_xpu.py for any AVG-related
reason (is_reduce_avg_supported was dropped in the previous commit), and
that file's only remaining purpose here was this one attribute fix, which
lives entirely in the pre-existing (pre-#22) reduce_avg workaround code.

experiment/xpu-monkeypatch-reduce-avg-attention already carries the same
fix as part of a strict superset (root + every per-layer FSDP2 wrapper, vs.
this PR's root-only version) -- keeping both risks two PRs racing to fix
the same lines. Leaving this PR to attention_utils_module and the VTune
profiler hooks only.
kahlun referenced this pull request in kahlun/verl-hardware-plugin-fork 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.
@kahlun kahlun changed the title [XPU] implement the missing capabilities of XPU for reduce_avg, attention, profiler [XPU] implement attention and profiler hooks for XPU Sep 24, 2026
kahlun referenced this pull request in kahlun/verl-hardware-plugin-fork Sep 24, 2026
Per kahlun's review-thread investigation on verl-project/verl#7917
(measured on real 4xB60 hardware, not reasoned from source): xccl does not
uniformly lack ReduceOp.AVG. oneCCL implements it on its SYCL-kernel
execution path but not on its scheduler path; path selection is internal
to oneCCL and isn't capability-aware, so an AVG request can abort
mid-collective ("average operation is not supported for the scheduler
path") rather than reliably succeed or reliably fail. Fix tracked for
oneCCL 2022.2 / torch 2.15, not yet available on the stack this plugin
targets.

Also note the separate, narrower double-division bug on very small
messages (intel/torch-xpu-ops#3020) is fixed on that same stack and is not
what either the FSDP2 engine override or this patch works around -- easy to
conflate the two since both are filed against xccl's AVG support.

The blanket "xccl does not support ReduceOp.AVG" phrasing this replaces was
accurate enough to justify the workaround but not accurate about why, and
was written before this investigation existed. Same mechanism, same
correctness -- corrected reasoning only. platform_xpu.py has the same
blanket phrasing but predates both this branch and #22; left alone here
since fixing it isn't part of this PR's diff.

76 passed.
- install_guidance.md: vLLM no longer needs to be built from source for
  XPU -- vLLM now publishes a prebuilt XPU wheel via wheels.vllm.ai, which
  docker/intel_gpu/Dockerfile.intel_gpu already uses instead of a
  from-source build. Update the prerequisite and Docker sections to match.
- quick_start.md: trim the inline verl-core/#7917 explanation on the
  Profiler row down to a pointer at profiling.md (full story already lives
  in install_guidance.md/profiling.md), and standardize "not yet
  supported/validated/tested" phrasing to "Work in Progress" across the
  summary bullets and Feature Support Matrix.
- README.md: apply the same "Work in Progress" phrasing to the Megatron
  engine directory-structure note; bump Last updated date.
physics31415926 pushed a commit 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>
kahlun added 3 commits October 8, 2026 04:57
…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.
…-attention

# Conflicts:
#	tests/test_plugin_registration.py
#	verl_hardware_plugin/platforms/platform_xpu.py
The registration patch self-gates on a live XPU device, matching
reduce_avg_allreduce_patch.apply(), so the tests stub that probe the same way
the reduce-avg suite does, and assert the no-device path leaves DistProfiler
untouched.
@kahlun kahlun changed the title [XPU] implement attention and profiler hooks for XPU [XPU] feat(xpu): add VTune (ITT) profiler backend Oct 8, 2026
@kahlun
kahlun force-pushed the feature/xpu-vtune-avg-attention branch from cc678bb to 2cf1b97 Compare October 8, 2026 06:22
… availability

The guide didn't say that a VTune collector is a separate install from the ITT
markers this plugin emits, which makes a missing collector look like broken
plugin support. ITT is notify-only -- markers are discarded when nothing has
attached -- so the two failure modes are indistinguishable without being told.

Verified on 1x Arc Pro B60: torch.profiler.itt.is_available() is True on
torch 2.13.0+xpu and torch._C._itt exposes rangePush/rangePop/mark, so the
marker path is live; but the docker/intel_gpu image's oneAPI 2026.1 install
ships no vtune component and no libittnotify, so no capture is possible there.

- Prerequisites: new bullet on the collector being a separate oneAPI component,
  absent from the image, plus the sampling permissions a container needs.
- Troubleshooting: how to tell a missing collector from broken ITT bindings.
wuxibin89 added a commit to verl-project/verl that referenced this pull request Oct 8, 2026
…orm hook and share the attention padding helpers (#7917)

### 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](verl-project/verl-hardware-plugin#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

```bash
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

- [x] Read the [Contribute
Guide](https://github.com/verl-project/verl/blob/main/CONTRIBUTING.md).
- [x] Apply [pre-commit
checks](https://github.com/verl-project/verl/blob/main/CONTRIBUTING.md#code-linting-and-formatting).
- [ ] Add / Update [the
documentation](https://github.com/verl-project/verl/tree/main/docs).
- [x] Add unit or end-to-end test(s) to [the CI
workflow](https://github.com/verl-project/verl/tree/main/.github/workflows).
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.
- [x] Once your PR is ready for CI, send a message in the `ci-request`
channel.
- [x] If your PR is related to the `recipe` submodule — not applicable.

---------

Co-authored-by: wuxibin <wuxibin@bytedance.com>
kahlun added 2 commits October 9, 2026 02:49
Resolve the accelerator reorganization (upstream verl-project#41) against this branch:

- Move the XPU profiler modules under
  verl_hardware_plugin/accelerators/xpu/profilers/ (git's rename detection
  had guessed accelerators/mlu/profilers/) and add the package __init__.
- Drop the now-deleted shared verl_hardware_plugin/profilers package; the
  only consumer was PlatformXPU.profiler_markers().
- Repoint every import of verl_hardware_plugin.profilers.* in platform_xpu,
  register_vtune and tests/test_plugin_registration.py.
- Keep upstream's platform_xpu.__init__ comment and add the vtune
  registration patch alongside the reduce_avg patch.
- Move the XPU docs to docs/accelerators/xpu/ and fix link depth
  (../development.md -> ../../development.md, ../user_guide_mlu/ ->
  ../mlu/). The docker/intel_gpu/ links were relative to a directory that
  does not exist in this repo at all -- it lives in the kahlun/verl fork,
  so they now point there explicitly.
- Refresh the directory tree in docs/accelerators/xpu/README.md.

Also corrects two comments the hook narrowing left stale:
reduce_avg_allreduce_patch no longer claims verl#7917 ships an
is_reduce_avg_supported() hook (it was dropped), and install_guidance no
longer attributes the xccl reduce_avg workaround to fsdp_xpu.py.
profiler_markers() is the only PlatformBase hook this PR implements and it had
no test, so the accelerator reorganization could have broken its lazy import of
itt_profile_xpu without the suite noticing. Assert the arity and order core
unpacks positionally, and that all four markers are callable no-ops when no ITT
collector is attached.
@kahlun
kahlun marked this pull request as ready for review October 9, 2026 03:24
kahlun added 3 commits October 9, 2026 03:31
…r links

- profiling.md's intro compared ITT support to Cambricon MLU's profiling
  path; removed, it added nothing XPU-specific.
- #7917 (the profiler_markers() hook) merged into verl-core main on
  2026-10-08. All four docs still called it 'open, not yet merged' and
  described a degraded fallback path as the default; updated to the
  merged state, including quick_start.md's feature matrix (Profiler row
  was 'Work in Progress').
- docker/intel_gpu/ doesn't exist on any verl main (fork or upstream) --
  it's only in open PR verl-project/verl#7371. Every link previously
  pointed at the kahlun/verl fork tree, implying it was already merged
  there; repointed to the PR itself and noted it's still unmerged.
@physics31415926

Copy link
Copy Markdown
Collaborator

please resolve the conflicts

…vg-attention

Resolve the test_plugin_registration.py conflict by keeping both the incoming TestReduceAvgAllReducePatchTPU tests from main and this branch's TestProfilerMarkersHook/TestVtuneProfilerToolConfig/TestVtuneProfilerRegistration tests.
@kahlun

kahlun commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

please resolve the conflicts

@physics31415926
Hi, the conflict has been resolved.
<edited, add tag>

@physics31415926
physics31415926 merged commit 0ae3354 into verl-project:main Oct 13, 2026
2 checks passed
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