made it possible to send expression to gpu alongside the needed data
Some checks failed
CI / Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} (x64, ubuntu-latest, 1.10) (push) Has been cancelled
CI / Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} (x64, ubuntu-latest, 1.6) (push) Has been cancelled
CI / Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} (x64, ubuntu-latest, pre) (push) Has been cancelled

This commit is contained in:
Daniel 2024-07-19 14:32:09 +02:00
parent 3145691d27
commit e1097202ab
4 changed files with 128 additions and 111 deletions

View File

@ -1,6 +1,6 @@
module ExpressionExecutorCuda
include("Interpreter.jl")
include("ExpressionProcessing.jl")
include("Interpreter.jl")
export interpret_gpu
export evaluate_gpu
@ -33,28 +33,6 @@ end
function evaluate_gpu(exprs::Vector{Expr}, X::Matrix{Float32}, p::Vector{Vector{Float32}})::Matrix{Float32}
end
function test()
Interpreter.CudaTest()
end
"Performs pre processing steps to the expressions.
- It replaces every variable with the according value stored in X and p.
- It transforms the expressions into postfix form and returns them.
"
function preprocess_expressions!(exprs::Vector{Expr}, X::Matrix{Float64}, p::Vector{Vector{Float64}})::Array{String}
symtable = ExpressionProcessing.construct_symtable(exprs, X, p)
postfixExpressions = Array{String,1}()
# Test if multi threading provides a speedup and if it does, roughly determin the size at which it is beneficial.
for i in eachindex(exprs)
expr = deepcopy(exprs[i])
ExpressionProcessing.replace_variables!(exprs[i], symtable, expr)
push!(postfixExpressions, ExpressionProcessing.expr_to_postfix(exprs[i]))
end
return postfixExpressions
end
end

View File

