Skip to content
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8b1962a
WIP: vLLM IR integration for rms_norm
bohnstingl Mar 25, 2026
91d803b
WIP: before rework
bohnstingl Mar 26, 2026
963c64f
WIP: before rework
bohnstingl Mar 26, 2026
2be7e5c
Cleanup
bohnstingl Mar 26, 2026
529ca83
Cleanup of rms_norm
bohnstingl Mar 27, 2026
fc9bc0d
Integrated rework from #872
bohnstingl Mar 27, 2026
96dfaaf
Enabled direct dispatch for rms_norm
bohnstingl Mar 27, 2026
2de9b77
Reverted rms_norm test file
bohnstingl Mar 27, 2026
172e63a
Merge branch 'main' of github.com:vllm-project/vllm-spyre into vllm_I…
bohnstingl Mar 28, 2026
555cd5d
Some reformatting
bohnstingl Mar 28, 2026
f27eb7b
Minor cleanup
bohnstingl Mar 28, 2026
60cb9a3
Reintroduced opaque wrapping layer
bohnstingl Mar 28, 2026
10a2a07
Reverting silu_and_mul; formatting
bohnstingl Mar 28, 2026
056dd2b
Merge branch 'main' of github.com:vllm-project/vllm-spyre into vllm_I…
bohnstingl Mar 31, 2026
aa33fbf
Rework of rms_norm provider
bohnstingl Apr 3, 2026
eb9efeb
Merge branch 'main' of github.com:vllm-project/vllm-spyre into vllm_I…
bohnstingl Apr 3, 2026
2405ea4
Removed padding
bohnstingl Apr 9, 2026
ec7d388
Merge branch 'main' of github.com:vllm-project/vllm-spyre into vllm_I…
bohnstingl Apr 9, 2026
d25c5f3
Moved dtype conversion into _supports_spyre
bohnstingl Apr 9, 2026
3d26458
Removed unnecessary settings
bohnstingl Apr 9, 2026
1fcfcd1
Merge branch 'main' of github.com:vllm-project/vllm-spyre into vllm_I…
bohnstingl Apr 14, 2026
f39edb6
Merge branch 'vllm_IR_integration' of github.com:bohnstingl/vllm-spyr…
bohnstingl Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions vllm_spyre_next/vllm_spyre_next/custom_ops/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""This module contains all custom ops for spyre"""

from . import rms_norm
from . import silu_and_mul
from . import vocab_parallel_embedding
from . import linear
Expand All @@ -11,7 +10,9 @@

def register_all():
logger.info("Registering custom ops for spyre_next")
rms_norm.register()
silu_and_mul.register()
vocab_parallel_embedding.register()
linear.register()

# IR provider registration (triggered by import)
from . import kernels as _kernels # noqa: F401
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Spyre IR provider registrations."""

from . import rms_norm as _rms_norm # noqa: F401
73 changes: 73 additions & 0 deletions vllm_spyre_next/vllm_spyre_next/custom_ops/kernels/rms_norm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Spyre IR provider for rms_norm.

Self-contained provider that handles:
- Device transfer: CPU → Spyre → compute → CPU
- Padding: batch < 64 → pad to Spyre minimum batch size
- No dtype promotion (torch-spyre limitation, stays in input dtype)
- Epsilon as tensor (scalar broadcast limited on Spyre)
"""

import torch
import torch.nn.functional as F

from vllm import ir

from ..utils import convert

_SPYRE_MIN_BATCH_SIZE = 64


def _supports_spyre(x, weight, epsilon, variance_size=None):
"""Accept tensors when variance_size is not used.
Falls back to native provider when
variance_size is set (not yet supported on Spyre).
"""
return variance_size is None


@ir.ops.rms_norm.register_impl("spyre", supports_args=_supports_spyre, supported=True)
def spyre_rms_norm(
x: torch.Tensor,
weight: torch.Tensor | None,
epsilon: float,
variance_size: int | None = None, # noqa: ARG001 — required by IR schema
) -> torch.Tensor:
"""Spyre IR provider for rms_norm.

