[Perf][DSv4.1] Faster Engram host lookups: sorted rows, inline big lookups (#59327)
Signed-off-by: Juntian Liu <Juntianl777@gmail.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Juntian Liu committed
Sep 30, 2026 at 20:54 UTC
4c2d277643e217344056e1d2c42115d5f005912f
6 files changed
+547
-556
tests/distributed/test_engram_dp_shard.py
+24
-5
@@ -39,10 +39,10 @@ from vllm.distributed.parallel_state import (
39
initialize_model_parallel,
40
)
41
from vllm.forward_context import set_forward_context
42
-from vllm.models.deepseek_v41.common.engram import EngramLayout
43
-from vllm.models.deepseek_v41.nvidia import engram as engram_ops
44
-from vllm.models.deepseek_v41.nvidia.engram import (
42
+from vllm.models.deepseek_v41.common import engram as engram_ops
43
+from vllm.models.deepseek_v41.common.engram import (
44
Engram,
45
+ EngramLayout,
46
ParallelEngramEmbedding,
47
engram_head_shard_rank,
48
gather_engram_hashes,
@@ -686,11 +686,30 @@ def test_engram_dp_shared_memory_runtime_requirements(
686
)
687
688
689
-def test_engram_tables_too_large_for_shm_are_not_shared(monkeypatch):
690
- """A /dev/shm smaller than the tables must fall back instead of failing startup."""
689
+@pytest.mark.parametrize("query_fails", [False, True])
690
+def test_engram_tables_too_large_for_shm_are_not_shared(monkeypatch, query_fails):
691
+ """A /dev/shm that is too small or cannot be queried must fall back, and the
692
+ leader must still broadcast that decision so its peers do not hang."""
693
+ broadcasts = []
694
+
695
+ def broadcast(obj):
696
+ broadcasts.append(obj)
697
+ return obj
698
+
699
+ def query(*args, **kwargs):
700
+ raise OSError("statvfs failed")
701
+
702
monkeypatch.setattr(engram_ops, "get_engram_dp_size", lambda: 2)
703
+ monkeypatch.setattr(
704
+ engram_ops,
705
+ "get_engram_dp_group",
706
+ lambda: SimpleNamespace(rank_in_group=0, broadcast_object=broadcast),
707
+ )
708
+ if query_fails:
709
+ monkeypatch.setattr(engram_ops, "check_shm_free_space", query)
710
layout = SimpleNamespace(num_embeddings=(1 << 50,), head_dim=DIM)
711
assert not engram_ops.can_share_engram_tables(layout)
712
+ assert len(broadcasts) == 1 and broadcasts[0] is not None
713
714
715
@pytest.mark.parametrize(
tests/kernels/test_engram.py
+25
-25
@@ -9,13 +9,10 @@ import torch
9
10
from vllm.models.deepseek_v41.common import engram as engram_ops
11
from vllm.models.deepseek_v41.common.engram import (
12
- Engram as CommonEngram,
13
-)
14
-from vllm.models.deepseek_v41.common.engram import (
12
+ Engram,
13
NgramHashState,
14
+ ParallelEngramEmbedding,
15
)
17
-from vllm.models.deepseek_v41.nvidia import engram as nvidia_engram_ops
18
-from vllm.models.deepseek_v41.nvidia.engram import Engram, ParallelEngramEmbedding
16
from vllm.platforms import current_platform
17
18
@@ -111,7 +108,7 @@ def test_fused_engram_post_wkv_matches_reference(
108
)
109
monkeypatch.setattr(engram_ops, "get_tensor_model_parallel_rank", lambda: tp_rank)
110
114
- module = CommonEngram.__new__(CommonEngram)
111
+ module = Engram.__new__(Engram)
112
torch.nn.Module.__init__(module)
113
module.dim = dim
114
module.hc_mult = hc_mult
@@ -121,6 +118,7 @@ def test_fused_engram_post_wkv_matches_reference(
118
module.embed_tokens = torch.nn.Identity()
119
# `forward` reads rows staged by `prepare_embeddings`, so inject kv there.
120
module.embed_tokens.tp_size = module.embed_tokens.n_hash_cols = 1
121
+ module.embed_tokens.dp_size = 1
122
module.staged_rows = kv.unsqueeze(1)
123
if use_sequence_parallel:
124
padded = torch.nn.functional.pad(kv, (0, 0, 0, (-num_kv_tokens) % tp_size))
@@ -592,9 +590,8 @@ def test_engram_rejects_empty_head_shards(tp_size, dp_size, n_heads, monkeypatch
590
monkeypatch.setattr(
591
engram_ops, "get_tensor_model_parallel_world_size", lambda: tp_size
592
)
595
- monkeypatch.setattr(nvidia_engram_ops, "get_engram_dp_size", lambda: dp_size)
593
+ monkeypatch.setattr(engram_ops, "get_engram_dp_size", lambda: dp_size)
594
monkeypatch.setattr(engram_ops, "get_tensor_model_parallel_rank", lambda: 0)
597
- monkeypatch.setattr(nvidia_engram_ops, "get_tensor_model_parallel_rank", lambda: 0)
595
with pytest.raises(AssertionError, match="ranks without hash heads"):
596
ParallelEngramEmbedding(n_heads * 17, 64, (17,) * n_heads)
597
@@ -602,10 +599,19 @@ def test_engram_rejects_empty_head_shards(tp_size, dp_size, n_heads, monkeypatch
599
@pytest.mark.skipif(
600
not current_platform.is_cuda_alike(), reason="CUDA or ROCm required"
601
)
605
-@pytest.mark.parametrize("cpu_offload", [False, True])
602
+@pytest.mark.parametrize(
603
+ "cpu_offload,sort_rows", [(False, False), (True, False), (True, True)]
604
+)
605
@pytest.mark.parametrize("tp_size", [1, 2, 4, 8])
607
-def test_engram_head_shards_reconstruct_checkpoint(cpu_offload, tp_size, monkeypatch):
608
- """Keep complete buckets and reconstruct head order, including TP padding."""
606
+def test_engram_head_shards_reconstruct_checkpoint(
607
+ cpu_offload, sort_rows, tp_size, monkeypatch
608
+):
609
+ """Keep complete buckets and reconstruct head order, including TP padding,
610
+ whether or not the host lookup sorts its rows by table offset."""
611
+ if sort_rows:
612
+ monkeypatch.setattr(
613
+ engram_ops, "_engram_lookup_thresholds", lambda _: (0, None)
614
+ )
615
head_sizes = (17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73)
616
num_rows, dim = sum(head_sizes), 64
617
torch.manual_seed(0)
@@ -796,10 +802,8 @@ def test_engram_shared_prefetch_stream_waits_per_layer():
802
803
804
@pytest.mark.skipif(not current_platform.is_cuda(), reason="CUDA required")
799
-@pytest.mark.parametrize(
800
- "backend,cpu_offload", [("nvidia", None), ("nvidia", False), ("common", False)]
801
-)
802
-def test_engram_constructor_honors_offload(monkeypatch, backend, cpu_offload):
805
+@pytest.mark.parametrize("cpu_offload", [None, False])
806
+def test_engram_constructor_honors_offload(monkeypatch, cpu_offload):
807
"""Default offload uses pinned storage and the given stream; False uses HBM."""
808
from vllm.config import EngramConfig
809
@@ -817,23 +821,18 @@ def test_engram_constructor_honors_offload(monkeypatch, backend, cpu_offload):
821
else EngramConfig(cpu_offload=cpu_offload),
822
scheduler_config=SimpleNamespace(max_num_batched_tokens=8),
823
)
820
- offloaded = backend == "nvidia" and vllm_config.engram_config.cpu_offload
821
- if backend == "common":
822
- vllm_config.engram_config = None
824
+ offloaded = vllm_config.engram_config.cpu_offload
825
monkeypatch.setattr(engram_ops, "get_current_vllm_config", lambda: vllm_config)
824
- monkeypatch.setattr(
825
- nvidia_engram_ops, "get_current_vllm_config", lambda: vllm_config
826
- )
826
monkeypatch.setattr(engram_ops, "get_tensor_model_parallel_world_size", lambda: 1)
827
monkeypatch.setattr(engram_ops, "get_tensor_model_parallel_rank", lambda: 0)
828
monkeypatch.setattr(
829
engram_ops, "ColumnParallelLinear", lambda *a, **k: torch.nn.Identity()
830
)
831
stream = torch.cuda.Stream()
833
- kwargs = {"prefetch_stream": stream} if backend == "nvidia" else {}
832
with torch.device("cuda"):
835
- cls = Engram if backend == "nvidia" else CommonEngram
836
- module = cls(config, None, layout, 0, False, "engram", **kwargs)
833
+ module = Engram(
834
+ config, None, layout, 0, False, "engram", prefetch_stream=stream
835
+ )
836
layer = module.embed_tokens
837
if offloaded:
838
assert layer.weight.device.type == "cpu" and layer.weight.is_pinned()
@@ -897,7 +896,8 @@ def test_engram_prefetch_detects_missing_dependency(monkeypatch, missing_depende
896
"The allocator reuse negative control requires the native allocator"
897
)
898
900
- def start(self, hash_ids, rows, stream):
899
+ def start(self, hash_ids, rows):
900
+ stream = self._prefetch_stream
901
if missing_dependency != "producer_wait":
902
stream.wait_stream(torch.cuda.current_stream())
903
if missing_dependency != "record_stream":
vllm/models/deepseek_v41/amd/model.py
+1
-5
@@ -65,13 +65,9 @@ from vllm.utils.math_utils import cdiv
65
from vllm.v1.attention.backends.registry import AttentionBackendEnum
66
from vllm.v1.worker.ubatching import dbo_current_ubatch_id
67
68
-from ..common.engram import EngramLayout, NgramHashState
68
+from ..common.engram import Engram, EngramLayout, NgramHashState
69
from ..common.mm_preprocess import IMAGE_SENTINEL_BASE_ID, image_sentinel_mask
70
71
-# Engram host offload and its prefetch stream are neither ROCm- nor
72
-# NVIDIA-specific, so they are imported rather than duplicated.
73
-from ..nvidia.engram import Engram
74
-
71
if typing.TYPE_CHECKING:
72
from vllm.v1.attention.backends.mla.sparse_swa import DeepseekSparseSWAMetadata
73
vllm/models/deepseek_v41/common/engram.py
+490
-22
@@ -34,23 +34,41 @@ chunk-by-chunk while an n-gram at position ``p`` needs the token ids at
34
cache for the rest.
35
"""
36
37
+import ctypes
38
+import mmap
39
+import os
40
+import tempfile
41
import weakref
42
+from contextlib import ExitStack
43
44
import numpy as np
45
import torch
46
from torch import nn
47
48
+from vllm.compilation.breakable_cudagraph import eager_break_during_capture
49
from vllm.config import VllmConfig, get_current_vllm_config
50
from vllm.distributed import (
51
+ get_dp_group,
52
+ get_engram_dp_group,
53
+ get_engram_dp_size,
54
get_tensor_model_parallel_rank,
55
get_tensor_model_parallel_world_size,
56
tensor_model_parallel_all_gather,
57
)
58
+from vllm.distributed.device_communicators.shm_broadcast import (
59
+ SHM_PATH,
60
+ check_shm_free_space,
61
+)
62
+from vllm.distributed.parallel_state import GroupCoordinator
63
+from vllm.forward_context import get_forward_context, is_forward_context_available
64
from vllm.logger import init_logger
65
from vllm.model_executor.layers.linear import ColumnParallelLinear
66
from vllm.model_executor.layers.quantization import QuantizationConfig
67
from vllm.model_executor.utils import set_weight_attrs
68
+from vllm.platforms import current_platform
69
from vllm.triton_utils import tl, triton
70
+from vllm.utils.platform_utils import is_uva_available
71
+from vllm.utils.torch_utils import get_accelerator_view_from_cpu_tensor
72
73
logger = init_logger(__name__)
74
@@ -58,6 +76,18 @@ logger = init_logger(__name__)
76
DEAD_ID = -1
77
78
79
+def _engram_lookup_thresholds(device: torch.device) -> tuple[int | None, int | None]:
80
+ """Host lookup thresholds on `device`: the rows from which to sort, and the
81
+ tokens (max over DP ranks) from which to run inline. None disables that path."""
82
+ if current_platform.is_cuda() and current_platform.is_device_capability_family(
83
+ 100, device.index or 0
84
+ ):
85
+ # Measured on GB200.
86
+ return 36864, 4096
87
+ # TODO: Add hand-tuned thresholds for other GPU families.
88
+ return None, None
89
+
90
+
91
def _is_prime(n: int) -> bool:
92
"""Deterministic Miller-Rabin for n < 2**32 (avoids a sympy import)."""
93
if n < 2:
@@ -583,12 +613,14 @@ def _engram_head_shard_weight_loader(
613
"ids_stride_t",
614
"ids_stride_h",
615
"GRID",
616
+ "SORTED",
617
]
618
)
619
def _engram_lookup_kernel(
620
weight,
621
scales,
622
ids,
623
+ sorted_dst,
624
out,
625
vocab_start,
626
vocab_end,
@@ -602,27 +634,37 @@ def _engram_lookup_kernel(
634
QUANT_BLOCK: tl.constexpr,
635
BLOCK_R: tl.constexpr,
636
GRID,
637
+ SORTED,
638
):
639
"""Gather fp8 rows, apply their ue8m0 block scales, write bf16.
640
641
Only this rank's heads are read; padded heads write zeros for all-gather.
609
- `weight`/`scales` may address pinned host memory through UVA.
642
+ `weight`/`scales` may address pinned host memory through UVA. If SORTED,
643
+ `ids` holds rows relative to vocab_start in table order and `sorted_dst`
644
+ their output rows.
645
"""
646
cols = tl.arange(0, DIM)
647
scale_cols = cols // QUANT_BLOCK
648
for base in tl.range(tl.program_id(0) * BLOCK_R, num_rows, GRID * BLOCK_R):
649
rows = base + tl.arange(0, BLOCK_R)
650
valid = rows < num_rows
616
- head = HEAD_START + rows % LOCAL_HEADS
617
- token = (rows // LOCAL_HEADS).to(tl.int64)
618
- index = tl.load(
619
- ids + token * ids_stride_t + head * ids_stride_h,
620
- mask=valid & (head < TOTAL_HEADS),
621
- other=-1,
622
- ).to(tl.int64)
623
- owned = valid & (head < TOTAL_HEADS)
624
- owned &= (index >= vocab_start) & (index < vocab_end)
625
- local = tl.where(owned, index - vocab_start, 0)
651
+ if SORTED:
652
+ local = tl.load(ids + rows, mask=valid, other=-1).to(tl.int64)
653
+ dst = tl.load(sorted_dst + rows, mask=valid, other=0).to(tl.int32)
654
+ owned = (local >= 0) & (local < vocab_end - vocab_start)
655
+ else:
656
+ head = HEAD_START + rows % LOCAL_HEADS
657
+ token = (rows // LOCAL_HEADS).to(tl.int64)
658
+ index = tl.load(
659
+ ids + token * ids_stride_t + head * ids_stride_h,
660
+ mask=valid & (head < TOTAL_HEADS),
661
+ other=-1,
662
+ ).to(tl.int64)
663
+ dst = rows
664
+ owned = valid & (head < TOTAL_HEADS)
665
+ owned &= (index >= vocab_start) & (index < vocab_end)
666
+ local = index - vocab_start
667
+ local = tl.where(owned, local, 0)
668
values = tl.load(
669
weight + local[:, None] * DIM + cols[None, :],
670
mask=owned[:, None],
@@ -636,16 +678,240 @@ def _engram_lookup_kernel(
678
# ue8m0 is a power of two, so its byte *is* the fp32 exponent field.
679
scale = (scale.to(tl.int32) << 23).to(tl.float32, bitcast=True)
680
tl.store(
639
- out + rows[:, None] * DIM + cols[None, :],
681
+ out + dst[:, None] * DIM + cols[None, :],
682
(values.to(tl.float32) * scale).to(tl.bfloat16),
683
mask=valid[:, None],
684
)
685
686
687
+def engram_head_shard_rank() -> int:
688
+ """This rank's slot among the hash-head shards of one engram table.
689
+
690
+ TP-major, so the shards a DP gather brings in are contiguous heads and
691
+ the following TP gather completes the head order.
692
+ """
693
+ dp_group = get_engram_dp_group()
694
+ dp_size = dp_group.world_size if dp_group is not None else 1
695
+ dp_rank = dp_group.rank_in_group if dp_group is not None else 0
696
+ return get_tensor_model_parallel_rank() * dp_size + dp_rank
697
+
698
+
699
+def engram_gathered_num_tokens() -> int:
700
+ """Per-replica token slot for the node-local Engram DP group."""
701
+ dp_metadata = get_forward_context().dp_metadata
702
+ if dp_metadata is None:
703
+ raise RuntimeError("a DP-shared engram table needs DP token metadata")
704
+ group = get_engram_dp_group()
705
+ assert group is not None
706
+ # Engram groups are contiguous slices of the full DP group.
707
+ start = get_dp_group().rank_in_group - group.rank_in_group
708
+ return int(
709
+ dp_metadata.num_tokens_across_dp_cpu[start : start + group.world_size].max()
710
+ )
711
+
712
+
713
+def gather_engram_hashes(
714
+ hash_ids: torch.Tensor, *, dp_shared_memory: bool = False
715
+) -> torch.Tensor:
716
+ """Collect the n-gram ids of every DP replica sharing one table.
717
+
718
+ Replicas are padded to a common token slot, so the gathered shape is
719
+ static under CUDA graph capture (where DP already pads alike).
720
+ """
721
+ dp_group = get_engram_dp_group()
722
+ if dp_group is None or dp_shared_memory:
723
+ return hash_ids
724
+ slot = engram_gathered_num_tokens()
725
+ if hash_ids.shape[0] > slot:
726
+ raise ValueError("Engram token count exceeds the DP token slot")
727
+ if hash_ids.shape[0] < slot:
728
+ pad = hash_ids.new_full(
729
+ (slot - hash_ids.shape[0], *hash_ids.shape[1:]), DEAD_ID
730
+ )
731
+ hash_ids = torch.cat((hash_ids, pad))
732
+ return dp_group.all_gather(hash_ids, dim=0)
733
+
734
+
735
+class DPSharedEngramStorage:
736
+ """Registered host weights shared by a node-local DP group with one writer."""
737
+
738
+ def __init__(
739
+ self, num_rows: int, dim: int, block_size: int, group: GroupCoordinator
740
+ ) -> None:
741
+ self.group = group
742
+ weight_bytes = num_rows * dim
743
+ storage = self._allocate(weight_bytes + weight_bytes // block_size)
744
+ self.weight = storage[:weight_bytes].view(torch.float8_e4m3fn).view(-1, dim)
745
+ self.weight_scale_inv = storage[weight_bytes:].view(-1, dim // block_size)
746
+ self._views: tuple[torch.Tensor, torch.Tensor] | None = None
747
+
748
+ def _allocate(self, num_bytes: int) -> torch.Tensor:
749
+ """Map and register one physical allocation across a node-local DP group."""
750
+ group = self.group
751
+ with ExitStack() as stack:
752
+ path = error = None
753
+ if group.rank_in_group == 0:
754
+ try:
755
+ check_shm_free_space(num_bytes)
756
+ backing_file = stack.enter_context(
757
+ tempfile.NamedTemporaryFile(prefix="vllm_engram_", dir=SHM_PATH)
758
+ )
759
+ backing_file.truncate(num_bytes)
760
+ path = backing_file.name
761
+ except Exception as exc:
762
+ error = f"{type(exc).__name__}: {exc}"
763
+ path, error = group.broadcast_object((path, error))
764
+ if error is not None:
765
+ raise RuntimeError(
766
+ "Engram shared-memory creation failed on EDP rank 0: " + error
767
+ )
768
+
769
+ mapping = owner = tensor = None
770
+ try:
771
+ with open(path, "r+b") as file:
772
+ mapping = mmap.mmap(file.fileno(), num_bytes, flags=mmap.MAP_SHARED)
773
+ owner = np.frombuffer(mapping, dtype=np.uint8)
774
+ pointer = owner.ctypes.data
775
+ tensor = torch.from_numpy(owner)
776
+ result = torch.cuda.cudart().cudaHostRegister(pointer, num_bytes, 0)
777
+ if result.value != 0:
778
+ raise RuntimeError(f"cudaHostRegister failed: {result}")
779
+ finalizer = weakref.finalize(owner, self._unregister, mapping, pointer)
780
+ finalizer.atexit = False # type: ignore[misc]
781
+ # The UVA helper otherwise allocates a private pinned copy.
782
+ if not tensor.is_pinned():
783
+ raise RuntimeError(
784
+ "cudaHostRegister did not pin the shared Engram mapping"
785
+ )
786
+ except Exception as exc:
787
+ error = f"{type(exc).__name__}: {exc}"
788
+
789
+ # Also fences peer mappings before the leader unlinks the file.
790
+ errors: list[str | None] = [None] * group.world_size
791
+ torch.distributed.all_gather_object(errors, error, group=group.cpu_group)
792
+ failures = "; ".join(
793
+ f"EDP rank {rank}: {error}"
794
+ for rank, error in enumerate(errors)
795
+ if error is not None
796
+ )
797
+ if failures:
798
+ # Dropping the owner runs the finalizer; the mapping then unmaps.
799
+ del tensor, owner, mapping
800
+ raise RuntimeError(
801
+ "Engram shared-memory initialization failed: " + failures
802
+ )
803
+ assert tensor is not None
804
+ return tensor
805
+
806
+ @staticmethod
807
+ def _unregister(mapping: mmap.mmap, pointer: int) -> None:
808
+ # Torch storage retains the numpy owner, including through cached UVA views.
809
+ # Keep its mmap alive until CUDA has released the registration.
810
+ result = torch.cuda.cudart().cudaHostUnregister(pointer)
811
+ if result.value != 0:
812
+ logger.warning("Engram cudaHostUnregister failed: %s", result)
813
+
814
+ def load_weight(
815
+ self, param: torch.nn.Parameter, loaded_weight: torch.Tensor
816
+ ) -> None:
817
+ if self.group.rank_in_group == 0:
818
+ _engram_head_shard_weight_loader(param, loaded_weight)
819
+ # Read order may differ across ranks. Equal load counts ensure all shared
820
+ # weights are ready after the last weight-loader call returns.
821
+ torch.distributed.barrier(group=self.group.cpu_group)
822
+
823
+ def get_views(
824
+ self, weight: torch.Tensor, scales: torch.Tensor
825
+ ) -> tuple[torch.Tensor, torch.Tensor]:
826
+ if (weight.data_ptr(), scales.data_ptr()) != (
827
+ self.weight.data_ptr(),
828
+ self.weight_scale_inv.data_ptr(),
829
+ ):
830
+ raise RuntimeError("Shared Engram parameter storage must not be replaced")
831
+ if self._views is None:
832
+ self._views = (
833
+ get_accelerator_view_from_cpu_tensor(self.weight),
834
+ get_accelerator_view_from_cpu_tensor(self.weight_scale_inv),
835
+ )
836
+ return self._views
837
+
838
+
839
+def can_share_engram_tables(layout: EngramLayout, block_size: int = 32) -> bool:
840
+ """Whether co-located DP replicas exist and /dev/shm can hold the full tables."""
841
+ if get_engram_dp_size() == 1:
842
+ logger.warning_once(
843
+ "Engram DP replicas are not co-located on one node; "
844
+ "storing the offloaded tables per rank instead of sharing them."
845
+ )
846
+ return False
847
+ num_bytes = sum(layout.num_embeddings) * (
848
+ layout.head_dim + layout.head_dim // block_size
849
+ )
850
+ group = get_engram_dp_group()
851
+ assert group is not None
852
+ # Only the leader allocates, so every replica follows its check.
853
+ error = None
854
+ if group.rank_in_group == 0:
855
+ if not os.path.isdir(SHM_PATH):
856
+ error = f"{SHM_PATH} is not mounted"
857
+ else:
858
+ try:
859
+ check_shm_free_space(num_bytes, allocation_name="Engram tables")
860
+ except Exception as exc:
861
+ error = f"{type(exc).__name__}: {exc}"
862
+ error = group.broadcast_object(error)
863
+ if error is not None:
864
+ logger.warning_once(
865
+ "Sharding the offloaded Engram tables across DP replicas: %s", error
866
+ )
867
+ return error is None
868
+
869
+
870
+def _allocate_huge_page_storage(num_bytes: int) -> torch.Tensor | None:
871
+ """Register prefaulted huge pages, or return None for pinned-memory fallback."""
872
+ try:
873
+ mapping = mmap.mmap(-1, num_bytes, flags=mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS)
874
+ mapping.madvise(mmap.MADV_HUGEPAGE)
875
+ except OSError as exc:
876
+ logger.warning(
877
+ "Engram huge-page allocation failed (%s); using pinned memory.", exc
878
+ )
879
+ return None
880
+
881
+ owner = np.frombuffer(mapping, dtype=np.uint8)
882
+ # Fault the pages in before CUDA pins them, so they can be huge.
883
+ owner[:: mmap.PAGESIZE] = 0
884
+ tensor = torch.from_numpy(owner)
885
+ pointer = tensor.data_ptr()
886
+ result = torch.cuda.cudart().cudaHostRegister(pointer, num_bytes, 0)
887
+ if result.value != 0:
888
+ logger.warning(
889
+ "Engram cudaHostRegister failed (%s); using pinned memory.", result
890
+ )
891
+ return None
892
+ # Tensor views retain owner; its finalizer retains mapping until unregister.
893
+ finalizer = weakref.finalize(
894
+ owner, DPSharedEngramStorage._unregister, mapping, pointer
895
+ )
896
+ finalizer.atexit = False # type: ignore[misc]
897
+ if not tensor.is_pinned():
898
+ logger.warning(
899
+ "CUDA did not recognize Engram registration; using pinned memory."
900
+ )
901
+ return None
902
+ return tensor
903
+
904
+
905
class ParallelEngramEmbedding(nn.Module):
646
- """TP-sharded hash heads with FP8 rows and per-block E8M0 scales."""
906
+ """Hash heads with FP8 rows and per-block E8M0 scales.
907
+
908
+ Heads are TP-sharded, and additionally DP-sharded across a node-local
909
+ group unless the table lives in (DP-shared) pinned host memory.
910
+ """
911
912
_weight_loader = staticmethod(_engram_head_shard_weight_loader)
913
+ _shared_memory: DPSharedEngramStorage | None = None
914
+ _packed: torch.Tensor | None = None
915
916
def __init__(
917
self,
@@ -653,10 +919,32 @@ class ParallelEngramEmbedding(nn.Module):
919
dim: int,
920
head_sizes: tuple[int, ...],
921
block_size: int = 32,
922
+ cpu_offload: bool = False,
923
+ dp_shared_memory: bool = False,
924
+ use_thp: bool = False,
925
) -> None:
926
super().__init__()
927
assert head_sizes and all(size > 0 for size in head_sizes)
928
assert sum(head_sizes) <= num_embeddings
929
+ self.cpu_offload = cpu_offload
930
+ self.dp_shared_memory = dp_shared_memory
931
+ self.use_thp = use_thp
932
+ self.dp_size = get_engram_dp_size()
933
+ if dp_shared_memory:
934
+ if not cpu_offload:
935
+ raise ValueError("dp_shared_memory requires cpu_offload=True")
936
+ if self.dp_size <= 1:
937
+ raise ValueError(
938
+ "dp_shared_memory requires a node-local Engram DP "
939
+ f"group with size > 1; effective Engram DP size is {self.dp_size}. "
940
+ "Check that the node layout and rank placement allow complete "
941
+ "DP replicas to be co-located."
942
+ )
943
+ self.dp_size = 1
944
+ if cpu_offload and not is_uva_available():
945
+ raise RuntimeError("Engram CPU offload requires UVA support")
946
+ self._views: tuple[torch.Tensor, torch.Tensor] | None = None
947
+ self._view_src: tuple[int, int] | None = None
948
self.num_embeddings = num_embeddings
949
self.dim = dim
950
self.block_size = block_size
@@ -688,20 +976,99 @@ class ParallelEngramEmbedding(nn.Module):
976
"engram_vocab_start": self.vocab_start_idx,
977
},
978
)
979
+ if cpu_offload:
980
+ # Constant dummy values avoid randomizing huge CPU lookup tables.
981
+ set_weight_attrs(self.weight, {"dummy_weight_value": 1.0})
982
+ # The ue8m0 encoding of scale 1.0 is exponent byte 127.
983
+ set_weight_attrs(self.weight_scale_inv, {"dummy_weight_value": 127})
984
+ logger.info(
985
+ "Engram table offloaded to pinned host memory: %d rows x %d, "
986
+ "%.2f GiB %s",
987
+ self.part_num_embeddings,
988
+ dim,
989
+ self.part_num_embeddings * (dim + dim // block_size) / 1024**3,
990
+ "shared across DP replicas" if dp_shared_memory else "per rank",
991
+ )
992
993
def _get_shard_info(self) -> tuple[int, int]:
693
- return self.tp_size, get_tensor_model_parallel_rank()
994
+ if self.dp_size == 1:
995
+ return self.tp_size, get_tensor_model_parallel_rank()
996
+ return self.tp_size * self.dp_size, engram_head_shard_rank()
997
998
def _allocate_weights(self) -> tuple[torch.Tensor, torch.Tensor]:
999
+ if self.dp_shared_memory:
1000
+ group = get_engram_dp_group()
1001
+ assert group is not None
1002
+ storage = DPSharedEngramStorage(
1003
+ self.part_num_embeddings, self.dim, self.block_size, group
1004
+ )
1005
+ self._shared_memory = storage
1006
+ self._weight_loader = storage.load_weight
1007
+ return storage.weight, storage.weight_scale_inv
1008
+ scale_dim = self.dim // self.block_size
1009
+ if not self.cpu_offload:
1010
+ return (
1011
+ torch.empty(
1012
+ self.part_num_embeddings, self.dim, dtype=torch.float8_e4m3fn
1013
+ ),
1014
+ torch.empty(self.part_num_embeddings, scale_dim, dtype=torch.uint8),
1015
+ )
1016
+ if self.use_thp:
1017
+ weight_bytes = self.part_num_embeddings * self.dim
1018
+ packed = _allocate_huge_page_storage(
1019
+ weight_bytes + weight_bytes // self.block_size
1020
+ )
1021
+ if packed is not None:
1022
+ self._packed = packed
1023
+ return (
1024
+ packed[:weight_bytes].view(torch.float8_e4m3fn).view(-1, self.dim),
1025
+ packed[weight_bytes:].view(-1, scale_dim),
1026
+ )
1027
+ # Model initialization may be inside a CUDA device context.
1028
return (
697
- torch.empty(self.part_num_embeddings, self.dim, dtype=torch.float8_e4m3fn),
1029
torch.empty(
699
- self.part_num_embeddings, self.dim // self.block_size, dtype=torch.uint8
1030
+ self.part_num_embeddings,
1031
+ self.dim,
1032
+ dtype=torch.float8_e4m3fn,
1033
+ device="cpu",
1034
+ pin_memory=True,
1035
+ ),
1036
+ torch.empty(
1037
+ self.part_num_embeddings,
1038
+ scale_dim,
1039
+ dtype=torch.uint8,
1040
+ device="cpu",
1041
+ pin_memory=True,
1042
),
1043
)
1044
1045
+ def collapse_huge_pages(self) -> None:
1046
+ """Best-effort MADV_COLLAPSE (Linux >= 6.1) of pages that faulted small."""
1047
+ if self._packed is None:
1048
+ return
1049
+ addr, num_bytes = self._packed.data_ptr(), self._packed.nbytes
1050
+ libc = ctypes.CDLL(None, use_errno=True)
1051
+ libc.madvise.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int)
1052
+ if libc.madvise(addr, num_bytes, 25) != 0:
1053
+ logger.warning(
1054
+ "Engram MADV_COLLAPSE failed; keeping existing pages: %s",
1055
+ os.strerror(ctypes.get_errno()),
1056
+ )
1057
+
1058
def _storage(self) -> tuple[torch.Tensor, torch.Tensor]:
704
- return self.weight.data, self.weight_scale_inv.data
1059
+ if self._shared_memory is not None:
1060
+ return self._shared_memory.get_views(self.weight, self.weight_scale_inv)
1061
+ if not self.cpu_offload:
1062
+ return self.weight.data, self.weight_scale_inv.data
1063
+ src = (self.weight.data_ptr(), self.weight_scale_inv.data_ptr())
1064
+ if self._view_src != src:
1065
+ self._views = (
1066
+ get_accelerator_view_from_cpu_tensor(self.weight.data),
1067
+ get_accelerator_view_from_cpu_tensor(self.weight_scale_inv.data),
1068
+ )
1069
+ self._view_src = src
1070
+ assert self._views is not None
1071
+ return self._views
1072
1073
def lookup(
1074
self, indices: torch.Tensor, out: torch.Tensor, background: bool = False
@@ -714,6 +1081,24 @@ class ParallelEngramEmbedding(nn.Module):
1081
if not rows:
1082
return
1083
weight, scales = self._storage()
1084
+ ids_stride_t, ids_stride_h = indices.stride()
1085
+ sort_min_rows, _ = _engram_lookup_thresholds(indices.device)
1086
+ sort = (
1087
+ self.cpu_offload
1088
+ and self._packed is None
1089
+ and sort_min_rows is not None
1090
+ and rows >= sort_min_rows
1091
+ )
1092
+ dst = indices
1093
+ if sort:
1094
+ # Small-page host tables miss the TLB per row; in table order, rows on
1095
+ # one page share it.
1096
+ head_end = self.head_start + self.part_n_hash_cols
1097
+ local = indices[:, self.head_start : head_end] - self.vocab_start_idx
1098
+ if (pad := self.part_n_hash_cols - local.shape[1]) > 0:
1099
+ local = nn.functional.pad(local, (0, pad), value=-1)
1100
+ indices, dst = local.flatten().sort()
1101
+ dst = dst.int()
1102
# The table dwarfs TLB reach, so a persistent grid near the SM count
1103
# beats one program per row; halve it to leave SMs for the main stream.
1104
tiles = triton.cdiv(rows, 16)
@@ -722,12 +1107,13 @@ class ParallelEngramEmbedding(nn.Module):
1107
weight,
1108
scales,
1109
indices,
1110
+ dst,
1111
out,
1112
self.vocab_start_idx,
1113
self.vocab_end_idx,
1114
rows,
729
- indices.stride(0),
730
- indices.stride(1),
1115
+ ids_stride_t,
1116
+ ids_stride_h,
1117
HEAD_START=self.head_start,
1118
LOCAL_HEADS=self.part_n_hash_cols,
1119
TOTAL_HEADS=self.n_hash_cols,
@@ -735,17 +1121,23 @@ class ParallelEngramEmbedding(nn.Module):
1121
QUANT_BLOCK=self.block_size,
1122
BLOCK_R=16,
1123
GRID=grid,
1124
+ SORTED=sort,
1125
)
1126
1127
def forward(self, indices: torch.Tensor) -> torch.Tensor:
1128
"""indices: [num_tokens, n_hash_cols] -> [num_tokens, n_hash_cols, dim]
1129
bf16, gathered from all shards for this replica's tokens."""
1130
+ num_tokens = indices.shape[0]
1131
+ if self.dp_size > 1:
1132
+ indices = gather_engram_hashes(indices)
1133
out = torch.empty(
1134
(indices.shape[0], self.part_n_hash_cols, self.dim),
1135
dtype=torch.bfloat16,
1136
device=indices.device,
1137
)
1138
self.lookup(indices, out)
1139
+ if self.dp_size > 1:
1140
+ out = _gather_engram_rows(out, num_tokens)
1141
if self.tp_size > 1:
1142
out = tensor_model_parallel_all_gather(out, dim=1)
1143
return out[:, : self.n_hash_cols]
@@ -891,6 +1283,25 @@ def _engram_select_rows(
1283
)
1284
1285
1286
+def _gather_engram_rows(staged: torch.Tensor, num_tokens: int) -> torch.Tensor:
1287
+ """Exchange DP tokens for heads, retaining only this replica's tokens."""
1288
+ dp_group = get_engram_dp_group()
1289
+ assert dp_group is not None
1290
+ slot, remainder = divmod(staged.shape[0], dp_group.world_size)
1291
+ assert remainder == 0 and 0 <= num_tokens <= slot
1292
+ gathered = dp_group.all_gather(staged, dim=0)
1293
+ local_heads, dim = staged.shape[1:]
1294
+ rows = staged.new_empty((num_tokens, dp_group.world_size * local_heads, dim))
1295
+ _engram_select_rows(
1296
+ gathered,
1297
+ rows,
1298
+ staged.shape[0],
1299
+ dp_group.rank_in_group * slot,
1300
+ local_heads * dim,
1301
+ )
1302
+ return rows
1303
+
1304
+
1305
class Engram(nn.Module):
1306
"""Writes an n-gram lookup into the residual stream, gated by how well it
1307
matches that stream.
@@ -901,6 +1312,9 @@ class Engram(nn.Module):
1312
training kernel).
1313
"""
1314
1315
+ _prefetch_stream: torch.cuda.Stream | None = None
1316
+ _prefetch_done: torch.cuda.Event | None = None
1317
+
1318
def __init__(
1319
self,
1320
config,
@@ -909,8 +1323,12 @@ class Engram(nn.Module):
1323
layer_hash_index: int,
1324
use_sequence_parallel: bool,
1325
prefix: str,
1326
+ prefetch_stream: torch.cuda.Stream | None = None,
1327
) -> None:
1328
super().__init__()
1329
+ # Layers sharing one stream serialize their offloaded lookups, so they
1330
+ # take turns instead of jointly starving decoder compute of SMs.
1331
+ self._prefetch_stream = prefetch_stream
1332
self.layer_hash_index = layer_hash_index
1333
self.dim = config.hidden_size
1334
self.hc_mult = config.hc_mult
@@ -950,28 +1368,78 @@ class Engram(nn.Module):
1368
def _create_embedding(
1369
self, layout: EngramLayout, layer_hash_index: int
1370
) -> ParallelEngramEmbedding:
1371
+ engram_config = get_current_vllm_config().engram_config
1372
+ assert engram_config is not None
1373
return ParallelEngramEmbedding(
1374
layout.num_embeddings[layer_hash_index],
1375
layout.head_dim,
1376
tuple(size for order in layout.primes[layer_hash_index] for size in order),
1377
+ cpu_offload=engram_config.cpu_offload,
1378
+ dp_shared_memory=bool(engram_config.dp_shared_memory),
1379
+ use_thp=engram_config.use_thp,
1380
)
1381
1382
def _init_staging(self, max_tokens: int, head_dim: int) -> None:
1383
# Persistent storage keeps lookup addresses stable across graph replays.
1384
self.staged_rows = torch.empty(
962
- max_tokens,
1385
+ max_tokens * self.embed_tokens.dp_size,
1386
self.embed_tokens.part_n_hash_cols,
1387
head_dim,
1388
dtype=torch.bfloat16,
1389
)
1390
+ if not self.embed_tokens.cpu_offload:
1391
+ self._prefetch_stream = None
1392
+ return
1393
+ if self._prefetch_stream is None:
1394
+ self._prefetch_stream = torch.cuda.Stream(device=self.staged_rows.device)
1395
+ self._prefetch_done = torch.cuda.Event()
1396
1397
def prepare_embeddings(self, hash_ids: torch.Tensor) -> None:
969
- """Look up rows before the decoder layers consume them."""
1398
+ """Look up, or prefetch from host memory, rows before the decoder layers
1399
+ consume them; DP-sharded tables take the group's gathered hash IDs."""
1400
rows = self.staged_rows[: hash_ids.shape[0]]
1401
assert rows.shape[0] == hash_ids.shape[0], "engram staging buffer too small"
972
- self.embed_tokens.lookup(hash_ids, rows)
1402
+ if self._prefetch_stream is None:
1403
+ self.embed_tokens.lookup(hash_ids, rows)
1404
+ else:
1405
+ self._start_prefetch(hash_ids, rows)
1406
+
1407
+ @eager_break_during_capture
1408
+ def _start_prefetch(self, hash_ids: torch.Tensor, rows: torch.Tensor) -> None:
1409
+ # Eager boundaries let the lookup span piecewise graph segments, and
1410
+ # decide per replay (on the replay stream) whether it runs inline.
1411
+ num_tokens = hash_ids.shape[0]
1412
+ # All DP ranks must agree, or EP collectives wait on the slowest one.
1413
+ if is_forward_context_available() and (dp := get_forward_context().dp_metadata):
1414
+ num_tokens = int(dp.num_tokens_across_dp_cpu.max())
1415
+ current = stream = torch.cuda.current_stream()
1416
+ # Big lookups stall persistent main-stream kernels anyway: run them inline.
1417
+ _, inline_min_tokens = _engram_lookup_thresholds(hash_ids.device)
1418
+ if inline_min_tokens is not None and num_tokens >= inline_min_tokens:
1419
+ self.embed_tokens.lookup(hash_ids, rows)
1420
+ else:
1421
+ stream = self._prefetch_stream
1422
+ assert stream is not None
1423
+ stream.wait_stream(current)
1424
+ # Keep temporary hash storage alive until lookup finishes reading it.
1425
+ hash_ids.record_stream(stream)
1426
+ with torch.cuda.stream(stream):
1427
+ self.embed_tokens.lookup(hash_ids, rows, background=True)
1428
+ assert self._prefetch_done is not None
1429
+ self._prefetch_done.record(stream)
1430
+
1431
+ @eager_break_during_capture
1432
+ def _finish_prefetch(self, event: torch.cuda.Event) -> None:
1433
+ # Wait for this layer's lookup only, not for later layers on the stream.
1434
+ torch.cuda.current_stream().wait_event(event)
1435
1436
def _ready_rows(self, num_tokens: int) -> torch.Tensor:
1437
+ if self._prefetch_done is not None:
1438
+ self._finish_prefetch(self._prefetch_done)
1439
+ if self.embed_tokens.dp_size > 1:
1440
+ slot = engram_gathered_num_tokens()
1441
+ staged = self.staged_rows[: slot * self.embed_tokens.dp_size]
1442
+ return _gather_engram_rows(staged, num_tokens)
1443
return self.staged_rows[:num_tokens]
1444
1445
def embed(self, hash_ids: torch.Tensor) -> torch.Tensor:
vllm/models/deepseek_v41/nvidia/engram.py
deleted
-497
@@ -1,497 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""NVIDIA Engram DP sharding, shared host storage, and asynchronous prefetch."""
4
-
5
-import ctypes
6
-import mmap
7
-import os
8
-import shutil
9
-import tempfile
10
-import weakref
11
-from contextlib import ExitStack
12
-
13
-import numpy as np
14
-import torch
15
-
16
-from vllm.compilation.breakable_cudagraph import eager_break_during_capture
17
-from vllm.config import get_current_vllm_config
18
-from vllm.distributed import (
19
- get_dp_group,
20
- get_engram_dp_group,
21
- get_engram_dp_size,
22
- get_tensor_model_parallel_rank,
23
- tensor_model_parallel_all_gather,
24
-)
25
-from vllm.distributed.device_communicators.shm_broadcast import (
26
- SHM_PATH,
27
- check_shm_free_space,
28
-)
29
-from vllm.distributed.parallel_state import GroupCoordinator
30
-from vllm.forward_context import get_forward_context
31
-from vllm.logger import init_logger
32
-from vllm.model_executor.utils import set_weight_attrs
33
-from vllm.models.deepseek_v41.common.engram import (
34
- DEAD_ID,
35
- EngramLayout,
36
- _engram_head_shard_weight_loader,
37
- _engram_select_rows,
38
-)
39
-from vllm.models.deepseek_v41.common.engram import (
40
- Engram as BaseEngram,
41
-)
42
-from vllm.models.deepseek_v41.common.engram import (
43
- ParallelEngramEmbedding as BaseParallelEngramEmbedding,
44
-)
45
-from vllm.utils.platform_utils import is_uva_available
46
-from vllm.utils.torch_utils import get_accelerator_view_from_cpu_tensor
47
-
48
-logger = init_logger(__name__)
49
-
50
-
51
-def _allocate_huge_page_storage(num_bytes: int) -> torch.Tensor | None:
52
- """Register prefaulted huge pages, or return None for pinned-memory fallback."""
53
- try:
54
- mapping = mmap.mmap(-1, num_bytes, flags=mmap.MAP_PRIVATE | mmap.MAP_ANONYMOUS)
55
- mapping.madvise(mmap.MADV_HUGEPAGE)
56
- except OSError as exc:
57
- logger.warning(
58
- "Engram huge-page allocation failed (%s); using pinned memory.", exc
59
- )
60
- return None
61
-
62
- owner = np.frombuffer(mapping, dtype=np.uint8)
63
- # Fault the pages in before CUDA pins them, so they can be huge.
64
- owner[:: mmap.PAGESIZE] = 0
65
- tensor = torch.from_numpy(owner)
66
- pointer = tensor.data_ptr()
67
- result = torch.cuda.cudart().cudaHostRegister(pointer, num_bytes, 0)
68
- if result.value != 0:
69
- logger.warning(
70
- "Engram cudaHostRegister failed (%s); using pinned memory.", result
71
- )
72
- return None
73
- # Tensor views retain owner; its finalizer retains mapping until unregister.
74
- finalizer = weakref.finalize(
75
- owner, DPSharedEngramStorage._unregister, mapping, pointer
76
- )
77
- finalizer.atexit = False # type: ignore[misc]
78
- if not tensor.is_pinned():
79
- logger.warning(
80
- "CUDA did not recognize Engram registration; using pinned memory."
81
- )
82
- return None
83
- return tensor
84
-
85
-
86
-def engram_head_shard_rank() -> int:
87
- """This rank's slot among the hash-head shards of one engram table.
88
-
89
- TP-major, so the shards a DP gather brings in are contiguous heads and
90
- the following TP gather completes the head order.
91
- """
92
- dp_group = get_engram_dp_group()
93
- dp_size = dp_group.world_size if dp_group is not None else 1
94
- dp_rank = dp_group.rank_in_group if dp_group is not None else 0
95
- return get_tensor_model_parallel_rank() * dp_size + dp_rank
96
-
97
-
98
-def engram_gathered_num_tokens() -> int:
99
- """Per-replica token slot for the node-local Engram DP group."""
100
- dp_metadata = get_forward_context().dp_metadata
101
- if dp_metadata is None:
102
- raise RuntimeError("a DP-shared engram table needs DP token metadata")
103
- group = get_engram_dp_group()
104
- assert group is not None
105
- # Engram groups are contiguous slices of the full DP group.
106
- start = get_dp_group().rank_in_group - group.rank_in_group
107
- return int(
108
- dp_metadata.num_tokens_across_dp_cpu[start : start + group.world_size].max()
109
- )
110
-
111
-
112
-def gather_engram_hashes(
113
- hash_ids: torch.Tensor, *, dp_shared_memory: bool = False
114
-) -> torch.Tensor:
115
- """Collect the n-gram ids of every DP replica sharing one table.
116
-
117
- Replicas are padded to a common token slot, so the gathered shape is
118
- static under CUDA graph capture (where DP already pads alike).
119
- """
120
- dp_group = get_engram_dp_group()
121
- if dp_group is None or dp_shared_memory:
122
- return hash_ids
123
- slot = engram_gathered_num_tokens()
124
- if hash_ids.shape[0] > slot:
125
- raise ValueError("Engram token count exceeds the DP token slot")
126
- if hash_ids.shape[0] < slot:
127
- pad = hash_ids.new_full(
128
- (slot - hash_ids.shape[0], *hash_ids.shape[1:]), DEAD_ID
129
- )
130
- hash_ids = torch.cat((hash_ids, pad))
131
- return dp_group.all_gather(hash_ids, dim=0)
132
-
133
-
134
-def can_share_engram_tables(layout: EngramLayout, block_size: int = 32) -> bool:
135
- """Whether co-located DP replicas exist and /dev/shm can hold the full tables."""
136
- if not os.path.isdir(SHM_PATH):
137
- logger.warning_once(
138
- "%s is not mounted; storing the offloaded Engram tables per rank "
139
- "instead of sharing them across DP replicas.",
140
- SHM_PATH,
141
- )
142
- return False
143
- if get_engram_dp_size() == 1:
144
- logger.warning_once(
145
- "Engram DP replicas are not co-located on one node; "
146
- "storing the offloaded tables per rank instead of sharing them."
147
- )
148
- return False
149
- num_bytes = sum(layout.num_embeddings) * (
150
- layout.head_dim + layout.head_dim // block_size
151
- )
152
- if shutil.disk_usage(SHM_PATH).total < num_bytes:
153
- logger.warning_once(
154
- "Sharing Engram tables across DP replicas needs %.1f GiB of %s "
155
- "(--shm-size or --ipc=host); sharding them across replicas instead.",
156
- num_bytes / 1024**3,
157
- SHM_PATH,
158
- )
159
- return False
160
- return True
161
-
162
-
163
-class DPSharedEngramStorage:
164
- """Registered host weights shared by a node-local DP group with one writer."""
165
-
166
- def __init__(
167
- self, num_rows: int, dim: int, block_size: int, group: GroupCoordinator
168
- ) -> None:
169
- self.group = group
170
- weight_bytes = num_rows * dim
171
- storage = self._allocate(weight_bytes + weight_bytes // block_size)
172
- self.weight = storage[:weight_bytes].view(torch.float8_e4m3fn).view(-1, dim)
173
- self.weight_scale_inv = storage[weight_bytes:].view(-1, dim // block_size)
174
- self._views: tuple[torch.Tensor, torch.Tensor] | None = None
175
-
176
- def _allocate(self, num_bytes: int) -> torch.Tensor:
177
- """Map and register one physical allocation across a node-local DP group."""
178
- group = self.group
179
- with ExitStack() as stack:
180
- path = error = None
181
- if group.rank_in_group == 0:
182
- try:
183
- check_shm_free_space(num_bytes)
184
- backing_file = stack.enter_context(
185
- tempfile.NamedTemporaryFile(prefix="vllm_engram_", dir=SHM_PATH)
186
- )
187
- backing_file.truncate(num_bytes)
188
- path = backing_file.name
189
- except Exception as exc:
190
- error = f"{type(exc).__name__}: {exc}"
191
- path, error = group.broadcast_object((path, error))
192
- if error is not None:
193
- raise RuntimeError(
194
- "Engram shared-memory creation failed on EDP rank 0: " + error
195
- )
196
-
197
- mapping = owner = tensor = None
198
- try:
199
- with open(path, "r+b") as file:
200
- mapping = mmap.mmap(file.fileno(), num_bytes, flags=mmap.MAP_SHARED)
201
- owner = np.frombuffer(mapping, dtype=np.uint8)
202
- pointer = owner.ctypes.data
203
- tensor = torch.from_numpy(owner)
204
- result = torch.cuda.cudart().cudaHostRegister(pointer, num_bytes, 0)
205
- if result.value != 0:
206
- raise RuntimeError(f"cudaHostRegister failed: {result}")
207
- finalizer = weakref.finalize(owner, self._unregister, mapping, pointer)
208
- finalizer.atexit = False # type: ignore[misc]
209
- # The UVA helper otherwise allocates a private pinned copy.
210
- if not tensor.is_pinned():
211
- raise RuntimeError(
212
- "cudaHostRegister did not pin the shared Engram mapping"
213
- )
214
- except Exception as exc:
215
- error = f"{type(exc).__name__}: {exc}"
216
-
217
- # Also fences peer mappings before the leader unlinks the file.
218
- errors: list[str | None] = [None] * group.world_size
219
- torch.distributed.all_gather_object(errors, error, group=group.cpu_group)
220
- failures = "; ".join(
221
- f"EDP rank {rank}: {error}"
222
- for rank, error in enumerate(errors)
223
- if error is not None
224
- )
225
- if failures:
226
- # Dropping the owner runs the finalizer; the mapping then unmaps.
227
- del tensor, owner, mapping
228
- raise RuntimeError(
229
- "Engram shared-memory initialization failed: " + failures
230
- )
231
- assert tensor is not None
232
- return tensor
233
-
234
- @staticmethod
235
- def _unregister(mapping: mmap.mmap, pointer: int) -> None:
236
- # Torch storage retains the numpy owner, including through cached UVA views.
237
- # Keep its mmap alive until CUDA has released the registration.
238
- result = torch.cuda.cudart().cudaHostUnregister(pointer)
239
- if result.value != 0:
240
- logger.warning("Engram cudaHostUnregister failed: %s", result)
241
-
242
- def load_weight(
243
- self, param: torch.nn.Parameter, loaded_weight: torch.Tensor
244
- ) -> None:
245
- if self.group.rank_in_group == 0:
246
- _engram_head_shard_weight_loader(param, loaded_weight)
247
- # Read order may differ across ranks. Equal load counts ensure all shared
248
- # weights are ready after the last weight-loader call returns.
249
- torch.distributed.barrier(group=self.group.cpu_group)
250
-
251
- def get_views(
252
- self, weight: torch.Tensor, scales: torch.Tensor
253
- ) -> tuple[torch.Tensor, torch.Tensor]:
254
- if (weight.data_ptr(), scales.data_ptr()) != (
255
- self.weight.data_ptr(),
256
- self.weight_scale_inv.data_ptr(),
257
- ):
258
- raise RuntimeError("Shared Engram parameter storage must not be replaced")
259
- if self._views is None:
260
- self._views = (
261
- get_accelerator_view_from_cpu_tensor(self.weight),
262
- get_accelerator_view_from_cpu_tensor(self.weight_scale_inv),
263
- )
264
- return self._views
265
-
266
-
267
-class ParallelEngramEmbedding(BaseParallelEngramEmbedding):
268
- """Extend TP lookup with DP head sharding or shared, CPU-offloaded TP slices."""
269
-
270
- _shared_memory: DPSharedEngramStorage | None = None
271
-
272
- def __init__(
273
- self,
274
- num_embeddings: int,
275
- dim: int,
276
- head_sizes: tuple[int, ...],
277
- block_size: int = 32,
278
- cpu_offload: bool = False,
279
- dp_shared_memory: bool = False,
280
- use_thp: bool = False,
281
- ) -> None:
282
- self.cpu_offload = cpu_offload
283
- self.dp_shared_memory = dp_shared_memory
284
- self.use_thp = use_thp
285
- self._packed: torch.Tensor | None = None
286
- self.dp_size = get_engram_dp_size()
287
- if dp_shared_memory:
288
- if not cpu_offload:
289
- raise ValueError("dp_shared_memory requires cpu_offload=True")
290
- if self.dp_size <= 1:
291
- raise ValueError(
292
- "dp_shared_memory requires a node-local Engram DP "
293
- f"group with size > 1; effective Engram DP size is {self.dp_size}. "
294
- "Check that the node layout and rank placement allow complete "
295
- "DP replicas to be co-located."
296
- )
297
- self.dp_size = 1
298
- if cpu_offload and not is_uva_available():
299
- raise RuntimeError("Engram CPU offload requires UVA support")
300
- self._views: tuple[torch.Tensor, torch.Tensor] | None = None
301
- self._view_src: tuple[int, int] | None = None
302
- super().__init__(num_embeddings, dim, head_sizes, block_size)
303
- if cpu_offload:
304
- # Constant dummy values avoid randomizing huge CPU lookup tables.
305
- set_weight_attrs(self.weight, {"dummy_weight_value": 1.0})
306
- # The ue8m0 encoding of scale 1.0 is exponent byte 127.
307
- set_weight_attrs(self.weight_scale_inv, {"dummy_weight_value": 127})
308
- logger.info(
309
- "Engram table offloaded to pinned host memory: %d rows x %d, "
310
- "%.2f GiB %s",
311
- self.part_num_embeddings,
312
- dim,
313
- self.part_num_embeddings * (dim + dim // block_size) / 1024**3,
314
- "shared across DP replicas" if dp_shared_memory else "per rank",
315
- )
316
-
317
- def _get_shard_info(self) -> tuple[int, int]:
318
- if self.dp_size == 1:
319
- return super()._get_shard_info()
320
- return self.tp_size * self.dp_size, engram_head_shard_rank()
321
-
322
- def _allocate_weights(self) -> tuple[torch.Tensor, torch.Tensor]:
323
- if self.dp_shared_memory:
324
- group = get_engram_dp_group()
325
- assert group is not None
326
- storage = DPSharedEngramStorage(
327
- self.part_num_embeddings, self.dim, self.block_size, group
328
- )
329
- self._shared_memory = storage
330
- self._weight_loader = storage.load_weight
331
- return storage.weight, storage.weight_scale_inv
332
- if not self.cpu_offload:
333
- return super()._allocate_weights()
334
- if self.use_thp:
335
- weight_bytes = self.part_num_embeddings * self.dim
336
- packed = _allocate_huge_page_storage(
337
- weight_bytes + weight_bytes // self.block_size
338
- )
339
- if packed is not None:
340
- self._packed = packed
341
- return (
342
- packed[:weight_bytes].view(torch.float8_e4m3fn).view(-1, self.dim),
343
- packed[weight_bytes:].view(-1, self.dim // self.block_size),
344
- )
345
- # Model initialization may be inside a CUDA device context.
346
- return (
347
- torch.empty(
348
- self.part_num_embeddings,
349
- self.dim,
350
- dtype=torch.float8_e4m3fn,
351
- device="cpu",
352
- pin_memory=True,
353
- ),
354
- torch.empty(
355
- self.part_num_embeddings,
356
- self.dim // self.block_size,
357
- dtype=torch.uint8,
358
- device="cpu",
359
- pin_memory=True,
360
- ),
361
- )
362
-
363
- def collapse_huge_pages(self) -> None:
364
- """Best-effort MADV_COLLAPSE (Linux >= 6.1) of pages that faulted small."""
365
- if self._packed is None:
366
- return
367
- addr, num_bytes = self._packed.data_ptr(), self._packed.nbytes
368
- libc = ctypes.CDLL(None, use_errno=True)
369
- libc.madvise.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int)
370
- if libc.madvise(addr, num_bytes, 25) != 0:
371
- logger.warning(
372
- "Engram MADV_COLLAPSE failed; keeping existing pages: %s",
373
- os.strerror(ctypes.get_errno()),
374
- )
375
-
376
- def _storage(self) -> tuple[torch.Tensor, torch.Tensor]:
377
- if self._shared_memory is not None:
378
- return self._shared_memory.get_views(self.weight, self.weight_scale_inv)
379
- if not self.cpu_offload:
380
- return super()._storage()
381
- src = (self.weight.data_ptr(), self.weight_scale_inv.data_ptr())
382
- if self._view_src != src:
383
- self._views = (
384
- get_accelerator_view_from_cpu_tensor(self.weight.data),
385
- get_accelerator_view_from_cpu_tensor(self.weight_scale_inv.data),
386
- )
387
- self._view_src = src
388
- assert self._views is not None
389
- return self._views
390
-
391
- def forward(self, indices: torch.Tensor) -> torch.Tensor:
392
- if self.dp_size == 1:
393
- return super().forward(indices)
394
- num_tokens = indices.shape[0]
395
- indices = gather_engram_hashes(indices)
396
- out = torch.empty(
397
- (indices.shape[0], self.part_n_hash_cols, self.dim),
398
- dtype=torch.bfloat16,
399
- device=indices.device,
400
- )
401
- self.lookup(indices, out)
402
- out = _gather_engram_rows(out, num_tokens)
403
- if self.tp_size > 1:
404
- out = tensor_model_parallel_all_gather(out, dim=1)
405
- return out[:, : self.n_hash_cols]
406
-
407
-
408
-def _gather_engram_rows(staged: torch.Tensor, num_tokens: int) -> torch.Tensor:
409
- """Exchange DP tokens for heads, retaining only this replica's tokens."""
410
- dp_group = get_engram_dp_group()
411
- assert dp_group is not None
412
- slot, remainder = divmod(staged.shape[0], dp_group.world_size)
413
- assert remainder == 0 and 0 <= num_tokens <= slot
414
- gathered = dp_group.all_gather(staged, dim=0)
415
- local_heads, dim = staged.shape[1:]
416
- rows = staged.new_empty((num_tokens, dp_group.world_size * local_heads, dim))
417
- _engram_select_rows(
418
- gathered,
419
- rows,
420
- staged.shape[0],
421
- dp_group.rank_in_group * slot,
422
- local_heads * dim,
423
- )
424
- return rows
425
-
426
-
427
-class Engram(BaseEngram):
428
- """NVIDIA Engram with asynchronous offload and node-local DP lookup."""
429
-
430
- _prefetch_stream: torch.cuda.Stream | None = None
431
- _prefetch_done: torch.cuda.Event | None = None
432
-
433
- def __init__(
434
- self, *args, prefetch_stream: torch.cuda.Stream | None = None, **kwargs
435
- ) -> None:
436
- # Layers sharing one stream serialize their offloaded lookups, so they
437
- # take turns instead of jointly starving decoder compute of SMs.
438
- self._prefetch_stream = prefetch_stream
439
- super().__init__(*args, **kwargs)
440
-
441
- def _create_embedding(
442
- self, layout: EngramLayout, layer_hash_index: int
443
- ) -> ParallelEngramEmbedding:
444
- engram_config = get_current_vllm_config().engram_config
445
- assert engram_config is not None
446
- return ParallelEngramEmbedding(
447
- layout.num_embeddings[layer_hash_index],
448
- layout.head_dim,
449
- tuple(size for order in layout.primes[layer_hash_index] for size in order),
450
- cpu_offload=engram_config.cpu_offload,
451
- dp_shared_memory=bool(engram_config.dp_shared_memory),
452
- use_thp=engram_config.use_thp,
453
- )
454
-
455
- def _init_staging(self, max_tokens: int, head_dim: int) -> None:
456
- super()._init_staging(max_tokens * self.embed_tokens.dp_size, head_dim)
457
- if not self.embed_tokens.cpu_offload:
458
- self._prefetch_stream = None
459
- return
460
- if self._prefetch_stream is None:
461
- self._prefetch_stream = torch.cuda.Stream(device=self.staged_rows.device)
462
- self._prefetch_done = torch.cuda.Event()
463
-
464
- def prepare_embeddings(self, hash_ids: torch.Tensor) -> None:
465
- """Prefetch local shared rows or the DP group's gathered hash IDs."""
466
- if self._prefetch_stream is None:
467
- return super().prepare_embeddings(hash_ids)
468
- rows = self.staged_rows[: hash_ids.shape[0]]
469
- assert rows.shape[0] == hash_ids.shape[0], "engram staging buffer too small"
470
- self._start_prefetch(hash_ids, rows, self._prefetch_stream)
471
-
472
- @eager_break_during_capture
473
- def _start_prefetch(
474
- self, hash_ids: torch.Tensor, rows: torch.Tensor, stream: torch.cuda.Stream
475
- ) -> None:
476
- # Eager boundaries let the lookup span piecewise graph segments.
477
- stream.wait_stream(torch.cuda.current_stream())
478
- # Keep temporary hash storage alive until lookup finishes reading it.
479
- hash_ids.record_stream(stream)
480
- with torch.cuda.stream(stream):
481
- self.embed_tokens.lookup(hash_ids, rows, background=True)
482
- assert self._prefetch_done is not None
483
- self._prefetch_done.record(stream)
484
-
485
- @eager_break_during_capture
486
- def _finish_prefetch(self, event: torch.cuda.Event) -> None:
487
- # Wait for this layer's lookup only, not for later layers on the stream.
488
- torch.cuda.current_stream().wait_event(event)
489
-
490
- def _ready_rows(self, num_tokens: int) -> torch.Tensor:
491
- if self._prefetch_done is not None:
492
- self._finish_prefetch(self._prefetch_done)
493
- if self.embed_tokens.dp_size > 1:
494
- slot = engram_gathered_num_tokens()
495
- staged = self.staged_rows[: slot * self.embed_tokens.dp_size]
496
- return _gather_engram_rows(staged, num_tokens)
497
- return super()._ready_rows(num_tokens)
vllm/models/deepseek_v41/nvidia/model.py
+7
-2
@@ -98,9 +98,14 @@ from vllm.utils.math_utils import cdiv
98
from vllm.v1.attention.backends.registry import AttentionBackendEnum
99
from vllm.v1.worker.ubatching import dbo_current_ubatch_id
100
101
-from ..common.engram import EngramLayout, NgramHashState
101
+from ..common.engram import (
102
+ Engram,
103
+ EngramLayout,
104
+ NgramHashState,
105
+ can_share_engram_tables,
106
+ gather_engram_hashes,
107
+)
108
from ..common.mm_preprocess import IMAGE_SENTINEL_BASE_ID, image_sentinel_mask
103
-from .engram import Engram, can_share_engram_tables, gather_engram_hashes
109
from .ops.mhc import (
110
MHC_OVERLAP_MAX_TOKENS,
111
init_mhc_all_reduce,