# `poros.prepare` — the one-line drop-in

One call that makes an existing HF model train blockwise:

```python
import poros
model = poros.prepare(model)        # patched in place; returns the same object
...                                  # use your normal HF Trainer / loop
poros.unprepare(model)               # restores the model (see below)
```

`unprepare` restores the model itself: original layers swapped back,
offloaded weights reloaded, `use_cache` and gradient-checkpointing toggles
reverted. One thing stays: when `prepare` ran with determinism on, the
process-global deterministic-algorithms / math-SDPA settings remain in
effect (a `CUBLAS_WORKSPACE_CONFIG` change after cuBLAS initializes would
not apply anyway). Call `torch.use_deterministic_algorithms(False)` if you
need the fast paths back in the same process.

`prepare` wraps the engine-level `patch_hf_model` and adds: precision
detection, an adapter safety gate, architecture validation, and a
structured report. It never touches your training config and never predicts
VRAM. Importing it needs no torch; calling it does.

## What `prepare` decides for you

| Decision | Default behavior | Override |
|---|---|---|
| Precision | Detects what the model is *actually* loaded as (HF `quantization_config`, else dominant param dtype). Only positively-detected NF4 earns the bitwise label; a mismatch warns and uses the detected value. | `precision="nf4"\|"bf16"\|"fp16"` |
| Adapter | Auto-detects (PEFT, native LoRA); detection is authoritative. Full fine-tune is refused (see gate below). | `adapter=` is advisory except `"unsafe_any_trainable"` |
| Architecture | Validated set → `validated_bitwise`. Anything else is checked from config only (no parity probe runs in this release) and reported `probed_not_validated` with no parity claim. | `architecture="validated_only"\|"probe"\|"off"` |
| `block_size` | Fixed validated default **4** — never guessed from VRAM (it's a memory↔speed knob, never parity). Clamped to the layer count. | `block_size=<int>` |

## The guarantee label

`model._poros_report.guarantee` after `prepare`:

- `validated_bitwise` — positively detected NF4 + validated arch + a
  strictly `validated` adapter (plain PEFT LoRA/QLoRA; `compatible`
  DoRA/RSLoRA/`modules_to_save` run fine but cap at `smoke_only`) +
  zero non-adapter trainables + deterministic path. The 0.00e+00 claim
  applies.
- `probed_not_validated` — arch is not in the first-party validated set (including architectures you registered yourself as `user_asserted`); ran without a parity claim. No runtime parity probe is performed in this release; the label reflects config inspection only.
- `smoke_only` — ran, no parity claim.
- `none` — an unsafe override (e.g. full fine-tune) disabled the guarantees.

## Gates that raise before wasting a run

| Error | Trigger | Escape hatch |
|---|---|---|
| `PorosFullFinetuneDetectedError` | trainable params are not adapter params | freeze the base + attach LoRA, or `unsafe_allow_full_trainable=True` (guarantee → `none`) |
| `PorosAdapterUnsupportedError` | experimental adapter under `guarantee="validated"` | validated adapter, or `unsafe_allow_unvalidated_adapter=True` |
| `PorosArchitectureUnsupportedError` | unvalidated arch under `validated_only`/`guarantee="validated"` | validated arch, or `architecture="probe"` |
| `PorosGuaranteeUnavailableError` | `guarantee="validated"` and ANY `validated_bitwise` condition is unmet (the error lists which) — `"validated"` delivers the guarantee or refuses, never a silent downgrade | fix the listed condition, or `guarantee="best_effort"` to proceed at the weaker label deliberately |
| `PorosAlreadyPreparedError` | second `prepare()` with different `block_size`/`guarantee`/`architecture`/`precision` | `poros.unprepare(model)` first; same-args re-prepare is a no-op |

## Useful extras

- `prepare(model, dry_run=True)` — full report, no patch.
- `prepare(model, output_dir=...)` (or a `trainer=` with `args.output_dir`) —
  writes `poros_run.json` next to your checkpoints. Its `memory.batch_size`,
  `memory.gradient_accumulation_steps`, and `memory.seq_len` fields are
  recorded from the trainer's args when you pass `trainer=`, and are `null`
  otherwise -- `null` means "not recorded", never a default. (`seq_len`
  stays `null` for a bare HF `Trainer`: sequence length lives in the
  collator there, not the args.)
- `verbose=True` (default) prints a one-screen summary of every decision above.
- **Sampling from a prepared model is correct but slow.** The streamed blocks
  hold no KV cache, so Poros turns `use_cache` off at prepare/patch time and
  `model.generate()` recomputes the full prefix each step (bitwise-equal to a
  resident run, just slower; forcing `use_cache=True` raises rather than
  returning wrong tokens). For fast generation, call
  `poros.unpatch_hf_model(model)` first, or load the saved adapter onto a
  fresh base as in `examples/inference.py`.
- `stack=` (env doctor at prepare-time) is not active in this build; it warns
  rather than report a result it hasn't checked. Use `poros doctor`.

Adapter statuses and what they mean: [adapters.md](adapters.md). Engine-level
surface (`patch_hf_model`): [api.md](api.md#hf-drop-in-patch).
