在CUDA上实现基于D3Q19模型的LBM流体模拟方法简介

LBM(Lattice Boltzmann Model)方法简介:
   LBM是始与上世纪80年代末起源于70年代的LGA(格子气自动机)理论模型,并最终从90年代初开始趋于成熟(在适定的范围内)的一种流体计算的介观方法(处于微观与宏观之间),从本质上讲,它比N-S方程的描述更本质化。LBM是一种基于统计物理学方法,事实上我们这里所说的LBM是为了简化Bolztmann方程的求解而进行适当但确异常合理和有效的计算模型,因为在该模型中不存在非线性项因而不存在传统数值方法(如预处理共轭梯度发和多重网格法)的数值发散问题,也即内迭代收敛问题。所谓内迭代收敛是指在某个当前的时间步在有限步之后是否可以得到当前流场的正确解(例如当我们在某个时间点上运行N次PCG解算器,这N次迭代并非是按时间方向向前推进,而是一个数学上的N次计算步骤)。虽然如此,LBM和传统方法一样也存在所谓的外迭代收敛问题;外迭代是指流场在整个时空尺度上是否可以在有限步之后收敛与平衡状态,也就是说在时间方向上流场收敛与稳态的快慢。此外LBM目前基本只适用于低March数不可压缩(或允许存在小尺度可压缩性)的情况,对于可压缩流体模型还未成熟,不过已经有很多这方面的研究发表。与传统数值方法相比LBM的另一个优点是对复杂边界的适应性,但对于自由表面LBM目前仍然没有完美的解决方法,一般和传统方法一样采用level set。

LBM计算模型:
  目前常用的规则网格模型有D2Q9,D3Q13,D3Q15,D3Q19(还有D1Q3,D2Q5,D3Q27),
当然还有各种非规则以及类似于传统网格划分的自适应格子布局。有兴趣的可以参考下郭照立 郑楚光合编的《格子Bolztmann方法的原理及应用》。
   
上面分别是D2Q9和D3Q19的LBM规则格子模型。下面按方向给出几种常用格子的布局。
为此我们先有如下约定:
  pA:A轴正方向 nA:A轴负方向;A={X,Y,Z}
  E={e0,e1,e2…,en}:格子运动矢量
  Oe:指向格子自身的矢量
于是:
D2Q9  :{ Oe , nX , pX , nY ,pY , nXnY, nXpY , pXnY, pXpY }

                       
          
 

 

D3Q15 { Oe ,
nX , pX , nY , pY , nZ , pZ ,        //主轴方向
nXnYnZ , nXnYpZ , nXpYnZ , nXpYpZ , //格子正方体的8最长个对角线
pXnYnZ , pXnYpZ , pXpYnZ , pXpYpZ
             }

D3Q19: { Oe ,
nX , pX , nY , pY , nZ , pZ , //主轴方向
nXnY , nXpY , pXnY , pXpY ,   //与3个主轴共面的对角线
nYnZ , nYpZ , pYnZ , pYpZ ,
nZnX , nZpX , pZnX , pZpX
             }

CUDA计算架构要点简介:

1.基本可以看作是局部并行,全局并发的APRAM模型。
最小并发单位是half warp.一个thread warp被scheduler split成2个hwarp交替执行,但哪个先执行的顺序没有定义,而且 同一个warp中的2个hwarp没有bank冲突.虽然乱序执行,但同一个warp的2个hwarp的执行是连续的.也即属于同一个warp 的2个half warp不能分别插在其他的warp序列中. 每个half warp的thread都是按照连续的laneid分别在一个MP(Multiprocessor)上的8个SP(Stream Processor)上执行,half warp中的后序8xlane计算发生在前序 8xlane计算的时钟上升沿,总的来说平均一个shader时钟执行一个8道计算.至于hwarp中的2个8道计算的折叠顺序没有明确定义, 但是我相信是按小序执行,即先lane(0)~lane(7),然后lane(8)~lane(15),这可以通过cuda profiler或者简单的程序得到验证。所以可以将CUDA的SIMT模型看作是在half warp/2尺度指令级或机器级并行以及half  warp尺度的线程级并发机器(当然对于整个thread grid也等价于block级并发操作)。

