| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- #!/usr/bin/env python3
- """Rebuild GLM-5.3-Flash TP=4 + DFlash2's KV specs on the nightly and drive
- get_kv_cache_groups / get_kv_cache_configs. Mode 'before' expects the stock
- tree's NotImplementedError; mode 'after' checks the patched tree end to end
- (groups, configs, block sizes, coordinator, pickle, a small GPU allocation).
- """
- import os
- import pickle
- import sys
- from dataclasses import replace
- import torch
- MODE = sys.argv[1]
- MODEL = "/models/glm-5.3-flash-nvfp4-nvidia"
- DRAFT = "/models/glm-5.3-flash-dflash2"
- KV_PIN = 27917287424
- TP = 4
- from vllm.engine.arg_utils import EngineArgs # noqa: E402
- args = EngineArgs(
- model=MODEL,
- tensor_parallel_size=TP,
- distributed_executor_backend="ray",
- max_model_len=524288,
- kv_cache_dtype="fp8_e4m3",
- block_size=2304,
- kv_cache_memory_bytes=KV_PIN,
- enable_prefix_caching=True,
- max_num_batched_tokens=16384,
- max_num_seqs=32,
- long_prefill_token_threshold=2304,
- trust_remote_code=True,
- gpu_memory_utilization=0.88,
- speculative_config={
- "method": "dflash",
- "model": DRAFT,
- "num_speculative_tokens": 7,
- },
- )
- vllm_config = args.create_engine_config()
- cc = vllm_config.cache_config
- mc = vllm_config.model_config
- # The engine core resolves the layout before grouping (resolve_kv_cache_layout):
- # candidates[0] of the workers' supported list. FlashInferMLASparseSM90Backend,
- # DeepseekV32IndexerBackend (GLM indexer) and KpoolTailBackend each declare only
- # LBHNC; TritonAttn (the drafter) declares no preference. So: LBHNC.
- cc.kv_cache_layout = "LBHNC"
- sc = vllm_config.speculative_config
- print(f"== vllm {__import__('vllm').__version__}")
- print(
- f"cache: block_size={cc.block_size} cache_dtype={cc.cache_dtype} "
- f"mamba_block_size={cc.mamba_block_size} mamba_page_size_padded={cc.mamba_page_size_padded} "
- f"mamba_cache_mode={cc.mamba_cache_mode} mamba_cache_dtype={cc.mamba_cache_dtype} "
- f"mamba_ssm_cache_dtype={cc.mamba_ssm_cache_dtype} use_kda_recoverssm={cc.use_kda_recoverssm} "
- f"kv_cache_memory_bytes={cc.kv_cache_memory_bytes} kv_cache_layout={cc.kv_cache_layout} "
- f"skip_page_size_padded={cc.skip_page_size_padded}"
- )
- print(
- f"spec: method={sc.method} k={sc.num_speculative_tokens} draft_arch={sc.draft_model_config.architectures} "
- f"use_eagle={sc.use_eagle()} block_drop={sc.use_eagle_block_drop()} "
- f"lookahead={vllm_config.num_prefill_lookahead_tokens} max_in_flight={vllm_config.max_in_flight_tokens} "
- f"max_model_len={mc.max_model_len}"
- )
- from vllm.config import set_current_vllm_config # noqa: E402
- from vllm.model_executor.layers.mamba.mamba_utils import ( # noqa: E402
- MambaStateDtypeCalculator,
- MambaStateShapeCalculator,
- )
- from vllm.models.glm5next.common.attention import ( # noqa: E402
- Glm5NextIndexerCache,
- Glm5NextTailCache,
- )
- from vllm.utils.torch_utils import kv_cache_dtype_str_to_dtype # noqa: E402
- from vllm.v1.kv_cache_interface import ( # noqa: E402
- MambaSpec,
- MLAAttentionSpec,
- SlidingWindowSpec,
- UniformTypeKVCacheSpecs,
- get_kv_quant_mode,
- )
- hf = mc.hf_text_config
- mla_layers = list(hf.linear_attn_config["full_attn_layers"])
- kda_layers = list(hf.linear_attn_config["kda_layers"])
- assert hf.kv_lora_rank == 512 and hf.index_kpool == 4 and hf.index_head_dim == 128
- P = "language_model.model.layers"
- fp8 = kv_cache_dtype_str_to_dtype("fp8_e4m3", mc)
- qm = get_kv_quant_mode("fp8_e4m3")
- from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention # noqa: E402
- kda_type = GatedDeltaNetAttention.mamba_type.fget(None)
- specs = {}
- with set_current_vllm_config(vllm_config):
- for i in range(hf.num_hidden_layers):
- if i in mla_layers:
- kc = Glm5NextIndexerCache(
- head_dim=hf.index_head_dim + hf.index_head_dim // 128 * 4,
- dtype=torch.uint8,
- prefix=f"{P}.{i}.self_attn.indexer.k_cache",
- cache_config=cc,
- index_kpool=hf.index_kpool,
- )
- specs[kc.prefix] = kc.get_kv_cache_spec(vllm_config)
- tc = Glm5NextTailCache(
- head_dim=hf.index_head_dim,
- dtype=torch.bfloat16,
- prefix=f"{P}.{i}.self_attn.indexer.tail_cache",
- cache_config=cc,
- index_kpool=hf.index_kpool,
- )
- specs[tc.prefix] = tc.get_kv_cache_spec(vllm_config)
- # mla_attention.MLAAttention.get_kv_cache_spec, head_size = kv_lora_rank + qk_rope(0)
- specs[f"{P}.{i}.self_attn.mla_attn"] = MLAAttentionSpec(
- block_size=cc.block_size,
- num_kv_heads=1,
- head_size=hf.kv_lora_rank + hf.qk_rope_head_dim,
- dtype=fp8,
- cache_dtype_str="fp8_e4m3",
- kv_quant_mode=qm,
- state_content_bytes=None,
- is_index_group_leader=True,
- non_causal_multi_token_decode=False,
- )
- else:
- assert i in kda_layers
- shapes = MambaStateShapeCalculator.kda_state_shape(
- TP,
- hf.linear_num_heads,
- hf.linear_head_dim,
- conv_kernel_size=hf.linear_conv_kernel_dim,
- num_spec=vllm_config.num_speculative_tokens,
- )
- dtypes = MambaStateDtypeCalculator.kda_state_dtype(
- mc.dtype, cc.mamba_cache_dtype, cc.mamba_ssm_cache_dtype
- )
- specs[f"{P}.{i}.self_attn"] = MambaSpec(
- shapes=tuple(shapes),
- dtypes=dtypes,
- block_size=cc.mamba_block_size,
- page_size_padded=cc.mamba_page_size_padded,
- mamba_type=kda_type,
- tp_replicated=False,
- mamba_cache_mode=cc.mamba_cache_mode,
- num_speculative_blocks=(
- 0 if cc.use_kda_recoverssm else vllm_config.num_speculative_tokens
- ),
- )
- # The drafter: build a real Attention layer under the draft config so the
- # backend selection and Attention.get_kv_cache_spec are the nightly's own.
- dhf = sc.draft_model_config.hf_config
- draft_cfg = replace(
- vllm_config,
- model_config=sc.draft_model_config,
- attention_config=replace(
- vllm_config.attention_config, backend=None, use_non_causal=True
- ),
- )
- draft_names = [f"model.layers.{i}.self_attn.attn" for i in range(dhf.num_hidden_layers)]
- draft_backend = None
- try:
- from vllm.model_executor.layers.attention.attention import Attention
- with set_current_vllm_config(draft_cfg):
- attn = Attention(
- dhf.num_attention_heads // TP,
- dhf.head_dim,
- dhf.head_dim**-0.5,
- num_kv_heads=max(1, dhf.num_key_value_heads // TP),
- cache_config=cc,
- quant_config=None,
- per_layer_sliding_window=dhf.sliding_window,
- prefix=draft_names[0],
- )
- draft_spec = attn.get_kv_cache_spec(draft_cfg)
- draft_backend = attn.get_attn_backend()
- print(
- f"draft backend: {draft_backend.get_name()} kernel blocks "
- f"{draft_backend.get_supported_kernel_block_sizes()}"
- )
- except Exception as e: # noqa: BLE001
- print(f"!! real drafter Attention construction failed: {type(e).__name__}: {e}")
- print(" falling back to a hand-built SlidingWindowSpec at the primary block")
- draft_spec = SlidingWindowSpec(
- block_size=cc.block_size,
- num_kv_heads=max(1, dhf.num_key_value_heads // TP),
- head_size=dhf.head_dim,
- dtype=fp8,
- kv_quant_mode=qm,
- sliding_window=dhf.sliding_window,
- )
- for name in draft_names:
- specs[name] = draft_spec
- print(f"draft spec: {draft_spec}")
- print(f"== {len(specs)} layer specs")
- seen = set()
- for name, spec in specs.items():
- key = (type(spec).__name__, spec.block_size, spec.page_size_bytes)
- if key in seen:
- continue
- seen.add(key)
- print(f" {name}: {type(spec).__name__} block={spec.block_size} page={spec.page_size_bytes}")
- from vllm.v1.core import kv_cache_utils as U # noqa: E402
- specs_list = [dict(specs) for _ in range(TP)]
- if MODE == "before":
- try:
- U.get_kv_cache_configs(vllm_config, specs_list, [KV_PIN] * TP)
- except NotImplementedError as e:
- print(f"== BEFORE: NotImplementedError as expected:\n {e}")
- sys.exit(0)
- print("!! BEFORE: no error raised; the stock tree changed")
- sys.exit(1)
- assert MODE == "after"
- groups = U.get_kv_cache_groups(vllm_config, dict(specs))
- print(f"== AFTER: {len(groups)} groups")
- for gid, g in enumerate(groups):
- s = g.kv_cache_spec
- inner = (
- f"{len(s.kv_cache_specs)} inner specs "
- f"{sorted({type(x).__name__ for x in s.kv_cache_specs.values()})}"
- if isinstance(s, UniformTypeKVCacheSpecs)
- else ""
- )
- print(
- f" [{gid}] {type(s).__name__} block={s.block_size} page={s.page_size_bytes} "
- f"cacheable={s.prefix_cacheable} eagle={g.is_eagle_group} layers={len(g.layer_names)} "
- f"{g.layer_names[0]}..{g.layer_names[-1]} {inner}"
- )
- configs = U.get_kv_cache_configs(vllm_config, specs_list, [KV_PIN] * TP)
- cfg = configs[0]
- assert all(c.num_blocks == cfg.num_blocks for c in configs)
- bpb = U._pool_bytes_per_block(cfg.kv_cache_groups)
- print(f"== config: num_blocks={cfg.num_blocks} bytes/block={bpb} ({bpb/2**20:.2f} MiB) "
- f"pool={cfg.num_blocks*bpb/2**30:.2f} GiB of {KV_PIN/2**30:.2f} GiB pinned")
- layout = U._glm5_next_tensor_layout(cfg.kv_cache_groups)
- assert layout is not None
- attn_group, mamba_groups, mla_names, idx_names, mla_page, idx_page, tail_names, tail_page, draft_groups = layout
- print(f" layout: {len(mla_names)} MLA x {mla_page} + {len(idx_names)} idx x {idx_page}; tail {len(tail_names)} x {tail_page}; "
- f"{len(mamba_groups)} mamba groups; {len(draft_groups)} draft groups "
- f"({sum(len(g.layer_names) for g in draft_groups)} layers x {draft_groups[0].kv_cache_spec.page_size_bytes} B)")
- size = cfg.kv_cache_tensors[0].size
- assert size == bpb * cfg.num_blocks
- mla_end = len(mla_names) * mla_page * cfg.num_blocks
- idx_end = mla_end + len(idx_names) * idx_page * cfg.num_blocks
- regions = []
- for t in cfg.kv_cache_tensors:
- assert t.size == size
- if t.layers[0] in draft_names:
- span = t.layer_stride * len(t.layers)
- assert t.offset >= idx_end and t.offset + span <= size, (t, idx_end, size)
- regions.append((t.offset, t.offset + span, t.layers[0], t.block_stride))
- regions.sort()
- assert len(regions) == len(draft_names)
- for a, b in zip(regions, regions[1:]):
- assert a[1] <= b[0], "draft regions overlap"
- assert regions[-1][1] == size and regions[0][0] == idx_end, "draft regions do not tile the tail of the pool"
- print(f" draft regions: {len(regions)} x {regions[0][1]-regions[0][0]} B, block_stride={regions[0][3]}, "
- f"from {idx_end} to {size} (pool end)")
- sched_bs, hash_bs = U.resolve_kv_cache_block_sizes(cfg, vllm_config)
- participating = [g.kv_cache_spec.block_size for g in cfg.kv_cache_groups if g.kv_cache_spec.prefix_cacheable]
- print(f"== block sizes: scheduler={sched_bs} hash={hash_bs} engine-core min(participating)={min(participating)} "
- f"participating={participating}")
- assert sched_bs == cc.block_size and min(participating) == cc.block_size
- sched_cfg = U.generate_scheduler_kv_cache_config(configs)
- tokens, conc = U.get_kv_cache_capacity(vllm_config, sched_cfg)
- print(f"== capacity: {tokens:,} tokens, {conc:.2f}x at {mc.max_model_len}")
- for gid, g in enumerate(sched_cfg.kv_cache_groups):
- s = g.kv_cache_spec
- need = U.cdiv(s.max_memory_usage_bytes(vllm_config), s.page_size_bytes)
- print(f" group {gid} {type(s).__name__}: {need} blocks per max-len request")
- from vllm.v1.core.kv_cache_coordinator import get_kv_cache_coordinator # noqa: E402
- coord = get_kv_cache_coordinator(
- kv_cache_config=sched_cfg,
- max_model_len=mc.max_model_len,
- max_in_flight_tokens=vllm_config.max_in_flight_tokens,
- use_eagle=sc.use_eagle_block_drop(),
- enable_caching=True,
- enable_kv_cache_events=False,
- dcp_world_size=1,
- pcp_world_size=1,
- scheduler_block_size=sched_bs,
- hash_block_size=hash_bs,
- num_prefill_lookahead=vllm_config.num_prefill_lookahead_tokens,
- )
- print(f"== coordinator: {type(coord).__name__}; managers "
- f"{[type(m).__name__ for m in coord.single_type_managers]}")
- print(f" hit-lookup groups: {[(type(g.spec).__name__, g.group_ids, g.use_eagle) for g in coord.attention_groups]}")
- print(f" eagle_group_ids={sorted(coord.eagle_group_ids)} partial_hash_hits={coord.enable_partial_hash_hits}")
- rt = pickle.loads(pickle.dumps(cfg))
- assert rt.kv_cache_groups[-1].kv_cache_spec == cfg.kv_cache_groups[-1].kv_cache_spec
- assert type(rt.kv_cache_groups[-1].kv_cache_spec).__module__ == "vllm.v1.core.kv_cache_utils"
- assert not rt.kv_cache_groups[-1].kv_cache_spec.prefix_cacheable
- print("== pickle round trip ok (draft spec by module path, still opted out)")
- # PP projection: a stage that holds no drafter layer must still take the GLM
- # layout (empty draft group), count no draft bytes and emit no draft tensor.
- stage_specs = {k: v for k, v in specs.items() if k not in draft_names}
- proj = U._project_kv_cache_groups_to_worker(groups, stage_specs)
- assert proj[-1].layer_names == [] and isinstance(proj[-1].kv_cache_spec, U.DraftSlidingWindowSpec)
- pl = U._glm5_next_tensor_layout(proj)
- assert pl is not None and pl[-1][0].layer_names == []
- stage_cfg = U.get_kv_cache_config_from_groups(vllm_config, proj, KV_PIN)
- stage_bpb = U._pool_bytes_per_block(proj)
- assert stage_bpb == len(mla_names) * mla_page + len(idx_names) * idx_page
- assert not any(t.layers[0] in draft_names for t in stage_cfg.kv_cache_tensors)
- assert U._max_memory_usage_bytes_from_groups(vllm_config, proj) < U._max_memory_usage_bytes_from_groups(vllm_config, groups)
- print(f"== PP projection without the drafter: {len(proj)} groups (last empty), bytes/block={stage_bpb}, "
- f"num_blocks={stage_cfg.num_blocks}, no draft tensors")
- # A small GPU allocation with the real view builder and a kernel split for the drafter.
- if torch.cuda.is_available():
- from vllm.v1.worker.utils import allocate_kv_cache, select_common_block_size
- small = U.get_kv_cache_config_from_groups(vllm_config, cfg.kv_cache_groups, 2 * 2**30)
- kernel = []
- for g in small.kv_cache_groups:
- s = g.kv_cache_spec
- if g.layer_names and g.layer_names[0] in draft_names and draft_backend is not None:
- kernel.append(select_common_block_size(s.block_size, [draft_backend]))
- else:
- kernel.append(s.block_size)
- lay = cc.get_resolved_kv_cache_layout()
- layout_name = lay.name
- caches = allocate_kv_cache(small, torch.device("cuda"), lay, kernel)
- dv = caches[draft_names[0]]
- print(f"== GPU alloc ({small.num_blocks} blocks, layout {layout_name}, kernel blocks {kernel}): "
- f"draft view shape={tuple(dv.shape)} dtype={dv.dtype} strides={dv.stride()}")
- # Draft regions must not alias anything else: poison one and check the rest.
- dv.view(torch.uint8).fill_(0xAB)
- for name, view in caches.items():
- if name in draft_names:
- continue
- v = view.view(torch.uint8) if view.dtype != torch.uint8 else view
- assert int(v.max()) == 0, f"{name} aliases the draft region"
- other = caches[draft_names[1]].view(torch.uint8)
- assert int(other.max()) == 0, "draft layers alias each other"
- print(" draft region is unshared (poison did not leak into any other view)")
- del caches
- print("== ALL CHECKS PASSED")
|