Repository navigation
Fuse the GPU boundary-condition updates into one kernel per color #647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| include("BoundaryConditionsCylindrical.jl") | ||
| include("BoundaryConditionsCartesian.jl") | ||
| include("BoundaryConditionsCylindrical.jl") | ||
| include("BoundaryConditionsCartesian.jl") | ||
| include("BoundaryConditionsGPU.jl") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,8 +30,60 @@ function apply_boundary_conditions_on_r_axis!( rbpot::AbstractArray{T, 4}, rbi: | |
| nothing | ||
| end | ||
|
|
||
| # One work-item per z-row: sum the row's φ columns of matching parity with the | ||
| # precomputed weights and broadcast the result back to those columns. Replaces | ||
| # a getindex + gemv + broadcast sequence (a temporary and several kernel | ||
| # launches per SOR iteration) on GPU backends. | ||
| @kernel function r0_bc_kernel!(rbpot, rbi::Int, nnz::Int, gw, even_points::Bool) | ||
| irbz = @index(Global, Linear) | ||
| rbz = irbz + 1 | ||
| inφ_ub = size(rbpot, 2) - 1 | ||
| @inbounds begin | ||
| last_odd = nnz - (iseven(nnz) ? 1 : 0) # odd real-z rows | ||
| if rbz <= div(last_odd, 2) + mod(last_odd, 2) + 1 | ||
| gwrow = even_points ? 6 : 5 | ||
| φ0 = even_points ? 2 : 1 | ||
| s = zero(eltype(rbpot)) | ||
| j = 0 | ||
| for inφ in φ0:2:inφ_ub | ||
| j += 1 | ||
| s += rbpot[rbz, inφ + 1, 2, rbi] * gw[gwrow, j] | ||
| end | ||
| for inφ in φ0:2:inφ_ub | ||
| rbpot[rbz, inφ + 1, 2, rbi] = s | ||
| end | ||
| end | ||
| last_even = nnz - (isodd(nnz) ? 1 : 0) # even real-z rows | ||
| if rbz <= div(last_even, 2) + mod(last_even, 2) + 1 | ||
| gwrow = even_points ? 5 : 6 | ||
| φ0 = even_points ? 1 : 2 | ||
| s = zero(eltype(rbpot)) | ||
| j = 0 | ||
| for inφ in φ0:2:inφ_ub | ||
| j += 1 | ||
| s += rbpot[rbz, inφ + 1, 2, rbi] * gw[gwrow, j] | ||
| end | ||
| for inφ in φ0:2:inφ_ub | ||
| rbpot[rbz, inφ + 1, 2, rbi] = s | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| function apply_boundary_conditions_at_r0!( | ||
| rbpot::GPUArrays.AbstractGPUArray{T, 4}, | ||
| rbi::Int, | ||
| nnz::Int, | ||
| gw::AbstractArray{T}, | ||
| update_even_points::Val{even_points} | ||
| ) where {T, even_points} | ||
| kern = r0_bc_kernel!(_ka_get_backend(rbpot)) | ||
| kern(rbpot, rbi, nnz, gw, even_points, ndrange = rbidx(nnz) - 1) | ||
| nothing | ||
| end | ||
|
Comment on lines
+72
to
+83
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From PR623: cross-check this |
||
|
|
||
| function apply_boundary_conditions_at_r0!( | ||
| rbpot::AbstractArray{T, 4}, | ||
| rbpot::AbstractArray{T, 4}, | ||
| rbi::Int, | ||
| nnz::Int, | ||
| gw::AbstractArray{T}, | ||
|
|
||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copying #623 (comment):
The code snippet @claudiaalvgar posted is: _ghost_ops_φ(ax::DiscreteAxis{T, :periodic, :periodic}, n::Int, gbf) where {T} = _ghost_ops(ax, n, gbf)
_ghost_ops_φ(ax::DiscreteAxis{T, :reflecting, :reflecting}, n::Int, gbf) where {T} = _ghost_ops(ax, n, gbf)
_ghost_ops_φ(::DiscreteAxis{T, BL, BR}, n::Int, gbf) where {T, BL, BR} =
throw(ArgumentError("boundary condition ($BL, $BR) is not supported for the φ axis")) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # GPU boundary-condition path: the CPU implementation applies the ghost-face | ||
| # updates as ~10 view broadcasts per red-black color, whose per-call host | ||
| # overhead dominates the GPU iteration loop. Here each face update is encoded | ||
| # as (active, source index, factor) with ghost = factor * pot[source] and all | ||
| # six faces are applied by one kernel launch per color. | ||
| # | ||
| # The op tables below are 1:1 transcriptions of the broadcast methods in | ||
| # BoundaryConditionsCylindrical.jl / BoundaryConditionsCartesian.jl. | ||
| # Ghost entries with more than one ghost coordinate are never read by the SOR | ||
| # stencil, so the faces may be applied concurrently. | ||
|
|
||
| const _GhostFaceOp{T} = Tuple{Bool, Int, T} | ||
| _no_ghost_op(::Type{T}) where {T} = (false, 1, one(T)) | ||
|
|
||
| # faces of the compressed (red-black) dimension: neighbors sit at rows 2 / end-1 | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :fixed, :fixed}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), _no_ghost_op(T)) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :infinite, :infinite}, n::Int, gbf) where {T} = | ||
| ((true, 2, gbf[1]), (true, n - 1, gbf[2])) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :fixed, :infinite}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), (true, n - 1, gbf[2])) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :infinite, :fixed}, n::Int, gbf) where {T} = | ||
| ((true, 2, gbf[1]), _no_ghost_op(T)) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :infinite, :reflecting}, n::Int, gbf) where {T} = | ||
| ((true, 2, gbf[1]), (true, n - 1, one(T))) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :reflecting, :infinite}, n::Int, gbf) where {T} = | ||
| ((true, 2, one(T)), (true, n - 1, gbf[2])) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :reflecting, :reflecting}, n::Int, gbf) where {T} = | ||
| ((true, 2, one(T)), (true, n - 1, one(T))) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :fixed, :reflecting}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), (true, n - 1, one(T))) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :reflecting, :fixed}, n::Int, gbf) where {T} = | ||
| ((true, 2, one(T)), _no_ghost_op(T)) | ||
| _ghost_ops_compressed(::DiscreteAxis{T, :periodic, :periodic}, n::Int, gbf) where {T} = | ||
| ((true, n - 1, one(T)), (true, 2, one(T))) | ||
|
|
||
| # faces of the non-compressed dimensions: reflecting neighbors sit at rows 3 / end-2 | ||
| _ghost_ops(::DiscreteAxis{T, :fixed, :fixed}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), _no_ghost_op(T)) | ||
| _ghost_ops(::DiscreteAxis{T, :infinite, :infinite}, n::Int, gbf) where {T} = | ||
| ((true, 2, gbf[1]), (true, n - 1, gbf[2])) | ||
| _ghost_ops(::DiscreteAxis{T, :fixed, :infinite}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), (true, n - 1, gbf[2])) | ||
| _ghost_ops(::DiscreteAxis{T, :infinite, :fixed}, n::Int, gbf) where {T} = | ||
| ((true, 2, gbf[1]), _no_ghost_op(T)) | ||
| _ghost_ops(::DiscreteAxis{T, :infinite, :reflecting}, n::Int, gbf) where {T} = | ||
| ((true, 2, gbf[1]), (true, n - 2, one(T))) | ||
| _ghost_ops(::DiscreteAxis{T, :reflecting, :infinite}, n::Int, gbf) where {T} = | ||
| ((true, 3, one(T)), (true, n - 1, gbf[2])) | ||
| _ghost_ops(::DiscreteAxis{T, :reflecting, :reflecting}, n::Int, gbf) where {T} = | ||
| ((true, 3, one(T)), (true, n - 2, one(T))) | ||
| _ghost_ops(::DiscreteAxis{T, :fixed, :reflecting}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), (true, n - 2, one(T))) | ||
| _ghost_ops(::DiscreteAxis{T, :reflecting, :fixed}, n::Int, gbf) where {T} = | ||
| ((true, 3, one(T)), _no_ghost_op(T)) | ||
| _ghost_ops(::DiscreteAxis{T, :periodic, :periodic}, n::Int, gbf) where {T} = | ||
| ((true, n - 1, one(T)), (true, 2, one(T))) | ||
| # the radial axis: no left face (r = 0 is handled separately), and the | ||
| # infinite/reflecting right face reads row end-2 (as in the broadcast methods) | ||
| _ghost_ops(::DiscreteAxis{T, :r0, :infinite}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), (true, n - 2, gbf[2])) | ||
| _ghost_ops(::DiscreteAxis{T, :r0, :reflecting}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), (true, n - 2, one(T))) | ||
| _ghost_ops(::DiscreteAxis{T, :r0, :fixed}, n::Int, gbf) where {T} = | ||
| (_no_ghost_op(T), _no_ghost_op(T)) | ||
|
|
||
| @kernel function faces_bc_kernel!(rbpot, rbi::Int, | ||
| a1l::Bool, s1l::Int, f1l, a1r::Bool, s1r::Int, f1r, | ||
| a2l::Bool, s2l::Int, f2l, a2r::Bool, s2r::Int, f2r, | ||
| a3l::Bool, s3l::Int, f3l, a3r::Bool, s3r::Int, f3r) | ||
| i, j = @index(Global, NTuple) | ||
| n1 = size(rbpot, 1); n2 = size(rbpot, 2); n3 = size(rbpot, 3) | ||
| @inbounds begin | ||
| if i <= n1 && j <= n2 | ||
| if a3l rbpot[i, j, 1, rbi] = f3l * rbpot[i, j, s3l, rbi] end | ||
| if a3r rbpot[i, j, n3, rbi] = f3r * rbpot[i, j, s3r, rbi] end | ||
| end | ||
| if i <= n1 && j <= n3 | ||
| if a2l rbpot[i, 1, j, rbi] = f2l * rbpot[i, s2l, j, rbi] end | ||
| if a2r rbpot[i, n2, j, rbi] = f2r * rbpot[i, s2r, j, rbi] end | ||
| end | ||
| if i <= n2 && j <= n3 | ||
| if a1l rbpot[ 1, i, j, rbi] = f1l * rbpot[s1l, i, j, rbi] end | ||
| if a1r rbpot[n1, i, j, rbi] = f1r * rbpot[s1r, i, j, rbi] end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| function _apply_faces_bc!(rbpot::GPUArrays.AbstractGPUArray{T, 4}, rbi::Int, | ||
| ops1::NTuple{2, _GhostFaceOp{T}}, ops2::NTuple{2, _GhostFaceOp{T}}, ops3::NTuple{2, _GhostFaceOp{T}}) where {T} | ||
| kern = faces_bc_kernel!(_ka_get_backend(rbpot)) | ||
| n1, n2, n3 = size(rbpot, 1), size(rbpot, 2), size(rbpot, 3) | ||
| kern(rbpot, rbi, | ||
| ops1[1]..., ops1[2]..., ops2[1]..., ops2[2]..., ops3[1]..., ops3[2]..., | ||
| ndrange = (max(n1, n2), max(n2, n3))) | ||
| nothing | ||
| end | ||
|
|
||
| function apply_boundary_conditions!(pcs::PotentialCalculationSetup{T, Cylindrical, 3, <:GPUArrays.AbstractGPUArray}, | ||
| update_even_points::Val{even_points}, only2d::Val{only_2d}) where {T, even_points, only_2d} | ||
| rbi::Int = even_points ? rb_even::Int : rb_odd::Int | ||
| rbpot = pcs.potential | ||
| if !only_2d | ||
| apply_boundary_conditions_at_r0!(rbpot, rbi, size(pcs.ϵ_r, 3) - 1, pcs.geom_weights[2], update_even_points) | ||
| end | ||
| # red-black dimension order is (z, φ, r) | ||
| ops_z = _ghost_ops_compressed(pcs.grid.axes[3], size(rbpot, 1), pcs.grid_boundary_factors[3]) | ||
| ops_φ = only_2d ? (_no_ghost_op(T), _no_ghost_op(T)) : _ghost_ops(pcs.grid.axes[2], size(rbpot, 2), pcs.grid_boundary_factors[2]) | ||
| ops_r = _ghost_ops(pcs.grid.axes[1], size(rbpot, 3), pcs.grid_boundary_factors[1]) | ||
| _apply_faces_bc!(rbpot, rbi, ops_z, ops_φ, ops_r) | ||
| nothing | ||
| end | ||
|
|
||
| function apply_boundary_conditions!(pcs::PotentialCalculationSetup{T, Cartesian, 3, <:GPUArrays.AbstractGPUArray}, | ||
| update_even_points::Val{even_points}, only2d::Val{only_2d}) where {T, even_points, only_2d} | ||
| rbi::Int = even_points ? rb_even::Int : rb_odd::Int | ||
| rbpot = pcs.potential | ||
| ops_x = _ghost_ops_compressed(pcs.grid.axes[1], size(rbpot, 1), pcs.grid_boundary_factors[1]) | ||
| ops_y = _ghost_ops(pcs.grid.axes[2], size(rbpot, 2), pcs.grid_boundary_factors[2]) | ||
| ops_z = _ghost_ops(pcs.grid.axes[3], size(rbpot, 3), pcs.grid_boundary_factors[3]) | ||
| _apply_faces_bc!(rbpot, rbi, ops_x, ops_y, ops_z) | ||
| nothing | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -164,6 +164,11 @@ Maybe this will change in the future. | |
| """ | ||
| get_sor_kernel(::Type{Cylindrical}, backend, CPU_via_KernelAbstractions, args...) = sor_cyl_gpu!(backend, args...) | ||
| get_sor_kernel(::Type{Cartesian}, backend, CPU_via_KernelAbstractions, args...) = sor_car_gpu!(backend, args...) | ||
|
|
||
| # Workgroup shapes tuned on an A100; both are 2.4-2.6x faster than the | ||
| # KernelAbstractions default across 2D/3D cylindrical and Cartesian grids. | ||
| # Dimension 1 is the memory-contiguous red-black dimension. | ||
| _sor_kernel_workgroupsize(ndrange) = ndrange[2] == 1 ? (128, 1, 1) : (32, 4, 1) | ||
|
Comment on lines
+168
to
+171
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| get_sor_kernel(::Type{Cylindrical}, ::CPU, CPU_via_KernelAbstractions::Val{false}) = nothing | ||
| get_sor_kernel(::Type{Cartesian}, ::CPU, CPU_via_KernelAbstractions::Val{false}) = nothing | ||
| get_sor_kernel(::Type{Cylindrical}, backend::CPU, CPU_via_KernelAbstractions::Val{true}, args...) = sor_cyl_gpu!(backend, args...) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,11 @@ function _update_till_convergence!( pcs::PotentialCalculationSetup{T, S, 3}, | |
| ) where {T, S, depletion_handling_enabled, only_2d, _is_weighting_potential} # <: GPUArrays.AbstractGPUArray | ||
| backend = _ka_get_backend(pcs.potential) | ||
| ndrange = size(pcs.potential)[1:3] .- 2 | ||
| kernel = get_sor_kernel(S, backend, Val(via_KernelAbstractions)) | ||
| kernel = if backend isa _KA_Backend && !(backend isa KernelAbstractions.CPU) | ||
| get_sor_kernel(S, backend, Val(via_KernelAbstractions), _sor_kernel_workgroupsize(ndrange)) | ||
| else | ||
| get_sor_kernel(S, backend, Val(via_KernelAbstractions)) | ||
| end | ||
|
Comment on lines
+14
to
+18
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copying #623 (comment): Manually verify that this is passed correctly via |
||
| c_limit = _is_weighting_potential ? convergence_limit : abs(convergence_limit * (iszero(pcs.bias_voltage) ? maximum(abs.(pcs.potential)) : pcs.bias_voltage)) | ||
| c = (one(c_limit) + c_limit) * 10 # Has to be larger than c_limit at the beginning | ||
| n_performed_iterations = 0 | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From PR623: