Skip to content

experiment: pure-monkeypatch XPU integration, zero PlatformBase hooks - #8

Merged
kahlun merged 3 commits into
mainfrom
experiment/xpu-monkeypatch-no-core-hooks
Oct 1, 2026
Merged

kahlun merged 3 commits into
mainfrom
experiment/xpu-monkeypatch-no-core-hooks

Conversation

@kahlun

@kahlun kahlun commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Companion experiment to PR #22 and issue #26 — implements the same capabilities as #7917's 4 PlatformBase hooks (is_reduce_avg_supported, attention_utils_module, dist_profiler_cls) entirely inside this plugin, with zero dependency on #7917 ever merging. Does not touch feature/xpu-vtune-avg-attention; built from main so it can be compared directly for footprint and how much it depends on core changes landing.

See docs/design/xpu-monkeypatch-experiment.md for the full per-capability verdict table.

Works without any core change:

  • attention_utils_module — patches attention_utils._get_attention_functions
  • is_reduce_avg_supported (FSDP2) — no patch at all; extended fsdp_xpu.py's existing post-initialize() step to cover every FSDP2-wrapped submodule, not just the root
  • is_reduce_avg_supported (3 raw dist.all_reduce(AVG) sites) — patches torch.distributed.all_reduce process-wide (flagged as a real design smell, not presented as clean)
  • dist_profiler_cls (vtune tool selection) — patches DistProfiler.__init__

Deliberately NOT patched (genuine blockers, not solved by patching harder):

Review fixes (Copilot round 1)

  • dist_profiler_patch_xpu had no XPU guard, unlike the other two patches — on a CPU/CUDA/NPU host, importing this plugin rerouted that host's profiler.tool: vtune to the Intel ITT implementation. Guard added, and the duplicated _xpu_available() helper factored into patches/_xpu_guard.py so all three modules share one definition.
  • apply_all() caught every exception at debug level. Now split by consequence: correctness-critical patches (reduce_avg, attention) raise RuntimeError on an XPU host; the vtune patch reports via logger.exception and continues; non-XPU hosts stay debug-only.
  • itt_profile_xpu leaked an ITT range when the timed body raised, in both marked_timer() and VtuneProfiler.annotate(). Both now pop in a finally.
  • The fourth finding (use self.model, not self.module) was inverted — verl's FSDPEngine has no self.model at all, and the old hasattr(self.model, ...) guard is why the workaround never ran. Copilot cited docs/development.md, so the docs were the wrong artifact; fixed there instead. Details in the review thread.

Hardware validation

Run on 2×B60 via devctl, inside the image built by PR #2's docker/intel_gpu/Dockerfile.intel_gpu with this branch as the build context. verl-core cbf4f31b, torch 2.13.0+xpu, vLLM 0.29.0.

  • Image from PR [XPU] feat: add Docker image for verl + Intel XPU plugin #2 is usable with this branch — builds, torch.xpu.is_available(), vLLM platform xpu, plugin entry point discovered
  • 85 unit tests pass in-image (was: no unit tests for patches/)
  • All three patches install on an XPU host; none install in the same image on a CPU-only container
  • attention_utils returns the npu_flash_attn_utils set; profiler.tool: vtune resolves to VtuneProfiler; 2-rank xccl all_reduce(AVG) returns the true mean
  • PR experiment: pure-monkeypatch XPU integration, zero PlatformBase hooks #8's premise confirmed on this image: verl-core cbf4f31b does define the PlatformBase hooks, and this plugin overrides none of them — so the hooks are inert and the patches did the work
  • Full FSDP2 GRPO training run with profiler.tool: vtune — not done; the above exercises the patches directly, not a trainer loop

One caveat recorded in docs/design/xpu-monkeypatch-experiment.md: a plugin-free baseline on the same pod showed unpatched xccl computing ReduceOp.AVG correctly for a small 2-rank tensor. That does not contradict the patch — oneCCL implements AVG on its SYCL-kernel path but not its scheduler path, and path selection is internal — but it does mean a 2-rank smoke test cannot demonstrate the failure the patch prevents.

Known divergence from the sibling branch

experiment/xpu-monkeypatch-reduce-avg-attention has two later fixes to the same patch that this branch does not yet carry, neither raised in this PR's review: the corrected oneCCL reasoning in the docstring (4b90cec) and scoping the patch to xccl process groups instead of all of them (08fb6a9). Left for a separate commit.

Answers verl-hardware-plugin#26's own question concretely: implements
attention_utils_module, is_reduce_avg_supported, and dist_profiler_cls
(vtune) entirely inside this plugin, with no dependency on
verl-project/verl#7917 ever merging.

