在 GPU 上求解 ODE 系统的选项?

计算科学 显卡
2021-12-01 22:39:00

我想在“可并行化”的设置中将 ODE 的求解系统外包到 GPU 上。例如,使用 512 个不同的参数集进行敏感性分析。

理想情况下,我想使用像 CVODE 这样的智能自适应时间步长求解器进行 ODE 求解,而不是像 Forward Euler 这样的固定时间步长,而是在 NVIDIA GPU 而不是 CPU 上运行它。

有人做过吗?有图书馆吗?

2个回答

DifferentialEquations.jl库是用于高级语言 (Julia) 的库,它具有用于将 ODE 系统自动转换为优化版本以在 GPU 上并行解决方案的工具。可以采用两种形式的并行性:大型 ODE 系统的基于数组的并行性和相对较小 (<100) ODE 系统的参数研究的参数并行性。它支持高阶隐式和显式方法,并且在基准测试中通常优于或匹配其他系统(至少,它包装了其他系统,因此很容易检查和使用它们!)

对于此特定功能,您可能需要查看DiffEqGPU.jl,它是用于自动参数并行的模块。DifferentialEquations.jl 库具有并行参数研究的功能,并且该模块增强了现有配置以使研究自动并行进行。一个人所做的是将他们现有的ODEProblem(或其他DEProblem类似SDEProblem的)转换为一个EnsembleProblem,并用一个指定prob_func其他问题是如何从原型中生成的。下面用高阶显式自适应方法在 GPU 上求解 Lorenz 方程的 10000 条轨迹:

using OrdinaryDiffEq, DiffEqGPU
function lorenz(du,u,p,t)
 @inbounds begin
     du[1] = p[1]*(u[2]-u[1])
     du[2] = u[1]*(p[2]-u[3]) - u[2]
     du[3] = u[1]*u[2] - p[3]*u[3]
 end
 nothing
end

u0 = Float32[1.0;0.0;0.0]
tspan = (0.0f0,100.0f0)
p = (10.0f0,28.0f0,8/3f0)
prob = ODEProblem(lorenz,u0,tspan,p)
prob_func = (prob,i,repeat) -> remake(prob,p=rand(Float32,3).*p)
monteprob = EnsembleProblem(prob, prob_func = prob_func)
@time sol = solve(monteprob,Tsit5(),EnsembleGPUArray(),trajectories=10_000,saveat=1.0f0)

请注意,用户无需编写 GPU 代码,并且使用单个 RTX 2080 与使用具有多线程并行性的 16 核 Xeon 机器相比,该基准性能提高了 5 倍。然后可以查看自述文件,了解如何执行诸如利用多个 GPU 和执行多处理 + GPU 以同时利用完整的 GPU 集群之类的事情。请注意,切换到多线程而不是 GPU 是一项更改:EnsembleThreads()而不是EnsembleGPUArray().

然后对于隐式求解器,同样的接口成立。例如,以下使用高阶 Rosenbrock 和隐式 Runge-Kutta 方法:

function lorenz_jac(J,u,p,t)
 @inbounds begin
     σ = p[1]
     ρ = p[2]
     β = p[3]
     x = u[1]
     y = u[2]
     z = u[3]
     J[1,1] = -σ
     J[2,1] = ρ - z
     J[3,1] = y
     J[1,2] = σ
     J[2,2] = -1
     J[3,2] = x
     J[1,3] = 0
     J[2,3] = -x
     J[3,3] = -β
 end
 nothing
end

function lorenz_tgrad(J,u,p,t)
 nothing
end

func = ODEFunction(lorenz,jac=lorenz_jac,tgrad=lorenz_tgrad)
prob_jac = ODEProblem(func,u0,tspan,p)
monteprob_jac = EnsembleProblem(prob_jac, prob_func = prob_func)

@time solve(monteprob_jac,Rodas5(linsolve=LinSolveGPUSplitFactorize()),EnsembleGPUArray(),dt=0.1,trajectories=10_000,saveat=1.0f0)
@time solve(monteprob_jac,TRBDF2(linsolve=LinSolveGPUSplitFactorize()),EnsembleGPUArray(),dt=0.1,trajectories=10_000,saveat=1.0f0)

虽然这种形式要求您提供 Jacobian 以便在 GPU 上使用(目前,将很快修复),但 DifferentialEquations.jl 文档演示了如何对数值定义的函数进行自动符号 Jacobian 计算,因此仍然没有手册在这里劳作。我强烈推荐这些算法,因为像 CVODE 这样的方法的分支逻辑通常会导致线程不同步,并且无论如何在这些类型的场景中执行起来不如 Rosenbrock 方法。

通过使用DifferentialEquations.jl,您还可以访问完整的库,其中包括可以利用这种GPU 加速的全局敏感性分析等功能。它还与双数兼容,可进行快速的局部灵敏度分析基于 GPU 的代码获得了 DifferentialEquations.jl 的所有特性,例如事件处理针对不同类型问题进行优化的大量 ODE 求解器,这意味着它不仅仅是一个简单的一次性 GPU ODE 求解器,而是一个功能齐全的系统的一部分,该系统也恰好具有高效的 GPU 支持。

您可能想查看Boost 的 odeint 库Thrust它们可以像这里讨论的那样组合。