JuliaGPU / JuliaGPU/GPUCompiler.jl
PTX: byval results in local memory usage
- Dominant language
- Julia
- Stars
- 187
- Forks
- 68
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 28
Description
Trying to migrate to CUDA.jl from CUDAdrv.jl (and Julia 1.3.1) but can't still due to performance issues. Here is an (artificial) example I managed to boil down to. I have this test code` add_kernel.jl`, where I have an array of 2d arrays and I want add it to another array of 2d arrays :
```
const threads = 256
#simple add matrixes kernel
function kernel_add_mat(n, x1, x2, y)
i = (blockIdx().x-1) * blockDim().x + threadIdx().x
if i <= n
@inbounds y[i] = x1[i] + x2[i]
end
return
end
@inline get_inputs3(indx_y, a, b, c) = (a, b, c)
@inline get_inputs3(indx_y, a1, a2, b1, b2, c1, c2) = indx_y == 1 ? (a1, b1, c1) : (a2, b2, c2)
@inline get_inputs3(indx_y, a1, a2, a3, b1, b2, b3, c1, c2, c3) = indx_y == 1 ? (a1, b1, c1) : indx_y == 2 ? (a2, b2, c2) : (a3, b3, c3)
#add arrays of matrixes kernel
function kernel_add_mat_z_slices(n, vararg...)
x1, x2, y = get_inputs3(blockIdx().y, vararg...)
i = (blockIdx().x-1) * blockDim().x + threadIdx().x
if i <= n
@inbounds y[i] = x1[i] + x2[i]
end
return
end
function add_z_slices!(y, x1, x2)
m1, n1 = size(x1[1]) #get size of first slice
blocks = (m1 * n1 + threads - 1) ÷ threads
#get length(x1) more blocks than needed to process 1 slice
@cuda blocks = blocks, length(x1) threads = threads kernel_add_mat_z_slices(m1 * n1, x1..., x2..., y...)
end
function add!(y, x1, x2)
m1, n1 = size(x1)
blocks = (m1 * n1 + threads - 1) ÷ threads
@cuda blocks = blocks, 1 threads = threads kernel_add_mat(m1 * n1, x1, x2, y)
end
num_z_slices = 3
Random.seed!(1)
#m, n = 7, 5 # tiny to measure overhead
#m, n = 521, 111
#m, n = 1521, 1111
#m, n = 3001, 1511 # prime numbers to test memory access correctness
m, n = 3072, 1536 # 256 multiplier
#m, n = 6007, 3001 # prime numbers to test memory access correctness
x1 = [cu(randn(Float32, (m, n)) .+ Float32(0.5)) for i = 1:num_z_slices]
x2 = [cu(randn(Float32, (m, n)) .+ Float32(0.5)) for i = 1:num_z_slices]
y1 = [similar(x1[1]) for i = 1:num_z_slices]
#reference down to bones add on GPU
add!(y1[1], x1[1], x2[1])
print("add! ");
@btime begin add!($y1[1], $x1[1], $x2[1]); synchronize() end
#adding arrays in an array
for slices = 1:num_z_slices
add_z_slices!(y1[1:slices], x1[1:slices], x2[1:slices])
print("add_z_slices!, slices = $slices ");
@btime begin add_z_slices!($y1[1:$slices], $x1[1:$slices], $x2[1:$slices]); synchronize() end
end
```
in julia 1.3.1 with CUDADrv (Win 10, GTX 2070, I have only 1 card.) I get the following numbers
```
add! 167.600 μs (35 allocations: 1.11 KiB)
add_z_slices!, slices = 1 171.101 μs (75 allocations: 2.97 KiB)
add_z_slices!, slices = 2 313.199 μs (93 allocations: 4.09 KiB)
add_z_slices!, slices = 3 456.600 μs (111 allocations: 5.41 KiB)
```
there is a slight overhead for adding z slicing code in case of just one slice. and then time grows reasonably linearly with the number of z slices increasing. however with julia 1.5.2 and CUDA.jl I get the following numbers:
```
add! 167.100 μs (14 allocations: 400 bytes)
add_z_slices!, slices = 1 169.800 μs (56 allocations: 2.33 KiB)
add_z_slices!, slices = 2 4.536 ms (68 allocations: 3.02 KiB)
add_z_slices!, slices = 3 2.435 ms (80 allocations: 3.83 KiB)
```
i.e. totally unreasonable growth with slices 2 and 3 (yet same performance with simple add kernel and even just one slice). Looks like an issue in CUDA.jl
here is how I setup julia 1.3.1 env
```
using Pkg
Pkg.activate("CUDAdrv")
pkg_ref = Dict("BenchmarkTools" => v"0.5.0",
"CUDAdrv" => v"5.0.1",
"CUDAnative" => v"2.7.0",
"CuArrays" => v"1.6.0",
)
for pkg in keys(pkg_ref)
println("install $pkg => ", pkg_ref[pkg])
Pkg.add(PackageSpec(name=pkg, version=pkg_ref[pkg]))
end
```
and this is how I setup julia 1.5.2 env
```
using Pkg
Pkg.activate("Cuda")
pkg_ref = Dict("BenchmarkTools" => v"0.5.0",
"CUDA" => v"2.0.0"
)
for pkg in keys(pkg_ref)
println("install $pkg => ", pkg_ref[pkg])
Pkg.add(PackageSpec(name=pkg, version=pkg_ref[pkg]))
end
```
and this is code to call the kernels in 1.3.1 `add_kernel_1.3.1.jl`:
```
using Pkg
Pkg.activate("CUDAdrv")
using BenchmarkTools, Printf, Random
using CUDAdrv: CuDevice, CuContext, DeviceSet, CuDefaultStream, synchronize, unsafe_destroy!, CUctx_flags, devices
use_dev = 0
dev = CuDevice(use_dev)
ctx = CuContext(dev)
using CUDAnative
CUDAnative.device!(use_dev)
using CuArrays
include("add_kernel.jl")
```
this is code to call the kernels in 1.5.2 `add_kernel_1.5.2.jl`:
```
using Pkg
Pkg.activate("Cuda")
using BenchmarkTools, Printf, Random, CUDA
include("add_kernel.jl")
```
and I call it like this
`C:\Bin\Julia-1.3.1\bin\julia.exe add_kernel_1.3.1.jl`
`C:\Bin\Julia 1.5.2\bin\julia.exe add_kernel_1.5.2.jl`
aj
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by running add_kernel.jl with add_kernel_1.3.1.jl and add_kernel_1.5.2.jl, comparing the single-slice and multi-slice kernels and their reported timings. Inspect the generated PTX or local-memory behavior for kernel_add_mat_z_slices. Done means the multi-slice performance regression is explained and corrected without changing the intended results.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- compilers, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100