Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
59b9bfa
WIP adding SSM support
santhnm2 Jul 21, 2025
8f8e7ba
Initial implementation
santhnm2 Jul 28, 2025
a7710d2
Merge with main
santhnm2 Jul 28, 2025
43e1385
Remove is_prefill_only function
santhnm2 Jul 28, 2025
274e277
Add TODO for Mamba state memory
santhnm2 Jul 28, 2025
7892eb8
Working without CUDA graphs
santhnm2 Jul 28, 2025
32fa21d
Formatting
santhnm2 Jul 28, 2025
df19a23
Make self.layer_map a dictionary instead of a GPU tensor
santhnm2 Jul 28, 2025
e3177aa
Use dictionary for non-hybrid layer map
santhnm2 Jul 28, 2025
88e241f
Cleanup
santhnm2 Jul 28, 2025
f6998d5
Address reviewer feedback and add unit tests
santhnm2 Jul 30, 2025
2753299
Bug fixes and add unit tests for DynamicInferenceContext
santhnm2 Jul 31, 2025
90c503d
Minor fix
santhnm2 Jul 31, 2025
6c0fbe2
Minor fixes
santhnm2 Jul 31, 2025
58c99d1
Clean up printing for TP > 1
santhnm2 Jul 31, 2025
dc56215
Minor perf improvements
santhnm2 Aug 1, 2025
6c2f52e
Merge with main
santhnm2 Aug 1, 2025
5a1f44d
Address feedback
santhnm2 Aug 1, 2025
f6ff0d2
Remove seq_idx function and version guard all API changes
santhnm2 Aug 4, 2025
041b8b4
Fix bug
santhnm2 Aug 5, 2025
b29afb2
Add comments
santhnm2 Aug 5, 2025
d7ee01c
Cleanup
santhnm2 Aug 5, 2025
0b169cf
Remove print_rank_0 and update run_inference_performance_test.py
santhnm2 Aug 5, 2025
dd95f89
Address comments
santhnm2 Aug 6, 2025
c33ccb3
Add fp8 inference support with unit test, clean up version guard
santhnm2 Aug 6, 2025
f01c85f
Simplify mamba state shape allocation
santhnm2 Aug 8, 2025
32cce56
Remove dead code
santhnm2 Aug 8, 2025
37361eb
Update comment
santhnm2 Aug 8, 2025
dcbfcfd
Fix unit tests
santhnm2 Aug 11, 2025
7b35710
Merge with main
santhnm2 Aug 18, 2025
3dc87f4
Remove is_hybrid_model from comment
santhnm2 Aug 18, 2025
3287cde
Merge with main
santhnm2 Aug 20, 2025
35b7b4b
WIP dynamic engine fixes
santhnm2 Aug 25, 2025
4b6f2f1
seems to be working...
santhnm2 Aug 25, 2025
044b473
Bug fixes
santhnm2 Aug 26, 2025
05b8307
Remove debugging code
santhnm2 Aug 26, 2025
cae79ec
fix
santhnm2 Aug 26, 2025
6359b8f
WIP debugging
santhnm2 Sep 8, 2025
a83984e
Microbatch prefill
santhnm2 Sep 8, 2025
0a0c502
Bug fix
santhnm2 Sep 8, 2025
49e6b91
Bug fix
santhnm2 Sep 8, 2025
ad9bfb3
disable seq_idx
santhnm2 Sep 9, 2025
e992ee2
Working!
santhnm2 Sep 9, 2025
bb0cf08
Add back varlen kernels, still working
santhnm2 Sep 10, 2025
504f8bc
WIP handle padding properly
santhnm2 Sep 15, 2025
74c78ea
Merge with main
santhnm2 Sep 16, 2025
55c5fb2
Undo deletion of nemotron_h_aligned template
tdene Sep 16, 2025
1616d00
Merge remote-tracking branch 'upstream/tde/bring_back_nemotron_chat' …
santhnm2 Sep 16, 2025
3e229d5
Bug fixes
santhnm2 Sep 17, 2025
99d3532
More bug fixes
santhnm2 Sep 17, 2025
536b2a0
Minor optimization
santhnm2 Sep 19, 2025
b504ef3
Merge with main
santhnm2 Sep 19, 2025
ed5cb98
Merge with main
santhnm2 Sep 29, 2025
eeefe0a
In progress test fixes
santhnm2 Sep 29, 2025
4d20537
Undo generated text change
santhnm2 Sep 29, 2025
c99790d
Undo text generation server changes
santhnm2 Sep 29, 2025
98423dd
Minor fixes
santhnm2 Sep 29, 2025
eb21382
Fix sequence parallelism
santhnm2 Oct 1, 2025
af3c126
Merge remote-tracking branch 'upstream/main' into dynamic_engine_ssm_v2
santhnm2 Oct 6, 2025
7b1ef6e
Merge with main, chunked prefill seems to work
santhnm2 Oct 7, 2025
408f3ae
Clean up shapes
santhnm2 Oct 7, 2025
6f630d4
Sequence parallel fix
santhnm2 Oct 7, 2025
2502d83
Merge with main
santhnm2 Oct 8, 2025
f22e96f
Merge remote-tracking branch 'upstream/main' into dynamic_engine_ssm_v2
santhnm2 Oct 9, 2025
292f2fe
Remove print
santhnm2 Oct 9, 2025
46399dd
Merge with main
santhnm2 Oct 15, 2025
0c09eab
Bug fixes
santhnm2 Oct 15, 2025
e4d3545
Formatting
santhnm2 Oct 15, 2025
6125f42
Formatting
santhnm2 Oct 15, 2025
c572f9e
Merge remote-tracking branch 'upstream/main' into dynamic_engine_ssm_v2
santhnm2 Oct 16, 2025
90e4f9f
Remove unnecessary changes
santhnm2 Oct 16, 2025
3683ed5
Update min library versions
santhnm2 Oct 16, 2025
823692e
Fix typo
santhnm2 Oct 16, 2025
8bc3de6
Minor fix
santhnm2 Oct 16, 2025
ecef7ee
Remove explicit args / kwargs
santhnm2 Oct 17, 2025
dfe525f
Merge with main
santhnm2 Oct 19, 2025
a71b171
Min diff
santhnm2 Oct 19, 2025
3d798d4
Update tools/run_inference_performance_test.py
santhnm2 Oct 20, 2025
176e68f
Merge with main
santhnm2 Oct 21, 2025
4746d85
Merge with main
santhnm2 Oct 23, 2025
4f6d7bb
Merge remote-tracking branch 'upstream/main' into dynamic_engine_ssm
santhnm2 Oct 23, 2025
04d1e14
Fix quotes
santhnm2 Oct 23, 2025
afaa59b
Merge remote-tracking branch 'upstream/main' into dynamic_engine_ssm
santhnm2 Oct 28, 2025
8f2538c
Merge with main
santhnm2 Oct 29, 2025
e4ce9df
Address comments
santhnm2 Oct 30, 2025
30e2ed5
Merge with main
santhnm2 Nov 7, 2025
b96b482
Use separate MambaMetadata class
santhnm2 Nov 7, 2025
146fa25
Add mamba metadata file
santhnm2 Nov 7, 2025
a60c681
Move buffer allocation out of MambaMetadata
santhnm2 Nov 7, 2025
867ca40
Merge with main
santhnm2 Nov 7, 2025
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
66 changes: 49 additions & 17 deletions examples/inference/gpt/gpt_dynamic_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from collections import defaultdict
from functools import partial
from tqdm import tqdm
from typing import Dict, List, Optional
from typing import Dict, List, Tuple, Optional

