Repository navigation
[XPU] Dispatch nn.LayerNorm to fused SYCL kernel via CustomOp - #57172
Conversation
Adds EagerLayerNorm(CustomOp) and NemotronLayerNorm1P(CustomOp), matching RMSNorm/GemmaRMSNorm's forward_native/forward_cuda/forward_xpu dispatch (vllm-xpu-kernels layer_norm/nemotron_layer_norm, PR vllm-project#577). Wires GPT-2/GPT-J/OPT/SigLIP/Nemotron, plus a replace_layernorm_class helper for the Transformers backend's generic module-replace. hasattr-guarded: no-op until vllm-xpu-kernels cuts a release past vllm-project#577 and requirements/xpu.txt is bumped to it. Signed-off-by: Michal Ganczarenko <michal.ganczarenko@intel.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nels empty_like preserves the input's strides; the kernel needs a contiguous out, same as GemmaRMSNorm.forward_xpu right above it in this file. Signed-off-by: Michal Ganczarenko <michal.ganczarenko@intel.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… dodge forward_xpu called torch.ops._C.layer_norm/nemotron_layer_norm directly, which isn't traceable, and worked around that with a torch.compiler. is_compiling() runtime check to skip the kernel under compile. GemmaRMSNorm avoids needing this: its kernel calls go through torch.ops.vllm.xpu_*, registered via direct_register_custom_op in _xpu_ops.py, which makes them compile-safe by construction - the standard CustomOp +/-custom_ops toggle is then the only gate needed, same as every other CustomOp here. Adds the matching xpu_layer_norm/xpu_fused_add_layer_norm/ xpu_nemotron_layer_norm/xpu_fused_add_nemotron_layer_norm registrations and switches both forward_xpu methods to call through them instead. Renames EagerLayerNorm -> StandardLayerNorm now that the "eager" dodge it was named for is gone. Signed-off-by: Michal Ganczarenko <michal.ganczarenko@intel.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Measured -3.1% throughput / -1.6% serve on XPU, reproduced twice with tight non-overlapping spreads - a real regression, not noise. Other Transformers-backend models (StarCoder2) gain through the identical swap, so exclude just this architecture rather than the whole path. Signed-off-by: Michal Ganczarenko <michal.ganczarenko@intel.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
|
||
|
|
||
| @CustomOp.register("layer_norm") | ||
| class StandardLayerNorm(CustomOp): |
There was a problem hiding this comment.
Why not just call it LayerNorm?
| class StandardLayerNorm(CustomOp): | |
| class LayerNorm(CustomOp): |
There was a problem hiding this comment.
There is already LayerNorm class being nn.Module at the end of the file, but I am open for alternatives for the name
There was a problem hiding this comment.
Could we port the other LayerNorm to be a custom op instead of adding a new one with a different name?
There was a problem hiding this comment.
I think we could. Please, take a look and let me know if it looks good to you - ff208db
|
Given that this is directly swapping |
I've tried to match the precedents already set by RMSNorm/GemmaRMSNorm in the same file (both dispatch fused kernels living in vllm-xpu-kernels), so I've chosen vllm-xpu-kernels at first. This is the place where vLLM's XPU-specific fast paths for attention/MoE/quant/norm ops already live. Nothing rules out upstreaming this to torch-xpu-ops, but this is another process. |
|
IIRC the reason that the Since there are no users of this new layer there are no fusion passes for it to be captured by. |
|
This pull request has merge conflicts that must be resolved before it can be |
Thanks for the clarification. Following up on the question about why this couldn't just be a kernel contribution to torch-xpu-ops instead of a vLLM-specific CustomOp + vllm-xpu-kernels kernel. Why this kernel is faster than native?torch-xpu-ops already has a LayerNorm kernel for XPU (
Why it does not meet requirements to be native?That second part is the actual reason this lives outside torch-xpu-ops: it's not a limitation of torch-xpu-ops's kernel quality, it's that any backend registered for aten::native_layer_norm inherits the full differentiable-op dispatch contract, which vLLM's inference-only serving path never needs. Bypassing it requires an op outside that contract - which is what CustomOp + vllm-xpu-kernels gives us, and what a general-purpose PyTorch backend project can't do without breaking autograd for everyone else using aten::native_layer_norm. Same pattern already used here for GemmaRMSNorm/RMSNorm, so this isn't a new precedent, just extending an existing one to LayerNorm. |
Resolve conflict in transformers/base.py: upstream added orig_to_new_stacked (used by register_fusion's weight mapper), this branch added the swap_layernorm/arch OLMo-exclusion check at the same insertion point. Kept both, no functional change to either. Also fix a mypy failure surfaced by the merge: opt.py's final_layer_norm needs the same explicit `StandardLayerNorm | None` pre-declaration project_out/project_in already use, since the first assignment (StandardLayerNorm) otherwise narrows the inferred type before the None branch is considered. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Michal Ganczarenko <michal.ganczarenko@intel.com>
Replace the `nn.Module` `LayerNorm` with the renamed `layer_norm` CustomOp, deriving the compute dtype from `torch.promote_types(x.dtype, weight.dtype)` so fp32 parameters keep the old fp32 reduction. Signed-off-by: Michal Ganczarenko <michal.ganczarenko@intel.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Norm never subclassed nn.LayerNorm, so the isinstance-gated swap it guarded was already unreachable. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Michal Ganczarenko <michal.ganczarenko@intel.com>
|
/ci run |
|
✅ Triggered Buildkite CI #92117 for commit |
…LM LayerNorm in the regional compilation unit test Root cause: vllm#57172 replaced torch nn.LayerNorm in OPT with vllm's CustomOp LayerNorm, so the test's torch LayerNorm filter matches nothing Upstream: vllm-project/vllm#57172 Fix: import LayerNorm from vllm.model_executor.layers.layernorm, which OPT now builds its norms from Signed-off-by: Paweł Olejniczak <pawelx.olejniczak@intel.com>
…LM LayerNorm in the regional compilation unit test Root cause: vllm#57172 replaced torch nn.LayerNorm in OPT with vllm's CustomOp LayerNorm, so the test's torch LayerNorm filter matches nothing Upstream: vllm-project/vllm#57172 Fix: import LayerNorm from vllm.model_executor.layers.layernorm, which OPT now builds its norms from Signed-off-by: Paweł Olejniczak <pawelx.olejniczak@intel.com>
…LM LayerNorm in the regional compilation unit test Root cause: vllm#57172 replaced torch nn.LayerNorm in OPT with vllm's CustomOp LayerNorm, so the test's torch LayerNorm filter matches nothing Upstream: vllm-project/vllm#57172 Fix: import LayerNorm from vllm.model_executor.layers.layernorm, which OPT now builds its norms from Signed-off-by: Paweł Olejniczak <pawelx.olejniczak@intel.com>
…LM LayerNorm in the regional compilation unit test Root cause: vllm#57172 replaced torch nn.LayerNorm in OPT with vllm's CustomOp LayerNorm, so the test's torch LayerNorm filter matches nothing Upstream: vllm-project/vllm#57172 Fix: import LayerNorm from vllm.model_executor.layers.layernorm, which OPT now builds its norms from Signed-off-by: Paweł Olejniczak <pawelx.olejniczak@intel.com>
Purpose
vllm-xpu-kernels exposes fused SYCL kernels for standard (non-RMS) LayerNorm (vllm-xpu-kernels#577) -
layer_norm,fused_add_layer_norm,nemotron_layer_norm,fused_add_nemotron_layer_norm- but nothing in vLLM dispatched to them yet. Every model with a barenn.LayerNormfell back to torch-xpu-ops' generic kernel on XPU.StandardLayerNorm, aCustomOp(registered"layer_norm") - drop-in for a barenn.LayerNorm, dispatching viaforward_native/forward_cuda/forward_xpu, matchingRMSNorm/GemmaRMSNormin the same file.NemotronLayerNorm1P(Nemotron's "+1 to weights" variant) from annn.LayerNormsubclass into aCustomOpsibling ("nemotron_layer_norm"); its XPU path also dispatches the fused-residual-add kernel when a residual is present.replace_layernorm_class(transformers/utils.py), matchingreplace_linear_class/replace_conv_class/replace_embedding_class, so the Transformers modeling backend's generic recursive module-replace picks this up too - covers StarCoder2-style models with no dedicated vLLM model file.torch.ops.vllm.xpu_*custom ops (_xpu_ops.py, viadirect_register_custom_op), the same wayGemmaRMSNormwraps its kernel - compile-safe by construction, gated only by the standardCustomOp+/-custom_opstoggle.hasattr(torch.ops._C, ...)-guarded and falls back to nativeF.layer_normwhen the op isn't present - currently a no-op everywhere, since novllm-xpu-kernelsrelease yet contains Incorrect output when using hf-internal-testing/llama-tokenizer #577 (just merged) andrequirements/xpu.txt's pin predates it.Test Plan
ruff check,ruff format, fullpre-commit(incl.mypy) on every touched file.layer_normandnemotron_layer_norm/fused_add_nemotron_layer_norm, checked against a plain-PyTorch fp32 reference.--enforce-eager) on the same hardware, per affected model.vllm bench serve/throughput/latency, baseline vs patched, N=3 median.tests/kernels/core/test_layernorm.pycoversRMSNorm/GemmaRMSNorm, nothing exercised plain LayerNorm before this PR). Not yet done: lm_eval accuracy check for any affected model. A reproducible kernel-level benchmark (benchmark/benchmark_layer_norm.py) is alreadycommitted on the vllm-xpu-kernels PR itself.
Test Result
Correctness: 64 parametrized cases + 2 fused-add cases, all pass vs plain-PyTorch fp32 reference.
Kernel microbenchmark (native
F.layer_normvs SYCL, bf16, hidden_size=4096):End-to-end serve: GPT-J, GPT-2, OPT-125m, Nemotron/Minitron-8B, PaliGemma (SigLIP), and StarCoder2-3B all served and generated correctly with
--enforce-eager; SYCL dispatch confirmed firing inside the real engine, not just via a direct standalone op call.End-to-end performance:
Caveat: the above was measured against an earlier revision of this dispatch code (direct
torch.ops._C.*calls, gated bytorch.compiler.is_compiling()). This PR wraps those same calls throughtorch.ops.vllm.xpu_*instead, fortorch.compilesafety - a thin pass-through to the identical kernel call, so behavior shouldbe unchanged, but hasn't itself been re-run on hardware.