2.对于共享缓寸的thread block机制类似CRCW模型(允许同时读同时写)。但是:
     如果多个线程”写入”同一个地址,虽然会保证总有一个被写入,但具体哪个没有定义,且会使thread序列化(串行执行),严重影响效率。

3.对于shared memory使用不当时的bank conflicts行为,比如
     extern __shared__ float2 smem[];
     …
     float2 reg=smem[threadIdx.x];
     …
   这种由于数据类型引起的bank冲突只会在线程并发中出现,而对于广播行为则不会存在,例如:
     假设block布局为16xN
     extern __shared__ float2 smem[];
     …
     reg=smem[threadIdx.y];//每个half warp内的所有thread读取相同的位置
   另外在这种情况下如果想要更新共享内存切记不要试图消除divergence branck,否则会由于half warp中的线程竞争产生bank conflicts.
外话:这一点在实现斯坦福大学的那篇《Stackless KD-Tree Traversal for High Performance GPU Ray Tracing >>的文章中描述的基于标准的PRAM CRCW模型的光线追踪算法的实现上特别重要,但同时也要注意,那里所描述的算法并不能严格映射到CUDA架构上,因为前面所说的原因,所以其中描述的基于标准CRCW模型的PRAM_OR操作并不能在O(1)时间内完成,而是O(log2N)(N=count threads of one ray packet,在我的实现中是每个half warp组成一个光线包,因此是O(4))
    
4.合理布局网格经常会提高程序的效率
这一点在下面将要描述的LBM模拟中都尤其重要.至于如何优化网格布局,下面的讲解中将会陆续提到。

5.常量缓寸的使用
   处理手册上提到的合理利用boardcast机制外,另应该特别注意:使用常量缓寸所减少寄存器的数量,是否可以增加可并发的thread block数量.如:
   一个TB布局为(128,1,1),占用的共享缓寸大小为5120,每个thread使用32个寄存器(在不使用const memory时并且不在nvcc中加寄存器数量限制的话),这时最大可并发线程块数量为2,如果使用常量缓寸不能使每个线程寄存器减少到 8192/384,那么效率反而不如直接将这些常量定义成宏(因为并没有提高可并发的TB数量以使得TB之间进行切换是想要更好的隐藏延迟的作用)。对于我这里一个由3个常量缓寸减少2个寄存器但没有增加可并发TB数量的情况,比直接作为宏调用慢了大约 -150ms+.
  
好了,不多废话,进入主题:

LBGK模型概述:
 
LBGK动量方程为:
∆f=Ω,
∆f 为迁移项,Ω为碰撞项
:
f(x+ei*δt,t+δt)-f(x,t)=—(1/ς)*(f(x,t)-feq(u,ρ)) (1)

fnew= fcurr-Ω ,feq为速度的平衡态分布
ς是松弛时间变量(我们这里主要针对单松弛时间法的LBGK模型,也有MRT(Multi Relaxtion Time))
它对应与N-S方程的黏度项:
  ς=(3.f*kinetic_visc+0.5f)
其中kinetic_visc是动力学黏度,它的计算有赖于Reyholds数(马赫数).
 
各种DnQm的Boltzmann格子模型的feq可统一表达成形式:


