ruDNN 0.1.72

Neural network operators for Ruda.
Documentation
# ruDNN

[English](../../README.md) | [简体中文](../zh/README.md) | [日本語](../ja/README.md) | **Deutsch** | [Русский](../ru/README.md)

**Englisch** | [简体中文](../zh/README.md)

Neuronale Netzbetreiber für Ruda.

- Cargo Paket: `ruDNN`
- Rostkiste: `rudnn`

## Features

| Feature |Operationen|
| --- | --- |
|`tensor-attention`| Attention |
| `tensor-paged-attention` | Seitenbasierte MHA/GQA/MLA |
|`tensor-convolution`|Faltung|
|`tensor-normalization`|LayerNorm, RMSNorm und Softmax|
|`tensor-moe`|MoE Routing, Dispatch und Expertenberechnung|
|`tensor-gated-delta`|Gated-Delta-Berechnung|
|`pooling`|Pooling|
|`interpolation`|Interpolation|
|`grid-sample`|Rasterstichprobe|
|`ctc`|CTC Verlust|

## Schnellstart

Aus dem RUDA-Arbeitsbereich erstellen:

```sh
git clone https://github.com/shuqi2077/RUDA.git
cd RUDA
cargo build --release --locked -p ruDNN --features tensor-normalization
```

## Dokumentation

- [Benutzerhandbuch](../../../docs/de/libraries/rudnn.md)
- [Umgebungseinrichtung](../../../docs/de/getting-started.md)
- [Cargo-Funktionen](../../Cargo.toml) · [Modulexporte](../../src/lib.rs)

## ruDNN Benutzerhandbuch

[Computerbibliotheken](../../../docs/de/libraries/README.md) · [ruBLAS](../../../docs/de/libraries/rublas.md) · [Tensoren und Frameworks](../../../docs/de/tensor-framework.md) · [中文](../zh/README.md)

### 1. Übersicht und Funktionen

ruDNN bietet neuronale Netzwerkoperationen. Das Cargo-Paket ist `ruDNN` und die Rust-Crate ist `rudnn`.

| Feature |Operationen|
| --- | --- |
|`tensor-attention`|Tensor-Aufmerksamkeit|
| `tensor-paged-attention` | Seitenbasierte MHA/GQA/MLA |
|`tensor-convolution`|Tensorfaltung|
|`pooling`, `interpolation`|Pooling und Interpolation|
|`grid-sample`, `ctc`|Rasterstichprobe und CTC|
|`tensor-moe`|Gerät MoE Routing und Expertenberechnung|
|`tensor-normalization`|Universelle Gerätenormalisierung für Modellebenen|
|`tensor-gated-delta`|Gated-Delta-Berechnung für Hybridarchitekturen wie Qwen3.5|

Aufmerksamkeit und Faltung verfügen jeweils über entsprechende Autotune-Funktionen. Siehe [Cargo.toml](../../Cargo.toml) und [Modulexporte](../../src/lib.rs).

### 2. Attention und Faltung

#### Attention

`rudnn::attention::tensor::attention` akzeptiert Abfrage, Schlüssel, Wert, optionale Maske, optionales attn_bias, AttentionModuleOptions und AttentionStrategy. Es gibt einen Gerätetensor oder AttentionSetupError zurück.

Die Strategien umfassen FlashBlackboxAccelerated, FlashUnit, Fallback und bei aktiviertem Feature Autotune. Ohne Autotuning ist Fallback der Standard, mit Autotuning Autotune. Fallback verwendet mehrere Kernels auf demselben Gerät, kein CPU-Backend.

Passen Sie Layout, Maske und Präzision an die ausgewählte Strategie an. Siehe die [Attention-Schnittstelle](../../src/attention/tensor/base.rs).

#### Faltung

`rudnn::convolution::tensor::conv_forward` übernimmt Eingabe, Gewicht, optional bias, ConvOptions<N> und ConvStrategy. Es gibt einen Gerätetensor oder ConvSetupError zurück. Es konvertiert Eingaben zur Ausführung in das Channel-Last-Layout und konvertiert die Ausgabe zurück. `conv_forward_nhwc` verwendet direkt das Channels-Last-Layout.

Zu den Strategien gehören Direct, ImplicitGemm und optional Autotune. Der Einstiegspunkt verwendet Direct für die dreidimensionale F32-Faltung. Die gruppierte Faltung verwendet auch Direct, wenn ImplicitGemm ausgewählt ist. Der Strategieparameter ist daher nicht immer eine strikte Anforderung, einen Algorithmus beizubehalten.

