# Architecture support

Poros maintains an explicit registry of validated transformer architectures.
An architecture is "validated" only after it passes the 0.00e+00 bitwise
parity gate: resident-vs-blockwise comparison at atol=0, rtol=0 on both
loss and adapter weights.

Parity evidence comes in three tiers, and each family's row below names the
tier it actually has (all committed under
[docs/validation/](validation/README.md)):

1. **Full campaign** — 200-step gates on all four surfaces (loss, weights,
   gradient norms, optimizer state).
2. **Architecture gate** — a bitwise gate over 5–50 optimizer steps
   (parity is structural, so a short gate is strong evidence, but it is
   not the campaign bar; the contributor minimum is 48 steps).
3. **Structural smoke** — load + streamed-load byte-identity only; no
   parity claim.

## Validated architectures

| Family | `model_type` | NF4 parity | Evidence tier | Notes |
|---|---|---|---|---|
| Qwen2.5 | `qwen2` | 0.00e+00 | full campaign (200-step) | 7B–72B validated. Also covers DeepSeek-R1-Distill-Qwen (dense `qwen2`). |
| Qwen3 | `qwen3` | 0.00e+00 | 50-step gate | 32B gated. |
| Qwen3.5 / Qwen3.6 (dense) | `qwen3_5` / `qwen3_5_text` | 0.00e+00 | short gate (5-step) | 0.8B–27B; gated on Qwen3.5-4B. Below the 48-step contributor bar; scheduled for a 48+ step gate. |
| Gemma 3 | `gemma3` / `gemma3_text` | 0.00e+00 | 50-step gate | 27B gated (gemma-3-27b-pt). |
| Gemma 4 (dense) | `gemma4` / `gemma4_text` | 0.00e+00 | 50-step gate | 31B gated (gemma-4-31B-it). Sliding-window + global hybrid attention handled by per-layer kwargs capture/replay. |

Note: Qwen3.6 dense ships `model_type: "qwen3_5"` in its config.json (the
same pattern as Qwen2.5 shipping `qwen2`), so both generations resolve to the
one validated registry entry — there is no separate `qwen3_6` type to add.

## GPU validation matrix

Each architecture's bitwise parity gate (`pytest -m gpu tests/test_gpu_parity.py`,
`torch.equal`) has passed on:

Parity gates per the paper's parity matrix (Table 3). A cell is `PASS` only where
that family's bitwise gate was actually recorded on that GPU; `—` means not gated
there (not a failure).

| GPU (sm) | qwen2 | qwen3 | qwen3_5 | gemma3 | gemma4 | Benchmarks |
|---|---|---|---|---|---|---|
| RTX 5090 (sm_120) | PASS (7B, 32B) | PASS (32B) | — | PASS (27B) | PASS (31B) | — |
| NVIDIA B300 SXM6 (sm_103) | PASS (32B, 72B) | PASS (32B) | — | — | — | [official-b300-results.md](official-b300-results.md) |
| NVIDIA A100 80GB PCIe (sm_80) | PASS (32B) | — | — | — | — | [official-a100-results.md](official-a100-results.md) |
| RTX PRO 6000 (sm_120) | PASS (32B) | — | — | — | — | — |
| RTX 4090 (sm_89) | — | — | PASS (4B) | — | — | — |
| RTX 3090 (sm_86) | PASS (7B) | — | — | — | — | — |

## Torch-stack validation matrix

The same bitwise gate, re-run per PyTorch stack via
`scripts/parity_torch_versions.sh` on A100 80GB PCIe, using the smallest
validated checkpoint per family (Qwen2.5-0.5B, Qwen3-0.6B, gemma-3-1b-pt,
gemma-4-31B-it). Raw rows: [`validation/torch_versions_parity.txt`](validation/torch_versions_parity.txt).

| Stack | qwen2 | qwen3 | gemma3 | gemma4 |
|---|---|---|---|---|
| torch 2.11.0+cu128 | PASS | PASS | PASS | PASS |
| torch 2.12.0+cu130 | PASS | PASS | PASS | PASS |

The full-size checkpoints are gated separately, not by this sweep: Qwen2.5-32B
and Qwen3-32B on the A100/B300/PRO 6000 campaigns, and Gemma3-27B and
Gemma4-31B on the RTX 5090 streamed-load sweep (torch 2.11.0+cu128, 50 steps —
[`validation/streamed-load/sweep/`](validation/streamed-load/sweep/)).

Execution modes (SDPA backend selection, TF32, bf16 autocast, `torch.compile`)
do not change parity either — measured as a resident-vs-blockwise *forward*
gate plus a reproducibility check, not a full training gate; see
[parity-modes.md](parity-modes.md).

Note: `google/gemma-4-12B-it` reports `model_type: gemma4_unified`, which is a
distinct architecture from the validated `gemma4` — the registry correctly
refuses it (`PorosArchNotValidatedError`). It is a roadmap target.

## Architecture coverage targets

| Family | Status | Notes |
|---|---|---|
| Gemma 4 unified (`gemma4_unified`, e.g. gemma-4-12B-it) | Target | Distinct `model_type` from the validated dense `gemma4`; needs its own parity run. |
| Gemma 4 (MoE) | Target | MoE routing under blockwise residency is unvalidated.  Dense first; MoE marked roadmap until measured. |

## Check a model before you train

```bash
poros check Qwen/Qwen2.5-7B          # a Hub id (needs huggingface-hub)
poros check ./my-local-model         # a local dir with config.json
poros check ./my-local-model/config.json
```

