diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c1d6fe4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Unified diffs contain a one-character context marker on blank context lines. +# Validate whitespace after applying the patch to its source tree instead. +patch/*.patch -whitespace diff --git a/Dockerfile.sm120-v024 b/Dockerfile.sm120-v024 index 5e0e4f7..27aa928 100644 --- a/Dockerfile.sm120-v024 +++ b/Dockerfile.sm120-v024 @@ -72,8 +72,9 @@ RUN pip install --no-cache-dir --break-system-packages flashinfer-python==0.6.14 --index-url https://flashinfer.ai/whl/cu130 \ "flashinfer-jit-cache==0.6.14+cu130" -# 3. our patch, applied to the installed package (pure-Python delta; -# includes the DSpark speculative-decoding backport) +# 3. Combined v0.24.0 patch: W2 streaming/recovery, the step-driven tier +# manager, the DSpark speculative backport, and SM120 fixes. One canonical +# patch keeps the image label an exact source identity. COPY patch/vllm-moet-v0.24.0.patch /tmp/vllm-moet.patch RUN cd "$(python3 -c 'import vllm, os; print(os.path.dirname(os.path.dirname(vllm.__file__)))')" \ && git apply --verbose /tmp/vllm-moet.patch \ diff --git a/patch/FILES.txt b/patch/FILES.txt index cbf9443..179ab44 100644 --- a/patch/FILES.txt +++ b/patch/FILES.txt @@ -4,6 +4,9 @@ # patch but not in the vllm fork branch the patch is generated from — # merge it into the branch first; do not ship the regeneration. csrc/nvfp4_ds_mla/concat_and_cache_nvfp4_ds_mla.cu +tests/model_executor/layers/quantization/test_moe_w2_cgroup_memory.py +tests/model_executor/layers/quantization/test_moe_w2_padded_routes.py +tests/model_executor/layers/quantization/test_moe_w2_step_pins.py tools/nvfp4_flashinfer_sm120/README.md tools/nvfp4_flashinfer_sm120/nvfp4_expand.cuh tools/nvfp4_flashinfer_sm120/patch_flashinfer.py @@ -12,6 +15,7 @@ vllm/compilation/cuda_graph.py vllm/config/speculative.py vllm/config/vllm.py vllm/envs.py +vllm/forward_context.py vllm/model_executor/layers/attention/mla_attention.py vllm/model_executor/layers/quantization/fp8.py vllm/model_executor/layers/quantization/modelopt.py @@ -27,6 +31,9 @@ vllm/model_executor/layers/quantization/utils/moe_w2_store.py vllm/model_executor/layers/quantization/utils/prefill_timers.py vllm/model_executor/layers/quantization/utils/skinny_fp8_cubit.py vllm/model_executor/layers/sparse_attn_indexer.py +vllm/model_executor/model_loader/__init__.py +vllm/model_executor/model_loader/default_loader.py +vllm/model_executor/model_loader/weight_utils.py vllm/model_executor/models/deepseek_mtp.py vllm/model_executor/models/qwen3_dflash.py vllm/model_executor/models/qwen3_dspark.py diff --git a/patch/vllm-moet-v0.24.0.patch b/patch/vllm-moet-v0.24.0.patch index 5fe2a51..05e230e 100644 --- a/patch/vllm-moet-v0.24.0.patch +++ b/patch/vllm-moet-v0.24.0.patch @@ -1,6 +1,6 @@ diff --git a/csrc/nvfp4_ds_mla/concat_and_cache_nvfp4_ds_mla.cu b/csrc/nvfp4_ds_mla/concat_and_cache_nvfp4_ds_mla.cu new file mode 100644 -index 000000000..df62c57cc +index 0000000..df62c57 --- /dev/null +++ b/csrc/nvfp4_ds_mla/concat_and_cache_nvfp4_ds_mla.cu @@ -0,0 +1,142 @@ @@ -146,9 +146,978 @@ index 000000000..df62c57cc + m.def("concat_and_cache_nvfp4_ds_mla", &concat_and_cache_nvfp4_ds_mla, + "Write MLA KV into the packed nvfp4_ds_mla layout (352 B/token)"); +} +diff --git a/tests/model_executor/layers/quantization/test_moe_w2_cgroup_memory.py b/tests/model_executor/layers/quantization/test_moe_w2_cgroup_memory.py +new file mode 100644 +index 0000000..97a0b6a +--- /dev/null ++++ b/tests/model_executor/layers/quantization/test_moe_w2_cgroup_memory.py +@@ -0,0 +1,124 @@ ++# SPDX-License-Identifier: Apache-2.0 ++# SPDX-FileCopyrightText: Copyright contributors to the vLLM project ++ ++import importlib.util ++import logging ++import os ++import sys ++import tempfile ++import unittest ++from pathlib import Path ++from types import ModuleType ++from unittest import mock ++ ++ROOT = Path(__file__).resolve().parents[4] ++STORE_PATH = ROOT / "vllm/model_executor/layers/quantization/utils/moe_w2_store.py" ++GIB = 1 << 30 ++SAFETY_ENV = { ++ "VLLM_MOE_W2_MIN_MEM_AVAILABLE_GB": "16", ++ "VLLM_MOE_W2_MIN_CGROUP_HEADROOM_GB": "4", ++} ++ ++ ++def _load_store_module(): ++ """Load the store in isolation; these checks need only CPU torch.""" ++ vllm_module = ModuleType("vllm") ++ vllm_module.__path__ = [str(ROOT / "vllm")] ++ logger_module = ModuleType("vllm.logger") ++ logger_module.init_logger = logging.getLogger ++ spec = importlib.util.spec_from_file_location("_test_moe_w2_store", STORE_PATH) ++ assert spec is not None and spec.loader is not None ++ module = importlib.util.module_from_spec(spec) ++ with mock.patch.dict( ++ sys.modules, ++ {"vllm": vllm_module, "vllm.logger": logger_module}, ++ ): ++ spec.loader.exec_module(module) ++ return module ++ ++ ++def _cgroup_status(max_available: int, high_available: int | None) -> dict: ++ return { ++ "known": True, ++ "version": 2, ++ "path": "/test", ++ "limited": True, ++ "max_available": max_available, ++ "high_available": high_available, ++ "current": 1 * GIB, ++ "events": {}, ++ } ++ ++ ++class TestMoeW2CgroupMemory(unittest.TestCase): ++ @classmethod ++ def setUpClass(cls): ++ cls.store = _load_store_module() ++ ++ def _v2_status(self, current: int, high: str, maximum: str) -> dict: ++ with tempfile.TemporaryDirectory() as root: ++ files = { ++ "memory.current": str(current), ++ "memory.high": high, ++ "memory.max": maximum, ++ "memory.stat": "anon 1\nfile 2\nfile_mapped 3\n", ++ "memory.events": "high 0\nmax 0\noom 0\n", ++ "memory.swap.current": "0", ++ "memory.swap.max": "max", ++ } ++ for name, value in files.items(): ++ Path(root, name).write_text(value) ++ with mock.patch.object( ++ self.store, "_active_cgroup_v2_dirs", return_value=[root] ++ ): ++ return self.store._cgroup_memory_status() ++ ++ def test_crossed_soft_high_is_separate_from_hard_max_headroom(self): ++ status = self._v2_status(11 * GIB, str(10 * GIB), str(20 * GIB)) ++ ++ self.assertTrue(status["limited"]) ++ self.assertEqual(status["max_available"], 9 * GIB) ++ self.assertEqual(status["high_available"], -1 * GIB) ++ ++ def test_crossed_soft_high_does_not_refuse_safe_hard_headroom(self): ++ with ( ++ mock.patch.dict(os.environ, SAFETY_ENV, clear=False), ++ mock.patch.object( ++ self.store, "_mem_available_bytes", return_value=64 * GIB ++ ), ++ mock.patch.object( ++ self.store, ++ "_cgroup_memory_status", ++ return_value=_cgroup_status(8 * GIB, -1 * GIB), ++ ), ++ ): ++ report = self.store._memory_preflight("soft-high", 2 * GIB) ++ ++ self.assertEqual(report["cgroup_max_available"], 8 * GIB) ++ self.assertEqual(report["cgroup_high_available"], -1 * GIB) ++ ++ def test_hard_max_headroom_still_refuses_unsafe_transient(self): ++ with ( ++ mock.patch.dict(os.environ, SAFETY_ENV, clear=False), ++ mock.patch.object( ++ self.store, "_mem_available_bytes", return_value=64 * GIB ++ ), ++ mock.patch.object( ++ self.store, ++ "_cgroup_memory_status", ++ return_value=_cgroup_status(6 * GIB, 20 * GIB), ++ ), ++ self.assertRaisesRegex(RuntimeError, "memory.max headroom"), ++ ): ++ self.store._memory_preflight("hard-max", 3 * GIB) ++ ++ def test_finite_soft_high_does_not_make_unlimited_max_limited(self): ++ status = self._v2_status(5 * GIB, str(10 * GIB), "max") ++ ++ self.assertFalse(status["limited"]) ++ self.assertIsNone(status["max_available"]) ++ self.assertEqual(status["high_available"], 5 * GIB) ++ ++ ++if __name__ == "__main__": ++ unittest.main() +diff --git a/tests/model_executor/layers/quantization/test_moe_w2_padded_routes.py b/tests/model_executor/layers/quantization/test_moe_w2_padded_routes.py +new file mode 100644 +index 0000000..2b4a328 +--- /dev/null ++++ b/tests/model_executor/layers/quantization/test_moe_w2_padded_routes.py +@@ -0,0 +1,363 @@ ++# SPDX-License-Identifier: Apache-2.0 ++# SPDX-FileCopyrightText: Copyright contributors to the vLLM project ++ ++import ast ++import importlib.util ++import logging ++import sys ++import unittest ++from pathlib import Path ++from types import ModuleType, SimpleNamespace ++from unittest import mock ++ ++import numpy as np ++import torch ++ ++ROOT = Path(__file__).resolve().parents[4] ++CUBIT_PATH = ROOT / "vllm/model_executor/layers/quantization/utils/moe_w2_cubit.py" ++DELTA_PATH = ROOT / "vllm/model_executor/layers/quantization/utils/moe_w2_delta.py" ++FORWARD_CONTEXT_PATH = ROOT / "vllm/forward_context.py" ++RUNNER_PATH = ROOT / "vllm/v1/worker/gpu_model_runner.py" ++UBATCH_PATH = ROOT / "vllm/v1/worker/gpu_ubatch_wrapper.py" ++ ++ ++def _load_function(path: Path, name: str): ++ tree = ast.parse(path.read_text()) ++ function = next( ++ node ++ for node in tree.body ++ if isinstance(node, ast.FunctionDef) and node.name == name ++ ) ++ module = ast.fix_missing_locations(ast.Module(body=[function], type_ignores=[])) ++ namespace = {"_BULK_PREFILL_TOKENS": 96, "np": np, "torch": torch} ++ exec(compile(module, str(path), "exec"), namespace) ++ return namespace[name] ++ ++ ++def _load_delta_module(): ++ vllm_module = ModuleType("vllm") ++ vllm_module.__path__ = [str(ROOT / "vllm")] ++ logger_module = ModuleType("vllm.logger") ++ logger_module.init_logger = logging.getLogger ++ spec = importlib.util.spec_from_file_location("_test_moe_w2_delta_pad", DELTA_PATH) ++ assert spec is not None and spec.loader is not None ++ module = importlib.util.module_from_spec(spec) ++ with ( ++ mock.patch.dict( ++ sys.modules, ++ {"vllm": vllm_module, "vllm.logger": logger_module}, ++ ), ++ mock.patch.dict("os.environ", {"VLLM_MOE_W2_DELTA_TRACE": "0"}, clear=False), ++ ): ++ spec.loader.exec_module(module) ++ return module ++ ++ ++class TestMoeW2PaddedRoutes(unittest.TestCase): ++ @classmethod ++ def setUpClass(cls): ++ cls.route_metadata = staticmethod( ++ _load_function(CUBIT_PATH, "_masked_route_metadata") ++ ) ++ cls.get_slot_mapping = staticmethod( ++ _load_function(CUBIT_PATH, "_get_token_slot_mapping") ++ ) ++ cls.get_has_prefill = staticmethod( ++ _load_function(CUBIT_PATH, "_get_has_prefill") ++ ) ++ cls.batch_has_prefill = staticmethod( ++ _load_function(RUNNER_PATH, "_batch_has_prefill") ++ ) ++ cls.delta = _load_delta_module() ++ ++ def test_runner_prefill_classification_uses_prompt_progress(self): ++ self.assertTrue( ++ self.batch_has_prefill( ++ np.array([0], dtype=np.int32), np.array([14], dtype=np.int32) ++ ) ++ ) ++ self.assertTrue( ++ self.batch_has_prefill( ++ np.array([20, 7], dtype=np.int32), ++ np.array([20, 8], dtype=np.int32), ++ ) ++ ) ++ self.assertFalse( ++ self.batch_has_prefill( ++ np.array([14, 20], dtype=np.int32), ++ np.array([14, 8], dtype=np.int32), ++ ) ++ ) ++ ++ def test_forward_context_prefill_overrides_bulk_fallback(self): ++ vllm_module = ModuleType("vllm") ++ vllm_module.__path__ = [str(ROOT / "vllm")] ++ context_module = ModuleType("vllm.forward_context") ++ context = SimpleNamespace(has_prefill=True) ++ context_module.get_forward_context = lambda: context ++ with mock.patch.dict( ++ sys.modules, ++ {"vllm": vllm_module, "vllm.forward_context": context_module}, ++ ): ++ self.assertTrue(self.get_has_prefill(14)) ++ context.has_prefill = False ++ self.assertFalse(self.get_has_prefill(128)) ++ context.has_prefill = None ++ self.assertFalse(self.get_has_prefill(14)) ++ self.assertTrue(self.get_has_prefill(128)) ++ ++ def test_fixed_shape_route_metadata_masks_padding(self): ++ sorted_ids = torch.arange(8) ++ slot_mapping = torch.tensor([20, 21, 22, -1]) ++ ++ token_valid, route_valid, pair_live, rows = self.route_metadata( ++ sorted_ids, slot_mapping, top_k=2, mblock=1, pad_row=4 ++ ) ++ ++ torch.testing.assert_close(token_valid, torch.tensor([True, True, True, False])) ++ torch.testing.assert_close( ++ route_valid, ++ torch.tensor([True, True, True, True, True, True, False, False]), ++ ) ++ torch.testing.assert_close(pair_live, route_valid) ++ torch.testing.assert_close(rows, torch.tensor([0, 0, 1, 1, 2, 2, 4, 4])) ++ ++ slot_mapping.copy_(torch.tensor([20, -1, -1, -1])) ++ _, route_valid, pair_live, rows = self.route_metadata( ++ sorted_ids, slot_mapping, top_k=2, mblock=1, pad_row=4 ++ ) ++ torch.testing.assert_close( ++ route_valid, ++ torch.tensor([True, True, False, False, False, False, False, False]), ++ ) ++ torch.testing.assert_close(pair_live, route_valid) ++ torch.testing.assert_close(rows, torch.tensor([0, 0, 4, 4, 4, 4, 4, 4])) ++ ++ _, route_valid, pair_live, _ = self.route_metadata( ++ sorted_ids, slot_mapping, top_k=2, mblock=4, pad_row=4 ++ ) ++ torch.testing.assert_close( ++ route_valid, ++ torch.tensor([True, True, False, False, False, False, False, False]), ++ ) ++ torch.testing.assert_close(pair_live, torch.tensor([True, False])) ++ ++ def test_seen_mask_excludes_disjoint_padding_experts_in_both_tiers(self): ++ ids = torch.tensor([[0, 1], [2, 3], [6, 7]]) ++ token_valid = torch.tensor([True, True, False]) ++ base_seen = torch.zeros(8, dtype=torch.uint8) ++ fp4_seen = torch.zeros_like(base_seen) ++ ++ self.delta.mark_seen(base_seen, ids, token_valid) ++ self.delta.mark_seen(fp4_seen, ids, token_valid) ++ ++ expected = torch.tensor([1, 1, 1, 1, 0, 0, 0, 0], dtype=torch.uint8) ++ torch.testing.assert_close(base_seen, expected) ++ torch.testing.assert_close(fp4_seen, expected) ++ ++ def test_slot_mapping_shape_mismatch_fails_loud(self): ++ vllm_module = ModuleType("vllm") ++ vllm_module.__path__ = [str(ROOT / "vllm")] ++ context_module = ModuleType("vllm.forward_context") ++ context_module.get_forward_context = lambda: SimpleNamespace( ++ token_slot_mapping=torch.zeros(3, dtype=torch.int64) ++ ) ++ with ( ++ mock.patch.dict( ++ sys.modules, ++ {"vllm": vllm_module, "vllm.forward_context": context_module}, ++ ), ++ self.assertRaisesRegex(RuntimeError, "match padded T"), ++ ): ++ self.get_slot_mapping(4) ++ ++ def test_static_capture_and_topology_contracts(self): ++ cubit_tree = ast.parse(CUBIT_PATH.read_text()) ++ functions = { ++ node.name: node ++ for node in cubit_tree.body ++ if isinstance(node, ast.FunctionDef) ++ } ++ for name in ( ++ "_desc_build_kernel", ++ "_desc_build_kernel_w4s", ++ "_desc_build_kernel_basecache", ++ "_desc_build_kernel_base_delta", ++ "_desc_build_kernel_base_delta_split", ++ ): ++ function = functions[name] ++ self.assertIn("pair_live_ptr", [arg.arg for arg in function.args.args]) ++ self.assertTrue( ++ any( ++ isinstance(node, ast.Name) and node.id == "pair_live_ptr" ++ for node in ast.walk(function) ++ ) ++ ) ++ ++ real_args = [arg.arg for arg in functions["_moe_w2_forward"].args.args] ++ fake_args = [arg.arg for arg in functions["_moe_w2_forward_fake"].args.args] ++ self.assertEqual(real_args, fake_args) ++ self.assertEqual(real_args, ["x", "topk_weights", "topk_ids", "layer_key"]) ++ ++ runner_source = RUNNER_PATH.read_text() ++ self.assertIn("force_eager=force_w2_prefill_eager", runner_source) ++ self.assertIn("and not has_prefill", runner_source) ++ self.assertIn("self._get_attention_kv_cache_gid()", runner_source) ++ self.assertIn("decode_context_parallel_size != 1", runner_source) ++ self.assertIn("token_slot_mapping[ubatch.token_slice]", runner_source) ++ self.assertIn("_w2_profile_token_slot_mapping", runner_source) ++ self.assertIn( ++ "profile_mapping[num_tokens_unpadded:num_tokens_padded].fill_(-1)", ++ runner_source, ++ ) ++ runner_tree = ast.parse(runner_source) ++ context_calls = [ ++ node ++ for node in ast.walk(runner_tree) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Name) ++ and node.func.id == "set_forward_context" ++ and any(keyword.arg == "slot_mapping" for keyword in node.keywords) ++ ] ++ self.assertGreaterEqual(len(context_calls), 5) ++ for call in context_calls: ++ self.assertIn( ++ "token_slot_mapping", {keyword.arg for keyword in call.keywords} ++ ) ++ self.assertIn("has_prefill", {keyword.arg for keyword in call.keywords}) ++ ++ context_source = FORWARD_CONTEXT_PATH.read_text() ++ self.assertIn("token_slot_mapping=token_slot_mapping", context_source) ++ self.assertIn("has_prefill=has_prefill", context_source) ++ ubatch_source = UBATCH_PATH.read_text() ++ self.assertIn("token_slot_mapping[i]", ubatch_source) ++ self.assertIn("has_prefill=has_prefill", ubatch_source) ++ ++ timed = functions["_moe_w2_forward_timed"] ++ align_call = next( ++ node ++ for node in ast.walk(timed) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Name) ++ and node.func.id == "moe_align_block_size" ++ ) ++ pad_kw = next( ++ keyword ++ for keyword in align_call.keywords ++ if keyword.arg == "pad_sorted_ids" ++ ) ++ self.assertIsInstance(pad_kw.value, ast.Constant) ++ self.assertIs(pad_kw.value.value, True) ++ alignment_guard = next( ++ node ++ for node in ast.walk(timed) ++ if isinstance(node, ast.If) ++ and isinstance(node.test, ast.BinOp) ++ and isinstance(node.test.op, ast.Mod) ++ ) ++ self.assertTrue( ++ any(isinstance(node, ast.Raise) for node in ast.walk(alignment_guard)) ++ ) ++ ++ ++@unittest.skipUnless( ++ torch.cuda.is_available() and importlib.util.find_spec("triton") is not None, ++ "CUDA and Triton are required", ++) ++class TestMoeW2PaddedRoutesCUDA(unittest.TestCase): ++ def test_graph_replay_masks_seen_misses_and_output(self): ++ from vllm.model_executor.layers.quantization.utils import ( ++ moe_w2_cubit, ++ moe_w2_delta, ++ ) ++ ++ device = torch.device("cuda") ++ T, top_k, mblock, n_experts = 4, 2, 1, 8 ++ sorted_ids = torch.arange(T * top_k, device=device, dtype=torch.int64) ++ expert_blocks = torch.arange(n_experts, device=device, dtype=torch.int32) ++ topk_ids = torch.arange(n_experts, device=device).view(T, top_k) ++ slot_mapping = torch.tensor([10, 11, 12, -1], device=device) ++ slot_row = torch.full((n_experts,), -1, dtype=torch.int32, device=device) ++ num_post = torch.tensor([T * top_k], dtype=torch.int32, device=device) ++ seen = torch.zeros(n_experts, dtype=torch.int32, device=device) ++ miss = torch.zeros(1, dtype=torch.int32, device=device) ++ desc = torch.zeros((2, n_experts, 6), dtype=torch.int64, device=device) ++ scratch = torch.zeros(1, dtype=torch.uint8, device=device) ++ routed_output = torch.zeros(T + 1, dtype=torch.float32, device=device) ++ route_values = torch.arange(1, n_experts + 1, device=device).float() ++ ++ def run_masked_routes(): ++ seen.zero_() ++ miss.zero_() ++ routed_output.zero_() ++ token_valid, route_valid, pair_live, rows = ( ++ moe_w2_cubit._masked_route_metadata( ++ sorted_ids, slot_mapping, top_k, mblock, T ++ ) ++ ) ++ moe_w2_delta.mark_seen(seen, topk_ids, token_valid) ++ moe_w2_cubit._desc_build_kernel_basecache[(1,)]( ++ expert_blocks, ++ num_post, ++ pair_live, ++ slot_row, ++ miss, ++ desc, ++ scratch.data_ptr(), ++ scratch.data_ptr(), ++ scratch.data_ptr(), ++ scratch.data_ptr(), ++ scratch.data_ptr(), ++ scratch.data_ptr(), ++ scratch.data_ptr(), ++ 1, ++ 0, ++ 0, ++ 0, ++ 1, ++ 1, ++ 1, ++ 1, ++ 1, ++ 1, ++ n_experts, ++ n_experts, ++ n_experts * 6, ++ mblock, ++ BLOCK=256, ++ ) ++ routed_output.index_add_( ++ 0, rows, torch.where(route_valid, route_values, 0.0) ++ ) ++ ++ side = torch.cuda.Stream() ++ side.wait_stream(torch.cuda.current_stream()) ++ with torch.cuda.stream(side): ++ run_masked_routes() ++ torch.cuda.current_stream().wait_stream(side) ++ ++ graph = torch.cuda.CUDAGraph() ++ with torch.cuda.graph(graph): ++ run_masked_routes() ++ ++ cases = ( ++ ([10, 11, 12, -1], 6, [3.0, 7.0, 11.0, 0.0]), ++ ([10, -1, -1, -1], 2, [3.0, 0.0, 0.0, 0.0]), ++ ) ++ for mapping, expected_routes, expected_output in cases: ++ slot_mapping.copy_(torch.tensor(mapping, device=device)) ++ graph.replay() ++ torch.cuda.synchronize() ++ ++ self.assertEqual(miss.item(), expected_routes) ++ self.assertEqual(seen.count_nonzero().item(), expected_routes) ++ promotion_candidates = ((seen > 0) & (slot_row < 0)).count_nonzero() ++ self.assertEqual(promotion_candidates.item(), expected_routes) ++ torch.testing.assert_close( ++ routed_output[:T], torch.tensor(expected_output, device=device) ++ ) ++ self.assertEqual(routed_output[T].item(), 0.0) ++ ++ ++if __name__ == "__main__": ++ unittest.main() +diff --git a/tests/model_executor/layers/quantization/test_moe_w2_step_pins.py b/tests/model_executor/layers/quantization/test_moe_w2_step_pins.py +new file mode 100644 +index 0000000..711fb77 +--- /dev/null ++++ b/tests/model_executor/layers/quantization/test_moe_w2_step_pins.py +@@ -0,0 +1,464 @@ ++# SPDX-License-Identifier: Apache-2.0 ++# SPDX-FileCopyrightText: Copyright contributors to the vLLM project ++ ++import ast ++import contextlib ++import importlib.util ++import logging ++import sys ++import threading ++import unittest ++from pathlib import Path ++from types import ModuleType ++from unittest import mock ++ ++import torch ++ ++ROOT = Path(__file__).resolve().parents[4] ++DELTA_PATH = ROOT / "vllm/model_executor/layers/quantization/utils/moe_w2_delta.py" ++GATE_PATH = ROOT / "vllm/model_executor/layers/quantization/utils/moe_w2_gate.py" ++RUNNER_PATH = ROOT / "vllm/v1/worker/gpu_model_runner.py" ++WORKER_PATH = ROOT / "vllm/v1/worker/gpu_worker.py" ++ ++ ++def _load_delta_module(): ++ """Load the tier in isolation; this regression needs only CPU torch.""" ++ vllm_module = ModuleType("vllm") ++ vllm_module.__path__ = [str(ROOT / "vllm")] ++ logger_module = ModuleType("vllm.logger") ++ logger_module.init_logger = logging.getLogger ++ spec = importlib.util.spec_from_file_location("_test_moe_w2_delta", DELTA_PATH) ++ assert spec is not None and spec.loader is not None ++ module = importlib.util.module_from_spec(spec) ++ with ( ++ mock.patch.dict( ++ sys.modules, ++ {"vllm": vllm_module, "vllm.logger": logger_module}, ++ ), ++ mock.patch.dict("os.environ", {"VLLM_MOE_W2_DELTA_TRACE": "0"}, clear=False), ++ ): ++ spec.loader.exec_module(module) ++ return module ++ ++ ++class TestMoeW2StepPins(unittest.TestCase): ++ @classmethod ++ def setUpClass(cls): ++ cls.delta = _load_delta_module() ++ ++ def _saturated_lru_tier(self): ++ tier = object.__new__(self.delta.DeltaTier) ++ tier._lock = threading.Lock() ++ tier.n_slots = 3 ++ tier._free = [] ++ tier._alloc_owner(tier.n_slots) ++ tier._owner_li[:] = torch.tensor([0, 0, 0]) ++ tier._owner_ei[:] = torch.tensor([0, 1, 2]) ++ tier._owner_tick[:] = torch.tensor([0, 1, 2]) ++ tier._policy = "lru" ++ tier._tick = 10 ++ tier._step_pins = {0, 1, 2} ++ tier._coupled_fp4 = None ++ tier._seen_host = torch.zeros((1, 32), dtype=torch.uint8) ++ tier._freq = torch.zeros((1, 32), dtype=torch.float32) ++ tier._need = torch.zeros((1, 32), dtype=torch.float32) ++ tier.slot_table = torch.full((1, 32), -1, dtype=torch.int32) ++ tier._mirror = torch.full((1, 32), -1, dtype=torch.int32) ++ for slot, (_, expert, _) in enumerate(tier._owner): ++ tier.slot_table[0, expert] = slot ++ tier._mirror[0, expert] = slot ++ tier._n_evicted = 0 ++ tier._win_evicted = 0 ++ return tier ++ ++ def test_step_scope_reset_keeps_saturated_lru_evicting(self): ++ tier = self._saturated_lru_tier() ++ ++ # This is the production failure mode before the runner reset: once ++ # every saturated slot has accumulated in _step_pins, even emergency ++ # promotion cannot find a victim. ++ self.assertEqual(tier._take_slots_batch(1, emergency=True), []) ++ ++ for expert in range(3, 19): ++ tier._tick += 3 ++ tier.step_begin() ++ expected = min(range(tier.n_slots), key=lambda slot: tier._owner[slot][2]) ++ self.assertEqual(tier._take_slots_batch(1, emergency=True), [expected]) ++ ++ # Mirror force_promote's ownership handoff and current-step pin. ++ tier._own(expected, 0, expert) ++ tier.slot_table[0, expert] = expected ++ tier._mirror[0, expert] = expected ++ tier._step_pins.add(expected) ++ self.assertEqual(tier._free, []) ++ ++ self.assertEqual(tier._n_evicted, 16) ++ ++ def test_explicit_seen_snapshot_wins_over_shared_snapshot_overwrite(self): ++ tier = self._saturated_lru_tier() ++ tier._step_pins.clear() ++ # Simulate another caller overwriting the shared host buffer after this ++ # caller captured expert 0. The immutable set must still protect slot 0. ++ tier._seen_host.zero_() ++ tier._seen_host[0, 2] = 1 ++ self.assertEqual(tier._take_slot({(0, 0)}), 1) ++ ++ def test_target_boundary_clears_seen_but_preserves_pins(self): ++ tier = object.__new__(self.delta.DeltaTier) ++ tier.dev = torch.device("cpu") ++ tier._snap_lock = threading.Lock() ++ tier._stream = mock.Mock() ++ tier.seen = torch.ones((2, 4), dtype=torch.uint8) ++ tier._step_pins = {1, 2} ++ main = object() ++ ++ with ( ++ mock.patch.object( ++ self.delta.torch.cuda, "current_stream", return_value=main ++ ), ++ mock.patch.object( ++ self.delta.torch.cuda, "stream", return_value=contextlib.nullcontext() ++ ), ++ ): ++ tier.routing_step_begin() ++ ++ self.assertEqual(tier.seen.count_nonzero().item(), 0) ++ self.assertEqual(tier._step_pins, {1, 2}) ++ tier._stream.wait_stream.assert_called_once_with(main) ++ ++ def test_module_boundaries_reset_both_tiers(self): ++ base = mock.Mock() ++ fp4 = mock.Mock() ++ with ( ++ mock.patch.object(self.delta, "_BASE_TIER", base), ++ mock.patch.object(self.delta, "_TIER", fp4), ++ ): ++ self.delta.begin_target_step() ++ self.delta.begin_replay_step() ++ self.delta.finish_forward_step() ++ ++ base.pause_for_forward.assert_called_once_with() ++ fp4.pause_for_forward.assert_called_once_with() ++ base.routing_step_begin.assert_called_once_with() ++ fp4.routing_step_begin.assert_called_once_with() ++ base.step_begin.assert_called_once_with() ++ fp4.step_begin.assert_called_once_with() ++ base.wake.assert_called_once_with() ++ fp4.wake.assert_called_once_with() ++ ++ def test_manager_pass_cannot_overlap_forward_window(self): ++ tier = object.__new__(self.delta.DeltaTier) ++ tier._forward_lock = threading.Lock() ++ tier._forward_paused = False ++ tier._wake = mock.Mock() ++ tier._wake_driven = False ++ ++ tier.pause_for_forward() ++ entered = threading.Event() ++ ++ def manager_pass(): ++ with tier._forward_lock: ++ entered.set() ++ ++ thread = threading.Thread(target=manager_pass) ++ thread.start() ++ self.assertFalse(entered.wait(0.05)) ++ tier.wake() ++ self.assertTrue(entered.wait(1.0)) ++ thread.join(timeout=1.0) ++ self.assertFalse(thread.is_alive()) ++ self.assertFalse(tier._forward_paused) ++ self.assertTrue(tier._wake_driven) ++ tier._wake.set.assert_called_once_with() ++ ++ def test_ensure_resident_drains_then_uses_exact_layer_snapshot(self): ++ tree = ast.parse(DELTA_PATH.read_text()) ++ ensure = next( ++ node ++ for node in ast.walk(tree) ++ if isinstance(node, ast.FunctionDef) and node.name == "ensure_resident" ++ ) ++ snap_scope = next( ++ node ++ for node in ast.walk(ensure) ++ if isinstance(node, ast.With) ++ and any( ++ isinstance(item.context_expr, ast.Attribute) ++ and item.context_expr.attr == "_snap_lock" ++ for item in node.items ++ ) ++ ) ++ lock_scope = next( ++ node ++ for node in ast.walk(snap_scope) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "synchronize" ++ ) ++ pool_lock = next( ++ node ++ for node in ast.walk(ensure) ++ if isinstance(node, ast.With) ++ and any( ++ isinstance(item.context_expr, ast.Attribute) ++ and item.context_expr.attr == "_lock" ++ for item in node.items ++ ) ++ ) ++ self.assertLess(lock_scope.lineno, pool_lock.lineno) ++ layer_snapshot = next( ++ node ++ for node in ast.walk(ensure) ++ if isinstance(node, ast.Assign) ++ and any( ++ isinstance(target, ast.Name) and target.id == "layer_seen_set" ++ for target in node.targets ++ ) ++ ) ++ self.assertIsInstance(layer_snapshot.value, ast.SetComp) ++ take = next( ++ node ++ for node in ast.walk(pool_lock) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "_take_slots_batch" ++ ) ++ seen_kw = next( ++ keyword for keyword in take.keywords if keyword.arg == "seen_set" ++ ) ++ self.assertIsInstance(seen_kw.value, ast.Name) ++ self.assertEqual(seen_kw.value.id, "layer_seen_set") ++ pin_clears = [ ++ node ++ for node in ast.walk(pool_lock) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "clear" ++ and isinstance(node.func.value, ast.Attribute) ++ and node.func.value.attr == "_step_pins" ++ ] ++ pin_mutations = [ ++ node ++ for node in ast.walk(pool_lock) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr in {"add", "update"} ++ and isinstance(node.func.value, ast.Attribute) ++ and node.func.value.attr == "_step_pins" ++ ] ++ self.assertEqual(len(pin_clears), 1) ++ self.assertGreaterEqual(len(pin_mutations), 2) ++ self.assertLess(pin_clears[0].lineno, take.lineno) ++ ++ def test_manager_and_force_promote_pass_immutable_seen_sets(self): ++ tree = ast.parse(DELTA_PATH.read_text()) ++ functions = { ++ node.name: node ++ for node in ast.walk(tree) ++ if isinstance(node, ast.FunctionDef) ++ } ++ tick_take = next( ++ node ++ for node in ast.walk(functions["_tick_once"]) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "_take_slot" ++ ) ++ self.assertEqual(len(tick_take.args), 1) ++ self.assertIsInstance(tick_take.args[0], ast.Name) ++ self.assertEqual(tick_take.args[0].id, "seen_set") ++ ++ force_take = next( ++ node ++ for node in ast.walk(functions["force_promote"]) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "_take_slots_batch" ++ ) ++ seen_kw = next( ++ keyword for keyword in force_take.keywords if keyword.arg == "seen_set" ++ ) ++ self.assertIsInstance(seen_kw.value, ast.Name) ++ self.assertEqual(seen_kw.value.id, "seen_set") ++ ++ wrapper_take = next( ++ node ++ for node in ast.walk(functions["_take_slot"]) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "_take_slots_batch" ++ ) ++ wrapper_kw = next( ++ keyword for keyword in wrapper_take.keywords if keyword.arg == "seen_set" ++ ) ++ self.assertIsInstance(wrapper_kw.value, ast.Name) ++ self.assertEqual(wrapper_kw.value.id, "seen_set") ++ ++ for name in ("_tick_once", "force_promote"): ++ assignments = [ ++ node ++ for node in ast.walk(functions[name]) ++ if isinstance(node, ast.Assign) ++ and any( ++ isinstance(target, ast.Name) and target.id == "seen_set" ++ for target in node.targets ++ ) ++ ] ++ self.assertEqual(len(assignments), 1, name) ++ ++ def test_current_prefetch_hit_is_repinned_for_replay(self): ++ tier = object.__new__(self.delta.DeltaTier) ++ tier.dev = torch.device("cpu") ++ tier._store = {0: object()} ++ tier.n_layers = 1 ++ tier._store_mask_cache = None ++ tier._store_mask_n = -1 ++ tier._snap_lock = threading.Lock() ++ tier._lock = threading.Lock() ++ tier._stream = mock.Mock() ++ tier.seen = torch.zeros((1, 4), dtype=torch.uint8) ++ tier.seen[0, 2] = 1 ++ tier._seen_host = torch.zeros_like(tier.seen) ++ tier._mirror = torch.full((1, 4), -1, dtype=torch.int32) ++ tier._mirror[0, 2] = 0 ++ tier._alloc_owner(1) ++ tier._owner_li[0] = 0 ++ tier._owner_ei[0] = 2 ++ tier._owner_tick[0] = 3 ++ tier._tick = 10 ++ tier._step_pins = {0} # draft_prefetch pinned this current-step hit ++ tier._need = torch.zeros((1, 4), dtype=torch.float32) ++ main = object() ++ event = mock.Mock() ++ ++ tier.step_begin() ++ self.assertEqual(tier._step_pins, set()) ++ with ( ++ mock.patch.object( ++ self.delta.torch.cuda, "current_stream", return_value=main ++ ), ++ mock.patch.object( ++ self.delta.torch.cuda, "stream", return_value=contextlib.nullcontext() ++ ), ++ mock.patch.object(self.delta.torch.cuda, "Event", return_value=event), ++ ): ++ self.assertEqual(tier.force_promote(), 0) ++ ++ self.assertEqual(tier._step_pins, {0}) ++ self.assertEqual(tier._owner[0], (0, 2, tier._tick)) ++ tier._stream.wait_stream.assert_called_once_with(main) ++ event.synchronize.assert_called_once_with() ++ ++ def test_runner_uses_routing_then_post_forward_pin_boundaries(self): ++ tree = ast.parse(RUNNER_PATH.read_text()) ++ execute_model = next( ++ node ++ for node in ast.walk(tree) ++ if isinstance(node, ast.FunctionDef) and node.name == "execute_model" ++ ) ++ target_begins = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "begin_target_step" ++ and isinstance(node.func.value, ast.Name) ++ and node.func.value.id == "_w2d" ++ ] ++ replay_begins = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "begin_replay_step" ++ and isinstance(node.func.value, ast.Name) ++ and node.func.value.id == "_w2d" ++ ] ++ self.assertEqual(len(target_begins), 1) ++ self.assertEqual(len(replay_begins), 1) ++ ++ swallowing_handlers = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Try) ++ and ( ++ target_begins[0] in ast.walk(node) or replay_begins[0] in ast.walk(node) ++ ) ++ ] ++ self.assertEqual(swallowing_handlers, []) ++ ++ target_forwards = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "_model_forward" ++ ] ++ first_forward = min(node.lineno for node in target_forwards) ++ post_forward_reset = replay_begins[0] ++ self.assertLess(target_begins[0].lineno, first_forward) ++ self.assertLess(first_forward, post_forward_reset.lineno) ++ ++ direct_tier_resets = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "step_begin" ++ ] ++ self.assertEqual(direct_tier_resets, []) ++ ++ def test_worker_releases_manager_only_after_gate_barrier(self): ++ tree = ast.parse(WORKER_PATH.read_text()) ++ execute_model = next( ++ node ++ for node in ast.walk(tree) ++ if isinstance(node, ast.FunctionDef) and node.name == "execute_model" ++ ) ++ finishes = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "_finish_w2_manager_step" ++ ] ++ barriers = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "_gate_pp_barrier" ++ ] ++ self.assertEqual(len(finishes), 2) ++ self.assertEqual(len(barriers), 2) ++ finally_finishes = [ ++ node ++ for node in ast.walk(execute_model) ++ if isinstance(node, ast.Try) ++ and any(finish in ast.walk(node) for finish in finishes) ++ and any( ++ finish in ast.walk(final) ++ for final in node.finalbody ++ for finish in finishes ++ ) ++ ] ++ self.assertEqual(len(finally_finishes), 2) ++ ++ gate_tree = ast.parse(GATE_PATH.read_text()) ++ should_reforward = next( ++ node ++ for node in ast.walk(gate_tree) ++ if isinstance(node, ast.FunctionDef) and node.name == "should_reforward" ++ ) ++ unsafe_wakes = [ ++ node ++ for node in ast.walk(should_reforward) ++ if isinstance(node, ast.Call) ++ and isinstance(node.func, ast.Attribute) ++ and node.func.attr == "wake_all" ++ ] ++ self.assertEqual(unsafe_wakes, []) ++ ++ ++if __name__ == "__main__": ++ unittest.main() diff --git a/tools/nvfp4_flashinfer_sm120/README.md b/tools/nvfp4_flashinfer_sm120/README.md new file mode 100644 -index 000000000..a147137db +index 0000000..a147137 --- /dev/null +++ b/tools/nvfp4_flashinfer_sm120/README.md @@ -0,0 +1,49 @@ @@ -203,7 +1172,7 @@ index 000000000..a147137db +- Microbench (isolated KV gather, RTX 5090): 1.86× tokens/s vs 656 B. diff --git a/tools/nvfp4_flashinfer_sm120/nvfp4_expand.cuh b/tools/nvfp4_flashinfer_sm120/nvfp4_expand.cuh new file mode 100644 -index 000000000..e27203c56 +index 0000000..e27203c --- /dev/null +++ b/tools/nvfp4_flashinfer_sm120/nvfp4_expand.cuh @@ -0,0 +1,211 @@ @@ -420,7 +1389,7 @@ index 000000000..e27203c56 +} diff --git a/tools/nvfp4_flashinfer_sm120/patch_flashinfer.py b/tools/nvfp4_flashinfer_sm120/patch_flashinfer.py new file mode 100644 -index 000000000..cfa92d323 +index 0000000..cfa92d3 --- /dev/null +++ b/tools/nvfp4_flashinfer_sm120/patch_flashinfer.py @@ -0,0 +1,412 @@ @@ -837,7 +1806,7 @@ index 000000000..cfa92d323 +print("SYNTAX-OK python") +print("PATCH-FLASHINFER-DONE") diff --git a/vllm/compilation/breakable_cudagraph.py b/vllm/compilation/breakable_cudagraph.py -index 6da3ec717..c84958ac0 100644 +index 6da3ec7..c84958a 100644 --- a/vllm/compilation/breakable_cudagraph.py +++ b/vllm/compilation/breakable_cudagraph.py @@ -175,10 +175,13 @@ class BreakableCUDAGraphCapture: @@ -857,7 +1826,7 @@ index 6da3ec717..c84958ac0 100644 self._capturing = True diff --git a/vllm/compilation/cuda_graph.py b/vllm/compilation/cuda_graph.py -index b63d86199..3cad1f058 100644 +index b63d861..3cad1f0 100644 --- a/vllm/compilation/cuda_graph.py +++ b/vllm/compilation/cuda_graph.py @@ -314,6 +314,10 @@ class CUDAGraphWrapper: @@ -872,7 +1841,7 @@ index b63d86199..3cad1f058 100644 # `output` is managed by pytorch's cudagraph pool output = self.runnable(*args, **kwargs) diff --git a/vllm/config/speculative.py b/vllm/config/speculative.py -index de505e122..f0b49e294 100644 +index de505e1..f0b49e2 100644 --- a/vllm/config/speculative.py +++ b/vllm/config/speculative.py @@ -54,6 +54,7 @@ MTPModelTypes = Literal[ @@ -1074,7 +2043,7 @@ index de505e122..f0b49e294 100644 return self.num_speculative_tokens_per_batch_size is not None diff --git a/vllm/config/vllm.py b/vllm/config/vllm.py -index ba7d26c93..67d3b5ea2 100644 +index ba7d26c..67d3b5e 100644 --- a/vllm/config/vllm.py +++ b/vllm/config/vllm.py @@ -524,6 +524,16 @@ class VllmConfig: @@ -1184,23 +2153,44 @@ index ba7d26c93..67d3b5ea2 100644 "nvfp4 KV cache is not supported with MLA (Multi-head Latent " "Attention) backends. Please use a different --kv-cache-dtype " diff --git a/vllm/envs.py b/vllm/envs.py -index 27a85bb3d..88f310c4f 100755 +index 27a85bb..4a657ca 100755 --- a/vllm/envs.py +++ b/vllm/envs.py -@@ -179,6 +179,9 @@ if TYPE_CHECKING: +@@ -179,6 +179,17 @@ if TYPE_CHECKING: VLLM_MOE_USE_DEEP_GEMM: bool = True VLLM_USE_DEEP_GEMM_E8M0: bool = True VLLM_USE_DEEP_GEMM_TMA_ALIGNED_SCALES: bool = True ++ VLLM_MOE_W2: bool = False ++ VLLM_MOE_W2_STORE_DIR: str = "" ++ VLLM_MOE_W2_PACK_ID: str = "" ++ VLLM_MOE_W2_CACHE_CONTROL: Literal[ ++ "required", "best-effort", "off" ++ ] = "required" ++ VLLM_MOE_W2_MIN_MEM_AVAILABLE_GB: float = 16.0 ++ VLLM_MOE_W2_MIN_CGROUP_HEADROOM_GB: float = 4.0 + # Opt-in: fused hand-written SASS (cubit) sparse-MLA on SM120 (experimental, + # eager only). See vllm/v1/attention/backends/mla/cubit_sparse_mla.py. + VLLM_SPARSE_MLA_CUBIT: bool | None = None VLLM_DEEP_GEMM_WARMUP: Literal[ "skip", "full", -@@ -1444,6 +1447,16 @@ environment_variables: dict[str, Callable[[], Any]] = { +@@ -1444,6 +1455,29 @@ environment_variables: dict[str, Callable[[], Any]] = { "VLLM_USE_DEEP_GEMM_TMA_ALIGNED_SCALES": lambda: bool( int(os.getenv("VLLM_USE_DEEP_GEMM_TMA_ALIGNED_SCALES", "1")) ), ++ # W2 pack-store cold-build safety. The implementation reads these at the ++ # point of use; registration here prevents the generic unknown-VLLM-env ++ # guard from misclassifying the supported controls. ++ "VLLM_MOE_W2": lambda: bool(int(os.getenv("VLLM_MOE_W2", "0"))), ++ "VLLM_MOE_W2_STORE_DIR": lambda: os.getenv("VLLM_MOE_W2_STORE_DIR", ""), ++ "VLLM_MOE_W2_PACK_ID": lambda: os.getenv("VLLM_MOE_W2_PACK_ID", ""), ++ "VLLM_MOE_W2_CACHE_CONTROL": env_with_choices( ++ "VLLM_MOE_W2_CACHE_CONTROL", "required", ++ ["required", "best-effort", "off"]), ++ "VLLM_MOE_W2_MIN_MEM_AVAILABLE_GB": lambda: float( ++ os.getenv("VLLM_MOE_W2_MIN_MEM_AVAILABLE_GB", "16")), ++ "VLLM_MOE_W2_MIN_CGROUP_HEADROOM_GB": lambda: float( ++ os.getenv("VLLM_MOE_W2_MIN_CGROUP_HEADROOM_GB", "4")), + # Opt-in: fused hand-written SASS (cubit) sparse-MLA decode on SM120, + # replacing the Triton accumulate+finish pair for supported decode shapes. + # Experimental; requires eager mode. See @@ -1214,8 +2204,65 @@ index 27a85bb3d..88f310c4f 100755 # DeepGemm JITs the kernels on-demand. The warmup attempts to make DeepGemm # JIT all the required kernels before model execution so there is no # JIT'ing in the hot-path. However, this warmup increases the engine +diff --git a/vllm/forward_context.py b/vllm/forward_context.py +index 5527ec1..a7cb4f4 100644 +--- a/vllm/forward_context.py ++++ b/vllm/forward_context.py +@@ -131,6 +131,10 @@ class ForwardContext: + no_compile_layers: dict[str, Any] + attn_metadata: dict[str, AttentionMetadata] | list[dict[str, AttentionMetadata]] + slot_mapping: dict[str, torch.Tensor] | list[dict[str, torch.Tensor]] ++ token_slot_mapping: torch.Tensor | list[torch.Tensor] | None = None ++ # True when any real request is still consuming prompt tokens. None is ++ # reserved for dummy/profile forwards that lack request lifecycle state. ++ has_prefill: bool | None = None + """ + Type Dict[str, AttentionMetadata] for v1, map from layer_name of each + attention layer to its attention metadata +@@ -209,6 +213,8 @@ def create_forward_context( + batch_descriptor: BatchDescriptor | None = None, + ubatch_slices: UBatchSlices | None = None, + slot_mapping: dict[str, torch.Tensor] | list[dict[str, torch.Tensor]] | None = None, ++ token_slot_mapping: torch.Tensor | list[torch.Tensor] | None = None, ++ has_prefill: bool | None = None, + additional_kwargs: dict[str, Any] | None = None, + skip_compiled: bool = False, + ): +@@ -222,6 +228,8 @@ def create_forward_context( + all_moe_layers=all_moe_layers, + attn_metadata=attn_metadata, + slot_mapping=slot_mapping or {}, ++ token_slot_mapping=token_slot_mapping, ++ has_prefill=has_prefill, + dp_metadata=dp_metadata, + cudagraph_runtime_mode=cudagraph_runtime_mode, + batch_descriptor=batch_descriptor, +@@ -256,6 +264,8 @@ def set_forward_context( + batch_descriptor: BatchDescriptor | None = None, + ubatch_slices: UBatchSlices | None = None, + slot_mapping: dict[str, torch.Tensor] | list[dict[str, torch.Tensor]] | None = None, ++ token_slot_mapping: torch.Tensor | list[torch.Tensor] | None = None, ++ has_prefill: bool | None = None, + skip_compiled: bool = False, + ): + """A context manager that stores the current forward context, +@@ -313,9 +323,11 @@ def set_forward_context( + cudagraph_runtime_mode, + batch_descriptor, + ubatch_slices, +- slot_mapping, +- additional_kwargs, +- skip_compiled, ++ slot_mapping=slot_mapping, ++ token_slot_mapping=token_slot_mapping, ++ has_prefill=has_prefill, ++ additional_kwargs=additional_kwargs, ++ skip_compiled=skip_compiled, + ) + + try: diff --git a/vllm/model_executor/layers/attention/mla_attention.py b/vllm/model_executor/layers/attention/mla_attention.py -index 051468ed1..33902056c 100644 +index 051468e..3390205 100644 --- a/vllm/model_executor/layers/attention/mla_attention.py +++ b/vllm/model_executor/layers/attention/mla_attention.py @@ -188,6 +188,7 @@ return curr_o @ W_O @@ -1272,7 +2319,7 @@ index 051468ed1..33902056c 100644 # Enforce that we enough for at least 1 page per request diff --git a/vllm/model_executor/layers/quantization/fp8.py b/vllm/model_executor/layers/quantization/fp8.py -index 7cdb04cfb..64813139b 100644 +index 7cdb04c..6481313 100644 --- a/vllm/model_executor/layers/quantization/fp8.py +++ b/vllm/model_executor/layers/quantization/fp8.py @@ -671,6 +671,21 @@ class Fp8MoEMethod(FusedMoEMethodBase): @@ -1336,7 +2383,7 @@ index 7cdb04cfb..64813139b 100644 assert self.moe_kernel is not None return self.moe_kernel.apply( diff --git a/vllm/model_executor/layers/quantization/modelopt.py b/vllm/model_executor/layers/quantization/modelopt.py -index d51a2dd31..2fe74cc55 100644 +index d51a2dd..2fe74cc 100644 --- a/vllm/model_executor/layers/quantization/modelopt.py +++ b/vllm/model_executor/layers/quantization/modelopt.py @@ -1,6 +1,7 @@ @@ -1551,23 +2598,36 @@ index d51a2dd31..2fe74cc55 100644 return UnquantizedLinearMethod() return None diff --git a/vllm/model_executor/layers/quantization/mxfp4.py b/vllm/model_executor/layers/quantization/mxfp4.py -index 1b2a8a74b..3afdb16fd 100644 +index 1b2a8a7..de83142 100644 --- a/vllm/model_executor/layers/quantization/mxfp4.py +++ b/vllm/model_executor/layers/quantization/mxfp4.py -@@ -617,6 +617,21 @@ class Mxfp4MoEMethod(FusedMoEMethodBase): +@@ -617,6 +617,34 @@ class Mxfp4MoEMethod(FusedMoEMethodBase): ) layer.register_parameter("w2_bias", w2_bias) set_weight_attrs(w2_bias, extra_weight_attrs) + # VLLM_MOE_W2: the raw checkpoint experts of all layers do not fit a -+ # single GPU; stage them in host RAM until the 2-bit planes are built -+ # in process_weights_after_loading. ++ # single GPU or a constrained host cgroup. Skip checkpoint staging ++ # entirely on a valid pack/cache hit; otherwise materialize one ++ # layer lazily and build its planes as soon as its last tensor loads. ++ # The all-layers CPU staging path remains only as the explicit ++ # VLLM_MOE_W2_STREAM_BUILD=0 fallback. + from vllm.model_executor.layers.quantization.utils import moe_w2_cubit -+ if moe_w2_cubit.is_w2_layer(getattr(layer, "layer_name", "")): -+ for pname in ("w13_weight", "w13_weight_scale", "w2_weight", -+ "w2_weight_scale"): ++ ++ if ( ++ moe_w2_cubit.is_w2_layer(getattr(layer, "layer_name", "")) ++ and not moe_w2_cubit.plan_pack_skip(layer) ++ and not moe_w2_cubit.arm_stream_build(layer, checkpoint_format="mxfp4") ++ ): ++ for pname in ( ++ "w13_weight", ++ "w13_weight_scale", ++ "w2_weight", ++ "w2_weight_scale", ++ ): + p_ = getattr(layer, pname) -+ attrs = {k: getattr(p_, k) for k in ("weight_loader",) -+ if hasattr(p_, k)} ++ attrs = { ++ k: getattr(p_, k) for k in ("weight_loader",) if hasattr(p_, k) ++ } + newp = torch.nn.Parameter(p_.data.cpu(), requires_grad=False) + layer.register_parameter(pname, newp) + set_weight_attrs(newp, attrs) @@ -1576,22 +2636,24 @@ index 1b2a8a74b..3afdb16fd 100644 def _setup_kernel( self, -@@ -722,6 +737,14 @@ class Mxfp4MoEMethod(FusedMoEMethodBase): +@@ -722,6 +750,16 @@ class Mxfp4MoEMethod(FusedMoEMethodBase): ) def process_weights_after_loading(self, layer): + # VLLM_MOE_W2: build 2-bit tensor-sym planes; skip Marlin/other backends. + from vllm.model_executor.layers.quantization.utils import moe_w2_cubit ++ + if moe_w2_cubit.is_w2_layer(getattr(layer, "layer_name", "")): -+ key = len(moe_w2_cubit._LAYERS) -+ moe_w2_cubit.build_layer_planes(layer, key) ++ key = getattr(layer, "_moe_w2_create_key", len(moe_w2_cubit._LAYERS)) ++ if not getattr(layer, "_moe_w2_stream_built", False): ++ moe_w2_cubit.build_layer_planes(layer, key) + layer._moe_w2_key = key + return + w13 = layer.w13_weight w2 = layer.w2_weight w13_scale = layer.w13_weight_scale -@@ -779,6 +802,19 @@ class Mxfp4MoEMethod(FusedMoEMethodBase): +@@ -779,6 +817,17 @@ class Mxfp4MoEMethod(FusedMoEMethodBase): shared_experts: SharedExperts | None, shared_experts_input: torch.Tensor | None, ) -> torch.Tensor: @@ -1601,18 +2663,16 @@ index 1b2a8a74b..3afdb16fd 100644 + # routed-expert output, matching the other non-modular applies here. + w2_key = getattr(layer, "_moe_w2_key", None) + if w2_key is not None: -+ from vllm.model_executor.layers.quantization.utils import ( -+ moe_w2_cubit) -+ assert layer.expert_map is None and \ -+ not layer.apply_router_weight_on_input -+ return moe_w2_cubit.moe_w2_forward(x, topk_weights, topk_ids, -+ w2_key) ++ from vllm.model_executor.layers.quantization.utils import moe_w2_cubit ++ ++ assert layer.expert_map is None and not layer.apply_router_weight_on_input ++ return moe_w2_cubit.moe_w2_forward(x, topk_weights, topk_ids, w2_key) + assert not self.is_monolithic assert self.moe_kernel is not None return self.moe_kernel.apply( diff --git a/vllm/model_executor/layers/quantization/utils/fp8_utils.py b/vllm/model_executor/layers/quantization/utils/fp8_utils.py -index 32a2d8689..3e83d5a87 100644 +index 32a2d86..3e83d5a 100644 --- a/vllm/model_executor/layers/quantization/utils/fp8_utils.py +++ b/vllm/model_executor/layers/quantization/utils/fp8_utils.py @@ -1122,6 +1122,11 @@ def deepgemm_post_process_fp8_weight_block( @@ -1629,10 +2689,10 @@ index 32a2d8689..3e83d5a87 100644 mn=r, diff --git a/vllm/model_executor/layers/quantization/utils/moe_w2_cubit.py b/vllm/model_executor/layers/quantization/utils/moe_w2_cubit.py new file mode 100644 -index 000000000..43bad76a2 +index 0000000..cf35fde --- /dev/null +++ b/vllm/model_executor/layers/quantization/utils/moe_w2_cubit.py -@@ -0,0 +1,1702 @@ +@@ -0,0 +1,2275 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Routed experts on 2-bit tensor-sym planes (cubit moe_w2) for the @@ -1677,8 +2737,9 @@ index 000000000..43bad76a2 + +_KERN = b"moe_w2_mm" +_DIR = os.getenv("VLLM_MOE_W2_CUBIT_DIR", "/cubit-share") -+_BLOCK = 4 # tokens per pair == kernel M limit -+_NTHR = 256 # NWARP=8 (K>=1024) ++_BLOCK = 4 # tokens per pair == kernel M limit ++_NTHR = 256 # NWARP=8 (K>=1024) ++_BULK_PREFILL_TOKENS = 96 + + +def _nwarp_for_k(k: int) -> int: @@ -1693,6 +2754,7 @@ index 000000000..43bad76a2 + return n + return 1 + ++ +_cu = None +_fns: dict = {} +_state = "uninit" @@ -1720,9 +2782,52 @@ index 000000000..43bad76a2 + v = v.permute(0, 3, 2, 5, 4, 1, 6).reshape(pairs * 16, K) + return v.contiguous().view(a.dtype) + ++ ++def _masked_route_metadata( ++ sorted_ids: torch.Tensor, ++ token_slot_mapping: torch.Tensor, ++ top_k: int, ++ mblock: int, ++ pad_row: int, ++) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: ++ """Build fixed-shape route masks from the runner's persistent slot map.""" ++ T = token_slot_mapping.shape[0] ++ token_valid = token_slot_mapping >= 0 ++ aligned_valid = sorted_ids < T * top_k ++ token_rows = (sorted_ids // top_k).clamp(max=T - 1) ++ route_valid = aligned_valid & token_valid[token_rows] ++ pair_live = route_valid.view(-1, mblock).any(dim=1) ++ rows = torch.where(route_valid, token_rows, torch.full_like(sorted_ids, pad_row)) ++ return token_valid, route_valid, pair_live, rows ++ ++ ++def _get_token_slot_mapping(T: int) -> torch.Tensor: ++ from vllm.forward_context import get_forward_context ++ ++ token_slot_mapping = get_forward_context().token_slot_mapping ++ if not isinstance(token_slot_mapping, torch.Tensor): ++ raise RuntimeError( ++ "moe_w2 requires a persistent token slot mapping in ForwardContext" ++ ) ++ if token_slot_mapping.ndim != 1 or token_slot_mapping.shape[0] != T: ++ raise RuntimeError( ++ "moe_w2 slot mapping must be one-dimensional and match padded T" ++ ) ++ return token_slot_mapping ++ ++ ++def _get_has_prefill(T: int) -> bool: ++ from vllm.forward_context import get_forward_context ++ ++ has_prefill = get_forward_context().has_prefill ++ if has_prefill is None: ++ return T > _BULK_PREFILL_TOKENS ++ return has_prefill ++ ++ +# layer_key -> dict(planes13, sc13, planes2, sc2, top_k, inter) +_LAYERS: dict[int, dict] = {} -+_WS: dict = {} # shared workspaces, sized lazily ++_WS: dict = {} # shared workspaces, sized lazily + +# ---- adaptive expert top-p (VLLM_MOE_W2_TOPP, colibri's --topp) ---------- +# Keep each token's routed experts only up to cumulative router weight p: @@ -1793,6 +2898,7 @@ index 000000000..43bad76a2 + return int(v) + try: + from vllm.config import get_current_vllm_config ++ + cfg = get_current_vllm_config().model_config.hf_config + cfg = cfg.get_text_config() + n = cfg.num_hidden_layers @@ -1814,6 +2920,7 @@ index 000000000..43bad76a2 + if "mtp" in name: + return False + import re ++ + m = re.search(r"\.layers\.(\d+)\.", name) + if m is None: + return False @@ -1824,13 +2931,22 @@ index 000000000..43bad76a2 + global _cu + if _cu is None: + cu = ctypes.CDLL("libcuda.so.1") -+ cu.cuLaunchKernel.argtypes = [ctypes.c_void_p] + [ctypes.c_uint] * 6 + [ -+ ctypes.c_uint, ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p), -+ ctypes.c_void_p] -+ cu.cuModuleLoad.argtypes = [ctypes.POINTER(ctypes.c_void_p), -+ ctypes.c_char_p] -+ cu.cuModuleGetFunction.argtypes = [ctypes.POINTER(ctypes.c_void_p), -+ ctypes.c_void_p, ctypes.c_char_p] ++ cu.cuLaunchKernel.argtypes = ( ++ [ctypes.c_void_p] ++ + [ctypes.c_uint] * 6 ++ + [ ++ ctypes.c_uint, ++ ctypes.c_void_p, ++ ctypes.POINTER(ctypes.c_void_p), ++ ctypes.c_void_p, ++ ] ++ ) ++ cu.cuModuleLoad.argtypes = [ctypes.POINTER(ctypes.c_void_p), ctypes.c_char_p] ++ cu.cuModuleGetFunction.argtypes = [ ++ ctypes.POINTER(ctypes.c_void_p), ++ ctypes.c_void_p, ++ ctypes.c_char_p, ++ ] + _cu = cu + return _cu + @@ -1850,9 +2966,13 @@ index 000000000..43bad76a2 + torch.cuda.init() + torch.zeros(1, device="cuda") + cu = _driver() -+ for tier, kern in (("w2", b"moe_w2_mm"), ("w4", b"moe_w4_mm"), -+ ("w4s", b"moe_w4s_mm"), -+ ("w2mc2", b"moe_w2_mm"), ("w2mc4", b"moe_w2_mm")): ++ for tier, kern in ( ++ ("w2", b"moe_w2_mm"), ++ ("w4", b"moe_w4_mm"), ++ ("w4s", b"moe_w4s_mm"), ++ ("w2mc2", b"moe_w2_mm"), ++ ("w2mc4", b"moe_w2_mm"), ++ ): + # GEMM contraction K: gate-up needs K=hidden (4096 DS4-Flash, + # 6144 GLM-5.x, 7168 Kimi-K2.x); down needs K=I/TP (2048 @ TP1, + # 1024 @ TP2, 512 @ TP4). Cubins are loaded opportunistically -- @@ -1867,11 +2987,15 @@ index 000000000..43bad76a2 + if not os.path.exists(path): + continue + mod = ctypes.c_void_p() -+ _ck(cu.cuModuleLoad(ctypes.byref(mod), path.encode()), -+ f"cuModuleLoad {path}") ++ _ck( ++ cu.cuModuleLoad(ctypes.byref(mod), path.encode()), ++ f"cuModuleLoad {path}", ++ ) + fn = ctypes.c_void_p() -+ _ck(cu.cuModuleGetFunction(ctypes.byref(fn), mod, kern), -+ "cuModuleGetFunction") ++ _ck( ++ cu.cuModuleGetFunction(ctypes.byref(fn), mod, kern), ++ "cuModuleGetFunction", ++ ) + _fns[(tier, k)] = fn + global _afrag_ok + if _AFRAG: @@ -1881,11 +3005,15 @@ index 000000000..43bad76a2 + if not os.path.exists(path): + continue + mod = ctypes.c_void_p() -+ _ck(cu.cuModuleLoad(ctypes.byref(mod), path.encode()), -+ f"cuModuleLoad {path}") ++ _ck( ++ cu.cuModuleLoad(ctypes.byref(mod), path.encode()), ++ f"cuModuleLoad {path}", ++ ) + fn = ctypes.c_void_p() -+ _ck(cu.cuModuleGetFunction(ctypes.byref(fn), mod, b"moe_w2_mm"), -+ "cuModuleGetFunction afrag") ++ _ck( ++ cu.cuModuleGetFunction(ctypes.byref(fn), mod, b"moe_w2_mm"), ++ "cuModuleGetFunction afrag", ++ ) + _fns[("w2mc4afrag", k)] = fn + _afrag_ok = True + logger.info("moe_w2_cubit: AFRAG prefill cubins loaded") @@ -1905,6 +3033,7 @@ index 000000000..43bad76a2 +# Load-time plane building +# -------------------------------------------------------------------------- + ++ +def _require_kernels(K13: int, K2: int, need_w4: bool) -> None: + """Fail loudly at weight load when the cubins this model's shapes need are + missing from _DIR (they are loaded opportunistically in _ensure_ready).""" @@ -1916,7 +3045,8 @@ index 000000000..43bad76a2 + missing = [f"{t}_k{k}" for t, k in need if (t, k) not in _fns] + assert not missing, ( + f"moe_w2_cubit: missing cubins for K13={K13}/K2={K2}: {missing} " -+ f"(dir {_DIR}; set VLLM_MOE_W2_CUBIT_DIR)") ++ f"(dir {_DIR}; set VLLM_MOE_W2_CUBIT_DIR)" ++ ) + + +def _fp4_tier_for_build(E: int, dev, n13k13: int, n2k2: int): @@ -1930,12 +3060,18 @@ index 000000000..43bad76a2 + the base slot the refinement is residency-coupled to.""" + from vllm.model_executor.layers.quantization.utils import moe_w2_delta + split = moe_w2_delta.split_enabled() -+ sc13, sc2 = ((n13k13 // 32, n2k2 // 32) -+ if moe_w2_delta.base_enabled() and not split else (0, 0)) ++ sc13, sc2 = ( ++ (n13k13 // 32, n2k2 // 32) ++ if moe_w2_delta.base_enabled() and not split ++ else (0, 0) ++ ) + div = 4 if split else 2 -+ return moe_w2_delta.get_tier(n_experts=E, dev=dev, -+ w13_bytes=n13k13 // div + sc13, -+ w2_bytes=n2k2 // div + sc2) ++ return moe_w2_delta.get_tier( ++ n_experts=E, ++ dev=dev, ++ w13_bytes=n13k13 // div + sc13, ++ w2_bytes=n2k2 // div + sc2, ++ ) + + +def _stage_fp4_host(tier, layer_key: int, fp13, sc13, fp2, sc2) -> None: @@ -1990,10 +3126,11 @@ index 000000000..43bad76a2 + their loaders. Returns True when the layer boots with zero checkpoint + staging.""" + from vllm.model_executor.layers.quantization.utils import moe_w2_delta -+ from vllm.model_executor.layers.quantization.utils import ( -+ moe_w2_planes_cache as _pc) ++ from vllm.model_executor.layers.quantization.utils import moe_w2_planes_cache as _pc + from vllm.model_executor.layers.quantization.utils.moe_w2_store import ( -+ pack_has_layer) ++ pack_has_layer, ++ ) ++ + global _n_created + key = _n_created + _n_created += 1 @@ -2012,8 +3149,7 @@ index 000000000..43bad76a2 + # host-resident: the pack store serves the base (and the FP4 + # need-pool when configured) — probe the sidecars. + n_keys = _layer_cutoff() + 1 -+ if not pack_has_layer("base", key, n_keys, E, -+ c13len + s13len + c2len + s2len): ++ if not pack_has_layer("base", key, n_keys, E, c13len + s13len + c2len + s2len): + return False + if moe_w2_delta.enabled(): + # over-base FP4 need-pool: mirror _fp4_tier_for_build's sizing @@ -2032,11 +3168,11 @@ index 000000000..43bad76a2 + # layer index, sized exactly like process-time try_load). + lidx = _pc.layer_idx_from_name(getattr(layer, "layer_name", "")) + if lidx is None or not _pc.cache_has_layer( -+ lidx, _pc.expected_sizes( -+ E, N13, K13, N2, K2, want_fp4=moe_w2_delta.enabled())): ++ lidx, ++ _pc.expected_sizes(E, N13, K13, N2, K2, want_fp4=moe_w2_delta.enabled()), ++ ): + return False -+ for pname in ("w13_weight", "w13_weight_scale", -+ "w2_weight", "w2_weight_scale"): ++ for pname in ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"): + p = getattr(layer, pname) + p.data = torch.empty(0, dtype=p.data.dtype, device="cpu") + p.weight_loader = _noop_loader @@ -2048,7 +3184,9 @@ index 000000000..43bad76a2 + logger.info( + "moe_w2 LOADER-SKIP armed: pack-resident expert layers are " + "neither host-staged nor copied from the checkpoint " -+ "(first: key %d)", key) ++ "(first: key %d)", ++ key, ++ ) + logger.debug("moe_w2: layer key %d loader-skipped", key) + return True + @@ -2086,11 +3224,28 @@ index 000000000..43bad76a2 + # layer's buffer the moment its first tensor arrives; with a + # layer-major checkpoint only a couple of layers are ever + # in-flight, an unordered one merely degrades to the old profile. ++ allocation_label = None + if param.data.numel() == 0: + shape = self._layer._moe_w2_stream_shapes[self._pname] -+ param.data = torch.empty(shape, dtype=param.data.dtype, -+ device="cpu") -+ ret = self._inner(param, loaded_weight, *args, **kwargs) ++ from vllm.model_executor.layers.quantization.utils import ( ++ moe_w2_store, ++ ) ++ ++ allocation_bytes = param.data.element_size() ++ for dim in shape: ++ allocation_bytes *= dim ++ label = f"lazy expert staging {self._pname} {tuple(shape)}" ++ moe_w2_store.allocation_preflight(label, allocation_bytes) ++ param.data = torch.empty(shape, dtype=param.data.dtype, device="cpu") ++ allocation_label = label ++ try: ++ ret = self._inner(param, loaded_weight, *args, **kwargs) ++ finally: ++ # torch.empty is demand-paged; the copy in `_inner` is what ++ # commits RSS/cgroup memory. Check after that touch, including a ++ # partial-copy failure, rather than after the virtual allocation. ++ if allocation_label is not None: ++ moe_w2_store.allocation_postflight(allocation_label) + ok = (ret is True) if kwargs.get("return_success") else True + if not ok: + return ret @@ -2100,11 +3255,13 @@ index 000000000..43bad76a2 + f"moe_w2 stream-build: {self._pname} load arrived after " + f"the layer was already built — more (expert, shard) " + f"tensors than expected; set VLLM_MOE_W2_STREAM_BUILD=0 " -+ f"and report the checkpoint") ++ f"and report the checkpoint" ++ ) + pend[self._pname] -= 1 + if all(v == 0 for v in pend.values()): + key = self._layer._moe_w2_create_key -+ build_layer_planes_nvfp4(self._layer, key) ++ builder = self._layer._moe_w2_stream_builder ++ builder(self._layer, key) + # Drop the staging storage IN PLACE on the ORIGINAL Parameter + # objects. _finish_layer replaced the layer's attributes with + # stub Parameters, but load_weights' params_dict (built once, @@ -2116,19 +3273,18 @@ index 000000000..43bad76a2 + p.data = torch.empty(0, dtype=p.data.dtype, device="cpu") + self._layer._moe_w2_stream_orig = () + self._layer._moe_w2_stream_built = True -+ logger.debug("moe_w2: layer key %d stream-built during load", -+ key) ++ logger.debug("moe_w2: layer key %d stream-built during load", key) + return ret + + -+def arm_stream_build(layer) -> bool: ++def arm_stream_build(layer, checkpoint_format: str = "nvfp4") -> bool: + """Arm the streaming per-layer build on a layer plan_pack_skip missed + (first boot, or a store that does not yet hold it). Expected loads per -+ param: w13-side shards land twice per expert (w1, w3), w2-side once — -+ exact counts over ALL SIX params the requant reads (including scale_2: ++ param: w13-side shards land twice per expert (w1, w3), w2-side once. ++ NVFP4 counts all six params the requant reads (including scale_2: + building before they land would bake uninitialized per-tensor scales -+ into the planes AND the caches), so completeness needs no -+ checkpoint-ordering assumption. ++ into the planes AND the caches); MXFP4 counts its four weight/scale ++ params. Completeness therefore needs no checkpoint-ordering assumption. + + Staging is LAZY: the four big params become 0-byte stubs here and a + layer's buffers materialize on its FIRST loaded tensor (the up-front @@ -2145,25 +3301,44 @@ index 000000000..43bad76a2 + E = layer.w13_weight.shape[0] + except Exception: # noqa: BLE001 - unexpected layout: staged path + return False -+ expected = {"w13_weight": 2 * E, "w13_weight_scale": 2 * E, -+ "w13_weight_scale_2": 2 * E, -+ "w2_weight": E, "w2_weight_scale": E, -+ "w2_weight_scale_2": E} ++ if checkpoint_format == "nvfp4": ++ expected = { ++ "w13_weight": 2 * E, ++ "w13_weight_scale": 2 * E, ++ "w13_weight_scale_2": 2 * E, ++ "w2_weight": E, ++ "w2_weight_scale": E, ++ "w2_weight_scale_2": E, ++ } ++ builder = build_layer_planes_nvfp4 ++ elif checkpoint_format == "mxfp4": ++ expected = { ++ "w13_weight": 2 * E, ++ "w13_weight_scale": 2 * E, ++ "w2_weight": E, ++ "w2_weight_scale": E, ++ } ++ builder = build_layer_planes ++ else: ++ raise ValueError( ++ f"moe_w2 stream-build: unsupported checkpoint format {checkpoint_format!r}" ++ ) + big = ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale") + wrappers = {} + for pname in expected: + p = getattr(layer, pname, None) + inner = getattr(p, "weight_loader", None) + if p is None or inner is None: -+ return False # leave the layer fully staged ++ return False # leave the layer fully staged + wrappers[pname] = (p, _StreamLoader(layer, pname, inner)) + layer._moe_w2_pending = expected ++ layer._moe_w2_stream_builder = builder ++ layer._moe_w2_stream_format = checkpoint_format + # originals of the BIG params: their storage is dropped in place at + # build time, and their SHAPES feed the lazy materialization + layer._moe_w2_stream_orig = tuple(getattr(layer, p) for p in big) -+ layer._moe_w2_stream_shapes = { -+ p: tuple(getattr(layer, p).shape) for p in big} -+ for pname in big: # lazy: nothing staged until loaded ++ layer._moe_w2_stream_shapes = {p: tuple(getattr(layer, p).shape) for p in big} ++ for pname in big: # lazy: nothing staged until loaded + p = getattr(layer, pname) + p.data = torch.empty(0, dtype=p.data.dtype, device="cpu") + for pname, (p, wrap) in wrappers.items(): @@ -2174,12 +3349,14 @@ index 000000000..43bad76a2 + "moe_w2 STREAM-BUILD armed: layer staging materializes on its " + "first loaded tensor and requants on its last (peak staging = " + "layers in flight, not the checkpoint); " -+ "VLLM_MOE_W2_STREAM_BUILD=0 restores the old path") ++ "VLLM_MOE_W2_STREAM_BUILD=0 restores the old path" ++ ) + return True + + -+def _try_skip_requant(layer, layer_key: int, E: int, N13: int, K13: int, -+ N2: int, K2: int, param_names) -> bool: ++def _try_skip_requant( ++ layer, layer_key: int, E: int, N13: int, K13: int, N2: int, K2: int, param_names ++) -> bool: + """Boot-from-pack: when every host store this config serves from already + holds this layer's rows (valid pack written by a previous boot), the + dequant->requant of the checkpoint experts produces bytes NOBODY reads — @@ -2198,37 +3375,45 @@ index 000000000..43bad76a2 + but ITS pack misses the layer, we also requant (the fp4 sections can + only be rebuilt from the checkpoint bytes).""" + from vllm.model_executor.layers.quantization.utils import moe_w2_delta ++ + if not moe_w2_delta.base_enabled(): + return False + dev = torch.device("cuda") + c13len, s13len = N13 * K13 // 4, N13 * K13 // 32 + c2len, s2len = N2 * K2 // 4, N2 * K2 // 32 + btier = moe_w2_delta.get_base_tier( -+ _layer_cutoff() + 1, E, dev, -+ w13_bytes=c13len + s13len, w2_bytes=c2len + s2len) ++ _layer_cutoff() + 1, E, dev, w13_bytes=c13len + s13len, w2_bytes=c2len + s2len ++ ) + if layer_key not in btier._store: + return False + tier = _fp4_tier_for_build(E, dev, N13 * K13, N2 * K2) + if tier is not None and layer_key not in tier._store: + return False -+ from vllm.model_executor.layers.quantization.utils import ( -+ moe_w2_planes_cache as _pc) ++ from vllm.model_executor.layers.quantization.utils import moe_w2_planes_cache as _pc ++ + _LAYERS[layer_key] = dict( -+ N13=N13, K13=K13, N2=N2, K2=K2, E=E, base=True, ++ N13=N13, ++ K13=K13, ++ N2=N2, ++ K2=K2, ++ E=E, ++ base=True, + tl_idx=_pc.layer_idx_from_name(getattr(layer, "layer_name", "")), -+ off_s13=c13len, off_c2=c13len + s13len, ++ off_s13=c13len, ++ off_c2=c13len + s13len, + off_s2=c13len + s13len + c2len, -+ off4_s13=2 * c13len, off4_c2=2 * c13len + s13len, ++ off4_s13=2 * c13len, ++ off4_c2=2 * c13len + s13len, + off4_s2=2 * c13len + s13len + 2 * c2len, + ) + stub = torch.empty(0, dtype=torch.uint8, device=dev) + for name in param_names: -+ layer.register_parameter( -+ name, torch.nn.Parameter(stub, requires_grad=False)) ++ layer.register_parameter(name, torch.nn.Parameter(stub, requires_grad=False)) + logger.info( -+ "moe_w2: layer %d requant SKIPPED — %s serving from pack " -+ "(boot-from-pack)", layer_key, -+ "base+fp4" if tier is not None else "base") ++ "moe_w2: layer %d requant SKIPPED — %s serving from pack (boot-from-pack)", ++ layer_key, ++ "base+fp4" if tier is not None else "base", ++ ) + return True + + @@ -2241,18 +3426,56 @@ index 000000000..43bad76a2 + """ + assert _ensure_ready(), "moe_w2 cubins missing" + dev = torch.device("cuda") -+ w13 = layer.w13_weight.data # [E, 2I, H/2] u8 (cpu) -+ s13 = layer.w13_weight_scale.data # [E, 2I, H/32] u8 -+ w2 = layer.w2_weight.data # [E, H, I/2] u8 -+ s2 = layer.w2_weight_scale.data # [E, H, I/32] u8 ++ from vllm.model_executor.layers.quantization.utils import moe_w2_delta ++ ++ if getattr(layer, "_moe_w2_pack_skip", False): ++ # Loader-level skip leaves 0-byte parameter stubs, so shapes must ++ # come from the create-time stash. The store probed by ++ # plan_pack_skip must still serve the layer; checkpoint bytes no ++ # longer exist as a fallback. ++ E, N13, K13, N2, K2 = layer._moe_w2_shapes ++ _require_kernels(K13, K2, need_w4=moe_w2_delta.enabled()) ++ if moe_w2_delta.base_enabled(): ++ assert _try_skip_requant( ++ layer, ++ layer_key, ++ E, ++ N13, ++ K13, ++ N2, ++ K2, ++ ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"), ++ ), ( ++ f"moe_w2: layer {layer_key} was loader-skipped on a pack " ++ f"sidecar hit but the pack no longer serves it " ++ f"(dir/sidecar changed mid-load?) — restart without the " ++ f"stale VLLM_MOE_W2_STORE_DIR state" ++ ) ++ return ++ assert _consume_planes_cache(layer, layer_key, dev, E, N13, K13, N2, K2), ( ++ f"moe_w2: layer {layer_key} was loader-skipped on a planes-" ++ f"cache hit but the cache no longer serves it — restart " ++ f"without the stale VLLM_MOE_W2_PLANES_CACHE state" ++ ) ++ return ++ w13 = layer.w13_weight.data # [E, 2I, H/2] u8 (cpu) ++ s13 = layer.w13_weight_scale.data # [E, 2I, H/32] u8 ++ w2 = layer.w2_weight.data # [E, H, I/2] u8 ++ s2 = layer.w2_weight_scale.data # [E, H, I/32] u8 + E, N13, _ = w13.shape + _, N2, _ = w2.shape -+ K13, K2 = N2, N13 // 2 # H, I (4096/2048 on DS4-Flash TP1) -+ from vllm.model_executor.layers.quantization.utils import moe_w2_delta ++ K13, K2 = N2, N13 // 2 # H, I (4096/2048 on DS4-Flash TP1) + _require_kernels(K13, K2, need_w4=moe_w2_delta.enabled()) -+ if _try_skip_requant(layer, layer_key, E, N13, K13, N2, K2, -+ ("w13_weight", "w13_weight_scale", "w2_weight", -+ "w2_weight_scale")): ++ if _try_skip_requant( ++ layer, ++ layer_key, ++ E, ++ N13, ++ K13, ++ N2, ++ K2, ++ ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"), ++ ): + return + + planes13 = torch.empty(E, N13 * K13 // 4, dtype=torch.uint8, device=dev) @@ -2261,7 +3484,10 @@ index 000000000..43bad76a2 + sc2 = torch.empty(E, N2 * K2 // 32, dtype=torch.uint8, device=dev) + + from vllm.model_executor.layers.quantization.utils.moe_w2_planes import ( -+ mxfp4_to_nibbles, pack_fp4_fragment_major) ++ mxfp4_to_nibbles, ++ pack_fp4_fragment_major, ++ ) ++ + # Pass the PER-RANK FP4 plane sizes (N*K//2 bytes/expert) so the delta tier's + # slots, host store, and pool indexing match the (TP-sharded) planes. On TP1 + # these equal the module constants -> the single-GPU path is unchanged. @@ -2300,14 +3526,26 @@ index 000000000..43bad76a2 + # created; the old "start on layer NUM_LAYERS-1" trigger never fired + # under PP, where layer_keys are local per rank and never reach 42) + -+ _finish_layer(layer, layer_key, dev, planes13, sc13, planes2, sc2, -+ N13, K13, N2, K2, E, -+ ("w13_weight", "w13_weight_scale", "w2_weight", -+ "w2_weight_scale")) ++ _finish_layer( ++ layer, ++ layer_key, ++ dev, ++ planes13, ++ sc13, ++ planes2, ++ sc2, ++ N13, ++ K13, ++ N2, ++ K2, ++ E, ++ ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"), ++ ) + + -+def build_layer_planes_fp8(layer, layer_key: int, -+ scale_suffix: str = "weight_scale_inv") -> None: ++def build_layer_planes_fp8( ++ layer, layer_key: int, scale_suffix: str = "weight_scale_inv" ++) -> None: + """FP8 block-quant checkpoint variant of build_layer_planes (Fp8MoEMethod: + DS4-Flash-FP8, GLM-5.2-FP8 — models without an FP4 release). + @@ -2320,21 +3558,30 @@ index 000000000..43bad76a2 + """ + from vllm.model_executor.layers.quantization.utils import moe_w2_delta + from vllm.model_executor.layers.quantization.utils.moe_w2_planes import ( -+ fp8_block_to_codes_scales, pack_fp4_fragment_major) ++ fp8_block_to_codes_scales, ++ pack_fp4_fragment_major, ++ ) + + assert _ensure_ready(), "moe_w2 cubins missing" + dev = torch.device("cuda") -+ w13 = layer.w13_weight.data # [E, 2I, H] e4m3 (cpu) ++ w13 = layer.w13_weight.data # [E, 2I, H] e4m3 (cpu) + s13 = getattr(layer, f"w13_{scale_suffix}").data # [E, 2I/128, H/128] f32 -+ w2 = layer.w2_weight.data # [E, H, I] e4m3 -+ s2 = getattr(layer, f"w2_{scale_suffix}").data # [E, H/128, I/128] f32 ++ w2 = layer.w2_weight.data # [E, H, I] e4m3 ++ s2 = getattr(layer, f"w2_{scale_suffix}").data # [E, H/128, I/128] f32 + assert w13.dtype == torch.float8_e4m3fn, w13.dtype + E, N13, K13 = w13.shape + _, N2, K2 = w2.shape + _require_kernels(K13, K2, need_w4=moe_w2_delta.enabled()) -+ if _try_skip_requant(layer, layer_key, E, N13, K13, N2, K2, -+ ("w13_weight", f"w13_{scale_suffix}", "w2_weight", -+ f"w2_{scale_suffix}")): ++ if _try_skip_requant( ++ layer, ++ layer_key, ++ E, ++ N13, ++ K13, ++ N2, ++ K2, ++ ("w13_weight", f"w13_{scale_suffix}", "w2_weight", f"w2_{scale_suffix}"), ++ ): + return + + planes13 = torch.empty(E, N13 * K13 // 4, dtype=torch.uint8, device=dev) @@ -2359,7 +3606,8 @@ index 000000000..43bad76a2 + sg = s13[e0:e1].to(dev, non_blocking=True) + for i in range(e1 - e0): + codes, sbytes, nib = fp8_block_to_codes_scales( -+ wg[i], sg[i], want_nibbles=fp13 is not None) ++ wg[i], sg[i], want_nibbles=fp13 is not None ++ ) + planes13[e0 + i] = pack_fragment_major(codes) + sc13[e0 + i] = pack_scales(sbytes) + if fp13 is not None: @@ -2368,7 +3616,8 @@ index 000000000..43bad76a2 + sg = s2[e0:e1].to(dev, non_blocking=True) + for i in range(e1 - e0): + codes, sbytes, nib = fp8_block_to_codes_scales( -+ wg[i], sg[i], want_nibbles=fp2 is not None) ++ wg[i], sg[i], want_nibbles=fp2 is not None ++ ) + planes2[e0 + i] = pack_fragment_major(codes) + sc2[e0 + i] = pack_scales(sbytes) + if fp2 is not None: @@ -2378,15 +3627,26 @@ index 000000000..43bad76a2 + _stage_fp4_host(tier, layer_key, fp13, sc13, fp2, sc2) + del fp13, fp2 + -+ _finish_layer(layer, layer_key, dev, planes13, sc13, planes2, sc2, -+ N13, K13, N2, K2, E, -+ ("w13_weight", f"w13_{scale_suffix}", "w2_weight", -+ f"w2_{scale_suffix}")) ++ _finish_layer( ++ layer, ++ layer_key, ++ dev, ++ planes13, ++ sc13, ++ planes2, ++ sc2, ++ N13, ++ K13, ++ N2, ++ K2, ++ E, ++ ("w13_weight", f"w13_{scale_suffix}", "w2_weight", f"w2_{scale_suffix}"), ++ ) + + -+def _consume_planes_cache(layer, layer_key: int, dev, -+ E: int, N13: int, K13: int, N2: int, -+ K2: int) -> bool: ++def _consume_planes_cache( ++ layer, layer_key: int, dev, E: int, N13: int, K13: int, N2: int, K2: int ++) -> bool: + """Serve one layer's planes from the planes cache (GPU-resident + configs). CPU tensors from the cache feed the same _stage_fp4_host/ + _finish_layer sinks as a fresh requant (their copy_ calls are @@ -2394,13 +3654,17 @@ index 000000000..43bad76a2 + requant) and the loader-skip path (stubs; the cache is the ONLY + source). Returns True on a hit.""" + from vllm.model_executor.layers.quantization.utils import ( -+ moe_w2_planes_cache as planes_cache) ++ moe_w2_planes_cache as planes_cache, ++ ) ++ + lidx = planes_cache.layer_idx_from_name(getattr(layer, "layer_name", "")) + if not planes_cache.enabled() or lidx is None: + return False + tier = _fp4_tier_for_build(E, dev, N13 * K13, N2 * K2) -+ cached = planes_cache.try_load(lidx, planes_cache.expected_sizes( -+ E, N13, K13, N2, K2, want_fp4=tier is not None)) ++ cached = planes_cache.try_load( ++ lidx, ++ planes_cache.expected_sizes(E, N13, K13, N2, K2, want_fp4=tier is not None), ++ ) + if cached is None: + return False + planes13 = cached["planes13"].view(E, -1).to(dev) @@ -2408,12 +3672,29 @@ index 000000000..43bad76a2 + planes2 = cached["planes2"].view(E, -1).to(dev) + sc2 = cached["sc2"].view(E, -1).to(dev) + if tier is not None: -+ _stage_fp4_host(tier, layer_key, cached["fp13"].view(E, -1), -+ sc13, cached["fp2"].view(E, -1), sc2) -+ _finish_layer(layer, layer_key, dev, planes13, sc13, planes2, -+ sc2, N13, K13, N2, K2, E, -+ ("w13_weight", "w13_weight_scale", "w2_weight", -+ "w2_weight_scale")) ++ _stage_fp4_host( ++ tier, ++ layer_key, ++ cached["fp13"].view(E, -1), ++ sc13, ++ cached["fp2"].view(E, -1), ++ sc2, ++ ) ++ _finish_layer( ++ layer, ++ layer_key, ++ dev, ++ planes13, ++ sc13, ++ planes2, ++ sc2, ++ N13, ++ K13, ++ N2, ++ K2, ++ E, ++ ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"), ++ ) + logger.info("moe_w2: layer %d planes from cache", lidx) + return True + @@ -2432,7 +3713,9 @@ index 000000000..43bad76a2 + """ + from vllm.model_executor.layers.quantization.utils import moe_w2_delta + from vllm.model_executor.layers.quantization.utils.moe_w2_planes import ( -+ nvfp4_to_codes_scales, pack_fp4_fragment_major) ++ nvfp4_to_codes_scales, ++ pack_fp4_fragment_major, ++ ) + + assert _ensure_ready(), "moe_w2 cubins missing" + dev = torch.device("cuda") @@ -2445,40 +3728,56 @@ index 000000000..43bad76a2 + _require_kernels(K13, K2, need_w4=moe_w2_delta.enabled()) + if moe_w2_delta.base_enabled(): + assert _try_skip_requant( -+ layer, layer_key, E, N13, K13, N2, K2, -+ ("w13_weight", "w13_weight_scale", "w2_weight", -+ "w2_weight_scale")), ( ++ layer, ++ layer_key, ++ E, ++ N13, ++ K13, ++ N2, ++ K2, ++ ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"), ++ ), ( + f"moe_w2: layer {layer_key} was loader-skipped on a pack " + f"sidecar hit but the pack no longer serves it " + f"(dir/sidecar changed mid-load?) — restart without the " -+ f"stale VLLM_MOE_W2_STORE_DIR state") ++ f"stale VLLM_MOE_W2_STORE_DIR state" ++ ) + return + # GPU-resident: materialize the planes from the planes cache + # (probed at create time; a miss here means the cache dir changed + # under a live load). -+ assert _consume_planes_cache(layer, layer_key, dev, -+ E, N13, K13, N2, K2), ( ++ assert _consume_planes_cache(layer, layer_key, dev, E, N13, K13, N2, K2), ( + f"moe_w2: layer {layer_key} was loader-skipped on a planes-" + f"cache hit but the cache no longer serves it — restart " -+ f"without the stale VLLM_MOE_W2_PLANES_CACHE state") ++ f"without the stale VLLM_MOE_W2_PLANES_CACHE state" ++ ) + return -+ w13 = layer.w13_weight.data # [E, 2I, H/2] u8 (cpu) -+ s13 = layer.w13_weight_scale.data # [E, 2I, H/16] e4m3 -+ s13_2 = layer.w13_weight_scale_2.data # [E, 2] f32 (w1, w3) -+ w2 = layer.w2_weight.data # [E, H, I/2] u8 -+ s2 = layer.w2_weight_scale.data # [E, H, I/16] e4m3 -+ s2_2 = layer.w2_weight_scale_2.data # [E] f32 ++ w13 = layer.w13_weight.data # [E, 2I, H/2] u8 (cpu) ++ s13 = layer.w13_weight_scale.data # [E, 2I, H/16] e4m3 ++ s13_2 = layer.w13_weight_scale_2.data # [E, 2] f32 (w1, w3) ++ w2 = layer.w2_weight.data # [E, H, I/2] u8 ++ s2 = layer.w2_weight_scale.data # [E, H, I/16] e4m3 ++ s2_2 = layer.w2_weight_scale_2.data # [E] f32 + assert w13.dtype == torch.uint8 and s13.dtype == torch.float8_e4m3fn, ( -+ w13.dtype, s13.dtype) ++ w13.dtype, ++ s13.dtype, ++ ) + E, N13, K13h = w13.shape + K13 = K13h * 2 + _, N2, K2h = w2.shape + K2 = K2h * 2 -+ group = K13 // s13.shape[2] # 16 for NVFP4 ++ group = K13 // s13.shape[2] # 16 for NVFP4 + _require_kernels(K13, K2, need_w4=moe_w2_delta.enabled()) -+ if _try_skip_requant(layer, layer_key, E, N13, K13, N2, K2, -+ ("w13_weight", "w13_weight_scale", "w2_weight", -+ "w2_weight_scale")): ++ if _try_skip_requant( ++ layer, ++ layer_key, ++ E, ++ N13, ++ K13, ++ N2, ++ K2, ++ ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"), ++ ): + return + + # Planes cache (VLLM_MOE_W2_PLANES_CACHE): the requant below is @@ -2491,7 +3790,9 @@ index 000000000..43bad76a2 + if _consume_planes_cache(layer, layer_key, dev, E, N13, K13, N2, K2): + return + from vllm.model_executor.layers.quantization.utils import ( -+ moe_w2_planes_cache as planes_cache) ++ moe_w2_planes_cache as planes_cache, ++ ) ++ + lidx = planes_cache.layer_idx_from_name(getattr(layer, "layer_name", "")) + tier = _fp4_tier_for_build(E, dev, N13 * K13, N2 * K2) + @@ -2515,12 +3816,12 @@ index 000000000..43bad76a2 + wg = w13[e0:e1].to(dev, non_blocking=True) + sg = s13[e0:e1].to(dev, non_blocking=True) + s2g = s13_2[e0:e1].to(dev, non_blocking=True) -+ half = N13 // 2 # rows [0:I]=w1, [I:2I]=w3 ++ half = N13 // 2 # rows [0:I]=w1, [I:2I]=w3 + for i in range(e1 - e0): + s2_row = torch.cat((s2g[i, 0].expand(half), s2g[i, 1].expand(half))) + codes, sbytes, nib = nvfp4_to_codes_scales( -+ wg[i], sg[i], s2_row, group=group, -+ want_nibbles=fp13 is not None) ++ wg[i], sg[i], s2_row, group=group, want_nibbles=fp13 is not None ++ ) + planes13[e0 + i] = pack_fragment_major(codes) + sc13[e0 + i] = pack_scales(sbytes) + if fp13 is not None: @@ -2530,36 +3831,69 @@ index 000000000..43bad76a2 + s2g = s2_2[e0:e1].to(dev, non_blocking=True) + for i in range(e1 - e0): + codes, sbytes, nib = nvfp4_to_codes_scales( -+ wg[i], sg[i], s2g[i], group=group, -+ want_nibbles=fp2 is not None) ++ wg[i], sg[i], s2g[i], group=group, want_nibbles=fp2 is not None ++ ) + planes2[e0 + i] = pack_fragment_major(codes) + sc2[e0 + i] = pack_scales(sbytes) + if fp2 is not None: + fp2[e0 + i] = _pack_fp4_plane(nib) + + if planes_cache.enabled() and lidx is not None: -+ planes_cache.store(lidx, dict(planes13=planes13, sc13=sc13, -+ planes2=planes2, sc2=sc2, -+ fp13=fp13, fp2=fp2)) ++ planes_cache.store( ++ lidx, ++ dict( ++ planes13=planes13, ++ sc13=sc13, ++ planes2=planes2, ++ sc2=sc2, ++ fp13=fp13, ++ fp2=fp2, ++ ), ++ ) + + if tier is not None: + _stage_fp4_host(tier, layer_key, fp13, sc13, fp2, sc2) + del fp13, fp2 + -+ _finish_layer(layer, layer_key, dev, planes13, sc13, planes2, sc2, -+ N13, K13, N2, K2, E, -+ ("w13_weight", "w13_weight_scale", "w2_weight", -+ "w2_weight_scale")) ++ _finish_layer( ++ layer, ++ layer_key, ++ dev, ++ planes13, ++ sc13, ++ planes2, ++ sc2, ++ N13, ++ K13, ++ N2, ++ K2, ++ E, ++ ("w13_weight", "w13_weight_scale", "w2_weight", "w2_weight_scale"), ++ ) + + -+def _finish_layer(layer, layer_key, dev, planes13, sc13, planes2, sc2, -+ N13, K13, N2, K2, E, param_names) -> None: ++def _finish_layer( ++ layer, ++ layer_key, ++ dev, ++ planes13, ++ sc13, ++ planes2, ++ sc2, ++ N13, ++ K13, ++ N2, ++ K2, ++ E, ++ param_names, ++) -> None: + from vllm.model_executor.layers.quantization.utils import moe_w2_delta ++ + # transformer layer index of this layer_key (dense-offset models: GLM's + # first sparse layer 3 -> key 0). LOOKA uses it to pair each key with + # its transformer layer's router (mlp.gate) weights. -+ from vllm.model_executor.layers.quantization.utils import ( -+ moe_w2_planes_cache as _pc) ++ from vllm.model_executor.layers.quantization.utils import moe_w2_planes_cache as _pc ++ + _tl = _pc.layer_idx_from_name(getattr(layer, "layer_name", "")) + if moe_w2_delta.base_enabled(): + # BASE cache (inverted delta): the 2-bit planes go to PINNED HOST RAM @@ -2570,61 +3904,97 @@ index 000000000..43bad76a2 + c13len, s13len = planes13.shape[1], sc13.shape[1] + c2len, s2len = planes2.shape[1], sc2.shape[1] + btier = moe_w2_delta.get_base_tier( -+ _layer_cutoff() + 1, E, dev, -+ w13_bytes=c13len + s13len, w2_bytes=c2len + s2len) ++ _layer_cutoff() + 1, ++ E, ++ dev, ++ w13_bytes=c13len + s13len, ++ w2_bytes=c2len + s2len, ++ ) + btier.add_layer_host_planes( + layer_key, + torch.cat((planes13, sc13), dim=1), -+ torch.cat((planes2, sc2), dim=1)) ++ torch.cat((planes2, sc2), dim=1), ++ ) + _LAYERS[layer_key] = dict( -+ N13=N13, K13=K13, N2=N2, K2=K2, E=E, base=True, tl_idx=_tl, -+ off_s13=c13len, off_c2=c13len + s13len, ++ N13=N13, ++ K13=K13, ++ N2=N2, ++ K2=K2, ++ E=E, ++ base=True, ++ tl_idx=_tl, ++ off_s13=c13len, ++ off_c2=c13len + s13len, + off_s2=c13len + s13len + c2len, + # FP4 need-pool slot sections ([fp4_13|sc13|fp4_2|sc2]; fp4 codes + # are 2x the 2-bit codes, scale sections identical) — read by the + # base+delta desc kernel when the FP4 tier coexists. -+ off4_s13=2 * c13len, off4_c2=2 * c13len + s13len, ++ off4_s13=2 * c13len, ++ off4_c2=2 * c13len + s13len, + off4_s2=2 * c13len + s13len + 2 * c2len, + ) + del planes13, sc13, planes2, sc2 + stub = torch.empty(0, dtype=torch.uint8, device=dev) + for name in param_names: + layer.register_parameter( -+ name, torch.nn.Parameter(stub, requires_grad=False)) -+ logger.info("moe_w2: layer %d planes HOST-staged (base cache, " -+ "%.2f GiB pinned)", layer_key, -+ E * btier.slot_bytes / 2**30) ++ name, torch.nn.Parameter(stub, requires_grad=False) ++ ) ++ logger.info( ++ "moe_w2: layer %d planes HOST-staged (base cache, %.2f GiB pinned)", ++ layer_key, ++ E * btier.slot_bytes / 2**30, ++ ) + return + + _LAYERS[layer_key] = dict( -+ planes13=planes13, sc13=sc13, planes2=planes2, sc2=sc2, -+ N13=N13, K13=K13, N2=N2, K2=K2, E=E, tl_idx=_tl, ++ planes13=planes13, ++ sc13=sc13, ++ planes2=planes2, ++ sc2=sc2, ++ N13=N13, ++ K13=K13, ++ N2=N2, ++ K2=K2, ++ E=E, ++ tl_idx=_tl, + ) + # Release checkpoint copies; keep CUDA stubs so device probes stay happy. + stub = torch.empty(0, dtype=torch.uint8, device=dev) + for name in param_names: -+ layer.register_parameter( -+ name, torch.nn.Parameter(stub, requires_grad=False)) -+ logger.info("moe_w2: layer %d planes built (%.2f GiB)", layer_key, -+ (planes13.nbytes + sc13.nbytes + planes2.nbytes + sc2.nbytes) -+ / 2**30) ++ layer.register_parameter(name, torch.nn.Parameter(stub, requires_grad=False)) ++ logger.info( ++ "moe_w2: layer %d planes built (%.2f GiB)", ++ layer_key, ++ (planes13.nbytes + sc13.nbytes + planes2.nbytes + sc2.nbytes) / 2**30, ++ ) + + +# -------------------------------------------------------------------------- +# Forward +# -------------------------------------------------------------------------- + -+def _workspaces(slots: int, tokens: int, dev, inter: int = 2048, -+ hidden: int = 4096, n_experts: int = 256) -> dict: ++ ++def _workspaces( ++ slots: int, ++ tokens: int, ++ dev, ++ inter: int = 2048, ++ hidden: int = 4096, ++ n_experts: int = 256, ++) -> dict: + # `inter` = per-rank expert intermediate size I (2048 on 1 GPU; 1024 @ TP2, + # 512 @ TP4 as the experts shard). The hidden H (4096 DS4, 6144 GLM-5.x) is + # NOT sharded, so the A-side (a1), x-quant (xq) and w2 output (c2) buffers + # stay H-wide; only the gate/up output (c13 = 2I), the intermediate + # activation (act/a2 = I) and its group-128 scales (as2 = I/128) follow the + # shard. -+ if (_WS.get("slots", 0) < slots or _WS.get("tokens", 0) < tokens -+ or _WS.get("inter") != inter or _WS.get("hidden") != hidden -+ or _WS.get("n_experts", 0) < n_experts): ++ if ( ++ _WS.get("slots", 0) < slots ++ or _WS.get("tokens", 0) < tokens ++ or _WS.get("inter") != inter ++ or _WS.get("hidden") != hidden ++ or _WS.get("n_experts", 0) < n_experts ++ ): + slots = max(slots, _WS.get("slots", 0)) + tokens = max(tokens, _WS.get("tokens", 0)) + n_experts = max(n_experts, _WS.get("n_experts", 0)) @@ -2637,46 +4007,43 @@ index 000000000..43bad76a2 + # token-side quant buffers; the LAST row is the permanent zero + # pad row (gather source for filler slots) — quant only ever + # writes rows [:T]. -+ xq=torch.zeros(tokens + 1, hidden, dtype=torch.float8_e4m3fn, -+ device=dev), -+ xs=torch.zeros(tokens + 1, hidden // 128, dtype=torch.float32, -+ device=dev), -+ a1=torch.zeros(slots + 4, hidden, dtype=torch.float8_e4m3fn, -+ device=dev), -+ as1=torch.zeros(slots + 4, hidden // 128, dtype=torch.float32, -+ device=dev), ++ xq=torch.zeros(tokens + 1, hidden, dtype=torch.float8_e4m3fn, device=dev), ++ xs=torch.zeros(tokens + 1, hidden // 128, dtype=torch.float32, device=dev), ++ a1=torch.zeros(slots + 4, hidden, dtype=torch.float8_e4m3fn, device=dev), ++ as1=torch.zeros(slots + 4, hidden // 128, dtype=torch.float32, device=dev), + # zeros, not empty: pad-pair rows are never written by the kernel + # (early EXIT) yet flow through silu/scatter math with weight 0; + # uninitialized inf/nan would poison 0*x. -+ c13=torch.zeros(slots + 4, 2 * inter, dtype=torch.bfloat16, -+ device=dev), ++ c13=torch.zeros(slots + 4, 2 * inter, dtype=torch.bfloat16, device=dev), + act=torch.zeros(slots + 4, inter, dtype=torch.bfloat16, device=dev), -+ a2=torch.zeros(slots + 4, inter, dtype=torch.float8_e4m3fn, -+ device=dev), -+ as2=torch.zeros(slots + 4, max(inter // 128, 1), -+ dtype=torch.float32, device=dev), -+ c2=torch.zeros(slots + 4, hidden, dtype=torch.bfloat16, -+ device=dev), -+ desc=torch.empty(4, slots // _BLOCK, 6, dtype=torch.int64, -+ device=dev), ++ a2=torch.zeros(slots + 4, inter, dtype=torch.float8_e4m3fn, device=dev), ++ as2=torch.zeros( ++ slots + 4, max(inter // 128, 1), dtype=torch.float32, device=dev ++ ), ++ c2=torch.zeros(slots + 4, hidden, dtype=torch.bfloat16, device=dev), ++ desc=torch.empty(4, slots // _BLOCK, 6, dtype=torch.int64, device=dev), + # split-FP4 (moe_w4s_mm) desc tables: 8 u64 per pair, 64 B ABI -+ desc4s=torch.empty(2, slots // _BLOCK, 8, dtype=torch.int64, -+ device=dev), ++ desc4s=torch.empty( ++ 2, slots // _BLOCK, 8, dtype=torch.int64, device=dev ++ ), + # -1 slot row for the tier-less desc path; sized to the MODEL's + # expert count (256 = DS4 default; 384 Kimi-K2.x reads past a + # fixed 256-row table). -+ no_slots=torch.full((max(n_experts, 256),), -1, -+ dtype=torch.int32, device=dev), ++ no_slots=torch.full( ++ (max(n_experts, 256),), -1, dtype=torch.int32, device=dev ++ ), + ) + if _afrag_ok: + # AFRAG destination buffers: the triton repack streams row-major + # a1/a2 into these (single pass, no copy-back); the desc tables + # point the GEMM at them instead of a1/a2. + _WS.update( -+ a1f=torch.zeros(slots + 4, hidden, dtype=torch.float8_e4m3fn, -+ device=dev), -+ a2f=torch.zeros(slots + 4, inter, dtype=torch.float8_e4m3fn, -+ device=dev), ++ a1f=torch.zeros( ++ slots + 4, hidden, dtype=torch.float8_e4m3fn, device=dev ++ ), ++ a2f=torch.zeros( ++ slots + 4, inter, dtype=torch.float8_e4m3fn, device=dev ++ ), + ) + return _WS + @@ -2715,13 +4082,38 @@ index 000000000..43bad76a2 + +@triton.jit +def _desc_build_kernel( -+ eids_ptr, npost_ptr, slot_ptr, d_ptr, -+ a1b, as1b, c13b, a2b, as2b, c2b, -+ p13b, s13b, p2b, s2b, poolb, -+ p13s, s13s, p2s, s2s, -+ slot_bytes, w13_bytes, -+ a1_rb, as1_rb, c13_rb, a2_rb, as2_rb, c2_rb, -+ n_experts, pairs, cap6, mblock, ++ eids_ptr, ++ npost_ptr, ++ pair_live_ptr, ++ slot_ptr, ++ d_ptr, ++ a1b, ++ as1b, ++ c13b, ++ a2b, ++ as2b, ++ c2b, ++ p13b, ++ s13b, ++ p2b, ++ s2b, ++ poolb, ++ p13s, ++ s13s, ++ p2s, ++ s2s, ++ slot_bytes, ++ w13_bytes, ++ a1_rb, ++ as1_rb, ++ c13_rb, ++ a2_rb, ++ as2_rb, ++ c2_rb, ++ n_experts, ++ pairs, ++ cap6, ++ mblock, + BLOCK: tl.constexpr, +): + """All four moe desc tables in one launch (24 columns per pair). @@ -2738,7 +4130,8 @@ index 000000000..43bad76a2 + e = tl.minimum(tl.maximum(e, 0), n_experts - 1) + slot = tl.load(slot_ptr + e, mask=mask, other=-1).to(tl.int64) + npost = tl.load(npost_ptr).to(tl.int64) -+ live = p < npost // mblock ++ has_real = tl.load(pair_live_ptr + p, mask=mask, other=0) != 0 ++ live = (p < npost // mblock) & has_real + is4 = slot >= 0 + m2 = tl.where(live & ~is4, mblock, 0).to(tl.int64) + m4 = tl.where(live & is4, mblock, 0).to(tl.int64) @@ -2759,11 +4152,16 @@ index 000000000..43bad76a2 + elif gi == 1: + b, s, a, as_, c, m = p2b + e * p2s, bs2, a2, as2, c2, m2 + elif gi == 2: -+ b, s, a, as_, c, m = (poolb + slot_c * slot_bytes, bs13, -+ a1, as1, c13, m4) ++ b, s, a, as_, c, m = (poolb + slot_c * slot_bytes, bs13, a1, as1, c13, m4) + else: -+ b, s, a, as_, c, m = (poolb + slot_c * slot_bytes + w13_bytes, -+ bs2, a2, as2, c2, m4) ++ b, s, a, as_, c, m = ( ++ poolb + slot_c * slot_bytes + w13_bytes, ++ bs2, ++ a2, ++ as2, ++ c2, ++ m4, ++ ) + tl.store(d + 0, a, mask=mask) + tl.store(d + 1, as_, mask=mask) + tl.store(d + 2, b, mask=mask) @@ -2774,7 +4172,7 @@ index 000000000..43bad76a2 + +@triton.jit +def _desc_build_kernel_w4s( -+ eids_ptr, npost_ptr, slot_ptr, d_ptr, ++ eids_ptr, npost_ptr, pair_live_ptr, slot_ptr, d_ptr, + a1b, as1b, c13b, a2b, as2b, c2b, + p13b, s13b, p2b, s2b, poolb, + p13s, s13s, p2s, s2s, @@ -2795,7 +4193,8 @@ index 000000000..43bad76a2 + e = tl.minimum(tl.maximum(e, 0), n_experts - 1) + slot = tl.load(slot_ptr + e, mask=mask, other=-1).to(tl.int64) + npost = tl.load(npost_ptr).to(tl.int64) -+ live = p < npost // mblock ++ has_real = tl.load(pair_live_ptr + p, mask=mask, other=0) != 0 ++ live = (p < npost // mblock) & has_real + is4 = slot >= 0 + m4 = tl.where(live & is4, mblock, 0).to(tl.int64) + base = p.to(tl.int64) * mblock @@ -2826,11 +4225,33 @@ index 000000000..43bad76a2 + +@triton.jit +def _desc_build_kernel_basecache( -+ eids_ptr, npost_ptr, slot_ptr, miss_ptr, d_ptr, -+ a1b, as1b, c13b, a2b, as2b, c2b, -+ poolb, slot_bytes, off_s13, off_c2, off_s2, -+ a1_rb, as1_rb, c13_rb, a2_rb, as2_rb, c2_rb, -+ n_experts, pairs, cap6, mblock, ++ eids_ptr, ++ npost_ptr, ++ pair_live_ptr, ++ slot_ptr, ++ miss_ptr, ++ d_ptr, ++ a1b, ++ as1b, ++ c13b, ++ a2b, ++ as2b, ++ c2b, ++ poolb, ++ slot_bytes, ++ off_s13, ++ off_c2, ++ off_s2, ++ a1_rb, ++ as1_rb, ++ c13_rb, ++ a2_rb, ++ as2_rb, ++ c2_rb, ++ n_experts, ++ pairs, ++ cap6, ++ mblock, + BLOCK: tl.constexpr, +): + """Base-cache variant of _desc_build_kernel: the 2-bit BASE planes live in @@ -2847,7 +4268,8 @@ index 000000000..43bad76a2 + e = tl.minimum(tl.maximum(e, 0), n_experts - 1) + slot = tl.load(slot_ptr + e, mask=mask, other=-1).to(tl.int64) + npost = tl.load(npost_ptr).to(tl.int64) -+ live = p < npost // mblock ++ has_real = tl.load(pair_live_ptr + p, mask=mask, other=0) != 0 ++ live = (p < npost // mblock) & has_real + hit = slot >= 0 + m = tl.where(live & hit, mblock, 0).to(tl.int64) + n_miss = tl.sum(tl.where(mask & live & ~hit, 1, 0)) @@ -2877,12 +4299,39 @@ index 000000000..43bad76a2 + +@triton.jit +def _desc_build_kernel_base_delta( -+ eids_ptr, npost_ptr, bslot_ptr, fslot_ptr, miss_ptr, d_ptr, -+ a1b, as1b, c13b, a2b, as2b, c2b, -+ bpoolb, bslot_bytes, off_s13, off_c2, off_s2, -+ fpoolb, fslot_bytes, off4_s13, off4_c2, off4_s2, -+ a1_rb, as1_rb, c13_rb, a2_rb, as2_rb, c2_rb, -+ n_experts, pairs, cap6, mblock, ++ eids_ptr, ++ npost_ptr, ++ pair_live_ptr, ++ bslot_ptr, ++ fslot_ptr, ++ miss_ptr, ++ d_ptr, ++ a1b, ++ as1b, ++ c13b, ++ a2b, ++ as2b, ++ c2b, ++ bpoolb, ++ bslot_bytes, ++ off_s13, ++ off_c2, ++ off_s2, ++ fpoolb, ++ fslot_bytes, ++ off4_s13, ++ off4_c2, ++ off4_s2, ++ a1_rb, ++ as1_rb, ++ c13_rb, ++ a2_rb, ++ as2_rb, ++ c2_rb, ++ n_experts, ++ pairs, ++ cap6, ++ mblock, + BLOCK: tl.constexpr, +): + """Base cache + FP4 need-pool coexistence variant: TWO slot tables with @@ -2900,7 +4349,8 @@ index 000000000..43bad76a2 + bslot = tl.load(bslot_ptr + e, mask=mask, other=-1).to(tl.int64) + fslot = tl.load(fslot_ptr + e, mask=mask, other=-1).to(tl.int64) + npost = tl.load(npost_ptr).to(tl.int64) -+ live = p < npost // mblock ++ has_real = tl.load(pair_live_ptr + p, mask=mask, other=0) != 0 ++ live = (p < npost // mblock) & has_real + is4 = fslot >= 0 + bhit = bslot >= 0 + m2 = tl.where(live & bhit & ~is4, mblock, 0).to(tl.int64) @@ -2936,12 +4386,39 @@ index 000000000..43bad76a2 + +@triton.jit +def _desc_build_kernel_base_delta_split( -+ eids_ptr, npost_ptr, bslot_ptr, fslot_ptr, miss_ptr, d_ptr, d4s_ptr, -+ a1b, as1b, c13b, a2b, as2b, c2b, -+ bpoolb, bslot_bytes, off_s13, off_c2, off_s2, -+ fpoolb, fslot_bytes, w13r_bytes, -+ a1_rb, as1_rb, c13_rb, a2_rb, as2_rb, c2_rb, -+ n_experts, pairs, cap6, cap8, mblock, ++ eids_ptr, ++ npost_ptr, ++ pair_live_ptr, ++ bslot_ptr, ++ fslot_ptr, ++ miss_ptr, ++ d_ptr, ++ d4s_ptr, ++ a1b, ++ as1b, ++ c13b, ++ a2b, ++ as2b, ++ c2b, ++ bpoolb, ++ bslot_bytes, ++ off_s13, ++ off_c2, ++ off_s2, ++ fpoolb, ++ fslot_bytes, ++ w13r_bytes, ++ a1_rb, ++ as1_rb, ++ c13_rb, ++ a2_rb, ++ as2_rb, ++ c2_rb, ++ n_experts, ++ pairs, ++ cap6, ++ cap8, ++ mblock, + BLOCK: tl.constexpr, +): + """Base cache + SPLIT FP4 need-pool: refinement slots are read AGAINST @@ -2961,9 +4438,10 @@ index 000000000..43bad76a2 + bslot = tl.load(bslot_ptr + e, mask=mask, other=-1).to(tl.int64) + fslot = tl.load(fslot_ptr + e, mask=mask, other=-1).to(tl.int64) + npost = tl.load(npost_ptr).to(tl.int64) -+ live = p < npost // mblock ++ has_real = tl.load(pair_live_ptr + p, mask=mask, other=0) != 0 ++ live = (p < npost // mblock) & has_real + bhit = bslot >= 0 -+ is4 = (fslot >= 0) & bhit # split serve needs BOTH resident ++ is4 = (fslot >= 0) & bhit # split serve needs BOTH resident + m2 = tl.where(live & bhit & ~is4, mblock, 0).to(tl.int64) + m4 = tl.where(live & is4, mblock, 0).to(tl.int64) + n_miss = tl.sum(tl.where(mask & live & ~bhit, 1, 0)) @@ -2977,7 +4455,7 @@ index 000000000..43bad76a2 + a2 = a2b + base * a2_rb + as2 = as2b + base * as2_rb + c2 = c2b + base * c2_rb -+ for gi in tl.static_range(2): # w2 tables (base pool sections) ++ for gi in tl.static_range(2): # w2 tables (base pool sections) + d = d_ptr + gi * cap6 + p * 6 + if gi == 0: + b, s, a, as_, c = bs, bs + off_s13, a1, as1, c13 @@ -2989,13 +4467,19 @@ index 000000000..43bad76a2 + tl.store(d + 3, s, mask=mask) + tl.store(d + 4, c, mask=mask) + tl.store(d + 5, m2, mask=mask) -+ for gi in tl.static_range(2): # w4s tables (base + refinement) ++ for gi in tl.static_range(2): # w4s tables (base + refinement) + d = d4s_ptr + gi * cap8 + p * 8 + if gi == 0: + bb, rr, ss, a, as_, c = bs, fs, bs + off_s13, a1, as1, c13 + else: -+ bb, rr, ss, a, as_, c = (bs + off_c2, fs + w13r_bytes, -+ bs + off_s2, a2, as2, c2) ++ bb, rr, ss, a, as_, c = ( ++ bs + off_c2, ++ fs + w13r_bytes, ++ bs + off_s2, ++ a2, ++ as2, ++ c2, ++ ) + tl.store(d + 0, a, mask=mask) + tl.store(d + 1, as_, mask=mask) + tl.store(d + 2, bb, mask=mask) @@ -3006,19 +4490,34 @@ index 000000000..43bad76a2 + tl.store(d + 7, tl.zeros_like(m4), mask=mask) + + -+def _launch(tier: str, K: int, desc: torch.Tensor, n_rows: int, pairs: int, -+ stream): ++def _launch(tier: str, K: int, desc: torch.Tensor, n_rows: int, pairs: int, stream): + fn = _fns[(tier, K)] -+ args = [ctypes.c_uint64(desc.data_ptr()), -+ ctypes.c_uint32(K), -+ ctypes.c_uint32(K // 64), -+ ctypes.c_uint32(n_rows * 2), -+ ctypes.c_uint32(K // 128)] ++ args = [ ++ ctypes.c_uint64(desc.data_ptr()), ++ ctypes.c_uint32(K), ++ ctypes.c_uint32(K // 64), ++ ctypes.c_uint32(n_rows * 2), ++ ctypes.c_uint32(K // 128), ++ ] + argv = (ctypes.c_void_p * len(args))( -+ *[ctypes.cast(ctypes.byref(x), ctypes.c_void_p) for x in args]) -+ _ck(_driver().cuLaunchKernel(fn, n_rows // 16, pairs, 1, -+ _nwarp_for_k(K) * 32, 1, 1, 0, -+ stream, argv, None), "launch") ++ *[ctypes.cast(ctypes.byref(x), ctypes.c_void_p) for x in args] ++ ) ++ _ck( ++ _driver().cuLaunchKernel( ++ fn, ++ n_rows // 16, ++ pairs, ++ 1, ++ _nwarp_for_k(K) * 32, ++ 1, ++ 1, ++ 0, ++ stream, ++ argv, ++ None, ++ ), ++ "launch", ++ ) + + +def _moe_w2_forward( @@ -3028,6 +4527,7 @@ index 000000000..43bad76a2 + layer_key: int, +) -> torch.Tensor: + from vllm.model_executor.layers.quantization.utils import prefill_timers ++ + with prefill_timers.span("moe_w2"): + return _moe_w2_forward_timed(x, topk_weights, topk_ids, layer_key) + @@ -3047,6 +4547,8 @@ index 000000000..43bad76a2 + + st = _LAYERS[layer_key] + T, H = x.shape ++ token_slot_mapping = _get_token_slot_mapping(T) ++ logical_prefill = _get_has_prefill(T) + # adaptive expert top-p (env-gated; identity when off). Must run before + # moe_align/mark_seen/route_log so dropped experts are neither fetched + # nor counted as routed. @@ -3054,23 +4556,26 @@ index 000000000..43bad76a2 + top_k = topk_ids.shape[1] + dev = x.device + stream = ctypes.c_void_p(torch.cuda.current_stream(dev).cuda_stream) -+ -+ # decode-sized calls use the proven 4-token kernel + delta tier; -+ # prefill-sized calls use the MC4 kernel (16 tokens per pair-entry = full -+ # QMMA-M, plane reads amortized 4x, ~1.5x over MC2) on the 2-bit base only. -+ # 96 = the largest cudagraph capture size: anything above is necessarily a -+ # prefill chunk; short tail chunks keep the delta-quality path. -+ prefill = T > 96 -+ mblock = 16 if prefill else _BLOCK ++ capturing = torch.cuda.is_current_stream_capturing() ++ if logical_prefill and capturing: ++ raise RuntimeError("moe_w2 prefill residency cannot run under CUDA capture") ++ ++ # Bulk prefills use the MC4 kernel (16 tokens per pair-entry = full ++ # QMMA-M) on the 2-bit base. Short prefill tails retain the 4-token kernel ++ # and delta-quality path, but still use eager per-layer base residency. ++ bulk_prefill = logical_prefill and T > _BULK_PREFILL_TOKENS ++ mblock = 16 if bulk_prefill else _BLOCK + sorted_ids, expert_blocks, num_post = moe_align_block_size( -+ topk_ids, mblock, st["E"]) ++ topk_ids, mblock, st["E"], pad_sorted_ids=True ++ ) + slots = sorted_ids.numel() ++ if slots % mblock: ++ raise RuntimeError("moe_w2 aligned route capacity must be mblock-divisible") + pairs = slots // mblock + # st["K2"] = per-rank expert intermediate I (w2 contraction), st["K13"] = + # hidden H (w13 contraction) -> size the workspaces for the model's shapes + # (and correctly under tensor parallelism). -+ ws = _workspaces(slots, T, dev, inter=st["K2"], hidden=st["K13"], -+ n_experts=st["E"]) ++ ws = _workspaces(slots, T, dev, inter=st["K2"], hidden=st["K13"], n_experts=st["E"]) + + # ---- activation quant (group-128) into the padded buffer; the buffer's + # last row is the permanent zero pad row for filler slots. @@ -3078,26 +4583,32 @@ index 000000000..43bad76a2 + pad_row = xq.shape[0] - 1 + _, xs = per_token_group_quant_fp8(x, 128, out_q=xq[:T]) + ws["xs"][:T] = xs -+ valid = sorted_ids < T * top_k -+ rows = torch.where(valid, sorted_ids // top_k, -+ torch.full_like(sorted_ids, pad_row)) -+ torch.index_select(xq.view(torch.uint8), 0, rows, -+ out=ws["a1"][:slots].view(torch.uint8)) ++ # Runner updates this persistent buffer every step and writes -1 to the ++ # cudagraph padding tail. Reading it on-device keeps replay dynamic while ++ # all tensor shapes and addresses stay capture-stable. ++ token_valid, valid, pair_live, rows = _masked_route_metadata( ++ sorted_ids, token_slot_mapping, top_k, mblock, pad_row ++ ) ++ torch.index_select( ++ xq.view(torch.uint8), 0, rows, out=ws["a1"][:slots].view(torch.uint8) ++ ) + torch.index_select(ws["xs"], 0, rows, out=ws["as1"][:slots]) + + # ---- desc tables in ONE triton launch + from vllm.model_executor.layers.quantization.utils import moe_w2_delta ++ + base_mode = st.get("base", False) + # AFRAG (prefill): the GEMM reads fragment-major activations from the + # dedicated a1f/a2f buffers (filled by the single-pass triton repack + # below); point the desc 'a' fields there. w4 tables are decode-only, + # so redirecting the shared base in prefill is safe. -+ use_afrag = prefill and _afrag_ok ++ use_afrag = bulk_prefill and _afrag_ok + a1_base = ws["a1f"] if use_afrag else ws["a1"] + a2_base = ws["a2f"] if use_afrag else ws["a2"] + d = ws["desc"] + cap = d.shape[1] + miss_rows = None ++ use_w4s = False + if base_mode: + # BASE cache: 2-bit planes come from the base tier's GPU pool; a live + # pair with a non-resident expert contributes zero and bumps the miss @@ -3107,29 +4618,30 @@ index 000000000..43bad76a2 + # (delta tier over the base cache, gate-filled) coexists on the + # decode path: FP4-resident pairs divert to the w4 tier. + btier = moe_w2_delta._BASE_TIER -+ tier = moe_w2_delta._TIER # FP4 need-pool (None unless opted in) -+ if torch.cuda.is_current_stream_capturing(): ++ tier = moe_w2_delta._TIER # FP4 need-pool (None unless opted in) ++ if capturing: + btier.notify_capture() + if tier is not None: + tier.notify_capture() -+ elif prefill: -+ btier.ensure_resident(layer_key, topk_ids.view(-1)) -+ moe_w2_delta.mark_seen(btier.seen[layer_key], topk_ids.view(-1).long()) ++ elif logical_prefill: ++ btier.ensure_resident(layer_key, topk_ids[token_valid].reshape(-1)) ++ moe_w2_delta.mark_seen(btier.seen[layer_key], topk_ids.long(), token_valid) + if tier is not None: + # the gate's force_promote reads the FP4 tier's own seen scatter -+ moe_w2_delta.mark_seen(tier.seen[layer_key], -+ topk_ids.view(-1).long()) -+ if not prefill: ++ moe_w2_delta.mark_seen(tier.seen[layer_key], topk_ids.long(), token_valid) ++ if not logical_prefill: + # LOOKA/PILOT (router-lookahead): score predictors + write the + # next layer's prediction. Must run BEFORE the route_log + # overwrite below (predictor [0] reads last step's ids from it). + # In-graph safe (persistent buffers, static shapes); no-op + # unless armed. -+ from vllm.model_executor.layers.quantization.utils import ( -+ moe_w2_looka) ++ from vllm.model_executor.layers.quantization.utils import moe_w2_looka ++ + if moe_w2_looka.enabled(): -+ moe_w2_looka.record(layer_key, x, topk_ids, btier.route_log) -+ if not prefill and btier.route_log is not None: ++ moe_w2_looka.record( ++ layer_key, x, topk_ids, btier.route_log, token_valid ++ ) ++ if not logical_prefill and btier.route_log is not None: + # per-(token,layer) routing log for the draft-prefetch predictor: + # a static [n_layers, T_cap, k_cap] buffer the runner reads back + # post-step (~KBs). In-graph safe: fixed shapes per captured @@ -3137,53 +4649,127 @@ index 000000000..43bad76a2 + # count hold stale ids — the host slices by the true T. + _t = min(topk_ids.shape[0], btier.route_log.shape[1]) + _k = min(topk_ids.shape[1], btier.route_log.shape[2]) ++ logged_ids = torch.where( ++ token_valid[:, None], topk_ids, torch.full_like(topk_ids, -1) ++ ) + btier.route_log[layer_key, :_t, :_k].copy_( -+ topk_ids[:_t, :_k], non_blocking=True) ++ logged_ids[:_t, :_k], non_blocking=True ++ ) + if layer_key == 0: + # per-step counter reset, in-graph (layer 0 runs first each step) + btier.miss_count.zero_() + slot_row = btier.slot_table[layer_key] -+ use_fp4 = tier is not None and not prefill ++ use_fp4 = tier is not None and not bulk_prefill + use_w4s_base = use_fp4 and moe_w2_delta.split_enabled() ++ use_w4s = use_w4s_base + if use_w4s_base: + fslot_row = tier.slot_table[layer_key] + d4s = ws["desc4s"] + _desc_build_kernel_base_delta_split[(triton.cdiv(pairs, 256),)]( -+ expert_blocks, num_post, slot_row, fslot_row, -+ btier.miss_count, d, d4s, -+ a1_base.data_ptr(), ws["as1"].data_ptr(), ws["c13"].data_ptr(), -+ a2_base.data_ptr(), ws["as2"].data_ptr(), ws["c2"].data_ptr(), -+ btier.pool.data_ptr(), btier.slot_bytes, -+ st["off_s13"], st["off_c2"], st["off_s2"], -+ tier.pool.data_ptr(), tier.slot_bytes, tier.w13_bytes, -+ st["K13"], (st["K13"] // 128) * 4, 4 * st["K2"], st["K2"], -+ (st["K2"] // 128) * 4, 2 * st["K13"], -+ st["E"], pairs, cap * 6, d4s.shape[1] * 8, mblock, BLOCK=256) ++ expert_blocks, ++ num_post, ++ pair_live, ++ slot_row, ++ fslot_row, ++ btier.miss_count, ++ d, ++ d4s, ++ a1_base.data_ptr(), ++ ws["as1"].data_ptr(), ++ ws["c13"].data_ptr(), ++ a2_base.data_ptr(), ++ ws["as2"].data_ptr(), ++ ws["c2"].data_ptr(), ++ btier.pool.data_ptr(), ++ btier.slot_bytes, ++ st["off_s13"], ++ st["off_c2"], ++ st["off_s2"], ++ tier.pool.data_ptr(), ++ tier.slot_bytes, ++ tier.w13_bytes, ++ st["K13"], ++ (st["K13"] // 128) * 4, ++ 4 * st["K2"], ++ st["K2"], ++ (st["K2"] // 128) * 4, ++ 2 * st["K13"], ++ st["E"], ++ pairs, ++ cap * 6, ++ d4s.shape[1] * 8, ++ mblock, ++ BLOCK=256, ++ ) + elif use_fp4: + fslot_row = tier.slot_table[layer_key] + _desc_build_kernel_base_delta[(triton.cdiv(pairs, 256),)]( -+ expert_blocks, num_post, slot_row, fslot_row, -+ btier.miss_count, d, -+ a1_base.data_ptr(), ws["as1"].data_ptr(), ws["c13"].data_ptr(), -+ a2_base.data_ptr(), ws["as2"].data_ptr(), ws["c2"].data_ptr(), -+ btier.pool.data_ptr(), btier.slot_bytes, -+ st["off_s13"], st["off_c2"], st["off_s2"], -+ tier.pool.data_ptr(), tier.slot_bytes, -+ st["off4_s13"], st["off4_c2"], st["off4_s2"], -+ st["K13"], (st["K13"] // 128) * 4, 4 * st["K2"], st["K2"], -+ (st["K2"] // 128) * 4, 2 * st["K13"], -+ st["E"], pairs, cap * 6, mblock, BLOCK=256) ++ expert_blocks, ++ num_post, ++ pair_live, ++ slot_row, ++ fslot_row, ++ btier.miss_count, ++ d, ++ a1_base.data_ptr(), ++ ws["as1"].data_ptr(), ++ ws["c13"].data_ptr(), ++ a2_base.data_ptr(), ++ ws["as2"].data_ptr(), ++ ws["c2"].data_ptr(), ++ btier.pool.data_ptr(), ++ btier.slot_bytes, ++ st["off_s13"], ++ st["off_c2"], ++ st["off_s2"], ++ tier.pool.data_ptr(), ++ tier.slot_bytes, ++ st["off4_s13"], ++ st["off4_c2"], ++ st["off4_s2"], ++ st["K13"], ++ (st["K13"] // 128) * 4, ++ 4 * st["K2"], ++ st["K2"], ++ (st["K2"] // 128) * 4, ++ 2 * st["K13"], ++ st["E"], ++ pairs, ++ cap * 6, ++ mblock, ++ BLOCK=256, ++ ) + else: + _desc_build_kernel_basecache[(triton.cdiv(pairs, 256),)]( -+ expert_blocks, num_post, slot_row, -+ btier.miss_count, d, -+ a1_base.data_ptr(), ws["as1"].data_ptr(), ws["c13"].data_ptr(), -+ a2_base.data_ptr(), ws["as2"].data_ptr(), ws["c2"].data_ptr(), -+ btier.pool.data_ptr(), btier.slot_bytes, -+ st["off_s13"], st["off_c2"], st["off_s2"], -+ st["K13"], (st["K13"] // 128) * 4, 4 * st["K2"], st["K2"], -+ (st["K2"] // 128) * 4, 2 * st["K13"], -+ st["E"], pairs, cap * 6, mblock, BLOCK=256) ++ expert_blocks, ++ num_post, ++ pair_live, ++ slot_row, ++ btier.miss_count, ++ d, ++ a1_base.data_ptr(), ++ ws["as1"].data_ptr(), ++ ws["c13"].data_ptr(), ++ a2_base.data_ptr(), ++ ws["as2"].data_ptr(), ++ ws["c2"].data_ptr(), ++ btier.pool.data_ptr(), ++ btier.slot_bytes, ++ st["off_s13"], ++ st["off_c2"], ++ st["off_s2"], ++ st["K13"], ++ (st["K13"] // 128) * 4, ++ 4 * st["K2"], ++ st["K2"], ++ (st["K2"] // 128) * 4, ++ 2 * st["K13"], ++ st["E"], ++ pairs, ++ cap * 6, ++ mblock, ++ BLOCK=256, ++ ) + # Miss pairs get scatter weight 0: the GEMMs early-EXIT on m=0 and + # never write their c13/c2 rows, but those workspace rows hold STALE + # values from a previous forward — zeroing the WEIGHT (not the rows) @@ -3192,84 +4778,130 @@ index 000000000..43bad76a2 + # — except under split, where serving needs the BASE slot too (an + # FP4-mapped/base-missing pair contributed zero and must replay). + e_pair = expert_blocks.to(torch.long).clamp_(0, st["E"] - 1) -+ resident = (slot_row[e_pair] >= 0) ++ resident = slot_row[e_pair] >= 0 + if use_fp4 and not use_w4s_base: -+ resident |= (fslot_row[e_pair] >= 0) ++ resident |= fslot_row[e_pair] >= 0 + miss_rows = resident.repeat_interleave(mblock)[:slots] + if not use_fp4: -+ tier = None # downstream w4 launches key off `tier` ++ tier = None # downstream w4 launches key off `tier` + else: -+ tier = moe_w2_delta._TIER # peek only; created by the plane builder -+ if tier is not None and not prefill: -+ if torch.cuda.is_current_stream_capturing(): ++ tier = moe_w2_delta._TIER # peek only; created by the plane builder ++ if tier is not None and not bulk_prefill: ++ if capturing: + tier.notify_capture() + slot_row = tier.slot_table[layer_key] + pool_ptr = tier.pool.data_ptr() -+ moe_w2_delta.mark_seen(tier.seen[layer_key], -+ topk_ids.view(-1).long()) ++ moe_w2_delta.mark_seen(tier.seen[layer_key], topk_ids.long(), token_valid) + else: + if tier is not None: -+ moe_w2_delta.mark_seen(tier.seen[layer_key], -+ topk_ids.view(-1).long()) ++ moe_w2_delta.mark_seen( ++ tier.seen[layer_key], topk_ids.long(), token_valid ++ ) + slot_row = ws["no_slots"] -+ pool_ptr = ws["a1"].data_ptr() # never dereferenced (m4=0) ++ pool_ptr = ws["a1"].data_ptr() # never dereferenced (m4=0) + _desc_build_kernel[(triton.cdiv(pairs, 256),)]( -+ expert_blocks, num_post, slot_row, d, -+ a1_base.data_ptr(), ws["as1"].data_ptr(), ws["c13"].data_ptr(), -+ a2_base.data_ptr(), ws["as2"].data_ptr(), ws["c2"].data_ptr(), -+ st["planes13"].data_ptr(), st["sc13"].data_ptr(), -+ st["planes2"].data_ptr(), st["sc2"].data_ptr(), pool_ptr, -+ st["planes13"].shape[1], st["sc13"].shape[1], -+ st["planes2"].shape[1], st["sc2"].shape[1], ++ expert_blocks, ++ num_post, ++ pair_live, ++ slot_row, ++ d, ++ a1_base.data_ptr(), ++ ws["as1"].data_ptr(), ++ ws["c13"].data_ptr(), ++ a2_base.data_ptr(), ++ ws["as2"].data_ptr(), ++ ws["c2"].data_ptr(), ++ st["planes13"].data_ptr(), ++ st["sc13"].data_ptr(), ++ st["planes2"].data_ptr(), ++ st["sc2"].data_ptr(), ++ pool_ptr, ++ st["planes13"].shape[1], ++ st["sc13"].shape[1], ++ st["planes2"].shape[1], ++ st["sc2"].shape[1], + (tier.slot_bytes if tier is not None else moe_w2_delta.SLOT_BYTES), + (tier.w13_bytes if tier is not None else moe_w2_delta.W13_BYTES), + # row strides (bytes). H-side: a1 fp8 [H], as1 f32 [H/128], c2 bf16 + # [H]. per-rank intermediate side: c13 bf16 [2I], a2 fp8 [I], as2 + # f32 [I/128]. K13 = H, K2 = I -> identical to the old literals on + # DS4 TP1 (H=4096, I=2048); GLM-5.x gets H=6144, TP shards shrink I. -+ st["K13"], (st["K13"] // 128) * 4, 4 * st["K2"], st["K2"], -+ (st["K2"] // 128) * 4, 2 * st["K13"], -+ st["E"], pairs, cap * 6, mblock, BLOCK=256) -+ if tier is not None and not prefill and moe_w2_delta.split_enabled(): ++ st["K13"], ++ (st["K13"] // 128) * 4, ++ 4 * st["K2"], ++ st["K2"], ++ (st["K2"] // 128) * 4, ++ 2 * st["K13"], ++ st["E"], ++ pairs, ++ cap * 6, ++ mblock, ++ BLOCK=256, ++ ) ++ use_w4s = ( ++ tier is not None ++ and not bulk_prefill ++ and not base_mode ++ and moe_w2_delta.split_enabled() ++ ) ++ if use_w4s: + # split-FP4: the extra 8-field tables for moe_w4s_mm (base/bs = + # the resident plane rows, ref = the slot's refinement sections) + d4s = ws["desc4s"] + _desc_build_kernel_w4s[(triton.cdiv(pairs, 256),)]( -+ expert_blocks, num_post, slot_row, d4s, -+ a1_base.data_ptr(), ws["as1"].data_ptr(), -+ ws["c13"].data_ptr(), a2_base.data_ptr(), -+ ws["as2"].data_ptr(), ws["c2"].data_ptr(), -+ st["planes13"].data_ptr(), st["sc13"].data_ptr(), -+ st["planes2"].data_ptr(), st["sc2"].data_ptr(), pool_ptr, -+ st["planes13"].shape[1], st["sc13"].shape[1], -+ st["planes2"].shape[1], st["sc2"].shape[1], -+ tier.slot_bytes, tier.w13_bytes, -+ st["K13"], (st["K13"] // 128) * 4, 4 * st["K2"], st["K2"], -+ (st["K2"] // 128) * 4, 2 * st["K13"], -+ st["E"], pairs, d4s.shape[1] * 8, mblock, BLOCK=256) ++ expert_blocks, ++ num_post, ++ pair_live, ++ slot_row, ++ d4s, ++ a1_base.data_ptr(), ++ ws["as1"].data_ptr(), ++ ws["c13"].data_ptr(), ++ a2_base.data_ptr(), ++ ws["as2"].data_ptr(), ++ ws["c2"].data_ptr(), ++ st["planes13"].data_ptr(), ++ st["sc13"].data_ptr(), ++ st["planes2"].data_ptr(), ++ st["sc2"].data_ptr(), ++ pool_ptr, ++ st["planes13"].shape[1], ++ st["sc13"].shape[1], ++ st["planes2"].shape[1], ++ st["sc2"].shape[1], ++ tier.slot_bytes, ++ tier.w13_bytes, ++ st["K13"], ++ (st["K13"] // 128) * 4, ++ 4 * st["K2"], ++ st["K2"], ++ (st["K2"] // 128) * 4, ++ 2 * st["K13"], ++ st["E"], ++ pairs, ++ d4s.shape[1] * 8, ++ mblock, ++ BLOCK=256, ++ ) + + # ---- w13 GEMMs (both tiers) -> fused silu*up -> quant -> w2 GEMMs + # AFRAG prefill: single-pass triton repack row-major a1/a2 -> fragment-major + # a1f/a2f (desc built against a1f/a2f above) so the GEMM loads each m16k32 + # A-fragment in one LDG.128. Numerics bit-identical to mc4. -+ w2tier = ("w2mc4afrag" if use_afrag else "w2mc4") if prefill else "w2" -+ # AFRAG repacks COMPLETE 16-row tiles. `slots` is moe_align's OVER-ALLOCATED -+ # row count (sorted_ids.numel() = topk*T + E*15), NOT a multiple of 16; the -+ # desc/kernel only ever touch the first `pairs*16` rows (num_post <= pairs*16), -+ # so repack exactly that tile-aligned region. Rows [pairs*16:slots] are unused -+ # filler (never read). Capacity is fine: pairs*16 <= slots <= a1.shape[0]-4. ++ w2tier = ("w2mc4afrag" if use_afrag else "w2mc4") if bulk_prefill else "w2" ++ # AFRAG repacks complete 16-row tiles. moe_align pads sorted_ids to mblock, ++ # so the bulk path's entire slot region is tile-aligned. + if use_afrag: + _afrag_repack(ws["a1"], ws["a1f"], pairs, st["K13"]) + _launch(w2tier, st["K13"], d[0], st["N13"], pairs, stream) + # split-FP4 dispatch: both residency modes fill ws["desc4s"] (classic: + # _desc_build_kernel_w4s against resident planes; base cache: + # _desc_build_kernel_base_delta_split against the coupled base slots) -+ use_w4s = (tier is not None and not prefill -+ and moe_w2_delta.split_enabled()) -+ if tier is not None and not prefill: ++ if tier is not None and not bulk_prefill: + if use_w4s: -+ _launch("w4s", st["K13"], ws["desc4s"][0], st["N13"], pairs, -+ stream) ++ _launch( ++ "w4s", st["K13"], ws["desc4s"][0], st["N13"], pairs, stream ++ ) + else: + _launch("w4", st["K13"], d[2], st["N13"], pairs, stream) + act = ws["act"][:slots] @@ -3279,9 +4911,11 @@ index 000000000..43bad76a2 + if use_afrag: + _afrag_repack(ws["a2"], ws["a2f"], pairs, st["K2"]) + _launch(w2tier, st["K2"], d[1], st["N2"], pairs, stream) -+ if tier is not None and not prefill: ++ if tier is not None and not bulk_prefill: + if use_w4s: -+ _launch("w4s", st["K2"], ws["desc4s"][1], st["N2"], pairs, stream) ++ _launch( ++ "w4s", st["K2"], ws["desc4s"][1], st["N2"], pairs, stream ++ ) + else: + _launch("w4", st["K2"], d[3], st["N2"], pairs, stream) + @@ -3304,9 +4938,8 @@ index 000000000..43bad76a2 + # (their GEMMs early-EXITed) — zero their scatter weight so a miss + # contributes exactly nothing (the replay recomputes them properly). + w = w * miss_rows.to(torch.float32) -+ dump = T * top_k # collision row for filler slots -+ dst = torch.where(valid, sorted_ids, -+ torch.full_like(sorted_ids, dump)).long() ++ dump = T * top_k # collision row for filler slots ++ dst = torch.where(valid, sorted_ids, torch.full_like(sorted_ids, dump)).long() + gath = torch.zeros(dump + 1, H, dtype=torch.float32, device=dev) + gath.index_copy_(0, dst, ws["c2"][:slots].float() * w.unsqueeze(1)) + return gath[:dump].view(T, top_k, H).sum(dim=1).to(x.dtype) @@ -3337,10 +4970,10 @@ index 000000000..43bad76a2 + return enabled() and _ensure_ready() diff --git a/vllm/model_executor/layers/quantization/utils/moe_w2_delta.py b/vllm/model_executor/layers/quantization/utils/moe_w2_delta.py new file mode 100644 -index 000000000..f7905fd40 +index 0000000..f9b12c8 --- /dev/null +++ b/vllm/model_executor/layers/quantization/utils/moe_w2_delta.py -@@ -0,0 +1,1783 @@ +@@ -0,0 +1,1899 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""FP4 delta tier for the 1-GPU 2-bit MoE path (quality restoration). @@ -3357,13 +4990,13 @@ index 000000000..f7905fd40 + (w13 8.4 MiB + w2 4.2 MiB packed back-to-back per slot); + - slot table: int32 [layers, 256] on GPU (-1 = base tier), read by the + desc-build kernel inside CUDA graphs; -+ - manager thread: consumes the forward's last-seen expert flags ++ - manager thread: between forwards, consumes the last-seen expert flags + (event-synced D2H), promotes seen-but-uncached experts (H2D on a side + stream, capped per pass), evicts only experts cold for >= 2 passes. -+ Passes are EVENT-DRIVEN: the runner signals step boundaries (wake_all -+ via step_begin / the gate decision) and the manager runs at most one -+ pass per signal, rate-limited by VLLM_MOE_W2_DELTA_TICK_MS; a wall -+ clock timeout only provides liveness for configs that never signal. ++ Passes are EVENT-DRIVEN: the worker opens a manager window after each ++ completed forward and closes it before the next one. The manager runs at ++ most one pass per signal, rate-limited by VLLM_MOE_W2_DELTA_TICK_MS; a ++ wall-clock timeout only provides liveness for configs that never signal. + +Consistency model (deliberate): the table update is racy versus graph +replay — the worst case is one step reading the OLD tier for an expert, @@ -3634,6 +5267,12 @@ index 000000000..f7905fd40 + self._tick = 0 + self._stop = False + self._thread = None ++ # Manager passes may rewrite pool slots that CUDA graphs read without ++ # taking the Python tier lock. Keep the whole pass outside the forward ++ # window: pause_for_forward() acquires this lock before a target starts; ++ # wake() releases it only after every fixed-point/gate replay finishes. ++ self._forward_lock = threading.Lock() ++ self._forward_paused = False + # Step-boundary wakeup (wake()/wake_all()): coalescing event; the + # _wake_driven latch flips the loop from legacy polling to pure + # event cadence the moment the first signal arrives. @@ -3671,8 +5310,8 @@ index 000000000..f7905fd40 + self._kpi_2nd = 0 # extra replays for second-order misses + self._kpi_fp_giveup = 0 # steps that accepted second-order residue + self._kpi_fp_resid = 0 # residual missing pairs in those steps -+ # Slots touched since step_begin(): promoted or hit by any pass of -+ # the CURRENT step. Never evictable (even in the emergency pass) — ++ # Slots touched since step_begin(), or by the current eager prefill ++ # layer. Never evictable (even in the emergency pass) — + # without this, a fixed-point iteration can evict pass-k's fetches + # to serve pass-k+1 (their seen marks are zeroed after each + # snapshot) and ping-pong past the replay cap. @@ -3857,21 +5496,39 @@ index 000000000..f7905fd40 + # runner): keep the legacy fixed-period poll. + self._wake.wait(timeout=_TICK_S) + self._wake.clear() -+ gap = _TICK_S - (time.monotonic() - last) -+ if gap > 0: -+ time.sleep(gap) -+ last = time.monotonic() -+ try: -+ torch.cuda.set_device(self.dev) -+ self._tick_once() -+ except Exception as e: # noqa: BLE001 - never kill serving -+ logger.warning("delta tick failed: %s", e) -+ time.sleep(1.0) ++ # A target/replay sequence holds this lock. If the next target ++ # starts before an event-driven pass does, the pass waits; if the ++ # pass already started, the target waits for it to finish. ++ with self._forward_lock: ++ gap = _TICK_S - (time.monotonic() - last) ++ if gap > 0: ++ time.sleep(gap) ++ last = time.monotonic() ++ try: ++ torch.cuda.set_device(self.dev) ++ self._tick_once() ++ except Exception as e: # noqa: BLE001 - never kill serving ++ logger.warning("delta tick failed: %s", e) ++ time.sleep(1.0) ++ ++ def pause_for_forward(self) -> None: ++ """Block until any manager pass finishes, then exclude new passes. ++ ++ Called on the runner thread before routing marks are cleared and a new ++ target forward starts. Idempotence keeps a failed prior forward safe: ++ the next call remains paused until a later worker completion releases ++ the window. ++ """ ++ if self._forward_paused: ++ return ++ self._forward_lock.acquire() ++ self._forward_paused = True + + def wake(self): -+ """Step-boundary signal (runner thread): run one manager pass as -+ soon as the rate limiter allows. Lock-free and coalescing — a burst -+ of calls between two passes collapses into one.""" ++ """Finish the forward window and signal one manager pass.""" ++ if self._forward_paused: ++ self._forward_paused = False ++ self._forward_lock.release() + self._wake_driven = True + self._wake.set() + @@ -3896,6 +5553,10 @@ index 000000000..f7905fd40 + # token counts for the hit-rate below — read under the snap lock + # so a concurrent snapshot can't swap the values underneath. + cnt_raw = self._seen_host[seen[:, 0], seen[:, 1]] ++ # Keep eviction protection bound to this exact snapshot. Other ++ # snapshot users may overwrite the shared host buffer after the ++ # lock is released. ++ seen_set = {tuple(pair) for pair in seen.tolist()} + if seen.numel() == 0: + return + if _CAPTURE and not self._cap_done: @@ -3953,7 +5614,7 @@ index 000000000..f7905fd40 + for li, ei in cand: + if promoted >= _PROMOTE_PER_TICK: + break -+ slot = self._take_slot() ++ slot = self._take_slot(seen_set) + if slot is None: + break + self._promote(li, ei, slot) @@ -4114,8 +5775,13 @@ index 000000000..f7905fd40 + logger.warning("moe_w2 pool-heat preload failed: %s", e) + return 0 + -+ def _take_slots_batch(self, k: int, emergency: bool = False, -+ min_cold: int = 2) -> list[int]: ++ def _take_slots_batch( ++ self, ++ k: int, ++ emergency: bool = False, ++ min_cold: int = 2, ++ seen_set: set[tuple[int, int]] | None = None, ++ ) -> list[int]: + """Take up to k slots (lock held by caller): free list first, then ONE + vectorized eviction pass over all slots. Replaces the old per-slot + python scan per promotion — O(n_slots) per TAKEN slot — which at GLM @@ -4129,9 +5795,9 @@ index 000000000..f7905fd40 + + Eviction policy is unchanged: least-valuable slot by _POLICY key + (need / freq / lru), restricted to slots whose owner is not active in -+ the current seen window (read directly from the _seen_host snapshot — -+ the call sites always passed a set built from exactly that) and cold -+ >= 2 ticks, so in-flight graph reads never hit a rewritten slot. ++ the caller's immutable seen snapshot (or _seen_host for best-effort ++ callers without one) and cold >= 2 ticks, so in-flight graph reads ++ never hit a rewritten slot. + Victims are unmapped here (graphs stop dispatching w4 before bytes + change); the caller reserves _owner for each returned slot.""" + out: list[int] = [] @@ -4148,14 +5814,22 @@ index 000000000..f7905fd40 + elif self._policy == "freq": + key = self._freq[lic, eic].double() + else: -+ # double, not clone: the tensorized owner-tick is int64 and the -+ # inf sentinel below cannot be represented there (lru is not a -+ # production policy for these tiers; surfaced by unit tests) ++ # LRU ticks are int64, but masked candidates need an infinity ++ # sentinel below. Promote to float so a saturated LRU pool can ++ # exclude in-flight slots instead of raising on the assignment. + key = tk.double() + # Hard exclusions: free markers, owners active in the current seen + # window (their slots may be read by this step's graph/replay), and + # step-pinned slots (touched by any pass of the current step). -+ blocked = (li < 0) | self._seen_host[lic, eic].to(torch.bool) ++ if seen_set is None: ++ active_seen = self._seen_host[lic, eic].to(torch.bool) ++ else: ++ seen_mask = torch.zeros_like(self._seen_host, dtype=torch.bool) ++ if seen_set: ++ seen_li, seen_ei = zip(*seen_set) ++ seen_mask[list(seen_li), list(seen_ei)] = True ++ active_seen = seen_mask[lic, eic] ++ blocked = (li < 0) | active_seen + if self._step_pins: + blocked[list(self._step_pins)] = True + # Residency coupling (split-FP4 over the base cache): never evict a @@ -4212,7 +5886,7 @@ index 000000000..f7905fd40 + + def _take_slot(self, seen_set=None): + """Single-slot wrapper (kept for the unit tests / external callers).""" -+ slots = self._take_slots_batch(1) ++ slots = self._take_slots_batch(1, seen_set=seen_set) + return slots[0] if slots else None + + def _promote(self, li, ei, slot): @@ -4235,16 +5909,22 @@ index 000000000..f7905fd40 + + def step_begin(self) -> None: + """Open a new step's pin scope (runner: before the first miss read; -+ prefill: at each ensure_resident). Slots touched after this call are ++ prefill: before the first ensure_resident). Slots touched after this call are + pinned against eviction until the next step_begin — the fixed-point -+ replay's passes must never cannibalize each other's fetches. -+ -+ Doubles as the manager's step-boundary signal: every live tier -+ (base AND fp4) gets one pass per step instead of a free-running -+ poll.""" ++ replay's passes must never cannibalize each other's fetches. The worker ++ signals the manager only after every replay has completed.""" + with self._lock: + self._step_pins.clear() -+ wake_all() ++ ++ def routing_step_begin(self) -> None: ++ """Discard routing marks left by warmup or the previous target step.""" ++ main = torch.cuda.current_stream(self.dev) ++ with self._snap_lock: ++ self.seen.zero_() ++ # The manager snapshots on this side stream without otherwise ++ # waiting for the runner. Order future snapshots after the zero. ++ with torch.cuda.stream(self._stream): ++ self._stream.wait_stream(main) + + # ---- draft-affinity prefetch (VLLM_MOE_W2_PREFETCH=1) ------------------ + @@ -4395,6 +6075,9 @@ index 000000000..f7905fd40 + seen = self._seen_host.nonzero() + if seen.numel() == 0: + return 0 ++ # Eviction must use the immutable routing snapshot captured above, ++ # not the shared host buffer that another caller can overwrite. ++ seen_set = {tuple(pair) for pair in seen.tolist()} + # Bound the working set to RECENT steps: `seen` otherwise accumulates + # up to 4 manager ticks of routings (the manager zeroes it lazily), so + # on deep/wide models a single fire tried to force-promote every @@ -4453,7 +6136,9 @@ index 000000000..f7905fd40 + # emergency=True: this is the synchronous runner-thread path with + # no forward in flight — leaving a miss UNRESTORED is worse than + # evicting a warm-but-idle slot (see _take_slots_batch). -+ slots = self._take_slots_batch(len(cand), emergency=True) ++ slots = self._take_slots_batch( ++ len(cand), emergency=True, seen_set=seen_set ++ ) + plan = [((li, ei), slot) for (li, ei), slot in zip(cand, slots)] + if not plan: + return 0 @@ -4504,17 +6189,25 @@ index 000000000..f7905fd40 + if layer_key not in self._store: + return 0 + ids = ids.unique().long() ++ ids_cpu = ids.cpu() ++ layer_seen_set = {(layer_key, int(e)) for e in ids_cpu} + mark_seen(self.seen[layer_key], ids.to(self.dev)) -+ # snapshot seen (protects eviction) exactly like force_promote ++ # Wait for prior-layer work before selecting eviction victims. Keep ++ # the device `seen` aggregate intact for telemetry, but protect only ++ # this layer's experts during the layer-at-a-time eager prefill scan; ++ # retaining prior layers in the eviction mask union-saturates the pool. + main = torch.cuda.current_stream(self.dev) + with self._snap_lock: + with torch.cuda.stream(self._stream): + self._stream.wait_stream(main) -+ self._seen_host.copy_(self.seen, non_blocking=True) + ev = torch.cuda.Event() + ev.record(self._stream) + ev.synchronize() + with self._lock: ++ # Each eager layer starts after the prior layer drained. Scope ++ # pins to this layer so the manager cannot recycle a current ++ # hit or freshly loaded slot before its GEMMs consume it. ++ self._step_pins.clear() + slots = self._mirror[layer_key].long()[ids.cpu()] + hit = slots >= 0 + if bool(hit.any()): @@ -4522,13 +6215,16 @@ index 000000000..f7905fd40 + # force_promote: protect them from a racing manager + # eviction while this layer's eager GEMMs read them) + self._owner_tick[slots[hit]] = self._tick ++ self._step_pins.update(int(s) for s in slots[hit].tolist()) + cand = [(layer_key, int(e)) for e in ids.cpu()[~hit].tolist()] + if not cand: + return 0 + # emergency=True: prefill MUST have its whole layer resident — + # an unfetched expert here zeroes contributions for EVERY token + # of the chunk (the existing pool-too-small warning path). -+ slots = self._take_slots_batch(len(cand), emergency=True) ++ slots = self._take_slots_batch( ++ len(cand), emergency=True, seen_set=layer_seen_set ++ ) + plan = [((li, ei), slot) for (li, ei), slot in zip(cand, slots)] + if not plan: + return 0 @@ -4538,6 +6234,7 @@ index 000000000..f7905fd40 + rows = self._store.rows_for([p for p, _ in plan], scan=True) + for ((li, ei), slot), row in zip(plan, rows): + self._own(slot, li, ei) ++ self._step_pins.add(slot) + with torch.cuda.stream(self._stream): + self.pool[slot].copy_(row, non_blocking=True) + with torch.cuda.stream(self._stream): @@ -4836,14 +6533,34 @@ index 000000000..f7905fd40 + self._cap_frames = [] + + -+def mark_seen(seen_row, ids): ++def mark_seen(seen_row, ids, token_valid=None): + """Record routed experts into a layer's seen row from the forward. Token + COUNTS when observability is on (token-weighted hit-rate / capture), else a -+ cheap binary flag. `ids` = flattened topk_ids (int64). Graph-capture-safe.""" ++ cheap binary flag. ``token_valid`` masks padded token rows without changing ++ the captured shape.""" ++ if token_valid is not None: ++ if ids.ndim != 2 or token_valid.ndim != 1: ++ raise RuntimeError( ++ "masked moe_w2 seen recording requires [T, K] ids and [T] validity" ++ ) ++ if ids.shape[0] != token_valid.shape[0]: ++ raise RuntimeError( ++ "moe_w2 token-validity length does not match routed token rows" ++ ) ++ updates = token_valid[:, None].expand_as(ids).reshape(-1).to(seen_row.dtype) ++ ids = ids.reshape(-1) ++ else: ++ updates = None + if _COUNT: -+ seen_row.index_add_(0, ids, torch.ones_like(ids, dtype=seen_row.dtype)) ++ if updates is None: ++ updates = torch.ones_like(ids, dtype=seen_row.dtype) ++ seen_row.index_add_(0, ids, updates) + else: -+ seen_row.index_fill_(0, ids, 1) ++ if updates is None: ++ seen_row.index_fill_(0, ids, 1) ++ else: ++ seen_row.scatter_reduce_(0, ids, updates, ++ reduce="amax", include_self=True) + + +_TIER: DeltaTier | None = None @@ -4869,6 +6586,26 @@ index 000000000..f7905fd40 +_BASE_GB = float(os.getenv("VLLM_MOE_W2_BASE_CACHE_GB", "0")) +_BASE_TIER: DeltaTier | None = None + ++ ++def begin_target_step() -> None: ++ """Exclude manager rewrites, then clear stale marks before the target.""" ++ for tier in (_BASE_TIER, _TIER): ++ if tier is not None: ++ tier.pause_for_forward() ++ tier.routing_step_begin() ++ ++ ++def begin_replay_step() -> None: ++ """Open both tiers' pin scopes after the target forward, before replay.""" ++ for tier in (_BASE_TIER, _TIER): ++ if tier is not None: ++ tier.step_begin() ++ ++ ++def finish_forward_step() -> None: ++ """Open the between-forward manager window and signal one pass.""" ++ wake_all() ++ +# Miss tolerance: a decode step with <= TOL missing routed (layer, expert) +# pairs keeps its logits (the missing pairs contributed zero) instead of +# replaying the graph. Rationale: at 99.9% token hit-rate a 600-pair step @@ -5007,6 +6744,25 @@ index 000000000..f7905fd40 + return _BASE_GB > 0 + + ++def _arm_split_coupling() -> None: ++ """Couple split-FP4 refinement residency to the base tier. ++ ++ Fresh requant builds create the FP4 tier before ``_finish_layer`` creates ++ the base tier, while pack-hit restores create them in the opposite order. ++ Invoke this after either singleton lookup so coupling cannot depend on ++ which path constructed its tier first. ++ """ ++ if not split_enabled() or _BASE_TIER is None or _TIER is None: ++ return ++ if _BASE_TIER._coupled_fp4 is _TIER: ++ return ++ _BASE_TIER._coupled_fp4 = _TIER ++ logger.info( ++ "moe_w2 delta: split-FP4 residency coupling armed " ++ "(base evictions exclude FP4-mapped experts)" ++ ) ++ ++ +def spec_suppressed() -> bool: + """Spec-guard latch (VLLM_MOE_W2_SPEC_GUARD): True while the base pool + is too cold for speculation to pay — the runner then skips scheduling @@ -5016,10 +6772,7 @@ index 000000000..f7905fd40 + + +def wake_all() -> None: -+ """Step-boundary broadcast: nudge every live tier's manager (base + -+ fp4/delta) to run one pass. Called once per decode step from -+ step_begin (base-cache configs) and the gate decision (delta-only -+ configs); lock-free, coalescing, safe before tiers exist.""" ++ """Post-forward broadcast: release and nudge each live tier manager.""" + t = _BASE_TIER + if t is not None: + t.wake() @@ -5073,6 +6826,7 @@ index 000000000..f7905fd40 + _BASE_TIER.n_slots, _BASE_TIER.slot_bytes / 2**20, + _BASE_TIER.n_slots * _BASE_TIER.slot_bytes / 2**30, + cov, n_layers * n_experts) ++ _arm_split_coupling() + return _BASE_TIER + + @@ -5109,12 +6863,6 @@ index 000000000..f7905fd40 + w2_bytes=W2_BYTES if w2_bytes is None else w2_bytes, + policy=policy, tag=fp4_tag if base_enabled() else "delta", + host_pinned=not base_enabled()) -+ if base_enabled() and split_enabled() and _BASE_TIER is not None: -+ # residency coupling: the base tier must not evict slots the -+ # FP4 tier's refinement rows are mapped against -+ _BASE_TIER._coupled_fp4 = _TIER -+ logger.info("moe_w2 delta: split-FP4 residency coupling armed " -+ "(base evictions exclude FP4-mapped experts)") + # Start the background manager as soon as the tier exists. It idles until + # experts are actually routed (seen empty -> early return) and only + # promotes layers whose host planes are already staged, so an early start @@ -5123,13 +6871,14 @@ index 000000000..f7905fd40 + # "start on the last layer built" trigger never ran and the tier sat + # inactive (pool allocated but no promotions). + _TIER.start() ++ _arm_split_coupling() + return _TIER diff --git a/vllm/model_executor/layers/quantization/utils/moe_w2_gate.py b/vllm/model_executor/layers/quantization/utils/moe_w2_gate.py new file mode 100644 -index 000000000..fcf18c484 +index 0000000..8891cf5 --- /dev/null +++ b/vllm/model_executor/layers/quantization/utils/moe_w2_gate.py -@@ -0,0 +1,188 @@ +@@ -0,0 +1,182 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Confidence-gated FP4 re-forward for the 2-bit MoE path (directive 2 / Step B). @@ -5262,12 +7011,6 @@ index 000000000..fcf18c484 + """ + global _n_steps, _n_fired + _n_steps += 1 -+ # Step-boundary signal for the tier managers: this is the one gate call -+ # guaranteed once per decode step, so delta-only configs (no base tier, -+ # hence no runner step_begin) still get event-driven manager passes -+ # instead of the legacy free-running poll. -+ from vllm.model_executor.layers.quantization.utils import moe_w2_delta -+ moe_w2_delta.wake_all() + if logits is None or logits.numel() == 0: + return False + if logits.dim() == 1: @@ -5320,10 +7063,10 @@ index 000000000..fcf18c484 + fire_rate=(_n_fired / _n_steps if _n_steps else 0.0)) diff --git a/vllm/model_executor/layers/quantization/utils/moe_w2_looka.py b/vllm/model_executor/layers/quantization/utils/moe_w2_looka.py new file mode 100644 -index 000000000..f5de680cf +index 0000000..6e1c3ea --- /dev/null +++ b/vllm/model_executor/layers/quantization/utils/moe_w2_looka.py -@@ -0,0 +1,256 @@ +@@ -0,0 +1,260 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Router-lookahead measurement (LOOKA) + prefetch (PILOT) for the moe_w2 @@ -5470,7 +7213,8 @@ index 000000000..f5de680cf + + +def record(layer_key: int, x: torch.Tensor, topk_ids: torch.Tensor, -+ route_log: torch.Tensor | None) -> None: ++ route_log: torch.Tensor | None, ++ token_valid: torch.Tensor) -> None: + """In-graph hook, called from the moe_w2 decode forward of every BASE + layer BEFORE the route_log is overwritten. Pure tensor ops on persistent + buffers (capture-safe); `layer_key` is a python int, so the branches @@ -5482,13 +7226,14 @@ index 000000000..f5de680cf + return + k_true = topk_ids.shape[1] + true = topk_ids[:, :k_true].int() ++ valid_routes = token_valid[:, None] + # [0] previous decode step, same layer (the affinity-class baseline). + # route_log still holds LAST step's ids for this layer here. + if route_log is not None: + prev = route_log[layer_key, :T, :k_true] + m0 = (true.unsqueeze(2) == prev.unsqueeze(1)).any(dim=2) -+ _hit[0] += m0.sum() -+ _tot[0] += true.numel() ++ _hit[0] += (m0 & valid_routes).sum() ++ _tot[0] += token_valid.sum() * k_true + # [1] router-lookahead: the prediction targeting THIS layer, written + # into _pred_buf by the previous layer's record() call (same step; the + # keys run in order inside one forward). Exists iff this layer's gate @@ -5496,8 +7241,8 @@ index 000000000..f5de680cf + if layer_key > 0 and layer_key in _gate_w: + pred = _pred_buf[:T] + m1 = (true.unsqueeze(2) == pred.unsqueeze(1)).any(dim=2) -+ _hit[1] += m1.sum() -+ _tot[1] += true.numel() ++ _hit[1] += (m1 & valid_routes).sum() ++ _tot[1] += token_valid.sum() * k_true + # predict layer_key+1's routing from THIS layer's expert input + w = _gate_w.get(layer_key + 1) + if w is not None: @@ -5507,6 +7252,8 @@ index 000000000..f5de680cf + if b is not None: + scores = scores + b + pred_ids = torch.topk(scores, _PILOT_K, dim=-1).indices.int() ++ pred_ids = torch.where(token_valid[:, None], pred_ids, ++ torch.full_like(pred_ids, -1)) + _pred_buf[:T].copy_(pred_ids) + if _pilot_log is not None: + _pilot_log[layer_key + 1, :T].copy_(pred_ids) @@ -5582,7 +7329,7 @@ index 000000000..f5de680cf + f"(top-{_PILOT_K}, n={tot[1]})") diff --git a/vllm/model_executor/layers/quantization/utils/moe_w2_planes.py b/vllm/model_executor/layers/quantization/utils/moe_w2_planes.py new file mode 100644 -index 000000000..634454b8d +index 0000000..634454b --- /dev/null +++ b/vllm/model_executor/layers/quantization/utils/moe_w2_planes.py @@ -0,0 +1,286 @@ @@ -5874,10 +7621,10 @@ index 000000000..634454b8d + return vals * s diff --git a/vllm/model_executor/layers/quantization/utils/moe_w2_planes_cache.py b/vllm/model_executor/layers/quantization/utils/moe_w2_planes_cache.py new file mode 100644 -index 000000000..752f88ded +index 0000000..9bffa48 --- /dev/null +++ b/vllm/model_executor/layers/quantization/utils/moe_w2_planes_cache.py -@@ -0,0 +1,226 @@ +@@ -0,0 +1,259 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Disk cache for built 2-bit expert planes (+ optional FP4 delta planes). @@ -5912,6 +7659,7 @@ index 000000000..752f88ded +import queue +import re +import threading ++from contextlib import suppress + +import numpy as np +import torch @@ -5948,15 +7696,33 @@ index 000000000..752f88ded + + +def _ckpt_id() -> str: -+ """Identity of the checkpoint the planes derive from: model path + -+ sha1 of the safetensors index (covers shard layout and tensor set).""" ++ """Cheap identity of the checkpoint the planes derive from. ++ ++ Hash the canonical model path and index contents, then the name, size and ++ nanosecond mtime of every referenced shard. This catches an in-place ++ checkpoint replacement without hashing a DS4-sized checkpoint at boot. ++ """ + from vllm.config import get_current_vllm_config + model = get_current_vllm_config().model_config.model + h = hashlib.sha1(model.encode()) + idx = os.path.join(model, "model.safetensors.index.json") ++ shards: set[str] = set() + if os.path.exists(idx): + with open(idx, "rb") as f: -+ h.update(f.read()) ++ raw = f.read() ++ h.update(raw) ++ with suppress(AttributeError, TypeError, ValueError, ++ json.JSONDecodeError): ++ shards.update(json.loads(raw).get("weight_map", {}).values()) ++ if not shards and os.path.isdir(model): ++ shards.update(name for name in os.listdir(model) ++ if name.endswith(".safetensors")) ++ for name in sorted(shards): ++ path = os.path.join(model, name) ++ stat = os.stat(path) ++ h.update(name.encode()) ++ h.update(str(stat.st_size).encode()) ++ h.update(str(stat.st_mtime_ns).encode()) + return h.hexdigest() + + @@ -5975,6 +7741,11 @@ index 000000000..752f88ded + ) + + ++def cache_identity() -> dict: ++ """Public deterministic identity shared by every derived W2 cache.""" ++ return _meta() ++ ++ +def _rank_dir() -> str: + world, rank = _tp_ids() + return os.path.join(os.environ["VLLM_MOE_W2_PLANES_CACHE"], @@ -6007,7 +7778,11 @@ index 000000000..752f88ded + try: + d = _rank_dir() + mp = os.path.join(d, "meta.json") -+ if not os.path.exists(mp) or json.load(open(mp)) != _meta(): ++ if not os.path.exists(mp): ++ return False ++ with open(mp) as f: ++ meta = json.load(f) ++ if meta != _meta(): + return False + for part, nbytes in sizes.items(): + p = os.path.join(d, f"layer{layer_idx}.{part}.bin") @@ -6028,7 +7803,9 @@ index 000000000..752f88ded + mp = os.path.join(d, "meta.json") + if not os.path.exists(mp): + return None -+ if json.load(open(mp)) != _meta(): ++ with open(mp) as f: ++ meta = json.load(f) ++ if meta != _meta(): + _mark_broken(f"meta mismatch in {d} (stale cache?) — rebuilding") + return None + out = {} @@ -6083,10 +7860,13 @@ index 000000000..752f88ded + os.makedirs(d, exist_ok=True) + if not _meta_written: + mp = os.path.join(d, "meta.json") -+ if os.path.exists(mp) and json.load(open(mp)) != _meta(): -+ # stale cache from another checkpoint/config: start over -+ for f in os.listdir(d): -+ os.unlink(os.path.join(d, f)) ++ if os.path.exists(mp): ++ with open(mp) as f: ++ old_meta = json.load(f) ++ if old_meta != _meta(): ++ # stale cache from another checkpoint/config: start over ++ for name in os.listdir(d): ++ os.unlink(os.path.join(d, name)) + with open(mp + ".tmp", "w") as f: + json.dump(_meta(), f) + os.replace(mp + ".tmp", mp) @@ -6106,67 +7886,628 @@ index 000000000..752f88ded + _mark_broken(f"store failed: {e}") diff --git a/vllm/model_executor/layers/quantization/utils/moe_w2_store.py b/vllm/model_executor/layers/quantization/utils/moe_w2_store.py new file mode 100644 -index 000000000..c0b937425 +index 0000000..a64cb58 --- /dev/null +++ b/vllm/model_executor/layers/quantization/utils/moe_w2_store.py -@@ -0,0 +1,656 @@ +@@ -0,0 +1,1405 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Host-side expert stores for the 2-bit MoE tiers (moe_w2_delta.DeltaTier). + -+Three backends behind one tiny interface: ++Three backends behind one tiny interface: ++ ++ - PinnedHostStore: today's behaviour — per-layer [E, slot_bytes] host ++ tensors (pinned or pageable), rows handed to cudaMemcpyAsync directly. ++ Default; byte-identical to the pre-store code path. ++ - MmapPackStore (VLLM_MOE_W2_STORE_DIR=