memcpy library, which moves data across the WSE’s I/O channels, and SdkRuntime, the Python
host runtime that drives it — loading programs, launching functions, and transferring data on
and off the device. This page covers how both work, starting with how memcpy routes data
through the fabric.
The functions provided by SdkRuntime manage the data transfer to and from the host’s filesystem or memory, through the host and WSE network interfaces, and finally route the data into your kernel. This last step is implemented on the WSE itself. It connects the I/O channel entry-points — which sit in fixed locations at the edges of the WSE — to your kernel, which can have a variable size and location. These I/O channels are connected to the fabric routers at PEs on the East and West edges, spaced roughly 16 rows apart. On WSE-2, there are a total of 60 channels at each edge. On WSE-3, there are a total of 62 channels at each edge.
memcpy infrastructure uses additional PEs around your kernel to route tensor data and also adds a small executable component to the kernel PEs. In addition to a halo around the kernel, the additional support PEs consume three columns on the West of the kernel and two columns on the East.
SdkRuntime supports up to 16 I/O channels, and can further reduce the I/O latency by buffer insertion on either side of the core kernel.
Set Up the memcpy Infrastructure
Thememcpy infrastructure of SdkRuntime moves data on and off the device in one of two modes:
streamingmode delivers data as a sequence of wavelets. Your kernel counts the wavelets as they arrive and acts once the full tensor has been received.copymode writes data directly into device memory without notifying your kernel — you copy the tensor in first, then launch a kernel function to act on it.
A and a function f that transforms it, computing f(A) looks different depending on the mode:
- In
streamingmode, you’d define a wavelet-triggered data task that receivesAand callsfonce all ofAhas arrived; - In
copymode, you’d copyAonto the device first, then launch a kernel that callsf.
memcpy infrastructure, you’ll need to do the following:
1
Pass compiler flags
Pass
--memcpy and --channels=k to cslc (the CSL compiler), where k is an integer between 1 and 16 specifying the number of I/O channels to use.2
Set fabric dimensions and offsets
Specify
--fabric-dims=dim_x,dim_y and --fabric-offsets=x,y to cslc, where width and height are your program rectangle’s dimensions, such that:3
Import memcpy parameters
In your top-level layout CSL file, instantiate
memcpy parameters by importing <memcpy/get_params> with an @import_module() statement. This specifies everything the infrastructure needs, including width and height of the kernel and any colors needed for streaming mode.4
Pass params to the PE program
Pass the
memcpy params to the PE program in the @set_tile_code call. These params are parameterized by the PE’s x coordinate in the program rectangle.5
Import the memcpy module
In your PE program, instantiate the
memcpy module by importing <memcpy/memcpy>.memcpy infrastructure in the top-level CSL file and the PE program will resemble the following example:
Use Streaming Mode
To usestreaming mode, you must specify colors for host-to-device and device-to-host streaming. Input streaming parameters are prefixed with MEMCPYH2D_DATA_ and output streaming parameters are prefixed with MEMCPYD2H_DATA_, followed by the tensor ID (an integer in the range 1-4) and an _ID suffix — for example, MEMCPYH2D_DATA_1_ID. Unused colors should be omitted, and only four colors per direction are allowed.
You can block and unblock the input tensor colors to overlap computation and communication.
Here’s an example instantiation of a program in the top-level CSL file using colors for memcpy streaming:
cslc as parameters. Here <i> is the tensor index (1-4) and <color_id> is the numeric ID of the color to use for that tensor:
--params=MEMCPYH2D_DATA_1_ID:1 --params=MEMCPYD2H_DATA_1_ID:16.
To stream the data into the device, you can either use a data task to read the data from the input tensor color or use a microthread (a lightweight hardware thread that can issue DSD operations without occupying the main compute engine) to read the data from a fabin_dsd. To bind a data task to an input color, call @bind_data_task at compile time:
fabout_dsd. For instance, assuming my_fabout_dsd and my_mem_buf_dsd are already defined:
Use Copy Mode
To usecopy mode to copy data to/from the device, you have to define the symbols for the tensors to be copied.
For example, the following code defines a pointer ptr_A pointing to tensor A, and exports it.
Launch Kernels
We can additionally usememcpy to launch a kernel function.
The following is an example of the kernel launching protocol. This program exports two functions to the host: f1 and f2.
Use Buffers
The compiler can insert buffers in the infrastructure to reduce the latency of the I/O. The buffer stores the wavelets from the I/O for one row of PEs while the core program rectangle is busy and cannot process the wavelets from the I/O. In other words, the buffer acts like aprefetch from the point of view of the computation.
There are two kinds of buffers: one stores the data for host-to-device transfers, and the other stores the data for device-to-host transfers. The width of the former is configured by --width-west-buf, and the width of the latter is configured by --width-east-buf. By default, --width-west-buf=0 and --width-east-buf=0, i.e., no buffers are inserted.
--width-west-buf=k means k columns of PEs are inserted to the West of the core kernel, and each PE can buffer 46 KB of data. If you have 500 PEs in a row, then 46 KB can buffer 23 wavelets per PE (recall that each wavelet holds 32 bits of data). If you want to stream or copy a tensor of size 100 per PE, then --width-west-buf=5 can buffer the whole tensor.
When compiling with --width-west-buf=k and --width-east-buf=p, you must specify --fabric-offsets=x,y such that x >= 4 + k and y >= 1, and --fabric-dims=dim_x,dim_y such that dim_x >= x + width + 3 + p and dim_y >= y + height + 1, where width and height are the width and height of the program rectangle.
SdkRuntime Host API
See SdkRuntime API Reference for full documentation of theSdkRuntime Python host API.
The SdkRuntime Python host API supports memory transfers and kernel launches through the functions memcpy_h2d(), memcpy_d2h() and launch(): memcpy_h2d() is used for host-to-device data transfers, memcpy_d2h() is used for device-to-host data transfers, and launch() is used for kernel launches.
Each function can be a blocking or nonblocking call, depending on the parameter nonblock of the API. If blocking mode (nonblock=False) is selected, the API waits until the operation is done. Otherwise, the function returns before the operation even starts. SdkRuntime can aggregate multiple nonblocking operations together to reduce the latency. However, you must take care to avoid race conditions in nonblocking mode. For example, if you have two memcpy_d2h() calls to the same destination, the content of the destination is undefined if both operations are nonblocking.
Instantiate SdkRuntime
You’ll need to import theSdkRuntime module, as well as the MemcpyDataType and MemcpyOrder modules for specifying data type and ordering of tensors.
To create an SdkRuntime object, pass the directory which contains the ELF files produced by the compiler, and the IP address of the WSE, if running on hardware, to SdkRuntime(). You can load the ELFs by load() and start the simulator or WSE with run(). After that, you can do any operation, either memory transfers or kernel launches. Finally, call stop() to shut down the simulator or WSE.
memcpy_h2d() and memcpy_d2h()
The functionmemcpy_h2d() transfers a tensor from host to device using either streaming mode or copy mode.
memcpy_d2h() transfers a tensor from device to host using either streaming mode or copy mode. The first parameter dest is the host tensor to receive the data from the device. The second parameter src is the color associated with this device-to-host transfer if streaming=True or the device symbol from which to copy if streaming=False. All other parameters are the same as memcpy_h2d().
order of memcpy_h2d() and memcpy_d2h() specifies either row-major or column-major. In both cases, the host tensor from which or to which data is copied is a 1D array of length w*h*l, where w and h are the width and height of the region of interest and l is the number of elements per PE to copy.
- Row-major
- Column-major
Mapping from 1D to
[w][h][l], l is the fastest varying dimension — elements contiguous on a PE will be contiguous on the host.memcpy_h2d() and memcpy_d2h() support both 16-bit and 32-bit data transfer via copy mode or streaming mode. When using memcpy_h2d() for a 16-bit tensor, you must perform zero extension from 16-bit to 32-bit. When using memcpy_d2h() for a 16-bit tensor, the returned array will contain 32-bit data where the higher 16 bits are zero. You have to strip out the higher 16 bits. See the sdk_utils module documentation for utilities to help perform this data transformation.
launch()
Thelaunch() function performs remote kernel launches of host-callable functions.
For example, to launch a host-callable function
my_fun with two arguments of type f32 in blocking mode, the call would look as follows: