Dokumentation / DeutschQuelltext ansehen ↗

ruTENSOR Benutzerhandbuch

English | 简体中文 | 日本語 | Deutsch | Русский

Computerbibliotheken · Tensor-Framework · 中文

ruTENSOR bietet Tensorkontraktionen, Reduktionen, physikalische Permutationen und elementweise Operationen mit benannten Achsen. Eingaben verwenden RudaTensor<R>; Die Anwendung wählt eine Gerätelaufzeit aus. Diese Bibliothek unterscheidet sich vom übergeordneten ruda-tensor-Framework.

1. Abhängigkeiten konfigurieren

Das Cargo-Paket ist ruTENSOR; Der Rust-Importname lautet rutensor. Die Standardeinstellungen ermöglichen std und die Berechnung des Gerätetensors, ohne dass ein Treiber ausgewählt werden muss. Die folgende Konfiguration platziert das Anwendungsverzeichnis neben dem RUDA-Quellverzeichnis:

[dependencies]
rutensor = { package = "ruTENSOR", path = "../RUDA/ruTENSOR" }
ruda-core = { path = "../RUDA/ruda-core", default-features = false, features = ["std", "tensor-host-data"] }
ruda-kernel = { path = "../RUDA/ruda-kernel", default-features = false, features = ["frontend-std", "device-tensor"] }
ruda-driver-cuda = { path = "../RUDA/ruda-driver-cuda", default-features = false, features = ["std"] }

2. Kontraktion, Reduktion und Permutation

Speichern Sie Folgendes als src/main.rs der Anwendung:

use ruda_core::tensor::data::TensorData;
use ruda_driver_cuda::{CudaDevice, CudaRuntime};
use ruda_kernel::tensor::{readback::into_data_sync, transfer::from_data};
use rutensor::{einsum, permute, reduce, ReductionOp};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let device = CudaDevice::default();
    let a = from_data::<CudaRuntime>(
        TensorData::new(vec![1f32, 2., 3., 4., 5., 6.], [2, 3]), &device,
    );
    let b = from_data::<CudaRuntime>(
        TensorData::new(vec![1f32, 0., 0., 1., 1., 1.], [3, 2]), &device,
    );
    let product = einsum("ik,kj->ij", &[&a, &b])?;
    let row_sums = reduce(&a, &[0, 1], &[0], ReductionOp::Sum)?;
    let transposed = permute(&a, &[1, 0])?;

    println!("product: {:?}", into_data_sync(product).to_vec::<f32>()?);
    println!("row sums: {:?}", into_data_sync(row_sums).to_vec::<f32>()?);
    println!("transpose: {:?}", into_data_sync(transposed).to_vec::<f32>()?);
    Ok(())
}

einsum("ik,kj->ij", ...) summiert über k und gibt die Form [2, 2] zurück. reduce behält Modus 0 bei und reduziert Modus 1, wodurch die Form [2] zurückgegeben wird. permute gibt einen neu zugewiesenen [3, 2]-Tensor zurück, keine Ansicht, die den Eingabespeicher gemeinsam nutzt.

3. Einsum-Ausdrücke

einsum(expression, inputs) akzeptiert eine oder mehrere Eingaben. Groß-/Kleinschreibung beachtende Buchstaben kennzeichnen Achsen; Auf der rechten Seite des Pfeils werden Ausgabeachsen ausgewählt und angeordnet.

Ausdruck Vorgang
ik,kj->ij Matrixmultiplikation
...ik,...kj->...ij Matrixmultiplikation mit Broadcast-Batch-Achsen
abc,cde->abde Mehrdimensionale Tensorkontraktion
ij,jk,kl->il Kontraktion mit drei Eingaben
i,j->ij Äußeres Produkt
ii->i Diagonale
ii-> Trace, der einen Skalar mit Rang Null zurückgibt
ijk->ki j reduzieren und übrige Achsen umordnen
...i->i Alle durch die Ellipse bezeichneten Achsen reduzieren

Für feste Ausdrücke, Formen, Schritte und dtypes konstruieren Sie EinsumPlan::new(expression, descriptors) und verwenden Sie execute(inputs) wieder. Um die Ausgabe- und arithmetische Genauigkeit anzugeben, verwenden Sie EinsumPlan::with_options oder einsum_with_options.

