├── .gitignore ├── requirements.txt ├── LICENSE ├── README.md └── futhark_with_fangs.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyopencl 2 | numpy 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018, Troels Henriksen 4 | 5 | Permission to use, copy, modify, and/or distribute this software for 6 | any purpose with or without fee is hereby granted, provided that the 7 | above copyright notice and this permission notice appear in all 8 | copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 11 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 12 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 13 | AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 14 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 15 | PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 16 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | PERFORMANCE OF THIS SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Futhark with Fangs! 2 | 3 | Futhark with Fangs is a small Python script that provides a web API to 4 | a [Futhark](https://futhark-lang.org) program. You thought GPU 5 | performance was inhibited by PCI Express bandwidth? Just wait until 6 | you have tried tunneling all your data over HTTP! 7 | 8 | ## Usage 9 | 10 | Create an amazing Futhark program, say, `futapp.fut`: 11 | 12 | ``` 13 | entry dotprod (xs: []i32) (ys: []i32) = reduce (+) 0 (map2 (+) xs ys) 14 | 15 | entry sumrows (xss: [][]i32) = map (reduce (+) 0) xss 16 | ``` 17 | 18 | Compile it to a Python module: 19 | 20 | ``` 21 | $ futhark pyopencl --library futapp.fut 22 | ``` 23 | 24 | Give it some fangs! 25 | 26 | ``` 27 | $ ./futhark_with_fangs.py futapp 28 | ``` 29 | 30 | Now go to another shell and POST some data with 31 | [curl](https://curl.haxx.se/), or your favourite HTTP library in your 32 | favourite programming language: 33 | 34 | ``` 35 | $ echo '[1,2] [3,4]' | curl -X POST --data-binary @- localhost:8000/dotprod 36 | 10i32 37 | ``` 38 | 39 | If an error occurs, you will probably get an appropriate HTTP status 40 | code back (404 for missing entry point, 400 if you pass it bad data, 41 | 500 if it fails for some other reason). 42 | 43 | ## Options 44 | 45 | By default, `futhark_with_fangs.py` listens on port 8000 on localhost. 46 | Use `--port` and ``--host`` to change it. 47 | 48 | ## Requirements 49 | 50 | * Python 3 (this ~~is~~ was 2018, people) 51 | * PyOpenCL 52 | * Numpy 53 | * Futhark 0.4.1 or newer. 54 | 55 | If you are lucky, you can run `pip3 install -r requirements.txt` to 56 | get the Python dependencies. 57 | -------------------------------------------------------------------------------- /futhark_with_fangs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Python module for providing a web RPC frontend to Futhark programs. 4 | 5 | Can be run as a program, in which case the first argument must be the 6 | name of a module produced by 'futhark pyopencl --library' (i.e. the 7 | file name without the 'py' part). 8 | """ 9 | 10 | __author__ = "Troels Henriksen" 11 | __copyright__ = "Copyright 2017, Troels Henriksen" 12 | __credits__ = ["Troels Henriksen"] 13 | __license__ = "ISC" 14 | __version__ = "1.0" 15 | __maintainer__ = "Troels Henriksen" 16 | __email__ = "athas@sigkill.dk" 17 | __status__ = "Dangerous" 18 | 19 | import io 20 | import pyopencl 21 | from http.server import * 22 | 23 | def listify(x): 24 | """If a tuple, turn into a list. If not, put it in a single-element list.""" 25 | if type(x) is tuple: 26 | return list(x) 27 | else: 28 | return [x] 29 | 30 | class FutharkRequestHandler(BaseHTTPRequestHandler): 31 | def __init__(self, request, client_address, server, module, instance): 32 | self.instance = instance 33 | self.module = module 34 | super().__init__(request, client_address, server) 35 | 36 | def do_POST(self): 37 | try: 38 | fname = self.path[1:] 39 | if not (fname in self.instance.entry_points): 40 | self.send_error(404, message='valid endpoints are: {}' 41 | .format(', '.join(self.instance.entry_points.keys()))) 42 | return 43 | 44 | (param_ts, ret_ts) = self.instance.entry_points[fname] 45 | reader = self.module.ReaderInput(self.rfile) 46 | args = list(map(lambda t: self.module.read_value(t, reader=reader), param_ts)) 47 | 48 | f = getattr(self.instance, fname) 49 | try: 50 | results = listify(f(*args)) 51 | except AssertionError as e: 52 | self.send_error(400, str(e)) 53 | else: 54 | self.send_response(200) 55 | self.end_headers() 56 | out = io.TextIOWrapper(self.wfile) 57 | for result in results: 58 | # We cannot print PyOpenCL arrays directly, so 59 | # turn them into Numpy arrays first. 60 | if type(result) == pyopencl.array.Array: 61 | result = result.get() 62 | self.module.write_value(result, out=out) 63 | out.write('\n') 64 | out.detach() # Avoid closing self.wfile when 'out' goes out of scope. 65 | except Exception as e: 66 | self.send_error(500) 67 | raise e 68 | 69 | def futhark_with_fangs(module, instance=None, 70 | server_address=('', 8000)): 71 | """Run a web frontend for a 'futhark pyopencl'-generated module.""" 72 | if instance == None: 73 | instance = module.__dict__[module.__name__]() 74 | httpd = HTTPServer(server_address, 75 | lambda request, client_address, server: 76 | FutharkRequestHandler(request, client_address, server, 77 | module, instance)) 78 | httpd.serve_forever() 79 | 80 | if __name__ == '__main__': 81 | import argparse 82 | import sys 83 | 84 | parser = argparse.ArgumentParser(description='Futhark with Fangs!') 85 | parser.add_argument('module', metavar='MODULE', 86 | help='The module to serve.') 87 | parser.add_argument('--host', metavar='HOST', default='', 88 | help='The hostname to listen from.') 89 | parser.add_argument('--port', metavar='PORT', type=int, default=8000, 90 | help='The port to listen from.') 91 | args = parser.parse_args() 92 | 93 | sys.path = ["."] + sys.path 94 | module = __import__(args.module) 95 | futhark_with_fangs(module, server_address=(args.host, args.port)) 96 | --------------------------------------------------------------------------------