# NEUROBRIX – ZERO FALLBACK POLICY

**Version:** 2.0
**Date:** 2026-02-08
**Status:** APPROVED
**Scope:** Tous les composants NeuroBrix (Prism, Kernels Operator, GraphExecutor, Import, NBX Loader)

---

## 1. PRINCIPE FONDAMENTAL

```
╔═══════════════════════════════════════════════════════════════════════════════════════════╗
║                                                                                           ║
║   "Si une ressource n'existe pas exactement comme demandée, on STOPPE."                  ║
║                                                                                           ║
║   NeuroBrix est un runtime DÉTERMINISTE, pas un framework permissif.                     ║
║   Aucune improvisation. Aucune substitution silencieuse. Aucun plan B automatique.       ║
║                                                                                           ║
╚═══════════════════════════════════════════════════════════════════════════════════════════╝
```

**Définition:** Un **fallback** est toute substitution automatique d'une ressource demandée par une alternative non explicitement demandée par l'utilisateur.

---

## 2. RAISONS TECHNIQUES

### 2.1 NeuroBrix ≠ PyTorch

NeuroBrix n'est pas un framework permissif.
C'est un runtime d'exécution bas niveau, déterministe, destiné à remplacer CUDA/cuDNN/cuBLAS dans le futur.

Si une op manque → l'implémentation n'existe pas.
On ne doit jamais "essayer autre chose".

### 2.2 Debuggabilité

Un fallback masque la vraie source d'erreur:
- Un kernel non écrit
- Un mapping op_type incorrect
- Une clé NBX cassée
- Une configuration hardware erronée

Avec fallback → tu arrives à la fin du pipeline, mais plusieurs opérations ont tourné sur un "plan B" invisible → débug impossible.

### 2.3 Cohérence Cross-Hardware

NeuroBrix doit tourner sur:
- NVIDIA: Volta, Ampere, Hopper
- AMD: ROCm (gfx90a, gfx942)
- Intel: GPU (futur)
- ARM, RISC-V (futur)

Un fallback brise cette cohérence.
NeuroBrix doit dire: "ce kernel n'existe pas encore pour ce hardware", pas essayer d'improviser.

---

## 3. CE QUI EST INTERDIT

### 3.1 Kernels

| Interdit | Exemple | Pourquoi |
|----------|---------|----------|
| Generic kernel | `softmax_generic()` si `softmax_volta()` manque | Masque un kernel non implémenté |
| PyTorch fallback | `torch.softmax()` si Triton manque | Viole Zero PyTorch Compute |
| CPU fallback | Exécuter sur CPU si GPU kernel manque | Performance catastrophique silencieuse |
| Cross-vendor | Utiliser kernel AMD sur NVIDIA | Comportement indéfini |

### 3.2 Types de Données

| Interdit | Exemple | Pourquoi |
|----------|---------|----------|
| Arbitrary dtype conversion | `bf16 → fp16` silencieux **sans vérification de range** | Perte de données si valeurs > 65504 (fp16 max). Conversion autorisée après scan via `_scan_bf16_fp16_safety()` |
| Uncontrolled dtype promotion | `fp16 + fp32 → fp32` sans règles | Comportement différent selon hardware |

**NOTE (2026-02):** DtypeEngine implémente les règles PyTorch AMP (Automatic Mixed Precision) dans `src/neurobrix/core/dtype/engine.py`. Les conversions dtype suivent les règles AMP standards:
- Ops FP32 (pow, rsqrt, softmax, sum...) → upcast vers fp32
- Ops FP16 (mm, bmm, conv...) → downcast vers compute_dtype
- Promote ops → type le plus large

**Ces conversions NE SONT PAS des fallbacks** — elles sont intentionnelles et suivent le design PyTorch AMP. Elles évitent les overflow/underflow (ex: RMSNorm pow→mean→rsqrt en fp16 produirait rsqrt(Inf)=0).

### 3.3 Devices

| Interdit | Exemple | Pourquoi |
|----------|---------|----------|
| CPU offload silencieux | Mettre un tensor sur CPU si GPU plein | Crash mémoire préférable à lenteur invisible |
| Device auto-select | `cuda:0` si device demandé indisponible | Exécution sur mauvais GPU |

### 3.4 Configurations

| Interdit | Exemple | Pourquoi |
|----------|---------|----------|
| Valeurs par défaut | `batch_size = 1` si non spécifié | Config incomplète = erreur |
| Paramètres optionnels silencieux | Ignorer un param manquant | Comportement imprévisible |

---

## 4. CE QUI EST AUTORISÉ

### 4.1 Fallbacks EXPLICITES (avec consentement utilisateur)

```yaml
# config/hardware/profile.yml
fallback_policy:
  allow_dtype_downcast: true    # Utilisateur consent à bf16→fp16 (vérifié par _scan_bf16_fp16_safety)
  allow_generic_kernels: false  # Utilisateur refuse les kernels génériques
```

**Règle:** Un fallback explicitement configuré par l'utilisateur N'EST PAS une violation.

### 4.2 Résolution Prism (dtype)