Das gleiche Modul stellt conv_data_backward und conv_weight_backward bereit. Überprüfen Sie Form, Optionen und Ausführungsanforderungen für jede Richtung. Siehe die [Faltungsschnittstelle](../../src/convolution/tensor/base.rs).

### 3. MoE-Workflow

`rudnn::moe` stellt diese lokale Berechnungssequenz bereit:

Eingabe-Logits → softmax/top-k → kompakte Verteilung → SwiGLU-Experten → gewichtete Zusammenführung.

|API|Zweck|
| --- | --- |
|`route(logits, RoutingOptions)`|Erstellt ein RoutingPlan|
|`RoutingPlan::expert_indices()`, `weights()`|Greift auf ausgewählte Experten und Gewichtungen zu|
|`RoutingPlan::dispatch(input)`|Versendet Token durch Experten|
|`SwiGluExperts::new(gate, up, down)`|Erstellt bias-freie Expertengewichte|
|`SwiGluExperts::forward_dispatched`|Berechnet versendete Token|
|`DispatchedTokens::combine`|Stellt die Token-Reihenfolge wieder her und kombiniert gewichtete Ergebnisse|
|`SwiGluExperts::forward(input, logits, options)`|Führt die komplette lokale Vorwärtssequenz aus|

Diese Schnittstellen verwenden `RudaTensor<R>`. Berechnungseinstiegspunkte geben `Result` mit `MoeError` für ungültige Formen, D-Typen oder andere Argumente zurück.

### 4. Routing-Vertrag

Logits haben die Form [T, E] und verwenden unquantisiertes F32, F16 oder BF16. `RoutingOptions` enthält `top_k: usize` und `renormalize: bool`, wobei 1 ≤ top_k ≤ E gilt.

Softmax berechnet vor der Top-K-Auswahl alle Experten in FP32. Unentschieden begünstigen niedrigere Experten-IDs. Wenn die Renormierung aktiviert ist, werden ausgewählte Gewichtungen neu normalisiert und dann in die Logits dtype umgewandelt. Zeilen, die NaN, positive Unendlichkeit oder nur negative Unendlichkeit enthalten, behalten die Gewichtungen von NaN bei, anstatt eine gleichmäßige Verteilung zu verwenden.

Ausgewählte Expertenindizes und -gewichte haben beide die Form [T, top_k]. Indizes verwenden U32.

### 5. Expertengewichte und Verteilung

Eingabetokens haben die Form [T, H]; gate und up haben [E, I, H], down hat [E, H, I]. Gewichte müssen denselben Gleitkomma-dtype und dasselbe Gerät verwenden. Expertenanzahl und Dimensionen müssen zu Routing und Eingabe passen.

Die Verteilung verwirft keine Tokens zur Durchsetzung einer Kapazitätsgrenze. Die atomare Zuweisungsreihenfolge innerhalb eines Experten ist nicht festgelegt; eine gespeicherte Zuordnung stellt die Token-Reihenfolge wieder her. Der Vorwärtseinstiegspunkt nimmt vorberechnete Logits entgegen, statt die Gate-Projektion des Modells auszuführen oder Gewichtsdateien zu laden.

Quelle: [Routing](../../src/moe/routing.rs), [Dispatch and Combine](../../src/moe/dispatch.rs), [Experten](../../src/moe/experts.rs) und [Tests](../../src/moe/tests.rs).

### 6. MoE aufrufen

Aktivieren Sie `tensor-moe` für Ihre `ruDNN`-Abhängigkeit. Bereiten Sie Gerätetensoren mit den oben genannten Layouts vor, erstellen Sie dann Expertengewichte und führen Sie die Vorwärtsoperation aus:

```rust
use rudnn::moe::{RoutingOptions, SwiGluExperts};

let experts = SwiGluExperts::new(gate, up, down)?;
let options = RoutingOptions {
    top_k: 2,
    renormalize: true,
};
let output = experts.forward(input, logits, options)?;
```

Dadurch werden zwei Experten pro Token ausgewählt, daher muss `E` mindestens 2 sein. `input` hat die Form `[T, H]`, `logits` hat die Form `[T, E]` und `output` hat die Form `[T, H]`. Verwenden Sie `experts` in nachfolgenden Eingabestapeln wieder, ohne das Gewichtsobjekt neu zu erstellen.