import torch
from tqdm import tqdm
Expand All @@ -28,18 +28,21 @@
from megatron.core.inference.text_generation_controllers.text_generation_controller import (
TextGenerationController,
)
from megatron.core.ssm.mamba_hybrid_layer_allocation import Symbols
from megatron.core.tokenizers.text.utils.build_tokenizer import build_tokenizer
from megatron.core.transformer.module import MegatronModule
from megatron.core.utils import get_attr_wrapped_model

sys.path.append(
os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
)
from megatron.training import get_args, get_model as _get_model, get_tokenizer, initialize_megatron
from megatron.training.checkpointing import load_checkpoint

from megatron.core.utils import configure_nvtx_profiling
from model_provider import model_provider
from gpt_builders import gpt_builder
from mamba_builders import mamba_builder

from megatron.core.utils import configure_nvtx_profiling

import json

Expand All @@ -54,7 +57,6 @@
from megatron.training import get_model as _get_model
from megatron.training import get_tokenizer, initialize_megatron
from megatron.training.checkpointing import load_checkpoint
from pretrain_gpt import model_provider

import torch
import io
Expand Down Expand Up @@ -86,9 +88,16 @@ def get_model() -> MegatronModule:

args = get_args()

if args.model_provider == "gpt":
model_builder = gpt_builder
elif args.model_provider == "mamba":
model_builder = mamba_builder
else:
raise ValueError(f"Invalid model provider {args.model_provider}")