`poros check` resolves the `model_type` from `config.json` *without loading the
model or importing torch* and reports its registry status — `validated`,
`user_asserted` (you registered it; runnable, no Poros parity guarantee),
`target` (roadmap), or `not-in-registry` — plus what to do next. Exit code is
`0` only for `validated` and `1` for everything else, including
`user_asserted`, so a CI or agent script can never mistake your own assertion
for a Poros guarantee.

## The architecture registry

The registry is a single source of truth (`_ARCHITECTURES` in
`poros/core/arch_registry.py`). Read it programmatically:

```python
from poros.core.arch_registry import validated_arch_types, architectures

validated_arch_types()   # {"qwen2", "qwen3", "qwen3_5", "qwen3_5_text",
                         #  "gemma3", "gemma3_text", "gemma4", "gemma4_text"}
architectures()          # full {model_type: ArchSpec} map incl. targets
```

There are three ways to extend it, in increasing order of permanence. The
first two create `user_asserted` entries — runnable, but carrying no Poros
parity guarantee. Only the third enters the `validated` set:

```python
import poros

# 1. At runtime, in your own code (no fork needed) -> user_asserted:
poros.register_architecture("phi3", notes="0.00e+00 on my own 200-step run")

# 2. Via environment, for an ops/CI override without code -> user_asserted:
#    POROS_USER_ASSERTED_ARCHS="phi3,starcoder2"

# 3. Permanently -> validated: add ONE line to _ARCHITECTURES, with a parity
#    run and a PR. This is the only path into the first-party evidence class.
```

The `notes=` field is your own record of what you measured; it does not
change the status. `poros check ./my-phi3-model` (it takes a model id or directory, not a bare
model_type) will report `user_asserted` and exit
non-zero after either of the first two.

`VALIDATED_BLOCKWISE_ARCH_TYPES` remains as a back-compat snapshot of the
built-in set. `validated_arch_types()` returns the same first-party set and is
deliberately not affected by runtime registration or the environment, which is
what makes it usable as the basis of a parity guarantee. For "what will Poros
agree to run", use `runnable_arch_types()` (validated + user-asserted); for
what you yourself added, `user_asserted_arch_types()`.

The `blockwise_arch_check` config field controls behavior for unvalidated
architectures:

- `"error"` (default): raise `PorosArchNotValidatedError` naming the
  supported list.  This is the safe default — it prevents training on an
  architecture that is neither validated nor user-asserted, where parity is
  unknown.
- `"warn"`: log a warning and proceed.  Use only for validation runs when
  testing a new architecture.
- `"off"`: skip the check entirely.  Not recommended for production.

## Layer discovery

Poros searches multiple root candidates and layer paths to locate the
transformer block container.  This handles standard layouts (Qwen2,
Qwen3) and nested/vision-language layouts (Gemma3, VLMs).

**Root candidates:**
```
"", "model", "base_model", "base_model.model",
"base_model.model.model", "language_model",
"model.language_model", "language_model.model"
```

**Layer paths:**
```
"model.layers", "language_model.layers", "gpt_neox.layers",
"transformer.h", "model.decoder.layers", "encoder.layer"
```

Plus `get_base_model()` as a dynamic root candidate for PEFT-wrapped models.

## Adding a new architecture

To validate a new architecture family:

1. Set `blockwise_arch_check: "warn"` in your config so the registry gate
   proceeds on the unvalidated family instead of raising.
2. Run a full parity test (48+ steps) and confirm 0.00e+00 max diff on loss
   and adapter weights.
3. If parity holds, add one line to `_ARCHITECTURES` in
   `poros/core/arch_registry.py`:
   ```python
   "phi3": ArchSpec("phi3", "validated", "0.00e+00", "70B validated 2026-..."),
   ```
   (For a private/local validation you don't want to upstream, call
   `poros.register_architecture("phi3", notes="parity 0.00e+00 on my run")`
   at runtime, or set `POROS_USER_ASSERTED_ARCHS` — no fork required. Both
   create **`user_asserted`** entries: Poros will run the architecture, but
   the result carries no Poros parity guarantee and `prepare()` reports
   `probed_not_validated`. `validated` stays a first-party evidence class
   backed by a committed Caistro artifact, and step 3 above — a registry line
   plus a PR — is the only way in.)
4. Add regression tests for the new family.
5. Submit a PR with the parity test results.

## Precision support

Precisions are registered the same way, in `poros/core/precision.py`
(`_PRECISIONS`). Only `nf4` is `stable` and carries the bitwise-parity
guarantee in the validated matrix; `bf16`/`fp16` are `experimental` with no
parity claim; the separate `bf16_bitwise` runtime is also `experimental` but
IS bitwise-gated (0.5B and 32B on A100), unoptimized and outside the official
matrix; `int8` is `planned`. `poros schema config` and `selectable_precisions()` reflect the
registry, so adding int8 or a LoRA-specific precision in v2 is a one-line
change here plus a load path — no scattering of string literals across the
trainer, model loader, and config.

## Design principle

The block manager owns residency only: load, stream, offload, and gradient
routing.  It does NOT reconstruct per-layer forward behavior (masks, rotary
embeddings, attention dispatch).  Those are pulled from the model's own
modules per forward pass.  This ensures any HF architecture's native
behavior is preserved and prevents per-architecture mask/attention bugs.