Spyre-specific implementation details:
- Device transfer: tensors arrive on CPU, are transferred to Spyre for
compute, and transferred back to CPU afterward.
- Padding: batches smaller than _SPYRE_MIN_BATCH_SIZE are padded.
- Epsilon as tensor: scalar broadcast limited on Spyre, expanded via
torch.full().
- No dtype promotion: torch-spyre limitation, stays in input dtype.
- variance_size: not supported; _supports_spyre rejects it so dispatch
falls back to native.
"""
Comment on lines +33 to +41

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.

I understand and agree with these constraints and I think we should proceed with this PR as is.
However, if we want to reduce the custom code for spyre even further, couldn't we reuse the upstream RMS norm if we automate the first two steps?

  1. to convert epsilon to a tensor could be a custom vllm compile pass? (or done in torch.spyre?)
  2. same for remove of the dtype cast?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We could consider this once we fully utilize torch.compile for our provider, see my comment below. In a sense, currently our provider function is not traced, and thus no graph is created and thus we can't do any of the custom passes.

target_device = torch.device("spyre")
target_dtype = torch.float16

x_device = x.device
x_dtype = x.dtype
orig_batch_size = x.shape[0]

# Pad to Spyre minimum batch size for reduction ops
if orig_batch_size < _SPYRE_MIN_BATCH_SIZE:
pad = _SPYRE_MIN_BATCH_SIZE - orig_batch_size
x = F.pad(x, (0, 0, 0, pad))

# Transfer to Spyre
x = convert(x, target_device, target_dtype)
if weight is not None:
weight = convert(weight, target_device, target_dtype)

# Compute (no float32 upcast, epsilon as tensor)
eps_tensor = torch.full(x.shape, epsilon, dtype=x.dtype, device=x.device)
variance = x.pow(2).mean(dim=-1, keepdim=True)
x = x * torch.rsqrt(variance + eps_tensor)

if weight is not None:
x = x * weight

# Transfer back to original device/dtype, remove padding
return convert(x, x_device, x_dtype)[:orig_batch_size, :]
236 changes: 0 additions & 236 deletions vllm_spyre_next/vllm_spyre_next/custom_ops/rms_norm.py

This file was deleted.

30 changes: 30 additions & 0 deletions vllm_spyre_next/vllm_spyre_next/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
# NB: We can't eagerly import many things from vllm since vllm.config
# will import this file. These would lead to circular imports
from vllm.config import VllmConfig
from vllm.config.kernel import IrOpPriorityConfig
else:
VllmConfig = None
IrOpPriorityConfig = None

logger = init_logger(__name__)

Expand Down Expand Up @@ -73,10 +75,38 @@ def log_server_boot(cls, vllm_config: VllmConfig) -> None:

logger.info(message, version, model_name)

@classmethod
def get_default_ir_op_priority(cls, vllm_config: "VllmConfig") -> "IrOpPriorityConfig":
from vllm.config.kernel import IrOpPriorityConfig

return IrOpPriorityConfig.with_default(
["native"],
rms_norm=["spyre", "native"],
)

@classmethod
def check_and_update_config(cls, vllm_config: VllmConfig) -> None:
cls.log_server_boot(vllm_config)

# ---- compilation / custom ops ----
# Upstream defaults custom_ops to ["none"] when backend == "inductor",
# which disables all CustomOp forward_oot methods. Spyre needs
# forward_oot to run (device sandwich, padding, IR dispatch to Spyre
# provider). Force custom ops ON so dispatch_forward selects forward_oot
# regardless of compilation mode.
compilation_config = vllm_config.compilation_config
custom_ops = compilation_config.custom_ops
if "none" in custom_ops:
custom_ops.remove("none")
if "all" not in custom_ops:
custom_ops.append("all")

# Must use torch wrapping (True) so ir.ops.rms_norm() routes through
# torch.ops.vllm_ir.rms_norm (a registered custom op). This creates
# an opaque boundary that Dynamo captures without tracing inside.
# Inductor. The provider therefore runs eagerly at each forward call.
compilation_config.ir_enable_torch_wrap = True

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

We need to use the ir_enable_torch_wrap at the moment, because of our .to(device="spyre") calls and also because an upstream issue in torch-spyre that needs to be investigated.
So, for the moment our provider function won't be compiled and runs in eager-mode.


# ---- worker ----
parallel_config = vllm_config.parallel_config
if parallel_config.worker_cls == "auto":
Expand Down
Loading