提交 0dd147a2 authored 作者: bergstrj@iro.umontreal.ca's avatar bergstrj@iro.umontreal.ca

merged; updated compile.py

import unittest
import gof, gof.modes, gof.opt
from compile import *
class Double(gof.result.ResultBase):
def __init__(self, data, name = "oignon"):
assert isinstance(data, float)
gof.result.ResultBase.__init__(self, role = None, data = data, name = name)
def __str__(self):
return self.name
def __repr__(self):
return self.name
class MyOp(gof.op.Op):
nin = -1
def __init__(self, *inputs):
assert len(inputs) == self.nin
for input in inputs:
if not isinstance(input, Double):
raise Exception("Error 1")
self.inputs = inputs
self.outputs = [Double(0.0, self.__class__.__name__ + "_R")]
def perform(self):
self.outputs[0].data = self.impl(*[input.data for input in self.inputs])
class Unary(MyOp):
nin = 1
class Binary(MyOp):
nin = 2
class Add(Binary):
def impl(self, x, y):
return x + y
class Sub(Binary):
def impl(self, x, y):
return x - y
class Mul(Binary):
def impl(self, x, y):
return x * y
class Div(Binary):
def impl(self, x, y):
return x / y
def env(inputs, outputs, validate = True, features = []):
return gof.env.Env(inputs, outputs, features = features, consistency_check = validate)
def perform_linker(env):
lnk = gof.link.PerformLinker(env)
return lnk
def graph1():
x = gof.modes.build(Double(1.0, 'x'))
y = gof.modes.build(Double(2.0, 'y'))
z = gof.modes.build(Double(3.0, 'z'))
o = Mul(Add(x, y).out, Div(x, y).out).out
return [x,y,z], [o]
def graph2():
x = gof.modes.build(Double(1.0, 'x'))
y = gof.modes.build(Double(2.0, 'y'))
z = gof.modes.build(Double(3.0, 'z'))
o = Mul(Add(x, y).out, Div(x, y).out).out
return [x,y,z], [o, o, o.owner.inputs[1]]
class _test_compile(unittest.TestCase):
def test_link_noopt(self):
gi, go = graph1()
fn, i, o = perform_linker(env(gi, go)).make_thunk(True)
fn()
self.failUnless(go[0].data == 1.5)
def test_link_opt(self):
opt = gof.opt.PatternOptimizer((Div, '1', '2'), (Div, '2', '1'))
gi, go = graph1()
e = env(gi, go)
opt.optimize(e)
fn, i, o = perform_linker(e).make_thunk(True)
fn()
self.failUnless(go[0].data == 6.0)
def test_prog_noopt(self):
gi, go = graph1()
p = Prog(gi,go)
self.failUnless(p() == 1.5)
def test_prog_opt(self):
opt = gof.opt.PatternOptimizer((Div, '1', '2'), (Div, '2', '1'))
gi, go = graph1()
p = Prog(gi,go, optimizer=opt)
self.failUnless(p() == 6.0)
def test_prog_multiout(self):
opt = gof.opt.PatternOptimizer((Div, '1', '2'), (Div, '2', '1'))
gi, go = graph2()
p = Prog(gi,go, optimizer=opt)
a,b,c = p()
self.failUnless(a == 6.0)
self.failUnless(b == 6.0)
self.failUnless(a is b)
self.failUnless(c == 2.0)
if __name__ == '__main__':
unittest.main()
import time, unittest """Convenient driver of graph construction, optimization, and linking."""
import numpy
import gof import gof
import gof.lib
import cutils
import core #TODO: put together some default optimizations
import opt
from copy import copy
_optimizations = None
def experimental_linker(env, target = None): def prog_py_opt(inputs, outputs, features=[]):
order = env.toposort() """Return an optimized graph running purely python implementations"""
return Prog(intputs, outputs, features, _optimizations, gof.link.PerformLinker, False)
for op in order:
op.refresh() def prog_opt(inputs, outputs, features=[]):
"""Return a fast implementation"""
py_ops = set() return Prog(intputs, outputs, features, _optimizations, gof.link.PerformLinker, False)
thunks = []
computed_results = [] class Prog:
"""An 'executable' compiled from a graph
for op in order:
try: This class is meant to be used as a function: the idea is to use
factory = op.c_thunk_factory() __call__(*args) and it will compute your graph's function on the args and
for input in op.inputs: return the value(s) corresponding to the output(s).
producer = input.owner
if producer in py_ops:
result = lambda factory = factory: cutils.run_cthunk(factory())
break
else:
thunk = factory()
result = lambda thunk = thunk: cutils.run_cthunk(thunk)
except NotImplementedError:
result = op._perform
py_ops.add(op)
thunks.append((result, op._perform_inplace))
computed_results.extend(op.outputs)
def ret():
for thunk, fallback in thunks:
try:
thunk()
except NotImplementedError:
fallback()
for r in computed_results:
r.state = gof.result.Computed
if not target:
return ret
else:
raise NotImplementedError("Cannot write thunk representation to a file.")
class profile_linker:
def __init__(self, env):
self.order = env.toposort()
self.thunks = [op._perform for op in self.order]
self.n_calls = 0
self.n_thunks = 0
self.times = [0.0 for op in self.order]
def print_for_dot(self):
#TODO: popen2("dot -Tpng | display") and actually make the graph window pop up
print "digraph unix { size = '6,6'; node [color = lightblue2; style = filled];"
for op in self.order:
for input in op.inputs:
if input.owner:
print input.owner.__class__.__name__ + str(abs(id(input.owner))), " -> ", op.__class__.__name__ + str(abs(id(op))), ";"
def slow_call(self): Attributes
"""Run the program, timing each thunk. """ fn - the return value of linker.make_function(False)
for i, thunk in enumerate(self.thunks):
start_time = time.time() Additional Attributes if keep_locals == True
thunk() inputs - inputs in the env
self.times[i] += time.time() - start_time outputs - outputs in the env
self.n_thunks += 1 features - features to add to the env
self.n_calls += 1 linker_cls - the linker class
linker - the linker allocated from env
def fast_call(self): env - The env passed to the linker
"""Run the program, but only time the entire loop.""" """
start_time = time.time() def __init__(self,
for th in self.thunks: inputs,
th() outputs,
self.n_thunks += len(self.thunks) features=[],
self.n_calls += 1 optimizer=None,
self.times[0] += time.time() - start_time linker_cls=gof.link.PerformLinker,
keep_locals=True):
__call__ = slow_call
env = gof.env.Env(inputs, outputs, features, consistency_check = True)
def dump(self, proportion=True):
"""Print statistics accumulated so far.""" if None is not optimizer:
total_time = sum(self.times) optimizer.optimize(env)
print self.n_calls, 'calls took', total_time, 'seconds to evaluate',
print self.n_thunks, 'thunks' linker = linker_cls(env)
if 0: if keep_locals: # useful flag for debugging
print 'Proportion of CPU per op' self.__dict__.update(locals())
for op, t in zip(self.order, self.times):
s_op = str(op).split()[0][1:] self.fn = linker.make_function(False)
print " %-35s %4.5f"% (s_op, t/total_time)
def __call__(self, *args):
print 'Proportion of CPU per op class' return self.fn(*args)
dct = {}
for op, t in zip(self.order, self.times):
s_op = str(op).split()[0][1:]
dct[s_op] = dct.get(s_op, 0.0) + t
for t, s_op in reversed(sorted([(t,op) for op, t in dct.items()])):
if proportion:
print " %-35s %4.5f"% (s_op, t/total_time)
else:
print " %-35s %4.5f"% (s_op, t)
class prog(gof.Prog):
def __init__(self, inputs, outputs, optimizer = opt.optimizer([]),
linker = experimental_linker):
"""Compile a subgraph.
N.B. This triggers computation of the subgraph leading to the outputs
that is not fed by the inputs (the orphans).
TODO: think about whether orphan computation should be in this function,
or in self.__call__()
"""
new_outputs = gof.mark_outputs_as_destroyed(outputs)
gof.Prog.__init__(self,
inputs,
new_outputs,
optimizer,
linker,
[])
self.outputs = outputs
self.compute_orphans()
def __call__(self, check_uncomputed = True):
"""Recompute the graph.
If the inputs are uncomputed (and check_uncomputed is True) then an
Exception is raised.
"""
if check_uncomputed:
for input in self.env.inputs:
if input.data is None:
raise Exception("You must provide a value for input %s!" % input)
return gof.Prog.__call__(self)
def compute_orphans(self):
for orphan in self.env.orphans():
if orphan.data is None:
if orphan.owner:
gof.lib.compute(orphan.owner)
else:
raise Exception("Orphan %s is uncomputed but needed to calculate the function." % orphan)
def to_func(inputs, outputs):
# print gof.Env(inputs, outputs).io_toposort()
## p = prog([copy(input) for input in inputs], gof.graph.clone(inputs, outputs))
p = prog(inputs, outputs)
def f(*args):
for input, value in zip(inputs, args):
p[input] = value
outputs = p()
if len(outputs) == 1:
return outputs[0]
else:
return outputs
return f
def single(*outputs, **kwargs):
return prog(gof.graph.inputs(outputs), outputs, **kwargs)
class _test_single_build_mode(unittest.TestCase):
def setUp(self):
core.build_mode()
numpy.random.seed(44)
def tearDown(self):
core.pop_mode()
def test_3(self):
a = core.Numpy2(data=numpy.random.rand(2,2))
b = core.Numpy2(data=numpy.random.rand(2,2))
c = core.add(a,b)
self.failUnless(c.data is None)
self.failUnless(c.state is gof.result.Empty)
p = single(c)
self.failUnless(c.data is not None)
self.failUnless(c.state is gof.result.Allocated)
self.failUnless(not core._approx_eq(c, a.data + b.data))
p()
self.failUnless(c.state is gof.result.Computed)
self.failUnless(core._approx_eq(c, a.data + b.data))
new_a = numpy.random.rand(2,2)
new_b = numpy.random.rand(2,2)
a.data[:] = new_a
b.data[:] = new_b
p()
self.failUnless(core._approx_eq(c, new_a + new_b))
def test_get_element(self):
core.build_eval_mode()
a_data = numpy.random.rand(2,2)
a = core.Numpy2(data=a_data)
pos = core.input((0,0))
a_i = core.get_slice(a, pos)
p = single(a_i)
#p()
#print 'aaaa', a_i.owner.out, a_i.owner, a_i.data, pos.data
#print 'pre p()'
for i in 0,1:
for j in 0,1:
pos.data = (i,j)
p()
#print 'asdf', i,j,a_i.data
#print a_i.owner.inputs[1].data
#a_i.owner.inputs[1].data = [i,j]
self.failUnless(a_data[i,j] == a_i.data)
core.pop_mode()
if __name__ == '__main__':
unittest.main()
...@@ -65,6 +65,9 @@ class Op(object): ...@@ -65,6 +65,9 @@ class Op(object):
def get_outputs(self): def get_outputs(self):
return self._outputs return self._outputs
def set_outputs(self, new): def set_outputs(self, new):
# the point of this function is
# 1. to save the subclass's __init__ function always having to set the role of the outputs
# 2. to prevent accidentally re-setting outputs, which would probably be a bug
if not hasattr(self, '_outputs') or self._outputs is None: if not hasattr(self, '_outputs') or self._outputs is None:
for i, output in enumerate(new): for i, output in enumerate(new):
output.role = (self, i) output.role = (self, i)
...@@ -86,6 +89,8 @@ class Op(object): ...@@ -86,6 +89,8 @@ class Op(object):
Shallow copy of this Op. The inputs are the exact same, but Shallow copy of this Op. The inputs are the exact same, but
the outputs are recreated because of the one-owner-per-result the outputs are recreated because of the one-owner-per-result
policy. policy.
This implementation permits a bottom-up copy of an entire graph.
""" """
return self.__class__(*self.inputs) return self.__class__(*self.inputs)
...@@ -186,6 +191,7 @@ class Op(object): ...@@ -186,6 +191,7 @@ class Op(object):
class GuardedOp(Op): class GuardedOp(Op):
"""An Op that disallows input properties to change after construction"""
def set_input(self, i, new): def set_input(self, i, new):
old = self._inputs[i] old = self._inputs[i]
......
...@@ -263,5 +263,6 @@ class ResultBase(object): ...@@ -263,5 +263,6 @@ class ResultBase(object):
# #
def same_properties(self, other): def same_properties(self, other):
"""Return bool; True iff all properties are equal (ignores contents, role)"""
raise AbstractFunction() raise AbstractFunction()
...@@ -121,6 +121,14 @@ def toposort(prereqs_d): ...@@ -121,6 +121,14 @@ def toposort(prereqs_d):
return seq return seq
def print_for_dot(self):
#TODO: popen2("dot -Tpng | display") and actually make the graph window pop up
print "digraph unix { size = '6,6'; node [color = lightblue2; style = filled];"
for op in self.order:
for input in op.inputs:
if input.owner:
print input.owner.__class__.__name__ + str(abs(id(input.owner))), " -> ", op.__class__.__name__ + str(abs(id(op))), ";"
# def schedule(**kwargs): # def schedule(**kwargs):
# after = kwargs.get('after', []) # after = kwargs.get('after', [])
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论