Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 52 additions & 8 deletions tests/v1/sample/test_head_dtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from vllm import LLM, SamplingParams
from vllm.model_executor.layers.logits_processor import LogitsProcessor
from vllm.model_executor.layers.vocab_parallel_embedding import (
ParallelLMHead,
UnquantizedEmbeddingMethod,
)

Expand All @@ -28,6 +29,7 @@ def __init__(
self.weight = weight
self.quant_method = object() if quantized else UnquantizedEmbeddingMethod()
self.shard_indices = shard_indices
self.tp_size = 1


def _build_processor(vocab_size: int) -> LogitsProcessor:
Expand Down Expand Up @@ -135,11 +137,59 @@ def test_fp32_head_rejects_quantized_lm_head(default_vllm_config):
lp._get_logits(torch.randn(4, 16, dtype=torch.bfloat16), lm_head, None)


def test_replicated_lm_head_skips_tp_communication_and_preserves_processing(
default_vllm_config,
):
from unittest import mock

vocab_size, hidden_size = 12, 8
soft_cap, scale = 2.0, 0.5
lp = LogitsProcessor(
vocab_size,
soft_cap=soft_cap,
scale=scale,
)
lp.head_dtype = torch.float32

hidden_states = torch.randn(4, hidden_size, dtype=torch.bfloat16)
weight = torch.randn(vocab_size, hidden_size, dtype=torch.bfloat16)
world_size_getter = (
"vllm.model_executor.layers.vocab_parallel_embedding."
"get_tensor_model_parallel_world_size"
)
with mock.patch(world_size_getter, return_value=2):
lm_head = ParallelLMHead(
vocab_size,
hidden_size,
params_dtype=torch.bfloat16,
disable_tp=True,
)
lm_head.weight_loader(lm_head.weight, weight)
assert lm_head.tp_size == 1

with mock.patch.object(lp, "_gather_logits") as gather_mock:
logits = lp(lm_head, hidden_states)

gather_mock.assert_not_called()

expected = torch.nn.functional.linear(hidden_states.float(), weight.float())
expected = torch.tanh(expected / soft_cap) * soft_cap * scale
torch.testing.assert_close(logits, expected)

all_gather_path = (
"vllm.model_executor.layers.logits_processor.tensor_model_parallel_all_gather"
)
with mock.patch(all_gather_path) as all_gather:
top = lp.get_top_tokens(lm_head, hidden_states)

all_gather.assert_not_called()
assert torch.equal(top, expected.argmax(dim=-1))


def test_get_top_tokens_honors_head_dtype(default_vllm_config):
# The spec-decode local-argmax path (get_top_tokens) must run the lm_head
# in head_dtype too, not just _get_logits.
import types
from unittest import mock

vocab_size, hidden_size = 64, 16
lp = _build_processor(vocab_size)
Expand All @@ -154,13 +204,7 @@ def test_get_top_tokens_honors_head_dtype(default_vllm_config):
),
)

with mock.patch(
"vllm.model_executor.layers.logits_processor."
"get_tensor_model_parallel_world_size",
return_value=1,
):
top = lp.get_top_tokens(lm_head, hidden_states, None)

