Skip to content

Commit

Permalink
Implement xmlrpc tuner server.
Browse files Browse the repository at this point in the history
  • Loading branch information
Kerilk committed Oct 4, 2024
1 parent 329bb57 commit 538935e
Show file tree
Hide file tree
Showing 9 changed files with 620 additions and 23 deletions.
16 changes: 8 additions & 8 deletions .github/workflows/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ jobs:
compiler: g++

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: sudo apt update; sudo apt install -y ${{ matrix.compiler }} ruby-dev libgsl-dev python3-dev valgrind
if: ${{ matrix.os == 'ubuntu-latest' }}
- run: brew install gsl automake libtool
if: ${{ matrix.os == 'macos-latest' }}
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
if: ${{ matrix.os == 'macos-latest' }}
- run: gem install --user-install rake ffi ffi-value whittle
- run: gem install --user-install rake ffi ffi-value whittle xmlrpc
- run: pip3 install --user parglare
- run: ./autogen.sh
- run: mkdir -p build
Expand All @@ -55,7 +55,7 @@ jobs:
- run: make -j check-valgrind-helgrind
working-directory: build
if: ${{ matrix.os == 'ubuntu-latest' }}
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
if: failure()
with:
name: build-and-check
Expand All @@ -78,11 +78,11 @@ jobs:
if: ${{ matrix.os == 'ubuntu-latest' }}
- run: brew install gsl automake libtool
if: ${{ matrix.os == 'macos-latest' }}
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
if: ${{ matrix.os == 'macos-latest' }}
- run: gem install --user-install rake ffi ffi-value whittle
- run: gem install --user-install rake ffi ffi-value whittle xmlrpc
- run: pip3 install --user parglare
- run: ./autogen.sh
- run: mkdir -p build
Expand All @@ -106,11 +106,11 @@ jobs:
if: ${{ matrix.os == 'ubuntu-latest' }}
- run: brew install gsl automake libtool
if: ${{ matrix.os == 'macos-latest' }}
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
if: ${{ matrix.os == 'macos-latest' }}
- run: gem install --user-install rake ffi ffi-value whittle
- run: gem install --user-install rake ffi ffi-value whittle xmlrpc
- run: pip3 install --user parglare
- run: ./autogen.sh
- run: mkdir -p build
Expand Down
1 change: 1 addition & 0 deletions bindings/python/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ EXTRA_DIST = \
test/test_objective_space.py \
test/test_configuration_space.py \
test/test_feature_space.py \
test/tuner_server.py \
setup.py

if ISMACOS
Expand Down
30 changes: 15 additions & 15 deletions bindings/python/cconfigspace/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,50 +551,50 @@ def serialize(self, format = 'binary', path = None, file_descriptor = None, call
s = ct.c_size_t(0)
res = ccs_object_serialize(self.handle, SerializeFormat.BINARY, SerializeOperation.SIZE, ct.byref(s), *options)
Error.check(res)
v = (ct.c_byte * s.value)()
v = ct.create_string_buffer(s.value)
res = ccs_object_serialize(self.handle, SerializeFormat.BINARY, SerializeOperation.MEMORY, ct.sizeof(v), v, *options)
Error.check(res)
return v
return v.raw

@classmethod
def deserialize(cls, format = 'binary', handle_map = None, map_handles = False, vector_callback = None, path = None, buffer = None, file_descriptor = None, callback = None):
if format != 'binary':
raise Error(Result(Result.ERROR_INVALID_VALUE))
mode_count = 0;
if path:
if path is not None:
mode_count += 1
if buffer:
if buffer is not None:
mode_count += 1
if file_descriptor:
if file_descriptor is not None:
mode_count += 1
if not mode_count == 1:
raise Error(Result(Result.ERROR_INVALID_VALUE))
if map_handles and not handle_map:
if map_handles and handle_map is None:
raise Error(Result(Result.ERROR_INVALID_VALUE))
o = ccs_object(0)
options = [DeserializeOption.END]
if map_handles:
options = [DeserializeOption.MAP_HANDLES] + options
if handle_map:
if handle_map is not None:
options = [DeserializeOption.HANDLE_MAP, handle_map.handle] + options
if vector_callback:
if vector_callback is not None:
vector_cb_wrapper = _get_deserialize_vector_callback_wrapper(vector_callback)
vector_cb_wrapper_func = ccs_object_deserialize_vector_callback_type(vector_cb_wrapper)
options = [DeserializeOption.VECTOR_CALLBACK, vector_cb_wrapper_func, ct.py_object()] + options
if callback:
if callback is not None:
cb_wrapper = _get_deserialize_data_callback_wrapper(callback)
cb_wrapper_func = ccs_object_deserialize_data_callback_type(cb_wrapper)
options = [DeserializeOption.DATA_CALLBACK, cb_wrapper_func, ct.py_object()] + options
elif _default_user_data_deserializer:
elif _default_user_data_deserializer is not None:
options = [DeserializeOption.DATA_CALLBACK, _default_user_data_deserializer, ct.py_object()] + options
if buffer:
s = ct.c_size_t(ct.sizeof(buffer))
res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.MEMORY, s, buffer, *options)
elif path:
if buffer is not None:
s = len(buffer)
res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.MEMORY, s, ct.create_string_buffer(buffer, s), *options)
elif path is not None:
p = str.encode(path)
pp = ct.c_char_p(p)
res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.FILE, pp, *options)
elif file_descriptor:
elif file_descriptor is not None:
fd = ct.c_int(file_descriptor)
res = ccs_object_deserialize(ct.byref(o), SerializeFormat.BINARY, SerializeOperation.FILE_DESCRIPTOR, fd, *options)
else:
Expand Down
13 changes: 13 additions & 0 deletions bindings/python/cconfigspace/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def __init__(self, handle = None, retain = False, auto_release = True):
else:
super().__init__(handle = handle, retain = retain, auto_release = auto_release)

