-
Notifications
You must be signed in to change notification settings - Fork 26
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
Support running multithreaded: Add an IOSvc, an algorithm for reading and writing for functionals in k4FWCore #173
Conversation
b8b9b34
to
8f83b37
Compare
30a5e80
to
3054187
Compare
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 left some comments on the service part
For the algorithm part. Previously the client algorithms were supposed to be derived from GaudiAlgorithms with custom Traits
, now they are meant to be derived with provided k4FWCore::Consumer
etc. Does it mean later all other flavors of functional algorithms (e.g. filters) should be added upon request? Or is there some incompatibility built-in?
k4FWCore/components/IOSvc.h
Outdated
Gaudi::Property<std::vector<std::string>> m_readingFileNames{this, "input", {}, "List of files to read"}; | ||
Gaudi::Property<std::string> m_writingFileName{this, "output", {}, "List of files to read"}; | ||
Gaudi::Property<std::vector<std::string>> m_outputCommands{ | ||
this, "outputCommands", {"keep *"}, "A set of commands to declare which collections to keep or drop."}; | ||
Gaudi::Property<std::string> m_inputType{this, "ioType", "ROOT", "Type of input file (ROOT, RNTuple)"}; |
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.
At least in Gaudi the convention seems to be property names starting with uppercase (e.g. "Input")
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.
And we have been using lower caps name for a while in PodioInput
and PodioOutput
: https://github.com/key4hep/k4FWCore/blob/main/k4FWCore/components/PodioInput.h#L51 and https://github.com/key4hep/k4FWCore/blob/main/k4FWCore/components/PodioOutput.h#L59; I don't have an opinion nor care much about this
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 only had a brief look so far. I think my main concern still is the fact that we re-implement Gaudi functionality here and that we have to maintain that. Maybe we can try to figure out if it's possible to upstream some of the necessary things in order to make this work?
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.
Having read a bit more, I am wondering whether this still supports algorithms that produce / consume other things than podio::CollectionBase
derived things. E.g. if some (internal) algorithm just produces some values or a vector
of something that is then consumed by another one.
Would it be possible to have Taken from
The avalanche scheduler recognizes only the transformer to have data dependencies
|
python/k4FWCore/IOSvc.py
Outdated
if attr == 'output': | ||
if os.path.dirname(value) and not os.path.exists(os.path.dirname(value)): | ||
os.makedirs(os.path.dirname(value)) |
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 reason to create a python wrapper instead of putting this in a C++ class directly?
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 this is also addressed in #170 already.
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.
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.
My concern was that the python wrapper will have different behavior and it's not related to make it work or be nicer in python. I think the intent was to have python as configuration layer but keep the logic in C++
#170 does also error handling
Yeah they have to be added on demand. With the Producer, Consumer and Transformer in this PR it's most use cases, we're only missing filtering and maybe some utility functionals |
k4FWCore/components/Writer.cpp
Outdated
setProperty("Cardinality", 1).ignore(); | ||
} | ||
|
||
mutable Gaudi::Property<std::vector<std::string>> m_OutputNames{this, "CollectionNames", {}}; |
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 the non-reentrant algorithm should override the bool isReEntrant() const
method. Parent returns true
.
setProperty("Cardinality", 1).ignore(); | |
} | |
mutable Gaudi::Property<std::vector<std::string>> m_OutputNames{this, "CollectionNames", {}}; | |
setProperty("Cardinality", 1).ignore(); | |
} | |
bool isReEntrant() const override { return false; } | |
mutable Gaudi::Property<std::vector<std::string>> m_OutputNames{this, "CollectionNames", {}}; |
k4FWCore/components/Writer.cpp
Outdated
// This is the case when no reading is being done | ||
// needs to be fixed? (new without delete) |
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.
the operator()
declaration is rather long and I feel it could benefit from refactoring to several smaller methods. The memory managment could be done on the top level and smaller methods receive a raw pointer (non-owning semantics)
As is the operator has several returns so a manual memory managment will be tedious and error prone.
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.
Here I don't see how to refactor, the steps are quite short but the error checking makes it very verbose. Memory management doesn't need to be done there, as the wrapper that is created with new is deleted by the store later (I checked if it's deleted then it's a double delete). So in the end it's 3 loops, once the first time to get which collections are available, another one to remove from the store the ones that belong to a frame and a final one to put into the frame whichever collections are going to be written.
The algorithms using runtime specification of collections with For instance, I changed
After inspecting the data dependencies: Data dependencies:
Data flow:
The baseline version without I think it could be good to test multi-threading with multiple different algorithms instead of a two tests dedicated to multi-threading but the algorithm tests running with default scheduler |
A question that just come to me while looking at #172: Does this still work with non functional algorithms? |
From @tmadlener
It doesn't because support for it hasn't been implemented (but could be). I have added a few asserts so that now one gets a proper error message when using input / output types that are not part of the allowed ones. This has been discussed several times in the EDM4hep meetings and I think the agreement lean towards not letting algorithms use classes outside of EDM4hep or podio. If I remember well, this is the same that we have with the current support for functionals in k4FWCore. |
I think the question about which types can be transported through the event store between algorithms is a separate concern to the question of whether we want to support non-functional algorithms. For the first one, I think having a limitation to only support EDM4hep / podio generated types is OK in the long run. I think we will probably need support for non-functional algorithms as well. At least at the moment we need to be able to run non-functional algorithms as well as to pass non-EDM4hep objects via the TES, due to the MarlinWrapper. There we need to pass an |
Another question that I just came across is how do we foresee the metadata handling here? Would that still simply work with the |
StatusCode code; | ||
if (m_hiveWhiteBoard) { | ||
if (!incident.context().valid()) { | ||
info() << "No context found in IOSvc" << endmsg; |
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.
info() << "No context found in IOSvc" << endmsg; | |
info() << "No context found in incident" << endmsg; |
Or why IOSvc?
Co-authored-by: Andre Sailer <[email protected]>
A few answers to some questions
This is now fixed and the data flow graph looks correct with From @tmadlener (sorry I think I replied to another question in the reply to this):
Yes, there are two tests
Not implemented for now. |
Update: rechecked and tuned the tests that mix "old" and functional algorithms in this PR: the conclusion is that when using |
Test 33: Transformer INFO Transforming 2 particles
33: IOSvc INFO No context found in IOSvc
33: ApplicationMgr INFO Application Manager Stopped successfully
33: NTupleSvc ERROR ERROR while saving NTuples
33: ApplicationMgr INFO Application Manager Finalized successfully
33: ApplicationMgr INFO Application Manager Terminated successfully with a user requested ScheduledStop
1/1 Test #33: FunctionalMTFile ................. Passed 4.02 sec Test |
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 couldn't find any violations of data or control flow. A few warnings could be silenced by putting a correct value explicitly (comments below), but in general should be ready to go 👍
Having said that I found one thing that was a bit puzzling and different with respect to plain Gaudi and previous k4FWCore algorithms. This isn't a deal-breaker
With the current design all of algorithm's inputs and outputs must be declared as the lists, even if an algorithm operates on singular objects.
For instance, a producer that takes nothing and produces a single collection has to be constructed with a list of keys for outputs (KeyValues
) instead of a single key for output (KeyValue
) as in plain Gaudi. All the provided keys are used in data dependency resolution but only the first key will be populated and put to an event store.
struct ExampleFunctionalProducer final : k4FWCore::Producer<edm4hep::MCParticleCollection()> { | |
// The pair in KeyValue can be changed from python and it corresponds | |
// to the name of the output collection | |
ExampleFunctionalProducer(const std::string& name, ISvcLocator* svcLoc) | |
: Producer(name, svcLoc, {}, KeyValues("OutputCollection", {"MCParticles"})) {} | |
// This is the function that will be called to produce the data | |
edm4hep::MCParticleCollection operator()() const override { | |
auto coll = edm4hep::MCParticleCollection(); | |
coll.create(1, 2, 3, 4.f, 5.f, 6.f); | |
coll.create(2, 3, 4, 5.f, 6.f, 7.f); | |
// We have to return whatever collection type we specified in the | |
// template argument | |
return coll; | |
} |
This is also reflected in the steering files:
- This would be a standard code for plain Gaudi producer but here will result in an error because
OutputCollection
must be a listExampleFunctionalProducer("Producer", OutputCollection="Foo")
- This is will execute correctly:
ExampleFunctionalProducer("Producer", OutputCollection=["Foo"])
- this will also execute correctly but produce only
Foo
. A scheduler will see the algorithm as producing both "Foo" and "Bar" collections. Ideally it shouldn't be even possible to put a second item:ExampleFunctionalProducer("Producer", OutputCollection=["Foo", "Bar"])
While this is could be solved with a convention, I'd say it can be misleading for algorithm developers. The documentation on functional algorithms is already a bit cryptic and now we have algorithms that have signatures for singular object in input/output but in property system declare operating on lists of objects
Co-authored-by: Mateusz Jakub Fila <[email protected]>
Co-authored-by: Mateusz Jakub Fila <[email protected]>
Co-authored-by: Mateusz Jakub Fila <[email protected]>
That is indeed something that isn't very nice about the current way. I'll have a look at how to change that. As discussed in the past meetings, we're merging this now which has gotten already quite big and issues will be fixed in other PRs. |
BEGINRELEASENOTES
IOSvc
to take of the input and output, together with aReader
andWriter
algorithm, to be used instead ofPodioInput
andPodioOutput
std::map<std::string, const Coll&>
for reading andstd::map<std::string, Coll>
, i.e. for reading we get const references to the collections and for writing we put the collections usingstd::move
typically in the mapstd::map<std::string, edm4hep::MCParticleCollection>
we only have to set the names in python and all the collections will be in the store available for the next algorithmsENDRELEASENOTES
Ready for review! During the coming days I'll test a few things: if there are memory leaks, metadata handling (most of it copied from what we were doing with
PodioOutput
), running multithreadedLeft out of this PR: writing multiple times in the same chain (multiple output files). That will be in a different PR, this one is already big enough.
In addition, when AIDASoft/podio#522 is merged I'll make the changes so that it's easy to swap between the normal ROOT, either in this PR or in a different one.