Spaces:
Sleeping
Sleeping
| """ | |
| Deep smoke tests for Node 7 (Weight Signal Extractor). | |
| Verifies the mathematical invariants of the extracted canonical weight signals | |
| using synthetic LoRA files generated via torch and safetensors. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import tempfile | |
| from pathlib import Path | |
| from unittest.mock import patch | |
| import pytest | |
| import torch | |
| import safetensors.torch | |
| from src.envs.subenv3.node7_weight_extractor import extract_weight_signals | |
| from src.utils.canonical import CanonicalComponents | |
| # --------------------------------------------------------------------------- | |
| # Tests | |
| # --------------------------------------------------------------------------- | |
| def test_layer_norms_positive(synthetic_lora_path): | |
| """All Frobenius norms of canonical updates must be strictly positive for random weights.""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| assert len(result.layer_norms) == 3 | |
| assert all(v > 0.0 for v in result.layer_norms.values()) | |
| def test_layer_sparsity_in_range(synthetic_lora_path): | |
| """Sparsity fraction must be in [0.0, 1.0].""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| assert all(0.0 <= v <= 1.0 for v in result.layer_sparsity.values()) | |
| def test_histogram_sums_to_one(synthetic_lora_path): | |
| """The weight magnitude histogram must be a valid probability distribution.""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| assert sum(result.weight_magnitude_histogram) == pytest.approx(1.0, abs=1e-5) | |
| def test_layer_correlation_matrix_is_square(synthetic_lora_path): | |
| """n_layers x n_layers correlation matrix.""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| n = len(result.layer_norms) | |
| assert len(result.layer_correlation_matrix) == n | |
| assert all(len(row) == n for row in result.layer_correlation_matrix) | |
| def test_layer_correlation_diagonal_is_one(synthetic_lora_path): | |
| """Pearson correlation with self must be 1.0.""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| for i, row in enumerate(result.layer_correlation_matrix): | |
| # Pearson correlation can be slightly off due to float precision | |
| assert abs(row[i] - 1.0) < 1e-4 | |
| def test_dominant_directions_leq_rank(synthetic_lora_path): | |
| """Dominant direction count cannot exceed the theoretical max (rank * layers).""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| # The extractor currently uses a mean, but we check against the global upper bound per prompt. | |
| assert result.canonical_dominant_directions <= result.lora_rank * len(result.layer_norms) | |
| def test_overfitting_signature_reflects_rank_utilization(tmp_path): | |
| """Collapsed rank + high norms must result in a high overfitting signature (> 0.5).""" | |
| # A is (in, rank) = (32, 8). We collapse it to rank 1. | |
| A = torch.zeros(32, 8) | |
| A[:, 0] = 1.0 | |
| # B is (out, rank) = (64, 8). Inflate update norm to trigger signature. | |
| B = torch.randn(64, 8) * 50.0 | |
| # Create 2 layers to satisfy the extractor's correlation matrix logic (n > 1) | |
| # Use .clone() to avoid safetensors RuntimeError regarding shared memory. | |
| tensors = { | |
| "layer_0.lora_A.weight": A.T.contiguous().clone(), | |
| "layer_0.lora_B.weight": B.contiguous().clone(), | |
| "layer_1.lora_A.weight": A.T.contiguous().clone(), | |
| "layer_1.lora_B.weight": B.contiguous().clone() | |
| } | |
| path = tmp_path / "collapsed_lora.safetensors" | |
| safetensors.torch.save_file(tensors, str(path)) | |
| result = extract_weight_signals(path) | |
| # rank_util = 1/8 = 0.125. (1 - 0.125) = 0.875. | |
| # norm will be high (> 10), so norm_factor = 1.0. | |
| # signature should be ~0.875. | |
| assert result.overfitting_signature > 0.5 | |
| def test_gradient_noise_nonnegative(synthetic_lora_path): | |
| """Gradient noise estimate (min/max S ratio) must be non-negative.""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| assert result.gradient_noise_estimate >= 0.0 | |
| def test_target_modules_matches_layer_count(synthetic_lora_path): | |
| """Verify target_modules list matches the number of LoRA pairs in the file.""" | |
| result = extract_weight_signals(synthetic_lora_path) | |
| assert len(result.target_modules) == 3 | |
| def test_canonical_not_called_on_nonlora_keys(tmp_path): | |
| """Non-2D or non-LoRA keys in safetensors must be ignored by the extractor.""" | |
| A = torch.randn(8, 32) | |
| B = torch.randn(64, 8) | |
| # 2 layers + a non-LoRA key. Use .clone() to avoid shared memory issues. | |
| tensors = { | |
| "layer_0.lora_A.weight": A.clone(), | |
| "layer_0.lora_B.weight": B.clone(), | |
| "layer_1.lora_A.weight": A.clone(), | |
| "layer_1.lora_B.weight": B.clone(), | |
| "norm.weight": torch.randn(64) # non-2D tensor (vector) | |
| } | |
| path = tmp_path / "mixed_keys.safetensors" | |
| safetensors.torch.save_file(tensors, str(path)) | |
| # Patch the function imported inside the extractor module | |
| target = "src.envs.subenv3.node7_weight_extractor.canonicalize_lora_factors" | |
| with patch(target) as mock_canon: | |
| # Provide a dummy return value | |
| mock_canon.return_value = CanonicalComponents( | |
| U=torch.randn(64, 8), | |
| S=torch.randn(8), | |
| Vt=torch.randn(8, 32), | |
| Q=torch.randn(32, 8) | |
| ) | |
| extract_weight_signals(path) | |
| # Should be called once per layer (2 total), not for norm.weight | |
| assert mock_canon.call_count == 2 | |