Go to Top

Continuous Integration for testing Metal GPGPU shaders for iOS with Swig, Numpy and Python

Apple Inc’s Metal is an alternative to OpenGL for graphics processing on iPhone and iPad, but it also supports general purpose data-parallel programming for GPUs (i.e. GPGPU programming) it is an alternative to OpenCL and Nvidia’s Cuda. The Metal shading language is based on the C++11 Specification [PDF] with specific extensions and restrictions, but one challenge with Metal is that it requires on-device testing, i.e. doesn’t support using the iPhone Simulator that comes with XCode. There has been workaround to deal with this – but it only removes/hides the Metal shaders from the app project and doesn’t actually test them.

Testing iOS Metal GPGPU shaders with Swig, Numpy and Python

So what if you want to test the Metal shaders and perhaps integrate them with Continous Integration such as Jenkins? One option can be to use a C++-based unit test framework, and adapt the Metal shader code to run with that (creating #ifdefs and corresponding C++ structs used in the Metal shader). However, writing shader tests in an even higher level language would be useful (e.g. Matlab or similar – e.g. Numpy), and exposing the Metal Shader to Python and numpy with SWIG can be a good alternative. The requirements to do this are:

  1. Adapt the Metal Shader with #ifdef‘s and necessary structs so it can compile as C++
  2. Create a softlink to (or copy) the ShaderFileName.metal into name ShaderFileName.cc – in order to make python SetupTools happy
  3. Create a SWIG interface file (.i) and corresponding c++ header file (.h) that uses the ShaderFileName.cc
  4. Metal Shaders supports 32 bit float and Numpy has 64 bit double precision floating, and Numpy also supports Complex Numbers. The way to support Complex Numbers in Python for easy testing (e.g. for FFT / Convolution in Deep Learning) is to annotate output in the .i file and do conversion within the Swig API to match what the Metal Shader expects, e.g.
    • %apply (int DIM1, std::complex<float>* IN_ARRAY1) {(int my_array_size, std::complex<float> *my_array)};
    • void set_complex_list(int my_array_size, std::complex<float>*my_array);
  5. Create a unit test in Python that first imports the Swig Wrapped Shader, see test_metal_deeplearningcomponentsapi.py below
  6. Create a setup.py file for installing the SWIG-wrapped Metal Shader, see setup.py below
  7. This can the easily be integrated with Jenkins (see run_by_jenkins.sh), and it is quite easy to add more Metal Shaders to test when you have it up and running
# test_metal_deeplearningcomponentsapi.py
import numpy 
import pytest
# import Swig library with Metal Shader
from deeplearningcomponentsapi.deeplearningcomponentsapi import DeepLearningComponentsAPI

class TestDeepLearningComponentsAPI(object):
    def test_fft1d(self):
        api = DeepLearningComponentsAPI()
        input_vector = api.create_complex_list(4)
        input_vector[0] = 1.0
        input_vector[1] = 2.0
        input_vector[2] = 8.0
        input_vector[3] = 3.0
        pyres = numpy.fft.fft(input_vector)
        api.set_complex_list(input_vector)
        api.fft1d(input_vector)
        assert numpy.allclose(pyres, input_vector)
# setup.py

from distutils.core import setup, Extension
from distutils.sysconfig import get_config_vars
import os
import numpy
from distutils import sysconfig
import subprocess

os.environ['CC'] = 'g++'
os.environ['CXX'] = 'g++'
os.environ['CPP'] = 'g++'
os.environ['LDSHARED'] = 'g++'

if "Darwin" in os.uname()[0]:
    python_version = "2.7"
    os.environ['CC'] = 'c++'
    os.environ['CXX'] = 'c++'
    os.environ['CPP'] = 'c++'
    os.environ['LDSHARED'] = 'c++'
    extra_compile_args = ['-std=c++0x']
    extra_link_args = ['-shared']

    for key in sysconfig_compile_vars:
        if '-mno-fused-madd' in str(sysconfig_compile_vars[key]):
            sysconfig_compile_vars[key] = sysconfig_compile_vars[key].replace('-mno-fused-madd','-Wno-deprecated-register')
        if '-Wall' in str(sysconfig_compile_vars[key]):
            sysconfig_compile_vars[key] = sysconfig_compile_vars[key].replace('-Wall', '-Wall -Wno-unused-parameter -Wno-self-assign')


# remove erranous default parameter Wstrict-prototypes
(opt,) = sysconfig.get_config_vars('OPT')
os.environ['OPT'] = " ".join(
    flag for flag in opt.split() if flag != '-Wstrict-prototypes'
)

setup( 
    name = "deeplearningcomponentsapi",
    author = 'Amund Tveit', 
    author_email = 'amund@memkite.com',
    version = "0.01",
    ext_modules = [ 
        Extension( 
            "deeplearningcomponentsapi._deeplearningcomponentsapi",
            sources = ["deeplearningcomponentsapi/deeplearningcomponentsapipy.i", "metalshaders/deeplearningcomponents.cc"], 
            swig_opts = ["-Wall","-c++", "-v", "-Ideps"], 
            libraries=['%s/lib/python2.7' % subprocess.Popen(
                ('python-config', '--prefix'), stdout=subprocess.PIPE).communicate()[0].strip()],
            #include_dirs = ['/usr/include/python2.7'],
            include_dirs = [numpy.get_include()],
            extra_compile_args = ['-std=c++0x','-Wunused-variable'],
            extra_link_args = ['-shared'],
            language="c++"
            ),
        ],
    packages=['deeplearningcomponentsapi'],
    package_dir={'deeplearningcomponentsapi': 'deeplearningcomponentsapi'}
    )
#!/bin/bash
# run_by_jenkins.sh

export PATH="/usr/local/bin:$PATH"
if [ ! -d "venv" ]; then
        virtualenv venv
fi
source venv/bin/activate
pwd
ls -al
pip install -r requirements.txt 

# installs actual library
python setup.py build_ext --inplace

# then run tests.
nose2 --verbose --plugin nose2.plugins.junitxml --junit-xml deeplearningcomponentsapi --with-cov --coverage-report html --coverage-report xml --coverage-config dot_coverage

 

Sample Jenkins Output

Screen Shot 2015-05-20 at 12.40.08

Conclusion

Hope you find it useful, please let me know if you have ideas how to extend this approach or other approaches for testing iOS Metal shaders together with Continuous Integration.

Best regards,

Amund Tveit (amund@memkite.com)

, , , ,

About Amund Tveit (@atveit - amund@memkite.com)

Amund Tveit works in Memkite on developing large-scale Deep Learning and Search (Convolutional Neural Network) with Swift and Metal for iOS (see deeplearning.education for a Memkite app video demo). He also maintains the deeplearning.university bibliography (github.com/memkite/DeepLearningBibliography)

Amund previously co-founded Atbrox , a cloud computing/big data service company (partner with Amazon Web Services), also doing some “sweat equity” startup investments in US and Nordic startups. His presentations about Hadoop/Mapreduce Algorithms and Search were among top 3% of all SlideShare presentations in 2013 and his blog posts has been frequently quoted by Big Data Industry Leaders and featured on front pages of YCombinator News and Reddit Programming

He previously worked for Google, where he was tech.lead for Google News for iPhone (mentioned as “Google News Now Looks Beautiful On Your iPhone” on Mashable.com), lead a team measuring and improving Google Services in the Scandinavian Countries (Maps and Search) and worked as a software engineer on infrastructure projects. Other work experience include telecom (IBM Canada) and insurance/finance (Storebrand).

Amund has a PhD in Computer Science. His publications has been cited more than 500 times. He also holds 4 US patents in the areas of search and advertisement technology, and a pending US patent in the area of brain-controlled search with consumer-level EEG devices.

Amund enjoys coding, in particular Python, C++ and Swift (iOS)

One Response to "Continuous Integration for testing Metal GPGPU shaders for iOS with Swig, Numpy and Python"

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>