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
5 changes: 5 additions & 0 deletions tensorrt_llm/_torch/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ class ModelConfig(Generic[TConfig]):
skip_create_weights_in_init: bool = False

spec_config: Optional["DecodingBaseConfig"] = None
# When False, the column-parallel LM head keeps its vocab-sharded output
# instead of all-gathering to full vocab. Used for one-model speculative
# draft models so greedy draft sampling can do a lighter TP gather. Defaults
# to True to preserve behavior for every non-draft model.
lm_head_gather_output: bool = True
lora_config: Optional["LoraConfig"] = None
sparse_attention_config: Optional["SparseAttentionConfig"] = None

Expand Down
18 changes: 16 additions & 2 deletions tensorrt_llm/_torch/models/modeling_speculative.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,9 @@ def __init__(self, draft_config):
draft_config.pretrained_config)

# Remove spec_config to prevent recursive spec-dec initialization
draft_config_no_spec = replace(draft_config, spec_config=None)
draft_config_no_spec = replace(draft_config,
spec_config=None,
lm_head_gather_output=False)

# Weights will be loaded later by ModelLoader.load_draft_weights()
self.draft_model_full = DraftModelClass(draft_config_no_spec)
Expand Down Expand Up @@ -863,7 +865,9 @@ def __init__(self, draft_config):
pretrained_cfg.architectures = original_archs

# Remove spec_config to prevent recursive spec-dec initialization
draft_config_no_spec = replace(draft_config, spec_config=None)
draft_config_no_spec = replace(draft_config,
spec_config=None,
lm_head_gather_output=False)

# Weights will be loaded later by ModelLoader.load_draft_weights()
self.draft_model_full = DraftModelClass(draft_config_no_spec)
Expand Down Expand Up @@ -1653,6 +1657,12 @@ def get_draft_model(model_config, draft_config, lm_head, model):
elif spec_dec_mode.is_dflash():
return DFlashForCausalLM(draft_config)
elif spec_dec_mode.is_draft_target_one_model():
# Keep the draft LM head vocab-sharded so greedy draft sampling uses the
# lighter TP gather (see SpecWorkerBase.greedy_sample_draft_with_tp_gather).
was_frozen = draft_config._frozen
draft_config._frozen = False
draft_config.lm_head_gather_output = False
draft_config._frozen = was_frozen
return AutoModelForCausalLM.from_config(draft_config)
else:
raise NotImplementedError(
Expand Down Expand Up @@ -1741,6 +1751,10 @@ def __init__(self, model: TModel, model_config: ModelConfig[TConfig]):
model_config.mapping,
use_separate_draft_kv_cache=self.use_separate_draft_kv_cache)
if self.spec_worker is not None:
# Cache the static draft->target vocab map now that the draft
# model is loaded, so workers read self._d2t instead of probing
# draft_model.model.d2t on every forward.
self.spec_worker.set_draft_model(self.draft_model)
self.epilogue.append(self.spec_worker)
self.layer_idx = -1

