Spaces:
Sleeping
Sleeping
| import math | |
| import pytest | |
| import torch | |
| import torch.nn.functional as F | |
| from mini_transformer import BasicEncoderDecoderTransformer | |
| from mini_transformer.configs import ModelCfg | |
| from mini_transformer.utils import ( | |
| calculate_attention, | |
| combine_masks, | |
| create_causal_mask, | |
| extract_all_attention_maps, | |
| join_heads, | |
| sample_from_logits, | |
| sinusoidal_positional_encoding, | |
| split_heads, | |
| ) | |
| ###___split_heads___### | |
| def test_happy_path_shape_dtype_device_cpu(): | |
| x = torch.randn(2, 10, 16) # B=2, S=10, D=16 | |
| out = split_heads(x, num_heads=4) | |
| assert out.shape == (2, 4, 10, 4) | |
| assert out.dtype == x.dtype | |
| assert out.device == x.device | |
| def test_smallest_valid(): | |
| x = torch.randn(1, 1, 4) | |
| out = split_heads(x, num_heads=2) | |
| assert out.shape == (1, 2, 1, 2) | |
| def test_zero_length_seq_allowed(): | |
| x = torch.randn(2, 0, 8) | |
| out = split_heads(x, num_heads=2) | |
| assert out.shape == (2, 2, 0, 4) | |
| def test_non_3d_input_raises_value_error(): | |
| x2 = torch.randn(10, 16) | |
| with pytest.raises(ValueError) as ei2: | |
| split_heads(x2, 4) | |
| assert "(B, S, D)" in str(ei2.value) | |
| x4 = torch.randn(2, 3, 4, 5) | |
| with pytest.raises(ValueError) as ei4: | |
| split_heads(x4, 4) | |
| assert "(B, S, D)" in str(ei4.value) | |
| def test_invalid_num_heads_type_raises_type_error(): | |
| x = torch.randn(2, 10, 16) | |
| with pytest.raises(TypeError): | |
| split_heads(x, 4.0) # float | |
| with pytest.raises(TypeError): | |
| split_heads(x, torch.tensor(4)) # Tensor | |
| def test_num_heads_leq_zero_raises_value_error(): | |
| x = torch.randn(2, 10, 16) | |
| with pytest.raises(ValueError): | |
| split_heads(x, 0) | |
| with pytest.raises(ValueError): | |
| split_heads(x, -2) | |
| def test_non_divisible_d_model_raises_value_error_message_has_values(): | |
| x = torch.randn(2, 5, 10) | |
| with pytest.raises(ValueError) as e: | |
| split_heads(x, 3) | |
| msg = str(e.value) | |
| assert "d_model (10)" in msg and "num_heads (3)" in msg | |
| def test_invariants_random_valid(B, S, D, H): | |
| x = torch.randn(B, S, D) | |
| out = split_heads(x, H) | |
| assert out.shape[0] == B | |
| assert out.shape[2] == S | |
| assert (H * (D // H)) == D | |
| assert x.numel() == out.numel() | |
| def test_grad_propagates(): | |
| x = torch.randn(2, 10, 16, requires_grad=True) | |
| out = split_heads(x, 4) | |
| # Do a simple scalar reduction to make grad non-trivial | |
| loss = out.square().mean() | |
| loss.backward() | |
| assert x.grad is not None | |
| assert x.grad.shape == x.shape | |
| def test_non_contiguous_input(): | |
| # Create a non-contiguous view by transposing and slicing | |
| base = torch.randn(10, 2, 16) | |
| x = base.transpose(0, 1)[:, :8, :] # shape (2, 8, 16), likely non-contiguous | |
| assert not x.is_contiguous() | |
| out = split_heads(x, 4) | |
| assert out.shape == (2, 4, 8, 4) | |
| def test_device_preserved_cuda(): | |
| x = torch.randn(2, 10, 16, device="cuda") | |
| out = split_heads(x, 4) | |
| assert out.device.type == "cuda" | |
| def test_dtype_half_bfloat_on_gpu(): | |
| x_half = torch.randn(2, 10, 16, device="cuda", dtype=torch.float16) | |
| out_half = split_heads(x_half, 4) | |
| assert out_half.dtype == torch.float16 | |
| # bfloat16 may not be available on all GPUs; guard with try | |
| try: | |
| x_bf16 = torch.randn(2, 10, 16, device="cuda", dtype=torch.bfloat16) | |
| out_bf16 = split_heads(x_bf16, 4) | |
| assert out_bf16.dtype == torch.bfloat16 | |
| except RuntimeError: | |
| pytest.skip("bfloat16 not supported on this device") | |
| ###___calculate_attention___### | |
| # ---------------------------- | |
| # Helpers | |
| # ---------------------------- | |
| def _rand(B=2, H=3, Sq=4, Sk=5, D=8, device="cpu", dtype=torch.float32): | |
| q = torch.randn(B, H, Sq, D, device=device, dtype=dtype) | |
| k = torch.randn(B, H, Sk, D, device=device, dtype=dtype) | |
| v = torch.randn(B, H, Sk, D, device=device, dtype=dtype) | |
| return q, k, v | |
| # ---------------------------- | |
| # A. Basic correctness | |
| # ---------------------------- | |
| def test_basic_shapes_and_row_sums(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| attn, probs = calculate_attention(q, k, v, mask=None, return_probs=True) | |
| assert attn.shape == (B, H, Sq, D) | |
| assert probs.shape == (B, H, Sq, Sk) | |
| # softmax rows sum to 1 | |
| assert torch.allclose(probs.sum(-1), torch.ones(B, H, Sq), atol=1e-6) | |
| def test_identity_prefers_diagonal(): | |
| # q == k == v as orthogonal-ish basis to encourage diagonal peak | |
| Sq = Sk = D = 4 | |
| base = torch.eye(D).view(1, 1, Sk, D).expand(1, 1, Sk, D).contiguous() | |
| q = base.clone() | |
| k = base.clone() | |
| v = base.clone() | |
| _, probs = calculate_attention(q, k, v, mask=None, return_probs=True) | |
| assert torch.equal(probs[0, 0].argmax(-1), torch.arange(Sq)) | |
| # ---------------------------- | |
| # B. Mask behavior (boolean & causal) | |
| # ---------------------------- | |
| def test_padding_mask_boolean_last_two_keys(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| mask = torch.zeros(B, H, Sq, Sk, dtype=torch.bool) | |
| mask[:, :, :, -2:] = True # mask last two keys | |
| _, probs = calculate_attention(q, k, v, mask=mask, return_probs=True) | |
| assert (probs[:, :, :, -2:] < 1e-6).all() | |
| # After zeroing masked entries, rows renormalize to ~1 | |
| assert torch.allclose(probs.masked_fill(mask, 0).sum(-1), torch.ones(B, H, Sq), atol=1e-6) | |
| def test_causal_mask_upper_triangle_zero(): | |
| B, H, S, D = 2, 3, 6, 8 | |
| q, k, v = _rand(B, H, S, S, D) | |
| causal = torch.ones(B, H, S, S, dtype=torch.bool).triu(1) # True above diagonal | |
| _, probs = calculate_attention(q, k, v, mask=causal, return_probs=True) | |
| assert (probs.triu(1) < 1e-6).all() | |
| def test_fully_masked_row_zero_probs_and_output(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| mask = torch.zeros(B, H, Sq, Sk, dtype=torch.bool) | |
| mask[:, :, 1, :] = True # fully mask a query row | |
| attn, probs = calculate_attention(q, k, v, mask=mask, return_probs=True) | |
| assert torch.allclose(probs[:, :, 1, :], torch.zeros(B, H, Sk)) | |
| assert torch.allclose(attn[:, :, 1, :], torch.zeros(B, H, D)) | |
| ###___extract_all_attention_maps___### | |
| def _make_model_cfg(num_layers: int, layer_norm_style: str | None = None) -> ModelCfg: | |
| return ModelCfg( | |
| name="test", | |
| best_checkpoint_path="/tmp/best.ckpt", | |
| latest_checkpoint_path="/tmp/latest.ckpt", | |
| tokenizer="demo", | |
| d_model=16, | |
| num_layers=num_layers, | |
| num_heads=4, | |
| d_ff=32, | |
| dropout_rate=0.0, | |
| max_seq_len=32, | |
| vocab_size=64, | |
| pad_id=0, | |
| bos_id=1, | |
| eos_id=2, | |
| layer_norm_style=layer_norm_style, | |
| ) | |
| def _run_attention_maps( | |
| *, | |
| num_layers: int, | |
| layer_norm_style: str | None, | |
| ) -> tuple[ModelCfg, BasicEncoderDecoderTransformer, dict, torch.Tensor, torch.Tensor]: | |
| cfg = _make_model_cfg(num_layers=num_layers, layer_norm_style=layer_norm_style) | |
| model = BasicEncoderDecoderTransformer(cfg) | |
| batch, src_len, tgt_len = 2, 5, 4 | |
| src_ids = torch.randint(0, cfg.vocab_size, (batch, src_len), dtype=torch.long) | |
| tgt_ids = torch.randint(0, cfg.vocab_size, (batch, tgt_len), dtype=torch.long) | |
| src_pad = torch.zeros(batch, src_len, dtype=torch.bool) | |
| tgt_pad = torch.zeros(batch, tgt_len, dtype=torch.bool) | |
| maps = extract_all_attention_maps(model, src_ids, tgt_ids, src_pad, tgt_pad) | |
| return cfg, model, maps, src_ids, tgt_ids | |
| def _assert_attention_shapes(maps, *, batch: int, heads: int, src_len: int, tgt_len: int) -> None: | |
| for layer_map in maps["enc_self"]: | |
| assert layer_map.shape == (batch, heads, src_len, src_len) | |
| assert torch.isfinite(layer_map).all() | |
| assert torch.allclose(layer_map.sum(-1), torch.ones(batch, heads, src_len), atol=1e-5) | |
| for layer_map in maps["dec_self"]: | |
| assert layer_map.shape == (batch, heads, tgt_len, tgt_len) | |
| assert torch.isfinite(layer_map).all() | |
| assert torch.allclose(layer_map.sum(-1), torch.ones(batch, heads, tgt_len), atol=1e-5) | |
| for layer_map in maps["dec_cross"]: | |
| assert layer_map.shape == (batch, heads, tgt_len, src_len) | |
| assert torch.isfinite(layer_map).all() | |
| assert torch.allclose(layer_map.sum(-1), torch.ones(batch, heads, tgt_len), atol=1e-5) | |
| def test_extract_attention_maps_handles_layer_norm_styles(layer_norm_style, expected_pre_norm): | |
| cfg, model, maps, src_ids, tgt_ids = _run_attention_maps( | |
| num_layers=6, layer_norm_style=layer_norm_style | |
| ) | |
| batch, src_len = src_ids.shape | |
| tgt_len = tgt_ids.shape[1] | |
| assert len(maps["enc_self"]) == cfg.num_layers | |
| assert len(maps["dec_self"]) == cfg.num_layers | |
| assert len(maps["dec_cross"]) == cfg.num_layers | |
| _assert_attention_shapes( | |
| maps, | |
| batch=batch, | |
| heads=cfg.num_heads, | |
| src_len=src_len, | |
| tgt_len=tgt_len, | |
| ) | |
| assert all(layer.pre_norm is expected_pre_norm for layer in model.encoder.layers) | |
| assert all(layer.pre_norm is expected_pre_norm for layer in model.decoder.layers) | |
| # ---------------------------- | |
| # C. Numerical stability | |
| # ---------------------------- | |
| def test_extreme_logits_no_nans_or_infs(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| q = q * 1000 | |
| k = k * 1000 | |
| attn, probs = calculate_attention(q, k, v, mask=None, return_probs=True) | |
| assert not (torch.isnan(probs).any() or torch.isinf(probs).any()) | |
| assert attn.shape == (B, H, Sq, D) | |
| def test_half_precision_close_to_fp32_or_skip(): | |
| if not torch.cuda.is_available(): | |
| pytest.skip("CUDA not available") | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q32, k32, v32 = _rand(B, H, Sq, Sk, D, device="cuda", dtype=torch.float32) | |
| q16, k16, v16 = q32.half(), k32.half(), v32.half() | |
| a16 = calculate_attention(q16, k16, v16, mask=None) | |
| a32 = calculate_attention(q32, k32, v32, mask=None) | |
| # looser tolerance for fp16 | |
| assert torch.allclose(a16.float(), a32.float(), atol=5e-2, rtol=5e-2) | |
| # ---------------------------- | |
| # D. Edge cases | |
| # ---------------------------- | |
| def test_singleton_softmax_is_one(): | |
| q = torch.randn(1, 1, 1, 1) | |
| k = torch.randn(1, 1, 1, 1) | |
| v = torch.randn(1, 1, 1, 1) | |
| _, p = calculate_attention(q, k, v, mask=None, return_probs=True) | |
| assert torch.allclose(p, torch.ones_like(p), atol=1e-6) | |
| # ---------------------------- | |
| # E. Autograd & determinism | |
| # ---------------------------- | |
| def test_gradients_flow_no_inplace_breakage(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q = torch.randn(B, H, Sq, D, requires_grad=True) | |
| k = torch.randn(B, H, Sk, D, requires_grad=True) | |
| v = torch.randn(B, H, Sk, D, requires_grad=True) | |
| out = calculate_attention(q, k, v, mask=None) | |
| loss = out.pow(2).sum() | |
| loss.backward() | |
| for t in (q, k, v): | |
| assert t.grad is not None and torch.isfinite(t.grad).all() | |
| def test_repeated_calls_consistent_without_dropout(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| a1 = calculate_attention(q, k, v, mask=None) | |
| a2 = calculate_attention(q, k, v, mask=None) | |
| assert torch.allclose(a1, a2) | |
| # ---------------------------- | |
| # F. Parity with PyTorch SDPA (when available) | |
| # ---------------------------- | |
| def test_sdpa_parity_if_available(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| try: | |
| sdpa = F.scaled_dot_product_attention( | |
| q, k, v, dropout_p=0.0, attn_mask=None, is_causal=False | |
| ) | |
| manual = calculate_attention(q, k, v, mask=None) | |
| assert torch.allclose(sdpa, manual, atol=1e-5, rtol=1e-4) | |
| except Exception: | |
| # Older PyTorch: skip | |
| pass | |
| # ---------------------------- | |
| # G. Performance smoke (sanity only) | |
| # ---------------------------- | |
| def test_perf_smoke_runs_reasonably(): | |
| # Not asserting timing; just ensure no OOM or pathological slowdowns | |
| q, k, v = _rand(B=1, H=8, Sq=1024, Sk=1024, D=64) | |
| _ = calculate_attention(q, k, v, mask=None, return_probs=False) | |
| q, k, v = _rand(B=2, H=8, Sq=1536, Sk=1536, D=64) | |
| _ = calculate_attention(q, k, v, mask=None, return_probs=False) | |
| # ---------------------------- | |
| # H. Device behavior | |
| # ---------------------------- | |
| def test_mask_on_cpu_qkv_on_gpu_autofix_or_skip(): | |
| if not torch.cuda.is_available(): | |
| pytest.skip("CUDA not available") | |
| prev = torch.are_deterministic_algorithms_enabled() | |
| try: | |
| # Disable determinism *only for this test* | |
| if prev: | |
| torch.use_deterministic_algorithms(False) | |
| q, k, v = _rand(device="cuda") | |
| mask = torch.zeros(q.shape[0], 1, 1, k.shape[2], dtype=torch.bool) # CPU | |
| attn, probs = calculate_attention(q, k, v, mask, return_probs=True) | |
| assert attn.device.type == "cuda" | |
| assert probs.device.type == "cuda" | |
| finally: | |
| # restore whatever the global setting was | |
| torch.use_deterministic_algorithms(prev) | |
| def test_qkv_device_mismatch_raises_clear_error(): | |
| if not torch.cuda.is_available(): | |
| pytest.skip("CUDA not available") | |
| prev = torch.are_deterministic_algorithms_enabled() | |
| try: | |
| if prev: | |
| torch.use_deterministic_algorithms(False) | |
| q, k, v = _rand(device="cuda") | |
| k = k.cpu() # force mismatch | |
| with pytest.raises(RuntimeError) as ei: | |
| _ = calculate_attention(q, k, v, mask=None) | |
| msg = str(ei.value) | |
| assert ( | |
| "q/k/v must be on the same device" in msg # our explicit check | |
| or "Expected all tensors to be on the same device" in msg # PyTorch matmul | |
| or "query, key, value must be on the same device" in msg # updated error wording | |
| ) | |
| finally: | |
| torch.use_deterministic_algorithms(prev) | |
| # ---------------------------- | |
| # I. Mask broadcastability | |
| # ---------------------------- | |
| def test_boolean_mask_broadcast_variants_equivalent(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| m_exact = torch.zeros(B, H, Sq, Sk, dtype=torch.bool) | |
| m_B1_1Sk = torch.zeros(B, 1, 1, Sk, dtype=torch.bool) | |
| m_11SqSk = torch.zeros(1, 1, Sq, Sk, dtype=torch.bool) | |
| a_exact, p_exact = calculate_attention(q, k, v, m_exact, return_probs=True) | |
| a1, p1 = calculate_attention(q, k, v, m_B1_1Sk, return_probs=True) | |
| a2, p2 = calculate_attention(q, k, v, m_11SqSk, return_probs=True) | |
| assert torch.allclose(a1, a_exact, atol=1e-6, rtol=1e-5) | |
| assert torch.allclose(p1, p_exact, atol=1e-6, rtol=1e-5) | |
| assert torch.allclose(a2, a_exact, atol=1e-6, rtol=1e-5) | |
| assert torch.allclose(p2, p_exact, atol=1e-6, rtol=1e-5) | |
| def test_non_broadcastable_mask_raises_value_error(): | |
| B, H, Sq, Sk, D = 2, 3, 4, 5, 8 | |
| q, k, v = _rand(B, H, Sq, Sk, D) | |
| bad = torch.zeros( | |
| B, H, Sq, dtype=torch.bool | |
| ) # missing last dim => not broadcastable to (B,H,Sq,Sk) | |
| with pytest.raises(ValueError) as ei: | |
| _ = calculate_attention(q, k, v, bad) | |
| assert "not broadcastable" in str(ei.value) | |
| ###___join_heads___### | |
| def test_join_heads_type_error(): | |
| with pytest.raises(TypeError): | |
| join_heads([1, 2, 3]) # not a tensor | |
| def test_join_heads_dim_error(): | |
| with pytest.raises(ValueError): | |
| join_heads(torch.randn(2, 3, 4)) # only 3D | |
| def test_join_heads_shape_values(): | |
| x = torch.randn(2, 0, 4, 5) # zero heads | |
| y = join_heads(x) | |
| assert y.shape == (2, 4, 0) | |
| def test_join_heads_roundtrip(): | |
| B, H, T, Dh = 2, 3, 5, 7 | |
| x = torch.randn(B, H, T, Dh) | |
| y = join_heads(x) | |
| assert y.shape == (B, T, H * Dh) | |
| # Check element mapping correctness | |
| for b in range(B): | |
| for h in range(H): | |
| for t in range(T): | |
| for d in range(Dh): | |
| assert torch.allclose(y[b, t, h * Dh + d], x[b, h, t, d]) | |
| ###___sinusoidal_positional_encoding___### | |
| def test_sinusoidal_shapes_and_dtype(seq_len, dim): | |
| pe = sinusoidal_positional_encoding(seq_len, dim) | |
| assert pe.shape == (seq_len, dim) | |
| assert pe.dtype == torch.float32 | |
| assert pe.requires_grad is False | |
| def test_sinusoidal_matches_reference_small_case(): | |
| seq_len, dim = 4, 6 | |
| pe = sinusoidal_positional_encoding(seq_len, dim) | |
| ref = torch.zeros_like(pe) | |
| base = 10_000.0 | |
| for pos in range(seq_len): | |
| for i in range(0, dim // 2): | |
| denom = base ** (2 * i / dim) | |
| ref[pos, 2 * i] = math.sin(pos / denom) | |
| ref[pos, 2 * i + 1] = math.cos(pos / denom) | |
| paired = (dim // 2) * 2 | |
| assert torch.allclose(pe[:, :paired], ref[:, :paired], atol=1e-6, rtol=1e-6) | |
| def test_sinusoidal_deterministic(): | |
| a = sinusoidal_positional_encoding(8, 16) | |
| b = sinusoidal_positional_encoding(8, 16) | |
| assert torch.allclose(a, b) | |
| def test_sinusoidal_invalid_inputs(): | |
| with pytest.raises(ValueError): | |
| sinusoidal_positional_encoding(0, 8) | |
| with pytest.raises(ValueError): | |
| sinusoidal_positional_encoding(-1, 8) | |
| with pytest.raises(ValueError): | |
| sinusoidal_positional_encoding(1, 0) | |
| ###___combine_masks___### | |
| def test_both_none_returns_none(): | |
| assert combine_masks(None, None) is None | |
| def test_one_none_returns_other(): | |
| m = torch.tensor([[True, False]]) | |
| assert torch.equal(combine_masks(m, None), m) | |
| assert torch.equal(combine_masks(None, m), m) | |
| def test_or_combination(): | |
| m1 = torch.tensor([[True, False]]) | |
| m2 = torch.tensor([[False, True]]) | |
| expected = torch.tensor([[True, True]]) | |
| out = combine_masks(m1, m2) | |
| assert torch.equal(out, expected) | |
| def test_shape_mismatch_error(): | |
| m1 = torch.ones(2, 2, dtype=torch.bool) | |
| m2 = torch.ones(3, 3, dtype=torch.bool) | |
| with pytest.raises(ValueError): | |
| combine_masks(m1, m2) | |
| def test_broadcastable_masks(): | |
| m1 = torch.tensor([[True, False, True]]) | |
| m2 = torch.tensor([[False]]) # broadcast across | |
| out = combine_masks(m1, m2) | |
| assert torch.equal(out, m1 | m2) | |
| def test_dtype_conversion(): | |
| # Non-bool masks still should work if they are 0/1 ints | |
| m1 = torch.tensor([[1, 0]], dtype=torch.int32) | |
| m2 = torch.tensor([[0, 1]], dtype=torch.int32) | |
| out = combine_masks(m1.bool(), m2.bool()) | |
| expected = torch.tensor([[True, True]]) | |
| assert torch.equal(out, expected) | |
| ###___sample_from_logits___### | |
| def test_greedy_equals_argmax(): | |
| torch.manual_seed(0) | |
| logits = torch.randn(4, 7) | |
| ids = sample_from_logits(logits) # greedy | |
| ref = torch.argmax(F.softmax(logits, dim=-1), dim=-1) | |
| assert torch.equal(ids, ref) | |
| def test_temperature_effect_no_sampling(): | |
| # Greedy with temperature should leave argmax unchanged (monotonic transform) | |
| logits = torch.tensor([[0.1, 1.2, 0.3]], dtype=torch.float32) | |
| ids_t1 = sample_from_logits(logits, do_sample=False, temperature=1.0) | |
| ids_t05 = sample_from_logits(logits, do_sample=False, temperature=0.5) | |
| assert torch.equal(ids_t1, ids_t05) # argmax unchanged | |
| def test_top_k_limits_candidates(): | |
| logits = torch.tensor([[1.0, 0.9, 0.1, -1.0]], dtype=torch.float32) | |
| # with top_k=1 greedy must select the max (index 0) | |
| ids = sample_from_logits(logits, do_sample=False, top_k=1) | |
| assert torch.equal(ids, torch.tensor([0])) | |
| # with sampling, and top_k=1, the only candidate is index 0 | |
| gen = torch.Generator().manual_seed(123) | |
| ids_s = sample_from_logits(logits, do_sample=True, top_k=1, rng=gen) | |
| assert torch.equal(ids_s, torch.tensor([0])) | |
| def test_top_p_nucleus_filters_tail(): | |
| logits = torch.tensor([[5.0, 4.0, 3.0, -5.0]], dtype=torch.float32) # probs heavily skewed | |
| ids = sample_from_logits(logits, do_sample=False, top_p=0.6) # should keep a minimal head | |
| assert ids.item() in (0, 1) # greediest is 0 anyway; check no crash | |
| def test_min_tokens_to_keep_safety(): | |
| logits = torch.tensor([[0.0, 0.0, 0.0, 0.0]], dtype=torch.float32) | |
| # Even with very small top_p, we keep at least 1 token | |
| ids = sample_from_logits(logits, do_sample=False, top_p=0.01, min_tokens_to_keep=1) | |
| assert ids.numel() == 1 | |
| def test_allow_and_deny_lists(): | |
| logits = torch.tensor([[0.1, 2.0, 0.3, 3.0]], dtype=torch.float32) # best is idx=3 | |
| # Allow only {0,2} → best among those is 2 | |
| ids = sample_from_logits(logits, do_sample=False, allowed_tokens=[0, 2]) | |
| assert torch.equal(ids, torch.tensor([2])) | |
| # Deny {3} → next best is 1 | |
| ids = sample_from_logits(logits, do_sample=False, disallowed_tokens=[3]) | |
| assert torch.equal(ids, torch.tensor([1])) | |
| def test_rng_reproducibility_sampling(): | |
| logits = torch.tensor([[1.0, 1.0, 1.0, 1.0]], dtype=torch.float32) # uniform | |
| g1 = torch.Generator().manual_seed(42) | |
| g2 = torch.Generator().manual_seed(42) | |
| s1 = sample_from_logits(logits, do_sample=True, rng=g1) | |
| s2 = sample_from_logits(logits, do_sample=True, rng=g2) | |
| assert torch.equal(s1, s2) | |
| def test_device_is_preserved_cpu_cuda(): | |
| target_device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu") | |
| logits = torch.randn(3, 5, device=target_device) | |
| out = sample_from_logits(logits) # greedy by default | |
| assert out.device == target_device | |
| assert out.dtype == torch.long | |
| def test_handles_higher_rank_logits_flattening(): | |
| # [B, T, V] -> flattened internally; here we just ensure no crash and correct shape | |
| logits = torch.randn(2, 3, 7) | |
| out = sample_from_logits(logits) # returns [B*T] | |
| assert out.shape == (2 * 3,) | |
| ###___create_causal_mask___### | |
| def test_causal_mask_shape_and_dtype_cpu(): | |
| x = torch.zeros(2, 5, dtype=torch.long) | |
| mask = create_causal_mask(x, num_heads=3) | |
| assert mask.shape == (2, 3, 5, 5) | |
| assert mask.dtype == torch.bool | |
| assert mask.device == x.device | |
| def test_causal_mask_is_upper_triangular(): | |
| x = torch.zeros(1, 4, dtype=torch.long) | |
| mask = create_causal_mask(x, num_heads=2) | |
| tri = mask[0, 0].to(torch.int) | |
| expected = torch.tensor( | |
| [ | |
| [0, 1, 1, 1], | |
| [0, 0, 1, 1], | |
| [0, 0, 0, 1], | |
| [0, 0, 0, 0], | |
| ] | |
| ) | |
| assert torch.equal(tri, expected) | |
| def test_causal_mask_tracks_device(): | |
| x = torch.zeros(1, 3, dtype=torch.long, device="cuda") | |
| mask = create_causal_mask(x, num_heads=4) | |
| assert mask.device.type == "cuda" | |
| def test_causal_mask_rejects_invalid_inputs(): | |
| with pytest.raises(ValueError): | |
| create_causal_mask(torch.zeros(2, 0, dtype=torch.long), num_heads=2) | |
| with pytest.raises(ValueError): | |
| create_causal_mask(torch.zeros(2, 3, dtype=torch.long), num_heads=0) | |