-
Notifications
You must be signed in to change notification settings - Fork 128
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
WIP: return fortran arrays in original order in python high-level interface #1681
Conversation
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.
One thing about the use of static
and minor things.
I added some documentation (good point). I've also added a test to show that I'm now passing the If you would prefer it, I could remove the configurable default for |
@germasch thanks for addressing all the points. Looks good.
Let me discuss with more people and find out more. In the meantime we can remove it and think more about it, while current users are not affected. |
one minor thing, can we have a separate PR for changes in bpls (it's a different topic) just so I can merge the changes in Python once this PR is done. Thanks! |
I'll drop he bpls changes from this PR. |
The thing is, the way things are right now, people are (potentially) affected. The old behavior is I think that's generally a difficult situation -- one wants to change behavior, because it turns out that some old behavior is not really how it should be. There are basically a few options:
My suggestion on this (and what the current PR does) is to follow the 3rd option. To limit the breakage (if any), one can put into the release notes:
I really dislike option 2. Option 1 wouldn't be unreasonable, though I suspect it won't really fix the issue that this PR is trying to address, since people don't tend to read documentation in much detail: Someone doing |
dea9f90
to
69d3736
Compare
@germasch I agree with your points. I just want to hear from other stakeholders before changing current behavior that could affect their current workflows. |
@williamfgc Sounds good to me. I dropped the bpls related changes from this PR. |
@germasch : Would it be sensible to add a few words about this feature into the
Why don't we add the ordering as a metadata parameter in Another question is whether we could do an in-place matrix transpose. Of course we'd need some performance numbers, but assuming higher RAM->CPU bandwidth than Disk->Ram, we should be able to do a matrix transpose on a lower timescale than reading the data. Assuming the performance numbers are reasonable, this seems like a very robust solution to me; no user parameters are required to do the right thing. |
This PR adds a bit of documentation, on the
Well, it's difficult to change existing file formats, though I did suggest to add the information into the metadata in future incarnations. For now the info is on ordering is global per file, though one probably could improve on that in a backwards compatible way.
I think one can argue either way on this. People here reasonably say they want to focus on performance, and obviously a transpose op may not be cheap, in particular, if it happens on data which is already in memory (e.g., because it's using one of in-memory engines). I don't think it'd necessarily be a bad idea, as long as the user explicitly says "I know what I'm doing". But modern languages like python or c++ (and in some sense even C) have the ability to handle different layouts on the fly, and I'd say that's preferable if available, so that's the low-hanging fruit I'm going for (specifically, I'm thinking from the point of view of my codes, and what I need for them.) |
Ok, so as I understand it:
However, let me float an alternative: Fortran writes and reads arrays in C ordering. In this case, we have the context that we are about the perform an expensive disk operation (though the in-memory stores are an important caveat as you mention), and hence the question is whether this operation is expensive relative to the disk ops. I suspect it's not (but haven't written a benchmark yet, so grain of salt), but my tolerance for expensive operations is rather high, because in my view the truly expensive operations are the ones that are performed between your ears when an abstraction leaks. Now, I don't want to make the perfect the enemy of the good, but the abstraction still leaks: The format is not self-describing. Hence every user is going to hit this same bug, and debug a couple days, and finally find your mitigation-which they'll appreciate! But better that they not hit it in the first place. Finally, I'm not convinced that the total cost of Fortran performing the transpose is positive. It is amortized over the reads. For instance, your bug report shows you wrote Fortran, and then used Python for reading and subsequent analysis. This seems like a common pattern for many users. Hence if the data is simply written in C ordering, most of the operations are done correctly. So @pnorbert @germasch @williamfgc is this a sensible solution? |
I don't think the file format is broken for the common case. That is, in my area of science, Fortran is clearly dominant, and always uses column-major. When writing from Fortran, it is correctly recorded that the data is column major. Adios2 is also doing the right thing when writing row-major data from C/C++. However, there are people (plenty, I suspect, but definitely me), who write column-major data from C/C++, and unless they transpose the data themselves, there is no way to get it onto disk with the correct metadata. That's a separate issue to the one here (it can be addressed to some extent, and I'm planning to do that). I think when writing column-major data from Python, Python will do a transpose for you to row-major, so that's expensive, but what ends up on disk is correctly described. What this PR addresses is the reading side, in particular, actually using the available information about the storage layout. When using a C/C++ tool like bpls on data written by Fortran, it will present the data with reversed index order, and hence transposed. This PR added a patch to bpls to just at least add a note about the presented data being transposed in this case, but I took that part out as requested. So this PR only addresses the reading side of the Python HL interface where it uses the original layout information from the metadata to create the Python/numpy array with the original ordering, so that both row-major and column-major array show up in their natural order. On the read side with Python, a transpose is never required, because it supports arbitrary strided layouts. That is also true in C++ if one uses a multi-d array class that supports both data layouts (which I think are most or all of the popular ones). But currently on the C++ side, the information is not available (I'm planning to change that). The only place where transposes really would be required is if writing both row-major and col-major into the same file, because there is no way to mark individual datasets with separate layouts, so one would have to settle on one or the other and transpose the other data. I'm not sure that's a real life issue, so I don't really have any plans about those. Everything else can be addressed within the current framework, and I'm planning to do that -- this is the first step.
Fortran would only have to perform a transpose if reading data that was originally in row-major format (and where the code can't instead just reverse the indices). I suspect that's a very uncommon case. The issue is much more about generic visualization / analysis tools which tend to be written in C/C++/Python, and which will work better if the index order is not reversed when reading column-major data.
|
Kai, can you describe the current state of this PR? I am trying to run the tutorial's heat2d example, Fortran output, python reader. The script will see the array in one ordering and make the selection in that, but the underlying C++ code will complain about out of bounds in the other ordering (it is confusing to me which sees in which order). Then, if I try to add
After adding
The shape of the variable in Python is calculated from
So maybe you did not take care of this function? How do you know what is the shape of the variable before you read? |
Well, I was thinking it was ready to be merged, but thanks to your review, there's some work to be done now ;)
I made a typo in the description above, it should be So ideally, the Python interface (and really any interface) should stick with just one index order. I knew that I didn't change anything about writing yet (which I think is relatively orthogonal), but I missed the shape in
Well, I guess I haven't hit the case where I need to know the shape beforehand (because I already generally know things about my simulation in my use). But FWIW what |
Over all, super great to have this feature, so thanks. I know it's not the most popular opinion and I may be dismissed by others here but I would strongly be in favor of using the actual data layout in the public API, i.e. I do really like having the data layout known in the variable. Maybe I'm not seeing it but what's the use case for |
The enum currently stands both for the actual layout of the variable (so it
may be unknown at the beginning), and for user settings (original tells
adios to represent the array towards the user as it is in the data).
…On Tue, Aug 27, 2019 at 9:59 AM Chuck Atkins ***@***.***> wrote:
Over all, super great to have this feature, so thanks.
I know it's not the most popular opinion and I may be dismissed by others
here but I would strongly be in favor of using the actual data layout in
the public API, i.e. RowMajor ColMajor, and move away from a language
based label, i.e. C or Fortran, as much as possible. Fortran is pretty
much always ColMajor but equating C ordering to RowMajor as a
nomenclature is either ambiguous often just wrong. It's certainly common
practice to equate the two but especially in the world of HPC with the
intermixing of C, C++, Fortran, Python, etc., not to mention GPU data
layouts, "C" based codes often intertwine their data ordering and use
ColMajor, RowMajor, or both
I do really like having the data layout known in the variable. Maybe I'm
not seeing it but what's the use case for Unknown and Original in the
::Layout enum? It's never really unknown since adios has to use something
to base it's index calculations off of.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1681?email_source=notifications&email_token=AAYYYLNXULCPB2N6SXE2RTTQGUXNHA5CNFSM4ILQGW32YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5H2TOI#issuecomment-525314489>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAYYYLIVEUTOJVLKDTKVBH3QGUXNHANCNFSM4ILQGW3Q>
.
|
Agree 100%, exactly my point.
|
@pnorbert, can you try whether the latest commit fixes your shape-related issue? |
6515c95
to
71fcf29
Compare
Only works for BP3 and BP4 for now, will default to Layout::Unknown otherwise. This function is not exposed in the public API as of now, it'll only be used by the python interface and bpls for now.
An additional, optional, `order` argument to read will determine how the data that is being read is represented as a numpy array. Options are 'C', 'F', 'K', which are row-major, column-major and original layout, respectively. If original layout is not known, 'C' will be used, which is previous behavior. If the order argument is not provided, the read will default to `adios2.File.read_order`, which itself defaults to 'K', but user code can override that default.
This is kinda tricky -- the goal is the user is able to work with the data without knowing it's original storage order, and this achieves that, though it requires a custom stride calculation.
This piece was missing to provide apps a consistent view of a variable in its original data layout / ordering. Adds a test for it, too. The implementation isn't pretty...
@germasch, The functionality and features implemented in here are great. Given it's significance to the API though it's really something that needs to be implemented within the core and then propagated to all the APIs bindings rather than being implemented just within the python bindings. While there's currently not complete feature parity across all the APIs, there is for the most part. A change like this in particular though would need to be available throughout. Given the disruption of such a change internally and the change to all the public APIs we've decided to push such an implementation to after the end-of-september release. We do have developer resources on the team to put towards implementing it though but the timeline will be pushed back a bit. |
Actually, I don't mind dropping this PR for the time being. Maybe not quite for the same reason -- the core functionality needed for the aspect covered here is a very minor addition, but I agree that it leave other aspects to-be-done for now, like HL writing and the low-level Python interface entirely. I wouldn't think having this only available in Python to start is too bad, since Python is the only language that natively supports different layouts. In fact, I think one can learn from it what a modern API for the other bindings might look like. Anyway, the major reason I agree that this PR may not be the best idea is that it either
Not really directly related, but the python HL API is not very pythonic at all. So there is an opportunity to introduce a more pythonic API and at the same time introduce new, more natural handling of layouts with that. |
@germasch feel free to propose more pythonic interfaces. Personally, I am not a Python experienced user. Most of the API design came from balancing the input from Python practitioners and what pybind11 v2.0 could support at the time for Python 2 and 3, which feels ancient today. |
So this is what I originally set out to do:
m_OriginalLayout
member tocore::Variable
, which defaults toUnknown
, but BP3 and BP4 engines will set it to the original layout the data was written with it.bpls
behavior is changed slightly (only): If the data was originally written from Fortran, it'll add the note(transposed)
to the shape, but other than that, it still shows the dimensions reversed / dumps the data transposed. (This could be changed in the future to present the data how it was originally written, if that's considered more useful than current behavior)More specifically, the
read
methods gain an additionalorder
parameter.'F'
means ,interpret the data as column-major (so if it was written by Fortran, it'll show up in its original order. If it was originally row-major, this makes it show up transposed.'C'
means, interpret the data as row-major, which is what current behavior is always.'K'
means, interpret the data as the layout that it was saved as (if known, default row-major).If no order parameter is provided, it defaults to
'K'
, which means both data written by C and Fortran will show up in their original order, which is what most people would want. However, it is a change in behavior. The default order, if not provided as an argument, can be easily changed usingwill make restore current behavior. So while this PR changes behavior, it's at least easy to restore the original behavior if anyone turns out to be affected. I chose this way because the default behavior will avoid confusion, at the cost of potentially causing issues to someone relying on the old behavior. One could prioritize the opposite way, ie., keep old behavior by default and everyone who would like to see their Fortran arrays presented in original order would need to explicitly set
read_order
to'K'
.Fixes #1665