-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cleanup exceptions #742
Cleanup exceptions #742
Conversation
use cout<< "DEBUG" instead
operator<< is needed for NTA_THROW macro, and now we can remove LoggingException, which only added the <<
use NTA_LOG_LEVEL = htm::LogLevel::LogLevel_xxx see Log.hpp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to have this code cleaned up. Thanks.
@@ -84,7 +84,7 @@ TEST(TMRegionTest, testSpecAndParameters) { | |||
Network net; | |||
|
|||
// Turn on runtime Debug logging. | |||
//if (verbose) LogItem::setLogLevel(LogLevel::LogLevel_Verbose); | |||
//if (verbose) NTA_LOG_LEVEL = LogLevel::LogLevel_Verbose; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: It would be better if this were a function or at least a macro so that if we need to change the logic we don't need to change it everywhere it is used.
Note that with the current logic, setting the log level only affects the current cpp file and is not global. That fact needs to be described someplace and there is no function to associate it with.
If the intent was to have the log level be set globally the variable NTA_LOG_LEVEL would have to be changed from static to extern and the actual variable would need to be defined in a .cpp someplace ... in Network.cpp perhaps.
In the NetworkAPI there are times that we would like to have this set globally so that debug messages on subordinate cpp's would be activated. Currently each Region displays something on each iteration if this is activated globally. Makes a nice trace.
After some more thought on this, it occured to me that when we start to do multi-threading we will also need to be able to make the logging thread specific. So there are three things to consider:
One way to do this is to create a Log class in Log.hpp and make that a static variable declared in the .hpp,
This may be overkill. but something to consider. |
you're absolutely right, and the design you propose sounds good and clean. But, this PR was just a side quest to get #736 working (and it even didn't work out). One problem with what you suggest is that we historically use I don't feel like looking at that now. So: should I aim to rework this PR to pass as-is, meaning just the cleanup, or we want to cancel it and rework completely later? |
which is superfulous and should not inverfere with the logic of the region
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a bug in handling of (TM)Region(s?) please have a look below
src/htm/regions/TMRegion.cpp
Outdated
tm_->getActiveCells(out->getData().getSDR()); | ||
NTA_DEBUG << "active " << *out << std::endl; | ||
} | ||
out = getOutput("predictedActiveCells"); | ||
if (out && (out->hasOutgoingLinks() || LogItem::isDebug())) { | ||
if (out && out->hasOutgoingLinks() ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dkeeney this is something I'd like to consult with you. Actually the problem I'm hitting here seems to be a bug in TMRegion,
a) I'm removing the "is Debug" part of the check, as it was supposed to be a helper for debugging, but breaks the logic (tests expecting such behavior are wrong)
b) there's a problem with requiring the hasOutgoingLinks()
, as:
This is the test failing:
net = engine.Network()
#net.setLogLevel(htm.bindings.engine_internal.LogLevel.Verbose) # Verbose shows data inputs and outputs while executing.
encoder = net.addRegion("encoder", "ScalarSensor", "{n: 6, w: 2}");
sp = net.addRegion("sp", "SPRegion", "{columnCount: 200}");
tm = net.addRegion("tm", "TMRegion", "");
net.link("encoder", "sp");
net.link("sp", "tm");
net.initialize();
encoder.setParameterReal64("sensedValue", 0.8); #Note: default range setting is -1.0 to +1.0
net.run(1)
sp_input = sp.getInputArray("bottomUpIn")
sdr = sp_input.getSDR()
self.assertTrue(np.array_equal(sdr.sparse, EXPECTED_RESULT1))
sp_output = sp.getOutputArray("bottomUpOut")
sdr = sp_output.getSDR()
self.assertTrue(np.array_equal(sdr.sparse, EXPECTED_RESULT2))
tm_output = tm.getOutputArray("predictedActiveCells")
sdr = tm_output.getSDR()
self.assertTrue(np.array_equal(sdr.sparse, EXPECTED_RESULT3))
The network is Encoder->SP->TM
,
I was surprised only the TM check is failing, but it is because the TM is a "leaf" (it's last part of the Network), and we have the condition if (out && out->hasOutgoingLinks() )
.
now, we want a similar check (to "has outputs") so that we only compute for connected regions. Using getInput("name").hasIncomingLinks()
would work for TM in this example, but would fail for the root (sensor/encoder).
So do we want a combination and determine if XXX (what is the "predictedActiveCells" called, it's not a region per se...?) is connected by
out = getOutput("predictedActiveCells");
if ((out && out->hasOutgoingLinks()) || (getInput("predictedActiveCells") && getInput("predictedActiveCells")->getIncomingLinks() ) ) { //means either XX,or YY exists and is connected in a chain XX->TM->YY
tm_->activateDendrites();
tm_->getWinnerCells(out->getData().getSDR());
NTA_DEBUG << "winners " << *out << std::endl;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am having a problem understanding your post on my cell phone.
I will have to check it out later when I return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (out && (out->hasOutgoingLinks() || LogItem::isDebug())) {
This logic is correct. Although we need someplace else to put isDebug() since you removed LogItem. This is part of the trace facility. When the debug mode is activated by an application by calling setLogLevel(LogLevel_Verbose), the trace should show each region that is executed and the outputs.
For the trace facility to work, we need at least the following:
- LogtLevel must be global.
- We need a function like setLogLevel( ) for applications to call. (they should not have access to the global LogLevel variable).
- We need a function like isDebug( ) to allow conditional processing related to the trace.
- We need a function (or Macro) like NTA_DEBUG( ) to indicate what is to be displayed in the trace.
This trace facility is extremely useful when debugging a problem with region execution and or linking. In particular, which data is being passed at which times and the order that the regions are executed. This is another case where we need more documentation and explanation of the 'features' that are available.
Oh, and the 'Verbose' facility that I used with some of the Unit Tests does not use the trace facility.
TMRegion has several 'optional' outputs. For the 'optional' outputs, if there is no outgoing connection, then the output is not generated. So, to get the trace in Verbose mode we should generate the output data even though it is not being sent to an output. That is what the above 'if' statement does.
However, recently we exposed the ability to access the output buffers directly with region.getOutput(name).getData(). This causes a problem because if there is no outgoing link the optional outputs cannot be accessed in this way. Perhaps to be on the safe side we should always produce all outputs even if they are not used. In that case we can remove isDebug( ) and remove the entire 'if' statement at the top but the cost is lower performance when the outputs are not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So do we want a combination and determine if XXX (what is the "predictedActiveCells" called, it's not a region per se...?) is connected by ....
I have no idea what you are trying to say here. Could you re-phrase it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (out && (out->hasOutgoingLinks() || LogItem::isDebug())) {
This logic is correct. Although we need someplace else to put isDebug() since you removed LogItem.
this is what I wanted to disprove. Debug is useful but we should not change regions' output based on it (as the output is what tests check)
For the trace facility to work, we need at least the following:
we have these, as:
LogtLevel must be global.
NTA_LOG_LEVEL
We need a function like setLogLevel( ) for applications to call. (they should not have access to the global LogLevel variable).
Network.setLogLevel()
(but they do have access to the global variable)
We need a function like isDebug( ) to allow conditional processing related to the trace.
NTA_LOG_LEVEL == htm::LogLevel::LogLevel_Verbose
We need a function (or Macro) like NTA_DEBUG( ) to indicate what is to be displayed in the trace.
can we use NTA_DEBUG?
TMRegion has several 'optional' outputs. For the 'optional' outputs, if there is no outgoing connection, then the output is not generated. So, to get the trace in Verbose mode we should generate the output data even though it is not being sent to an output. That is what the above 'if' statement does.
going back to what I was trying to prove wrong.
For the 'optional' outputs, if there is no outgoing connection, then the output is not generated
and
However, recently we exposed the ability to access the output buffers directly with region.getOutput(name).getData(). This causes a problem because if there is no outgoing link the optional outputs cannot be accessed in this way. Perhaps to be on the safe side we should always produce all outputs even if they are not used.
I'm not sure what the "optional" output is in this context, but this is the bug. In the test mentioned, the Network looks like encoder -> SP -> TM
with TM's getOutput("predictedActiveCells")
apparently being optional. But users (and our tests) can expect the output and query for it, and it's empty.
So we should either:
- always produce all outputs, as you suggest.
- or document this and require user to mark the (optional) output as required. How would one do that? (eg on network_test.py, see Cleanup exceptions #742 (comment) )
- what I wanted to do in 2149e83 is extending the limitation from "has outgoing" to "has in or out links". If you look at it as a graph problem, a network with (some) disconnected regions should not compute them (
E A->B->C
: do not compute E). Now we compute all ofB
because it has out link to C. But we do not compute optional outputs ofC
(as it has no outgoing links)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static LogLevel NTA_LOG_LEVEL = LogLevel::LogLevel_Minimal; // change this in your class to set log level
The above variable is not Global. It will create a different instance in every .cpp in which log.hpp is included. So it will work only if the logLevel is set and used in the same .cpp.
To make this global you need to declare it extern
in the .hpp and someplace in some .cpp it must be declared without the static
. OR instantiate a class that has this as a class variable...a more C++ way of doing things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, and you called the outputs 'regions'. That confused me at first. A region is the wrapper around an algorithm. A region can have multiple outputs.
Keep in mind that when we start doing multi-threading, this global variable will not be very useful. A better way to handle trace when doing threads is to have the global in the thread-global space so that each thread's trace can be controlled independently. That is why I wanted to use a function or Macro to set the logging variable rather than assigning a value to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets always generate ALL outputs, which means we don't need isDebug( ).[...] If they are declaring E then they must be using if for something.
you're right, let's not be smarter than the user, the implementation will also be simpler.
A better way to handle trace when doing threads is to have the global in the thread-global space so that each thread's trace can be controlled independently. That is why I wanted to use a function or Macro to set the logging variable rather than assigning a value to it.
I agree. Are we able to do that w/o a significant change to how the NTA_WARN etc macros are used? Or we make a big step and deprecate the macros and use the Log class you've charted (like in java Logging works) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or we make a big step and deprecate the macros and use the Log class
Oh, I don't propose deprecating the macros, just change the macros to use the Log class.
If you declare the class as a static variable in the Log.hpp file it will create a separate instance of it in each .cpp in which Log.hpp is included. But that is ok since we only need state from the class variable which will be global in scope. An alternative is to go head and use the thread global space to store the LogLevel state (prefered). Either way we would not need a Log.cpp. Everything could be hidden by Macros so that they can be changed without changing the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, there is a new feature in C++11 that I did not know about. Its called thread_local
. So we don't have to get the thread id and allocate the thread specific space for it. Its done for us.
See https://en.cppreference.com/w/cpp/language/storage_duration
thread storage duration. The storage for the object is allocated when the thread begins
and deallocated when the thread ends. Each thread has its own instance of the object.
Only objects declared thread_local have this storage duration. thread_local can appear
together with static or extern to adjust linkage. See Non-local variables and Static local
variables for details on initialization of objects with this storage duration.
With this you don't need the Log class.
but not successful yet
I like what you did for the marco's but the 'trace' facility will not work as you have it. If you would like I am willing to re-implement a simplified trace for you in Log.hpp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@breznak I took another look at this so see if we can agree on what it takes to finish it. I like what you have done and would just like to correct the logging facility.
This gives me everything I was looking for.
- I will have Network::setLogLevel(LogLevel_xxx)
- The LogLevel is global within a thread.
src/htm/regions/TMRegion.cpp
Outdated
@@ -176,6 +176,16 @@ void TMRegion::initialize() { | |||
args_.sequencePos = 0; | |||
} | |||
|
|||
|
|||
bool TMRegion::isConnected_(string name) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked about always generating all outputs, even if no connections are made. So this new function would not be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
about always generating all outputs, even if no connections are made
I'm having second thoughts about this. I've implemented that in TMRegion and it works fine! (same should then be done for SPRegion).
What we should consider is the tradeoff between ease of use for the user/speed. We're computing outputs that may not be needed (and I'm not sure of it's costly enough to care).
The proble is only with the leaf node (here TM, and not SP) which has no outgoing links. Alternative approach would be a dummy OutputRegion that the user would add after the TM to specify which outputs are linked (=used).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we should consider is the tradeoff between ease of use for the user/speed.
I agree with you. We should not be computing the outputs if they are not used if we can help it.
The problem is when an app gets the input or output buffers directly with region.getOutputData( ) or region.setInputData( ). These could be used on any buffer at any time without the region impl being involved.
Originally, these two functions were not exposed to apps. The apps had to call region.getArrayParameter(name) to access the 'optional' data and in those handlers the region impl had the chance to generate the buffer before returning it.
Perhaps we could add a hook in getOutputData( ) that allowed a region impl to do something before it returned the buffer. That adds yet another complication to the region impl's, but only for those that have optional buffers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That adds yet another complication to the region impl's, but only for those that have optional buffers.
the implementation seems already quite complex to me.
- is there a way in the Spec to say "I will be using this optional output"?
- or we just KISS it and compute it all for now, unless someone complains on Regions' performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a way in the Spec to say "I will be using this optional output"?
No, the Spec is static and describes what the region will do, not how it is used.
or we just KISS it and compute it all for now, unless someone complains on Regions' performance.
I guess so. We should keep thinking about how we might manage this however.
@breznak I hope you don't mind me messing with this. |
To see the trace work, I added a test. See TMRegionTest.cpp You will need to turn on verbose for it to show in that test. No since displaying it all for normal running of the unit test. |
This is what the trace looks like on Ubuntu 19.04
|
The trace would be a little more readable if I removed the std::endl from the end of the operator<< for the SDR class so that the trailing ']' would not appear on the next line. But I did not for fear that I might mess up some other display that outputs SDR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope you don't mind me messing with this.
not at all, thank you for improving the logging facility!!
But I did not for fear that I might mess up some other display that outputs SDR.
it wouldn't be any critical, but I think it's fine as is for now 👍
this is ACK for me, please approve and we can merge this!
@@ -28,32 +28,36 @@ | |||
|
|||
namespace htm { | |||
enum class LogLevel { LogLevel_None = 0, LogLevel_Minimal=1, LogLevel_Normal=2, LogLevel_Verbose=3 }; | |||
static LogLevel NTA_LOG_LEVEL = LogLevel::LogLevel_Minimal; // change this in your class to set log level | |||
// change this in your class to set log level using Network.setLogLevel(level); | |||
extern thread_local LogLevel NTA_LOG_LEVEL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess non NetworkAPI classes can also use the static Network.setLogLevel(), but writing directly to this variable should work too, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but writing directly to this variable should work too, right?
yes. you can just assign to it. NTA_LOG_LEVEL = LogLevel::LogLevel_Verbose;
We could use some other class to put the setLogLevel( ) function on that is accessible by the algorithms but I did not find anything general enough. Anyway, the python binding interface has it on the Network class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets see if it will allow me to approve this since the last push come from me.
Thanks for review and your code, David 👍
yeah, that's a funny thing. If I open a PR with someone elses code to this repo, the person who opens the PR owns it and cannot give reviews. |
NTA_LOG_LEVEL = htm::LogLevel::LogLevel_{Verbose,...}
to set logging sensitivityFor #175
This hopes to help a strange crash in #736 related to NTA_THROW macrounfortunately no