Spaces:
Paused
Paused
Download gemma/diffusion/hackable_diffusion_adapter/hd/lora_test.py from Jit89/bmap-gemma-engine: direct link, hf CLI and curl.
- Browser
- Download file 21.5 kB
-
https://huggingface.co/spaces/Jit89/bmap-gemma-engine/resolve/main/gemma/diffusion/hackable_diffusion_adapter/hd/lora_test.py
- Command line
-
hf download hf://spaces/Jit89/bmap-gemma-engine/gemma/diffusion/hackable_diffusion_adapter/hd/lora_test.py
-
curl -L -o lora_test.py https://huggingface.co/spaces/Jit89/bmap-gemma-engine/resolve/main/gemma/diffusion/hackable_diffusion_adapter/hd/lora_test.py
21.5 kB
| # Copyright 2026 DeepMind Technologies Limited. | |
| # | |
| # 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. | |
| """Tests for lora.fuse_lora_params and target_modules.""" | |
| from absl.testing import absltest | |
| from absl.testing import parameterized | |
| from unittest import mock | |
| from flax import linen as nn | |
| from gemma.diffusion.hackable_diffusion_adapter.hd import lora | |
| import jax | |
| import jax.numpy as jnp | |
| import numpy as np | |
| # --------------------------------------------------------------------------- | |
| # Helper to build a minimal param tree with one LoRA adapter. | |
| # --------------------------------------------------------------------------- | |
| def _make_params( | |
| base_path: tuple[str, ...], | |
| base_shape: tuple[int, ...], | |
| lora_parent: tuple[str, ...], | |
| a_shape: tuple[int, ...], | |
| b_shape: tuple[int, ...], | |
| *, | |
| base_value: float = 1.0, | |
| a_value: float = 1.0, | |
| b_value: float = 1.0, | |
| ) -> dict: # pylint: disable=g-bare-generic | |
| """Build a nested param dict with one base weight and one LoRA adapter. | |
| Args: | |
| base_path: Tuple path for the base weight (e.g. ('attn', 'q_einsum', 'w')). | |
| base_shape: Shape of the base weight array. | |
| lora_parent: Tuple path to the LoRA parent (above 'lora/a', 'lora/b'). | |
| a_shape: Shape of the LoRA 'a' matrix. | |
| b_shape: Shape of the LoRA 'b' matrix. | |
| base_value: Fill value for the base weight. | |
| a_value: Fill value for the LoRA 'a' matrix. | |
| b_value: Fill value for the LoRA 'b' matrix. | |
| Returns: | |
| A nested dict representing the param tree. | |
| """ | |
| import flax.traverse_util # pylint: disable=g-import-not-at-top | |
| flat = {} | |
| flat[base_path] = np.full(base_shape, base_value, dtype=np.float32) | |
| flat[lora_parent + ('lora', 'a')] = np.full( | |
| a_shape, a_value, dtype=np.float32 | |
| ) | |
| flat[lora_parent + ('lora', 'b')] = np.full( | |
| b_shape, b_value, dtype=np.float32 | |
| ) | |
| return flax.traverse_util.unflatten_dict(flat) | |
| class FindBaseWeightKeyTest(parameterized.TestCase): | |
| """Tests for lora._find_base_weight_key.""" | |
| def test_lora_einsum_named_variant(self): | |
| """_LoRAEinsum_gating_einsum -> sibling 'gating_einsum'.""" | |
| original_flat = { | |
| ('mlp', 'gating_einsum'): np.zeros((2, 6144, 1536)), | |
| ('mlp', 'linear'): np.zeros((6144, 1536)), | |
| } | |
| result = lora._find_base_weight_key( | |
| ('mlp', '_LoRAEinsum_gating_einsum'), original_flat | |
| ) | |
| self.assertEqual(result, ('mlp', 'gating_einsum')) | |
| def test_lora_einsum_named_variant_linear(self): | |
| """_LoRAEinsum_linear -> sibling 'linear'.""" | |
| original_flat = { | |
| ('mlp', 'gating_einsum'): np.zeros((2, 6144, 1536)), | |
| ('mlp', 'linear'): np.zeros((6144, 1536)), | |
| } | |
| result = lora._find_base_weight_key( | |
| ('mlp', '_LoRAEinsum_linear'), original_flat | |
| ) | |
| self.assertEqual(result, ('mlp', 'linear')) | |
| def test_lora_einsum_0_default(self): | |
| """_LoRAEinsum_0 -> sibling 'w'.""" | |
| original_flat = { | |
| ('attn', 'q_einsum', 'w'): np.zeros((8, 1536, 256)), | |
| } | |
| result = lora._find_base_weight_key( | |
| ('attn', 'q_einsum', '_LoRAEinsum_0'), original_flat | |
| ) | |
| self.assertEqual(result, ('attn', 'q_einsum', 'w')) | |
| def test_shared_scope_kernel(self): | |
| """Shared-scope LoRADense: lora_parent + 'kernel'.""" | |
| original_flat = { | |
| ('dense', 'kernel'): np.zeros((128, 64)), | |
| ('dense', 'bias'): np.zeros((64,)), | |
| } | |
| result = lora._find_base_weight_key(('dense',), original_flat) | |
| self.assertEqual(result, ('dense', 'kernel')) | |
| def test_shared_scope_w(self): | |
| """Shared-scope LoRAEinsum: lora_parent + 'w'.""" | |
| original_flat = { | |
| ('einsum', 'w'): np.zeros((8, 256)), | |
| } | |
| result = lora._find_base_weight_key(('einsum',), original_flat) | |
| self.assertEqual(result, ('einsum', 'w')) | |
| def test_bare_array_fallback(self): | |
| """lora_parent itself is the base weight (bare array, no sub-key).""" | |
| original_flat = { | |
| ('layer', 'weight'): np.zeros((10, 20)), | |
| } | |
| result = lora._find_base_weight_key(('layer', 'weight'), original_flat) | |
| self.assertEqual(result, ('layer', 'weight')) | |
| def test_not_found(self): | |
| """Returns None when no matching base weight exists.""" | |
| original_flat = { | |
| ('other', 'param'): np.zeros((10,)), | |
| } | |
| result = lora._find_base_weight_key( | |
| ('layer', '_LoRAEinsum_0'), original_flat | |
| ) | |
| self.assertIsNone(result) | |
| def test_lora_einsum_0_prefers_w_over_kernel(self): | |
| """_LoRAEinsum_0 should find 'w' first (case 1 before case 2).""" | |
| original_flat = { | |
| ('attn', 'w'): np.zeros((8,)), | |
| ('attn', '_LoRAEinsum_0', 'kernel'): np.zeros((8,)), | |
| } | |
| result = lora._find_base_weight_key( | |
| ('attn', '_LoRAEinsum_0'), original_flat | |
| ) | |
| self.assertEqual(result, ('attn', 'w')) | |
| class FuseLoraParamsTest(parameterized.TestCase): | |
| """Tests for lora.fuse_lora_params.""" | |
| def test_dense_2d(self): | |
| """Simple Dense: a=(in, r), b=(r, out), weight=(in, out).""" | |
| rank = 4 | |
| in_dim, out_dim = 16, 8 | |
| params = _make_params( | |
| base_path=('dense', 'kernel'), | |
| base_shape=(in_dim, out_dim), | |
| lora_parent=('dense',), | |
| a_shape=(in_dim, rank), | |
| b_shape=(rank, out_dim), | |
| base_value=0.0, | |
| a_value=1.0, | |
| b_value=1.0, | |
| ) | |
| fused = lora.fuse_lora_params(params) | |
| # delta = a @ b = ones(16,4) @ ones(4,8) = full(16,8, fill=4) | |
| fused_w = fused['dense']['kernel'] | |
| expected_delta = np.full((in_dim, out_dim), rank, dtype=np.float32) | |
| np.testing.assert_allclose(fused_w, expected_delta) | |
| # LoRA keys should be gone. | |
| self.assertNotIn('lora', fused['dense']) | |
| def test_mlp_linear(self): | |
| """MLP linear: einsum '...H,HF->...F', weight=(H, D). | |
| LoRA: a=(H, r), b=(r, D). a @ b = (H, D) matches weight directly. | |
| _LoRAEinsum_linear -> base at ('mlp', 'linear'). | |
| """ | |
| rank = 4 | |
| d_model, hidden = 16, 32 | |
| params = _make_params( | |
| base_path=('mlp', 'linear'), | |
| base_shape=(hidden, d_model), | |
| lora_parent=('mlp', '_LoRAEinsum_linear'), | |
| a_shape=(hidden, rank), | |
| b_shape=(rank, d_model), | |
| base_value=0.0, | |
| ) | |
| fused = lora.fuse_lora_params(params) | |
| fused_w = fused['mlp']['linear'] | |
| self.assertEqual(fused_w.shape, (hidden, d_model)) | |
| def test_q_einsum(self): | |
| """q_einsum: a=(D, r), b=(r, N, H), weight=(N, D, H). | |
| _LoRAEinsum_0 under q_einsum -> base at ('attn', 'q_einsum', 'w'). | |
| """ | |
| rank = 2 | |
| d_model, num_heads, head_dim = 12, 3, 4 | |
| params = _make_params( | |
| base_path=('attn', 'q_einsum', 'w'), | |
| base_shape=(num_heads, d_model, head_dim), | |
| lora_parent=('attn', 'q_einsum', '_LoRAEinsum_0'), | |
| a_shape=(d_model, rank), | |
| b_shape=(rank, num_heads, head_dim), | |
| base_value=0.0, | |
| ) | |
| fused = lora.fuse_lora_params( | |
| params, | |
| ) | |
| fused_w = fused['attn']['q_einsum']['w'] | |
| self.assertEqual(fused_w.shape, (num_heads, d_model, head_dim)) | |
| def test_kv_einsum(self): | |
| """kv_einsum: a=(D, r), b=(r, C, K, H), weight=(C, K, D, H). | |
| _LoRAEinsum_0 under kv_einsum -> base at ('attn', 'kv_einsum', 'w'). | |
| """ | |
| rank = 2 | |
| d_model, num_kv, head_dim = 12, 1, 4 | |
| c_dim = 2 # key+value | |
| params = _make_params( | |
| base_path=('attn', 'kv_einsum', 'w'), | |
| base_shape=(c_dim, num_kv, d_model, head_dim), | |
| lora_parent=('attn', 'kv_einsum', '_LoRAEinsum_0'), | |
| a_shape=(d_model, rank), | |
| b_shape=(rank, c_dim, num_kv, head_dim), | |
| base_value=0.0, | |
| ) | |
| fused = lora.fuse_lora_params(params) | |
| fused_w = fused['attn']['kv_einsum']['w'] | |
| self.assertEqual(fused_w.shape, (c_dim, num_kv, d_model, head_dim)) | |
| def test_attn_vec_einsum(self): | |
| """attn_vec_einsum: a=(N, H, r), b=(r, D), weight=(N, H, D). | |
| _LoRAEinsum_0 -> base at ('attn', 'attn_vec_einsum', 'w'). | |
| """ | |
| rank = 2 | |
| num_heads, head_dim, d_model = 3, 4, 12 | |
| params = _make_params( | |
| base_path=('attn', 'attn_vec_einsum', 'w'), | |
| base_shape=(num_heads, head_dim, d_model), | |
| lora_parent=('attn', 'attn_vec_einsum', '_LoRAEinsum_0'), | |
| a_shape=(num_heads, head_dim, rank), | |
| b_shape=(rank, d_model), | |
| base_value=0.0, | |
| ) | |
| fused = lora.fuse_lora_params(params) | |
| fused_w = fused['attn']['attn_vec_einsum']['w'] | |
| self.assertEqual(fused_w.shape, (num_heads, head_dim, d_model)) | |
| def test_gating_einsum(self): | |
| """gating_einsum: a=(D, r), b=(r, 2, H), weight=(2, H, D). | |
| _LoRAEinsum_gating_einsum -> base at ('mlp', 'gating_einsum'). | |
| """ | |
| rank = 2 | |
| d_model, hidden = 12, 24 | |
| params = _make_params( | |
| base_path=('mlp', 'gating_einsum'), | |
| base_shape=(2, hidden, d_model), | |
| lora_parent=('mlp', '_LoRAEinsum_gating_einsum'), | |
| a_shape=(d_model, rank), | |
| b_shape=(rank, 2, hidden), | |
| base_value=0.0, | |
| ) | |
| fused = lora.fuse_lora_params(params) | |
| fused_w = fused['mlp']['gating_einsum'] | |
| self.assertEqual(fused_w.shape, (2, hidden, d_model)) | |
| def test_fuse_adds_delta_to_base(self): | |
| """Verify the fused weight = base_weight + delta.""" | |
| rank = 2 | |
| in_dim, out_dim = 6, 4 | |
| base_val = 10.0 | |
| params = _make_params( | |
| base_path=('dense', 'kernel'), | |
| base_shape=(in_dim, out_dim), | |
| lora_parent=('dense',), | |
| a_shape=(in_dim, rank), | |
| b_shape=(rank, out_dim), | |
| base_value=base_val, | |
| a_value=1.0, | |
| b_value=1.0, | |
| ) | |
| fused = lora.fuse_lora_params(params) | |
| fused_w = fused['dense']['kernel'] | |
| # delta = ones(6,2) @ ones(2,4) = full(6,4, fill=2) | |
| expected = np.full((in_dim, out_dim), base_val + rank, dtype=np.float32) | |
| np.testing.assert_allclose(fused_w, expected) | |
| def test_no_lora_params_returns_original(self): | |
| """If there are no LoRA params, return the original tree unchanged.""" | |
| import flax.traverse_util # pylint: disable=g-import-not-at-top | |
| flat = { | |
| ('dense', 'kernel'): np.ones((4, 4), dtype=np.float32), | |
| } | |
| params = flax.traverse_util.unflatten_dict(flat) | |
| fused = lora.fuse_lora_params(params) | |
| np.testing.assert_array_equal( | |
| fused['dense']['kernel'], params['dense']['kernel'] | |
| ) | |
| def test_multiple_adapters(self): | |
| """Fuse multiple LoRA adapters in the same param tree.""" | |
| import flax.traverse_util # pylint: disable=g-import-not-at-top | |
| rank = 2 | |
| flat = { | |
| # Adapter 1: Dense with shared-scope. | |
| ('dense', 'kernel'): np.zeros((8, 4), dtype=np.float32), | |
| ('dense', 'lora', 'a'): np.ones((8, rank), dtype=np.float32), | |
| ('dense', 'lora', 'b'): np.ones((rank, 4), dtype=np.float32), | |
| # Adapter 2: _LoRAEinsum_0 under q_einsum. | |
| ('attn', 'q_einsum', 'w'): np.zeros((3, 8, 4), dtype=np.float32), | |
| ('attn', 'q_einsum', '_LoRAEinsum_0', 'lora', 'a'): np.ones( | |
| (8, rank), dtype=np.float32 | |
| ), | |
| ('attn', 'q_einsum', '_LoRAEinsum_0', 'lora', 'b'): np.ones( | |
| (rank, 3, 4), dtype=np.float32 | |
| ), | |
| } | |
| params = flax.traverse_util.unflatten_dict(flat) | |
| fused = lora.fuse_lora_params(params) | |
| # Both base weights should have been modified. | |
| self.assertFalse(np.all(fused['dense']['kernel'] == 0)) | |
| self.assertFalse(np.all(fused['attn']['q_einsum']['w'] == 0)) | |
| # LoRA keys should be gone. | |
| self.assertNotIn('lora', fused['dense']) | |
| self.assertNotIn('_LoRAEinsum_0', fused['attn']['q_einsum']) | |
| def test_missing_base_weight_raises(self): | |
| """Raise KeyError if no base weight is found for a LoRA adapter.""" | |
| import flax.traverse_util # pylint: disable=g-import-not-at-top | |
| flat = { | |
| ('orphan', '_LoRAEinsum_0', 'lora', 'a'): np.ones( | |
| (4, 2), dtype=np.float32 | |
| ), | |
| ('orphan', '_LoRAEinsum_0', 'lora', 'b'): np.ones( | |
| (2, 3), dtype=np.float32 | |
| ), | |
| } | |
| params = flax.traverse_util.unflatten_dict(flat) | |
| with self.assertRaises(KeyError): | |
| lora.fuse_lora_params(params) | |
| class FindInterleavingTest(parameterized.TestCase): | |
| """Tests for lora._find_interleaving.""" | |
| def test_identity(self): | |
| """seq_a + seq_b == target → trivial interleaving.""" | |
| result = lora._find_interleaving((4, 8), (4,), (8,)) | |
| self.assertEqual(result, ['a', 'b']) | |
| def test_reversed(self): | |
| """seq_b then seq_a.""" | |
| result = lora._find_interleaving((8, 4), (4,), (8,)) | |
| self.assertEqual(result, ['b', 'a']) | |
| def test_kv_einsum_pattern(self): | |
| """kv_einsum: target=(C=2, K=1, D=1536, H=256), a=(D,), b=(C,K,H).""" | |
| result = lora._find_interleaving((2, 1, 1536, 256), (1536,), (2, 1, 256)) | |
| self.assertEqual(result, ['b', 'b', 'a', 'b']) | |
| def test_q_einsum_pattern(self): | |
| """q_einsum: target=(N=8, D=1536, H=256), a=(D,), b=(N,H).""" | |
| result = lora._find_interleaving((8, 1536, 256), (1536,), (8, 256)) | |
| self.assertEqual(result, ['b', 'a', 'b']) | |
| def test_gating_einsum_pattern(self): | |
| """gating_einsum: target=(2, H=6144, D=1536), a=(D,), b=(2,H).""" | |
| result = lora._find_interleaving((2, 6144, 1536), (1536,), (2, 6144)) | |
| self.assertEqual(result, ['b', 'b', 'a']) | |
| def test_no_valid_interleaving_raises(self): | |
| """Raise ValueError when no interleaving is possible.""" | |
| with self.assertRaises(ValueError): | |
| lora._find_interleaving((10, 20), (5,), (8,)) | |
| def test_ambiguous_interleaving_raises(self): | |
| """Raise ValueError when axes have the same size → ambiguous.""" | |
| # target=(64, 64), a=(64,), b=(64,) — two valid orderings. | |
| with self.assertRaises(ValueError): | |
| lora._find_interleaving((64, 64), (64,), (64,)) | |
| class ComputeLoraDeltaTest(parameterized.TestCase): | |
| """Tests for lora._compute_lora_delta.""" | |
| def test_2d_fast_path(self): | |
| """2D case: a @ b matches target directly.""" | |
| a = np.ones((6, 2), dtype=np.float32) | |
| b = np.ones((2, 4), dtype=np.float32) | |
| delta = lora._compute_lora_delta(a, b, target_shape=(6, 4)) | |
| self.assertEqual(delta.shape, (6, 4)) | |
| # ones(6,2) @ ones(2,4) = full(6,4, fill=2) | |
| np.testing.assert_allclose(delta, np.full((6, 4), 2.0)) | |
| def test_3d_needs_interleaving(self): | |
| """q_einsum: a=(D,r), b=(r,N,H), target=(N,D,H).""" | |
| rank = 2 | |
| d, n, h = 12, 3, 4 | |
| a = np.ones((d, rank), dtype=np.float32) | |
| b = np.ones((rank, n, h), dtype=np.float32) | |
| delta = lora._compute_lora_delta(a, b, target_shape=(n, d, h)) | |
| self.assertEqual(delta.shape, (n, d, h)) | |
| def test_4d_kv_einsum(self): | |
| """kv_einsum: a=(D,r), b=(r,C,K,H), target=(C,K,D,H).""" | |
| rank = 2 | |
| d, c, k, h = 12, 2, 1, 4 | |
| a = np.ones((d, rank), dtype=np.float32) | |
| b = np.ones((rank, c, k, h), dtype=np.float32) | |
| delta = lora._compute_lora_delta(a, b, target_shape=(c, k, d, h)) | |
| self.assertEqual(delta.shape, (c, k, d, h)) | |
| def test_numerical_correctness(self): | |
| """Verify the delta equals the correct contraction numerically.""" | |
| rng = np.random.RandomState(42) | |
| a = rng.randn(5, 3).astype(np.float32) # (D=5, r=3) | |
| b = rng.randn(3, 2, 4).astype(np.float32) # (r=3, N=2, H=4) | |
| target_shape = (2, 5, 4) # (N, D, H) | |
| delta = lora._compute_lora_delta(a, b, target_shape=target_shape) | |
| # Manual: einsum 'ar,rbc->bac' | |
| expected = jnp.einsum('ar,rbc->bac', a, b) | |
| np.testing.assert_allclose(delta, expected, rtol=1e-5) | |
| # --------------------------------------------------------------------------- | |
| # Tests for _matches_target_modules and ALL_LINEAR. | |
| # --------------------------------------------------------------------------- | |
| def _make_mock_module(path, name=None): | |
| """Create a lightweight mock with ``.path`` and ``.name`` attributes.""" | |
| m = mock.MagicMock(spec=['path', 'name']) | |
| m.path = path | |
| m.name = name or (path[-1] if path else '') | |
| return m | |
| class MatchesTargetModulesTest(parameterized.TestCase): | |
| """Tests for lora._matches_target_modules.""" | |
| def test_none_matches_everything(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| self.assertTrue(lora._matches_target_modules(module, None)) | |
| def test_all_linear_matches_everything(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| self.assertTrue(lora._matches_target_modules(module, lora.ALL_LINEAR)) | |
| def test_exact_name_match(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| self.assertTrue(lora._matches_target_modules(module, ['q_einsum'])) | |
| def test_parent_scope_match(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| self.assertTrue(lora._matches_target_modules(module, ['attn'])) | |
| def test_full_path_regex_match(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| self.assertTrue(lora._matches_target_modules(module, [r'layer_0/attn'])) | |
| def test_no_match(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| self.assertFalse(lora._matches_target_modules(module, ['mlp'])) | |
| def test_multiple_patterns_any_matches(self): | |
| module = _make_mock_module(('layer_0', 'mlp', 'linear')) | |
| self.assertTrue(lora._matches_target_modules(module, ['q_einsum', 'mlp'])) | |
| def test_regex_pattern(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'kv_einsum')) | |
| self.assertTrue(lora._matches_target_modules(module, [r'(q|kv)_einsum'])) | |
| def test_regex_layer_range(self): | |
| """Regex can match a range of layers.""" | |
| mod0 = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| mod5 = _make_mock_module(('layer_5', 'attn', 'q_einsum')) | |
| mod10 = _make_mock_module(('layer_10', 'attn', 'q_einsum')) | |
| pattern = [r'layer_[0-4]/'] | |
| self.assertTrue(lora._matches_target_modules(mod0, pattern)) | |
| self.assertFalse(lora._matches_target_modules(mod5, pattern)) | |
| self.assertFalse(lora._matches_target_modules(mod10, pattern)) | |
| def test_invalid_string_raises(self): | |
| module = _make_mock_module(('layer_0', 'attn', 'q_einsum')) | |
| with self.assertRaises(ValueError): | |
| lora._matches_target_modules(module, 'some-random-string') | |
| def test_fallback_to_name_when_no_path(self): | |
| """When module has no .path attribute, falls back to .name.""" | |
| m = mock.MagicMock(spec=['name']) | |
| m.name = 'q_einsum' | |
| self.assertTrue(lora._matches_target_modules(m, ['q_einsum'])) | |
| self.assertFalse(lora._matches_target_modules(m, ['mlp'])) | |
| # --------------------------------------------------------------------------- | |
| # End-to-end tests for LoRA with target_modules. | |
| # --------------------------------------------------------------------------- | |
| class _TwoLayerModel(nn.Module): | |
| """Minimal model with two named Dense sublayers.""" | |
| def __call__(self, x): | |
| h = nn.Dense(8, name='dense_a')(x) | |
| return nn.Dense(4, name='dense_b')(h) | |
| class LoRATargetModulesE2ETest(parameterized.TestCase): | |
| """End-to-end tests for LoRA target_modules filtering.""" | |
| def _init_lora(self, target_modules): | |
| """Init a LoRA-wrapped _TwoLayerModel and return the params tree.""" | |
| inner = _TwoLayerModel() | |
| lora_model = lora.LoRA(rank=2, model=inner, target_modules=target_modules) | |
| params = lora_model.init(jax.random.key(0), jnp.zeros((1, 4)))['params'] | |
| return params | |
| def test_none_applies_to_all(self): | |
| """target_modules=None wraps all Dense layers.""" | |
| params = self._init_lora(None) | |
| self.assertIn('lora', params['dense_a']) | |
| self.assertIn('lora', params['dense_b']) | |
| def test_all_linear_applies_to_all(self): | |
| """target_modules='all-linear' wraps all Dense layers.""" | |
| params = self._init_lora(lora.ALL_LINEAR) | |
| self.assertIn('lora', params['dense_a']) | |
| self.assertIn('lora', params['dense_b']) | |
| def test_single_target(self): | |
| """Only dense_a gets LoRA.""" | |
| params = self._init_lora(['dense_a']) | |
| self.assertIn('lora', params['dense_a']) | |
| self.assertNotIn('lora', params['dense_b']) | |
| def test_single_target_other(self): | |
| """Only dense_b gets LoRA.""" | |
| params = self._init_lora(['dense_b']) | |
| self.assertNotIn('lora', params['dense_a']) | |
| self.assertIn('lora', params['dense_b']) | |
| def test_both_targets_explicit(self): | |
| """Both layers targeted explicitly.""" | |
| params = self._init_lora(['dense_a', 'dense_b']) | |
| self.assertIn('lora', params['dense_a']) | |
| self.assertIn('lora', params['dense_b']) | |
| def test_regex_target(self): | |
| """Regex pattern matches both Dense layers.""" | |
| params = self._init_lora([r'dense_[ab]']) | |
| self.assertIn('lora', params['dense_a']) | |
| self.assertIn('lora', params['dense_b']) | |
| def test_no_match_no_lora_params(self): | |
| """If no module matches, no LoRA params are created.""" | |
| params = self._init_lora(['nonexistent']) | |
| self.assertNotIn('lora', params.get('dense_a', {})) | |
| self.assertNotIn('lora', params.get('dense_b', {})) | |
| def test_lora_shapes(self): | |
| """Verify the LoRA param shapes when targeting one Dense.""" | |
| params = self._init_lora(['dense_a']) | |
| lora_params = params['dense_a']['lora'] | |
| # dense_a: input=4, output=8, rank=2 | |
| self.assertEqual(lora_params['a'].shape, (4, 2)) | |
| self.assertEqual(lora_params['b'].shape, (2, 8)) | |
| if __name__ == '__main__': | |
| absltest.main() | |