# Build model.
model = _get_model(
partial(model_provider, gpt_builder),
partial(model_provider, model_builder),
wrap_with_ddp=False
)

Expand All @@ -115,7 +124,10 @@ def get_model() -> MegatronModule:
def get_inference_context(
requests: List[Request],
sampling_params: Optional[SamplingParams] = None,
calculate_max_sequence_length_from_requests: bool = True
calculate_max_sequence_length_from_requests: bool = True,
layer_type_list: Optional[List[str]] = None,
mamba_conv_states_shape: Optional[Tuple[int]] = None,
mamba_ssm_states_shape: Optional[Tuple[int]] = None,
):
"""The inference context manages the KV cache and other inference state."""

Expand Down Expand Up @@ -154,6 +166,9 @@ def get_inference_context(
max_tokens_override=args.inference_dynamic_batching_max_tokens_override,
tensor_model_parallel_size=args.tensor_model_parallel_size,
materialize_only_last_token_logits=not args.return_log_probs,
layer_type_list=layer_type_list,
mamba_conv_states_shape=mamba_conv_states_shape,
mamba_ssm_states_shape=mamba_ssm_states_shape,
cache_mla_latent=args.multi_latent_attention and args.cache_mla_latents,
kv_lora_rank=args.kv_lora_rank if args.multi_latent_attention else None,
qk_pos_emb_head_dim=args.qk_pos_emb_head_dim,
Expand Down Expand Up @@ -364,21 +379,38 @@ def main():
termination_id=args.termination_id if args.termination_id is not None else tokenizer.eod,
)

# Requests, context, conroller.
model = get_model()

# Layer type list for hybrid models
decoder = get_attr_wrapped_model(model, "decoder")
layer_type_list = getattr(decoder, "layer_type_list", None)
if layer_type_list is not None and Symbols.MAMBA in layer_type_list:
(mamba_conv_states_shape, mamba_ssm_states_shape) = decoder.mamba_state_shapes_per_request()
else:
mamba_conv_states_shape = None
mamba_ssm_states_shape = None

# Requests, context, controller.
requests = build_requests(args, tokenizer, sampling_params)
context = get_inference_context(requests, sampling_params)
context = get_inference_context(
requests,
sampling_params,
layer_type_list=layer_type_list,
mamba_conv_states_shape=mamba_conv_states_shape,
mamba_ssm_states_shape=mamba_ssm_states_shape,
)
controller = get_inference_controller(model, context)

# Validate all context_length's <= max_tokens.
invalid_prompt_length_map = {}
for request_idx, request in enumerate(requests):
if len(request.prompt_tokens) > context.max_tokens:
invalid_prompt_length_map[request_idx] = len(request.prompt_tokens)
assert not invalid_prompt_length_map, (
"request idxs with prompts longer than context.max_tokens: "
", ".join(f"{k}({v})" for k, v in invalid_prompt_length_map.items())
)
if args.disable_chunked_prefill:
invalid_prompt_length_map = {}
for request_idx, request in enumerate(requests):
if len(request.prompt_tokens) > context.max_tokens:
invalid_prompt_length_map[request_idx] = len(request.prompt_tokens)
assert not invalid_prompt_length_map, (
"request idxs with prompts longer than context.max_tokens: "
", ".join(f"{k}({v})" for k, v in invalid_prompt_length_map.items())
)

# Inference engine.
engine = DynamicInferenceEngine(
Expand Down Expand Up @@ -418,8 +450,8 @@ def main():
)