Prism **résout** les dtypes selon le hardware, ce n'est PAS un fallback:

```
config.json dit: bf16
hardware dit: supports [fp32, fp16]
Prism résout: fp16 (meilleur supporté)
```

**Pourquoi c'est OK:** Prism fait une **décision d'allocation** documentée, pas une substitution silencieuse. Le résultat est tracé dans `execution_plan.json`.

### 4.3 Sélection de Variant (architecture)

Choisir `softmax_volta()` vs `softmax_ampere()` selon l'architecture N'EST PAS un fallback:

```python
# OK - Sélection basée sur variable embarquée
kernel = kernels[architecture]  # "volta" → softmax_volta

# INTERDIT - Fallback silencieux
kernel = kernels.get(architecture, generic_kernel)
```

### 4.4 Ops Metadata (non-compute)

Les opérations **metadata** (Reshape, Transpose, Squeeze, etc.) peuvent utiliser PyTorch car elles ne font pas de calcul:

```python
# OK - Metadata op (pas de compute)
output = input.reshape(new_shape)  # PyTorch reshape

# INTERDIT - Compute op
output = torch.softmax(input)  # Doit être Triton
```

**Règle:** Si `OpCategory == METADATA` → PyTorch autorisé.

### 4.5 Execution Modes (Compiled DEFAULT, Native DEBUG, Triton R&D)

NeuroBrix a trois modes d'exécution:

```bash
# Mode 1: COMPILED (default) — Zero-overhead CompiledSequenceV2 + DtypeEngine AMP
neurobrix run --model ... --hardware ... --prompt ...
# (ou: PYTHONPATH=src python -m neurobrix run ... en mode dev)

# Mode 2: NATIVE (debug) — PyTorch ATen dict-based dispatcher
neurobrix run --model ... --hardware ... --prompt ... --seq_aten

# Mode 3: TRITON (R&D) — Custom Triton kernels (opt-in experimental)
neurobrix run --model ... --hardware ... --prompt ... --triton
```

**Pourquoi c'est OK:**
1. **Mode COMPILED par défaut** - Zero overhead, AMP rules, arena-based
2. **Mode NATIVE explicite** - Debug uniquement (`--seq_aten` flag visible)
3. **Mode TRITON opt-in** - R&D kernels (`--triton` flag visible)
4. **Consentement utilisateur** - Flags explicites pour modes non-default

**Architecture:**
```
COMPILED (default)
    → CompiledSequenceV2 (arena-based, zero dict lookups)
    → DtypeEngine (PyTorch AMP autocast rules)
    → Compiled ops avec integer slot addressing

NATIVE (--seq_aten)
    → NativeATenDispatcher (dict-based, ad-hoc stability fixes)
    → PyTorch ATen natif
    → Utile pour debug avant compilation

TRITON (--triton)
    → KernelAdapter + classification.py
    → Custom Triton kernels (TRITON class)
    → PyTorch pour METADATA ops
```

**Ce n'est PAS une violation** car les modes sont explicitement documentés et contrôlés par flags.

---

## 5. COMPORTEMENT ATTENDU PAR COMPOSANT

### 5.1 Kernels Operator

**Location:** `src/neurobrix/kernels/registry.py` (flat structure, pas `core/kernels/operator/`)

```python
def get_kernel(self, op_name: str) -> Callable:
    kernel = self._find_kernel(op_name, self.vendor, self.architecture)

    if kernel is None:
        raise KernelNotFoundError(
            f"ZERO FALLBACK: No kernel '{op_name}' for "
            f"vendor={self.vendor}, arch={self.architecture}.\n"
            f"Action required: Implement src/neurobrix/kernels/ops/{op_name}.py"
        )

    return kernel
```

### 5.2 Prism (Hardware Supervisor)

**Location:** `src/neurobrix/core/prism/solver.py`

**NOTE**: Prism fait une **résolution documentée**, pas un fallback silencieux:

```python
def _resolve_dtype(self, requested: str, profile: PrismProfile) -> torch.dtype:
    """
    Resolve optimal dtype based on hardware capabilities.
    RULE: requested not supported → try bf16→fp16 (with range scan), else fp32.
    Safety: _scan_bf16_fp16_safety() verifies all bf16 weights ≤ 65504.
    If safe → fp16 (saves memory, V100 compatible). If unsafe → fp32.
    Always safe without scan: bf16 → fp32 (same exponent range, wider mantissa).
    """
    all_support = profile.devices_support_dtype(requested)

    if all_support:
        return dtype_map.get(requested, torch.float32)

    # Logged resolution to fp32 - NOT a silent fallback
    print(f"[Prism] {requested} not supported. Using float32.")
    return torch.float32
```

**Pourquoi c'est OK**: La résolution est documentée et loggée, pas silencieuse. Le résultat est tracé dans `execution_plan.json`.

### 5.3 GraphExecutor

**Location:** `src/neurobrix/core/runtime/graph_executor.py`