@ -1,17 +1,22 @@
module ExpressionProcessing
export construct_symtable
export replace_variables!
export expr_to_postfix
export SymbolTable64
export PostfixType
export Operator, Add, Subtract, Multiply, Divide, Power, Abs, Log, Exp, Sqrt
export ElementType, FLOAT64, OPERATOR, INT64
export ExpressionElement
@enum Operator Add=1 Subtract=2 Multiply=3 Divide=4 Power=5 Abs=6 Log=7 Exp=8 Sqrt=9
const SymbolTable64 = Dict{Tuple{Expr, Symbol},Float64}
const PostfixType = Vector{Union{Float64,Operator,Symbol}}
@enum Operator::Int64 Add=1 Subtract=2 Multiply=3 Divide=4 Power=5 Abs=6 Log=7 Exp=8 Sqrt=9
@enum ElementType FLOAT64=1 OPERATOR=2 INT64=3
# Maybe switch from Array{String} to Array{Union{Float64, Symbol}}?
struct ExpressionElement
Type::ElementType
value::Int64 # Reinterpret the stored value to type "ElementType" when using it
end
const PostfixType = Vector{ExpressionElement}
"Converts a julia expression to its postfix notation"
function expr_to_postfix(expr::Expr)::PostfixType
postfix = PostfixType()
operator = get_operator(expr.args[1])
@ -20,20 +25,81 @@ function expr_to_postfix(expr::Expr)::PostfixType
for j in 2:length(expr.args)
arg = expr.args[j]
if typeof(arg) === Expr
# exprElement = convert_to_ExpressionElement()
append!(postfix, expr_to_postfix(arg))
elseif typeof(arg) === Symbol # variables/parameters
push!(postfix, arg)
exprElement = convert_to_ExpressionElement(convert_var_to_int(arg))
push!(postfix, exprElement)
else
push!(postfix, convert(Float64, arg))
exprElement = convert_to_ExpressionElement(convert(Float64, arg))
push!(postfix, exprElement)
end
if length(postfix) >= 2
push!(postfix, operator)
exprElement = convert_to_ExpressionElement(operator)
push!(postfix, exprElement)
end
end
return postfix
end
function get_operator(op::Symbol)::Operator
if op == :+
return Add
elseif op == :-
return Subtract
elseif op == :*
return Multiply
elseif op == :/
return Divide
elseif op == :^
return Power
elseif op == :abs
return Abs
elseif op == :log
return Log
elseif op == :exp
return Exp
elseif op == :sqrt
return Sqrt
end
end
"Extracts the number from a variable/parameter and returns it. If the symbol is a parameter ```pn```, the resulting value will be negativ.
```x0 and p0``` are not allowed."
function convert_var_to_int(var::Symbol)::Int
varStr = String(var)
number = parse(Int32, SubString(varStr, 2))
if varStr[1] == 'p'
number = -number
end
return number
end
function convert_to_ExpressionElement(element)::ExpressionElement
value = reinterpret(Int64, element)
if element isa Float64
return ExpressionElement(FLOAT64, value)
elseif element isa Int64
return ExpressionElement(INT64, value)
elseif element isa Operator
return ExpressionElement(OPERATOR, value)
else
error("Element was of type '$(typeof(element))', which is not supported.")
end
end
#
# Everything below is currently not needed. Left here for potential future use
#
const SymbolTable64 = Dict{Tuple{Expr, Symbol},Float64}
"Replaces all the variables and parameters of the given expression with their corresponding value stored in the symtable
# Arguments
- `symtable::SymbolTable64`: Contains the values of all variables for each expression
@ -78,26 +144,4 @@ function fill_symtable!(expr::Expr, symtable::SymbolTable64, values::Vector{Floa
end
end
function get_operator(op::Symbol)::Operator
if op == :+
return Add
elseif op == :-
return Subtract
elseif op == :*
return Multiply
elseif op == :/
return Divide
elseif op == :^
return Power
elseif op == :abs
return Abs
elseif op == :log
return Log
elseif op == :exp
return Exp
elseif op == :sqrt
return Sqrt
end
end
end
end

View File

@ -1,36 +1,8 @@
module Interpreter
using CUDA
include("ExpressionProcessing.jl")
using .ExpressionProcessing: PostfixType, Add, Subtract, Operator
using ..ExpressionProcessing
export Interpret
export CudaTest
function CudaTest()
N = 2^20
x = CUDA.fill(1.0f0, N)
y = CUDA.fill(2.0f0, N)
kernelAdd = @cuda launch=false InterpretExplicit!(ExpressionProcessing.Add, x, y)
# kernelAdd = @cuda launch=false InterpretExplicit!(Add, x, y, reference)
config = launch_configuration(kernelAdd.fun)
threads = min(length(y), config.threads)
blocks = cld(length(y), threads)
kernelAdd(Add, x, y; threads, blocks)
# println(y[1])
# @test all(Array(y) .== 3.0f0)
kernelSubtract = @cuda launch=false InterpretExplicit!(ExpressionProcessing.Subtract, x, y)
configSub = launch_configuration(kernelSubtract.fun)
threadsSub = min(length(y), configSub.threads)
blocksSub = cld(length(y), threadsSub)
CUDA.fill!(y, 2.0f0)
# kernelSubtract(Subtract, x, y; threadsSub, blocksSub)
# @test all(Array(y) .== -1.0f0)
# println(y[1])
end
export interpret
"Interprets the given expressions with the values provided.
# Arguments
@ -38,19 +10,49 @@ end
- variables::Matrix{Float64} : The variables to use. Each column is mapped to the variables x1..xn
- parameters::Vector{Vector{Float64}} : The parameters to use. Each Vector contains the values for the parameters p1..pn. The number of parameters can be different for every expression
"
function Interpret(expressions::Vector{ExpressionProcessing.PostfixType}, variables::Matrix{Float64}, parameters::Vector{Vector{Float64}})
function interpret(expressions::Vector{ExpressionProcessing.PostfixType}, variables::Matrix{Float64}, parameters::Vector{Vector{Float64}})
# TODO:
# create CUDA array and fill it with the expressions, variables and parameters
# calculate needed number of threads, probably based off of the number of expressions, so I can ensure each warp takes the same execution path
# Start the kernel
cudaExprs = Vector{CuArray{ExpressionProcessing.PostfixType}}(undef, length(expressions))
# create CUDA array and fill it with the variables and parameters
# create CUDA array for calculation results
variableRows = size(variables, 1)
cudaVars = CuArray(variables)
paramRows = get_max_parameter_rows(parameters)
p1aramCols = length(parameters)
cudaParams = CuArray{Float64}(undef, p1aramCols, paramRows) # length(parameters) == number of expressions
# TODO: Fill cudaParams
# TODO: Move CuArray(expression[i]) outside the loop for a more efficient transfer to GPU but leave kernel signature as is
for i in eachindex(expressions)
push!(cudaExprs, CuArray(expressions[i]))
cudaExpr = CuArray(expressions[i])
kernel = @cuda launch=false interpret_expression(cudaExpr, cudaVars, cudaParams, i)
config = launch_configuration(kernel.fun)
threads = min(variableRows, config.threads)
blocks = cld(variableRows, threads)
kernel(cudaExpr, cudaVars, cudaParams, i; threads, blocks)
end
# cudaExprs = CuArray(copy(expressions))
end
function interpret_expression(expression, variables, parameters, exprIndex::Int)
#TODO Implement interpreter
end
"Retrieves the number of entries for the largest inner vector"
function get_max_parameter_rows(params::Vector{Vector{T}})::Int where T
maxLength = 0
for i in eachindex(params)
if length(params) > maxLength
maxLength = length(params)
end
end
return maxLength
end
@deprecate InterpretExplicit!(op::Operator, x, y) interpret_expression(expression, variables, parameters, exprIndex::Int)
# Kernel
function InterpretExplicit!(op::Operator, x, y)
index = (blockIdx().x - 1) * blockDim().x + threadIdx().x

View File

@ -11,28 +11,21 @@ variables[1,2] = 3
parameters[1] = Vector{Float64}(undef, 1)
parameters[1][1] = 5
@testset "Test symtable construction" begin
symtable = construct_symtable(expressions, variables, parameters)
@test haskey(symtable, (expressions[1], :x1))
@test haskey(symtable, (expressions[1], :x2))
@test haskey(symtable, (expressions[1], :p1))
@test symtable[(expressions[1], :x1)] 2.0 atol=0.01
@test symtable[(expressions[1], :x2)] 3.0 atol=0.01
@test symtable[(expressions[1], :p1)] 5.0 atol=0.01
end
@testset "Test variable replacement in expression" begin
symtable = construct_symtable(expressions, variables, parameters)
exprCopy = deepcopy(expressions[1])
replace_variables!(exprCopy, symtable, expressions[1])
@test eval(exprCopy) == 10 # If it can be evaluated, it means all variables have been replaced, as there are no globally defined variables
@testset "Test conversion expression element" begin
reference1 = ExpressionElement(FLOAT64, reinterpret(Int64, 1.0))
reference2 = ExpressionElement(INT64, reinterpret(Int64, 1))
reference3 = ExpressionElement(OPERATOR, reinterpret(Int64, Add))
@test isequal(reference1, ExpressionProcessing.convert_to_ExpressionElement(1.0))
@test isequal(reference2, ExpressionProcessing.convert_to_ExpressionElement(1))
@test isequal(reference3, ExpressionProcessing.convert_to_ExpressionElement(Add))
end
@testset "Test conversion to postfix" begin
reference = PostfixType()
append!(reference, [:x1, 1.0, :x2, Multiply, Add, :p1, Add])
append!(reference, [ExpressionProcessing.convert_to_ExpressionElement(1), ExpressionProcessing.convert_to_ExpressionElement(1.0), ExpressionProcessing.convert_to_ExpressionElement(2), ExpressionProcessing.convert_to_ExpressionElement(Multiply),
ExpressionProcessing.convert_to_ExpressionElement(Add), ExpressionProcessing.convert_to_ExpressionElement(-1), ExpressionProcessing.convert_to_ExpressionElement(Add)])
postfix = expr_to_postfix(expressions[1])
@test isequal(reference, postfix)