Um das Routing zu überprüfen oder Ihre eigene Verarbeitung zwischen den Phasen einzufügen, rufen Sie diese separat auf:

```rust
use rudnn::moe::route;

let routing = route(logits, options)?;
let selected_experts = routing.expert_indices();
let selected_weights = routing.weights();
let dispatched = routing.dispatch(input)?;
let expert_output = experts.forward_dispatched(&dispatched)?;
let output = dispatched.combine(expert_output)?;
```

Dies sind alternative Formen; Die zweite beginnt mit einer neuen Charge `input` und `logits`. `combine` verwendet die Dispatch-Zuordnung, um die Token-Reihenfolge wiederherzustellen und führt Expertenausgaben mithilfe von Routing-Gewichten zusammen.

Form, dtype oder Gerätekonflikte geben `MoeError` zurück. Um Ergebnisse auf dem Host zu lesen, verwenden Sie `ruda_kernel::tensor::readback::into_data_sync(output)`, das auf Geräteergebnisse wartet und `TensorData` zurückgibt.

### 7. LayerNorm, RMSNorm und Softmax

Aktivieren Sie `tensor-normalization` und importieren Sie diese Funktionen aus `rudnn::normalization`. Alle arbeiten auf der endgültigen Eingabeachse und behalten die Form bei:

|Funktion|Eingabe|Parameter|
| --- | --- | --- |
|`layer_norm(input, gamma, beta, epsilon)`|F32/F16/BF16|F32 Vektor `gamma`, optionaler F32 Vektor `beta`|
|`rms_norm(input, gamma, epsilon)`|F32/F16/BF16|F32 Vektor `gamma`|
|`softmax_last_axis(input)`|F32|Keine zusätzlichen Parameter|

Die Eingabe muss unquantisiert sein und eine nicht leere Endachse haben. Für die Endachsenlänge H müssen `gamma` und `beta` die Form `[H]` haben, unquantisiert sein und sich das Eingabegerät teilen. `epsilon` muss endlich und positiv sein. Affine Parameter bleiben auch für BF16/F16-Eingänge F32. Statistik und affine Arithmetik verwenden FP32, wobei die Ausgabe in die Eingabe dtype umgewandelt wird.

```rust
use ruda_kernel::{dsl::Runtime, tensor::RudaTensor};
use rudnn::normalization::{NormalizationError, layer_norm, rms_norm, softmax_last_axis};

fn normalize<R: Runtime>(
    input: RudaTensor<R>,
    gamma: RudaTensor<R>,
    beta: Option<RudaTensor<R>>,
    epsilon: f32,
) -> Result<RudaTensor<R>, NormalizationError> {
    layer_norm(input, gamma, beta, epsilon)
}

fn normalize_rms<R: Runtime>(
    input: RudaTensor<R>,
    gamma: RudaTensor<R>,
    epsilon: f32,
) -> Result<RudaTensor<R>, NormalizationError> {
    rms_norm(input, gamma, epsilon)
}

fn probabilities<R: Runtime>(
    logits: RudaTensor<R>,
) -> Result<RudaTensor<R>, NormalizationError> {
    softmax_last_axis(logits)
}
```

### 8. Gated-Delta-Prefill und Rekurrenz

Aktivieren Sie `tensor-gated-delta`. Verwenden Sie `chunk_gated_delta_rule(input, chunk_size)` für das Vorfüllen von Chunked-Sequenzen und `gated_delta_rule(input)` für die Token-für-Token-Wiederholung. Beide nehmen `GatedDeltaInput<R>`:

|Feld| Form/Typ |
| --- | --- |
|`query`, `key`|`[B, H, T, K]`, passend zu F32/F16/BF16|
|`value`|`[B, H, T, V]`, gleicher dtype wie Abfrage|
|`beta`|`[B, H, T]`, gleicher dtype wie Abfrage|
|`log_decay`|`[B, H, T]`, F32|
|`initial_state`|`[B, H, K, V]`, F32|
|`query_scale`| Endlicher f32-Wert gemäß Modellkonfiguration |

Alle Tensoren müssen unquantisiert sein und sich auf demselben Gerät befinden. Angebot Q/K nach modellspezifischer Normalisierung; Der Einstiegspunkt übernimmt dies nicht für Sie. Diese Funktion verwendet die vorherigen `Runtime`- und `RudaTensor`-Importe wieder:

