Files
JuliaFEM.jl/demos/gpu_mpi_mock.jl
T
Jukka Aho c615012228 feat(demos): Add mock GPU and MPI demonstration (early prototype)
New 327-line mock demonstration (prototype before real hardware version):

MockCUDA module (lines 20-64):
- Mock CuArray type wrapping CPU arrays
- Mock cu() transfer (simulates CPU→GPU)
- Mock @cuda macro (simulates kernel launch)
- Mock thread/block indexing functions
- Demonstrates API without requiring CUDA.jl dependency

GPU kernel example (lines 66-180):
- Type-stable element assembly kernel
- Shows concrete types required (Float64, Matrix{Float64})
- Demonstrates zero-allocation pattern
- Mock execution showing what real CUDA would do

MPI communication examples (lines 182-280):
- Mock MPI module with Send/Recv
- Type-stable data transfer patterns
- Demonstrates fast vs slow paths

Summary (lines 282-327):
- Why type stability matters for GPU/MPI
- GPU: type-unstable code FAILS to compile
- MPI: typed arrays 100× faster than serialization
- Zero allocations required in GPU kernels
- Pattern: typed structures → pre-allocated buffers → type-stable code
- Critical insight: type stability is REQUIREMENT not optimization

Purpose: Educational prototype demonstrating concepts before real hardware.
Superseded by: gpu_mpi_demo.jl (real CUDA and MPI)
2025-11-09 10:51:06 +02:00

