Skip to content

Conditional variants#24858

Merged
alalazo merged 16 commits intodevelopfrom
features/conditional-variants
Nov 3, 2021
Merged

Conditional variants#24858
alalazo merged 16 commits intodevelopfrom
features/conditional-variants

Conversation

@becker33
Copy link
Copy Markdown
Member

@becker33 becker33 commented Jul 13, 2021

A common question from users has been how to model variants that are new in new versions of a package, or variants that are dependent on other variants. Our stock answer so far has been an unsatisfying combination of "just have it do nothing in the old versoin" and "tell Spack it conflicts".

This PR enables conditional variants, on any spec condition. The syntax is straightforward, and matches that of previous features.

variant('version_based', default=False, when='@2.0:', description="Variant that is only available in versions 2.0 and later")
variant('variant_based', default=False, when='+version_based', description="Variant that depends on another variant")

PR includes tests.

Closes #9740
This is a prereq to addressing #14337

@adamjstewart
Copy link
Copy Markdown
Member

Can't wait to test this on packages like boost and PyTorch! With the old concretizer, I have to disable half of the variants in PyTorch because they don't exist.

@becker33 becker33 force-pushed the features/conditional-variants branch 4 times, most recently from aeecaf2 to 1724798 Compare July 13, 2021 22:26
@tgamblin
Copy link
Copy Markdown
Member

tgamblin commented Jul 14, 2021

@becker33: does this work well at all with the old concretizer? I love this feature but had been thinking it would really suck without a proper solver. I guess we don't have to worry too much longer, as 0.17 will make the new one default.

@becker33
Copy link
Copy Markdown
Member Author

@tgamblin it seems to work reasonably well with the old concretizer -- it's at least passing all the tests there. I do think it increases the capability to express relationships that the old concretizer can't solve, but it won't break existing packages/variants until someone updates to use it, and at that point I think it's better to model reality well than conform to the old concretizer well, given that the new one will be default soon.

@becker33 becker33 force-pushed the features/conditional-variants branch 4 times, most recently from 87652d4 to 2ff0898 Compare July 14, 2021 21:56
@adamjstewart
Copy link
Copy Markdown
Member

Tested this with py-torch with the following modifications:

Click to expand!
diff --git a/var/spack/repos/builtin/packages/py-torch/package.py b/var/spack/repos/builtin/packages/py-torch/package.py
index 63c7cac7cc..dc002ae210 100644
--- a/var/spack/repos/builtin/packages/py-torch/package.py
+++ b/var/spack/repos/builtin/packages/py-torch/package.py
@@ -49,51 +49,36 @@ class PyTorch(PythonPackage, CudaPackage):
 
     # All options are defined in CMakeLists.txt.
     # Some are listed in setup.py, but not all.
-    variant('caffe2', default=True, description='Build Caffe2')
+    variant('caffe2', default=True, when='@1.7:', description='Build Caffe2')
     variant('cuda', default=not is_darwin, description='Use CUDA')
-    variant('rocm', default=False, description='Use ROCm')
-    variant('cudnn', default=not is_darwin, description='Use cuDNN')
-    variant('fbgemm', default=True, description='Use FBGEMM (quantized 8-bit server operators)')
-    variant('kineto', default=True, description='Use Kineto profiling library')
-    variant('magma', default=not is_darwin, description='Use MAGMA')
+    variant('rocm', default=False, when='@1.0:', description='Use ROCm')
+    variant('cudnn', default=not is_darwin, when='@1.0: +cuda', description='Use cuDNN')
+    variant('fbgemm', default=True, when='@1.0:', description='Use FBGEMM (quantized 8-bit server operators)')
+    variant('kineto', default=True, when='@1.8:', description='Use Kineto profiling library')
+    variant('magma', default=not is_darwin, when='+cuda', description='Use MAGMA')
     variant('metal', default=is_darwin, description='Use Metal for Caffe2 iOS build')
-    variant('nccl', default=not is_darwin, description='Use NCCL')
+    variant('nccl', default=not is_darwin, when='+cuda platform=linux', description='Use NCCL')
+    variant('nccl', default=not is_darwin, when='+cuda platform=cray', description='Use NCCL')
+    variant('nccl', default=not is_darwin, when='+rocm platform=linux', description='Use NCCL')
+    variant('nccl', default=not is_darwin, when='+rocm platform=cray', description='Use NCCL')
     variant('nnpack', default=True, description='Use NNPACK')
-    variant('numa', default=not is_darwin, description='Use NUMA')
+    variant('numa', default=not is_darwin, when='platform=linux', description='Use NUMA')
+    variant('numa', default=not is_darwin, when='platform=cray', description='Use NUMA')
     variant('numpy', default=True, description='Use NumPy')
     variant('openmp', default=True, description='Use OpenMP for parallel code')
