ruda-kernel 0.1.50

Ruda Rust kernel DSL and device tensor operations.
use crate::dsl::prelude::*;
use alloc::vec;
use half::f16;
use ruda_core::ir::{CoopMma, FloatKind, Operation, PointerOp, Type, UIntKind};

#[ruda]
fn vectors(a: &Vector<f16, Const<8>>, b: &Vector<f16, Const<4>>, c: &mut Vector<f32, Const<4>>) {
    let definition = crate::dsl::cmma::MmaDefinition::<f16, f16, f32>::new(16usize, 8usize, 16usize);
    definition.execute_vectors_inplace(a, b, c);
}

#[ruda]
fn arrays(a: &Array<Vector<f16, Const<2>>>, b: &Array<Vector<f16, Const<2>>>, c: &mut Vector<f32, Const<4>>) {
    let definition = crate::dsl::cmma::MmaDefinition::<f16, f16, f32>::new(16usize, 8usize, 16usize);
    definition.execute_vector_inplace(a, b, c);
}

#[test]
fn manual_mma_vector_sequence_index_mut_keeps_the_accumulator_addressable() {
    let mut scope = Scope::root(false);
    let c = scope.create_local(Type::from(FloatKind::F32).with_vector_size(4));
    let mut sequence = Sequence::<Vector<f32, Const<4>>>::__expand_new(&mut scope);
    sequence.__expand_push_method(&mut scope, c.clone().into());
    let reference = sequence.__expand_index_mut_method(&mut scope, 0);
    assert!(scope.reference_storage(*c).is_some());
    let pointer = scope.reference_pointer(*reference.expand).expect("mutable sequence element reference");
    let a = scope.create_local(Type::from(FloatKind::F16).with_vector_size(8));
    let b = scope.create_local(Type::from(FloatKind::F16).with_vector_size(4));
    scope.register_type::<usize>(UIntKind::U32.into());
    vectors::expand(&mut scope, a.into(), b.into(), reference);
    assert_mma_writeback(&scope, pointer);
}

#[test]
fn manual_mma_vector_intrinsics_read_values_and_write_through_accumulator_reference() {
    for vector_operands in [false, true] {
        let mut scope = Scope::root(false);
        scope.register_type::<usize>(UIntKind::U32.into());
        let half = Type::from(FloatKind::F16);
        let c = scope.create_local(Type::from(FloatKind::F32).with_vector_size(4));
        let c_reference = scope.reference_borrow(c, true).unwrap();
        let c_pointer = scope.reference_pointer(*c_reference).unwrap();
        if vector_operands {
            let a = scope.create_local(half.with_vector_size(8));
            let b = scope.create_local(half.with_vector_size(4));
            let a = scope.reference_borrow(a, false).unwrap();
            let b = scope.reference_borrow(b, false).unwrap();
            vectors::expand(&mut scope, a.into(), b.into(), c_reference.into());
        } else {
            let a = scope.create_local_array(half.with_vector_size(2), 4);
            let b = scope.create_local_array(half.with_vector_size(2), 2);
            arrays::expand(&mut scope, a.into(), b.into(), c_reference.into());
        }
        assert_mma_writeback(&scope, c_pointer);
    }
}

fn assert_mma_writeback(scope: &Scope, pointer: ruda_core::ir::Variable) {
        let (position, result) = scope.instructions.iter().enumerate().find_map(|(i, instruction)| {
            if let Operation::CoopMma(CoopMma::ExecuteManual { registers_a, registers_b, registers_c, .. }) = instruction.operation {
                for value in [registers_a, registers_b, registers_c] {
                    assert!(scope.reference_target(value).is_none(), "MMA must read a value, not a reference proxy");
                }
                Some((i, instruction.out.unwrap()))
            } else { None }
        }).expect("manual MMA instruction");
        let mut pointers = vec![pointer];
        for instruction in &scope.instructions[..position] {
            if let Operation::Copy(input) = instruction.operation {
                if pointers.contains(&input) { pointers.push(instruction.out.unwrap()); }
            }
        }
        let mut values = vec![result];
        let mut writeback = false;
        for instruction in &scope.instructions[position + 1..] {
            match instruction.operation {
                Operation::Copy(input) if values.contains(&input) => values.push(instruction.out.unwrap()),
                Operation::Pointer(PointerOp::Store { pointer, value, .. })
                    if pointers.contains(&pointer) && values.contains(&value) => writeback = true,
                _ => {}
            }
        }
        assert!(writeback, "MMA result must update the caller's accumulator, not a discarded proxy");
}