```python
def execute_op(self, op: dict) -> torch.Tensor:
    op_type = op["op_type"]  # Format: "aten::view"

    # Pas de try/except qui cache les erreurs
    # Compiled mode: pre-resolved function pointer
    # Native mode: NativeATenDispatcher
    # Triton mode: KernelAdapter lookup

    # Si kernel manquant → exception propagée
    # Jamais de: kernel = kernel or torch_fallback

    return kernel(inputs, outputs, attrs)
```

### 5.4 Import (NBXBuilder — Forge Tool)

**Location:** `forge/importer/builder.py` (private forge system, NOT public neurobrix package)

**User-facing import:** `neurobrix import <org>/<model>` (downloads from neurobrix.es registry)

```python
def build_component(self, component: str) -> Path:
    input_spec = self._get_input_spec(component)

    if input_spec is None:
        raise ImportError(
            f"ZERO FALLBACK: Cannot generate input_spec for '{component}'.\n"
            f"config.json is incomplete or component type unknown.\n"
            f"Action: Ensure config.json has required fields."
        )

    return self._build_component(input_spec)
```

**Error message format (user-facing):**
```
ZERO FALLBACK: Model not found in cache

Action required:
  Re-import: neurobrix remove <model> && neurobrix import <org>/<model>
```

### 5.5 NBX Loader

**Location:** `src/neurobrix/nbx/container.py` et `src/neurobrix/core/runtime/loader.py`

```python
def load_component(self, name: str) -> Component:
    if name not in self._manifest["components"]:
        raise ComponentNotFoundError(
            f"ZERO FALLBACK: Component '{name}' not in manifest.\n"
            f"Available: {list(self._manifest['components'].keys())}"
        )

    topology = self._load_topology(name)
    if topology is None:
        raise TopologyNotFoundError(
            f"ZERO FALLBACK: No topology.json for '{name}'.\n"
            f"NBX file may be corrupted or incomplete.\n"
            f"Action: Re-import model (neurobrix remove {name} && neurobrix import org/{name})"
        )

    return Component(topology)
```

---

## 6. FORMAT DES MESSAGES D'ERREUR

Toute erreur ZERO FALLBACK doit suivre ce format:

```
ZERO FALLBACK: [Description courte du problème]

Context:
  - Component: [nom du composant]
  - Operation: [op demandée]
  - Vendor: [nvidia/amd/...]
  - Architecture: [volta/ampere/...]
  - Requested: [ce qui était demandé]
  - Available: [ce qui existe]

Action required:
  [Instructions claires pour résoudre le problème]

Documentation:
  See docs/KERNELS_OPERATOR_SYSTEM.md section X.Y
```

**Exemple réel:**

```
ZERO FALLBACK: No kernel 'GroupNorm' for vendor=nvidia, arch=volta

Context:
  - Component: transformer
  - Operation: GroupNorm
  - Vendor: nvidia
  - Architecture: volta
  - Requested: src/neurobrix/kernels/ops/groupnorm.py
  - Available: ['add', 'mul', 'matmul', 'softmax', 'layernorm', 'gelu']

Action required:
  Implement kernel at: src/neurobrix/kernels/ops/groupnorm.py
  Or use LayerNorm if model allows substitution.

Documentation:
  See docs/KERNELS_OPERATOR_SYSTEM.md section 4.2
```

---

## 7. CHECKLIST D'IMPLÉMENTATION

Pour chaque composant, vérifier:

- [ ] Aucun `try/except` qui avale silencieusement les erreurs
- [ ] Aucun `.get(key, default_value)` pour des ressources critiques
- [ ] Aucun `if kernel is None: use_alternative()`
- [ ] Aucun `or` fallback: `kernel = find() or generic()`
- [ ] Messages d'erreur suivent le format standard
- [ ] Logs tracent les décisions (pas les substitutions)

---

## 8. RÉSUMÉ

```
┌─────────────────────────────────────────────────────────────────┐
│                      ZERO FALLBACK POLICY                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ❌ INTERDIT                      ✅ AUTORISÉ                   │
│  ─────────────────────────────    ─────────────────────────     │
│  • Generic kernels                • Sélection par architecture  │
│  • PyTorch compute fallback       • PyTorch metadata ops        │
│  • CPU offload silencieux         • Résolution Prism (tracée)   │
│  • Arbitrary dtype conversion     • DtypeEngine AMP rules       │
│  • Valeurs par défaut             • Fallback explicite (config) │
│  • try/except silencieux          • Erreurs explicites          │
│                                                                 │
│  RÈGLE: Si ça n'existe pas exactement → STOP avec message clair │
│                                                                 │
│  MODES: compiled (default) + --seq_aten (PyTorch ATen debug)    │
│         + --triton (Triton kernels R&D)                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

---

## 9. RÉFÉRENCES

- **TECHNICAL_SPEC.md**: Principe ZERO FALLBACK (Section Principes Absolus)
- **KERNELS_OPERATOR_SYSTEM.md**: Implémentation dans Kernels Operator
- **PRISM_LOGICS.md**: Résolution dtype (non-fallback)

---

*ZERO FALLBACK POLICY - NeuroBrix Infrastructure*
*"Explicit failure is better than implicit degradation"*