# Print unique prompts + outputs.
if torch.distributed.get_rank() == 0:

if torch.distributed.get_rank() == 0:
def escape_str(s):
return s.replace("\n", "\\n")

Expand Down
3 changes: 3 additions & 0 deletions examples/inference/gpt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ def arrival(r):
if len(time_offsets) == 0:
time_offsets = [0.0]

# Ensure first time is 0.
time_offsets = [to - time_offsets[0] for to in time_offsets]

# Truncate to num_requests.
assert len(time_offsets) >= num_requests
time_offsets = time_offsets[:num_requests]
Expand Down
106 changes: 106 additions & 0 deletions megatron/core/inference/contexts/attention_context/mamba_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import torch


class MambaMetadata:
"""Manages the metadata tensors required for Mamba layers during inference."""

def __init__(self, max_requests: int):
"""
Initializes the Mamba slot allocator.

Args:
max_requests (int): The maximum number of concurrent requests.
"""
self.max_requests = max_requests

# Metadata for mapping requests to slots in the static Mamba state buffer
self.request_to_mamba_state_idx = torch.full(
(self.max_requests,), -1, dtype=torch.int32, device=torch.cuda.current_device()
)

# Separate mapping used only for CUDA graph compatibility
self.request_to_mamba_state_idx_cudagraph_only = torch.full(
(self.max_requests,), -1, dtype=torch.int32, device=torch.cuda.current_device()
)

# Allocator for Mamba state slots
self.mamba_state_free_slots = torch.arange(
self.max_requests, dtype=torch.int32, device=torch.cuda.current_device()
)
self.mamba_state_free_slot_count = self.max_requests

def reset(self) -> None:
"""
Resets all Mamba states and frees all allocated slots.
"""
self.request_to_mamba_state_idx.fill_(-1)
self.request_to_mamba_state_idx_cudagraph_only.fill_(-1)

# Re-initialize the free slot pool
self.mamba_state_free_slots = torch.arange(
self.max_requests, dtype=torch.int32, device=torch.cuda.current_device()
)
self.mamba_state_free_slot_count = self.max_requests

def reset_cudagraph_mapping(self) -> None:
"""
Resets only the CUDA graph mapping tensor.
"""
self.request_to_mamba_state_idx_cudagraph_only.fill_(-1)

def update_cudagraph_mapping(
self, active_mamba_indices: torch.Tensor, num_active_requests: int
) -> None:
"""
Updates the dedicated CUDA graph mapping tensor with the indices
of currently active requests.

Args:
active_mamba_indices (Tensor): Tensor containing the Mamba slot indices
for active requests.
num_active_requests (int): The number of active requests.
"""
self.request_to_mamba_state_idx_cudagraph_only[0:num_active_requests] = active_mamba_indices

def allocate_slot(self) -> int:
"""
Allocates a new slot for a request in the Mamba state buffers.

Returns:
int: The index of the allocated slot.
Returns None if no slots are available.
"""
if self.mamba_state_free_slot_count == 0:
return None

# Get a free slot
self.mamba_state_free_slot_count -= 1
mamba_idx = self.mamba_state_free_slots[self.mamba_state_free_slot_count]

return mamba_idx

def free_slots(self, request_indices: torch.Tensor) -> None:
"""
Frees the Mamba state slots associated with the given request indices.

Args:
request_indices (Tensor): A 1D tensor of request indices to free.
"""
# Get the Mamba state indices for finished requests
mamba_indices_to_free = self.request_to_mamba_state_idx[request_indices]

# Filter out any invalid indices (e.g., -1)
mamba_indices_to_free = mamba_indices_to_free[mamba_indices_to_free != -1]
num_to_free = len(mamba_indices_to_free)

if num_to_free > 0:
# Add the freed indices back to the free slot pool
start_idx = self.mamba_state_free_slot_count
end_idx = start_idx + num_to_free
self.mamba_state_free_slots[start_idx:end_idx] = mamba_indices_to_free
self.mamba_state_free_slot_count = end_idx

# Invalidate the Mamba state index for the finished requests
self.request_to_mamba_state_idx[request_indices] = -1
Loading
Loading