added DualLinker

上级 b3d22914
import unittest
from link import PerformLinker
from cc import *
from result import ResultBase
from op import Op
......@@ -91,14 +92,20 @@ class Binary(MyOp):
class Add(Binary):
def c_code(self):
return "%(z)s = %(x)s + %(y)s;"
def perform(self):
self.outputs[0].data = self.inputs[0].data + self.inputs[1].data
class Sub(Binary):
def c_code(self):
return "%(z)s = %(x)s - %(y)s;"
def perform(self):
self.outputs[0].data = -10 # erroneous
class Mul(Binary):
def c_code(self):
return "%(z)s = %(x)s * %(y)s;"
def perform(self):
self.outputs[0].data = self.inputs[0].data * self.inputs[1].data
class Div(Binary):
def c_validate_update(self):
......@@ -110,6 +117,9 @@ class Div(Binary):
"""
def c_code(self):
return "%(z)s = %(x)s / %(y)s;"
def perform(self):
self.outputs[0].data = self.inputs[0].data / self.inputs[1].data
import modes
......@@ -187,5 +197,57 @@ class _test_OpWiseCLinker(unittest.TestCase):
fn = lnk.make_function()
self.failUnless(fn(2.0, 2.0, 2.0) == 2.0)
class MyExc(Exception):
pass
def _my_checker(x, y):
if x.data != y.data:
raise MyExc("Output mismatch.", {'performlinker': x.data, 'clinker': y.data})
class _test_DualLinker(unittest.TestCase):
def test_straightforward(self):
x, y, z = inputs()
e = add(mul(x, y), mul(y, z)) # add and mul are correct in C and in Python
lnk = DualLinker(env([x, y, z], [e]), checker = _my_checker)
fn = lnk.make_function()
res = fn(7.2, 1.5, 3.0)
self.failUnless(res == 15.3, res)
def test_mismatch(self):
x, y, z = inputs()
e = sub(mul(x, y), mul(y, z)) # sub is correct in C but erroneous in Python
g = env([x, y, z], [e])
lnk = DualLinker(g, checker = _my_checker)
fn = lnk.make_function()
self.failUnless(CLinker(g).make_function()(1.0, 2.0, 3.0) == -4.0) # good
self.failUnless(OpWiseCLinker(g).make_function()(1.0, 2.0, 3.0) == -4.0) # good
self.failUnless(PerformLinker(g).make_function()(1.0, 2.0, 3.0) == -10.0) # (purposely) wrong
try:
# this runs OpWiseCLinker and PerformLinker in parallel and feeds
# results of matching operations to _my_checker to verify that they
# are the same.
res = fn(1.0, 2.0, 3.0)
self.fail()
except MyExc, e:
pass
else:
self.fail()
def test_orphan(self):
x, y, z = inputs()
x.data = 7.2
e = add(mul(x, y), mul(y, z)) # add and mul are correct in C and in Python
lnk = DualLinker(env([y, z], [e]), checker = _my_checker)
fn = lnk.make_function()
res = fn(1.5, 3.0)
self.failUnless(res == 15.3, res)
if __name__ == '__main__':
unittest.main()
......@@ -745,6 +745,84 @@ class OpWiseCLinker(Linker):
def _default_checker(x, y):
"""
Default checker for DualLinker. This checks that the
results
"""
if x.data != y.data:
raise Exception("Output mismatch.", {'performlinker': x.data, 'clinker': y.data})
class DualLinker(Linker):
"""
Runs the env in parallel using PerformLinker and CLinker.
The thunk/function produced by DualLinker uses PerformLinker as the
"main" implementation: the inputs and outputs are fed to/taken from
the Ops' perform. However, DualLinker also instantiates a copy of
the env on which it runs OpWiseCLinker. At each step, the results
of perform and of the C implementation are verified using a checker
function.
"""
def __init__(self, env, checker = _default_checker):
"""
Initialize a DualLinker.
The checker argument must be a function that takes two Result
instances. The first one passed will be the output computed by
PerformLinker and the second one the output computed by
OpWiseCLinker. The checker should compare the data fields of
the two results to see if they match. By default, DualLinker
uses ==. A custom checker can be provided to compare up to a
certain error tolerance.
If a mismatch occurs, the checker should raise an exception to
halt the computation. If it does not, the computation will
carry on and errors will snowball. The checker can sidestep
the problem by fiddling with the data, but it should be
careful not to share data between the two outputs (or inplace
operations that use them will interfere).
"""
self.env = env
self.checker = checker
def make_thunk(self, inplace = False):
if inplace:
env1 = self.env
else:
env1 = self.env.clone(True)
env2, equiv = env1.clone_get_equiv(True)
op_order_1 = env1.toposort()
op_order_2 = [equiv[op.outputs[0]].owner for op in op_order_1] # we need to have the exact same order so we can compare each step
thunks1 = [op.perform for op in op_order_1]
thunks2 = [CLinker(op).make_thunk(True)[0] for op in op_order_2]
def f():
for input1, input2 in zip(env1.inputs, env2.inputs):
# set the inputs to be the same in both branches
# the copy is necessary in order for inplace ops not to interfere
input2.data = copy(input1.data)
for thunk1, thunk2, op1, op2 in zip(thunks1, thunks2, op_order_1, op_order_2):
try:
thunk1()
thunk2()
for output1, output2 in zip(op1.outputs, op2.outputs):
self.checker(output1, output2)
except:
exc_type, exc_value, exc_trace = sys.exc_info()
try:
trace = op1.trace
except AttributeError:
trace = ()
exc_value.__thunk_trace__ = trace
exc_value.args = exc_value.args + (op1, )
raise exc_type, exc_value, exc_trace
return f, env1.inputs, env1.outputs
......
......@@ -490,6 +490,14 @@ class Env(graph.Graph):
def __str__(self):
return "[%s]" % ", ".join(graph.as_string(self.inputs, self.outputs))
def clone_get_equiv(self, clone_inputs = True):
equiv = graph.clone_get_equiv(self.inputs, self.outputs, clone_inputs)
new = self.__class__([equiv[input] for input in self.inputs],
[equiv[output] for output in self.outputs],
self._features.keys(),
consistency_check = False)
return new, equiv
def clone(self, clone_inputs = True):
equiv = graph.clone_get_equiv(self.inputs, self.outputs, clone_inputs)
new = self.__class__([equiv[input] for input in self.inputs],
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论