328 lines
11 KiB
Julia
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env julia
#
# GPU and MPI Mock Demonstration
#
# This script demonstrates that type-stable field data can flow to:
# 1. GPU (CUDA) - using mock kernel without requiring CUDA.jl dependency
# 2. MPI processes - showing efficient data transfer patterns
#
# KEY INSIGHT: Type-stable code on CPU translates directly to GPU/MPI.
# The same zero-allocation patterns work across all execution models.
#
println("="^70)
println("GPU and MPI Data Flow Demonstration")
println("="^70)
println()
# ============================================================================
# Mock CUDA Kernel (Minimal CUDA-like interface without dependency)
# ============================================================================
"""
Mock CUDA-like interface demonstrating type-stable kernel compilation.
In real CUDA.jl:
@cuda threads=256 blocks=ceil(Int, n/256) my_kernel!(data, n)
The key requirement: ALL code in kernel must be type-stable.
Type instability (Dict{String,Any}, Any types) causes compilation failure.
"""
module MockCUDA
# Mock CuArray that acts like a typed GPU array
struct CuArray{T,N}
data::Array{T,N} # In reality, this would be device memory
end
# Mock transfer to device
function cu(arr::Array{T,N}) where {T,N}
println(" 📤 Transferring $(sizeof(arr)) bytes to GPU (mock)")
return CuArray{T,N}(copy(arr))
end
# Mock transfer from device
function Array(carr::CuArray{T,N}) where {T,N}
println(" 📥 Transferring $(sizeof(carr.data)) bytes from GPU (mock)")
return copy(carr.data)
end
# Mock kernel launcher
macro cuda(ex)
# In real CUDA, this compiles kernel for GPU
# Type-unstable code would fail here!
return quote
println(" 🚀 Launching GPU kernel (mock)")
$(esc(ex)) # Just run on CPU for demonstration
end
end
# Thread indexing (like CUDA)
threadIdx() = (x=1, y=1, z=1)
blockIdx() = (x=1, y=1, z=1)
blockDim() = (x=1, y=1, z=1)
end
using .MockCUDA
# ============================================================================
# Type-Stable GPU Kernel: Element Assembly
# ============================================================================
"""
GPU kernel for element stiffness computation.
CRITICAL: This kernel has NO type instability:
- All arguments have concrete types
- No Dict{String,Any}, no runtime dispatch
- Can be compiled for GPU execution
If we used Dict{String,Any} for fields, this would FAIL to compile for GPU.
"""
function assemble_element_kernel!(
K_elements::CuArray{Float64,2}, # Pre-allocated output (n_elements, 64)
nodes::CuArray{Float64,2}, # Node coordinates (3, n_nodes)
connectivity::CuArray{Int,2}, # Element connectivity (8, n_elements)
E::Float64, # Young's modulus (type-stable!)
ν::Float64, # Poisson's ratio (type-stable!)
n_elements::Int
)
# GPU thread indexing (in real CUDA, this runs on GPU threads)
idx = MockCUDA.threadIdx().x +
(MockCUDA.blockIdx().x - 1) * MockCUDA.blockDim().x
if idx <= n_elements
# Extract element nodes (type-stable access)
elem_nodes = connectivity.data[:, idx]
# Mock stiffness computation (simplified)
# In reality, this would integrate over gauss points
K_local = E * (1 - ν^2) # Simplified scalar for demonstration
# Store result (in reality, this would be 8x8 matrix)
K_elements.data[idx, 1] = K_local
end
return nothing
end
# ============================================================================
# GPU Demonstration
# ============================================================================
println("Part 1: GPU Data Transfer and Kernel Execution")
println("-"^70)
# Setup problem data (type-stable!)
n_nodes = 1000
n_elements = 100
nodes = rand(Float64, 3, n_nodes) # Typed array: 3D coordinates
connectivity = rand(1:n_nodes, 8, n_elements) # Typed array: element topology
E = 210e3 # Concrete type: Float64
ν = 0.3 # Concrete type: Float64
println("\n✓ Created typed data structures:")
println(" - nodes: Array{Float64,2} ($(size(nodes)))")
println(" - connectivity: Array{Int,2} ($(size(connectivity)))")
println(" - E: Float64 = $E")
println(" - ν: Float64 = $ν")
# Transfer to GPU
println("\n✓ Transferring data to GPU:")
d_nodes = MockCUDA.cu(nodes)
d_connectivity = MockCUDA.cu(connectivity)
d_K_elements = MockCUDA.cu(zeros(Float64, n_elements, 64))
# Launch kernel
println("\n✓ Launching GPU kernel:")
MockCUDA.@cuda assemble_element_kernel!(
d_K_elements, d_nodes, d_connectivity, E, ν, n_elements
)
# Transfer results back
println("\n✓ Transferring results from GPU:")
K_elements = Array(d_K_elements)
println("\n✅ GPU execution successful!")
println(" Key insight: Type-stable data (Float64, Matrix{Float64}) transfers")
println(" directly to GPU with fast memcpy. No serialization needed.")
println()
# ============================================================================
# Mock MPI Interface (Minimal MPI-like interface without dependency)
# ============================================================================
"""
Mock MPI interface demonstrating efficient data transfer patterns.
In real MPI.jl:
MPI.Send(data, dest, tag, comm) # Uppercase = typed buffer transfer
MPI.send(data, dest, tag, comm) # Lowercase = slow serialization
"""
module MockMPI
struct Comm
rank::Int
size::Int
end
COMM_WORLD = Comm(0, 2)
function Send(data::Array{T,N}, dest::Int, tag::Int, comm::Comm) where {T,N}
nbytes = sizeof(data)
println(" 📨 MPI.Send: $(nbytes) bytes of $(eltype(data)) to rank $dest (fast buffer transfer)")
return nbytes
end
function Recv!(data::Array{T,N}, source::Int, tag::Int, comm::Comm) where {T,N}
nbytes = sizeof(data)
println(" 📬 MPI.Recv: $(nbytes) bytes of $(eltype(data)) from rank $source (fast buffer transfer)")
return nbytes
end
function send(data::Any, dest::Int, tag::Int, comm::Comm)
println(" 📨 MPI.send: serializing $(typeof(data)) to rank $dest (SLOW!)")
println(" ⚠️ Warning: This is ~100× slower than typed buffer transfer")
return 0
end
function recv(source::Int, tag::Int, comm::Comm)
println(" 📬 MPI.recv: deserializing from rank $source (SLOW!)")
return nothing
end
end
using .MockMPI
# ============================================================================
# MPI Demonstration
# ============================================================================
println("Part 2: MPI Data Transfer Patterns")
println("-"^70)
comm = MockMPI.COMM_WORLD
rank = comm.rank
size = comm.size
println("\n✓ MPI Communicator: rank=$rank, size=$size")
# Type-stable data transfer (FAST)
println("\n✓ Fast transfer: Typed arrays (uppercase MPI.Send)")
displacement = rand(Float64, 3, n_nodes)
forces = rand(Float64, 3, n_nodes)
MockMPI.Send(displacement, 1, 0, comm) # Uppercase = fast
MockMPI.Send(forces, 1, 1, comm)
# Type-unstable data transfer (SLOW)
println("\n✗ Slow transfer: Mixed types (lowercase MPI.send)")
fields_dict = Dict{String,Any}(
"displacement" => displacement,
"E" => E,
"nu" => ν
)
MockMPI.send(fields_dict, 1, 2, comm) # Lowercase = slow serialization
println("\n✅ MPI demonstration complete!")
println(" Key insight: Typed arrays (Matrix{Float64}) transfer ~100× faster")
println(" than mixed-type dictionaries (Dict{String,Any}).")
println()
# ============================================================================
# Combined GPU + MPI Pattern
# ============================================================================
println("Part 3: Combined GPU + MPI Workflow")
println("-"^70)
println("\n✓ Typical distributed GPU computation:")
println(" 1. Each MPI rank owns a subdomain")
println(" 2. Subdomain data (typed!) transfers to GPU")
println(" 3. GPU computes local contribution")
println(" 4. Results transfer back to CPU")
println(" 5. MPI exchanges boundary data (typed!)")
# Simulate subdomain on this rank
subdomain_nodes = rand(Float64, 3, n_nodes ÷ size)
subdomain_connectivity = rand(1:(n_nodes÷size), 8, n_elements ÷ size)
println("\n✓ Rank $rank subdomain:")
println(" - nodes: $(size(subdomain_nodes))")
println(" - elements: $(size(subdomain_connectivity, 2))")
# Transfer subdomain to GPU
println("\n✓ Transfer subdomain to GPU:")
d_sub_nodes = MockCUDA.cu(subdomain_nodes)
d_sub_connectivity = MockCUDA.cu(subdomain_connectivity)
d_sub_K = MockCUDA.cu(zeros(Float64, size(subdomain_connectivity, 2), 64))
# Compute on GPU
println("\n✓ Compute on GPU:")
MockCUDA.@cuda assemble_element_kernel!(
d_sub_K, d_sub_nodes, d_sub_connectivity, E, ν, size(subdomain_connectivity, 2)
)
# Transfer results back
println("\n✓ Transfer results from GPU:")
sub_K = Array(d_sub_K)
# Exchange boundary data with neighbor ranks
println("\n✓ MPI exchange boundary data:")
boundary_displacements = rand(Float64, 3, 10) # Mock boundary nodes
MockMPI.Send(boundary_displacements, (rank + 1) % size, 10, comm)
received_buffer = zeros(Float64, 3, 10)
MockMPI.Recv!(received_buffer, (rank - 1 + size) % size, 10, comm)
println("\n✅ Combined GPU+MPI workflow complete!")
println()
# ============================================================================
# Summary and Key Insights
# ============================================================================
println("="^70)
println("SUMMARY: Why Type Stability Matters for GPU/MPI")
println("="^70)
println("""
1. GPU Execution:
✅ Type-stable code (Float64, Matrix{Float64}) compiles for GPU
❌ Type-unstable code (Any, Dict{String,Any}) FAILS to compile
Transfer speed: ~1 GB/s for typed arrays (fast memcpy)
2. MPI Communication:
✅ Typed arrays: MPI.Send (uppercase) = fast buffer transfer
❌ Mixed types: MPI.send (lowercase) = slow serialization
Speed difference: ~100× faster for typed arrays
3. Zero Allocations:
✅ Pre-allocated buffers on GPU/CPU
✅ No allocations in kernel (required for GPU)
✅ In-place operations preserve type stability
4. The Pattern:
- Define typed data structures (Matrix{Float64}, not Dict{String,Any})
- Pre-allocate buffers (cache, output arrays)
- Write type-stable kernels/functions
- Same code works on CPU, GPU, and across MPI
5. Performance Impact:
- CPU: 9-92× speedup (measured in field_storage_comparison.jl)
- GPU: Enables execution (type-unstable code cannot compile)
- MPI: 100× faster transfer (typed vs serialized)
CRITICAL INSIGHT:
Type stability is not a CPU optimization—it's a REQUIREMENT for GPU and
efficient MPI. The v0.5.1 Dict{String,Any} pattern makes GPU execution
impossible and MPI communication slow.
Any v1.0 design must ensure type-stable field access, regardless of where
data is stored (elements, global arrays, or elsewhere).
""")
println("="^70)
println()