[KV Connector][Mooncake] Add store group semantics (#44956)

Signed-off-by: Schatten <czhengt@qq.com> Co-authored-by: OpenAI Codex <codex@openai.com> Co-authored-by: Yifan Qiao <yifanqiao@inferact.ai>

Schatten committed Aug 5, 2026 at 16:25 UTC fd859d2e0689ce254bfb0e67483fba76405ca9df
2 files changed +531 -3
tests/v1/kv_connector/unit/test_mooncake_store_worker.py
+463 -2
@@ -76,6 +76,8 @@ def _make_store_sending_thread(
76 tp_rank: int = 0,
77 put_step: int = 1,
78 replicate_config: object | None = None,
79 + enable_group_semantics: bool = False,
80 + supports_group_ids: bool = False,
81 ) -> mooncake_store_worker.KVCacheStoreSendingThread:
82 if coord is None:
83 coord = _default_send_coord()
@@ -94,6 +96,8 @@ def _make_store_sending_thread(
96 kv_role="kv_producer",
97 ready_event=threading.Event(),
98 replicate_config=replicate_config,
99 + enable_group_semantics=enable_group_semantics,
100 + supports_group_ids=supports_group_ids,
101 )
102 thread.request_queue.task_done = MagicMock()
103 return thread
@@ -162,6 +166,16 @@ def _make_store_req(req_id: str, block_hashes: list[bytes]) -> ReqMeta:
166 )
167
168
169 +def _make_multi_group_store_req(req_id: str, block_hashes: list[bytes]) -> ReqMeta:
170 + return ReqMeta(
171 + req_id=req_id,
172 + token_len_chunk=32,
173 + block_ids=([0, 1], [2, 3]),
174 + block_hashes=block_hashes,
175 + can_save=True,
176 + )
177 +
178 +
179 _DISK_OFFLOAD_SINGLE_KEY_BYTES = worker._estimate_disk_offload_staging_bytes([256])
180 _DISK_OFFLOAD_USABLE_BUDGET_RATIO = 0.9
181 _DISK_OFFLOAD_BUDGET_FOR_THREE_KEYS = 4 * _DISK_OFFLOAD_SINGLE_KEY_BYTES
@@ -276,6 +290,7 @@ def _patch_worker_runtime(
290 monkeypatch.setattr(worker, "get_pcp_group", lambda: single_rank_group)
291 monkeypatch.setattr(worker, "get_dcp_group", lambda: dcp_group)
292 monkeypatch.setattr(worker, "get_ip", lambda: local_ip)
293 + monkeypatch.setattr(worker, "LookupKeyServer", MagicMock())
294
295
296 def test_pool_key_to_string_without_prefix_is_unchanged():
@@ -599,7 +614,13 @@ def test_store_sending_thread_retries_skipped_range_after_pressure():
614 assert store.batch_put_from_multi_buffers.call_args.args[0] == keys
615
616
602 -def _make_partial_tail_send_thread(store):
617 +def _make_partial_tail_send_thread(
618 + store,
619 + *,
620 + replicate_config=None,
621 + enable_group_semantics=False,
622 + supports_group_ids=False,
623 +):
624 coord = SimpleNamespace(
625 enable_partial_hash_hits=True,
626 hash_block_size=4,
@@ -616,6 +637,9 @@ def _make_partial_tail_send_thread(store):
637 store,
638 coord=coord,
639 token_databases=[db],
640 + replicate_config=replicate_config,
641 + enable_group_semantics=enable_group_semantics,
642 + supports_group_ids=supports_group_ids,
643 )
644
645
@@ -648,6 +672,32 @@ def test_partial_tail_offload_skips_null_source_blocks():
672 assert addrs == [[0x1000 + 2 * 256], [0x1000 + 3 * 256]]
673
674
675 +def test_partial_tail_offload_replaces_stale_group_ids_after_filtering():
676 + store = MagicMock()
677 + store.batch_is_exist.return_value = [1, 0, 0]
678 + store.batch_put_from_multi_buffers.return_value = [256, 256]
679 + replicate_config = SimpleNamespace(group_ids=["stale"])
680 + thread = _make_partial_tail_send_thread(
681 + store,
682 + replicate_config=replicate_config,
683 + enable_group_semantics=True,
684 + supports_group_ids=True,
685 + )
686 +
687 + assert thread._maybe_offload_partial_tail(_make_partial_tail_req([1, 2, 3]))
688 +
689 + keys, _addrs, _sizes, config = store.batch_put_from_multi_buffers.call_args.args
690 + assert keys == [
691 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6131",
692 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6132",
693 + ]
694 + assert config is replicate_config
695 + assert config.group_ids == [
696 + "vllm-mooncake-store:test-model@6131",
697 + "vllm-mooncake-store:test-model@6132",
698 + ]
699 +
700 +
701 def test_partial_tail_offload_honors_active_pressure_gate():
702 store = MagicMock()
703 thread = _make_partial_tail_send_thread(store)
@@ -969,6 +1019,291 @@ def test_store_sending_thread_passes_default_replicate_config_when_no_preferred_
1019 assert call_args[3] is replicate_config
1020
1021
1022 +def test_group_id_detection_uses_class_attribute_without_instantiating():
1023 + class ReplicateConfigWithClassGroupIdsAndRequiredInit:
1024 + group_ids = None
1025 +
1026 + def __init__(self, required):
1027 + self.group_ids = required
1028 +
1029 + assert worker._replicate_config_supports_group_ids(
1030 + ReplicateConfigWithClassGroupIdsAndRequiredInit,
1031 + object(),
1032 + )
1033 +
1034 +
1035 +def test_group_id_detection_uses_instance_attribute_when_class_lacks_attribute():
1036 + class ReplicateConfigWithInstanceGroupIds:
1037 + def __init__(self) -> None:
1038 + self.group_ids = None
1039 +
1040 + replicate_config = ReplicateConfigWithInstanceGroupIds()
1041 +
1042 + assert worker._replicate_config_supports_group_ids(
1043 + ReplicateConfigWithInstanceGroupIds,
1044 + replicate_config,
1045 + )
1046 +
1047 +
1048 +def test_group_id_detection_rejects_old_replicate_config():
1049 + class ReplicateConfigWithoutGroupIds:
1050 + __slots__ = ()
1051 +
1052 + assert not worker._replicate_config_supports_group_ids(
1053 + ReplicateConfigWithoutGroupIds,
1054 + ReplicateConfigWithoutGroupIds(),
1055 + )
1056 +
1057 +
1058 +def test_store_sending_thread_sets_group_ids_when_enabled():
1059 + store = MagicMock()
1060 + store.batch_is_exist.side_effect = lambda keys: [0] * len(keys)
1061 + store.batch_put_from_multi_buffers.return_value = [256, 256]
1062 + replicate_config = SimpleNamespace(group_ids=None)
1063 + thread = _make_store_sending_thread(
1064 + store,
1065 + replicate_config=replicate_config,
1066 + enable_group_semantics=True,
1067 + supports_group_ids=True,
1068 + )
1069 +
1070 + thread.add_stored_request("req-a")
1071 + thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
1072 +
1073 + assert store.batch_put_from_multi_buffers.call_count == 1
1074 + keys, _addrs, _sizes, config = store.batch_put_from_multi_buffers.call_args.args
1075 + assert config is replicate_config
1076 + assert config.group_ids == [
1077 + "vllm-mooncake-store:test-model@6130",
1078 + "vllm-mooncake-store:test-model@6131",
1079 + ]
1080 + assert len(config.group_ids) == len(keys)
1081 +
1082 +
1083 +def test_store_sending_thread_leaves_group_ids_unchanged_when_flag_disabled():
1084 + store = MagicMock()
1085 + store.batch_is_exist.side_effect = lambda keys: [0] * len(keys)
1086 + store.batch_put_from_multi_buffers.return_value = [256, 256]
1087 + replicate_config = SimpleNamespace(group_ids=["existing"])
1088 + thread = _make_store_sending_thread(
1089 + store,
1090 + replicate_config=replicate_config,
1091 + enable_group_semantics=False,
1092 + supports_group_ids=True,
1093 + )
1094 +
1095 + thread.add_stored_request("req-a")
1096 + thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
1097 +
1098 + assert store.batch_put_from_multi_buffers.call_count == 1
1099 + assert store.batch_put_from_multi_buffers.call_args.args[3] is replicate_config
1100 + assert replicate_config.group_ids == ["existing"]
1101 +
1102 +
1103 +def test_store_sending_thread_leaves_group_ids_unchanged_when_unsupported():
1104 + store = MagicMock()
1105 + store.batch_is_exist.side_effect = lambda keys: [0] * len(keys)
1106 + store.batch_put_from_multi_buffers.return_value = [256, 256]
1107 + replicate_config = SimpleNamespace(group_ids=["existing"])
1108 + thread = _make_store_sending_thread(
1109 + store,
1110 + replicate_config=replicate_config,
1111 + enable_group_semantics=True,
1112 + supports_group_ids=False,
1113 + )
1114 +
1115 + thread.add_stored_request("req-a")
1116 + thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
1117 +
1118 + assert store.batch_put_from_multi_buffers.call_count == 1
1119 + assert store.batch_put_from_multi_buffers.call_args.args[3] is replicate_config
1120 + assert replicate_config.group_ids == ["existing"]
1121 +
1122 +
1123 +def test_mooncake_group_id_uses_logical_chunk_and_excludes_physical_ranks():
1124 + group_id = worker._make_mooncake_group_id(
1125 + KeyMetadata(
1126 + "test-model",
1127 + tp_rank=3,
1128 + pcp_rank=5,
1129 + dcp_rank=7,
1130 + pp_rank=11,
1131 + group_id=13,
1132 + ),
1133 + "abcdef",
1134 + )
1135 +
1136 + assert group_id == "vllm-mooncake-store:test-model@abcdef"
1137 + assert "tp_rank" not in group_id
1138 + assert "pp_rank" not in group_id
1139 + assert "pcp" not in group_id
1140 + assert "dcp" not in group_id
1141 + assert "group:" not in group_id
1142 +
1143 +
1144 +def test_mooncake_group_id_uses_cache_prefix_namespace():
1145 + metadata_a = KeyMetadata("test-model", 0, 0, 0, 0, cache_prefix="deployment-a")
1146 + metadata_b = KeyMetadata("test-model", 0, 0, 0, 0, cache_prefix="deployment-b")
1147 +
1148 + group_id_a = worker._make_mooncake_group_id(metadata_a, "abcdef")
1149 + group_id_b = worker._make_mooncake_group_id(metadata_b, "abcdef")
1150 +
1151 + assert group_id_a == "vllm-mooncake-store:deployment-a@test-model@abcdef"
1152 + assert group_id_b == "vllm-mooncake-store:deployment-b@test-model@abcdef"
1153 + assert group_id_a != group_id_b
1154 +
1155 +
1156 +def test_store_sending_thread_group_id_excludes_physical_sharding():
1157 + store = MagicMock()
1158 + store.batch_is_exist.return_value = [0, 0]
1159 + store.batch_put_from_multi_buffers.return_value = [256, 256]
1160 + replicate_config = SimpleNamespace(group_ids=None)
1161 + db = ChunkedTokenDatabase(
1162 + KeyMetadata(
1163 + "test-model",
1164 + tp_rank=2,
1165 + pcp_rank=0,
1166 + dcp_rank=0,
1167 + pp_rank=0,
1168 + group_id=0,
1169 + ),
1170 + block_size=16,
1171 + )
1172 + db.set_kv_caches_base_addr([0x1000])
1173 + db.set_block_len([256])
1174 + thread = _make_store_sending_thread(
1175 + store,
1176 + token_databases=[db],
1177 + tp_rank=2,
1178 + replicate_config=replicate_config,
1179 + enable_group_semantics=True,
1180 + supports_group_ids=True,
1181 + )
1182 +
1183 + thread.add_stored_request("req-a")
1184 + thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
1185 +
1186 + keys, _addrs, _sizes, config = store.batch_put_from_multi_buffers.call_args.args
1187 + assert keys == [
1188 + "test-model@tp_rank:2@pcp0@dcp0@pp_rank:0@group:0@6130",
1189 + "test-model@tp_rank:2@pcp0@dcp0@pp_rank:0@group:0@6131",
1190 + ]
1191 + assert config.group_ids == [
1192 + "vllm-mooncake-store:test-model@6130",
1193 + "vllm-mooncake-store:test-model@6131",
1194 + ]
1195 +
1196 +
1197 +def test_store_sending_thread_multiple_segments_share_logical_group_id():
1198 + store = MagicMock()
1199 + store.batch_is_exist.return_value = [0, 0]
1200 + store.batch_put_from_multi_buffers.return_value = [512, 512]
1201 + replicate_config = SimpleNamespace(group_ids=None)
1202 + db = ChunkedTokenDatabase(KeyMetadata("test-model", 0, 0, 0, 0), block_size=16)
1203 + db.set_kv_caches_base_addr([0x1000, 0x2000])
1204 + db.set_block_len([256, 256])
1205 + thread = _make_store_sending_thread(
1206 + store,
1207 + token_databases=[db],
1208 + replicate_config=replicate_config,
1209 + enable_group_semantics=True,
1210 + supports_group_ids=True,
1211 + )
1212 +
1213 + thread.add_stored_request("req-a")
1214 + thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
1215 +
1216 + keys, addrs, sizes, config = store.batch_put_from_multi_buffers.call_args.args
1217 + assert keys == [
1218 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6130",
1219 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6131",
1220 + ]
1221 + assert addrs == [[0x1000, 0x2000], [0x1100, 0x2100]]
1222 + assert sizes == [[256, 256], [256, 256]]
1223 + assert config.group_ids == [
1224 + "vllm-mooncake-store:test-model@6130",
1225 + "vllm-mooncake-store:test-model@6131",
1226 + ]
1227 +
1228 +
1229 +def test_store_sending_thread_group_ids_share_across_kv_cache_groups():
1230 + from vllm.v1.kv_cache_interface import FullAttentionSpec, KVCacheGroupSpec
1231 +
1232 + store = MagicMock()
1233 + store.batch_is_exist.return_value = [0, 0, 0, 0]
1234 + store.batch_put_from_multi_buffers.return_value = [256, 256, 256, 256]
1235 + replicate_config = SimpleNamespace(group_ids=None)
1236 + spec = FullAttentionSpec(block_size=16, num_kv_heads=8, head_size=64, dtype=None)
1237 + coord = mooncake_store_worker.MooncakeStoreCoordinator(
1238 + [
1239 + KVCacheGroupSpec(["layer0"], spec),
1240 + KVCacheGroupSpec(["layer1"], spec),
1241 + ],
1242 + scheduler_block_size=16,
1243 + hash_block_size=16,
1244 + )
1245 + token_databases = []
1246 + for group_id, base_addr in enumerate([0x1000, 0x3000]):
1247 + db = ChunkedTokenDatabase(
1248 + KeyMetadata("test-model", 0, 0, 0, 0, group_id=group_id),
1249 + block_size=16,
1250 + )
1251 + db.set_kv_caches_base_addr([base_addr])
1252 + db.set_block_len([256])
1253 + token_databases.append(db)
1254 + thread = _make_store_sending_thread(
1255 + store,
1256 + coord=coord,
1257 + token_databases=token_databases,
1258 + replicate_config=replicate_config,
1259 + enable_group_semantics=True,
1260 + supports_group_ids=True,
1261 + )
1262 +
1263 + thread.add_stored_request("req-a")
1264 + thread._handle_request(_make_multi_group_store_req("req-a", [b"a0", b"a1"]))
1265 +
1266 + keys, addrs, sizes, config = store.batch_put_from_multi_buffers.call_args.args
1267 + assert keys == [
1268 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6130",
1269 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6131",
1270 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:1@6130",
1271 + "test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:1@6131",
1272 + ]
1273 + assert addrs == [[0x1000], [0x1100], [0x3200], [0x3300]]
1274 + assert sizes == [[256], [256], [256], [256]]
1275 + # Different vLLM KV cache groups for the same prefix chunk share the
1276 + # same Mooncake lifecycle group id.
1277 + assert config.group_ids == [
1278 + "vllm-mooncake-store:test-model@6130",
1279 + "vllm-mooncake-store:test-model@6131",
1280 + "vllm-mooncake-store:test-model@6130",
1281 + "vllm-mooncake-store:test-model@6131",
1282 + ]
1283 + assert config.group_ids[0] == config.group_ids[2]
1284 + assert config.group_ids[1] == config.group_ids[3]
1285 +
1286 +
1287 +def test_store_sending_thread_group_ids_follow_missing_key_filter():
1288 + store = MagicMock()
1289 + store.batch_is_exist.return_value = [1, 0]
1290 + store.batch_put_from_multi_buffers.return_value = [256]
1291 + replicate_config = SimpleNamespace(group_ids=None)
1292 + thread = _make_store_sending_thread(
1293 + store,
1294 + replicate_config=replicate_config,
1295 + enable_group_semantics=True,
1296 + supports_group_ids=True,
1297 + )
1298 +
1299 + thread.add_stored_request("req-a")
1300 + thread._handle_request(_make_store_req("req-a", [b"a0", b"a1"]))
1301 +
1302 + keys, _addrs, _sizes, config = store.batch_put_from_multi_buffers.call_args.args
1303 + assert keys == ["test-model@tp_rank:0@pcp0@dcp0@pp_rank:0@group:0@6131"]
1304 + assert config.group_ids == ["vllm-mooncake-store:test-model@6131"]
1305 +
1306 +
1307 def test_estimate_disk_offload_staging_bytes_sums_multi_segment_sizes():
1308 assert worker._estimate_disk_offload_staging_bytes([256, 512]) == 12288
1309
@@ -1392,6 +1727,130 @@ def test_worker_put_striding_covers_every_rank_get_namespace(
1727 )
1728
1729
1730 +def test_requester_worker_group_semantics_falls_back_without_group_ids(
1731 + tmp_path,
1732 + monkeypatch,
1733 +):
1734 + store = MagicMock()
1735 + store.setup.return_value = 0
1736 + _install_fake_mooncake(monkeypatch, store)
1737 + _patch_worker_runtime(monkeypatch)
1738 + warning = MagicMock()
1739 + monkeypatch.setattr(worker.logger, "warning", warning)
1740 + monkeypatch.setenv(
1741 + "MOONCAKE_CONFIG_PATH",
1742 + _write_mooncake_config(
1743 + tmp_path,
1744 + {
1745 + "metadata_server": "http://metadata/endpoint",
1746 + "protocol": "tcp",
1747 + "device_name": "",
1748 + "master_server_address": "10.0.0.7:50051",
1749 + },
1750 + ),
1751 + )
1752 +
1753 + w = worker.MooncakeStoreWorker(
1754 + _make_vllm_config(
1755 + extra_config={
1756 + "enable_group_semantics": True,
1757 + }
1758 + ),
1759 + _make_kv_cache_config(),
1760 + )
1761 +
1762 + assert w.enable_group_semantics is True
1763 + assert w._supports_group_ids is False
1764 + assert any(
1765 + "does not support ReplicateConfig.group_ids" in call.args[0]
1766 + for call in warning.call_args_list
1767 + )
1768 +
1769 +
1770 +def test_requester_worker_group_semantics_string_false_does_not_enable(
1771 + tmp_path,
1772 + monkeypatch,
1773 +):
1774 + store = MagicMock()
1775 + store.setup.return_value = 0
1776 + _install_fake_mooncake(monkeypatch, store)
1777 + _patch_worker_runtime(monkeypatch)
1778 + warning = MagicMock()
1779 + monkeypatch.setattr(worker.logger, "warning", warning)
1780 + monkeypatch.setenv(
1781 + "MOONCAKE_CONFIG_PATH",
1782 + _write_mooncake_config(
1783 + tmp_path,
1784 + {
1785 + "metadata_server": "http://metadata/endpoint",
1786 + "protocol": "tcp",
1787 + "device_name": "",
1788 + "master_server_address": "10.0.0.7:50051",
1789 + },
1790 + ),
1791 + )
1792 +
1793 + w = worker.MooncakeStoreWorker(
1794 + _make_vllm_config(
1795 + extra_config={
1796 + "enable_group_semantics": "false",
1797 + }
1798 + ),
1799 + _make_kv_cache_config(),
1800 + )
1801 +
1802 + assert w.enable_group_semantics is False
1803 + assert not any(
1804 + "does not support ReplicateConfig.group_ids" in call.args[0]
1805 + for call in warning.call_args_list
1806 + )
1807 +
1808 +
1809 +def test_requester_worker_group_semantics_string_true_enables(
1810 + tmp_path,
1811 + monkeypatch,
1812 +):
1813 + store = MagicMock()
1814 + store.setup.return_value = 0
1815 +
1816 + class FakeReplicateConfig:
1817 + def __init__(self) -> None:
1818 + self.group_ids = None
1819 +
1820 + fake_store_module = types.ModuleType("mooncake.store")
1821 + fake_store_module.MooncakeDistributedStore = lambda: store # type: ignore[attr-defined]
1822 + fake_store_module.ReplicateConfig = FakeReplicateConfig # type: ignore[attr-defined]
1823 + fake_mooncake_module = types.ModuleType("mooncake")
1824 + fake_mooncake_module.store = fake_store_module # type: ignore[attr-defined]
1825 + monkeypatch.setitem(sys.modules, "mooncake", fake_mooncake_module)
1826 + monkeypatch.setitem(sys.modules, "mooncake.store", fake_store_module)
1827 + _patch_worker_runtime(monkeypatch)
1828 + monkeypatch.setenv(
1829 + "MOONCAKE_CONFIG_PATH",
1830 + _write_mooncake_config(
1831 + tmp_path,
1832 + {
1833 + "metadata_server": "http://metadata/endpoint",
1834 + "protocol": "tcp",
1835 + "device_name": "",
1836 + "master_server_address": "10.0.0.7:50051",
1837 + },
1838 + ),
1839 + )
1840 +
1841 + w = worker.MooncakeStoreWorker(
1842 + _make_vllm_config(
1843 + extra_config={
1844 + "enable_group_semantics": "true",
1845 + }
1846 + ),
1847 + _make_kv_cache_config(),
1848 + )
1849 +
1850 + assert w.enable_group_semantics is True
1851 + assert w._supports_group_ids is True
1852 +
1853 +
1854 # ---------------------------------------------------------------------------
1855 # Helpers for register_kv_caches tests
1856 # ---------------------------------------------------------------------------
@@ -1481,7 +1940,7 @@ def test_store_sending_thread_only_stores_swa_blocks_in_window():
1940 store = MagicMock()
1941 store.batch_is_exist.side_effect = lambda keys: [0] * len(keys)
1942 store.batch_put_from_multi_buffers.side_effect = (
1484 - lambda keys, addrs, sizes, replicate_config: [256] * len(keys)
1943 + lambda keys, addrs, sizes, *_args: [256] * len(keys)
1944 )
1945
1946 full_spec = FullAttentionSpec(
@@ -1767,6 +2226,8 @@ def _make_bare_worker(
2226
2227 worker.disk_offload_buffer_budget_bytes = None
2228 worker.store_replicate_config = SimpleNamespace()
2229 + worker.enable_group_semantics = False
2230 + worker._supports_group_ids = False
2231 worker._kv_connector_stats_lock = threading.Lock()
2232 worker.kv_connector_stats = MooncakeStoreConnectorStats()
2233
vllm/distributed/kv_transfer/kv_connector/v1/mooncake/store/worker.py
+68 -1
@@ -89,6 +89,22 @@ def _rotate_list(values: list[_T], offset: int) -> list[_T]:
89 return values[offset:] + values[:offset]
90
91
92 +def _replicate_config_supports_group_ids(
93 + replicate_config_cls: type[Any],
94 + replicate_config: Any,
95 +) -> bool:
96 + if hasattr(replicate_config_cls, "group_ids"):
97 + return True
98 + return hasattr(replicate_config, "group_ids")
99 +
100 +
101 +def _make_mooncake_group_id(metadata: KeyMetadata, chunk_hash: str) -> str:
102 + # Mooncake group ids describe the lifecycle unit. For vLLM, that unit is
103 + # a prefix chunk, so shard dimensions stay only in the object key.
104 + prefix = f"{metadata.cache_prefix}@" if metadata.cache_prefix else ""
105 + return f"vllm-mooncake-store:{prefix}{metadata.model_name}@{chunk_hash}"
106 +
107 +
108 # Mirrors FileStorageConfig::local_buffer_size in Mooncake C++.
109 DEFAULT_MOONCAKE_DISK_STAGING_BUFFER_BYTES = 1280 * 1024 * 1024
110
@@ -459,6 +475,8 @@ class KVCacheStoreSendingThread(KVTransferThread):
475 ready_event: threading.Event,
476 enable_kv_event: bool = False,
477 replicate_config: Any = None,
478 + enable_group_semantics: bool = False,
479 + supports_group_ids: bool = False,
480 record_operation: Callable[..., None] | None = None,
481 ):
482 super().__init__(
@@ -479,6 +497,8 @@ class KVCacheStoreSendingThread(KVTransferThread):
497 # Caller always passes a non-None ReplicateConfig — see
498 # MooncakeStoreWorker.__init__ where store_replicate_config is built.
499 self.replicate_config = replicate_config
500 + self.enable_group_semantics = enable_group_semantics
501 + self.supports_group_ids = supports_group_ids
502
503 # Pause store requests when CPU/disk offloading is under pressure.
504 self._store_pressure_active = False
@@ -567,6 +587,9 @@ class KVCacheStoreSendingThread(KVTransferThread):
587 keys: list[str] = []
588 addrs: list[list[int]] = []
589 sizes: list[list[int]] = []
590 + group_ids: list[str] | None = (
591 + [] if self.enable_group_semantics and self.supports_group_ids else None
592 + )
593 saved = self._saved_offset.get(req_meta.req_id, 0)
594 for g_idx, db in enumerate(self.token_databases):
595 group_blocks = req_meta.block_ids[g_idx]
@@ -603,9 +626,17 @@ class KVCacheStoreSendingThread(KVTransferThread):
626 )
627 continue
628 addr, size = db.prepare_value_for_block(block_id)
606 - keys.append(db.key_for(key_hash))
629 + key = db.key_for(key_hash)
630 + keys.append(key)
631 addrs.append(addr)
632 sizes.append(size)
633 + if group_ids is not None:
634 + group_ids.append(
635 + _make_mooncake_group_id(
636 + db.metadata,
637 + key.rsplit("@", 1)[-1],
638 + )
639 + )
640
641 if not keys:
642 return True
@@ -633,9 +664,14 @@ class KVCacheStoreSendingThread(KVTransferThread):
664 keys = [keys[i] for i in missing]
665 addrs = [addrs[i] for i in missing]
666 sizes = [sizes[i] for i in missing]
667 + if group_ids is not None:
668 + group_ids = [group_ids[i] for i in missing]
669 if req_meta.current_event is not None:
670 # Fence the CoW block copy enqueued earlier this step.
671 req_meta.current_event.synchronize()
672 + if group_ids is not None:
673 + assert len(group_ids) == len(keys)
674 + self.replicate_config.group_ids = group_ids
675 batch_bytes = _sum_batch_bytes(sizes)
676 put_start = time.perf_counter()
677 try:
@@ -797,6 +833,18 @@ class KVCacheStoreSendingThread(KVTransferThread):
833 ]
834 group_indices = [group_indices[i] for i in missing_indices]
835
836 + group_ids = (
837 + [
838 + _make_mooncake_group_id(
839 + self.token_databases[g_idx].metadata,
840 + key.rsplit("@", 1)[-1],
841 + )
842 + for key, g_idx in zip(keys, group_indices, strict=True)
843 + ]
844 + if self.enable_group_semantics and self.supports_group_ids
845 + else None
846 + )
847 +
848 logger.debug(
849 "Storing KV cache for %d blocks (groups=%s) for request %s",
850 len(keys),
@@ -855,6 +903,10 @@ class KVCacheStoreSendingThread(KVTransferThread):
903 if current_event is not None:
904 current_event.synchronize()
905
906 + if group_ids is not None:
907 + assert len(group_ids) == len(keys)
908 + self.replicate_config.group_ids = group_ids
909 +
910 batch_bytes = _sum_batch_bytes(sizes)
911 put_start = time.perf_counter()
912 try:
@@ -1210,6 +1262,19 @@ class MooncakeStoreWorker:
1262 preferred_segment = rdma_utils.get_configured_preferred_segment(extra_config)
1263 self.preferred_segment = preferred_segment
1264 self.store_replicate_config = ReplicateConfig()
1265 + self.enable_group_semantics = (
1266 + str(extra_config.get("enable_group_semantics", "False")).strip().lower()
1267 + == "true"
1268 + )
1269 + self._supports_group_ids = _replicate_config_supports_group_ids(
1270 + ReplicateConfig, self.store_replicate_config
1271 + )
1272 + if self.enable_group_semantics and not self._supports_group_ids:
1273 + logger.warning(
1274 + "Mooncake group semantics is enabled, but the installed "
1275 + "Mooncake package does not support ReplicateConfig.group_ids. "
1276 + "Falling back to the existing batch_put_from_multi_buffers path."
1277 + )
1278 if preferred_segment is not None:
1279 self.store_replicate_config.preferred_segment = preferred_segment
1280
@@ -1503,6 +1568,8 @@ class MooncakeStoreWorker:
1568 ready_event_sending,
1569 self.enable_kv_events,
1570 self.store_replicate_config,
1571 + enable_group_semantics=self.enable_group_semantics,
1572 + supports_group_ids=self._supports_group_ids,
1573 record_operation=self._record_kv_connector_operation,
1574 )
1575 self.kv_send_thread.start()