- attention_utils_module: patch attention_utils._get_attention_functions
  (re-invoked every call, so import order doesn't matter).
- is_reduce_avg_supported (FSDP2): no patch needed at all -- extended
  fsdp_xpu.py's existing post-initialize() workaround to cover every
  FSDP2-wrapped submodule, not just the root.
- is_reduce_avg_supported (3 raw dist.all_reduce(AVG) sites): patch
  torch.distributed.all_reduce process-wide. Flagged as a real design
  smell in the module docstring, not presented as clean.
- dist_profiler_cls: patch DistProfiler.__init__ (the class method, not
  the class name) to add a vtune branch.

profiler_markers and the rollout-server profiler allowlist are
deliberately NOT patched here -- both are genuine blockers (import-time
module selection / value nulled and consumed in the same method body),
not solved by patching harder. See docs/design/xpu-monkeypatch-experiment.md
for the full verdict table and why an earlier draft of this plan (wrapping
apply_fsdp2 directly) had the same import-order problem as
profiler_markers and was replaced with the fsdp_xpu.py approach instead.

Does not touch feature/xpu-vtune-avg-attention (PR #22) -- this is a
separate branch from main so the two approaches can be compared directly.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved correctness and safety issues remain in FSDP initialization, patch guarding, exception cleanup, and profiler labeling.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 3 Medium severity

Open (4)
What changed in this PR

This PR adds a plugin-only Intel XPU integration using monkeypatches and FSDP extensions, without requiring core PlatformBase changes.

Changes:

  • Adds attention, AVG reduction, and VTune profiler patches.
  • Extends nested FSDP2 reduction handling.
  • Documents supported capabilities and remaining blockers.
File Summary
verl_hardware_plugin/​profilers/​itt_profile_xpu.py Adds VTune/ITT profiling support.
verl_hardware_plugin/​patches/​reduce_avg_allreduce_patch_xpu.py Adds the XPU AVG reduction workaround.
verl_hardware_plugin/​patches/​dist_profiler_patch_xpu.py Adds VTune profiler dispatch.
verl_hardware_plugin/​patches/​attention_patch_xpu.py Routes XPU attention utilities.
verl_hardware_plugin/​patches/​__init__.py Applies plugin patches.
verl_hardware_plugin/​engines/​fsdp_xpu.py Configures nested FSDP2 reduction handling.
verl_hardware_plugin/​__init__.py Initializes the patches.
docs/​design/​xpu-monkeypatch-experiment.md Documents the experiment and limitations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

if hasattr(self.model, "set_force_sum_reduction_for_comms"):
self.model.set_force_sum_reduction_for_comms(True)
logger.info("Enabled force_sum_reduction_for_comms for XPU")
_force_sum_reduction_on_all_fsdp_modules(self.module)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this against verl-core rather than the plugin docs, and it is inverted: self.module is correct and self.model is the bug.

FSDPEngine assigns the wrapped model in _build_model_optimizer() (called from initialize()), and grep -rn "self\.model\s*=" verl/workers/engine/ returns nothing anywhere in that tree — the only self.model* attribute on these engines is self.model_config. So the previous hasattr(self.model, "set_force_sum_reduction_for_comms") guard was silently always False, which is exactly why the xccl workaround never actually ran. Switching to self.module is what makes it fire.

You cited docs/development.md:583-586 as the contract, and that was the right thing to point at — the doc was the wrong artifact. Fixed all 7 occurrences there and stated the attribute contract explicitly in the template docstring so it does not get reintroduced. Added tests/test_fsdp_xpu_engine.py::test_verl_fsdp_engine_exposes_module_not_model, which inspects the real FSDPEngine class source, so a future verl rename fails a test instead of silently disabling the workaround again.

Verified on 2×B60 via devctl: the workaround now reports enabling force_sum_reduction_for_comms on the root plus every per-layer FSDP2 wrapper.

Comment on lines +33 to +36
try:
module.apply()
except Exception as e:
logger.debug("%s.apply() skipped: %s", module.__name__, e)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, fixed. apply_all() now splits failures by consequence instead of swallowing them:

  • non-XPU host: nothing was going to install anyway (every module guards), so still debug-only, no raise;
  • XPU host, correctness-critical patch (reduce_avg_allreduce_patch_xpu, attention_patch_xpu): re-raised as RuntimeError — a missing reduce workaround means wrong gradients, which is precisely the silent-until-much-later failure you describe;
  • XPU host, feature patch (dist_profiler_patch_xpu): logger.exception and continue, since losing profiler.tool: vtune costs observability, not correctness.

Also moved the patch-module imports inside the try, so a module-level ImportError (e.g. verl-core moving a symbol) follows the same policy rather than escaping apply_all() and breaking plugin load on every host.

Covered by tests/test_xpu_patches.py::test_apply_all_raises_when_required_patch_fails_on_xpu and ::test_apply_all_reports_but_survives_optional_patch_failure_on_xpu.

Comment on lines +50 to +55
def apply() -> None:
global _applied
if _applied:
return

from verl.utils.profiler.profile import DistProfiler, _NoOpProfiler

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and this one had teeth — this repo has already been bitten by exactly this shape (the MLU profiler patch breaking other platforms). Added the guard.

Rather than adding a third copy of _xpu_available(), I factored it into patches/_xpu_guard.py and pointed all three modules at it, so the guard cannot drift between them again.

Verified both directions on the same image: on a CPU-only container all three patches report _applied is False, torch.distributed.all_reduce is unchanged, and DistProfiler.__init__.__qualname__ is still core's DistProfiler.__init__; on a 2×B60 pod all three install. Regression test: tests/test_xpu_patches.py::test_dist_profiler_untouched_without_xpu.

…ance

Copilot review on PR #8, four findings; three were real:

- dist_profiler_patch_xpu had no XPU guard, unlike the other two patches.
  DistProfiler is a process-wide class object, so importing this plugin on a
  CPU/CUDA/NPU host rerouted *that* host's `profiler.tool: vtune` to the
  Intel ITT implementation. Added the guard. Also factored the duplicated
  _xpu_available() helper into patches/_xpu_guard.py so all three modules
  share one definition and it cannot drift again.

- patches.apply_all() caught every exception and logged at debug, so an XPU
  process could continue after a required workaround failed to install.
  Split by consequence: reduce_avg and attention are correctness-critical
  and now raise RuntimeError on an XPU host; the vtune patch is
  observability and is reported with logger.exception. Non-XPU hosts stay
  debug-only, since nothing was going to install there anyway. Patch modules
  are now imported inside the try so a module-level import failure follows
  the same policy.

- itt_profile_xpu leaked an ITT range when the timed body raised, in both
  marked_timer() and VtuneProfiler.annotate(). A leaked range makes every
  later range on the thread nest inside it, so the trace is unreadable in
  exactly the runs worth reading. Both now pop in a finally; annotate() also
  stops discrete collection on the error path.

The fourth finding (use self.model, not self.module, in fsdp_xpu.py) is
inverted. verl's FSDPEngine assigns the wrapped model to self.module in
_build_model_optimizer(), called from initialize(); there is no self.model on
these engines, only self.model_config -- confirmed by grep over
verl/workers/engine/, which has no `self.model =` anywhere. The old
`hasattr(self.model, ...)` guard is why the workaround silently never ran.
Copilot cited docs/development.md as the contract, so the docs were the wrong
artifact: fixed all 7 occurrences there and stated the contract explicitly so
it does not get reintroduced.

Also adds the unit tests the PR body listed as outstanding, covering each
fix: non-XPU no-op per patch module, the raise/log-and-continue split, ITT
push/pop balance on the exception path, and the engine's
root-plus-every-wrapped-layer recursion.
Ran PR #8's branch on 2xB60 via devctl, inside the image PR #2's Dockerfile
builds (build context = this branch, so COPY . puts this code in the image).
verl-core cbf4f31b, torch 2.13.0+xpu, vLLM 0.29.0. 85 unit tests pass
in-image; all three patches install on the XPU host and none install in the
same image on a CPU-only container; attention/vtune/AVG all behave as
claimed; and verl-core's PlatformBase hooks are confirmed inert because this
plugin overrides none of them, which is PR #8's whole premise.

Also corrects 'xccl cannot execute ReduceOp.AVG at all' in the failure-policy
text I added in the previous commit. A plugin-free baseline on the same pod
showed unpatched xccl returning the correct mean for a small 2-rank AVG
collective. That is consistent with the real reason, already established on
the sibling branch from 4xB60 measurements: oneCCL implements AVG on its
SYCL-kernel path but not its scheduler path, and path selection is internal
and not capability-aware, so a small collective can land on the working path.
Recorded the baseline result and that caveat in the design doc so nobody
reads a passing 2-rank smoke test as evidence the patch is unnecessary.

Note: this branch's reduce_avg_allreduce_patch_xpu docstring still carries the
older blanket phrasing, and still patches every process group rather than
scoping to xccl ones. Both were corrected on
experiment/xpu-monkeypatch-reduce-avg-attention (4b90cec, 08fb6a9) and are
left for a separate commit here -- neither was raised in this PR's review.
kahlun added a commit that referenced this pull request Sep 24, 2026
# Conflicts:
#	verl_hardware_plugin/patches/__init__.py
@kahlun
kahlun merged commit 70ae658 into main Oct 1, 2026
2 checks passed
kahlun added a commit that referenced this pull request Oct 13, 2026
verl-core's set_numa_affinity() uses pynvml.nvmlDeviceSetCpuAffinity(),
which is absent on XPU and silently no-ops, leaving every XPU worker
unpinned and paying cross-socket memory traffic for the life of the job.
This replaces it entirely from the plugin side (pyzes PCI BDF lookup ->
sysfs numa_node -> cpulist -> os.sched_setaffinity), applied from
PlatformXPU.__init__ like reduce_avg_allreduce_patch, with zero verl-core
changes. Rebases the pure-monkeypatch result from
experiment/xpu-numa-affinity-monkeypatch onto current main and the
patches/xpu/ layout, dropping the unrelated attention/profiler/reduce_avg
bundling that branch carried from PR #8's base.

Verified end-to-end on B60 2026-09-24 (6/6 checks, 512->256 CPUs narrowed,
bdf=0000:3d:00.0 numa_node=0) on the prior branch; same patch logic here,
rebased.
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