| import functools |
| import subprocess |
| import sys |
| from contextlib import redirect_stdout |
| from io import StringIO |
| from pathlib import Path |
| from unittest import mock |
| from unittest.mock import Mock, call, ANY |
|
|
| import torch |
|
|
| wd = Path(__file__).parent.parent.absolute() |
|
|
|
|
| @functools.lru_cache(maxsize=1) |
| def load_generate_script(): |
| sys.path.append(str(wd)) |
|
|
| import generate |
|
|
| return generate |
|
|
|
|
| def test_generate(): |
| generate = load_generate_script() |
|
|
| from lit_llama.model import LLaMA, LLaMAConfig |
|
|
| T, C = 5, 3 |
| logits = torch.randn(T, C) |
| input_idx = torch.randint(10, size=(T,)) |
|
|
| config = LLaMAConfig(block_size=128, vocab_size=16, n_layer=1, n_head=4, n_embd=8) |
| model = LLaMA(config) |
| max_new_tokens = 20 |
|
|
| multinomial_results = [] |
| original_multinomial = torch.multinomial |
|
|
| def multinomial(*args, **kwargs): |
| out = original_multinomial(*args, **kwargs) |
| multinomial_results.append(out) |
| return out |
|
|
| with mock.patch("torch.multinomial", multinomial): |
| out = generate.generate(model, input_idx, max_new_tokens, max_seq_length=10, top_k=4) |
|
|
| assert out.size(0) == T + max_new_tokens |
| multinomial_results = torch.hstack(multinomial_results) |
| expected = torch.cat((input_idx, multinomial_results)) |
| assert out.shape == expected.shape |
| torch.testing.assert_close(out, expected) |
|
|
|
|
| @mock.patch("torch.cuda.is_bf16_supported", return_value=False) |
| def test_main(tmp_path, monkeypatch): |
| generate = load_generate_script() |
|
|
| checkpoint_path = tmp_path / "ckpt" |
| checkpoint_path.touch() |
| tokenizer_path = tmp_path / "tokenizer" |
| tokenizer_path.touch() |
|
|
| class FabricMock(Mock): |
| @property |
| def device(self): |
| return torch.device("cpu") |
|
|
| monkeypatch.setattr(generate.L, "Fabric", FabricMock) |
| model_mock = Mock() |
| monkeypatch.setattr(generate.LLaMA, "from_name", model_mock) |
| lookup_mock = Mock(return_value="1T") |
| monkeypatch.setattr(generate, "llama_model_lookup", lookup_mock) |
| load_mock = Mock() |
| load_mock.return_value = load_mock |
| load_mock.__enter__ = Mock() |
| load_mock.__exit__ = Mock() |
| monkeypatch.setattr(generate.torch, "load", load_mock) |
| monkeypatch.setattr(generate, "lazy_load", load_mock) |
| tokenizer_mock = Mock() |
| tokenizer_mock.return_value.encode.return_value = torch.tensor([[1, 2, 3]]) |
| tokenizer_mock.return_value.decode.return_value = "foo bar baz" |
| monkeypatch.setattr(generate, "Tokenizer", tokenizer_mock) |
| generate_mock = Mock() |
| generate_mock.return_value = torch.tensor([[3, 2, 1]]) |
| monkeypatch.setattr(generate, "generate", generate_mock) |
|
|
| num_samples = 2 |
| out = StringIO() |
| with redirect_stdout(out): |
| generate.main( |
| checkpoint_path=checkpoint_path, |
| tokenizer_path=tokenizer_path, |
| temperature=2.0, |
| top_k=2, |
| num_samples=num_samples, |
| ) |
|
|
| model_mock.assert_called_once_with("1T") |
| load_mock.assert_called_once_with(checkpoint_path) |
| tokenizer_mock.assert_called_once_with(tokenizer_path) |
| assert len(tokenizer_mock.return_value.decode.mock_calls) == num_samples |
| assert torch.allclose(tokenizer_mock.return_value.decode.call_args[0][0], generate_mock.return_value) |
| assert generate_mock.mock_calls == [call(ANY, ANY, 50, ANY, temperature=2.0, top_k=2)] * num_samples |
| |
| assert out.getvalue() == "foo bar baz\n" * num_samples |
|
|
|
|
| def test_cli(): |
| cli_path = wd / "generate.py" |
| output = subprocess.check_output([sys.executable, cli_path, "-h"]) |
| output = str(output.decode()) |
| assert "Generates text samples" in output |
|
|