Expand Down
2 changes: 1 addition & 1 deletion tensorrt_llm/_torch/models/modeling_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ def __init__(self, model: TModel, *, config: ModelConfig[TConfig],
dtype=config.pretrained_config.torch_dtype,
mapping=config.mapping,
tensor_parallel_mode=TensorParallelMode.COLUMN,
gather_output=True,
gather_output=config.lm_head_gather_output,
reduce_output=False,
use_custom_cublas_mm=getattr(model, 'use_custom_cublas_mm',
False),
Expand Down
4 changes: 4 additions & 0 deletions tensorrt_llm/_torch/pyexecutor/llm_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,10 @@ def __init__(
self.py_target_probs = None
self.py_last_draft_tokens = None
self.py_num_accepted_draft_tokens = 0
# One-model rejection: set per-iteration by _handle_dynamic_draft_len when
# this gen request produced 0 real draft tokens, so _prepare_tp_inputs
# one-hots its stale draft_probs slot. Consumed (and cleared) there.
self.py_needs_onehot_draft_probs = False
self.py_num_accepted_draft_tokens_indices = []
self.py_rewind_draft_token_separate_adjustment = 0
self.py_per_pos_drafted = [0] * MAX_SPEC_DECODE_POSITIONS
Expand Down
13 changes: 13 additions & 0 deletions tensorrt_llm/_torch/pyexecutor/model_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -3296,6 +3296,10 @@ def _prepare_tp_inputs(
draft_tokens = []
draft_lens = []
gen_request_seq_slots = [] # per generation request
# One-model rejection: slots of gen requests that produced 0 real draft
# tokens this step (marked in _handle_dynamic_draft_len); their stale
# draft_probs rows are one-hot'd after spec_metadata.prepare().
padding_gen_slots = []
multimodal_params_list = []
mrope_position_ids = [
] # (start_idx, end_idx, (3,1,L) mrope_pos_ids) per multimodal request
Expand Down Expand Up @@ -3541,6 +3545,10 @@ def append_cross_attention_state(request: LlmRequest,
self.runtime_draft_len)
runtime_draft_token_buffer_width = runtime_tokens_per_gen_step - 1
for request in extend_requests:
if getattr(request, "py_needs_onehot_draft_probs", False):
if request.py_seq_slot is not None:
padding_gen_slots.append(request.py_seq_slot)
request.py_needs_onehot_draft_probs = False # consume once
request_ids.append(request.py_request_id)
request_accepted_path[
request.
Expand Down Expand Up @@ -4278,6 +4286,11 @@ def previous_seq_slots_device():
spec_metadata.populate_sampling_params_for_one_model(
scheduled_requests.all_requests())
spec_metadata.prepare()
# One-model rejection: one-hot the stale draft_probs rows of gen
# requests that produced no draft tokens this step, so the (possibly
# captured) rejection kernel reads a legal placeholder distribution.
spec_metadata.write_padding_onehot_draft_probs(
padding_gen_slots, self.runtime_draft_len)
inputs['spec_metadata'] = spec_metadata

if self.enable_attention_dp:
Expand Down
9 changes: 9 additions & 0 deletions tensorrt_llm/_torch/pyexecutor/py_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3194,8 +3194,17 @@ def _handle_dynamic_draft_len(self,
scheduled_batch.batch_size, self.model_engine.max_draft_len)
# 2. Pad or truncate draft tokens to the resolved length
DRAFT_BUFFER_PAD = 0 # Buffer sentinel, not PARD mask_token_id.
rejection_on = getattr(self.model_engine.spec_config,
"use_rejection_sampling", False)
for request in scheduled_batch.generation_requests:
current_num_draft_tokens = len(request.py_draft_tokens)
# One-model rejection: a gen request entering with 0 real draft
# tokens produced no draft-prob scatter for its slot last iter,
# so next iter's rejection kernel would read a stale draft_probs
# row. Mark it (pre-pad signal) so _prepare_tp_inputs writes a
# one-hot placeholder row after spec_metadata.prepare().
request.py_needs_onehot_draft_probs = (
rejection_on and current_num_draft_tokens == 0)
if spec_dec_mode.is_pard():
# special case: PARD carries 2K-1 draft tokens per request
runtime_draft_token_buffer_width = (
Expand Down
32 changes: 14 additions & 18 deletions tensorrt_llm/_torch/speculative/dflash.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,17 +407,8 @@ def forward(

self._execute_guided_decoder_if_present(logits)

# Target now emits K+1 logits per gen request and the previous step
# stored K draft tokens per gen request (no filler padding).
if num_gens > 0:
draft_tokens = spec_metadata.draft_tokens.reshape(num_gens, K)
else:
draft_tokens = spec_metadata.draft_tokens.reshape(0, K)

logits_for_accept = logits

accepted_tokens, num_accepted_tokens = self._sample_and_accept_draft_tokens_base(
logits_for_accept, draft_tokens, num_contexts, batch_size, spec_metadata
accepted_tokens, num_accepted_tokens = self.sample_and_accept_draft_tokens(
logits, attn_metadata, spec_metadata
)

# Update GDN/Mamba recurrent states to the accepted token's state.
Expand Down Expand Up @@ -498,17 +489,22 @@ def forward(
vocab_size = gen_logits.shape[-1]
gen_logits = gen_logits.reshape(num_gens, K, vocab_size)

d2t = getattr(draft_model.model, "d2t", None)
gen_draft_tokens = torch.argmax(gen_logits, dim=-1, keepdim=False).long()

if d2t is not None:
gen_draft_tokens = d2t[gen_draft_tokens] + gen_draft_tokens

gen_draft_tokens = gen_draft_tokens.type(torch.int32)
gen_draft_tokens = self.sample_draft_tokens(
gen_logits,
spec_metadata,
batch_size,
num_contexts=num_contexts,
)

else:
gen_draft_tokens = torch.empty((0, K), dtype=torch.int32, device="cuda")

# Context requests are not drafted by the block worker (zero placeholder
# token); fill their draft-prob slot rows with a one-hot placeholder so
# they are a legal distribution when they become gen requests next iter.
gen_vocab = vocab_size if num_gens > 0 else None
self.write_context_onehot_draft_probs(spec_metadata, num_contexts, num_gens, K, gen_vocab)

if num_contexts > 0 and num_gens > 0:
ctx_draft_tokens = torch.zeros((num_contexts, K), dtype=torch.int32, device="cuda")
next_draft_tokens = torch.cat([ctx_draft_tokens, gen_draft_tokens], dim=0)
Expand Down
40 changes: 7 additions & 33 deletions tensorrt_llm/_torch/speculative/draft_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,14 @@ def forward(
hidden_states[gather_ids], draft_model.lm_head, attn_metadata, True
)
if self.guided_decoder is not None:
d2t = getattr(draft_model.model, "d2t", None)
self.guided_decoder.execute_draft_batch(logits, d2t, draft_step=i)
self.guided_decoder.execute_draft_batch(logits, self._d2t, draft_step=i)

new_draft_token = self.draft_decoder(logits, draft_model)
new_draft_token = self.sample_draft_tokens(
logits,
spec_metadata,
batch_size,
draft_step=i,
)
next_draft_tokens.append(new_draft_token)

# Update inputs and metadata for next draft step
Expand Down Expand Up @@ -314,36 +318,6 @@ def forward(
"next_new_tokens": next_new_tokens,
}

def sample_and_accept_draft_tokens(
self,
logits: torch.Tensor,
attn_metadata: AttentionMetadata,
spec_metadata: DraftTargetOneModelSpecMetadata,
):
batch_size = attn_metadata.num_seqs
num_contexts = attn_metadata.num_contexts
num_gens = batch_size - num_contexts
runtime_draft_len = spec_metadata.runtime_draft_len

if spec_metadata.draft_tokens is None:
draft_tokens = torch.zeros(
(num_gens, runtime_draft_len), dtype=torch.int, device=logits.device
)
else:
draft_tokens = spec_metadata.draft_tokens.reshape(num_gens, runtime_draft_len)

return self._sample_and_accept_draft_tokens_base(
logits, draft_tokens, num_contexts, batch_size, spec_metadata
)

def draft_decoder(
self,
logits: torch.Tensor,
draft_model: nn.Module,
):
d2t = getattr(draft_model.model, "d2t", None)
return self._draft_sampler_greedy(logits, d2t)

def prepare_1st_drafter_inputs(
self,
input_ids: torch.LongTensor,
Expand Down
Loading
Loading