{
  "site": "https://www.neurarch.com",
  "description": "Every structural check Neurarch runs on a model graph. The same checks run in the editor, in CI through the GitHub Action, and over the wire at POST /api/v1/check.",
  "count": 50,
  "verify": "https://www.neurarch.com/api/v1/check",
  "catalogue": "https://www.neurarch.com/rules.html",
  "checks": [
    {
      "id": "G01",
      "title": "Impact preview",
      "severity": "warn",
      "category": "blast radius",
      "engineId": "impact",
      "page": "https://www.neurarch.com/r/impact-preview",
      "provenance": null,
      "detail": {
        "trigger": "Any destructive action (delete_component, delete_components_matching, rename_components_matching, clear_canvas, replace_model) where downstream impact > 4 layers, or any of them are shape-changing.",
        "surfaces": "Number of downstream layers affected, how many would reshape, how many carry weights (rebuild required).",
        "override": "User confirms in the impact-preview card before the action lands."
      }
    },
    {
      "id": "G02",
      "title": "Param explosion",
      "severity": "block",
      "category": "resource",
      "engineId": "param-explosion",
      "page": "https://www.neurarch.com/r/param-explosion",
      "provenance": null,
      "detail": {
        "trigger": "Estimated total parameter count grows by ≥ threshold × baseline (default 5×, slider 2-20×). Blocks at 2× threshold, warns below.",
        "why": "A misread \"scale this 10×\" prompt should not silently turn a 60M model into a 6B model that won't fit anywhere.",
        "setting": "useProviderStore.paramExplosionThreshold(). Settings → \"Param explosion threshold\"."
      }
    },
    {
      "id": "G03",
      "title": "Cycle introduction",
      "severity": "block",
      "category": "structure",
      "engineId": "cycle",
      "page": "https://www.neurarch.com/r/cycle-introduction",
      "provenance": null,
      "detail": {
        "trigger": "An add_connection closes a cycle in the existing DAG. DFS check on the union of current edges and proposed edges.",
        "why": "NN forward graphs are acyclic. Recurrence lives inside the layer type (LSTM, GRU), not as a graph cycle. A connection that closes a cycle is almost always a mis-step.",
        "source": "DAG requirement matches PyTorch nn.Module.forward semantics. torch.autograd docs note that gradient computation requires a DAG."
      }
    },
    {
      "id": "G04",
      "title": "Orphan layers",
      "severity": "warn",
      "category": "structure",
      "engineId": "orphan",
      "page": "https://www.neurarch.com/r/orphan-layers",
      "provenance": null,
      "detail": {
        "trigger": "add_component without afterName, and no follow-up add_connection referencing the new component as either endpoint.",
        "why": "A new layer that's not wired is dead code. The agent occasionally proposes these when it forgets to chain edits; the warning flips the decision back to the user."
      }
    },
    {
      "id": "G05",
      "title": "Shape inference (new)",
      "severity": "block",
      "category": "shape",
      "engineId": "shape",
      "page": "https://www.neurarch.com/r/shape-inference-new",
      "provenance": "head-dim-divisibility",
      "detail": {
        "trigger": "Propagates tensor shapes through the sandboxed post-action graph and surfaces only the issues the action would introduce. Sub-checks: attention embedDim % numHeads, GQA numHeads % numKVHeads, elementwise merge parent equality, concat axis compatibility, explicit linear inFeatures vs upstream, computeOutputShape throw, and outputs with NaN / 0 / negative dims at the first layer to introduce them.",
        "source": "Per-layer transforms from componentRegistry.computeOutputShape. Head-dim convention from Vaswani et al. 2017 (Attention Is All You Need). GQA ratio from Ainslie et al. 2023 (GQA).",
        "why": "Text-layer review tools (Cursor, Copilot) can't catch embedDim=384, numHeads=5 until the GPU rejects the kernel. This gate fires sub-millisecond, pre-apply."
      }
    },
    {
      "id": "G06",
      "title": "Param range",
      "severity": "warn",
      "category": "param-range",
      "engineId": null,
      "page": "https://www.neurarch.com/r/param-range",
      "provenance": null,
      "detail": {
        "trigger": "A single parameter value falls outside its conventional range on add_component or update_params: e.g. dropout above 1, stride of 0, numHeads of 0. Uses the same convention-based ranges the inspector shows inline.",
        "why": "These are values the shape gate cannot see: the graph still propagates, the model still builds, and the run is quietly wrong. A dropout of 1.2 is not a shape error, it is a silently dead layer.",
        "source": "src/utils/paramConstraints.ts::validateParamValue, shared with the inspector so CI and the canvas never disagree."
      }
    },
    {
      "id": "R01",
      "title": "No Input node",
      "severity": "block",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/no-input-node",
      "provenance": null,
      "detail": {
        "trigger": "Model has ≥ 1 component but no component of type input.",
        "why": "Without an Input layer, tensor shapes can't be propagated and generated code is incomplete.",
        "fix": "Drag an Input layer from the I/O section of the palette."
      }
    },
    {
      "id": "R02",
      "title": "No Output node",
      "severity": "warn",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/no-output-node",
      "provenance": null,
      "detail": {
        "trigger": "Model has > 1 component but no component of type output.",
        "why": "Code generator can't tell where the forward pass terminates."
      }
    },
    {
      "id": "R03",
      "title": "Isolated components",
      "severity": "warn",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/isolated-components",
      "provenance": null,
      "detail": {
        "trigger": "Component has no inbound and no outbound connections.",
        "why": "Most often a stranded layer left over from a refactor. Code generator skips it but it inflates the visual graph."
      }
    },
    {
      "id": "R04",
      "title": "Dead-end layer",
      "severity": "warn",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/dead-end-layer",
      "provenance": null,
      "detail": {
        "trigger": "A non-output node has inbound connections but no outbound connections.",
        "why": "Compute happens but the result is discarded. Gradients flow into a black hole."
      }
    },
    {
      "id": "R05",
      "title": "BatchNorm / LayerNorm after activation",
      "severity": "warn",
      "category": "ordering",
      "engineId": null,
      "page": "https://www.neurarch.com/r/batchnorm-layernorm-after-activation",
      "provenance": null,
      "detail": {
        "trigger": "A normalization layer is connected directly downstream of an activation (relu, gelu, swish, etc.).",
        "why": "Normalization is meant to stabilize the pre-activation distribution. Applying it after activation breaks the assumption and shifts already-nonlinear features back toward zero mean.",
        "source": "Ioffe & Szegedy 2015, BatchNorm places BN between Linear/Conv and the activation."
      }
    },
    {
      "id": "R06",
      "title": "Dropout directly before BatchNorm",
      "severity": "info",
      "category": "ordering",
      "engineId": null,
      "page": "https://www.neurarch.com/r/dropout-directly-before-batchnorm",
      "provenance": null,
      "detail": {
        "trigger": "A dropout layer is connected directly upstream of a BatchNorm.",
        "why": "Dropout at train time changes the activation variance; BN's running statistics get distorted. A known train-vs-eval mismatch.",
        "source": "Li et al. 2018, \"Understanding the Disharmony Between Dropout and Batch Normalization\"."
      }
    },
    {
      "id": "R07",
      "title": "Softmax / Sigmoid directly before Output",
      "severity": "info",
      "category": "ordering",
      "engineId": null,
      "page": "https://www.neurarch.com/r/softmax-sigmoid-directly-before-output",
      "provenance": null,
      "detail": {
        "trigger": "An explicit Softmax or Sigmoid layer is wired directly into Output.",
        "why": "PyTorch's nn.CrossEntropyLoss applies LogSoftmax internally; an explicit Softmax before it double-applies and slows training. BCEWithLogitsLoss has the same issue with Sigmoid.",
        "source": "torch.nn.CrossEntropyLoss docs."
      }
    },
    {
      "id": "R08",
      "title": "Normalization at output",
      "severity": "warn",
      "category": "ordering",
      "engineId": null,
      "page": "https://www.neurarch.com/r/normalization-at-output",
      "provenance": null,
      "detail": {
        "trigger": "A normalization layer is wired directly into Output without a final linear / classification head.",
        "why": "Normalization zero-centers the logits, breaking calibration. The model's output scale is no longer meaningful."
      }
    },
    {
      "id": "R09",
      "title": "Deep network with no residual connections",
      "severity": "warn",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/deep-network-with-no-residual-connections",
      "provenance": null,
      "detail": {
        "trigger": "≥ 8 weight-carrying layers (conv2d / linear / etc.) and zero residual / skip / add nodes.",
        "why": "Gradient signal degrades through depth without skip connections.",
        "source": "He et al. 2015, ResNet."
      }
    },
    {
      "id": "R10",
      "title": "Attention without positional encoding",
      "severity": "warn",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/attention-without-positional-encoding",
      "provenance": null,
      "detail": {
        "trigger": "Model contains attention layers but no positional encoding (sinusoidal, learned, RoPE, ALiBi).",
        "why": "Self-attention is permutation-invariant. Without PE, the model is a bag-of-words and can't learn order.",
        "source": "Vaswani et al. 2017, Attention Is All You Need, Section 3.5."
      }
    },
    {
      "id": "R11",
      "title": "Sigmoid / Tanh in deep networks",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/sigmoid-tanh-in-deep-networks",
      "provenance": null,
      "detail": {
        "trigger": "Sigmoid or Tanh activation appears in a network with ≥ 5 weight-carrying layers.",
        "why": "Saturating activations vanish gradients in deep stacks. ReLU / GELU / SiLU are the modern defaults.",
        "source": "Glorot & Bengio 2010 on the vanishing gradient problem."
      }
    },
    {
      "id": "R12",
      "title": "Deep network with no normalization",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/deep-network-with-no-normalization",
      "provenance": null,
      "detail": {
        "trigger": "≥ 7 non-I/O layers and zero normalization layers (BN / LN / IN / GN / RMSNorm).",
        "why": "Without normalization, deep nets converge slowly and are sensitive to initialization scale."
      }
    },
    {
      "id": "R13",
      "title": "Very high dropout rate",
      "severity": "warn",
      "category": "performance",
      "engineId": null,
      "page": "https://www.neurarch.com/r/very-high-dropout-rate",
      "provenance": null,
      "detail": {
        "trigger": "Any dropout layer with p > 0.65.",
        "why": "Common dropout rates are 0.1 to 0.5. Anything above 0.65 usually indicates a typo or a confused regularization strategy."
      }
    },
    {
      "id": "R14",
      "title": "Very large activation tensor",
      "severity": "warn",
      "category": "performance",
      "engineId": null,
      "page": "https://www.neurarch.com/r/very-large-activation-tensor",
      "provenance": null,
      "detail": {
        "trigger": "Any layer's estimated activation tensor exceeds 50 M elements (~200 MB per sample at float32).",
        "why": "Activations live in GPU memory through the backward pass. One bloated layer forces tiny batch sizes or OOM."
      }
    },
    {
      "id": "R15",
      "title": "MoE without auxiliary loss",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/moe-without-auxiliary-loss",
      "provenance": null,
      "detail": {
        "trigger": "Model contains an MoE layer but no auxiliary load-balance loss is referenced.",
        "why": "Without an aux loss, experts collapse into a few dominant ones. Most papers add a 0.01 weight router-balance term.",
        "source": "Shazeer et al. 2017, Sparsely-Gated MoE, Section 4."
      }
    },
    {
      "id": "R16",
      "title": "GQA numHeads not divisible by numKVHeads",
      "severity": "block",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/gqa-numheads-not-divisible-by-numkvheads",
      "provenance": "gqa-head-divisibility",
      "detail": {
        "trigger": "A groupedQueryAttention component has params where numHeads % numKVHeads != 0.",
        "why": "GQA groups query heads; the group size must divide the head count. Mis-set ratios crash on the first attention forward.",
        "source": "Ainslie et al. 2023, GQA."
      }
    },
    {
      "id": "R17",
      "title": "SwiGLU intermediateSize convention",
      "severity": "info",
      "category": "performance",
      "engineId": null,
      "page": "https://www.neurarch.com/r/swiglu-intermediatesize-convention",
      "provenance": null,
      "detail": {
        "trigger": "A swiGLU / gated FFN has intermediateSize that doesn't follow the common ~(2/3) × 4 × hidden convention used in LLaMA / Mistral.",
        "why": "Param-count parity with standard FFN; mis-sized SwiGLU silently changes the FLOP and param budget.",
        "source": "Shazeer 2020, GLU Variants."
      }
    },
    {
      "id": "R18",
      "title": "Conv feeds Linear with no flatten / pool",
      "severity": "block",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/conv-feeds-linear-with-no-flatten-pool",
      "provenance": null,
      "detail": {
        "trigger": "A convolution (conv1d/2d/3d or a depthwise/separable/transpose variant) connects directly into a linear layer.",
        "why": "Conv outputs a multi-dimensional feature map; Linear expects a flat [batch, features] tensor. The forward pass raises a shape error, or silently mis-multiplies the spatial dims. Insert a Flatten or a Global Average Pool between them.",
        "source": "Standard CNN classifier construction (e.g. LeNet / AlexNet head)."
      }
    },
    {
      "id": "R19",
      "title": "Back-to-back activations",
      "severity": "warn",
      "category": "ordering",
      "engineId": null,
      "page": "https://www.neurarch.com/r/back-to-back-activations",
      "provenance": null,
      "detail": {
        "trigger": "An activation node (relu/gelu/silu/sigmoid/tanh/softmax/…) connects directly into another activation node with no Linear/Conv/Norm between them.",
        "why": "Two activations in a row add compute but no expressivity (same-type is a no-op duplicate). A common slip is ReLU → Softmax, which clips logits to ≥ 0 and distorts the output distribution.",
        "source": "Composition of pointwise non-linearities adds no representational power without an intervening affine map (universal-approximation premise)."
      }
    },
    {
      "id": "R20",
      "title": "Linear → Linear with no activation",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/linear-linear-with-no-activation",
      "provenance": null,
      "detail": {
        "trigger": "A linear layer connects directly into another linear layer with no activation between them.",
        "why": "Two stacked linear maps collapse into one (W₂·W₁), so the extra layer costs parameters but adds no representational power, usually a forgotten ReLU. Deliberate low-rank / factorized projections (down-proj → up-proj) are the safe exception.",
        "source": "Linear-map composition closure; the rank of the product cannot exceed the smaller intermediate dimension."
      }
    },
    {
      "id": "R21",
      "title": "Dropout immediately before Output",
      "severity": "warn",
      "category": "ordering",
      "engineId": null,
      "page": "https://www.neurarch.com/r/dropout-immediately-before-output",
      "provenance": null,
      "detail": {
        "trigger": "A dropout layer is the last node before the output node.",
        "why": "In training this randomly zeroes the final logits fed to the loss; at eval it is a no-op, so train and eval behaviour diverge. Dropout belongs before the final projection (Linear/Conv), not after it.",
        "source": "Srivastava et al. 2014 (Dropout): dropout regularizes hidden representations, not output logits."
      }
    },
    {
      "id": "R22",
      "title": "Conv stride larger than kernel",
      "severity": "warn",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/conv-stride-larger-than-kernel",
      "provenance": null,
      "detail": {
        "trigger": "A downsampling convolution (conv1d/2d/3d, depthwise, or separable) has stride > kernelSize.",
        "why": "The kernel jumps past its own footprint each step, so a band of the input is never read, a silent loss of information. Non-overlapping patches use stride == kernel (e.g. ViT 16/16); stride > kernel is almost always a typo.",
        "source": "Convolution arithmetic: Dumoulin & Visin 2016."
      }
    },
    {
      "id": "R23",
      "title": "Non-spatial tensor into Conv",
      "severity": "warn",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/non-spatial-tensor-into-conv",
      "provenance": null,
      "detail": {
        "trigger": "A flatten or linear layer connects directly into a (non-transpose) convolution.",
        "why": "Mirror of R18: a Conv needs a [channels, …spatial] feature map, but Flatten / Linear emit a flat [batch, features] vector. The forward pass raises a shape error unless the spatial dims are rebuilt with a Reshape / Unflatten first.",
        "source": "PyTorch nn.Conv*d input-shape contract."
      }
    },
    {
      "id": "R24",
      "title": "Back-to-back normalization",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/back-to-back-normalization",
      "provenance": null,
      "detail": {
        "trigger": "A normalization layer connects directly into another normalization layer (e.g. LayerNorm → BatchNorm).",
        "why": "Normalizing an already-normalized tensor is redundant; the second layer mostly re-centres/re-scales what the first produced and just burns its own learnable parameters.",
        "source": "Idempotence of standardization: Ioffe & Szegedy 2015."
      }
    },
    {
      "id": "R25",
      "title": "Duplicate positional encoding",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/duplicate-positional-encoding",
      "provenance": null,
      "detail": {
        "trigger": "More than one positional-encoding layer (positionalEncoding, learnedPositionalEmbedding, rope, alibi) in the model.",
        "why": "Position is normally injected once. Stacking absolute + rotary, or two of the same, double-counts position and tends to hurt more than help.",
        "source": "Positional-encoding design: Vaswani et al. 2017, RoPE (Su et al. 2021)."
      }
    },
    {
      "id": "R26",
      "title": "Pooling feeds Linear with no flatten",
      "severity": "warn",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/pooling-feeds-linear-with-no-flatten",
      "provenance": null,
      "detail": {
        "trigger": "A spatial pooling layer (maxpool, avgpool, adaptive pool) connects directly into a linear layer. Global pools are exempt: they already collapse spatial dims.",
        "why": "Sibling of R18: pooling still emits a [channels, …spatial] map, but Linear expects a flat [batch, features] tensor, so the forward pass raises a shape error. Insert a Flatten or a Global Average Pool first.",
        "source": "Standard CNN classifier head construction."
      }
    },
    {
      "id": "R27",
      "title": "Flatten before attention",
      "severity": "warn",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/flatten-before-attention",
      "provenance": null,
      "detail": {
        "trigger": "A flatten layer connects directly into an attention layer.",
        "why": "Flatten collapses the sequence dimension into one long vector, leaving a length-1 sequence, so attention has nothing to relate. Keep the [sequence, dim] layout and flatten only after the attention stack.",
        "source": "Attention operates over a sequence axis: Vaswani et al. 2017."
      }
    },
    {
      "id": "R28",
      "title": "ConvTranspose checkerboard risk",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/convtranspose-checkerboard-risk",
      "provenance": null,
      "detail": {
        "trigger": "A ConvTranspose2d has kernelSize not divisible by stride (with stride > 1).",
        "why": "Uneven kernel overlap during upsampling deposits more weight on some output pixels than others, producing checkerboard artifacts. Make kernelSize a multiple of stride, or upsample with Upsample + Conv (resize-convolution).",
        "source": "Odena et al. 2016, Deconvolution and Checkerboard Artifacts."
      }
    },
    {
      "id": "R29",
      "title": "GroupNorm channels not divisible by numGroups",
      "severity": "block",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/groupnorm-channels-not-divisible-by-numgroups",
      "provenance": "compute-error",
      "detail": {
        "trigger": "A GroupNorm layer's channel count is not an exact multiple of numGroups.",
        "why": "GroupNorm splits channels into equal groups; a non-divisible count raises at construction. Parallel to the GQA / attention head-dim divisibility checks. Set numGroups to a divisor of the channel count.",
        "source": "Wu & He 2018, Group Normalization."
      }
    },
    {
      "id": "R30",
      "title": "Very large Linear layer",
      "severity": "warn",
      "category": "performance",
      "engineId": null,
      "page": "https://www.neurarch.com/r/very-large-linear-layer",
      "provenance": null,
      "detail": {
        "trigger": "A single Linear exceeds ~1B parameters (inFeatures × outFeatures).",
        "why": "A dense layer that large (~4 GB float32) almost always means a feature map was flattened without pooling first. Add a Global Average Pool / more downsampling, or factorize the layer. Embedding and vocab-projection heads are the expected exception.",
        "source": "Parameter-budget hygiene; a single matrix this size dominates model memory."
      }
    },
    {
      "id": "R31",
      "title": "Full multi-head attention at LLM scale",
      "severity": "info",
      "category": "performance",
      "engineId": null,
      "page": "https://www.neurarch.com/r/full-multi-head-attention-at-llm-scale",
      "provenance": null,
      "detail": {
        "trigger": "A transformer stack (≥6 attention layers) at LLM width (embedDim ≥ 2048) uses full multi-head attention everywhere, with no grouped-query or latent attention present.",
        "why": "At real model scale the KV cache, not the weights, dominates serving memory. Full per-head K/V caching is what production LLMs move away from: grouped-query attention (e.g. 8:1) cuts the cache ~8×, and multi-head latent attention (MLA) shrinks it ~10× or more, without changing the parameter count. Advice, not a bug: set numKVHeads below numHeads, or switch to mla.",
        "source": "Serving-cost roofline; the same KV-cache math the app and the public serving calculator run."
      }
    },
    {
      "id": "R32",
      "title": "Default init assumes ReLU, saturating activation follows",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/default-init-assumes-relu-saturating-activation-follows",
      "provenance": null,
      "detail": {
        "trigger": "A Linear/Conv layer feeds a sigmoid or tanh activation directly.",
        "why": "PyTorch's default Kaiming (He) init is derived for ReLU-family activations. Feeding a saturating activation from a He-initialized layer starts training in the saturated tails and shrinks early gradients. Use Xavier init with the matching gain, or a ReLU-family activation.",
        "source": "Glorot & Bengio 2010 (Xavier) vs He et al. 2015 (Kaiming) derivation assumptions."
      }
    },
    {
      "id": "R33",
      "title": "Deep attention stack without depth-scaled init",
      "severity": "info",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/deep-attention-stack-without-depth-scaled-init",
      "provenance": null,
      "detail": {
        "trigger": "≥ 8 attention layers stacked in one model.",
        "why": "Residual-branch outputs add up with depth; unscaled init lets activation variance grow layer over layer. GPT-2/LLaMA-family models scale residual output projections by depth: N(0, 0.02 / &radic;(2L)).",
        "source": "GPT-2 (Radford et al. 2019) init convention; carried forward by LLaMA/Mistral."
      }
    },
    {
      "id": "R34",
      "title": "LM head width disagrees with embedding vocab",
      "severity": "info",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/lm-head-width-disagrees-with-embedding-vocab",
      "provenance": null,
      "detail": {
        "trigger": "The model has an embedding with a numeric vocabSize and attention layers, but the final Linear feeding the Output projects to a different width.",
        "why": "A language model's head must project back to vocab size (and is usually weight-tied to the embedding); a mismatch means the model cannot emit token logits. A classifier head over N classes is the expected exception, which is why this is info, not error.",
        "source": "Weight-tying convention (Press & Wolf 2017); standard LM head contract."
      }
    },
    {
      "id": "R35",
      "title": "KV cache exceeds serving budget at reference context",
      "severity": "warn",
      "category": "performance",
      "engineId": null,
      "page": "https://www.neurarch.com/r/kv-cache-exceeds-serving-budget-at-reference-context",
      "provenance": null,
      "detail": {
        "trigger": "Total fp16 K/V cache across all attention layers (GQA-aware: counts numKVHeads, skips MLA) exceeds 4 GB for a single 8,192-token sequence.",
        "why": "That much cache for ONE sequence is gone before weights or activations load; on a 24 GB GPU it caps concurrency at a handful of requests. Raise the GQA ratio, switch to MLA, reduce depth/width, or accept a shorter serving context.",
        "source": "Same per-token KV math as R31 and the serving calculator, applied as an absolute budget instead of a pattern."
      }
    },
    {
      "id": "R36",
      "title": "Predictive branch with no stop-gradient on the target",
      "severity": "warn",
      "category": "pattern",
      "engineId": null,
      "page": "https://www.neurarch.com/r/predictive-branch-with-no-stop-gradient-on-the-target",
      "provenance": null,
      "detail": {
        "trigger": "Model contains a JEPA predictor but no emaTarget node and no branch carrying stopGradient.",
        "why": "Joint-embedding predictive objectives have exactly one degenerate solution: emit a constant. The momentum target that carries no gradient is the only thing ruling it out, so a gradient-carrying target is not a slower design, it is one whose loss reaches zero without learning anything.",
        "source": "Grill et al. 2020, BYOL and Chen & He 2020, SimSiam, Section 4."
      }
    },
    {
      "id": "R37",
      "title": "Matryoshka prefix wider than the embedding",
      "severity": "block",
      "category": "structure",
      "engineId": null,
      "page": "https://www.neurarch.com/r/matryoshka-prefix-wider-than-the-embedding",
      "provenance": null,
      "detail": {
        "trigger": "A matryoshkaHead lists a nestedDims entry greater than its own embedDim.",
        "why": "The head's entire contract is that embedding[:d] is usable on its own for each listed d. A d larger than the width names a prefix that does not exist, so a caller who truncates to it silently gets the full width instead and indexes at a size nobody chose. There is no reading of the graph under which a prefix longer than the whole was meant.",
        "source": "Kusupati et al. 2022, Matryoshka Representation Learning."
      }
    },
    {
      "id": "C01",
      "title": "Vocabulary resized, a token-indexed tensor was not",
      "severity": "block",
      "category": "edit",
      "engineId": "vocab-resize-incomplete",
      "page": "https://www.neurarch.com/r/vocabulary-resized-a-token-indexed-tensor-was-not",
      "provenance": null,
      "detail": {
        "trigger": "Compared with the base, the input embedding (or vocab_size in config.json) changed size, and another tensor with the old vocabulary as its row count kept it: a per-layer embedding (embed_tokens_per_layer, vocab_size_per_layer_input), an output head, or a second copy of vocab_size in a nested text_config.",
        "why": "New token ids index past the end of every tensor that kept the old size. The resize and the save both succeed and nothing raises, so an agent that iterates on errors has nothing to iterate on.",
        "seen_in": "transformers#45276 (Gemma 4 resize_token_embeddings skips embed_tokens_per_layer and the output embeddings), transformers#39921 (Gemma 3n)."
      }
    },
    {
      "id": "C02",
      "title": "Weight tying lost in the edit",
      "severity": "warn",
      "category": "edit",
      "engineId": "weight-tying-lost",
      "page": "https://www.neurarch.com/r/weight-tying-lost-in-the-edit",
      "provenance": null,
      "detail": {
        "trigger": "The base ties the output head to the input embedding (tie_word_embeddings: true, or a head with tied) and the changed model sets it false, drops the key, or keeps the tie on a head whose shape no longer matches the embedding.",
        "why": "An untied head is a second vocab × hidden matrix that drifts from the embedding from the first step. When the flag is dropped on save, the reload decides by class default and comes back untied with no error.",
        "seen_in": "transformers#38160 (tie_word_embeddings not saved on a customised model), peft#2944."
      }
    },
    {
      "id": "C03",
      "title": "Attention config keys that did not move together",
      "severity": "warn",
      "category": "edit",
      "engineId": "attention-config-drift",
      "page": "https://www.neurarch.com/r/attention-config-keys-that-did-not-move-together",
      "provenance": null,
      "detail": {
        "trigger": "In config.json: head_dim equalled hidden_size / num_attention_heads in the base and stayed put while one side moved; or num_attention_heads changed and num_key_value_heads did not, which silently changes the query-group size (or turns multi-head attention into grouped-query attention). A change that breaks divisibility is reported under G05 / R16 instead.",
        "why": "An explicit head_dim is honoured as written, so the projections build at a width nobody chose and the model runs. Divisibility failures crash; these do not."
      }
    },
    {
      "id": "C04",
      "title": "Context length moved without RoPE scaling",
      "severity": "warn",
      "category": "edit",
      "engineId": "rope-context-drift",
      "page": "https://www.neurarch.com/r/context-length-moved-without-rope-scaling",
      "provenance": null,
      "detail": {
        "trigger": "In config.json: max_position_embeddings was raised and the RoPE scaling factor (rope_scaling or rope_parameters) did not change, the factor changed and the length did not, or both moved by different ratios.",
        "why": "Positions past the trained range are extrapolated rather than interpolated, or the extension is encoded but never used because runtimes size the window from max_position_embeddings.",
        "seen_in": "Qwen3-30B-A3B discussion #7 (rope keys for vLLM)."
      }
    },
    {
      "id": "C05",
      "title": "LoRA target_modules match nothing",
      "severity": "block",
      "category": "adapter",
      "engineId": "lora-target-no-match",
      "page": "https://www.neurarch.com/r/lora-target-modules-match-nothing",
      "provenance": null,
      "detail": {
        "trigger": "The change spec&#39;s adapter.targets (PEFT target_modules: a list, a regular expression, or all-linear) matches no Linear, Embedding or Conv module in the graph, matched the way PEFT matches: full name equal to the entry or ending in .entry.",
        "why": "An adapter with nothing to attach to trains zero parameters. PEFT raises on the first adapter; a second adapter on the same model is accepted silently.",
        "seen_in": "peft#3533 (inject_adapter silently accepts a second adapter whose target_modules match nothing)."
      }
    },
    {
      "id": "C06",
      "title": "LoRA targets reach only part of the model",
      "severity": "warn",
      "category": "adapter",
      "engineId": "lora-target-partial",
      "page": "https://www.neurarch.com/r/lora-targets-reach-only-part-of-the-model",
      "provenance": null,
      "detail": {
        "trigger": "Some entries of target_modules match nothing while others match, or an attention projection is matched in some layers and missed in the rest.",
        "why": "PEFT only errors when every entry misses. A typo or another architecture&#39;s module name in a list of five trains on the other four without a word.",
        "seen_in": "peft#3533."
      }
    },
    {
      "id": "C07",
      "title": "LoRA target is not a Linear",
      "severity": "warn",
      "category": "adapter",
      "engineId": "lora-target-wrapper",
      "page": "https://www.neurarch.com/r/lora-target-is-not-a-linear",
      "provenance": null,
      "detail": {
        "trigger": "A target_modules entry matches a module PEFT cannot inject into: a class that holds a Linear without being one, a whole attention block, a norm.",
        "why": "PEFT supports nn.Linear, nn.Embedding, nn.Conv1d/2d/3d and transformers Conv1D. A wrapper is refused or skipped; target the Linear inside it by its own name.",
        "seen_in": "peft#3129 (Gemma4ClippableLinear inherits from nn.Module, not nn.Linear)."
      }
    }
  ]
}
