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 2, 2024
1 parent 329bb57 commit e5cbf7f
Show file tree
Hide file tree
Showing 4 changed files with 342 additions and 5 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 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
Expand All @@ -39,7 +39,7 @@ jobs:
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 @@ -82,7 +82,7 @@ jobs:
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 Expand Up @@ -110,7 +110,7 @@ jobs:
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/ruby/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ EXTRA_DIST = \
test/test_configuration_space.rb \
test/test_distribution.rb \
test/test_evaluation.rb \
test/tuner_server.rb \
rakefile \
cconfigspace.gemspec \
LICENSE
Expand Down
125 changes: 125 additions & 0 deletions bindings/ruby/test/test_tuner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,130 @@ def test_user_defined
assert( t_copy.optima.collect(&:configuration).include?(t_copy.suggest) )
File.delete('tuner.ccs')
end

require 'open3'
require 'xmlrpc/client'
require 'base64'
class TunerProxy
attr_reader :server
attr_reader :id
attr_reader :handle_map
attr_reader :objective_space
def initialize(name: "", objective_space: nil)
@server = XMLRPC::Client.new2('http://localhost:8080/RPC2')
connected = false
start = Time.now
while !connected
begin
connected = server.call('connected')
rescue
raise if Time.now - start > 10
end
end
if objective_space
@objective_space = objective_space
buff = objective_space.serialize
str = Base64.encode64(buff.get_bytes(0, buff.size))
@id, result = server.call('tuner.create', name, str)
@handle_map = CCS.deserialize(buffer: FFI::MemoryPointer.from_string(Base64.decode64(result)))
else
@id, result = server.call('tuner.load', name)
@handle_map = CCS::Map.new
@objective_space = CCS.deserialize(buffer: FFI::MemoryPointer.from_string(Base64.decode64(result)), handle_map: @handle_map, map_handles: true)
map = CCS::Map.new
@handle_map.pairs.select { |_ , v|
v.is_a?(CCS::Context) || v.is_a?(CCS::TreeSpace)
}.each { |k, v|
map[CCS::Object::new(v.handle, retain: false, auto_release: false)] = k
}
buff = map.serialize
server.call('tuner.set_handle_map', @id, Base64.encode64(buff.get_bytes(0, buff.size)))
end
end

def ask(count = 1)
server.call('tuner.ask', @id, 100).collect { |c|
CCS.deserialize(buffer: FFI::MemoryPointer.from_string(Base64.decode64(c)), handle_map: @handle_map)
}
end

def tell(evals = nil)
evals_serialized = evals.collect { |e| e.serialize }.collect { |buff| Base64.encode64(buff.get_bytes(0, buff.size)) }
server.call('tuner.tell', @id, evals_serialized)
self
end

def history
server.call('tuner.history', @id).collect { |e|
CCS.deserialize(buffer: FFI::MemoryPointer.from_string(Base64.decode64(e)), handle_map: @handle_map)
}
end

def history_size
server.call('tuner.history_size', @id)
end

def optima
server.call('tuner.optima', @id).collect { |e|
CCS.deserialize(buffer: FFI::MemoryPointer.from_string(Base64.decode64(e)), handle_map: @handle_map)
}
end

def num_optima
server.call('tuner.num_optima', @id)
end

def suggest
e = server.call('tuner.suggest', @id)
CCS.deserialize(buffer: FFI::MemoryPointer.from_string(Base64.decode64(e)), handle_map: @handle_map)
end

def save
server.call('tuner.save', @id)
end
end

def test_server
begin
pid = nil
thr = Thread.new do
Open3.popen2e(RbConfig.ruby, File.join(File.dirname(__FILE__), 'tuner_server.rb')) { |stdin, stdout_stderr, wait_thr|
pid = wait_thr.pid
stdout_stderr.read
}
end
os = create_tuning_problem
t = TunerProxy.new(name: "my_tuner", objective_space: os)
func = lambda { |(x, y, z)|
[(x-2)**2, Math.sin(z+y)]
}
evals = t.ask(100).collect { |c|
CCS::Evaluation::new(objective_space: os, configuration: c, values: func[c.values])
}
t.tell evals
hist = t.history
assert_equal(100, hist.size)
evals = t.ask(100).collect { |c|
CCS::Evaluation::new(objective_space: os, configuration: c, values: func[c.values])
}
t.tell evals
assert_equal(200, t.history_size)
objs = t.optima.collect(&:objective_values).sort
objs.collect { |(_, v)| v }.each_cons(2) { |v1, v2| assert( (v1 <=> v2) > 0 ) }
assert( t.optima.collect(&:configuration).include?(t.suggest) )

t.save
t_copy = TunerProxy.new(name: "my_tuner")
hist = t_copy.history
assert_equal(200, hist.size)
assert_equal(t.num_optima, t_copy.num_optima)
objs = t_copy.optima.collect(&:objective_values).sort
objs.collect { |(_, v)| v }.each_cons(2) { |v1, v2| assert( (v1 <=> v2) > 0 ) }
assert( t_copy.optima.collect(&:configuration).include?(t_copy.suggest) )
ensure
Process.kill("TERM", pid)
thr.join
end
end
end

