{"record":{"id":"f840d55b6dfee740","repo":"sgl-project/sglang","slug":"n-q-n-k-must-be-one-of-valid-n-got-n-q-n-q-n","errorCode":null,"errorMessage":"n_q/n_k must be one of {VALID_N}, got n_q={n_q}, n_k={n_k}","messagePattern":"n_q/n_k must be one of (.+?), got n_q=(.+?), n_k=(.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/multimodal_gen/runtime/layers/attention/backends/subblock_sparse/router.py","lineNumber":184,"sourceCode":"class SubBlockRouter:\n    \"\"\"Builds ``q2k_block_index`` from sub-block-pooled Q/K.\n\n    Args:\n        n_k: key sub-blocks per 64-token block (1, 2, 4 or 8). 1 reproduces plain avg\n            pooling; 4 is the quality/cost point the recall table above lands on.\n        n_q: query sub-blocks, same values. Splitting Q *alone* (n_q>1 with n_k=1) is\n            worse than not splitting; splitting both sides together is what the default\n            does. Costs n_q times the score matrix, 0.5% of the denoise time.\n\n    Structural block reservation (an attention sink, or forcing the diagonal j == i) was\n    measured on 200 real H3 attention cells and is deliberately absent: at a fixed budget\n    the diagonal changed relative L2 by 0.2% and the sink only helped in DiT layers 2-32,\n    which did not survive to the pixels.\n    \"\"\"\n\n    def __init__(self, n_k: int = 4, n_q: int = 4) -> None:\n        if n_k not in VALID_N or n_q not in VALID_N:\n            raise ValueError(\n                f\"n_q/n_k must be one of {VALID_N}, got n_q={n_q}, n_k={n_k}\"\n            )\n        self.n_k, self.n_q = n_k, n_q\n\n    @torch.no_grad()\n    def scores(\n        self, q: torch.Tensor, k: torch.Tensor, softmax_scale: float\n    ) -> torch.Tensor:\n        \"\"\"``[B, S, H, D] -> [B, H, Gq, Gk]`` block scores (log-space, higher = keep).\n\n        Two Triton kernels: pool, then GEMM + segmented log-sum-exp in registers, so the\n        ``[B, H, Gq*n_q, Gk*n_k]`` intermediate never reaches memory.\n\n        ``softmax_scale * log2(e)`` is folded into Q so the kernel can use the exp2/log2\n        hardware instructions; it multiplies by ln 2 on the way out, so scores come back\n        in natural-log units. Selection is a top-k and any monotone rescale leaves that\n        alone, so the units only matter to a reader of the magnitudes.\n","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/multimodal_gen/runtime/layers/attention/backends/subblock_sparse/router.py#L166-L202","documentation":"The sub-block sparse attention router partitions each head into n_k key blocks and n_q query blocks, and only a fixed set VALID_N of block counts is supported (typically {1,2,4,8}). The constructor validates both n_k and n_q against VALID_N and raises ValueError otherwise.","triggerScenarios":"Constructing the router with block counts outside VALID_N, e.g. SubblockSparseRouter(n_k=3, n_q=4), n_k=6, or 0; even one invalid value triggers the raise.","commonSituations":"Tuning sparsity hyperparameters with values like 3 or 6 that don't match power-of-two block grids; copying configs from papers/repo forks with different VALID_N sets; dividing head_dim by a granularity not supported after a version change.","solutions":["Set both n_q and n_k to values in VALID_N (check the module constant; usually powers of two like 1, 2, 4, 8).","Read VALID_N directly: `from sglang.multimodal_gen.runtime.layers.attention.backends.subblock_sparse.router import VALID_N; print(VALID_N)` and pick from it.","If you need an unsupported granularity, extend VALID_N in a fork after verifying the partition math in scores() supports it."],"exampleFix":"# before\nrouter = SubblockSparseRouter(n_k=3, n_q=6)  # not in VALID_N\n\n# after\nfrom ...subblock_sparse.router import VALID_N  # e.g. {1, 2, 4, 8}\nrouter = SubblockSparseRouter(n_k=4, n_q=4)","handlingStrategy":"validation","validationCode":"from sglang.multimodal_gen.runtime.layers.attention.backends.subblock_sparse.router import VALID_N\nassert n_q in VALID_N and n_k in VALID_N, f\"n_q/n_k must be in {VALID_N}\"\nrouter = SubblockSparseRouter(n_k=n_k, n_q=n_q)","typeGuard":"def is_valid_n(n: int) -> bool:\n    return n in VALID_N","tryCatchPattern":null,"preventionTips":["Import VALID_N from the module instead of hardcoding the allowed set in configs.","Prefer power-of-two block counts when tuning sparsity.","Add a config schema check that validates router hyperparameters at load time."],"tags":["hyperparameter","validation","sparse-attention","constructor"],"backgroundTag":"value-not-in-allowed-set","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}