# LICENSE HEADER MANAGED BY add-license-header # # Copyright 2018 Kornia Team # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import pytest import torch import torch.nn.functional as F # Reference implementations used in the test suite: def batched_dot_product(x: torch.Tensor, y: torch.Tensor, keepdim: bool = False) -> torch.Tensor: return (x * y).sum(-1, keepdim) def normalized_orig(v: torch.Tensor, eps: float = 1e-6) -> torch.Tensor: return v / batched_dot_product(v, v, keepdim=True).add(eps).sqrt() def normalized_fnorm(v: torch.Tensor, eps: float = 1e-6) -> torch.Tensor: return F.normalize(v, p=2, dim=-1, eps=eps) # -- Reference normalization using explicit loops (earlier implementation) -- def normalized_ref(v: torch.Tensor, eps: float = 1e-6) -> torch.Tensor: # clone input and iterate over flattened vectors to preserve batch dims out = v.clone() flat = out.view(-1, v.size(-1)) for i, vec in enumerate(flat): norm = torch.sqrt((vec * vec).sum() + eps) flat[i] = vec / norm return out @pytest.fixture(params=[(2, 3), (4, 5, 6), (10, 128)]) def random_batch(request): shape = request.param torch.manual_seed(0) return torch.randn(*shape, dtype=torch.float32) def test_batched_dot_product_shape(random_batch): x = random_batch y = random_batch.clone() out1 = batched_dot_product(x, y) out2 = batched_dot_product(x, y, keepdim=True) assert out1.shape == x.shape[:-1] assert out2.shape == x.shape[:-1] + (1,) @pytest.mark.parametrize("fn", [normalized_orig, normalized_fnorm]) def test_unit_norm(random_batch, fn): v = random_batch out = fn(v) assert out.shape == v.shape norms = torch.linalg.norm(out.flatten(0, -2), dim=-1) assert torch.allclose(norms, torch.ones_like(norms), atol=1e-6) @pytest.mark.parametrize("fn", [normalized_orig, normalized_fnorm]) def test_normalized_against_ref(fn, random_batch): v = random_batch out = fn(v) ref = normalized_ref(v) assert torch.allclose(out, ref, atol=1e-6), ( f"{fn.__name__} differs from reference by {(out - ref).abs().max().item():.3e}" ) @pytest.mark.parametrize("fn", [normalized_fnorm]) def test_optimized_matches_orig(fn, random_batch): v = random_batch out_opt = fn(v) out_orig = normalized_orig(v) assert torch.allclose(out_opt, out_orig, atol=1e-6), ( f"{fn.__name__} differs from normalized_orig by {(out_opt - out_orig).abs().max().item():.3e}" ) def test_orig_matches_fnormalize(random_batch): v = random_batch out_orig = normalized_orig(v) out_fnorm = F.normalize(v, p=2, dim=-1, eps=1e-6) assert torch.allclose(out_orig, out_fnorm, atol=1e-6), ( f"normalized_orig differs from F.normalize by {(out_orig - out_fnorm).abs().max().item():.3e}" )