Files
vLLM/vllm/models/qwen4_exp/common/ngram_embedding.py
T

823 lines
30 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Shared Qwen4Exp n-gram embedding storage with device and pinned-host backends.
Both the NVIDIA and AMD Qwen4Exp implementations use these classes so the (large)
n-gram embedding table can be kept in pinned host memory and looked up through
Unified Virtual Addressing on any CUDA-alike platform.
"""
import threading
from abc import ABC, abstractmethod
from typing import ClassVar
import torch
import torch.nn.functional as F
from torch import nn
from vllm.compilation.breakable_cudagraph import eager_break_during_capture
from vllm.distributed import get_dp_group, get_etp_group, get_tp_group
from vllm.forward_context import DPMetadata, get_forward_context
from vllm.logger import init_logger
from vllm.model_executor.layers.quantization.base_config import (
QuantizationConfig,
QuantizeMethodBase,
)
from vllm.model_executor.layers.quantization.compressed_tensors.compressed_tensors import ( # noqa: E501
CompressedTensorsConfig,
)
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
from vllm.model_executor.layers.quantization.inc import INCConfig
from vllm.model_executor.layers.quantization.modelopt import (
ModelOptMixedPrecisionConfig,
ModelOptQuantConfigBase,
)
from vllm.model_executor.layers.quantization.quark.quark import QuarkConfig
from vllm.model_executor.layers.quantization.utils.fp8_utils import (
create_fp8_scale_parameter,
)
from vllm.model_executor.layers.quantization.utils.nvfp4_emulation_utils import (
_e2m1_inline,
_e4m3_inline,
dequantize_to_dtype,
)
from vllm.model_executor.layers.quantization.utils.quant_utils import (
is_layer_skipped,
)
from vllm.model_executor.parameter import (
ModelWeightParameter,
PerTensorScaleParameter,
)
from vllm.model_executor.utils import set_weight_attrs
from vllm.triton_utils import tl, triton
from vllm.utils.platform_utils import is_uva_available
from vllm.utils.torch_utils import get_accelerator_view_from_cpu_tensor
from .ple import PLEVocabParallelEmbedding
logger = init_logger(__name__)
class Qwen4ExpPLEEmbedding(PLEVocabParallelEmbedding, ABC):
"""ETP-sharded PLE table shared by device and pinned-host backends."""
supports_prefetch: ClassVar[bool] = False
def __init__(
self,
num_embeddings: int,
embedding_dim: int,
*,
params_dtype: torch.dtype,
padding_size: int,
prefix: str,
embedding_method: "Qwen4ExpPLEEmbeddingMethod",
num_ngram_heads: int = 1,
max_total_tokens: int = 0,
data_parallel_rank: int = 0,
reduce_results: bool = True,
) -> None:
del num_ngram_heads, max_total_tokens
super().__init__(
num_embeddings,
embedding_dim,
params_dtype=params_dtype,
padding_size=padding_size,
prefix=prefix,
quant_method=embedding_method,
parallel_group=get_etp_group(),
reduce_results=reduce_results,
)
self.embedding_method = embedding_method
self.data_parallel_rank = data_parallel_rank
tp_size = get_tp_group().world_size
if self.tp_size % tp_size:
raise ValueError(
"ETP size must be divisible by TP size, but got "
f"ETP={self.tp_size} and TP={tp_size}"
)
self.etp_data_parallel_size = self.tp_size // tp_size
@abstractmethod
def allocate_embedding_weight(
self,
num_embeddings: int,
embedding_dim: int,
dtype: torch.dtype,
) -> torch.Tensor:
"""Allocate storage for the complete embedding weight."""
raise NotImplementedError
def dequantize(
self,
embeddings: torch.Tensor,
output_dtype: torch.dtype,
) -> torch.Tensor:
"""Delegate storage-format conversion to the embedding method."""
return self.embedding_method.dequantize(self, embeddings, output_dtype)
def _get_dp_gather_slot(self, local_num_tokens: int) -> tuple[int, int]:
"""Return the per-DP slot size and this rank's slot offset."""
if self.etp_data_parallel_size == 1:
return local_num_tokens, 0
dp_metadata: DPMetadata | None = get_forward_context().dp_metadata
if dp_metadata is None:
raise RuntimeError("ETP spanning DP requires DP token metadata")
group_start = (self.data_parallel_rank // self.etp_data_parallel_size) * (
self.etp_data_parallel_size
)
group_end = group_start + self.etp_data_parallel_size
token_counts = dp_metadata.num_tokens_across_dp_cpu.tolist()
group_counts = token_counts[group_start:group_end]
slot_size = max(group_counts)
dp_rank = get_dp_group().rank_in_group
return slot_size, dp_rank * slot_size
def _gather_dp_ids(
self,
ngram_ids: torch.Tensor,
slot_size: int,
) -> torch.Tensor:
"""Gather DP-local IDs that share one ETP-sharded PLE table."""
if self.etp_data_parallel_size == 1:
return ngram_ids
if ngram_ids.shape[0] < slot_size:
padding = ngram_ids.new_zeros(
slot_size - ngram_ids.shape[0], ngram_ids.shape[1]
)
ngram_ids = torch.cat((ngram_ids, padding), dim=0)
return get_dp_group().all_gather(ngram_ids, dim=0)
def _select_embeddings(
self,
embeddings: torch.Tensor,
local_num_tokens: int,
slot_offset: int,
) -> torch.Tensor:
"""Select this DP rank's rows from the ETP-reduced embeddings."""
if self.etp_data_parallel_size == 1:
return embeddings
return embeddings.narrow(0, slot_offset, local_num_tokens)
@abstractmethod
def start_prefetch(self, ngram_ids: torch.Tensor) -> None:
"""Start an asynchronous lookup when supported."""
raise NotImplementedError
@abstractmethod
def finalize_prefetch(self, total_tokens: int) -> torch.Tensor:
"""Return prefetched embeddings for the full input token set."""
raise NotImplementedError
class Qwen4ExpPLEEmbeddingMethod(QuantizeMethodBase):
"""Quantization interface shared by resident and pinned PLE tables."""
# PLE post-load processing only validates scales in their current storage.
requires_device_loading: bool = False
@staticmethod
def from_quant_config(
quant_config: QuantizationConfig | None,
prefix: str,
embedding_dtype: str | None = None,
) -> "Qwen4ExpPLEEmbeddingMethod":
"""Select the concrete PLE embedding format for a layer."""
if embedding_dtype == "nvfp4":
return Qwen4ExpPLENvFp4EmbeddingMethod()
if embedding_dtype == "float8_e4m3fn":
return Qwen4ExpPLEFp8EmbeddingMethod()
if embedding_dtype is not None:
raise ValueError(
"ple_embedding_dtype must be 'nvfp4' or 'float8_e4m3fn', "
f"got {embedding_dtype!r}"
)
if quant_config is None:
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
if isinstance(quant_config, ModelOptQuantConfigBase):
if quant_config.is_layer_excluded(prefix):
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
if isinstance(quant_config, ModelOptMixedPrecisionConfig):
quant_algo = quant_config._resolve_quant_algo(prefix)
if quant_algo == "FP8":
return Qwen4ExpPLEFp8EmbeddingMethod()
if quant_algo == "NVFP4":
return Qwen4ExpPLENvFp4EmbeddingMethod()
if quant_algo is not None:
raise ValueError(
f"Unsupported quant_algo {quant_algo!r} for the PLE embedding "
f"{prefix!r}; expected FP8 or NVFP4"
)
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
if (
isinstance(quant_config, CompressedTensorsConfig)
and quant_config.get_scheme_dict(None, layer_name=prefix) is None
):
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
if (
isinstance(quant_config, INCConfig)
and not quant_config.config_parser.resolve(None, prefix).quantized
):
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
# Quark quantizes only Linear and MoE layers; PLE tables stay BF16.
if isinstance(quant_config, QuarkConfig):
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
if isinstance(quant_config, Fp8Config):
ignored_layers = quant_config.ignored_layers
if is_layer_skipped(
prefix,
ignored_layers,
quant_config.packed_modules_mapping,
match_mode=quant_config.ignored_layers_match_mode,
):
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
# PLE checkpoint shards form one runtime embedding parameter.
shard_prefix = f"{prefix}.shard_"
if any(name.startswith(shard_prefix) for name in ignored_layers):
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
if quant_config.is_checkpoint_fp8_serialized:
return Qwen4ExpPLEFp8EmbeddingMethod()
raise NotImplementedError(
"Qwen4Exp PLE embedding only supports serialized FP8 checkpoints"
)
logger.warning(
"Falling back to unquantized PLE embedding for %s "
"with quantization config %s.",
prefix,
type(quant_config).__name__,
)
return Qwen4ExpPLEUnquantizedEmbeddingMethod()
def apply(
self,
layer: nn.Module,
x: torch.Tensor,
bias: torch.Tensor | None = None,
) -> torch.Tensor:
raise NotImplementedError("PLE weights only support embedding lookup")
def embedding(self, layer: nn.Module, input_: torch.Tensor) -> torch.Tensor:
return F.embedding(input_, layer.weight)
def lookup_dtype(self, layer: nn.Module) -> torch.dtype:
"""Return the dtype of rows that a pinned-host lookup writes."""
return layer.weight.dtype
def lookup_from_pinned(
self,
layer: "Qwen4ExpPLEPinnedHostEmbedding",
ids: torch.Tensor,
output: torch.Tensor,
) -> None:
"""Copy the ETP-owned rows for ``ids`` from pinned host memory."""
row_bytes = layer.embedding_dim * layer.weight.element_size()
_lookup_ple_embedding_from_pinned_kernel[(ids.numel(),)](
layer._uva_weight,
ids,
output,
row_bytes,
layer.shard_indices.org_vocab_start_index,
layer.shard_indices.org_vocab_end_index,
BLOCK_D=triton.next_power_of_2(row_bytes),
)
@abstractmethod
def dequantize(
self,
layer: nn.Module,
embeddings: torch.Tensor,
output_dtype: torch.dtype,
) -> torch.Tensor:
"""Convert looked-up PLE rows to the activation dtype."""
raise NotImplementedError
class Qwen4ExpPLEUnquantizedEmbeddingMethod(Qwen4ExpPLEEmbeddingMethod):
"""Unquantized PLE embedding storage and lookup semantics."""
def create_weights(
self,
layer: Qwen4ExpPLEEmbedding,
input_size_per_partition: int,
output_partition_sizes: list[int],
input_size: int,
output_size: int,
params_dtype: torch.dtype,
**extra_weight_attrs,
) -> None:
del input_size, output_size
weight = nn.Parameter(
layer.allocate_embedding_weight(
sum(output_partition_sizes),
input_size_per_partition,
params_dtype,
),
requires_grad=False,
)
set_weight_attrs(weight, {"input_dim": 1, "output_dim": 0})
set_weight_attrs(weight, extra_weight_attrs)
layer.register_parameter("weight", weight)
def dequantize(
self,
layer: nn.Module,
embeddings: torch.Tensor,
output_dtype: torch.dtype,
) -> torch.Tensor:
del layer, output_dtype
return embeddings
class Qwen4ExpPLEFp8EmbeddingMethod(Qwen4ExpPLEEmbeddingMethod):
"""FP8 PLE embedding with one global checkpoint scale."""
def create_weights(
self,
layer: Qwen4ExpPLEEmbedding,
input_size_per_partition: int,
output_partition_sizes: list[int],
input_size: int,
output_size: int,
params_dtype: torch.dtype,
**extra_weight_attrs,
) -> None:
del input_size, output_size, params_dtype
weight_loader = extra_weight_attrs.get("weight_loader")
weight = ModelWeightParameter(
data=layer.allocate_embedding_weight(
sum(output_partition_sizes),
input_size_per_partition,
torch.float8_e4m3fn,
),
input_dim=1,
output_dim=0,
weight_loader=weight_loader,
)
layer.register_parameter("weight", weight)
weight_scale = create_fp8_scale_parameter(
PerTensorScaleParameter,
output_partition_sizes,
input_size_per_partition,
None,
weight_loader,
scale_dtype=torch.float32,
)
layer.register_parameter("weight_scale", weight_scale)
def process_weights_after_loading(self, layer: nn.Module) -> None:
"""Reject FP8 PLE checkpoints without a global scale."""
sentinel = torch.finfo(torch.float32).min
if torch.any(layer.weight_scale == sentinel):
raise ValueError("FP8 PLE checkpoint is missing its global scale")
def dequantize(
self,
layer: nn.Module,
embeddings: torch.Tensor,
output_dtype: torch.dtype,
) -> torch.Tensor:
weight_scale = getattr(layer, "weight_scale", None)
if weight_scale is None:
raise RuntimeError("FP8 PLE embedding is missing its global scale")
if weight_scale.device != embeddings.device:
raise RuntimeError("FP8 PLE embedding scale must be on the output device")
return embeddings.to(output_dtype) * weight_scale.to(output_dtype)
# NVFP4 packs two E2M1 values per byte and shares one E4M3 scale per block.
_NVFP4_VALUES_PER_BYTE = 2
_NVFP4_BLOCK_SIZE = 16
class Qwen4ExpPLENvFp4EmbeddingMethod(Qwen4ExpPLEEmbeddingMethod):
"""NVFP4 PLE embedding: packed E2M1 rows, block scales, one global scale.
Rows stay packed in storage. A lookup decodes only the selected rows to
the activation dtype, before the ETP reduction.
"""
def create_weights(
self,
layer: Qwen4ExpPLEEmbedding,
input_size_per_partition: int,
output_partition_sizes: list[int],
input_size: int,
output_size: int,
params_dtype: torch.dtype,
**extra_weight_attrs,
) -> None:
del input_size, output_size, params_dtype
if input_size_per_partition % _NVFP4_BLOCK_SIZE:
raise ValueError(
"NVFP4 PLE embedding dimension must be divisible by "
f"{_NVFP4_BLOCK_SIZE}, got {input_size_per_partition}"
)
weight_loader = extra_weight_attrs.get("weight_loader")
num_rows = sum(output_partition_sizes)
for name, width, dtype in (
(
"weight",
input_size_per_partition // _NVFP4_VALUES_PER_BYTE,
torch.uint8,
),
(
"weight_scale",
input_size_per_partition // _NVFP4_BLOCK_SIZE,
torch.float8_e4m3fn,
),
):
parameter = ModelWeightParameter(
data=layer.allocate_embedding_weight(num_rows, width, dtype),
input_dim=1,
output_dim=0,
weight_loader=weight_loader,
)
layer.register_parameter(name, parameter)
weight_scale_2 = create_fp8_scale_parameter(
PerTensorScaleParameter,
output_partition_sizes,
input_size_per_partition,
None,
weight_loader,
scale_dtype=torch.float32,
)
layer.register_parameter("weight_scale_2", weight_scale_2)
if layer.weight_scale.is_pinned():
self._uva_weight_scale = get_accelerator_view_from_cpu_tensor(
layer.weight_scale
)
def process_weights_after_loading(self, layer: nn.Module) -> None:
"""Reject NVFP4 PLE checkpoints without a global scale."""
sentinel = torch.finfo(torch.float32).min
if torch.any(layer.weight_scale_2 == sentinel):
raise ValueError("NVFP4 PLE checkpoint is missing its global scale")
def lookup_dtype(self, layer: nn.Module) -> torch.dtype:
return layer.params_dtype
def embedding(self, layer: nn.Module, input_: torch.Tensor) -> torch.Tensor:
ids = input_.reshape(-1)
if not input_.is_cuda:
rows = dequantize_to_dtype(
F.embedding(ids, layer.weight),
F.embedding(ids, layer.weight_scale),
layer.weight_scale_2,
layer.params_dtype,
swizzle=False,
)
return rows.reshape(*input_.shape, layer.embedding_dim)
output = torch.empty(
(*input_.shape, layer.embedding_dim),
dtype=layer.params_dtype,
device=input_.device,
)
if ids.numel():
# Device rows are already local, so every ID in range is owned.
_lookup_nvfp4_ple_embedding_kernel[(ids.numel(),)](
layer.weight,
layer.weight_scale.view(torch.uint8),
layer.weight_scale_2,
ids,
output,
layer.embedding_dim,
0,
layer.weight.shape[0],
BLOCK_D=triton.next_power_of_2(layer.embedding_dim),
)
return output
def lookup_from_pinned(
self,
layer: "Qwen4ExpPLEPinnedHostEmbedding",
ids: torch.Tensor,
output: torch.Tensor,
) -> None:
_lookup_nvfp4_ple_embedding_kernel[(ids.numel(),)](
layer._uva_weight,
self._uva_weight_scale.view(torch.uint8),
layer.weight_scale_2,
ids,
output,
layer.embedding_dim,
layer.shard_indices.org_vocab_start_index,
layer.shard_indices.org_vocab_end_index,
BLOCK_D=triton.next_power_of_2(layer.embedding_dim),
)
def dequantize(
self,
layer: nn.Module,
embeddings: torch.Tensor,
output_dtype: torch.dtype,
) -> torch.Tensor:
del layer
# The lookup already decoded the rows to the activation dtype.
return embeddings.to(output_dtype)
@triton.jit
def _lookup_nvfp4_ple_embedding_kernel(
weight_ptr,
scale_ptr,
global_scale_ptr,
ids_ptr,
output_ptr,
embedding_dim,
vocab_start,
vocab_end,
BLOCK_D: tl.constexpr,
):
"""Decode owned NVFP4 PLE rows; write zeros for rows another rank owns."""
row = tl.program_id(0).to(tl.int64)
idx = tl.load(ids_ptr + row).to(tl.int64)
owned = (idx >= vocab_start) & (idx < vocab_end)
local_idx = tl.where(owned, idx - vocab_start, 0)
offsets = tl.arange(0, BLOCK_D)
in_row = offsets < embedding_dim
mask = owned & in_row
packed = tl.load(
weight_ptr + local_idx * (embedding_dim // 2) + offsets // 2,
mask=mask,
other=0,
)
# The low nibble holds the even element and the high nibble the odd one.
codes = (packed >> ((offsets % 2) * 4)) & 0xF
scales = _e4m3_inline(
tl.load(
scale_ptr + local_idx * (embedding_dim // 16) + offsets // 16,
mask=mask,
other=0,
)
)
global_scale = tl.load(global_scale_ptr).to(tl.float32)
values = _e2m1_inline(codes) * scales * global_scale
tl.store(output_ptr + row * embedding_dim + offsets, values, mask=in_row)
class Qwen4ExpPLEDeviceEmbedding(Qwen4ExpPLEEmbedding):
"""PLE table allocated on the active model device."""
def allocate_embedding_weight(
self,
num_embeddings: int,
embedding_dim: int,
dtype: torch.dtype,
) -> torch.Tensor:
"""Allocate the complete PLE weight on the active device."""
return torch.empty(num_embeddings, embedding_dim, dtype=dtype)
def start_prefetch(self, ngram_ids: torch.Tensor) -> None:
"""Resident embedding prefetch is a no-op."""
return None
def finalize_prefetch(self, total_tokens: int) -> torch.Tensor:
"""Reject prefetch finalization for resident embeddings."""
raise RuntimeError("Device PLE embedding does not support prefetch")
def forward(self, ngram_ids: torch.Tensor) -> torch.Tensor:
"""Gather ETP IDs, optionally reduce lookup results, and select local rows."""
slot_size, slot_offset = self._get_dp_gather_slot(ngram_ids.shape[0])
gathered_ids = self._gather_dp_ids(ngram_ids, slot_size)
embeddings = super().forward(gathered_ids)
return self._select_embeddings(
embeddings,
ngram_ids.shape[0],
slot_offset,
)
@triton.jit
def _lookup_ple_embedding_from_pinned_kernel(
weight_ptr: tl.pointer_type(tl.uint8), # type: ignore[valid-type]
ids_ptr,
output_ptr: tl.pointer_type(tl.uint8), # type: ignore[valid-type]
row_bytes,
tp_vocab_start,
tp_vocab_end,
BLOCK_D: tl.constexpr,
):
"""Copy TP-owned PLE rows as raw bytes from a CUDA view of pinned host memory.
Byte pointers keep the storage dtype out of the kernel signature, so
dtypes Triton cannot lower on the GPU (FP8 E4M3FN before SM89) still work.
"""
row_id = tl.program_id(0).to(tl.int64)
global_idx = tl.load(ids_ptr + row_id)
in_range = (global_idx >= tp_vocab_start) & (global_idx < tp_vocab_end)
local_idx = tl.where(in_range, global_idx - tp_vocab_start, 0)
offsets = tl.arange(0, BLOCK_D)
store_mask = offsets < row_bytes
load_mask = store_mask & in_range
values = tl.load(
weight_ptr + local_idx * row_bytes + offsets,
mask=load_mask,
other=0,
)
tl.store(
output_ptr + row_id * row_bytes + offsets,
values,
mask=store_mask,
)
class Qwen4ExpPLEPinnedHostEmbedding(Qwen4ExpPLEEmbedding):
"""PLE table loaded into pinned CPU memory and looked up through UVA."""
supports_prefetch: ClassVar[bool] = True
def __init__(
self,
num_embeddings: int,
embedding_dim: int,
*,
params_dtype: torch.dtype,
padding_size: int,
prefix: str,
embedding_method: Qwen4ExpPLEEmbeddingMethod,
num_ngram_heads: int = 1,
max_total_tokens: int = 0,
data_parallel_rank: int = 0,
reduce_results: bool = True,
) -> None:
if not is_uva_available():
raise RuntimeError("Engram CPU offload requires UVA support")
super().__init__(
num_embeddings,
embedding_dim,
params_dtype=params_dtype,
padding_size=padding_size,
prefix=prefix,
embedding_method=embedding_method,
num_ngram_heads=num_ngram_heads,
max_total_tokens=max_total_tokens,
data_parallel_rank=data_parallel_rank,
reduce_results=reduce_results,
)
self._uva_weight = get_accelerator_view_from_cpu_tensor(self.weight)
self._prefetch_stream: torch.cuda.Stream | None = None
self._prefetch_buffer: torch.Tensor | None = None
self._prefetch_alloc_lock = threading.Lock()
self._prefetch_rows = max_total_tokens * self.etp_data_parallel_size
self._num_ngram_heads = num_ngram_heads
self._output_dim = num_ngram_heads * self.embedding_dim
def allocate_embedding_weight(
self,
num_embeddings: int,
embedding_dim: int,
dtype: torch.dtype,
) -> torch.Tensor:
"""Allocate the complete PLE weight directly in pinned CPU memory."""
return torch.empty(
num_embeddings,
embedding_dim,
dtype=dtype,
device="cpu",
pin_memory=True,
)
def _lookup(
self,
input_ids: torch.Tensor,
output: torch.Tensor | None = None,
) -> torch.Tensor:
"""Look up local ETP rows in the embedding method's lookup dtype."""
expected_shape = (*input_ids.shape, self.embedding_dim)
lookup_dtype = self.embedding_method.lookup_dtype(self)
if output is None:
output = torch.empty(
expected_shape,
dtype=lookup_dtype,
device=input_ids.device,
)
elif (
tuple(output.shape) != expected_shape
or output.dtype != lookup_dtype
or output.device != input_ids.device
):
raise ValueError(
"PLE prefetch output must match the input shape, lookup dtype, "
"and input device"
)
flat_ids = input_ids.reshape(-1).long()
if flat_ids.numel():
self.embedding_method.lookup_from_pinned(self, flat_ids, output)
return output
def sync_lookup(self, ngram_ids: torch.Tensor) -> torch.Tensor:
"""Synchronous UVA lookup for platforms without prefetch wiring."""
slot_size, slot_offset = self._get_dp_gather_slot(ngram_ids.shape[0])
gathered_ids = self._gather_dp_ids(ngram_ids, slot_size)
embeddings = self._lookup(gathered_ids)
if self.reduce_results:
embeddings = self._reduce_etp_embeddings(embeddings)
else:
# The outer TP reduce-scatter requires ETP=TP.
assert self.etp_data_parallel_size == 1
return self._select_embeddings(
embeddings,
ngram_ids.shape[0],
slot_offset,
)
def _reduce_etp_embeddings(self, embeddings: torch.Tensor) -> torch.Tensor:
"""Combine pinned lookup results owned by different ETP ranks."""
if self.tp_size == 1:
return embeddings
assert self.parallel_group is not None
if embeddings.dtype in (torch.float8_e4m3fn, torch.float8_e5m2):
# Each vocabulary row has one owner, so reduce the raw FP8 bytes.
reduced = self.parallel_group.all_reduce(embeddings.view(torch.int8))
return reduced.view(embeddings.dtype)
return self.parallel_group.all_reduce(embeddings)
@eager_break_during_capture
def start_prefetch(self, ngram_ids: torch.Tensor) -> None:
"""Gather ETP IDs and launch their UVA lookup on the side stream."""
buffer = self._prefetch_buffer
if buffer is None:
# First use allocates. The eager profile run always precedes
# cudagraph capture, so allocation never happens mid-capture;
# the lock keeps concurrent first callers from tearing the
# stream/buffer pair.
with self._prefetch_alloc_lock:
buffer = self._prefetch_buffer
if buffer is None:
if torch.cuda.is_current_stream_capturing():
raise RuntimeError(
"pinned PLE prefetch buffer must be allocated "
"eagerly, before cudagraph capture"
)
self._prefetch_stream = torch.cuda.Stream(
device=self._uva_weight.device
)
buffer = torch.empty(
self._prefetch_rows,
self._num_ngram_heads,
self.embedding_dim,
dtype=self.embedding_method.lookup_dtype(self),
device=self._uva_weight.device,
)
self._prefetch_buffer = buffer
prefetch_stream = self._prefetch_stream
if prefetch_stream is None:
raise RuntimeError("pinned PLE prefetch stream was not allocated")
slot_size, _ = self._get_dp_gather_slot(ngram_ids.shape[0])
gathered_ids = self._gather_dp_ids(ngram_ids, slot_size)
if gathered_ids.shape[0] > buffer.shape[0]:
raise ValueError(
f"pinned PLE prefetch buffer holds {buffer.shape[0]} rows, "
f"but the batch needs {gathered_ids.shape[0]}"
)
active_output = buffer[: gathered_ids.shape[0]]
prefetch_stream.wait_stream(torch.cuda.current_stream())
# Prevent ID storage reuse until the prefetch stream finishes reading it.
gathered_ids.record_stream(prefetch_stream)
with torch.cuda.stream(prefetch_stream):
self._lookup(gathered_ids, output=active_output)
@eager_break_during_capture
def _finalize_prefetch(
self,
prefetch_output: torch.Tensor,
output: torch.Tensor,
) -> None:
"""Join the side stream, optionally reduce ETP shards, and select rows."""
prefetch_stream = self._prefetch_stream
if prefetch_stream is None:
raise RuntimeError("pinned PLE finalize requires a prior start_prefetch")
torch.cuda.current_stream().wait_stream(prefetch_stream)
slot_size, slot_offset = self._get_dp_gather_slot(output.shape[0])
embeddings = prefetch_output[: slot_size * self.etp_data_parallel_size]
if self.reduce_results:
embeddings = self._reduce_etp_embeddings(embeddings)
else:
# The outer TP reduce-scatter requires ETP=TP.
assert self.etp_data_parallel_size == 1
embeddings = self._select_embeddings(
embeddings,
output.shape[0],
slot_offset,
)
output.copy_(embeddings.flatten(-2))
def finalize_prefetch(self, total_tokens: int) -> torch.Tensor:
"""Finalize the full-token pinned lookup with optional ETP reduction."""
buffer = self._prefetch_buffer
if buffer is None:
raise RuntimeError("pinned PLE lookup requires a prior start_prefetch")
# total_tokens counts this DP rank's tokens before SP sharding.
output = buffer.new_empty((total_tokens, self._output_dim))
self._finalize_prefetch(buffer, output)
return output
def forward(self, input_: torch.Tensor) -> torch.Tensor:
raise RuntimeError(
"Pinned-host PLE embedding requires "
"start_prefetch() and finalize_prefetch()."
)