Design Space Exploration

In this section, you will learn about the following components in Spatial:

  • DSE Parameters

  • Interpreting DSE Results

Overview

Design spaces can become overwhelmingly large very quickly. Spatial provides tools for helping a user explore the space intuitively and quickly. Under the hood, it uses neural networks to model the target devices, and tools such as HyperMapper to quickly and intelligently explore the space.

While we introduce the DSE tools in this section, there is still work to be done by the developer to build better apps. In the performance tutorial, we explain how to work in parallel with DSE to get more performance out of Spatial.

 

Basic implementation

import spatial.dsl._

@spatial object HelloSpatialDSE extends SpatialApp {
  def main(args: Array[String]): Void = {
    // Create Args
    val N = ArgIn[Int]
    val y = ArgOut[Int]

    // Set arg
    val elems = args(0).to[Int]
    setArg(N, elems)

    // Create DRAM
    val dram = DRAM[Int](N)

    // Populate DRAM
    val data = Array.tabulate(elems){i => i}
    setMem(dram, data)

    // Create DSE parameters
    val tileSize = 32 (16 -> 16 -> 128) // "16 to 128 in increments of 16.  Default = 32"
    val loadPar = 1 (1, 2, 4, 8, 16) // "Any value in the list. Default = 1"
    val reducePar = 2 (1 -> 8) // "1 to 8 in increments of 1. Default = 2"
    bound(N) = 2048 // Set "default" value of runtime arg for model analysis

    // Don't allow user to hit corner cases
    assert(elems % tileSize == 0)

    Accel {
      val sram = SRAM[Int](tileSize)
      val global_accum = Reg[Int](0)

      Reduce(global_accum)(N by tileSize) { i =>
        sram load dram(i::i + tileSize par loadPar)
        val local_accum = Reg[Int](0)
        Reduce(local_accum)(tileSize by 1 par reducePar){j =>
          sram(j)
        }{_+_}
        local_accum.value
      }{_+_}

      y := global_accum.value
    }

    // Report the answer
    println(r"Result is ${getArg(y)}")
  }
}

The application on the left shows an example application written in a way that exposes tuneable parameters to DSE. Specifically, the programmer must mark these variables using one of the following syntax options:

  • (<min> -> <max>) - All values between min and max (inclusive) are possible selections

  • (<min> -> <step> -> <max>) - All values between min and max (inclusive), striding by step each time, are possible selections.

  • (<a>, <b>, …, <z>) - Any value in this list is a possible selection.

In this particular application, we program the accelerator to fetch data from DRAM and find the total sum. The three parameters we expose are tileSize (size of on-chip memory and number of contiguous elements per DMA request), loadPar (parallelization of the dequeue operation off of the DMA bus), and reducePar (parallelization of the innermost reduction).

While the programmer may be tempted to choose the maximum possible tile sizes and parallelization factors, there are often complex trade-offs between these choices in terms of resource utilization and performance. For example, increasing load parallelization may not improve performance much if the bottleneck is the reduction (see the instrumentation tutorial for more information on this kind of topic). The goal of the DSE tool is to automatically and rapidly search the space of parameters you have provided to find the optimal point. Note that many optimizations related to restructuring the code and controller hierarchy are not visible to the DSE tool.

In addition to the explicitly annotated variables, all outer controllers that are not explicitly marked with a schedule are part of the DSE search. The tool checks whether the Sequential or Pipelined schedule for each outer controller is optimal.

Spatial contains built-in resource utilization models for its IR. These models are structured as XGBoost models. Ensure you have these models (stored as pmmls) in $SPATIAL_HOME/models/resources. If you do not have pmmls there, try running git submodule update --init. If you have files in this directory but they are corrupted and only contain a few lines of text, make sure you run git lfs install, before getting the submodule, as they are stored using git lfs.

Spatial also contains built-in runtime models. For runtime-dynamic portions of your application (the value of N in our example on the left), the programmer must provide a default value for the DSE tool to use. In this case, we set it to 2048, meaning it will treat any instance of N in our application as a constant 2048 when it builds the runtime model of our application.

The compiler will spit out a pre-DSE and post-unrolling runtime model in <gendir>/<appname>/models/model_dse.scala and <gendir>/<appname>/models/model_final.scala. You can run make dse-model, make final-model, or make sensitivity in the generated direcotry to play with the model yourself after the app completes. This allows you to set runtime-dynamic parameters and dse parameters in an interactive way. Run make help to see more information in the generated directory.

To run an application and apply the DSE tool to it, use the --tune flag when compiling the Spatial application. There are a few supported modes right now, specifically --bruteforce and --hypermapper. The former will exhaustively search the parameter space, while the latter uses the multi-objective Bayesian Optimization tool called Hypermapper. This is the recommended mode. For more details on using Hypermapper and Spatial together, see this tutorial.