Skip to content

Conversation

@shrit
Copy link
Member

@shrit shrit commented Dec 20, 2023

In reality, we do not need to have an overload per matrix type, in fact, these two functions can be merged in one function. that is independent from the arma

@shrit shrit requested review from rcurtin and zoq December 20, 2023 22:47
y = arma::max(y, x);
y.set_size(size(x));
y.zeros();
y = max(y, x);
Copy link
Contributor

@conradsnicta conradsnicta Dec 21, 2023

Choose a reason for hiding this comment

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

Suggest to change y = max(y, x) to y = arma::max(y, x).

C++ is notorious for problems involved with Argument Dependent Lookup (ADL). For example, the compiler may viably think that max(y,x) is supposed to mean std::max(y,x), and then spit out an error, complaining that std::max() doesn't know what to do with the arguments.

Placing the arma:: namespace qualification ensures that only max() from Armadillo is meant here.

The rules for ADL are quite byzantine and easily abused: https://en.cppreference.com/w/cpp/language/adl

Copy link
Member Author

Choose a reason for hiding this comment

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

I intentionally removed the arma because I do not see another way of using this function with coot.
Do you have a solution for this problem?

Copy link
Member

Choose a reason for hiding this comment

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

The longer-term aim here is to be able to use Armadillo or Bandicoot with the same mlpack code. After all, the linear algebraic operations themselves live at a higher level of abstraction than the details of how the matrices are stored (for the most part). That has been our goal for several years, and ADL is the only reasonable way to accomplish this. Although there are some warts with ADL, and it is complicated and routinely criticized, it is hard to see how it can go wrong for something like this.

If you have max(y, x) where y and x are objects from the arma:: namespace, then arma:: is added to the list of namespaces that are searched for the best match for max(), and everything works fine. If it does not work fine, and the user has not done anything "malicious" like adding other matching overloads that can cause the compiler to hiccup (which they shouldn't be doing anyway!), then the issue is a compiler bug.

Copy link
Contributor

@conradsnicta conradsnicta Dec 21, 2023

Choose a reason for hiding this comment

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

I suggest to provide an overloaded function specialised for Bandicoot matrices, possibly within an #ifdef MLPACK_USE_BANDICOOT guard:

static void Fn(const coot::Mat<eT>& x, coot::Mat<eT>& y)

The function would contain a call to coot::max(). It's perfectly fine to have both Armadillo and Bandicoot implementations of Fn() available at the same time.

In my experience it's less painful to not rely on ADL. Here's an example of ADL weirdness from ~5 years ago: https://gitlab.com/conradsnicta/armadillo-code/-/issues/94

Within Armadillo, the max() function is implemented using template restrictors, which may have unintended interactions with ADL voodoo:
https://gitlab.com/conradsnicta/armadillo-code/-/blob/12.6.x/include/armadillo_bits/fn_max.hpp?ref_type=heads#L73-92

Copy link
Member

Choose a reason for hiding this comment

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

Yes, but what I am getting at is that without ADL, every single linear algebra expression inside mlpack will need to have duplicate Bandicoot and Armadillo implementations. That's a massive amount of extra code to maintain and keep up to date---it's not realistic.

If we have to make exceptions explicitly for min() and max() and size() (due to the naming conflicts with std:: functionality), that's fine, but the way to do that would be to have an mlpack-local min() and max() implementation that redirect to the correct Armadillo/Bandicoot implementations instead of using ADL. But for the rest of the functionality in those libraries (e.g., say, repmat() or repelem() or eig_sym() or whatever), ADL should suffice.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I also suspect that the main problem is interference from functions defined in the std namespace. Besides the usual suspects (min(), max(), size()) there are also other std functions that have same name as arma functions: clamp(), abs(), arg(), etc.

@conradsnicta
Copy link
Contributor

Overall, I'd suggest to keep the original version of the code (ie. separate functions for Mat and Cube), as it's clear in its intent.

Anytime template arguments are more general (either by adding template args or replacing existing ones), it takes longer for compiler to try out various possible instantiations of the function. This increases compilation time and memory use.

Having two separate functions is less work for the compiler in this case.

@rcurtin
Copy link
Member

rcurtin commented Dec 21, 2023

I don't mind replacing the implementation as it will also work with Bandicoot; however, we do need to figure out the Windows build issue first. I am wondering if the build failure is a result of MSVC defining min and max as macros (which shouldn't be happening, I think, but it could). @shrit could you try building with the code (max)(y, x) to prevent a macro expansion? Let's see what that does, and if it works, then we at least know what is happening and can figure out how to disable those macros in MSVC.

@conradsnicta
Copy link
Contributor

... try building with the code (max)(y, x) to prevent a macro expansion? Let's see what that does, and if it works, then we at least know what is happening and can figure out how to disable those macros in MSVC.

Armadillo undefines any min and max macros, which suggests that this might be an ADL related problem.

Alternatively, it's possible that the un-definitions of min and max macros happen "too late" (eg. relevant system and mlpack headers are included before the main armadillo header). In that case, it may be useful to unconditionally undefine min and max early on in the mlpack header chain.

@rcurtin
Copy link
Member

rcurtin commented Dec 23, 2023

... try building with the code (max)(y, x) to prevent a macro expansion? Let's see what that does, and if it works, then we at least know what is happening and can figure out how to disable those macros in MSVC.

Armadillo undefines any min and max macros, which suggests that this might be an ADL related problem.

Yeah, unfortunately, I think you are right; even with a #undef max in the relevant file (f5a33d3), the failure is still the same. So some kind of "safe" version of functions that have names that also exist in the STL is probably necessary, unfortunately.

Based on my read of the ADL rules, the MSVC compiler is in the wrong here, but I don't think I am going to go through the effort to actually report that. Their team has actually been responsive to Armadillo and mlpack related bugs in the past (at one point, not sure if it is still true, mlpack itself was a part of their compiler test suite).

Copy link
Member

@rcurtin rcurtin left a comment

Choose a reason for hiding this comment

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

Nice, it works! I see this uses the MLPACK_HAS_COOT macro which I don't think is defined anywhere, but I am sure we will add configuration for it soon enough. Anyway, just some small little comments before merge. 👍

@shrit
Copy link
Member Author

shrit commented Jan 4, 2024

@rcurtin very good will apply these so we can merge it tomorrow,
Yes indeed, MLPACK_HAS_COOT, will be coming in the next PRs, it is only here to allow the compilation process

@shrit shrit merged commit 6397a19 into mlpack:master Jan 5, 2024
@shrit shrit deleted the rectifier branch January 5, 2024 18:51
This was referenced May 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants