Skip to content

Commit bda197c

Browse files
authored
Add python examples (KhronosGroup#31)
* Add Python examples * Add vim files to gitignore * Fix Python CI: Missing runs-on, simplify miniforge download, fix typos * Add README to Python directory * Add demo-array.py PyOpenCL demo * Drop "with_echo" shell func from Python example CI * flake8: cd to python before running linter
1 parent f226eb0 commit bda197c

File tree

9 files changed

+465
-0
lines changed

9 files changed

+465
-0
lines changed

.github/workflows/presubmit.yml

+36
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,39 @@ jobs:
5454
cd build
5555
cmake ../
5656
cmake --build .
57+
58+
python:
59+
name: Exercise Python examples on ${{matrix.os}}
60+
strategy:
61+
matrix:
62+
os: [ubuntu-latest, macos-latest]
63+
runs-on: ${{ matrix.os }}
64+
steps:
65+
- uses: actions/checkout@v2
66+
- name: Environment setup
67+
run: |
68+
MINIFORGE_INSTALL_DIR=.miniforge3
69+
MINIFORGE_INSTALL_SH="Miniforge3-$(uname)-$(uname -m).sh"
70+
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/$MINIFORGE_INSTALL_SH"
71+
72+
bash "$MINIFORGE_INSTALL_SH" -b -p "$MINIFORGE_INSTALL_DIR"
73+
PATH="$MINIFORGE_INSTALL_DIR/bin/:$PATH" conda update conda --yes --quiet
74+
PATH="$MINIFORGE_INSTALL_DIR/bin/:$PATH" conda update --all --yes --quiet
75+
PATH="$MINIFORGE_INSTALL_DIR/bin:$PATH" conda env create --file python/.test-conda-env.yml --name testing --quiet
76+
77+
- name: Linter
78+
run: |
79+
80+
source ".miniforge3/bin/activate" testing
81+
(cd python && flake8)
82+
83+
- name: Run examples
84+
run: |
85+
86+
source ".miniforge3/bin/activate" testing
87+
for i in python/*.py; do
88+
echo "-----------------------------------------------------------------------"
89+
echo "RUNNING $i"
90+
echo "-----------------------------------------------------------------------"
91+
time python $i
92+
done

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
build/
22
install/
3+
4+
# vim
5+
*~
6+
.*.sw[op]

python/.test-conda-env.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: test-conda-env
2+
channels:
3+
- conda-forge
4+
- nodefaults
5+
6+
dependencies:
7+
- python=3
8+
- flake8
9+
- numpy
10+
- pocl
11+
- mako
12+
- pyopencl
13+
- pip

python/README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Using OpenCL from Python
2+
3+
The examples in this directory illustrate how to use OpenCL from
4+
[Python](https://www.python.org/) using [PyOpenCL](https://documen.tician.de/pyopencl/).
5+
6+
If you would like to try them out on your own computer (Linux/mac/Windows are all supported),
7+
follow the [installation instructions](https://documen.tician.de/pyopencl/misc.html#installation)
8+
and then simply run the example file:
9+
```sh
10+
python3 example-file.py
11+
```
12+
There is no build process and no need to compile anything.
13+
14+
## Examples
15+
16+
- `demo.py` demonstrates memory allocation and running kernels.
17+
- `demo-array.py` demonstrates memory allocation and running kernels,
18+
but using the minimal array package that comes with PyOpenCL.
19+
- `dump-properties.py` demonstrates access to properties with
20+
a sort-of reimplementation of the `clinfo` utility.
21+
- `transpose.py` demonstrates a matrix transposition kernel.
22+
23+
You can find many more examples in [PyOpenCL's Github repository](https://github.com/inducer/pyopencl/tree/main/examples).

python/demo-array.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pyopencl as cl
2+
import pyopencl.array as cl_array
3+
import numpy
4+
import numpy.linalg as la
5+
6+
a = numpy.random.rand(50000).astype(numpy.float32)
7+
b = numpy.random.rand(50000).astype(numpy.float32)
8+
9+
ctx = cl.create_some_context()
10+
queue = cl.CommandQueue(ctx)
11+
12+
a_dev = cl_array.to_device(queue, a)
13+
b_dev = cl_array.to_device(queue, b)
14+
dest_dev = cl_array.empty_like(a_dev)
15+
16+
prg = cl.Program(ctx, """
17+
__kernel void sum(__global const float *a,
18+
__global const float *b, __global float *c)
19+
{
20+
int gid = get_global_id(0);
21+
c[gid] = a[gid] + b[gid];
22+
}
23+
""").build()
24+
25+
knl = prg.sum # Use this Kernel object for repeated calls
26+
knl(queue, a.shape, None, a_dev.data, b_dev.data, dest_dev.data)
27+
28+
print(la.norm((dest_dev - (a_dev+b_dev)).get()))

python/demo.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env python
2+
3+
import numpy as np
4+
import pyopencl as cl
5+
6+
a_np = np.random.rand(50000).astype(np.float32)
7+
b_np = np.random.rand(50000).astype(np.float32)
8+
9+
ctx = cl.create_some_context()
10+
queue = cl.CommandQueue(ctx)
11+
12+
mf = cl.mem_flags
13+
a_g = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=a_np)
14+
b_g = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf=b_np)
15+
16+
prg = cl.Program(ctx, """
17+
__kernel void sum(
18+
__global const float *a_g, __global const float *b_g, __global float *res_g)
19+
{
20+
int gid = get_global_id(0);
21+
res_g[gid] = a_g[gid] + b_g[gid];
22+
}
23+
""").build()
24+
25+
res_g = cl.Buffer(ctx, mf.WRITE_ONLY, a_np.nbytes)
26+
knl = prg.sum # Use this Kernel object for repeated calls
27+
knl(queue, a_np.shape, None, a_g, b_g, res_g)
28+
29+
res_np = np.empty_like(a_np)
30+
cl.enqueue_copy(queue, res_np, res_g)
31+
32+
# Check on CPU with Numpy:
33+
print(res_np - (a_np + b_np))
34+
print(np.linalg.norm(res_np - (a_np + b_np)))
35+
assert np.allclose(res_np, a_np + b_np)

python/dump-properties.py

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import pyopencl as cl
2+
from optparse import OptionParser
3+
4+
parser = OptionParser()
5+
parser.add_option("-s", "--short", action="store_true",
6+
help="don't print all device properties")
7+
8+
(options, args) = parser.parse_args()
9+
10+
11+
def print_info(obj, info_cls):
12+
for info_name in sorted(dir(info_cls)):
13+
if not info_name.startswith("_") and info_name != "to_string":
14+
info = getattr(info_cls, info_name)
15+
try:
16+
info_value = obj.get_info(info)
17+
except Exception:
18+
info_value = "<error>"
19+
20+
if (info_cls == cl.device_info and info_name == "PARTITION_TYPES_EXT"
21+
and isinstance(info_value, list)):
22+
print("{}: {}".format(info_name, [
23+
cl.device_partition_property_ext.to_string(v,
24+
"<unknown device partition property %d>")
25+
for v in info_value]))
26+
else:
27+
try:
28+
print(f"{info_name}: {info_value}")
29+
except Exception:
30+
print("%s: <error>" % info_name)
31+
32+
33+
for platform in cl.get_platforms():
34+
print(75*"=")
35+
print(platform)
36+
print(75*"=")
37+
if not options.short:
38+
print_info(platform, cl.platform_info)
39+
40+
for device in platform.get_devices():
41+
if not options.short:
42+
print(75*"-")
43+
print(device)
44+
if not options.short:
45+
print(75*"-")
46+
print_info(device, cl.device_info)
47+
ctx = cl.Context([device])
48+
for mf in [
49+
cl.mem_flags.READ_ONLY,
50+
#cl.mem_flags.READ_WRITE,
51+
#cl.mem_flags.WRITE_ONLY
52+
]:
53+
for itype in [
54+
cl.mem_object_type.IMAGE2D,
55+
cl.mem_object_type.IMAGE3D
56+
]:
57+
try:
58+
formats = cl.get_supported_image_formats(ctx, mf, itype)
59+
except Exception:
60+
formats = "<error>"
61+
else:
62+
def str_chd_type(chdtype):
63+
result = cl.channel_type.to_string(chdtype,
64+
"<unknown channel data type %d>")
65+
66+
result = result.replace("_INT", "")
67+
result = result.replace("UNSIGNED", "U")
68+
result = result.replace("SIGNED", "S")
69+
result = result.replace("NORM", "N")
70+
result = result.replace("FLOAT", "F")
71+
return result
72+
73+
formats = ", ".join(
74+
"{}-{}".format(
75+
cl.channel_order.to_string(iform.channel_order,
76+
"<unknown channel order 0x%x>"),
77+
str_chd_type(iform.channel_data_type))
78+
for iform in formats)
79+
80+
print("{} {} FORMATS: {}\n".format(
81+
cl.mem_object_type.to_string(itype),
82+
cl.mem_flags.to_string(mf),
83+
formats))
84+
del ctx

python/setup.cfg

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[flake8]
2+
ignore = E126,E127,E128,E123,E226,E241,E242,E265,W503,E402,N,B
3+
max-line-length=85
4+
5+
inline-quotes = "
6+
docstring-quotes = """
7+
multiline-quotes = """
8+

0 commit comments

Comments
 (0)