_glm53_dflash2_kv_groups_test.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. #!/usr/bin/env python3
  2. """Rebuild GLM-5.3-Flash TP=4 + DFlash2's KV specs on the nightly and drive
  3. get_kv_cache_groups / get_kv_cache_configs. Mode 'before' expects the stock
  4. tree's NotImplementedError; mode 'after' checks the patched tree end to end
  5. (groups, configs, block sizes, coordinator, pickle, a small GPU allocation).
  6. """
  7. import os
  8. import pickle
  9. import sys
  10. from dataclasses import replace
  11. import torch
  12. MODE = sys.argv[1]
  13. MODEL = "/models/glm-5.3-flash-nvfp4-nvidia"
  14. DRAFT = "/models/glm-5.3-flash-dflash2"
  15. KV_PIN = 27917287424
  16. TP = 4
  17. from vllm.engine.arg_utils import EngineArgs # noqa: E402
  18. args = EngineArgs(
  19. model=MODEL,
  20. tensor_parallel_size=TP,
  21. distributed_executor_backend="ray",
  22. max_model_len=524288,
  23. kv_cache_dtype="fp8_e4m3",
  24. block_size=2304,
  25. kv_cache_memory_bytes=KV_PIN,
  26. enable_prefix_caching=True,
  27. max_num_batched_tokens=16384,
  28. max_num_seqs=32,
  29. long_prefill_token_threshold=2304,
  30. trust_remote_code=True,
  31. gpu_memory_utilization=0.88,
  32. speculative_config={
  33. "method": "dflash",
  34. "model": DRAFT,
  35. "num_speculative_tokens": 7,
  36. },
  37. )
  38. vllm_config = args.create_engine_config()
  39. cc = vllm_config.cache_config
  40. mc = vllm_config.model_config
  41. # The engine core resolves the layout before grouping (resolve_kv_cache_layout):
  42. # candidates[0] of the workers' supported list. FlashInferMLASparseSM90Backend,
  43. # DeepseekV32IndexerBackend (GLM indexer) and KpoolTailBackend each declare only
  44. # LBHNC; TritonAttn (the drafter) declares no preference. So: LBHNC.
  45. cc.kv_cache_layout = "LBHNC"
  46. sc = vllm_config.speculative_config
  47. print(f"== vllm {__import__('vllm').__version__}")
  48. print(
  49. f"cache: block_size={cc.block_size} cache_dtype={cc.cache_dtype} "
  50. f"mamba_block_size={cc.mamba_block_size} mamba_page_size_padded={cc.mamba_page_size_padded} "
  51. f"mamba_cache_mode={cc.mamba_cache_mode} mamba_cache_dtype={cc.mamba_cache_dtype} "
  52. f"mamba_ssm_cache_dtype={cc.mamba_ssm_cache_dtype} use_kda_recoverssm={cc.use_kda_recoverssm} "
  53. f"kv_cache_memory_bytes={cc.kv_cache_memory_bytes} kv_cache_layout={cc.kv_cache_layout} "
  54. f"skip_page_size_padded={cc.skip_page_size_padded}"
  55. )
  56. print(
  57. f"spec: method={sc.method} k={sc.num_speculative_tokens} draft_arch={sc.draft_model_config.architectures} "
  58. f"use_eagle={sc.use_eagle()} block_drop={sc.use_eagle_block_drop()} "
  59. f"lookahead={vllm_config.num_prefill_lookahead_tokens} max_in_flight={vllm_config.max_in_flight_tokens} "
  60. f"max_model_len={mc.max_model_len}"
  61. )
  62. from vllm.config import set_current_vllm_config # noqa: E402
  63. from vllm.model_executor.layers.mamba.mamba_utils import ( # noqa: E402
  64. MambaStateDtypeCalculator,
  65. MambaStateShapeCalculator,
  66. )
  67. from vllm.models.glm5next.common.attention import ( # noqa: E402
  68. Glm5NextIndexerCache,
  69. Glm5NextTailCache,
  70. )
  71. from vllm.utils.torch_utils import kv_cache_dtype_str_to_dtype # noqa: E402
  72. from vllm.v1.kv_cache_interface import ( # noqa: E402
  73. MambaSpec,
  74. MLAAttentionSpec,
  75. SlidingWindowSpec,
  76. UniformTypeKVCacheSpecs,
  77. get_kv_quant_mode,
  78. )
  79. hf = mc.hf_text_config
  80. mla_layers = list(hf.linear_attn_config["full_attn_layers"])
  81. kda_layers = list(hf.linear_attn_config["kda_layers"])
  82. assert hf.kv_lora_rank == 512 and hf.index_kpool == 4 and hf.index_head_dim == 128
  83. P = "language_model.model.layers"
  84. fp8 = kv_cache_dtype_str_to_dtype("fp8_e4m3", mc)
  85. qm = get_kv_quant_mode("fp8_e4m3")
  86. from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention # noqa: E402
  87. kda_type = GatedDeltaNetAttention.mamba_type.fget(None)
  88. specs = {}
  89. with set_current_vllm_config(vllm_config):
  90. for i in range(hf.num_hidden_layers):
  91. if i in mla_layers:
  92. kc = Glm5NextIndexerCache(
  93. head_dim=hf.index_head_dim + hf.index_head_dim // 128 * 4,
  94. dtype=torch.uint8,
  95. prefix=f"{P}.{i}.self_attn.indexer.k_cache",
  96. cache_config=cc,
  97. index_kpool=hf.index_kpool,
  98. )
  99. specs[kc.prefix] = kc.get_kv_cache_spec(vllm_config)
  100. tc = Glm5NextTailCache(
  101. head_dim=hf.index_head_dim,
  102. dtype=torch.bfloat16,
  103. prefix=f"{P}.{i}.self_attn.indexer.tail_cache",
  104. cache_config=cc,
  105. index_kpool=hf.index_kpool,
  106. )
  107. specs[tc.prefix] = tc.get_kv_cache_spec(vllm_config)
  108. # mla_attention.MLAAttention.get_kv_cache_spec, head_size = kv_lora_rank + qk_rope(0)
  109. specs[f"{P}.{i}.self_attn.mla_attn"] = MLAAttentionSpec(
  110. block_size=cc.block_size,
  111. num_kv_heads=1,
  112. head_size=hf.kv_lora_rank + hf.qk_rope_head_dim,
  113. dtype=fp8,
  114. cache_dtype_str="fp8_e4m3",
  115. kv_quant_mode=qm,
  116. state_content_bytes=None,
  117. is_index_group_leader=True,
  118. non_causal_multi_token_decode=False,
  119. )
  120. else:
  121. assert i in kda_layers
  122. shapes = MambaStateShapeCalculator.kda_state_shape(
  123. TP,
  124. hf.linear_num_heads,
  125. hf.linear_head_dim,
  126. conv_kernel_size=hf.linear_conv_kernel_dim,
  127. num_spec=vllm_config.num_speculative_tokens,
  128. )
  129. dtypes = MambaStateDtypeCalculator.kda_state_dtype(
  130. mc.dtype, cc.mamba_cache_dtype, cc.mamba_ssm_cache_dtype
  131. )
  132. specs[f"{P}.{i}.self_attn"] = MambaSpec(
  133. shapes=tuple(shapes),
  134. dtypes=dtypes,
  135. block_size=cc.mamba_block_size,
  136. page_size_padded=cc.mamba_page_size_padded,
  137. mamba_type=kda_type,
  138. tp_replicated=False,
  139. mamba_cache_mode=cc.mamba_cache_mode,
  140. num_speculative_blocks=(
  141. 0 if cc.use_kda_recoverssm else vllm_config.num_speculative_tokens
  142. ),
  143. )
  144. # The drafter: build a real Attention layer under the draft config so the
  145. # backend selection and Attention.get_kv_cache_spec are the nightly's own.
  146. dhf = sc.draft_model_config.hf_config
  147. draft_cfg = replace(
  148. vllm_config,
  149. model_config=sc.draft_model_config,
  150. attention_config=replace(
  151. vllm_config.attention_config, backend=None, use_non_causal=True
  152. ),
  153. )
  154. draft_names = [f"model.layers.{i}.self_attn.attn" for i in range(dhf.num_hidden_layers)]
  155. draft_backend = None
  156. try:
  157. from vllm.model_executor.layers.attention.attention import Attention
  158. with set_current_vllm_config(draft_cfg):
  159. attn = Attention(
  160. dhf.num_attention_heads // TP,
  161. dhf.head_dim,
  162. dhf.head_dim**-0.5,
  163. num_kv_heads=max(1, dhf.num_key_value_heads // TP),
  164. cache_config=cc,
  165. quant_config=None,
  166. per_layer_sliding_window=dhf.sliding_window,
  167. prefix=draft_names[0],
  168. )
  169. draft_spec = attn.get_kv_cache_spec(draft_cfg)
  170. draft_backend = attn.get_attn_backend()
  171. print(
  172. f"draft backend: {draft_backend.get_name()} kernel blocks "
  173. f"{draft_backend.get_supported_kernel_block_sizes()}"
  174. )
  175. except Exception as e: # noqa: BLE001
  176. print(f"!! real drafter Attention construction failed: {type(e).__name__}: {e}")
  177. print(" falling back to a hand-built SlidingWindowSpec at the primary block")
  178. draft_spec = SlidingWindowSpec(
  179. block_size=cc.block_size,
  180. num_kv_heads=max(1, dhf.num_key_value_heads // TP),
  181. head_size=dhf.head_dim,
  182. dtype=fp8,
  183. kv_quant_mode=qm,
  184. sliding_window=dhf.sliding_window,
  185. )
  186. for name in draft_names:
  187. specs[name] = draft_spec
  188. print(f"draft spec: {draft_spec}")
  189. print(f"== {len(specs)} layer specs")
  190. seen = set()
  191. for name, spec in specs.items():
  192. key = (type(spec).__name__, spec.block_size, spec.page_size_bytes)
  193. if key in seen:
  194. continue
  195. seen.add(key)
  196. print(f" {name}: {type(spec).__name__} block={spec.block_size} page={spec.page_size_bytes}")
  197. from vllm.v1.core import kv_cache_utils as U # noqa: E402
  198. specs_list = [dict(specs) for _ in range(TP)]
  199. if MODE == "before":
  200. try:
  201. U.get_kv_cache_configs(vllm_config, specs_list, [KV_PIN] * TP)
  202. except NotImplementedError as e:
  203. print(f"== BEFORE: NotImplementedError as expected:\n {e}")
  204. sys.exit(0)
  205. print("!! BEFORE: no error raised; the stock tree changed")
  206. sys.exit(1)
  207. assert MODE == "after"
  208. groups = U.get_kv_cache_groups(vllm_config, dict(specs))
  209. print(f"== AFTER: {len(groups)} groups")
  210. for gid, g in enumerate(groups):
  211. s = g.kv_cache_spec
  212. inner = (
  213. f"{len(s.kv_cache_specs)} inner specs "
  214. f"{sorted({type(x).__name__ for x in s.kv_cache_specs.values()})}"
  215. if isinstance(s, UniformTypeKVCacheSpecs)
  216. else ""
  217. )
  218. print(
  219. f" [{gid}] {type(s).__name__} block={s.block_size} page={s.page_size_bytes} "
  220. f"cacheable={s.prefix_cacheable} eagle={g.is_eagle_group} layers={len(g.layer_names)} "
  221. f"{g.layer_names[0]}..{g.layer_names[-1]} {inner}"
  222. )
  223. configs = U.get_kv_cache_configs(vllm_config, specs_list, [KV_PIN] * TP)
  224. cfg = configs[0]
  225. assert all(c.num_blocks == cfg.num_blocks for c in configs)
  226. bpb = U._pool_bytes_per_block(cfg.kv_cache_groups)
  227. print(f"== config: num_blocks={cfg.num_blocks} bytes/block={bpb} ({bpb/2**20:.2f} MiB) "
  228. f"pool={cfg.num_blocks*bpb/2**30:.2f} GiB of {KV_PIN/2**30:.2f} GiB pinned")
  229. layout = U._glm5_next_tensor_layout(cfg.kv_cache_groups)
  230. assert layout is not None
  231. attn_group, mamba_groups, mla_names, idx_names, mla_page, idx_page, tail_names, tail_page, draft_groups = layout
  232. print(f" layout: {len(mla_names)} MLA x {mla_page} + {len(idx_names)} idx x {idx_page}; tail {len(tail_names)} x {tail_page}; "
  233. f"{len(mamba_groups)} mamba groups; {len(draft_groups)} draft groups "
  234. f"({sum(len(g.layer_names) for g in draft_groups)} layers x {draft_groups[0].kv_cache_spec.page_size_bytes} B)")
  235. size = cfg.kv_cache_tensors[0].size
  236. assert size == bpb * cfg.num_blocks
  237. mla_end = len(mla_names) * mla_page * cfg.num_blocks
  238. idx_end = mla_end + len(idx_names) * idx_page * cfg.num_blocks
  239. regions = []
  240. for t in cfg.kv_cache_tensors:
  241. assert t.size == size
  242. if t.layers[0] in draft_names:
  243. span = t.layer_stride * len(t.layers)
  244. assert t.offset >= idx_end and t.offset + span <= size, (t, idx_end, size)
  245. regions.append((t.offset, t.offset + span, t.layers[0], t.block_stride))
  246. regions.sort()
  247. assert len(regions) == len(draft_names)
  248. for a, b in zip(regions, regions[1:]):
  249. assert a[1] <= b[0], "draft regions overlap"
  250. assert regions[-1][1] == size and regions[0][0] == idx_end, "draft regions do not tile the tail of the pool"
  251. print(f" draft regions: {len(regions)} x {regions[0][1]-regions[0][0]} B, block_stride={regions[0][3]}, "
  252. f"from {idx_end} to {size} (pool end)")
  253. sched_bs, hash_bs = U.resolve_kv_cache_block_sizes(cfg, vllm_config)
  254. participating = [g.kv_cache_spec.block_size for g in cfg.kv_cache_groups if g.kv_cache_spec.prefix_cacheable]
  255. print(f"== block sizes: scheduler={sched_bs} hash={hash_bs} engine-core min(participating)={min(participating)} "
  256. f"participating={participating}")
  257. assert sched_bs == cc.block_size and min(participating) == cc.block_size
  258. sched_cfg = U.generate_scheduler_kv_cache_config(configs)
  259. tokens, conc = U.get_kv_cache_capacity(vllm_config, sched_cfg)
  260. print(f"== capacity: {tokens:,} tokens, {conc:.2f}x at {mc.max_model_len}")
  261. for gid, g in enumerate(sched_cfg.kv_cache_groups):
  262. s = g.kv_cache_spec
  263. need = U.cdiv(s.max_memory_usage_bytes(vllm_config), s.page_size_bytes)
  264. print(f" group {gid} {type(s).__name__}: {need} blocks per max-len request")
  265. from vllm.v1.core.kv_cache_coordinator import get_kv_cache_coordinator # noqa: E402
  266. coord = get_kv_cache_coordinator(
  267. kv_cache_config=sched_cfg,
  268. max_model_len=mc.max_model_len,
  269. max_in_flight_tokens=vllm_config.max_in_flight_tokens,
  270. use_eagle=sc.use_eagle_block_drop(),
  271. enable_caching=True,
  272. enable_kv_cache_events=False,
  273. dcp_world_size=1,
  274. pcp_world_size=1,
  275. scheduler_block_size=sched_bs,
  276. hash_block_size=hash_bs,
  277. num_prefill_lookahead=vllm_config.num_prefill_lookahead_tokens,
  278. )
  279. print(f"== coordinator: {type(coord).__name__}; managers "
  280. f"{[type(m).__name__ for m in coord.single_type_managers]}")
  281. print(f" hit-lookup groups: {[(type(g.spec).__name__, g.group_ids, g.use_eagle) for g in coord.attention_groups]}")
  282. print(f" eagle_group_ids={sorted(coord.eagle_group_ids)} partial_hash_hits={coord.enable_partial_hash_hits}")
  283. rt = pickle.loads(pickle.dumps(cfg))
  284. assert rt.kv_cache_groups[-1].kv_cache_spec == cfg.kv_cache_groups[-1].kv_cache_spec
  285. assert type(rt.kv_cache_groups[-1].kv_cache_spec).__module__ == "vllm.v1.core.kv_cache_utils"
  286. assert not rt.kv_cache_groups[-1].kv_cache_spec.prefix_cacheable
  287. print("== pickle round trip ok (draft spec by module path, still opted out)")
  288. # PP projection: a stage that holds no drafter layer must still take the GLM
  289. # layout (empty draft group), count no draft bytes and emit no draft tensor.
  290. stage_specs = {k: v for k, v in specs.items() if k not in draft_names}
  291. proj = U._project_kv_cache_groups_to_worker(groups, stage_specs)
  292. assert proj[-1].layer_names == [] and isinstance(proj[-1].kv_cache_spec, U.DraftSlidingWindowSpec)
  293. pl = U._glm5_next_tensor_layout(proj)
  294. assert pl is not None and pl[-1][0].layer_names == []
  295. stage_cfg = U.get_kv_cache_config_from_groups(vllm_config, proj, KV_PIN)
  296. stage_bpb = U._pool_bytes_per_block(proj)
  297. assert stage_bpb == len(mla_names) * mla_page + len(idx_names) * idx_page
  298. assert not any(t.layers[0] in draft_names for t in stage_cfg.kv_cache_tensors)
  299. assert U._max_memory_usage_bytes_from_groups(vllm_config, proj) < U._max_memory_usage_bytes_from_groups(vllm_config, groups)
  300. print(f"== PP projection without the drafter: {len(proj)} groups (last empty), bytes/block={stage_bpb}, "
  301. f"num_blocks={stage_cfg.num_blocks}, no draft tensors")
  302. # A small GPU allocation with the real view builder and a kernel split for the drafter.
  303. if torch.cuda.is_available():
  304. from vllm.v1.worker.utils import allocate_kv_cache, select_common_block_size
  305. small = U.get_kv_cache_config_from_groups(vllm_config, cfg.kv_cache_groups, 2 * 2**30)
  306. kernel = []
  307. for g in small.kv_cache_groups:
  308. s = g.kv_cache_spec
  309. if g.layer_names and g.layer_names[0] in draft_names and draft_backend is not None:
  310. kernel.append(select_common_block_size(s.block_size, [draft_backend]))
  311. else:
  312. kernel.append(s.block_size)
  313. lay = cc.get_resolved_kv_cache_layout()
  314. layout_name = lay.name
  315. caches = allocate_kv_cache(small, torch.device("cuda"), lay, kernel)
  316. dv = caches[draft_names[0]]
  317. print(f"== GPU alloc ({small.num_blocks} blocks, layout {layout_name}, kernel blocks {kernel}): "
  318. f"draft view shape={tuple(dv.shape)} dtype={dv.dtype} strides={dv.stride()}")
  319. # Draft regions must not alias anything else: poison one and check the rest.
  320. dv.view(torch.uint8).fill_(0xAB)
  321. for name, view in caches.items():
  322. if name in draft_names:
  323. continue
  324. v = view.view(torch.uint8) if view.dtype != torch.uint8 else view
  325. assert int(v.max()) == 0, f"{name} aliases the draft region"
  326. other = caches[draft_names[1]].view(torch.uint8)
  327. assert int(other.max()) == 0, "draft layers alias each other"
  328. print(" draft region is unshared (poison did not leak into any other view)")
  329. del caches
  330. print("== ALL CHECKS PASSED")