From b3eca17ae32bba27d26dfc015ea41ca3c2048141 Mon Sep 17 00:00:00 2001 From: Anton Reinhard Date: Thu, 7 Nov 2024 14:13:07 +0100 Subject: [PATCH] Add Strassen Matrix Multiplication Implementation for unit tests (#42) * Add Strassen Matrix Multiplication algorithm for unit tests * Add more error info * Add unit tests * Fix strassen test --- src/graph/mute.jl | 12 ++ src/task/compute.jl | 9 +- test/runtests.jl | 7 +- test/strassen/impl.jl | 334 +++++++++++++++++++++++++++++++++++++ test/strassen/strassen.png | Bin 0 -> 61260 bytes test/strassen_test.jl | 62 +++++++ 6 files changed, 419 insertions(+), 5 deletions(-) create mode 100644 test/strassen/impl.jl create mode 100644 test/strassen/strassen.png create mode 100644 test/strassen_test.jl diff --git a/src/graph/mute.jl b/src/graph/mute.jl index 1ada0ba..f927967 100644 --- a/src/graph/mute.jl +++ b/src/graph/mute.jl @@ -61,6 +61,18 @@ function _insert_node!(graph::DAG, node::Node; track=true, invalidate_cache=true return node end +function _insert_edge!( + ::DAG, ::DataTaskNode, ::DataTaskNode, ::Int=0; track=true, invalidate_cache=true +) + throw("trying to create an edge between two data nodes which is not allowed") +end + +function _insert_edge!( + ::DAG, ::ComputeTaskNode, ::ComputeTaskNode, ::Int=0; track=true, invalidate_cache=true +) + throw("trying to create an edge between two compute nodes which is not allowed") +end + """ _insert_edge!(graph::DAG, node1::Node, node2::Node, index::Int=0; track = true, invalidate_cache = true) diff --git a/src/task/compute.jl b/src/task/compute.jl index dd78b09..4355142 100644 --- a/src/task/compute.jl +++ b/src/task/compute.jl @@ -42,7 +42,7 @@ function get_function_call(node::ComputeTaskNode) end function get_function_call(node::DataTaskNode) - @assert length(children(node)) == 1 "trying to call get_function_call on a data task node that has $(length(node.children)) children instead of 1" + @assert length(children(node)) == 1 "trying to call get_function_call on a data task node that has $(length(node.children)) children instead of 1\nchildren: $(node.children)" # TODO: dispatch to device implementations generating the copy commands return [ @@ -78,14 +78,17 @@ function result_type(fc::FunctionCall, known_res_types::Dict{Symbol,Type}) if length(types) > 1 throw( - "failure during type inference: function call $fc is type unstable, possible return types: $types", + "failure during type inference: function call $fc with argument types $(argument_types) is type unstable, possible return types: $types", ) end if isempty(types) throw( - "failure during type inference: function call $fc has no return types, this is likely because no method matches the arguments", + "failure during type inference: function call $fc with argument types $(argument_types) has no return types, this is likely because no method matches the arguments", ) end + if types[1] == Any + @warn "inferred return type 'Any' in task $fc with argument types $(argument_types)" + end return types[1] end diff --git a/test/runtests.jl b/test/runtests.jl index 71c05e0..491870d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,9 @@ using SafeTestsets -@safetestset "Utility Unit Tests " begin +@safetestset "Utility Unit Tests " begin include("unit_tests_utility.jl") end -# TODO: Make a new simple test model and rewrite tests here + +@safetestset "Strassen Matrix Multiplication Tests " begin + include("strassen_test.jl") +end diff --git a/test/strassen/impl.jl b/test/strassen/impl.jl new file mode 100644 index 0000000..b74d31b --- /dev/null +++ b/test/strassen/impl.jl @@ -0,0 +1,334 @@ +module MatrixMultiplicationImpl + +using ComputableDAGs + +const STRASSEN_MIN_SIZE = 32 # minimum matrix size to use Strassen algorithm instead of naive algorithm +const DEFAULT_TYPE = Float64 # default type of matrix multiplication assumed + +# problem model definition +struct MatrixMultiplication{T} + size::Int # size of multiplication +end + +MatrixMultiplication(size::Int) = MatrixMultiplication{DEFAULT_TYPE}(size) + +struct ComputeTask_Slice{X_SLICE,Y_SLICE} <: AbstractComputeTask end # perform a matrix slicing operation, subindexing the matrix with the ranges X_SLICE and Y_SLICE +struct ComputeTask_Add <: AbstractComputeTask end # perform matrix addition +struct ComputeTask_Sub <: AbstractComputeTask end # perform matrix subtraction +struct ComputeTask_MultBase <: AbstractComputeTask end # perform matrix multiplication on two matrices using naive algorithm +struct ComputeTask_MultStrassen <: AbstractComputeTask end # perform one strassen assembly step from C11, C12, C21, C22 + +ComputableDAGs.children(::ComputeTask_Slice) = 1 # A +ComputableDAGs.children(::ComputeTask_Add) = 2 # A, B +ComputableDAGs.children(::ComputeTask_Sub) = 2 # A, B +ComputableDAGs.children(::ComputeTask_MultBase) = 2 # A, B +ComputableDAGs.children(::ComputeTask_MultStrassen) = 7 # M1...M7 + +ComputableDAGs.compute_effort(::ComputeTask_Slice) = 0 +ComputableDAGs.compute_effort(::ComputeTask_Add) = 0 +ComputableDAGs.compute_effort(::ComputeTask_Sub) = 0 +ComputableDAGs.compute_effort(::ComputeTask_MultBase) = 0 +ComputableDAGs.compute_effort(::ComputeTask_MultStrassen) = 0 + +@inline ComputableDAGs.compute( + ::ComputeTask_Slice{UR_X,UR_Y}, A::AbstractMatrix +) where {UR_X,UR_Y} = A[UR_X, UR_Y] +@inline ComputableDAGs.compute(::ComputeTask_Add, A::AbstractMatrix, B::AbstractMatrix) = + A + B +@inline ComputableDAGs.compute(::ComputeTask_Sub, A::AbstractMatrix, B::AbstractMatrix) = + A - B +@inline function ComputableDAGs.compute( + ::ComputeTask_MultBase, A::MATRIX_T, B::MATRIX_T +)::MATRIX_T where {MATRIX_T<:AbstractMatrix} + return A * B +end + +function ComputableDAGs.compute( + ::ComputeTask_MultStrassen, C11::MATRIX_T, C12::MATRIX_T, C21::MATRIX_T, C22::MATRIX_T +) where {MATRIX_T<:AbstractMatrix} + return [ + C11 C12 + C21 C22 + ] +end + +# return data node with the result +function _dag_build_helper!( + g::DAG, + mm::MatrixMultiplication{T}, + A::DataTaskNode, # Data node that contains matrix A + B::DataTaskNode, # Data node that contains matrix B +) where {T} + mm_size = mm.size + @assert iseven(mm_size) "matrix size is not even: $mm_size" + mm_half_size = div(mm_size, 2) + + data_size = mm.size^2 * sizeof(T) + data_size_half = div(data_size, 4) + + if mm_size < STRASSEN_MIN_SIZE + mb_task = insert_node!(g, ComputeTask_MultBase()) + mb_data = insert_node!(g, DataTask(data_size)) + + insert_edge!(g, A, mb_task, 1) + insert_edge!(g, B, mb_task, 2) + insert_edge!(g, mb_task, mb_data) + return mb_data + end + + # STRASSEN step + h1 = 1:mm_half_size + h2 = (mm_half_size + 1):mm_size + + # -- Subindexing of A and B to prepare A_11, A_12, and so on + A11_t = insert_node!(g, ComputeTask_Slice{h1,h1}()) + insert_edge!(g, A, A11_t) + A12_t = insert_node!(g, ComputeTask_Slice{h1,h2}()) + insert_edge!(g, A, A12_t) + A21_t = insert_node!(g, ComputeTask_Slice{h2,h1}()) + insert_edge!(g, A, A21_t) + A22_t = insert_node!(g, ComputeTask_Slice{h2,h2}()) + insert_edge!(g, A, A22_t) + + A11_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A11_t, A11_d) + A12_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A12_t, A12_d) + A21_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A21_t, A21_d) + A22_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A22_t, A22_d) + + B11_t = insert_node!(g, ComputeTask_Slice{h1,h1}()) + insert_edge!(g, B, B11_t) + B12_t = insert_node!(g, ComputeTask_Slice{h1,h2}()) + insert_edge!(g, B, B12_t) + B21_t = insert_node!(g, ComputeTask_Slice{h2,h1}()) + insert_edge!(g, B, B21_t) + B22_t = insert_node!(g, ComputeTask_Slice{h2,h2}()) + insert_edge!(g, B, B22_t) + + B11_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, B11_t, B11_d) + B12_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, B12_t, B12_d) + B21_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, B21_t, B21_d) + B22_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, B22_t, B22_d) + + # M1 = (A11 + A22) x (B11 + B22) + local M1_d::DataTaskNode + begin + A_sum_t = insert_node!(g, ComputeTask_Add()) # A11 + A22 + B_sum_t = insert_node!(g, ComputeTask_Add()) # B11 + B22 + insert_edge!(g, A11_d, A_sum_t, 1) + insert_edge!(g, A22_d, A_sum_t, 2) + insert_edge!(g, B11_d, B_sum_t, 1) + insert_edge!(g, B22_d, B_sum_t, 2) + A_sum_d = insert_node!(g, DataTask(data_size_half)) + B_sum_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A_sum_t, A_sum_d) + insert_edge!(g, B_sum_t, B_sum_d) + + M1_d = _dag_build_helper!( + g, MatrixMultiplication{T}(mm_half_size), A_sum_d, B_sum_d + ) + end + + # M2 = (A21 + A22) x B11 + local M2_d::DataTaskNode + begin + A_sum_t = insert_node!(g, ComputeTask_Add()) # A21 + A22 + insert_edge!(g, A21_d, A_sum_t, 1) + insert_edge!(g, A22_d, A_sum_t, 2) + A_sum_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A_sum_t, A_sum_d) + + M2_d = _dag_build_helper!(g, MatrixMultiplication{T}(mm_half_size), A_sum_d, B11_d) + end + + # M3 = A11 x (B12 - B22) + local M3_d::DataTaskNode + begin + B_dif_t = insert_node!(g, ComputeTask_Sub()) # B12 - B22 + insert_edge!(g, B12_d, B_dif_t, 1) + insert_edge!(g, B22_d, B_dif_t, 2) + B_dif_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, B_dif_t, B_dif_d) + + M3_d = _dag_build_helper!(g, MatrixMultiplication{T}(mm_half_size), A11_d, B_dif_d) + end + + # M4 = A22 x (B21 - B11) + local M4_d::DataTaskNode + begin + B_dif_t = insert_node!(g, ComputeTask_Sub()) # B21 - B11 + insert_edge!(g, B21_d, B_dif_t, 1) + insert_edge!(g, B11_d, B_dif_t, 2) + B_dif_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, B_dif_t, B_dif_d) + + M4_d = _dag_build_helper!(g, MatrixMultiplication{T}(mm_half_size), A22_d, B_dif_d) + end + + # M5 = (A11 + A12) x B22 + local M5_d::DataTaskNode + begin + A_sum_t = insert_node!(g, ComputeTask_Add()) # A11 + A12 + insert_edge!(g, A11_d, A_sum_t, 1) + insert_edge!(g, A12_d, A_sum_t, 2) + A_sum_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A_sum_t, A_sum_d) + + M5_d = _dag_build_helper!(g, MatrixMultiplication{T}(mm_half_size), A_sum_d, B22_d) + end + + # M6 = (A21 - A11) x (B11 + B12) + local M6_d::DataTaskNode + begin + A_dif_t = insert_node!(g, ComputeTask_Sub()) # A21 - A11 + B_sum_t = insert_node!(g, ComputeTask_Add()) # B11 + B12 + insert_edge!(g, A21_d, A_dif_t, 1) + insert_edge!(g, A11_d, A_dif_t, 2) + insert_edge!(g, B11_d, B_sum_t, 1) + insert_edge!(g, B12_d, B_sum_t, 2) + A_dif_d = insert_node!(g, DataTask(data_size_half)) + B_sum_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A_dif_t, A_dif_d) + insert_edge!(g, B_sum_t, B_sum_d) + + M6_d = _dag_build_helper!( + g, MatrixMultiplication{T}(mm_half_size), A_dif_d, B_sum_d + ) + end + + # M7 = (A12 - A22) x (B21 + B22) + local M7_d::DataTaskNode + begin + A_dif_t = insert_node!(g, ComputeTask_Sub()) # A12 - A22 + B_sum_t = insert_node!(g, ComputeTask_Add()) # B21 + B22 + insert_edge!(g, A12_d, A_dif_t, 1) + insert_edge!(g, A22_d, A_dif_t, 2) + insert_edge!(g, B21_d, B_sum_t, 1) + insert_edge!(g, B22_d, B_sum_t, 2) + A_dif_d = insert_node!(g, DataTask(data_size_half)) + B_sum_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, A_dif_t, A_dif_d) + insert_edge!(g, B_sum_t, B_sum_d) + + M7_d = _dag_build_helper!( + g, MatrixMultiplication{T}(mm_half_size), A_dif_d, B_sum_d + ) + end + + # C11 = M1 + M4 - M5 + M7 + local C11_d::DataTaskNode + begin + s1_t = insert_node!(g, ComputeTask_Add()) # M1 + M4 + s2_t = insert_node!(g, ComputeTask_Sub()) # M7 - M5 + C11_t = insert_node!(g, ComputeTask_Add()) # s1 + s2 + insert_edge!(g, M1_d, s1_t, 1) # +M1 + insert_edge!(g, M4_d, s1_t, 2) # +M4 + insert_edge!(g, M7_d, s2_t, 1) # +M7 + insert_edge!(g, M5_d, s2_t, 2) # -M5 + + s1_d = insert_node!(g, DataTask(data_size_half)) + s2_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, s1_t, s1_d) + insert_edge!(g, s2_t, s2_d) + + insert_edge!(g, s1_d, C11_t, 1) + insert_edge!(g, s2_d, C11_t, 2) + + C11_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, C11_t, C11_d) + end + + # C12 = M3 + M5 + local C12_d::DataTaskNode + begin + C12_t = insert_node!(g, ComputeTask_Add()) + C12_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, M3_d, C12_t, 1) + insert_edge!(g, M5_d, C12_t, 2) + insert_edge!(g, C12_t, C12_d) + end + + # C21 = M2 + M4 + local C21_d::DataTaskNode + begin + C21_t = insert_node!(g, ComputeTask_Add()) + C21_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, M2_d, C21_t, 1) + insert_edge!(g, M4_d, C21_t, 2) + insert_edge!(g, C21_t, C21_d) + end + + # C22 = M1 - M2 + M3 + M6 + local C22_d::DataTaskNode + begin + s1_t = insert_node!(g, ComputeTask_Sub()) # M1 - M2 + s2_t = insert_node!(g, ComputeTask_Add()) # M3 + M6 + C22_t = insert_node!(g, ComputeTask_Add()) # s1 + s2 + insert_edge!(g, M1_d, s1_t, 1) # +M1 + insert_edge!(g, M2_d, s1_t, 2) # -M2 + insert_edge!(g, M3_d, s2_t, 1) # +M3 + insert_edge!(g, M6_d, s2_t, 2) # +M6 + + s1_d = insert_node!(g, DataTask(data_size_half)) + s2_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, s1_t, s1_d) + insert_edge!(g, s2_t, s2_d) + + insert_edge!(g, s1_d, C22_t, 1) + insert_edge!(g, s2_d, C22_t, 2) + + C22_d = insert_node!(g, DataTask(data_size_half)) + insert_edge!(g, C22_t, C22_d) + end + + # Assemble new Matrix C + + assemble_t = insert_node!(g, ComputeTask_MultStrassen()) + insert_edge!(g, C11_d, assemble_t, 1) + insert_edge!(g, C12_d, assemble_t, 2) + insert_edge!(g, C21_d, assemble_t, 3) + insert_edge!(g, C22_d, assemble_t, 4) + + C_d = insert_node!(g, DataTask(data_size)) + insert_edge!(g, assemble_t, C_d) + + return C_d +end + +function ComputableDAGs.graph(mm::MatrixMultiplication{T}) where {T} + g = DAG() + + A_d = insert_node!(g, DataTask(mm.size^2 * sizeof(T)), "A") + B_d = insert_node!(g, DataTask(mm.size^2 * sizeof(T)), "B") + + C_d = _dag_build_helper!(g, mm, A_d, B_d) + + return g +end + +function ComputableDAGs.input_expr( + ::MatrixMultiplication, name::String, input_symbol::Symbol +) + if name == "A" + return Meta.parse("$input_symbol[1]") + elseif name == "B" + return Meta.parse("$input_symbol[2]") + else + throw("unknown data node name $name") + end +end + +function ComputableDAGs.input_type(mm::MatrixMultiplication{T}) where {T} + return Tuple{Matrix{T},Matrix{T}} +end + +export MatrixMultiplication + +end diff --git a/test/strassen/strassen.png b/test/strassen/strassen.png new file mode 100644 index 0000000000000000000000000000000000000000..48836cbefa4e53094942189c65d0849df504bba4 GIT binary patch literal 61260 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�yS)y|Kb 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 CA*0a% literal 0 HcmV?d00001 diff --git a/test/strassen_test.jl b/test/strassen_test.jl new file mode 100644 index 0000000..e67491e --- /dev/null +++ b/test/strassen_test.jl @@ -0,0 +1,62 @@ +using ComputableDAGs +using RuntimeGeneratedFunctions +RuntimeGeneratedFunctions.init(@__MODULE__) +using StaticArrays + +include("strassen/impl.jl") +using .MatrixMultiplicationImpl + +TEST_TYPES = (Int32, Int64, Float32, Float64) +TEST_SIZES = (16, 32, 64, 128) #, 256 +NODE_NUMBERS = (4, 70, 532, 3766) #, 26404 +EDGE_NUMBERS = (3, 96, 747, 5304) #, 37203 + +@testset "Strassen Matrix Type $M_T Size $(TEST_SIZES[M_SIZE_I])" for (M_T, M_SIZE_I) in + Iterators.product( + TEST_TYPES, eachindex(TEST_SIZES) +) + M_SIZE = TEST_SIZES[M_SIZE_I] + NODE_NUM_EXPECTED = NODE_NUMBERS[M_SIZE_I] + EDGE_NUM_EXPECTED = EDGE_NUMBERS[M_SIZE_I] + + input = (rand(M_T, (M_SIZE, M_SIZE)), rand(M_T, (M_SIZE, M_SIZE))) + + mm = MatrixMultiplication{M_T}(M_SIZE) + + @testset "Construction" begin + @test mm.size == M_SIZE + @test input_type(mm) == Tuple{Matrix{M_T},Matrix{M_T}} + @test input isa input_type(mm) + @test_throws "unknown data node name C" input_expr(mm, "C", :input) + end + + g = graph(mm) + @testset "DAG properties" begin + @test is_valid(g) + + @test length(ComputableDAGs.get_entry_nodes(g)) == 2 + @test get_exit_node(g) isa DataTaskNode + + props = get_properties(g) + @test NODE_NUM_EXPECTED == props.noNodes + @test EDGE_NUM_EXPECTED == props.noEdges + end + + f = get_compute_function(g, mm, cpu_st(), @__MODULE__) + + if (M_SIZE > 256) + continue + end + + @testset "Execution" begin + @test Base.return_types(f, (typeof(input),))[1] == typeof(input[1]) + @test isapprox(f(input), input[1] * input[2]) + end + + @testset "Execution with closures" begin + f_closures = get_compute_function(g, mm, cpu_st(), @__MODULE__; closures_size=100) + + @test Base.return_types(f_closures, (typeof(input),))[1] == typeof(input[1]) + @test isapprox(f_closures(input), input[1] * input[2]) + end +end