提交 e9a07232 authored 作者: Pascal Lamblin's avatar Pascal Lamblin

Renamed BadCLinkerOutput into BadThunkOutput.

This reflects the fact that the C linker is not necessarily the problem, and that this exception is also raised when different calls to the same thunk (for instance, with different preallocated outputs) give different results.
上级 89037589
...@@ -63,7 +63,10 @@ Reference ...@@ -63,7 +63,10 @@ Reference
This mode catches several kinds of internal error: This mode catches several kinds of internal error:
- inconsistent c_code and perform implementations (see `BadCLinkerOutput`) - inconsistent outputs when calling the same Op twice with the same
inputs, for instance if c_code and perform implementations, are
inconsistent, or in case of incorrect handling of output memory
(see `BadThunkOutput`)
- a variable replacing another when their runtime values don't match. This is a symptom of - a variable replacing another when their runtime values don't match. This is a symptom of
an incorrect optimization step, or faulty Op implementation (raises `BadOptimization`) an incorrect optimization step, or faulty Op implementation (raises `BadOptimization`)
...@@ -144,11 +147,17 @@ There following are DebugMode exceptions you might encounter: ...@@ -144,11 +147,17 @@ There following are DebugMode exceptions you might encounter:
.. class:: BadCLinkerOutput(DebugModeError) .. class:: BadThunkOutput(DebugModeError)
This exception means that python (``perform``) and c (``c_code``) for an Op This exception means that different calls to the same Op with the same
didn't compute the same thing like they were supposed to. inputs did not compute the same thing like they were supposed to.
The problem might be a bug in either ``perform`` or ``c_code`` (or both). For instance, it can happen if the python (``perform``) and c (``c_code``)
implementations of the Op are inconsistent (the problem might be a bug in
either ``perform`` or ``c_code`` (or both)). It can also happen if
``perform`` or ``c_code`` does not handle correctly output memory that
has been preallocated (for instance, if it did not clear the memory before
accumulating into it, or if it assumed the memory layout was C-contiguous
even if it is not).
......
...@@ -115,24 +115,35 @@ class DebugModeError(Exception): ...@@ -115,24 +115,35 @@ class DebugModeError(Exception):
pass pass
class BadCLinkerOutput(DebugModeError): class BadThunkOutput(DebugModeError):
"""Exception: an Op's c_code and perform implementations don't agree.""" """
Exception: Calling the same Op twice gives inconsistent outputs.
It can be raised, for instance, if an Op's c_code and perform method
do not agree, or if one of these methods do not give the same result
when called twice with the same inputs (but different memory layouts
for the output).
"""
r = None r = None
"""The `Variable` instance for which conflicting values were computed""" """The `Variable` instance for which conflicting values were computed"""
val_py = None thunk1 = ''
"""The value computed by `r.owner.op.perform`""" val1 = None
"""The value computed by `thunk1`"""
val_c = None thunk2 = ''
"""The value computed by `r.owner.op.c_code`""" val2 = None
"""The value computed by `thunk2`"""
def __init__(self, r, val_py, val_c): def __init__(self, r, thunk1, val1, thunk2, val2):
"""Initialize members""" """Initialize members"""
DebugModeError.__init__(self) # to be compatible with python2.4 DebugModeError.__init__(self) # to be compatible with python2.4
self.r = r self.r = r
self.val_py = val_py self.thunk1 = thunk1
self.val_c = val_c self.val1 = val1
self.thunk2 = thunk2
self.val2 = val2
def offending_op(self): def offending_op(self):
"""Return the Op class whose c_code and perform """Return the Op class whose c_code and perform
...@@ -146,45 +157,47 @@ class BadCLinkerOutput(DebugModeError): ...@@ -146,45 +157,47 @@ class BadCLinkerOutput(DebugModeError):
"""Return a pretty multiline string representating the cause """Return a pretty multiline string representating the cause
of the exception""" of the exception"""
sio = StringIO() sio = StringIO()
print >> sio, "BadCLinkerOutput" print >> sio, "BadThunkOutput"
print >> sio, " variable:", self.r print >> sio, " variable :", self.r
print >> sio, " Outputs Type :", self.r.type print >> sio, " Outputs Type:", self.r.type
print >> sio, " Inputs Type:", [i.type for i in self.r.owner.inputs] print >> sio, " Inputs Type :", [i.type for i in self.r.owner.inputs]
print >> sio, " Apply :", self.r.owner print >> sio, " Apply :", self.r.owner
print >> sio, " val_py :", self.val_py print >> sio, " thunk1 :", self.thunk1
print >> sio, " val_c :", self.val_c print >> sio, " thunk2 :", self.thunk2
print >> sio, " val1 :", self.val1
print >> sio, " val2 :", self.val2
print >> sio, " op :", self.offending_op() print >> sio, " op :", self.offending_op()
try: try:
ssio = StringIO() ssio = StringIO()
print >> ssio, " PyValue shape, dtype, strides, min, max, n_inf, n_nan:", print >> ssio, " Value 1 : shape, dtype, strides, min, max, n_inf, n_nan:",
print >> ssio, self.val_py.shape, print >> ssio, self.val1.shape,
print >> ssio, self.val_py.dtype, print >> ssio, self.val1.dtype,
print >> ssio, self.val_py.strides, print >> ssio, self.val1.strides,
print >> ssio, self.val_py.min(), print >> ssio, self.val1.min(),
print >> ssio, self.val_py.max(), print >> ssio, self.val1.max(),
print >> ssio, numpy.isinf(self.val_py).sum(), print >> ssio, numpy.isinf(self.val1).sum(),
print >> ssio, numpy.isnan(self.val_py).sum(), print >> ssio, numpy.isnan(self.val1).sum(),
# only if all succeeds to we add anything to sio # only if all succeeds to we add anything to sio
print >> sio, ssio.getvalue() print >> sio, ssio.getvalue()
except Exception: except Exception:
pass pass
try: try:
ssio = StringIO() ssio = StringIO()
print >> ssio, " CValue shape, dtype, strides, min, max, n_inf, n_nan:", print >> ssio, " Value 2 : shape, dtype, strides, min, max, n_inf, n_nan:",
print >> ssio, self.val_c.shape, print >> ssio, self.val2.shape,
print >> ssio, self.val_c.dtype, print >> ssio, self.val2.dtype,
print >> ssio, self.val_c.strides, print >> ssio, self.val2.strides,
print >> ssio, self.val_c.min(), print >> ssio, self.val2.min(),
print >> ssio, self.val_c.max(), print >> ssio, self.val2.max(),
print >> ssio, numpy.isinf(self.val_c).sum(), print >> ssio, numpy.isinf(self.val2).sum(),
print >> ssio, numpy.isnan(self.val_c).sum(), print >> ssio, numpy.isnan(self.val2).sum(),
# only if all succeeds to we add anything to sio # only if all succeeds to we add anything to sio
print >> sio, ssio.getvalue() print >> sio, ssio.getvalue()
except Exception: except Exception:
pass pass
try: try:
ov = numpy.asarray(self.val_c) ov = numpy.asarray(self.val1)
nv = numpy.asarray(self.val_py) nv = numpy.asarray(self.val2)
ssio = StringIO() ssio = StringIO()
absdiff = numpy.absolute(nv - ov) absdiff = numpy.absolute(nv - ov)
print >> ssio, " Max Abs Diff: ", numpy.max(absdiff) print >> ssio, " Max Abs Diff: ", numpy.max(absdiff)
...@@ -1273,6 +1286,8 @@ def _check_preallocated_output(node, thunk, prealloc_modes, def_val, ...@@ -1273,6 +1286,8 @@ def _check_preallocated_output(node, thunk, prealloc_modes, def_val,
init_outputs): init_outputs):
_logger.debug(' name = %s', name) _logger.debug(' name = %s', name)
thunk_name = '%s with %s output' % (perform, name)
if not out_map: if not out_map:
# Map is empty, there is no need to execute thunk() again # Map is empty, there is no need to execute thunk() again
_logger.warn('%s: out_map is empty', name) _logger.warn('%s: out_map is empty', name)
...@@ -1295,13 +1310,13 @@ def _check_preallocated_output(node, thunk, prealloc_modes, def_val, ...@@ -1295,13 +1310,13 @@ def _check_preallocated_output(node, thunk, prealloc_modes, def_val,
for r in node.outputs: for r in node.outputs:
if not r.type.is_valid_value(storage_map[r][0]): if not r.type.is_valid_value(storage_map[r][0]):
raise InvalidValueError(r, storage_map[r][0], raise InvalidValueError(r, storage_map[r][0],
hint='%s with %s output' % (perform, name), hint=thunk_name,
specific_hint=r.type.value_validity_msg( specific_hint=r.type.value_validity_msg(
storage_map[r][0])) storage_map[r][0]))
_check_inputs(node, storage_map, r_vals, dr_vals, active_order_set, _check_inputs(node, storage_map, r_vals, dr_vals, active_order_set,
clobber_dr_vals=False, clobber_dr_vals=False,
perform='%s with output %s' % (perform, name), perform=thunk_name,
warn_input_not_reused=False) warn_input_not_reused=False)
_check_viewmap(node, storage_map) _check_viewmap(node, storage_map)
...@@ -1309,8 +1324,9 @@ def _check_preallocated_output(node, thunk, prealloc_modes, def_val, ...@@ -1309,8 +1324,9 @@ def _check_preallocated_output(node, thunk, prealloc_modes, def_val,
for r in node.outputs: for r in node.outputs:
if not r.type.values_eq_approx(r_vals[r], storage_map[r][0]): if not r.type.values_eq_approx(r_vals[r], storage_map[r][0]):
# TODO: indicate it is not a C/Py problem # TODO: indicate it is not a C/Py problem
raise BadCLinkerOutput(r, val_py=r_vals[r], raise BadThunkOutput(r,
val_c=storage_map[r][0]) thunk1='Reference value', val1=r_vals[r],
thunk2=thunk_name, val2=storage_map[r][0])
# Clear storage_map # Clear storage_map
for r in node.outputs: for r in node.outputs:
...@@ -1885,7 +1901,9 @@ class _Linker(gof.link.LocalLinker): ...@@ -1885,7 +1901,9 @@ class _Linker(gof.link.LocalLinker):
if not r.type.values_eq_approx(r_vals[r], storage_map[r][0]): if not r.type.values_eq_approx(r_vals[r], storage_map[r][0]):
#import pdb; pdb.set_trace() #import pdb; pdb.set_trace()
#r.type.values_eq_approx(r_vals[r], storage_map[r][0]) #r.type.values_eq_approx(r_vals[r], storage_map[r][0])
raise BadCLinkerOutput(r, val_py=r_vals[r], val_c=storage_map[r][0]) raise BadThunkOutput(r,
thunk1='perform', val1=r_vals[r],
thunk2='c_code', val2=storage_map[r][0])
else: else:
#print >> sys.stderr, i, "DEBUGMODE storing reference output %x" % id(storage_map[r][0]) #print >> sys.stderr, i, "DEBUGMODE storing reference output %x" % id(storage_map[r][0])
#retrieve each output from the storage_map #retrieve each output from the storage_map
...@@ -2297,7 +2315,10 @@ class DebugMode(Mode): ...@@ -2297,7 +2315,10 @@ class DebugMode(Mode):
This mode catches several kinds of internal error: This mode catches several kinds of internal error:
- inconsistent c_code and perform implementations (see `BadCLinkerOutput`) - inconsistent outputs when calling the same Op twice with the same
inputs, for instance if c_code and perform implementations, are
inconsistent, or in case of incorrect handling of output memory
(see `BadThunkOutput`),
- a variable replacing another when their runtime values don't - a variable replacing another when their runtime values don't
match. This is a symptom of an incorrect optimization step, or match. This is a symptom of an incorrect optimization step, or
......
...@@ -196,7 +196,7 @@ wb1i = WeirdBrokenOp('times1_inplace') ...@@ -196,7 +196,7 @@ wb1i = WeirdBrokenOp('times1_inplace')
wb1 = WeirdBrokenOp('times1') wb1 = WeirdBrokenOp('times1')
def test_badclinkeroutput(): def test_badthunkoutput():
a = theano.tensor.dvector() a = theano.tensor.dvector()
b = theano.tensor.dvector() b = theano.tensor.dvector()
...@@ -212,7 +212,7 @@ def test_badclinkeroutput(): ...@@ -212,7 +212,7 @@ def test_badclinkeroutput():
f_good([1.0, 2.0, 3.0], [2, 3, 4]) f_good([1.0, 2.0, 3.0], [2, 3, 4])
try: try:
f_inconsistent([1.0, 2.0, 3.0], [2, 3, 4]) f_inconsistent([1.0, 2.0, 3.0], [2, 3, 4])
except debugmode.BadCLinkerOutput, e: except debugmode.BadThunkOutput, e:
#print repr(e) #print repr(e)
assert e.r.owner.op is inconsistent assert e.r.owner.op is inconsistent
return # TEST PASS return # TEST PASS
...@@ -721,7 +721,7 @@ class Test_preallocated_output(unittest.TestCase): ...@@ -721,7 +721,7 @@ class Test_preallocated_output(unittest.TestCase):
check_preallocated_output=['f_contiguous']) check_preallocated_output=['f_contiguous'])
f = theano.function([a, b], out, mode=mode) f = theano.function([a, b], out, mode=mode)
self.assertRaises(debugmode.BadCLinkerOutput, f, a_val, b_val) self.assertRaises(debugmode.BadThunkOutput, f, a_val, b_val)
def test_output_broadcast_tensor(self): def test_output_broadcast_tensor(self):
v = theano.tensor.fvector('v') v = theano.tensor.fvector('v')
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论