Figure 1.
The basic execution unit is a thread. Threads are grouped into thread blocks. Each thread block is executed on a single multi-processor. Threads in a thread block can communicate through shared memory which is essentially a user-controlled cache. In the latest GPUs, shared memory can be configured to act as an L1 cache. Register space can be used to store data local to threads with the fastest access speed. Spill-over data that does not fit in registers goes to local memory, which is physically stored in the main memory and which is very slow. Main memory has three components. Global memory is accessible to all threads and is cached through an L2 cache that is shared among all multi-processors on the latest generation GPUs. Constant memory is typically used to store data that is used by all threads (simulation constants) and that is automatically cached. Texture memory is read-only memory with automatic cache.
Figure 2.
Three Level Search for Finding Index f of the Reaction to be Fired.
In Figure 2(a), we use a warp voting function to find the block Bl in which rf occurs. The cumulative block sums are maintained through incremental updates during simulation. In Figure 2(b), we use reduction and warp voting functions to find the chunk Cm in block Bl in which rf occurs. Finally in Figure 2(c), we use a parallel prefix sum and warp voting to find the index of rf in chunk Cm.
Figure 3.
Stoichiometric Data Structure.
The stoichiometric matrix is stored as a linear array. Since each reaction has at most 2 reactants and 2 products, each entry in the array (corresponding to one reaction) has 4 data fields. Each data field has 2 parts. The first is the index of the specie and the second is the change in the molecular count. The stoichiomteric data structure is common across all runs and therefore only a single copy is maintained in the global memory on the GPU.
Figure 4.
Reaction-Reaction Dependency Graph.
Two arrays used to represent the dependency graph for each reaction rj. The first is an array of indices. The second is the array of dependent reactions sorted by the block in which they occur. The index array is used to indicate the end point for each block in the dependent reaction array. Each element of the dependent contains the type of the reaction T, the global index of the reaction rm, the reaction constant km, and the indices of the reactant species sa, sb. The reaction-reaction dependency graph is common across all runs and therefore only a single copy is maintained in the global memory on the GPU.
Figure 5.
Updating Reaction Block Partial Sums.
For sparsely connected systems, we use one thread per block to compute the change in the sum of propensities , within a block Bi. For densely connected systems, the whole thread warp is used. The changes in the partial block sums
are found for all i = 1, 2…p in parallel using the parallel-prefix sum. Finally, the block partial propensity sums are updated in parallel as
.
Figure 6.
Figure 6(a) shows the time trajectories of two randomly selected species output from the GPU and CPU executions. Figure 6(b) shows the percentage residuals of time trajectories of two other randomly selected species.
Figure 7.
Figure 7(a) list shows the results for the Colloidal Aggregation Model (the strongly connected system). For N chemical species, the number reactions is . The number of dependent reactions is 3N–7 and therefore scales with the number of species. Figure 7(b) shows the results for the Cyclic Chain Model (the weakly connected system). For N chemical species, this network has M = N reactions. The initial molecular counts of all species [si] were set to 1. All reaction constants ki were set to 1. These initial conditions are the same as in [13]. The graphs for PSSA-CR and SPDM were obtained from [13]. Figure 7(c) shows the results for the randomly generated system. The size of the dependent reactions list varies for 8–16 for these systems. Note that running the CR algorithm on all 4 cores (one realization per core) gives a performance gain <4× over a single core run. This gain could be expected of all other serial CPU algorithms as well.
Figure 8.
Performance w.r.t. Number of Realizations.
Both the strongly connected and the weakly connected systems show a decrease in time per update as the number of realizations is increased. This decrease flattens out, indicating saturation of the GPU computing power. The strongly connected system here had the number of species at N = 252 and the weakly connected system had N = 50, 000.
Figure 9.
Performance w.r.t. Number of Reaction Blocks.
Figure 9(a) shows the results for a weakly connected system. Figure 9(b) shows the results for a strongly connected system. Note that in both instances, the warp per block performance initially increases with the number of blocks and then decreases. This is because the warp per block includes a parallel-prefix sum for each reaction block that can get expensive.