#!/usr/bin/env python3 """Byte-exact VRAM budget for GLM-5.2 hybrid quant on 4x RTX PRO 6000 (96GB, SM120). Model constants from zai-org/GLM-5.2 config.json (verified against the lukealonso/GLM-5.2-NVFP4 safetensors index: predicted 450.99 GB vs actual 450.93 GB total_size). """ GB = 1e9 GiB = 2**30 # --- architecture --- H = 6144 # hidden_size MOE_I = 2048 # moe_intermediate_size DENSE_I = 12288 # intermediate_size (dense layers 0-2) VOCAB = 154880 N_LAYERS = 78 # main layers N_MTP = 1 # layer 78 N_DENSE = 3 # layers 0,1,2 N_EXPERT_LAYERS = 75 + 1 # layers 3..77 + MTP layer N_EXPERTS = 256 KV_LORA = 512 QK_ROPE = 64 Q_LORA = 2048 N_HEADS = 64 QK_NOPE = 192 V_HEAD = 256 IDX_HEADS, IDX_HDIM = 32, 128 N_FULL_INDEXERS = 22 # 21 main (IndexShare freq=4) + 1 MTP CTX = 1_048_576 # --- per-component parameter counts --- expert_params_down = MOE_I * H # 12.58M expert_params_gateup = 2 * H * MOE_I # 25.17M expert_params = expert_params_down + expert_params_gateup # 37.75M layer_expert_params = N_EXPERTS * expert_params # 9.664B attn_params = (H*Q_LORA + Q_LORA*(N_HEADS*(QK_NOPE+QK_ROPE)) + H*(KV_LORA+QK_ROPE) + KV_LORA*(N_HEADS*(QK_NOPE+V_HEAD)) + (N_HEADS*V_HEAD)*H) # 165.03M per layer indexer_params = Q_LORA*IDX_HEADS*IDX_HDIM + H*IDX_HDIM + H*IDX_HEADS # 9.37M shared_expert_params = expert_params dense_mlp_params = 3 * H * DENSE_I gate_params = H * N_EXPERTS embed_params = VOCAB * H TOTAL_LAYER_ENTRIES = N_LAYERS + N_MTP # 79 nonexpert = { 'attention': attn_params * TOTAL_LAYER_ENTRIES, 'indexers': indexer_params * N_FULL_INDEXERS, 'dense_mlp': dense_mlp_params * N_DENSE, 'shared_experts': shared_expert_params * N_EXPERT_LAYERS, 'gates_fp32': gate_params * N_EXPERT_LAYERS, # router kept fp32 'embed+lm_head': 2 * embed_params, 'mtp_extras': 2 * H * H + 4 * H, # eh_proj etc } # --- bytes/param for each quant recipe (routed experts) --- def gguf_bpw(down_bpw, gateup_bpw): return (expert_params_down*down_bpw + expert_params_gateup*gateup_bpw) / expert_params RECIPES = { # NVFP4: 4b weight + fp8 scale per 16 = 4.5 bpw (+ negligible per-tensor scales) 'nvfp4': 4.5, # unsloth UD-Q3_K_XL donor: down=IQ4_XS(4.25), gate/up=IQ3_XXS(3.0625) 'ud_q3': gguf_bpw(4.25, 3.0625), # unsloth UD-Q2_K_XL donor: down=IQ3_XXS(3.0625), gate/up=IQ2_XS(2.3125) 'ud_q2': gguf_bpw(3.0625, 2.3125), } def layer_expert_bytes(recipe): return layer_expert_params * RECIPES[recipe] / 8 # --- KV cache at 1M --- kv_latent_per_tok = (KV_LORA + QK_ROPE) * TOTAL_LAYER_ENTRIES # values (MTP has KV too) def kv_bytes(bytes_per_val, scale_overhead=0.02): return kv_latent_per_tok * bytes_per_val * CTX * (1 + scale_overhead) idx_cache = N_FULL_INDEXERS * IDX_HDIM * 1 * CTX # fp8 indexer k-cache # --- GPU budget --- GPU_MEM = 97887 * 2**20 # RTX PRO 6000 Blackwell, nvidia-smi reported N_GPU = 4 RESERVE_PER_GPU = 6 * GiB # cuda ctx + nccl + cudagraphs + activations + frag def report(attn_dtype_bytes=2, n4=0, n3=0, n2=0, kv='fp8'): assert n4+n3+n2 == 75 ne = dict(nonexpert) scale = {'attention': attn_dtype_bytes/2, 'shared_experts': attn_dtype_bytes/2, 'dense_mlp': attn_dtype_bytes/2} ne_bytes = 0 for k, p in ne.items(): b = p * (4 if k=='gates_fp32' else 2) b *= scale.get(k, 1) ne_bytes += b expert_bytes = (n4*layer_expert_bytes('nvfp4') + n3*layer_expert_bytes('ud_q3') + n2*layer_expert_bytes('ud_q2') + layer_expert_bytes('nvfp4')) # +MTP nvfp4 weights = ne_bytes + expert_bytes kvb = kv_bytes(1 if kv=='fp8' else 0.5) + idx_cache total_need = weights + kvb total_have = N_GPU * (GPU_MEM - RESERVE_PER_GPU) print(f'--- attn={16*attn_dtype_bytes//2}bit experts: {n4}xNVFP4 {n3}xUD-Q3 {n2}xUD-Q2 (+MTP nvfp4), KV={kv} ---') print(f' non-expert weights: {ne_bytes/GB:8.2f} GB') print(f' expert weights: {expert_bytes/GB:8.2f} GB (avg {expert_bytes*8/ (N_EXPERT_LAYERS*layer_expert_params):.2f} bpw)') print(f' KV latent @1M: {kv_bytes(1 if kv=="fp8" else 0.5)/GB:8.2f} GB') print(f' indexer cache @1M: {idx_cache/GB:8.2f} GB') print(f' TOTAL NEED: {total_need/GB:8.2f} GB = {total_need/GiB:.1f} GiB') print(f' TOTAL HAVE (4x{GPU_MEM/GiB:.1f}GiB - 4x{RESERVE_PER_GPU/GiB:.0f}GiB): {total_have/GB:.2f} GB = {total_have/GiB:.1f} GiB') margin = total_have - total_need print(f' MARGIN: {margin/GB:+8.2f} GB {"FITS" if margin>0 else "DOES NOT FIT"}') # PP=4 per-stage worst case: stage holding embed+lm_head + its layers return margin print('kv latent values/token:', kv_latent_per_tok, '->', kv_latent_per_tok, 'B/tok fp8,', f'{kv_bytes(1)/GB:.1f} GB @1M fp8') print('nonexpert param count:', sum(nonexpert.values())/1e9, 'B params') print('checkpoint check: nvfp4-all =', (sum(v*2 for v in nonexpert.values()) + 2*nonexpert['gates_fp32'] + 76*layer_expert_bytes('nvfp4'))/GB, 'GB vs actual 450.93') print() print('=== v1: BF16 attention (as shipped in NVFP4 repo) ===') report(2, 10, 30, 35) report(2, 8, 26, 41) report(2, 6, 22, 47) print() print('=== v2: FP8 attention/shared/dense (unsloth uses Q8_0 there too) ===') report(1, 14, 40, 21) report(1, 12, 36, 27) report(1, 10, 30, 35)