Conversation
AlignedMTLWeighting computed the eigenvalue cutoff used to find the rank of the Gramian with torch.finfo().eps, i.e. the machine epsilon of the default dtype (float32 in most setups), whatever the dtype of the Gramian. With a float64 input, eigenvalues smaller than about m * 1.2e-7 times the largest one were treated as zero, so a task whose gradient is a few thousand times smaller than the others was dropped from the balance transformation entirely. Use torch.finfo(M.dtype).eps instead.
PierreQuinton
left a comment
There was a problem hiding this comment.
Good catch, LGTM. Just to be sure /opencode:Plan Can you check if this is applicable anywhere else in the aggregation package?
|
/opencode:Plan please review this. Has the bug always been there or has it been introduced in a non-initial commit? If so, which one? |
ValerianRey
left a comment
There was a problem hiding this comment.
Good catch thanks! Small comment and we can merge
| def test_float64_small_eigenvalue_is_kept() -> None: | ||
| J = tensor_([[1.0, 0.0], [0.0, 1e-4]], dtype=torch.float64) | ||
| result = AlignedMTL()(J) | ||
| assert_close(result, tensor_([5e-5, 5e-5], dtype=torch.float64)) |
There was a problem hiding this comment.
I think we should make this test agnostic of the dtype (i.e. rename it test_smal_eigenvalue_is_kept, and not use dtype=torch.float64). One of our CI runs uses dtype float64, so it will be tested on both float32 and float64
|
Verified both key facts directly against GitHub. Here's the answer: The bug has always been there — since the very first commitThe
The file (then tol = torch.max(lambda_) * len(M) * torch.finfo().epsI confirmed this by fetching the file content at that commit — the line is byte-identical to what Additional context
So: no non-initial commit to blame — the fix in this PR is the first time this tolerance computation has ever been touched. |

Problem
AlignedMTLWeighting._compute_balance_transformationdecides which eigenvalues of the Gramian count as non-zero with:torch.finfo()without an argument gives the epsilon of the default dtype (float32in most setups), not of the Gramian. So with afloat64input, every eigenvalue below aboutm * 1.2e-7times the largest one is treated as zero, even though it is perfectly representable. For tasks whose gradient norms differ by more than a factor of roughly 2000 (form = 2), the smaller task gets dropped from the balance transformation, and the scale used byscale_mode="min"also changes. That is exactly the situation AlignedMTL is meant to handle.Repro (CPU, default dtype
float32):On
main:The last line should be
[5e-05, 5e-05]. Instead, the second task is ignored completely and the result is half the first gradient.Solution
Use the epsilon of the Gramian's dtype:
For
float32inputs with the default dtype left atfloat32, the tolerance stays exactly the same. With this change the repro prints[5e-05, 5e-05]for the last case.Tests
test_float64_small_eigenvalue_is_keptintests/unit/aggregation/test_aligned_mtl.py. It aggregates thefloat64matrix[[1, 0], [0, 1e-4]]and checks that the result is[5e-5, 5e-5]. It fails onmain(it returns[0.5, 0]) and passes with the fix. The matrix is built infloat64on purpose, so the test checks the same thing underfloat32and underPYTEST_TORCH_DTYPE=float64.pytest tests/unit/aggregation -W error: 1507 passed, 15 skipped, for bothfloat32andPYTEST_TORCH_DTYPE=float64(CPU only, without the optional cvxpy/qpsolvers extras).ruff check,ruff format --checkandty checkpass on the changed files.Added an entry under
[Unreleased] / Fixedin the changelog.