Skip to main content
Now that you’ve written a complete program in the previous tutorial, this tutorial introduces a central concept in CSL: memory Data Structure Descriptors (DSDs). Memory DSDs provide an efficient mechanism for performing operations on entire tensors.

Learning Objectives

After completing this tutorial, you should know how to:
  • Define memory DSDs for tensor accesses
  • Use memory DSDs in builtin operations on tensors
  • Use builtins to initialize tensors

Example Overview

Your program will run on a single processing element (PE). Like the previous tutorial, this tutorial demonstrates the program with a simulated fabric consisting of an 8 x 3 block of PEs. The problem steps are identical to the previous tutorial. The layout file, host code, and compile and run commands are also identical. Only pe_program.csl needs to change, and this tutorial takes a closer look at those changes.

Modify the CSL

The previous tutorial created a complete CSL program using a single PE to initialize and compute y = Ax + b. What needs to change in pe_program.csl to take advantage of memory DSDs and builtin operations on tensors?
  1. Define DSDs for accessing the tensors
  2. Rewrite the gemv function to operate on these DSDs
The previous tutorial walked through layout.csl, which is the same for this tutorial. The new pe_program.csl is included below, with the changes highlighted.

Define the Memory DSDs

First, take a look at the DSDs defined for accessing b and y:
b_dsd and y_dsd are the memory DSDs for accessing b, and y, respectively. The tensor_access field defines the access pattern of these DSDs. |i| specifies the induction variable, and {M} specifies the loop bound; i.e., these DSDs access M elements. After ->, an expression is given for accessing a memory location using the induction variable. This expression must be affine, or linear plus a constant. The access pattern for these DSDs is straightforward: these DSDs loop over all M elements, in order, of their respective tensors. Now take a look at the DSD for accessing A:
This DSD accesses M elements of A, but strided by N elements; i.e., A_dsd accesses elements 0, N, 2*N, ... (M-1)*N. Because A is stored in row major format, this means that A_dsd as defined here accesses the 0th column of A.
These memory DSDs are of type mem1d_dsd, which are one-dimensional memory DSDs. CSL also provides mem4d_dsd, multidimensional memory DSDs for up to four dimensions.You can learn more about memory DSDs in the language reference guide Data Structure Descriptors.

Use the DSDs to Compute GEMV

Now that the DSDs are defined, take a look at how to use them to compute GEMV. Recall that the previous gemv() function was defined as follows:
Now, gemv() looks like this:
Notice that there’s now only one explicit loop over N, instead of two explicit loops. At each iteration, this @fmacs operation does the following:
  • performs a vector-scalar multiplication between the column of A referenced by A_dsd and the scalar x[i],
  • performs an elementwise vector addition between this result and the vector y,
  • and stores this final result into y.
Thus, each @fmacs operation increments the M elements of y by the vector-scalar product of column i of A and element i of x. The @increment_dsd_offset operation at each loop iteration increments A_dsd to reference the next column of A. This builtin operation takes A_dsd and creates a new DSD by offsetting its access by 1 f32 element. For instance, the first time this operation occurs, A_dsd will now access elements 1, N+1, 2*N+1, ... (M-1)*N+1 of A. Again, because A is stored row major, this will access the 1st column of A. Once this loop over the N columns of A is complete, y contains the result of A*x. The @fadds operation performs an elementwise vector addition between y and b, storing the result back in y. Now y contains the result of A*x + b.

Use Builtins to Initialize Tensors

You may have noticed one other slight change to this code. Instead of initializing x, b, and y, in the initialize function, this code uses builtins to provide values for them at declaration:
The @constants builtin returns a tensor of the specified type, with all elements initialized to the specified value. Thus, x is initialized as an N element tensor of all ones, and b is initialized as an M element tensor of all twos. The @zeros builtin is rather obvious. y is initialized as an M element tensor of all zeros.

Compile and Run the Program

As with the previous tutorial, compile and run this code using:
You should see a SUCCESS! message at the end of execution.

Exercises

A is stored row-major in the above code. How would you rewrite A_dsd and the gemv function if A were stored column major instead?

Next

In the next tutorial, you’ll use host-to-device memcpy, and copy host-initialized values for A, x, and b onto the device.