@classmethod
def from_handle(cls, handle, retain = True, auto_release = True):
return cls(handle = handle, retain = retain, auto_release = auto_release)

def __len__(self):
v = ct.c_size_t()
res = ccs_map_get_keys(self.handle, 0, None, ct.byref(v))
Expand Down Expand Up @@ -73,3 +77,12 @@ def values(self):
res = ccs_map_get_values(self.handle, sz, v, None)
Error.check(res)
return [x.value for x in v]

def pairs(self):
sz = self.__len__()
if sz == 0:
return []
v = (Datum * sz)()
k = (Datum * sz)()
res = ccs_map_get_pairs(self.handle, sz, k, v, None)
return [(k[i].value, v[i].value) for i in range(sz)]
105 changes: 105 additions & 0 deletions bindings/python/test/test_tuner.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import unittest
import sys
import os as _os
import signal
import threading
import subprocess
import datetime
import base64
import xmlrpc.client
import ctypes as ct
from random import choice
sys.path.insert(1, '.')
sys.path.insert(1, '..')
Expand Down Expand Up @@ -167,6 +174,104 @@ def get_vector_data(otype, name):
self.assertTrue(t_copy.suggest() in [x.configuration for x in optims_2])
_os.remove('tuner.ccs')

class ServerThread(threading.Thread):
def __init__(self):
self.p = None
threading.Thread.__init__(self)

def run(self):
subprocess.run(['python3', _os.path.join(_os.path.abspath(_os.path.dirname(__file__)), 'tuner_server.py')], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL)

class TunerProxy:
def __init__(self, name = "", objective_space = None):
self.server = xmlrpc.client.ServerProxy("http://localhost:8000/")
connected = False
start = datetime.datetime.now()
while not connected:
try:
connected = self.server.connected()
except Exception as err:
if datetime.datetime.now() - start > datetime.timedelta(seconds = 10):
raise
if objective_space is not None:
self.objective_space = objective_space
buff = objective_space.serialize()
self.id, result = self.server.create(name, buff)
self.handle_map = ccs.deserialize(buffer = result.data)
else:
self.id, result = self.server.load(name)
self.handle_map = ccs.Map()
self.objective_space = ccs.deserialize(buffer = result.data, handle_map = self.handle_map, map_handles = True)
rev_map = ccs.Map()
for (k, v) in self.handle_map.pairs():
if isinstance(v, ccs.Context) or isinstance(v, ccs.TreeSpace):
rev_map[ccs.Object(v.handle, retain = False, auto_release = False)] = k
self.server.set_handle_map(self.id, rev_map.serialize())

def ask(self, count = 1):
return [ccs.deserialize(buffer = conf_str.data, handle_map = self.handle_map) for conf_str in self.server.ask(self.id, count)]

def tell(self, evals = []):
self.server.tell(self.id, [e.serialize() for e in evals])
return self

def history(self):
return [ccs.deserialize(buffer = eval_str.data, handle_map = self.handle_map) for eval_str in self.server.history(self.id)]

def history_size(self):
return self.server.history_size(self.id)

def optima(self):
return [ccs.deserialize(buffer = eval_str.data, handle_map = self.handle_map) for eval_str in self.server.optima(self.id)]

def num_optima(self):
return self.server.num_optima(self.id)

def suggest(self):
return ccs.deserialize(buffer = self.server.suggest(self.id).data, handle_map = self.handle_map)

def save(self):
return self.server.save(self.id)

def kill(self):
return self.server.kill()

def test_server(self):
thr = self.ServerThread()
thr.start()
try:

os = self.create_tuning_problem()
t = self.TunerProxy(name = 'my_tuner', objective_space = os)
func = lambda x, y, z: [(x-2)*(x-2), sin(z+y)]
evals = [ccs.Evaluation(objective_space = os, configuration = c, values = func(*(c.values))) for c in t.ask(100)]
t.tell(evals)
hist = t.history()
self.assertEqual(100, len(hist))
evals = [ccs.Evaluation(objective_space = os, configuration = c, values = func(*(c.values))) for c in t.ask(100)]
t.tell(evals)
self.assertEqual(200, t.history_size())
optims = t.optima()
objs = [x.objective_values for x in optims]
objs.sort(key = lambda x: x[0])
# assert pareto front
self.assertTrue(all(objs[i][1] >= objs[i+1][1] for i in range(len(objs)-1)))
self.assertTrue(t.suggest() in [x.configuration for x in optims])

t.save()
t_copy = self.TunerProxy(name = "my_tuner")
hist = t_copy.history()
self.assertEqual(200, len(hist))
optims_2 = t_copy.optima()
self.assertEqual(len(optims), len(optims_2))
objs = [x.objective_values for x in optims_2]
objs.sort(key = lambda x: x[0])
self.assertTrue(all(objs[i][1] >= objs[i+1][1] for i in range(len(objs)-1)))
self.assertTrue(t_copy.suggest() in [x.configuration for x in optims_2])

finally:
xmlrpc.client.ServerProxy("http://localhost:8000/").kill()
thr.join()

if __name__ == '__main__':
unittest.main()
Expand Down
Loading

0 comments on commit 538935e

Please sign in to comment.