-
Notifications
You must be signed in to change notification settings - Fork 56
[Spyre-Next] [Feature] rms_norm rework with vLLM IR #877
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
8b1962a
91d803b
963c64f
2be7e5c
529ca83
fc9bc0d
96dfaaf
2de9b77
172e63a
555cd5d
f27eb7b
60cb9a3
10a2a07
056dd2b
aa33fbf
eb9efeb
2405ea4
ec7d388
d25c5f3
3d26458
1fcfcd1
f39edb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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. | ||
| """ | ||
| 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, :] | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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__) | ||
|
|
||
|
|
@@ -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 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| # ---- worker ---- | ||
| parallel_config = vllm_config.parallel_config | ||
| if parallel_config.worker_cls == "auto": | ||
|
|
||
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.