```rust
use rudnn::gated_delta::{
    GatedDeltaError, GatedDeltaInput, GatedDeltaOutput, chunk_gated_delta_rule,
};

fn delta_prefill<R: Runtime>(
    query: RudaTensor<R>,
    key: RudaTensor<R>,
    value: RudaTensor<R>,
    beta: RudaTensor<R>,
    log_decay: RudaTensor<R>,
    initial_state: RudaTensor<R>,
    query_scale: f32,
    chunk_size: usize,
) -> Result<GatedDeltaOutput<R>, GatedDeltaError> {
    chunk_gated_delta_rule(
        GatedDeltaInput {
            query, key, value, beta, log_decay, initial_state, query_scale,
        },
        chunk_size,
    )
}
```

Das zurückgegebene `output` hat die Form `[B, H, T, V]` und denselben dtype wie query. `final_state` ist F32 mit der Form `[B, H, K, V]`. Übergeben Sie für den nächsten Abschnitt derselben Sequenz diesen `final_state` als `initial_state`. Halten Sie für verschiedene Sequenzen getrennte Zustände vor. Der Anfangszustand wird nicht in-place überschrieben.

`chunk_size` muss positiv sein. Der dreieckige Arbeitsbereich von `4 × (chunk_size² + chunk_size)` Bytes darf das Shared-Memory-Limit des Geräts pro Arbeitsgruppe nicht überschreiten. Der Einstiegspunkt füllt den letzten Chunk auf; das Padding wird aus der Ausgabe ausgeschlossen. Ungültige Argumente liefern `GatedDeltaError`.

Informationen zu Text- und Bildaufrufen auf Modellebene finden Sie im [ruLLM-Inferenzleitfaden](../../../docs/de/model-inference.md).

### 9. Seitenbasierte Attention und MLA

Aktivieren Sie `tensor-paged-attention` und verwenden Sie `rudnn::paged_attention`. `HostPlan::new(page_size, pages, tables, lengths, sequence_ids, positions)` validiert die Scheduling-Metadaten auf dem Host; positions enthält absolute, bei null beginnende Positionen innerhalb jeder Sequenz. `DevicePlan::upload(host, &q)` überträgt diese Metadaten auf das Gerät und die Ausführungsqueue der Query. Der Plan darf nur bei unverändertem Schedule wiederverwendet werden.

`DevicePlan::attention(q, k, v, scale, causal)` liest physische Cache-Seiten direkt für gepacktes Prefill/Decode variabler Länge. Q hat die Form `[queries, Hq, D]`, K `[pages, page_size, Hkv, D]`, V `[pages, page_size, Hkv, Dv]` und das Ergebnis `[queries, Hq, Dv]`; `Hq` muss durch `Hkv` teilbar sein. Eingaben müssen zusammenhängende, nicht quantisierte F32/F16/BF16-Tensoren mit gleichem dtype, Gerät und gleicher Queue sein. Q/K/V müssen endlich und scale endlich und positiv sein. `D` und `Dv` liegen in `1..=1024`; das Gerät muss eine Plane mit 32 oder 64 Lanes und das erforderliche Launch-Grid unterstützen. Vorwärtslauf und Rückwärtslauf erster Ordnung sind verfügbar; beliebige externe Masken und quantisierte KV-Caches werden nicht unterstützt.

`DevicePlan::mla(q, qp, latent, kp, scale, causal)` nimmt Queries mit absorbierter Projektion `[queries, H, R]`, Positionsqueries `[queries, H, P]`, einen gemeinsamen latenten Cache `[pages, page_size, 1, R]` und einen Positionscache `[pages, page_size, 1, P]` entgegen. Das Ergebnis ist komprimierter Kontext `[queries, H, R]`; `P` liegt in `1..=256`. Positionskodierung erfolgt vor dem Aufruf, Value-/Output-Projektionen danach. Verwenden Sie die ursprüngliche QK-Skalierung des Modells, nicht eine aus dem komprimierten Rang abgeleitete Skalierung.

Die obigen Methoden verwenden den ungeteilten Pfad. Zum Aufteilen einer Historie erstellen Sie `SplitWorkspace::new(&q, queries, heads, value_dim, splits)` mit `2..=32` Splits und rufen `attention_with_workspace` oder `mla_with_workspace` auf. Teilstatistiken und Zusammenführung verwenden FP32. Ein Workspace ist auf 64 MiB begrenzt und darf nur bei passender Form in derselben geordneten Ausführungsqueue wiederverwendet werden.