-    variant('qnnpack', default=True, description='Use QNNPACK (quantized 8-bit operators)')
-    variant('valgrind', default=not is_darwin, description='Use Valgrind')
-    variant('xnnpack', default=True, description='Use XNNPACK')
-    variant('mkldnn', default=True, description='Use MKLDNN')
+    variant('qnnpack', default=True, when='@1.0:', description='Use QNNPACK (quantized 8-bit operators)')
+    variant('valgrind', default=not is_darwin, when='@1.8: platform=linux', description='Use Valgrind')
+    variant('valgrind', default=not is_darwin, when='@1.8: platform=cray', description='Use Valgrind')
+    variant('xnnpack', default=True, when='@1.5:', description='Use XNNPACK')
+    variant('mkldnn', default=True, when='@1.0:', description='Use MKLDNN')
     variant('distributed', default=not is_darwin, description='Use distributed')
-    variant('mpi', default=not is_darwin, description='Use MPI for Caffe2')
-    variant('gloo', default=not is_darwin, description='Use Gloo')
-    variant('tensorpipe', default=not is_darwin, description='Use TensorPipe')
-    variant('onnx_ml', default=True, description='Enable traditional ONNX ML API')
-
-    conflicts('+cuda', when='+rocm')
-    conflicts('+cudnn', when='~cuda')
-    conflicts('+magma', when='~cuda')
-    conflicts('+nccl', when='~cuda~rocm')
-    conflicts('+nccl', when='platform=darwin')
-    conflicts('+numa', when='platform=darwin', msg='Only available on Linux')
-    conflicts('+valgrind', when='platform=darwin', msg='Only available on Linux')
-    conflicts('+mpi', when='~distributed')
-    conflicts('+gloo', when='~distributed')
-    conflicts('+tensorpipe', when='~distributed')
-    conflicts('+kineto', when='@:1.7.999')
-    conflicts('+valgrind', when='@:1.7.999')
-    conflicts('~caffe2', when='@0.4.0:1.6.999')  # no way to disable caffe2?
-    conflicts('+caffe2', when='@:0.3.1')  # caffe2 did not yet exist?
-    conflicts('+tensorpipe', when='@:1.5.999')
-    conflicts('+xnnpack', when='@:1.4.999')
-    conflicts('~onnx_ml', when='@:1.4.999')  # no way to disable ONNX?
-    conflicts('+rocm', when='@:0.4.999')
-    conflicts('+cudnn', when='@:0.4.999')
-    conflicts('+fbgemm', when='@:0.4.999,1.4.0')
-    conflicts('+qnnpack', when='@:0.4.999')
-    conflicts('+mkldnn', when='@:0.4.999')
+    variant('mpi', default=not is_darwin, when='+mpi', description='Use MPI for Caffe2')
+    variant('gloo', default=not is_darwin, when='+mpi', description='Use Gloo')
+    variant('tensorpipe', default=not is_darwin, when='@1.6: +mpi', description='Use TensorPipe')
+    variant('onnx_ml', default=True, when='@1.5:', description='Enable traditional ONNX ML API')
+
+    conflicts('+cuda+rocm')
+    conflicts('+fbgemm', when='@1.4.0')
 
     conflicts('cuda_arch=none', when='+cuda',
               msg='Must specify CUDA compute capabilities of your GPU, see '

Here is what I observed:

  1. This works perfectly with both the original and clingo concretizers!
  2. With depends_on, I can simulate "or" by using multiple lines. However, the following doesn't seem to work:
     variant('nccl', default=not is_darwin, when='+cuda platform=linux', description='Use NCCL')
     variant('nccl', default=not is_darwin, when='+cuda platform=cray', description='Use NCCL')
     variant('nccl', default=not is_darwin, when='+rocm platform=linux', description='Use NCCL')
     variant('nccl', default=not is_darwin, when='+rocm platform=cray', description='Use NCCL')
  3. Another common feature request is variant defaults that change based on other variants. I don't think this is possible with the current implementation, right? I'm not sure what this would even look like...

Another idea that would be interesting. Right now there are a lot of packages with something like:

if self.spec.satisfies('@1.2:'):
    if '+foo' in self.spec:
        args.append('--enable-foo')
    else:
        args.append('--disable-foo')

GDAL is a good example of this. I'm wondering if the following would also work if the +foo variant only exists in 1.2+:

if '+foo' in self.spec:
    args.append('--enable-foo')
elif '~foo' in self.spec:
    args.append('--disable-foo')

@becker33
Copy link
Copy Markdown
Member Author

@adamjstewart thanks for checking this out!

This PR extends the when syntax to allow a list of conditions (for or). If you use that syntax, you can get or to work properly instead of using multiple lines.

variant('nccl', default=not is_darwin, when='+cuda platform=linux', description='Use NCCL')
variant('nccl', default=not is_darwin, when='+cuda platform=cray', description='Use NCCL')
variant('nccl', default=not is_darwin, when='+rocm platform=linux', description='Use NCCL')
variant('nccl', default=not is_darwin, when='+rocm platform=cray', description='Use NCCL')

could become

variant('nccl', default=not is_darwin, when=['+cuda platform=linux', '+cuda platform=cray', '+rocm platform=linux', '+rocm platform=cray'], description='Use NCCL')

The reason I did that is because separate defaults per-declaration cannot work without some really substantial work, and I don't want people to try

variant('foo', default=False, when='@1.1')
variant('foo', default=True, when='@1.2:')

However, as long as only the when-clause is changing, I can probably make that work in the interest of not confusing people who are used to the old syntax. (The new syntax does work for all the other commands that accept when clauses though, so it should catch on well enough for "or"). I'll take a look at making the duplicate variant declarations work for or.

@adamjstewart
Copy link
Copy Markdown
Member

This PR extends the when syntax to allow a list of conditions

Does this work for all directives, not just variant? That's really cool!

@becker33
Copy link
Copy Markdown
Member Author

Yep, it works for all directives! I don't have tests in for all of them, but I have tests in for depends_on and all of them except for variant use the same code to handle it.

Also, multiply defined variants work now as long as you don't try to change the defaults/values/validators.

@becker33 becker33 force-pushed the features/conditional-variants branch 3 times, most recently from e40c5e7 to 421f3dc Compare July 16, 2021 21:39
Copy link
Copy Markdown
Member

@alalazo alalazo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally this looks great. I tried modifying cp2k and I can make all the CUDA related variants disappear if ~cuda is in the spec. This should give a sensible improvement in readability for complex specs!

The only major thing: I have been a bit puzzled by the extension of the when= argument to accept a list of specs. My first impression is that it is not obvious that this would be an OR and it is effectively syntactic sugar for having two directives with different when= arguments. It also deviates a bit from the declarative nature of directives. I guess I have two questions:

  • Is this feature technically necessary to get conditional variants?
  • Should we at least check what would be user reactions to it (mine and @adamjstewart seem to be opposite)?

Also, this slight modification:

diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
index 45d9fd52d3..9b21e99bb3 100644
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -252,7 +252,7 @@ def _wrapper(*args, **kwargs):
                 if DirectiveMeta._when_constraints_from_context:
                     # Check that directives not yet supporting the when= argument
                     # are not used inside the context manager
-                    if decorated_function.__name__ in ('version', 'variant'):
+                    if decorated_function.__name__ in ('version',):
                         msg = ('directive "{0}" cannot be used within a "when"'
                                ' context since it does not support a "when=" '
                                'argument')

is needed to make this feature work with the when context manager.

Comment on lines +443 to +525
variant_desc, _ = self.variants[name]
if set(variant_desc.values) == set((True, False)):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am reading this correctly here we are treating the absence of a variant as if it was set to False and so we'll emit e.g. a --disable-foo if self.enable_or_disabled('foo') is called. Just wanted to double check this is the semantics we want, rather than just disregarding it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it would throw an exception on line 443, because it unconditionally gets the variant from a dictionary.

max(cols, 70) - max_name - max_vals - 14,
)
self.column_widths = (max_name, max_vals, max_description)
self.column_widths = (max_name, max_when, max_vals, max_description)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a minor point, but we should probably revisit formatting. Right now I have this on my screen:
Screenshot from 2021-07-27 16-38-19