211 changes: 211 additions & 0 deletions bindings/ruby/test/tuner_server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
require 'minitest/autorun'
require_relative '../lib/cconfigspace'
require 'xmlrpc/server'
require 'base64'

CCS.init

class TunerData
attr_accessor :history, :optima
def initialize
@history = []
@optima = []
end
end

del = lambda { |tuner| nil }
ask = lambda { |tuner, _, count|
if count
cs = tuner.search_space
[cs.samples(count), count]
else
[nil, 1]
end
}
tell = lambda { |tuner, evaluations|
tuner.tuner_data.history.concat(evaluations)
evaluations.each { |e|
discard = false
tuner.tuner_data.optima = tuner.tuner_data.optima.collect { |o|
unless discard
case e.compare(o)
when :CCS_COMPARISON_EQUIVALENT, :CCS_COMPARISON_WORSE
discard = true
o
when :CCS_COMPARISON_NOT_COMPARABLE
o
else
nil
end
else
o
end
}.compact
tuner.tuner_data.optima.push(e) unless discard
}
}
get_history = lambda { |tuner, _|
tuner.tuner_data.history
}
get_optima = lambda { |tuner, _|
tuner.tuner_data.optima
}
suggest = lambda { |tuner, _|
if tuner.tuner_data.optima.empty?
ask.call(tuner, 1)
else
tuner.tuner_data.optima.sample.configuration
end
}
get_vector_data = lambda { |otype, name|
[CCS::UserDefinedTuner.get_vector(del: del, ask: ask, tell: tell, get_optima: get_optima, get_history: get_history, suggest: suggest), TunerData.new]
}

s = XMLRPC::Server.new(8080)
count = 0
tuners = {}
# Could be on disk
tuners_store = {}
mutex = Mutex.new

TunerStruct = Struct.new(:tuner, :handle_map)

s.add_handler('connected') do
true
end

s.add_handler('tuner.create') do |name, os_string|
handle_map = CCS::Map.new
os_string = Base64.decode64(os_string)
os = CCS.deserialize(buffer: FFI::MemoryPointer.from_string(os_string), handle_map: handle_map, map_handles: true)
t = CCS::UserDefinedTuner::new(name: name, objective_space: os, del: del, ask: ask, tell: tell, get_optima: get_optima, get_history: get_history, suggest: suggest, tuner_data: TunerData.new)

map = CCS::Map.new
handle_map.pairs.select { |_ , v|
v.is_a?(CCS::Context) || v.is_a?(CCS::TreeSpace)
}.each { |k, v|
map[CCS::Object::new(v.handle, retain: false, auto_release: false)] = k
}
buff = map.serialize
tstruct = TunerStruct.new(t, handle_map)
id = nil
mutex.synchronize {
id = count
count += 1
tuners[id] = tstruct
}
[id, Base64.encode64(buff.get_bytes(0, buff.size))]
end

s.add_handler('tuner.ask') do |id, count|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
tstruct.tuner.ask(count).collect { |c|
buff = c.serialize
Base64.encode64(buff.get_bytes(0, buff.size))
}
end

s.add_handler('tuner.tell') do |id, evals|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
evals.collect! { |e|
e = Base64.decode64(e)
CCS.deserialize(buffer: FFI::MemoryPointer.from_string(e), handle_map: tstruct.handle_map)
}
tstruct.tuner.tell evals
true
end

s.add_handler('tuner.history') do |id|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
tstruct.tuner.history.collect { |e|
buff = e.serialize
Base64.encode64(buff.get_bytes(0, buff.size))
}
end

s.add_handler('tuner.history_size') do |id|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
tstruct.tuner.history_size
end

s.add_handler('tuner.optima') do |id|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
tstruct.tuner.optima.collect { |e|
buff = e.serialize
Base64.encode64(buff.get_bytes(0, buff.size))
}
end

s.add_handler('tuner.num_optima') do |id|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
tstruct.tuner.num_optima
end

s.add_handler('tuner.suggest') do |id|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
e = tstruct.tuner.suggest
buff = e.serialize
Base64.encode64(buff.get_bytes(0, buff.size))
end

s.add_handler('tuner.save') do |id|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
buff = tstruct.tuner.serialize
mutex.synchronize {
tuners_store[tstruct.tuner.name] = buff.get_bytes(0, buff.size)
}
true
end

s.add_handler('tuner.load') do |name|
buff = nil
mutex.synchronize {
buff = tuners_store[name]
}
t = CCS.deserialize(buffer: buff, vector_callback: get_vector_data)
tstruct = TunerStruct.new(t, nil)
id = nil
mutex.synchronize {
id = count
count += 1
tuners[id] = tstruct
}
buff = t.objective_space.serialize
[id, Base64.encode64(buff.get_bytes(0, buff.size))]
end

s.add_handler('tuner.set_handle_map') do |id, map_str|
tstruct = nil
mutex.synchronize {
tstruct = tuners[id]
}
tstruct.handle_map = CCS.deserialize(buffer: FFI::MemoryPointer.from_string(Base64.decode64(map_str)))
true
end

s.serve

0 comments on commit e5cbf7f

Please sign in to comment.