#!/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()