|
11 | 11 | dflash_loss_decay, |
12 | 12 | exp_loss_decay, |
13 | 13 | kl_div_loss, |
| 14 | + lk_hybrid_loss, |
14 | 15 | loss_function, |
15 | 16 | neg_log_acceptance_loss, |
16 | 17 | resolve_loss_fn, |
@@ -164,6 +165,70 @@ def test_resolve_nla(self): |
164 | 165 | assert resolve_loss_fn("nla") is neg_log_acceptance_loss |
165 | 166 |
|
166 | 167 |
|
| 168 | +class TestLKHybridLoss: |
| 169 | + def test_eta_zero_reduces_to_kl(self): |
| 170 | + """eta=0 gives lambda=1 everywhere, so the loss is pure KL.""" |
| 171 | + torch.manual_seed(0) |
| 172 | + logits, targets = torch.randn(1, 4, 50), torch.randn(1, 4, 50) |
| 173 | + assert torch.allclose( |
| 174 | + lk_hybrid_loss(logits, targets, eta=0.0), |
| 175 | + kl_div_loss(logits, targets), |
| 176 | + atol=1e-6, |
| 177 | + ) |
| 178 | + |
| 179 | + def test_large_eta_reduces_to_tv(self): |
| 180 | + """Large eta drives lambda->0, so the loss approaches pure TV.""" |
| 181 | + torch.manual_seed(0) |
| 182 | + logits, targets = torch.randn(1, 4, 50), torch.randn(1, 4, 50) |
| 183 | + assert torch.allclose( |
| 184 | + lk_hybrid_loss(logits, targets, eta=1e6), |
| 185 | + tv_loss(logits, targets), |
| 186 | + atol=1e-5, |
| 187 | + ) |
| 188 | + |
| 189 | + def test_shape_and_finite(self): |
| 190 | + """Output is [1, seq_len] and finite at the default blend setting.""" |
| 191 | + torch.manual_seed(0) |
| 192 | + logits, targets = torch.randn(1, 4, 50), torch.randn(1, 4, 50) |
| 193 | + out = lk_hybrid_loss(logits, targets, eta=3.0) |
| 194 | + assert out.shape == (1, 4) |
| 195 | + assert torch.isfinite(out).all() |
| 196 | + |
| 197 | + def test_alpha_is_detached_in_weight(self): |
| 198 | + """The alpha inside lambda must be stop-gradient. |
| 199 | +
|
| 200 | + Verify the impl's gradient matches the detached form, and that NOT |
| 201 | + detaching would differ. |
| 202 | + """ |
| 203 | + torch.manual_seed(0) |
| 204 | + logits = torch.randn(1, 3, 40, requires_grad=True) |
| 205 | + targets = torch.randn(1, 3, 40) |
| 206 | + g_impl = torch.autograd.grad( |
| 207 | + lk_hybrid_loss(logits, targets, eta=3.0).sum(), logits |
| 208 | + )[0] |
| 209 | + |
| 210 | + def manual(detach): |
| 211 | + dp, tp = torch.softmax(logits, -1), torch.softmax(targets, -1) |
| 212 | + ov = torch.minimum(dp, tp).sum(-1) |
| 213 | + alpha = ov.detach() if detach else ov |
| 214 | + lam = torch.exp(-3.0 * alpha) |
| 215 | + return (lam * kl_div_loss(logits, targets) + (1 - lam) * (1 - ov)).sum() |
| 216 | + |
| 217 | + g_detached = torch.autograd.grad(manual(True), logits, retain_graph=True)[0] |
| 218 | + g_nodetach = torch.autograd.grad(manual(False), logits)[0] |
| 219 | + assert torch.allclose(g_impl, g_detached, atol=1e-5) |
| 220 | + assert not torch.allclose(g_detached, g_nodetach, atol=1e-4) |
| 221 | + |
| 222 | + def test_resolve_binds_eta(self): |
| 223 | + """resolve_loss_fn binds eta into the 'lk_hybrid' callable via partial.""" |
| 224 | + torch.manual_seed(0) |
| 225 | + logits, targets = torch.randn(1, 4, 50), torch.randn(1, 4, 50) |
| 226 | + fn = resolve_loss_fn("lk_hybrid", lk_loss_eta=2.0) |
| 227 | + assert torch.allclose( |
| 228 | + fn(logits, targets), lk_hybrid_loss(logits, targets, eta=2.0), atol=1e-6 |
| 229 | + ) |
| 230 | + |
| 231 | + |
167 | 232 | class TestComputeAccuracySingleStep: |
168 | 233 | def test_prev_correct_chain(self): |
169 | 234 | """Conditional accuracy across ttt steps tracks cumulative correctness. |
|
0 commit comments