Lazy resampling executes non-lazy transforms without executing pending transforms #7370
Replies: 34 comments
-
Thanks and agree that adding an option will allow for different use cases, one of the use cases is to achieve fast but less precise results compared with the non-lazy... {"optimize": "lazy_to_front", another_option: 3"} #json-like Looks highly extensible |
Beta Was this translation helpful? Give feedback.
-
hi @atbenmurray I don't understand the issue well
which Noise? there is no intensity Noise transform here. or do you mean something else? --
Will it move all lazy transforms to the front? (then it won't be the right sequence of transforms ,) . If I understand it correctly - to move ALL lazy trasforms to front/back, then it will be applicable only to some special cases (and not in general), since we can't simply re-arrange transforms. A common transform list I use is following (with Indentities for current "lazy_evaluation" version)
I don't think we can simply re-arrange lazy (spatial) transforms to the front. I personally think we should do lazy transform explicit. Anyone who wants to use them must create a separate list of spatial transforms with Compose( spatial_transforms, lazy=True), and then use that new compose_transform as a step between main transforms |
Beta Was this translation helpful? Give feedback.
-
@myron thanks for the detailed response! In general, people shouldn't have to use this compose option. The primary purpose for it is to work around the situation we have now for the upcoming release. As I understand it, we have benchmarked pipelines that rely on the dev branch behaviour. The dev branch produces incorrect output in the general case of having lazy and non lazy transforms mixed together, because, unless you use Identity/Identityd (becoming ApplyPending/ApplyPendingd) to enforce ordering, all non-lazy transforms will be executed first. This decision was taken to improve performance on a benchmark that had a Lambda transform in it (as far as I am aware from the other conversations), but it will break our user's pipelines in the general case. I'm doing some experiments with your pipeline so that I fully understand the implications @wyli I guess I really need to get a complete list of the pipelines this behaviour was done for so we can be sure that this fix would sort those pipelines for benchmarking while allowing default behaviour to not break our users' pipelines |
Beta Was this translation helpful? Give feedback.
-
I think we just use the by the way I disagree with "The dev branch behaviour has a major issue in that unless you use Identity/Identityd (becoming ApplyPending/ApplyPendingd) to enforce ordering", could you rephrase to avoid misleading info? |
Beta Was this translation helpful? Give feedback.
-
The goal has always been to allow it to work without breaking anything in the general case. That way it is accessible just as a switch that can be flipped at the policy level rather than having to rewrite pipelines. The user is always then free to get very fancy with how they organise their transforms to get the extra benefit, but |
Beta Was this translation helpful? Give feedback.
-
I've modified it to say 'will produce incorrect output'. |
Beta Was this translation helpful? Give feedback.
-
@wyli I really need to see the pipelines that the dev branch behaviour was intended for. Surely you can point me at them. The flag isn't a fix unless we know it fixes those specific cases. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Great! So if we eyeball these and verify that moving all the lazy transforms last is the equivalent of dev branch behaviour, then this is the right modification for the PR and we should be able to avoid needing to re-benchmark |
Beta Was this translation helpful? Give feedback.
-
I think I understand your point, but my point is that, there are 200+ transforms in monai, even if it's in non-lazy mode, you can easily compose certain sequences of some of them that may not work as you think (especially if you don't read the documentation)... we can't say that the monai codebase is incorrect or has major issue, right? |
Beta Was this translation helpful? Give feedback.
-
also, we'll never achieve exactly the same result by design, like the padding modes, output data types (quantisations), and interpolation modes. In most use cases, there's a trade-off for speed/interpolation quality/memory footprint, in my opinion, we just need more documentation about the details and let the user decides what transform sequence to use. |
Beta Was this translation helpful? Give feedback.
-
That's why the default behaviour should be as has always been stated in the design and presentations and discussions, i.e. that the ordering of transform execution is unmodified, and that any changes to transform ordering should come from option flags. Anyway, our plan is to add the option flag which should allow the benchmarked pipelines to run as discussed, and have the default behaviour be to not modify transform order. There won't be any need to document anything as an issue in docstrings or release notes, because it won't be an issue, just an option that allows you to move all the lazy transforms last if you want to do so. draft of a docstring fragment for compose options: option 'move_lazy_last': This option reorders the transforms in a given pipeline so that they are executed after all non-lazy transforms. This can be helpful for performance and image quality, as it results in fewer resampling operations. Note that this option should not be used with pipelines that require ordering to be preserved, such as if you have a mixed sequence of lazy and non-lazy spatial transforms that must be performed in the specified order. If your pipeline uses |
Beta Was this translation helpful? Give feedback.
-
Ok, so after trying a couple of different interpretations of the dev lazy mode, I know what So: 'LX' means lazy transform with id X (id doesn't mean anything here, just helps with clarity in the examples) Given a list of transforms:
The presence of an ApplyPending splits the transform list into multiple sublists
Each sublist is then sorted so non-lazy transforms go first, lazy transforms go next, and then the ApplyPending goes last:
Which then concatenates back to:
This precisely mimics the behaviour in dev and makes sense from a user perspective; it is easy to explain that an ApplyPending effectively splits the list of transforms into sublists and schedules each separately. I've implemented this and I am running a wide selection of tests on it. |
Beta Was this translation helpful? Give feedback.
-
On the dev branch there is no reordering of the user-defined transforms with or without lazy resampling, you can turn on the verbose=True to confirm this. If you are talking about the 'applied_operations' #6371 #6439, these are about inversing of a compose using MONAI/tests/test_integration_lazy_samples.py Line 159 in d29914d It has nothing to do with the transform call, that is |
Beta Was this translation helpful? Give feedback.
-
There is effective reordering on Given the following pipeline on dev:
Flip is executed lazily
Spacing is executed lazily
Rand3DElastic is executed non-lazily, without the pending transforms being executed first. This means that Rand3DElastic is the first to change the actual data (non-lazy execution), ie out of order
Rotate is executed lazily
Finally, the pending transforms are executed
Rand3DElastic is the first transform to be executed on the data; it should have been the 3rd It doesn't matter that the logging shows that Rand3DElastic was visited in order. It is the first transform that is executed on the data. That is what I mean by effective reordering. This should not be the default behaviour, because we want users to switch on lazy without it breaking their pipelines. |
Beta Was this translation helpful? Give feedback.
-
Agreed, that's what we have on the Dev branch, that's what we have for lazy resampling on the Dev branch. |
Beta Was this translation helpful? Give feedback.
-
It is a bug that
gets effectively executed as
The default behaviour should be that no reordering occurs, because it breaks what the user has defined for their pipeline. Reordering must be opt in. PR will allow original reordering, reordering, inversion of reordering, for both cache and non-cache. |
Beta Was this translation helpful? Give feedback.
-
I don't think that's a valid/good assumption for monai now and future |
Beta Was this translation helpful? Give feedback.
-
I only point that out because you mentioned that rather than addressing the core point and in the previous paragraph I explained that the behaviour is clearly defined for caching / non-caching I've already shown in previous discussions, presentations and so forth that we can integrate caching into the same mechanism that does things like specifying out of order performance optimisations. |
Beta Was this translation helpful? Give feedback.
-
ok, in the interests of time for releasing v1.2, just to reiterate my understanding:
If you don't agree with any of the above, let's have an offline discussion with the wider team... |
Beta Was this translation helpful? Give feedback.
-
I'd phrase as the following:
But yes, in essence, it should be fine |
Beta Was this translation helpful? Give feedback.
-
Sure, that's good. Implementation-wise, do you plan to include all the code changes in PR #6257 and what's the timeline? If needed I can help run some benchmarks and integration tests with all the testing environments we currently have. |
Beta Was this translation helpful? Give feedback.
-
Yes, PR #6257 should come across in its entirety. It has the default lazy mode, the lazy_to_last flag mode, and the restored ability to run invert. I hope to get the commit done by afternoon / late afternoon today. |
Beta Was this translation helpful? Give feedback.
-
I note that in the pipeline
Rand3DElastic doesn't appear in the applied_operations list. Is this intentional? If not, I can resolve that while I am at it. |
Beta Was this translation helpful? Give feedback.
-
Theres work in progress #1793 for that, but need a major rework to use the latest API |
Beta Was this translation helpful? Give feedback.
-
Ok, so it looks like at least some of the pipelines result in moving execution order around on a per key basis. As a result, it won't be possible to have inversion work for that mode without largely rewriting Example:
So I intend to do the following:
Then for 1.3, I'll have to look at reimplementing the invert mechanism to work with inconsistent ordering on different dictionary keys. It might be that the full lazy implementation already handles this but I'll have to check. |
Beta Was this translation helpful? Give feedback.
-
The commits for this are now on #6257 |
Beta Was this translation helpful? Give feedback.
-
@wyli Ok so there is still an issue. Vanilla lazy mode requires that the user does not have to modify their pipeline in order to run lazy=True, so The only way that I can think to handle this is to have transforms such as |
Beta Was this translation helpful? Give feedback.
-
For me, warning people isn't enough. lazy with options=None should never break a pipeline. I'm also not keen on the idea of cropping transforms calling apply_pending internally. It can't happen anyway because the existence of Transforms that require it should be able to indicate that they do through the LazyTrait interface. I'm testing something out that solves the issue in the in_order pipeline |
Beta Was this translation helpful? Give feedback.
-
the root cause is that the crop sampling locations are defined in the image space instead of the physical space. in the end the coordinates should be defined in the original physical space and they should carry the geometric information during the preprocessing then there's no ambiguity. |
Beta Was this translation helpful? Give feedback.
-
Describe the bug
Lazy resampling will execute the following pipeline incorrectly:
My understanding is that this was implemented deliberately to provide improved performance on some of the existing pipelines that are being benchmarked, but it will break a lot of our users pipelines if the non-lazy transforms rely on the pending transforms to have been run:
. Noise will undergo spatial distortion that was not intended
. Non-lazy spatial transforms will output the wrong result
While we want the best performance on our pipelines, we shouldn't expect our users to have to modify their pipelines to execute correctly.
The most obvious solution is a new flag for Compose. We've discussed it as a 1.3 feature but now we need it for this release.
Options can be thought of like compile flags for a c++ compiler.
We should pick a way of specifying them that is extensible enough for our purpose and easy for the user:
*** Flags for 1.2 ***
We only need one or two flags for 1.2
.
"lazy_to_front"
,"lazy_to_back"
These should physically reorder the transforms for execution. It can reshuffle the transforms in a way similar to RandomOrder.
@myron This should cover the benchmark performance that you need, yes?
@wyli This seems implementable in #6257 fairly simply. Given that RandomOrder works fine during inverse AFAIK, it means we also don't need to lose invert
Beta Was this translation helpful? Give feedback.
All reactions