Jax backend#757
Conversation
janden
left a comment
There was a problem hiding this comment.
Great work! Some things do discuss here. Mainly clarifying the triple inheritance stuff which scare me a bit.
| from .numpy_frontend import ScatteringNumPy1D | ||
| from .base_frontend import ScatteringBase1D | ||
|
|
||
| class ScatteringJax1D(ScatteringJax, ScatteringNumPy1D, ScatteringBase1D): |
There was a problem hiding this comment.
Again, slightly terrifying and definitely deserving of a comment if not a rewrite.
There was a problem hiding this comment.
Explanation:
- Multiple inheritance generally looks for methods/attributes in the order of the classes specified above. Hence
ScatteringJaxgoes first in order to have priority adding its attributes in. - Apart from the inheritance of
ScatteringNumpPy1Dthis class and its__init__look exactly like the one inScatteringNumPy1Ditself. - I had to include
ScatteringBase1Dbecause I needed to overwrite the__init__, basically copying and pasting the__init__fromScatteringNumPy1Dbut with the backend changed. I had to do that because otherwise it was doing something numpy-specific from theScatteringNumPy1D.__init__. In order to do that overwriting I needed access toScatteringBase1D(from whichScatteringNumPy1Dalso inherits and which it uses in its__init__) - I initially tried switching positions 2 and 3 and I got a multiple inheritance error (because
ScatteringNumPy1Dderives fromScatteringBase1D) and so not all permutations are actually allowed.
Will add some comments
There was a problem hiding this comment.
added some comments
There was a problem hiding this comment.
For 3. I don't know that you need to include ScatteringBase1D in the inheritance list just to call its constructor. It is a super-super-class of the current one, so there's no issue calling its constructor like you're doing. I tried removing this locally and tests pass.
There was a problem hiding this comment.
ScatteringBase1D is already inherited via ScatteringNumpy1D. Its redundant here.
| assert np.allclose(jax_result, numpy_result) | ||
|
|
||
|
|
||
| def test_jnp_multiplication(): |
There was a problem hiding this comment.
How is this different from the previous test?
There was a problem hiding this comment.
don't know! I copied and pasted from the previous PR :D Will check
| @@ -0,0 +1,36 @@ | |||
| import pytest | |||
There was a problem hiding this comment.
Rename test_jax_scattering1d.py per convention.
| T = x.shape[-1] | ||
| scattering = Scattering1D(J, T, Q, backend=backend, frontend='jax') | ||
|
|
||
| Sx = scattering(x) |
There was a problem hiding this comment.
Do we know that Jax is actually being used here (as opposed to NP)? There should be a way to find out using the mock package, I think. Conversely, it would be good to make sure that NP is not being used (except for filter generation) so that a call doesn't slip through by accident.
There was a problem hiding this comment.
Here is another pictorial proof.
You mean build a fake numpy or something that errors on all/most methods? With https://docs.python.org/dev/library/unittest.mock.html ?
There was a problem hiding this comment.
You mean build a fake numpy or something that errors on all/most methods?
Yeah or is that overkill, do you think? I don't know that you need to replace numpy (you could do that with monkeypatch, I think). You can just do things like .called on various methods. Maybe NP will crash already if you give it Jax arrays.
eickenberg
left a comment
There was a problem hiding this comment.
Thanks for the review!
I left some comments about your questions.
Going to address them code-side now
| from .numpy_frontend import ScatteringNumPy1D | ||
| from .base_frontend import ScatteringBase1D | ||
|
|
||
| class ScatteringJax1D(ScatteringJax, ScatteringNumPy1D, ScatteringBase1D): |
There was a problem hiding this comment.
Explanation:
- Multiple inheritance generally looks for methods/attributes in the order of the classes specified above. Hence
ScatteringJaxgoes first in order to have priority adding its attributes in. - Apart from the inheritance of
ScatteringNumpPy1Dthis class and its__init__look exactly like the one inScatteringNumPy1Ditself. - I had to include
ScatteringBase1Dbecause I needed to overwrite the__init__, basically copying and pasting the__init__fromScatteringNumPy1Dbut with the backend changed. I had to do that because otherwise it was doing something numpy-specific from theScatteringNumPy1D.__init__. In order to do that overwriting I needed access toScatteringBase1D(from whichScatteringNumPy1Dalso inherits and which it uses in its__init__) - I initially tried switching positions 2 and 3 and I got a multiple inheritance error (because
ScatteringNumPy1Dderives fromScatteringBase1D) and so not all permutations are actually allowed.
Will add some comments
| assert np.allclose(jax_result, numpy_result) | ||
|
|
||
|
|
||
| def test_jnp_multiplication(): |
There was a problem hiding this comment.
don't know! I copied and pasted from the previous PR :D Will check
| @@ -0,0 +1,36 @@ | |||
| import pytest | |||
| T = x.shape[-1] | ||
| scattering = Scattering1D(J, T, Q, backend=backend, frontend='jax') | ||
|
|
||
| Sx = scattering(x) |
There was a problem hiding this comment.
Here is another pictorial proof.
You mean build a fake numpy or something that errors on all/most methods? With https://docs.python.org/dev/library/unittest.mock.html ?
| test_data_dir = os.path.dirname(__file__) | ||
|
|
||
| with open(os.path.join(test_data_dir, 'test_data_1d.npz'), 'rb') as f: | ||
| buffer = io.BytesIO(f.read()) |
There was a problem hiding this comment.
huh, why were we doing this? Instead of np.load?
|
Added 2D jax backend with passing tests. This required moving some of the numpy input checks from the scattering function to the numpy backend |
|
Added 3D backend. Another quick review and discussion of the inheritance situation would be great! |
|
Ok I'll review 2D and 3D once we've converged on 1D. |
|
@janden could we touch base on this one soon-ish and try to converge on a design decision? |
|
Whats the current state of this PR @janden @eickenberg ? I can jump in and help. Sorry I never finished this during my internship! |
|
I don't have the relevant background in JAX but i heartily support this: JAX looks really easy to use as a drop-in replacement for NumPy ! |
MuawizChaudhary
left a comment
There was a problem hiding this comment.
tests error out on the numpy 3d. Why did the input checks have to be changed for numpy again?
| from .numpy_frontend import ScatteringNumPy1D | ||
| from .base_frontend import ScatteringBase1D | ||
|
|
||
| class ScatteringJax1D(ScatteringJax, ScatteringNumPy1D, ScatteringBase1D): |
There was a problem hiding this comment.
ScatteringBase1D is already inherited via ScatteringNumpy1D. Its redundant here.
| self.backend.input_checks(x) | ||
|
|
There was a problem hiding this comment.
i'll look into it later
MuawizChaudhary
left a comment
There was a problem hiding this comment.
I can fix some of this up for y'all if thats fine with yall.
|
@janden I fixed up various things in the jax front/backends. So this PR is ready for your review. Unfortunately, it appears that Jenkins does not have Jax installed. So that needs fixing. |
MuawizChaudhary
left a comment
There was a problem hiding this comment.
I think we could merge this in at any time. Unsure if we wanna do it soon tho, ot later closer to release.
shall we remove py35 from Pip CI ? |
I vote yes, can you make an issue. |
|
I have attempted to update the GitHub worfklow in a recent PR (#892 ) |
|
I agree. We'll rebase this once #892 is merged, then get back to reviewing it. |
|
@janden i think you have the ability to reset tests for a pr? |
Yes but it won't make a difference until we've rebased. |
|
Alright, i'm rebasing. Everyone stand back |
|
Jenkins is failing ... Torch 2D ? tests/scattering2d/test_torch_scattering2d.py ...............sSending interrupt signal to process
Terminated
script returned exit code 143 |
My guess is that rebuilding the docker image took too much time and there wasn't enough time to run the tests. I could be wrong though. Re-running to check. |
|
Something happened with the rebase. There are lots of old commits that shouldn't be here. Do you have access to the original tree? |
|
Ok looks like I was able to restore it (see #898). Is it ok if I force-push that branch on here so that we can continue the review in the same place? |
Ping @lostanlen |
|
@janden yes, it's all good to me. apologies for the mishap in rebase |
janden
left a comment
There was a problem hiding this comment.
Looks good. Mostly comments and questions about testing here, which I think can be streamlined. Since the implementation so closely mirrors the NP frontend, I think we can get away with a simple test against sample data for each dimension and be done (perhaps checking on host and on device to be safe). This will ensure that Jax is doing the correct thing. All the other tests basically reproduce the tests we have for the NP frontend.
| buffer = io.BytesIO(f.read()) | ||
| data = np.load(buffer) | ||
|
|
||
| x = data['x'] |
There was a problem hiding this comment.
Shouldn't we be casting to JNP arrays or doing device_put or something here (like in 1D)?
There was a problem hiding this comment.
we should also be parameterizing Jax with cpu and gpu device in that instance then...
Codecov Report
@@ Coverage Diff @@
## dev #757 +/- ##
==========================================
+ Coverage 87.86% 88.07% +0.20%
==========================================
Files 64 73 +9
Lines 2250 2297 +47
==========================================
+ Hits 1977 2023 +46
- Misses 273 274 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
|
@MuawizChaudhary we're almost there. would you like to help us with simplifying the tests here? |
|
yeah, I'm at CVPR rn but I'll get to it tonight |
686d04b to
ad4dc25
Compare
|
... i fucked up |
|
Cherry-picked your commits onto the cleaned-up branch. Just remember to rebase your branch before you commit and push next time. |
eickenberg
left a comment
There was a problem hiding this comment.
Looks great to me.
I had some questions wherever numpy files were modified, wondering whether that was from some previous PR or whether it pertained to making the structure jax compatible. I don't think any changes need to be made, but I'd like to understand the instances where the numpy files are modified in terms of this question, so comments appreciated.
I would also add an "Approve" to this PR, but it's grayed out for me, because I am the original author of this PR 😹
| def __call__(self, x): | ||
| """This method is an alias for `scattering`.""" | ||
|
|
||
| self.backend.input_checks(x) |
There was a problem hiding this comment.
Is this also from another PR or added during this one?
| ScatteringBase2D.create_filters(self) | ||
|
|
||
| def scattering(self, input): | ||
| if not type(input) is np.ndarray: |
There was a problem hiding this comment.
again the question whether this numpy file modification is an integral part of this PR
| integrals = [] | ||
| for i_q, q in enumerate(integral_powers): | ||
| integrals[:, i_q] = (input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1) | ||
| integrals.append((input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1)) |
There was a problem hiding this comment.
is this modification to accommodate jax, because jax doesnt allow setting subarrays?
this will likely double the memory usage - worth considering whether to override this only in jax and keep numpy as it was?
There was a problem hiding this comment.
yes, we have to do some .set and one additional operation.
| for i_q, q in enumerate(integral_powers): | ||
| integrals[:, i_q] = (input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1) | ||
| integrals.append((input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1)) | ||
| integrals = cls._np.float32(cls._np.stack(integrals, axis=-1)) |
There was a problem hiding this comment.
why is the cast to float32 necessary? isn't the input also float32?
There was a problem hiding this comment.
it could be float64, so tests fail in that instance when we expect float32. Also, previously in this code integrals was defined as a float32 array.
| def scattering(self, input_array): | ||
| if not type(input_array) is np.ndarray: | ||
| raise TypeError('The input should be a NumPy array.') | ||
| self.backend.input_checks(input_array) |
There was a problem hiding this comment.
this is to let jax arrays through as well?
I like the change, just need to understand the motivation. I would have assumed that since in the jax case, np = jax.numpy, it should all be transparent
There was a problem hiding this comment.
Yes. I would have assumed so too. But I think np.array should be valid. perhaps we should add a special check for numpy or jax in the jax backend, overriding input_checks?
| def __call__(self, x): | ||
| """This method is an alias for `scattering`.""" | ||
|
|
||
| self.backend.input_checks(x) |
| ScatteringBase2D.create_filters(self) | ||
|
|
||
| def scattering(self, input): | ||
| if not type(input) is np.ndarray: |
| def input_checks(x): | ||
| if x is None: | ||
| raise TypeError('The input should be not empty.') |
There was a problem hiding this comment.
perhaps we should check that its a jax array?
| for i_q, q in enumerate(integral_powers): | ||
| integrals[:, i_q] = (input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1) | ||
| integrals.append((input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1)) | ||
| integrals = cls._np.float32(cls._np.stack(integrals, axis=-1)) |
There was a problem hiding this comment.
it could be float64, so tests fail in that instance when we expect float32. Also, previously in this code integrals was defined as a float32 array.
| integrals = [] | ||
| for i_q, q in enumerate(integral_powers): | ||
| integrals[:, i_q] = (input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1) | ||
| integrals.append((input_array ** q).reshape((input_array.shape[0], -1)).sum(axis=1)) |
There was a problem hiding this comment.
yes, we have to do some .set and one additional operation.
| def scattering(self, input_array): | ||
| if not type(input_array) is np.ndarray: | ||
| raise TypeError('The input should be a NumPy array.') | ||
| self.backend.input_checks(input_array) |
There was a problem hiding this comment.
Yes. I would have assumed so too. But I think np.array should be valid. perhaps we should add a special check for numpy or jax in the jax backend, overriding input_checks?
* ENH jax base backend and frontend * ENH jax backend and frontend for 1D * TST general jax.numpy - numpy correspondence tests * TST test for jax 1d backend * WIP address Joakim's comments * WIP jax 2d * WIP typo in jax 2d * ENH add 2D jax backend * ENH jax backend 3d * ENH jax entry point * MAINT fix 3d numpy frontend test * MAINT remove uneeded inheritances * STY change script names to be more inline with current script naming convention * TST edited workflows to account for jax and jaxlib * TST adding jax to jenkins * MAINT fix jaxbackend not complying with numpy backend after mergeal * MAINT cast to float32 * update JAX tests (tuple Q) * TST Fix seed for 2D FFT test (Jax) * MAINT removed lonely jax import * TST just have one test * TST removed unneeded test * MAINT make sure that we check for numpy ndarrays in addition to numpy-like ndarrays Co-authored-by: Muawiz Chaudhary <muawizc@gmail.com> Co-authored-by: Vincent Lostanlen <vincent.lostanlen@ls2n.fr> Co-authored-by: Joakim Andén <janden@kth.se>
* ENH jax base backend and frontend * ENH jax backend and frontend for 1D * TST general jax.numpy - numpy correspondence tests * TST test for jax 1d backend * WIP address Joakim's comments * WIP jax 2d * WIP typo in jax 2d * ENH add 2D jax backend * ENH jax backend 3d * ENH jax entry point * MAINT fix 3d numpy frontend test * MAINT remove uneeded inheritances * STY change script names to be more inline with current script naming convention * TST edited workflows to account for jax and jaxlib * TST adding jax to jenkins * MAINT fix jaxbackend not complying with numpy backend after mergeal * MAINT cast to float32 * update JAX tests (tuple Q) * TST Fix seed for 2D FFT test (Jax) * MAINT removed lonely jax import * TST just have one test * TST removed unneeded test * MAINT make sure that we check for numpy ndarrays in addition to numpy-like ndarrays Co-authored-by: Muawiz Chaudhary <muawizc@gmail.com> Co-authored-by: Vincent Lostanlen <vincent.lostanlen@ls2n.fr> Co-authored-by: Joakim Andén <janden@kth.se>

Here is the jax backend for 1D. Tests pass.
I will add 2D and 3D here in the next days.
Please start reviewing this when you get a chance.