feq_i=ω_i*ρ*(1+(e_i•u_i)/(cs^2)+((e_i•u_i)^2)/(2*(cs^4))+(u_i^2)/(2*(cs^2)) (2)
  ω是权重,满足归一化条件,即:
   ∑ω=1
cs是一个与声速有关的标量,对于前面提到的模型统一取为c/√3,c是由格子物理尺度决定的单位动力学速度的模。N-S方程的扩散项和压力都隐藏在LBGK方程的碰撞项的二次项里,平流的作用则通过格子之间的迁移间接的表现。如果想要了解当前流场的压力分布,很容易:

p=cs*ρ

不同模型的取值如下:

D2Q9:
  中心点  :4/9
  主轴方向:1/9
  对角线上:1/36

D3Q15:
  中心点        :2/9
  主轴方向      :1/9
  最长对角线方向:1/72

D3Q19:
  中心点              :1/3
  主轴方向            :1/18
  与主平面共面的对角线:1/36

D3Q27:
  中心点              :8/27
  主轴方向            :2/27
  与主平面共面的对角线:1/54
  最长对角线          :1/216

在LBM方法中宏观速度和密度是统计均值得到的:
 
  ρ=∑feq  , ρu=∑feq•e_i  (3)

现在需要的基本都有了,于是有如下模拟算法:

start:
0->给定初始速度和密度,然后通过(2)式初始化包分布
1->进入主循环
1.0->在当前时间步通过(3)式计算得到流场的宏观速度
1.1->根据边界条件更新平衡态包分布
1.2->执行碰撞
1.3->执行迁移
   ->如果流场没有收敛于平衡状态,跳转到1.0步继续执行
         ->否则结束

通过CUDA实现

需要注意的地方:
  主要注意的是访问全局内存时是否存在非合并访问。由于全局内存在Y,Z方向是自动对齐的,因此只需要考虑在X方向有偏移造成非合并访问的情况。这时可以使用共享缓寸来避免uncoalesed,在这里我的模拟数据尺寸是N=64x128x128,总共需要的全局内存大小是N*19*2*sizeof(float)+N*sizeof(float4)
之所以取64作为X方向的尺寸是因为在我的机器上可以获得最佳的执行效率(occupancy也最大:0.333,否则如果X方向取为128,Z取为64,则只有0.25,效率也低一些,我这里大约低-400~500+),但你可能需要为自己的GPU做不同的选择。
  线程块的布局:(nTHREAD,1,1)//nTHREAD=64
  网格布局    :(128,128)
特别的由于这样特殊大小的数据结构,所有的计算可以在一个核心里实现而不用另外的块边界交换处理的核心,但对于大尺寸的2D问题则需要考虑(参见<>)。其次通过共享内存的一点使用的小技巧以及使用周期性流出边界条件上也避免了流出边界单独处理的情况。
  在读取全局内存时由于内存位置与thread是对齐的因而没有read uncoalesed,而通过shared mem写入全局也避免了stored uncoalesed,同时half warp内的各个线程访问不同的bank,因此也没有bank conflicts:效率达到最佳化

  对于障碍物边界可以使用[R5]中的FH格式或MLS和YMS格式,对于非工程领域的实时CG模拟足够了

感觉有些乱,事实上我在开始写这篇文章时几乎不知道接下来往哪里写,于是顺其自然的一气呵成。可能逻辑上有些乱,但对于不善于写文章的我来说这也相当自以为是的。呼,累了,就此结束吧。

至于源代码我就不想贴在这里了,如果有兴趣可以联系我:
电话:1387474741*10+1135910281: o(O!O)o
     
QQ : 747474748-451921367

另:感谢google为我节省了一些画图的时间(上面2个DnQm模型图片)

Reference:

[R0] 郭照立 郑楚光: <>
[R1] 陈飞国, 葛蔚, 李静: <>
[R2] Tolke: <>

[R3] Jonas Tölke, Jannis Linxweiler, Sebastian Geller, Manfred Krafczyk:
<>

[R4] Samuel Williams?y, Jonathan Carter?, Leonid Oliker? John Shalf?, Katherine Yelick:
<>

[R5] Wei Li ,Zhe Fan, Xiaoming Wei, and Arie Kaufman:
<>

[R6] Lan Shi, Li Yi & Liyuan Zhang:
<>

[R8] Ye Zhao:<>

[R9] V. Heuveline ,J.-P. Weiß:
<>

本文由 Cyrosly 撰写

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/22785983/viewspace-619700/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/22785983/viewspace-619700/

你可能感兴趣的:(在CUDA上实现基于D3Q19模型的LBM流体模拟方法简介)