Skip to content

Commit d581178

Browse files
[SYCL][L0] Programming guide for multi-tile and multi-card under Level-Zero backend. (#3872)
Signed-off-by: Sergey V Maslov <[email protected]>
1 parent 8d3921e commit d581178

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# Considerations for programming to multi-tile and multi-card under Level-Zero backend
2+
3+
## 1 Devices discovery
4+
### 1.1 Root-devices
5+
6+
Intel GPUs are represented as SYCL GPU devices, root-devices.
7+
The discovery of root-devices is best with "sycl-ls" tool, for example:
8+
9+
```
10+
$ sycl-ls
11+
[opencl:0] GPU : Intel(R) OpenCL HD Graphics 3.0 [21.19.19792]
12+
[opencl:0] CPU : Intel(R) OpenCL 2.1 [2020.11.11.0.03_160000]
13+
[level_zero:0] GPU : Intel(R) Level-Zero 1.1 [1.1.19792]
14+
[host:0] HOST: SYCL host platform 1.2 [1.2]
15+
```
16+
17+
Note that "sycl-ls" shows all devices from all platforms of all SYCL backends that are seen
18+
by SYCL runtime. Thus in the example above there is CPU (managed by OpenCL backend) and 2!
19+
GPUs corresponding to the single physical GPU (managed by either OpenCL or Level-Zero backend).
20+
There are few ways to filter observable root-devices.
21+
22+
One is using environment variable SYCL_DEVICE_FILTER described in [EnvironmentVariables.md](https://github.com/intel/llvm/blob/sycl/sycl/doc/EnvironmentVariables.md)
23+
```
24+
$ SYCL_DEVICE_FILTER=level_zero sycl-ls
25+
[level_zero:0] GPU : Intel(R) Level-Zero 1.1 [1.1.19792]
26+
```
27+
Another way is to use similar SYCL API described in [FilterSelector.md](https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/FilterSelector/FilterSelector.adoc)
28+
E.g. `filter_selector("level_zero")` will only see Level-Zero operated devices.
29+
30+
If there are multiple GPUs in a system then they will be seen as multiple different root-devices.
31+
On Linux these would be multiple SYCL root-devices of the same SYCL platform (representing Level-Zero driver).
32+
On Windows these would appear as root-devices of multiple different SYCL platforms (Level-Zero drivers).
33+
34+
`CreateMultipleRootDevices=N NEOReadDebugKeys=1` evironment variables can be used to emulate multiple GPU cards, e.g.
35+
```
36+
$ CreateMultipleRootDevices=2 NEOReadDebugKeys=1 SYCL_DEVICE_FILTER=level_zero sycl-ls
37+
[level_zero:0] GPU : Intel(R) Level-Zero 1.1 [1.1.19792]
38+
[level_zero:1] GPU : Intel(R) Level-Zero 1.1 [1.1.19792]
39+
```
40+
41+
### 1.2 Sub-devices
42+
43+
Some Intel GPU HW is composed of multiple tiles, e.g. 4 tile ATS.
44+
The root-device in such cases can be partitioned to sub-devices, each corresponding to the physical tiles.
45+
46+
``` C++
47+
try {
48+
vector<device> SubDevices = RootDevice.create_sub_devices<
49+
cl::sycl::info::partition_property::partition_by_affinity_domain>(
50+
cl::sycl::info::partition_affinity_domain::next_partitionable);
51+
}
52+
```
53+
54+
Each call to `create_sub_devices` will return exactly the same sub-devices and in the persistent order.
55+
To control what sub-devices are exposed by Level-Zero UMD one can use ZE_AFFINITY_MASK environment variable.
56+
57+
NOTE: The `partition_by_affinity_domain` is the only partitioning supported for Intel GPU.
58+
Similar `next_partitionable` and `numa` are the only partitioning properties supported (both doing the same thing).
59+
60+
`CreateMultipleSubDevices=N NEOReadDebugKeys=1` environment variables can be used to emulate multiple tiles of a GPU.
61+
62+
## 2 Context
63+
64+
Contexts are used for resources isolation and sharing. A SYCL context may consist of one or multiple devices.
65+
Both root-devices and sub-devices can be within single context, but they all should be of the same SYCL platform.
66+
A SYCL program (kernel_bundle) created against a context with multiple devices will be built to each of the root-devices in the context.
67+
For context that consists of multiple sub-devices of the same root-device only single build (to that root-device) is needed.
68+
69+
## 3 Memory
70+
### 3.1 USM
71+
72+
There are multiple ways to allocate memory:
73+
74+
`malloc_device`:
75+
- Allocation can only be accessed by the specified device but not by other devices in the context nor by host.
76+
- The data stays on the device all the time and thus is the fastest available for kernel execution.
77+
- Explicit copy is needed for transfer data to the host or other devices in the context.
78+
79+
`malloc_host`:
80+
- Allocation can be accessed by the host and any other device in the context.
81+
- The data stays on the host all the time and is accessed via PCI from the devices.
82+
- No explicit copy is needed for synchronizing of the data with the host or devices.
83+
84+
`malloc_shared`:
85+
- Allocation can be accessed by the host and the specified device only.
86+
- The data can migrate (operated by the Level-Zero driver) between the host and the device for faster access.
87+
- No explicit copy is necessary for synchronizing between the host and the device, but it is needed for other devices in the context.
88+
89+
NOTE: Memory allocated against a root-device is accessible by all of its sub-devices (tiles).
90+
So if operating on a context with multiple sub-devices of the same root-device then you can use `malloc_device` on that root-device instead of using the slower `malloc_host`.
91+
Remember that if using `malloc_device` you'd need an explicit copy out to the host if it necessary to see data there.
92+
93+
### 3.2 Buffer
94+
95+
SYCL buffers are also created against a context and under the hood are mapped to the Level-Zero USM allocation discussed above.
96+
The current mapping is following:
97+
98+
- For integrated device the allocations are made on host, and are accessible by the host and the device without any copying.
99+
- Memory buffers for context with sub-devices of the same root-device (possibly including the root-device itself) are allocated on that root-device.
100+
Thus they are readily accessible by all the devices in such context. The synchronization with the host is performed by SYCL RT with map/unmap doing implicit copies when necessary.
101+
- Memory buffers for context with devices from different root-devices in it are allocated on host (thus made accessible to all devices).
102+
103+
## 4 Queue
104+
105+
SYCL queue is always attached to a single device in a possibly multi-device context.
106+
Some typical scenarios are the following (from most performant to least performant):
107+
108+
**A.** Context with a single sub-device in it and the queue is attached to that sub-device (tile)
109+
- The execution/visibility is limited to the single sub-device only
110+
- Expected to offer the best performance per tile
111+
- Example:
112+
``` C++
113+
try {
114+
vector<device> SubDevices = ...;
115+
for (auto &D : SubDevices) {
116+
// Each queue is in its own context, no data sharing across them.
117+
auto Q = queue(D);
118+
Q.submit([&](handler& cgh) {...});
119+
}
120+
}
121+
```
122+
123+
**B.** Context with multiple sub-devices of the same root-device (multi-tile)
124+
- Queues are to be attached to the sub-devices effectively implementing "explicit scaling"
125+
- The root-device should not be passed to such context for better performance
126+
- Example:
127+
``` C++
128+
try {
129+
vector<device> SubDevices = ...;
130+
auto C = context(SubDevices);
131+
for (auto &D : SubDevices) {
132+
// All queues share the same context, data can be shared across queues.
133+
auto Q = queue(C, D);
134+
Q.submit([&](handler& cgh) {...});
135+
}
136+
}
137+
```
138+
139+
**C.** Context with a single root-device in it and the queue is attached to that root-device
140+
- The work will be automatically distributed across all sub-devices/tiles via "implicit scaling" by the driver
141+
- The most simple way to enable multi-tile HW but doesn't offer possibility to target specific tiles
142+
- Example:
143+
``` C++
144+
try {
145+
// The queue is attached to the root-device, driver distributes to sub-devices, if any.
146+
auto D = device(gpu_selector{});
147+
auto Q = queue(D);
148+
Q.submit([&](handler& cgh) {...});
149+
}
150+
```
151+
152+
**D.** Contexts with multiple root-devices (multi-card)
153+
- The most unrestrictive context with queues attached to different root-devices
154+
- Offers most sharing possibilities at the cost of slow access through host memory or explicit copies needed
155+
- Example:
156+
``` C++
157+
try {
158+
auto P = platform(gpu_selector{});
159+
auto RootDevices = P.get_devices();
160+
auto C = context(RootDevices);
161+
for (auto &D : RootDevices) {
162+
// Context has multiple root-devices, data can be shared across multi-card (requires explict copying)
163+
auto Q = queue(C, D);
164+
Q.submit([&](handler& cgh) {...});
165+
}
166+
}
167+
```
168+
169+
Depending on the chosen programming model (A,B,C,D) and algorithm used make sure to do proper memory allocation/synchronization.
170+
171+
## 5 Examples
172+
173+
These are few examples of programming to multiple tiles and multiple cards:
174+
- https://github.com/jeffhammond/PRK/blob/dpct/Cxx11/dgemm-multigpu-onemkl.cc
175+
- https://github.com/pvelesko/PPP/tree/master/languages/c%2B%2B/sycl/gpu2gpu

sycl/doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ Developing oneAPI DPC++ Compiler
3434
LinkedAllocations
3535
Assert
3636
SharedLibraries
37+
MultiTileCardWithLevelZero
3738

0 commit comments

Comments
 (0)