4. Deskriptoren und Ausführungspläne

Ein Mode ist ein i32-Label. Die gleiche Bezeichnung identifiziert die gleiche logische Achse über alle Tensoren hinweg, unabhängig von ihrer physischen Position. TensorDescriptor speichert Ausmaße, Elementschritte und Speicher dtype. OperandDescriptor fügt Beschriftungen und eine unäre Eingabetransformation hinzu.

Diese Funktionen erstellen und führen einen Plan für D = alpha * A @ B + beta * C aus:

use ruda_kernel::{dsl::Runtime, tensor::RudaTensor};
use rutensor::{
    ComputeType, DType, OperandDescriptor, OperationDescriptor,
    Plan, Result, TensorDescriptor,
};

fn make_plan<R: Runtime>(
    a: &RudaTensor<R>, b: &RudaTensor<R>, c: &RudaTensor<R>,
    m: usize, n: usize,
) -> Result<Plan> {
    let operation = OperationDescriptor::contraction(
        OperandDescriptor::from_tensor(a, &[0, 2])?,
        OperandDescriptor::from_tensor(b, &[2, 1])?,
        Some(OperandDescriptor::from_tensor(c, &[0, 1])?),
        TensorDescriptor::contiguous(&[m, n], DType::F32)?,
        &[0, 1],
        ComputeType::F32,
    )?;
    Plan::new(operation)
}

fn execute<R: Runtime>(
    plan: &Plan, a: &RudaTensor<R>, b: &RudaTensor<R>, c: &RudaTensor<R>,
    alpha: f64, beta: f64,
) -> Result<RudaTensor<R>> {
    plan.execute(&[a, b, c], &[alpha, beta])
}

Pläne behalten keine Eingabepuffer bei. Nachfolgende Ausführungen können unterschiedliche Tensoren mit passenden Formen, Schritten und D-Typen verwenden. Alle Eingaben müssen sich auf demselben Gerät befinden.

Konstruktor Ausführungseingabereihenfolge Skalare Reihenfolge
contraction A, B; optional C alpha; beta bei vorhandenem C
sum_product Alle Produkteingaben; optional C alpha; beta bei vorhandenem C
reduction A; optional C alpha; beta bei vorhandenem C
permutation A alpha
elementwise_binary A, B alpha, beta
elementwise_trinary A, B, C alpha, beta, gamma

Plan::execute ordnet die Ausgabe zu. Plan::execute_into akzeptiert einen Ausgabetensor und gibt ihn zurück, dessen Form, Schritte und dtype mit dem Ausgabedeskriptor übereinstimmen. Sein Puffer muss exklusiver Eigentümer sein und darf nicht mit Eingaben oder anderen Ansichten geteilt werden.

Verwenden Sie TensorDescriptor::new(extents, strides, dtype) für aufgefüllte oder neu angeordnete Ausgabelayouts. Abtriebsachsen dürfen sich nicht überlappen. Explizite Operationsdeskriptoren können über die Ausgabeform zusätzliche Broadcast-Achsen einführen.

5. Reduktion und elementweise Operationen

reduce(input, input_modes, output_modes, operation) reduziert Etiketten, die in der Ausgabe fehlen; reduzierte Achsen werden entfernt:

ReductionOp Vorgang Leere Reduktion
Sum Summe 0
Product Produkt 1
Min Mindestens Positive Unendlichkeit
Max Maximal Negativ unendlich

elementwise_binary und elementwise_trinary richten benannte Achsen aus und übertragen sie unter Verwendung von BinaryOp::{Add, Mul, Min, Max}. Trinäre Operationen werten (alpha * op(A) op_ab beta * op(B)) op_abc gamma * op(C) aus.

Wählen Sie Identity, Negate, Abs, Sqrt, Exp, Log, Sin, Cos, Tanh, Relu, Reciprocal oder Conjugate bis OperandDescriptor::with_unary. Log ist der natürliche Logarithmus; Conjugate entspricht Identity für reale Werte. Min und Max verbreiten NaNs.

6. Präzision, Speicher und Fehler

ruTENSOR macht einen Ruda Rust API sichtbar, nicht den NVIDIA cuTENSOR C ABI. Das Gerät muss die ausgewählten Speicher- und Rechentypen unterstützen.