`DevicePlan::append(k, v, key_cache, value_cache)` liefert die Cache-Tensoren, die für weitere Aufrufe aufzubewahren sind. Gemeinsam genutzte Cache-Allokationen werden vor Änderungen kopiert; gemeinsam genutzte physische Präfixseiten erfordern zusätzlich Copy-on-Write durch den Scheduler. Doppelte Schreibzugriffe auf dieselbe physische Position werden abgewiesen.

### 10. Gruppenbeschränktes Sigmoid-MoE-Routing

Mit `tensor-moe` liefert `route_sigmoid_grouped(logits, bias, options)` einen `RoutingPlan`. Logits haben die Form `[tokens, experts]` und den Typ F32/F16/BF16; der optionale Korrektur-Bias ist FP32 `[experts]` auf demselben Gerät und derselben Queue. Bias beeinflusst nur die Auswahl. Die zurückgegebenen Gewichte verwenden die ursprünglichen Sigmoid-Werte, optionale Renormalisierung und `scale`; bei Gleichstand werden kleinere IDs bevorzugt.

`GroupRoutingOptions` enthält `top_k`, `groups`, `selected_groups`, `group_top_two`, `renormalize` und `scale`. Die Expertenzahl liegt in `1..=1024`, die Gruppenzahl in `1..=128` und top-k in `1..=64`. Die Expertenzahl muss durch die Gruppenzahl teilbar sein, die Zahl ausgewählter Gruppen muss gültig sein und top-k darf deren gesamte Expertenzahl nicht überschreiten. `group_top_two=true` summiert die beiden größten korrigierten Werte je Gruppe und erfordert mindestens zwei Experten pro Gruppe; andernfalls wird das Maximum verwendet. Scale muss endlich und positiv sein.

`SwiGluExperts::forward_sigmoid_grouped(input, logits, bias, options, strategy)` führt Routing, Dispatch, Expertenberechnung und Kombination aus. `forward_dispatched_with_strategy` wählt `GroupedStrategy::Scalar`, `Auto` oder `TensorCore`; die bestehenden Methoden `forward` und `forward_dispatched` behalten `Scalar` bei. Tensor-Core-Ausführung erfordert geeignete F16/BF16-Hardware. `Auto` fällt nur bei nicht unterstützter Konfiguration zurück, nicht bei Kompilierungs- oder Ausführungsfehlern. Modellprojektionen, gemeinsame Experten und Residualzweige bleiben Aufgabe des Aufrufers.

### 11. Rückwärtslauf für paged Attention und geordnete Historiengradienten

`DevicePlan::attention_backward(q, k, v, grad_out, scale, causal)` liefert `AttentionBackward { dq, dk, dv }`; `mla_backward(q, qp, latent, kp, grad_out, scale, causal)` liefert `MlaBackward { dq, dqp, dlatent, dkp }`. `dlatent` enthält Key- und Value-Beiträge. Wahrscheinlichkeiten werden im Rückwärtslauf neu berechnet, statt eine vollständige Score-Matrix zu speichern.

Die unsicheren APIs `attention_backward_selected_into` und `mla_backward_selected_into` akzeptieren unabhängig optionale Gradientenpuffer. `None` lässt die jeweilige Ausgabe und ihren zweigspezifischen Historienpuffer weg; andere Ableitungen können den Eingang weiterhin benötigen. Ausgaben dürfen weder Eingaben noch einander überlappen. Alle Zugriffe erfolgen auf der geordneten Queue des Plans. Historiengradienten verwenden hier FP32-Atomics und sind nicht bitweise deterministisch.

Für eine Historienreduktion ohne Atomics erstellen Sie `OrderedBackwardWorkspace::new(&plan, &q)?` und übergeben ihn veränderbar an `attention_backward_ordered_into` oder `mla_backward_ordered_into`. Die Überlappungsverbote gelten auch für Workspace-Puffer. Wiederverwendung erfordert kompatible unveränderliche Zeitplanmetadaten, Query-/Head-Anzahl, Gerät und Queue. Statistik und inverse Metadaten teilen ein Budget von 64 MiB. Eine feste Summationsreihenfolge garantiert keine Bitgleichheit zwischen Geräten oder mit dem atomaren Pfad.