top = lp.get_top_tokens(lm_head, hidden_states, None)
expected = torch.nn.functional.linear(hidden_states.float(), weight.float()).argmax(
dim=-1
)
Expand Down
6 changes: 3 additions & 3 deletions vllm/model_executor/layers/logits_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from vllm.config import get_current_vllm_config
from vllm.distributed import (
get_tensor_model_parallel_world_size,
tensor_model_parallel_all_gather,
tensor_model_parallel_gather,
)
Expand Down Expand Up @@ -145,7 +144,8 @@ def _get_logits(
logits = self._apply_head(lm_head, hidden_states, embedding_bias)

# Gather logits for TP
logits = self._gather_logits(logits)
if lm_head.tp_size > 1:
logits = self._gather_logits(logits)

# Remove paddings in vocab (if any).
if logits is not None:
Expand All @@ -169,7 +169,7 @@ def get_top_tokens(
"The local argmax reduction optimization is not supported for "
"non-positive logit scaling factors."
)
tp_size = get_tensor_model_parallel_world_size()
tp_size = lm_head.tp_size

logits = self._apply_head(lm_head, hidden_states, embedding_bias)
if self.soft_cap is not None:
Expand Down
29 changes: 24 additions & 5 deletions vllm/model_executor/layers/vocab_parallel_embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ class VocabParallelEmbedding(PluggableLayer):
padding_size: padding size for the vocabulary.
quant_config: quant config for the layer
prefix: full name of the layer in the state dict
disable_tp: If true, tensor parallelism will be disabled for this layer.
""" # noqa: E501

# --8<-- [end:vocab_parallel_embedding]
Expand All @@ -245,12 +246,19 @@ def __init__(
padding_size: int = DEFAULT_VOCAB_PADDING_SIZE,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
*,
disable_tp: bool = False,
):
super().__init__()

# Keep the input dimensions.
tp_rank = get_tensor_model_parallel_rank()
self.tp_size = get_tensor_model_parallel_world_size()
self.disable_tp = disable_tp
if disable_tp:
tp_rank, self.tp_size = 0, 1
else:
tp_rank = get_tensor_model_parallel_rank()
self.tp_size = get_tensor_model_parallel_world_size()
self.tp_rank = tp_rank
self.num_embeddings = num_embeddings
self.padding_size = padding_size
self.org_vocab_size = org_num_embeddings or num_embeddings
Expand Down Expand Up @@ -323,6 +331,13 @@ def __init__(
params_dtype=params_dtype,
weight_loader=self.weight_loader,
)
self.update_param_tp_status()

def update_param_tp_status(self):
for param in self.parameters():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is this for?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is for compat with weight reloading, see #48025. This is just mirroring LinearBase now that we have disable_tp in this class

if isinstance(param, BasevLLMParameter):
param.tp_rank = self.tp_rank
param.tp_size = self.tp_size

@classmethod
def _get_indices(
Expand Down Expand Up @@ -487,9 +502,9 @@ def forward(self, input_):
# Mask the output embedding.
if self.tp_size > 1:
output_parallel.masked_fill_(input_mask.unsqueeze(-1), 0)
# Reduce across all the model parallel GPUs.
output = tensor_model_parallel_all_reduce(output_parallel)
return output
# Reduce across all the model parallel GPUs.
return tensor_model_parallel_all_reduce(output_parallel)
return output_parallel

def extra_repr(self) -> str:
s = f"num_embeddings={self.num_embeddings_per_partition}"
Expand All @@ -516,6 +531,7 @@ class ParallelLMHead(VocabParallelEmbedding):
params_dtype: type of the parameters.
org_num_embeddings: original vocabulary size (without LoRA).
padding_size: padding size for the vocabulary.
disable_tp: If true, tensor parallelism will be disabled for this layer.
"""

# --8<-- [end:parallel_lm_head]
Expand All @@ -530,6 +546,8 @@ def __init__(
padding_size: int = DEFAULT_VOCAB_PADDING_SIZE,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
*,
disable_tp: bool = False,
):
super().__init__(
num_embeddings,
Expand All @@ -539,6 +557,7 @@ def __init__(
padding_size,
quant_config,
prefix,
disable_tp=disable_tp,
)
self.quant_config = quant_config
if bias:
Expand Down
22 changes: 15 additions & 7 deletions vllm/model_executor/models/qwen3_dspark.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from vllm.model_executor.layers.logits_processor import LogitsProcessor
from vllm.model_executor.layers.vocab_parallel_embedding import (
ParallelLMHead,
VocabParallelEmbedding,
)

from .qwen3_dflash import DFlashQwen3ForCausalLM, DFlashQwen3Model
Expand All @@ -40,6 +39,10 @@ class DSparkMarkovHead(nn.Module):
``vocab_size``); ``markov_w2`` projects it to a draft-vocab bias
(``draft_vocab_size``) added to the base draft logits. The two sizes
coincide for full-vocab drafts.

Both weights are replicated because the head runs sequentially for every
draft position. Sharding them would add an all-reduce and a full-vocab
gather to each position.
"""

def __init__(
Expand All @@ -50,19 +53,24 @@ def __init__(
prefix: str,
) -> None:
super().__init__()
# TODO(ben): profile for which (if any) it makes sense to replicate or TP-shard
self.markov_w1 = VocabParallelEmbedding(
vocab_size, markov_rank, prefix=maybe_prefix(prefix, "markov_w1")
)
self.markov_w1 = nn.Embedding(vocab_size, markov_rank)
self.markov_w2 = ParallelLMHead(
draft_vocab_size, markov_rank, prefix=maybe_prefix(prefix, "markov_w2")
draft_vocab_size,
markov_rank,
bias=False,
prefix=maybe_prefix(prefix, "markov_w2"),
disable_tp=True,
)

def embed(self, token_ids: torch.Tensor) -> torch.Tensor:
"""r-dim Markov embedding of ``token_ids`` ([B] -> [B, r])."""
return self.markov_w1(token_ids)

def bias(self, markov_embed: torch.Tensor, logits_processor) -> torch.Tensor:
def bias(
self,
markov_embed: torch.Tensor,
logits_processor: LogitsProcessor,
) -> torch.Tensor:
"""Vocab-size transition bias from a Markov embedding ([B, r] -> [B, V])."""
return logits_processor(self.markov_w2, markov_embed)

Expand Down
Loading