which is ok but may be improved in terms of:

  1. Wrapping the Description line
  2. Joining the when clauses for variants and avoid [] when it is always present

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to not display the when column at all if it isn't used by a package.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output looks better to me, thanks! There are still a few super-minor issues - for instance this is from spack info gromacs:
Screenshot from 2021-11-02 19-51-46

I think though that we shouldn't hold this PR on that and we may fix minor issues like this in v0.17.1

_patch_order_index = 0


def make_when_specs(value):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name is very similar to make_when_spec so maybe we should change it. Also it would probably be good to prepend both names with an underscore to make it clear they are used only in this module.

@becker33 becker33 force-pushed the features/conditional-variants branch from e04e94b to a589faa Compare November 2, 2021 18:00
@spackbot-app spackbot-app bot added the documentation Improvements or additions to documentation label Nov 2, 2021
@alalazo
Copy link
Copy Markdown
Member

alalazo commented Nov 3, 2021

The failure on Fedora is happening on many PRs. It is unrelated to changes and possibly due to synchronization issues with upstream when new Fedora releases are pushed.

@alalazo alalazo merged commit 67cd92e into develop Nov 3, 2021
@alalazo alalazo deleted the features/conditional-variants branch November 3, 2021 07:11
alalazo added a commit to alalazo/spack that referenced this pull request Nov 30, 2021
alalazo added a commit that referenced this pull request Nov 30, 2021
haampie pushed a commit that referenced this pull request Nov 30, 2021
alalazo added a commit that referenced this pull request Dec 23, 2021
@adamjstewart
Copy link
Copy Markdown
Member

Did we ever add support for when=[...]? It seems like that feature was removed from this PR.

capitalaslash pushed a commit to capitalaslash/spack that referenced this pull request Aug 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Variants might be conditional on the version of the package

5 participants