| Workspace-Option | Standard | Wirkung |
| --- | --- | --- |
| `set_query_pruning(bool)` | `true` | Überspringt nachweislich kausal unsichtbare Queries, ohne die übrige Summationsreihenfolge zu ändern. |
| `set_history_row_cache(bool)` | `false` | Nutzt Historienzeilen threadlokal wieder; kein zusätzlicher Gerätetensor, aber möglicherweise höherer Registerdruck. |
| `set_history_compaction(bool)?` | `false` | Berechnet Historiengradienten nur für aktive physische Seiten und schreibt inaktive Seiten explizit mit Null. |

`RUDA_PAGED_ORDERED_CACHE_ROWS=1` und `RUDA_PAGED_ORDERED_COMPACT_HISTORY=1` aktivieren die letzten beiden Optionen beim Erstellen des Workspace. Nicht gesetzt oder `0` deaktiviert sie; andere Werte sind Fehler. Änderungen der Umgebung ändern bestehende Workspaces nicht.

Aktive Seiten sind über effektive KV-Längen von Sequenzen mit Queries erreichbar; Tabellenkapazität oder die Anzahl von Nichtnullgradienten sind nicht maßgeblich. Erstmaliges Aktivieren lädt einen Index von `physical_pages * 4` Bytes innerhalb desselben Budgets hoch. Späteres Umschalten verwendet ihn erneut; Deaktivieren behält den Speicher. `history_compaction_pages()` liefert `Option<(active_pages, inactive_pages)>`, `bytes()` zählt behaltenen Speicher mit. Budgetfehler lassen den bisherigen Modus unverändert. Der atomare PyTorch-Standard bleibt erhalten; eine Beschleunigung ist nicht garantiert.

### 12. MoE-Training erster Ordnung

`selected_router_weights(&logits, &indices, options)` liefert FP32-Gewichte `[T, top_k]`; `selected_router_backward(&logits, &indices, &grad_weights, options)` liefert `[T, E]`-Gradienten im Logits-dtype. Logits sind zusammenhängende F32/F16/BF16-Tensoren, Indizes zusammenhängend in U32/I32/I64; es gilt `1 <= top_k <= min(E, 64)` auf demselben Gerät und derselben Queue. `RouterWeightOptions` wählt `RouterScoring::Softmax` oder `Sigmoid`, optionale Renormalisierung ausgewählter Gewichte und anschließend einen endlichen positiven `scale`. `grad_weights` ist FP32.

Wiederholte Indizes haben Gather-Semantik. Ungültige Indizes erzeugen NaN für die ganze Zeile ohne ungültigen Speicherzugriff oder Host-Synchronisierung. Differenziert werden kontinuierliche Gewichte einer festen Auswahl, nicht Top-k-/Gruppenentscheidungen oder Korrektur-Bias. `RoutingPlan::into_training(logits, options)` behält die Auswahl und berechnet Gewichte neu. Verwenden Sie `RouterTrainingPlan::routing()` zur Verteilung und `backward(&grad_weights)` für Logits-Gradienten. Gespeicherte Eingaben müssen unverändert bleiben.

Behalten Sie `output` und `cache` aus `SwiGluExperts::forward_dispatched_training(&dispatched, strategy)`. Die Rückwärtskette ist:

1. `dispatched.combine_backward(&expert_output, grad_output)` liefert `dexpert` und FP32-`dweights`.
2. `cache.backward(dexpert)` liefert `dinput` für verteilte Zeilen sowie FP32-`dgate`, `dup`, `ddown`.
3. `dispatched.dispatch_backward(dinput)` summiert ausgewählte Zeilen zum ursprünglichen Token, ohne Routinggewichte erneut anzuwenden.
4. Router-Backward erhält `dweights` und liefert Logits-Gradienten.

`combine_backward` verwendet standardmäßig `CombineGradientStrategy::Serial`; `combine_backward_with_strategy` erlaubt `Plane`. Experten-`backward` ist unabhängig von der Vorwärtsstrategie standardmäßig skalar; `backward_with_strategy` akzeptiert `GroupedStrategy::Scalar`, `Auto` oder `TensorCore`. Tensor Core benötigt unterstützte F16/BF16-Hardware. `Auto` fällt nur bei fehlenden Fähigkeiten zurück, nicht bei Kompilierungs- oder Ausführungsfehlern.