Skip to content

Commit ad90230

Browse files
Adding more export tests (#13410) (#13450)
* Tests for vLLMHFExporter and TensorRT lazy compiler * Adding sentencepiece tokenizer test for export * More unit tests for tensorrt_llm.py * more tensorrt_mm_exporter tests * Fix style * Add header * Apply isort and black reformatting * new tests for lora and export utils * Apply isort and black reformatting * Fix some of the codeql issues * Add header * Marking tests as GPU only --------- Signed-off-by: Onur Yilmaz <[email protected]> Signed-off-by: oyilmaz-nvidia <[email protected]> Co-authored-by: Onur Yilmaz <[email protected]> Co-authored-by: oyilmaz-nvidia <[email protected]>
1 parent f83213f commit ad90230

File tree

8 files changed

+1168
-0
lines changed

8 files changed

+1168
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
import os
17+
import tempfile
18+
import unittest
19+
from unittest.mock import MagicMock, patch
20+
21+
import pytest
22+
import torch
23+
24+
25+
@pytest.mark.run_only_on('GPU')
26+
class TestBuild(unittest.TestCase):
27+
28+
@pytest.mark.run_only_on('GPU')
29+
def setUp(self):
30+
self.temp_dir = tempfile.mkdtemp()
31+
self.mock_config = {
32+
"mm_cfg": {
33+
"vision_encoder": {
34+
"from_pretrained": "test_model",
35+
"hidden_size": 768,
36+
},
37+
"mm_mlp_adapter_type": "linear",
38+
"hidden_size": 4096,
39+
}
40+
}
41+
self.mock_weights = {
42+
"model.embedding.word_embeddings.adapter_layer.mm_projector_adapter.mm_projector.weight": torch.randn(
43+
4096, 768
44+
),
45+
"model.embedding.word_embeddings.adapter_layer.mm_projector_adapter.mm_projector.bias": torch.randn(4096),
46+
}
47+
48+
@pytest.mark.run_only_on('GPU')
49+
def tearDown(self):
50+
# Clean up temporary directory
51+
if os.path.exists(self.temp_dir):
52+
for root, dirs, files in os.walk(self.temp_dir, topdown=False):
53+
for name in files:
54+
os.remove(os.path.join(root, name))
55+
for name in dirs:
56+
os.rmdir(os.path.join(root, name))
57+
os.rmdir(self.temp_dir)
58+
59+
@pytest.mark.run_only_on('GPU')
60+
@patch('nemo.export.multimodal.build.TensorRTLLM')
61+
def test_build_trtllm_engine(self, mock_trtllm):
62+
# Test basic functionality
63+
mock_exporter = MagicMock()
64+
mock_trtllm.return_value = mock_exporter
65+
66+
from nemo.export.multimodal.build import build_trtllm_engine
67+
68+
build_trtllm_engine(
69+
model_dir=self.temp_dir,
70+
visual_checkpoint_path="test_path",
71+
model_type="neva",
72+
tensor_parallelism_size=1,
73+
max_input_len=256,
74+
max_output_len=256,
75+
max_batch_size=1,
76+
max_multimodal_len=1024,
77+
dtype="bfloat16",
78+
)
79+
80+
mock_exporter.export.assert_called_once()
81+
82+
@pytest.mark.run_only_on('GPU')
83+
@patch('nemo.export.multimodal.build.MLLaMAForCausalLM')
84+
@patch('nemo.export.multimodal.build.build_trtllm')
85+
def test_build_mllama_trtllm_engine(self, mock_build_trtllm, mock_mllama):
86+
# Test basic functionality
87+
mock_model = MagicMock()
88+
mock_mllama.from_hugging_face.return_value = mock_model
89+
mock_build_trtllm.return_value = MagicMock()
90+
91+
from nemo.export.multimodal.build import build_mllama_trtllm_engine
92+
93+
build_mllama_trtllm_engine(
94+
model_dir=self.temp_dir,
95+
hf_model_path="test_path",
96+
tensor_parallelism_size=1,
97+
max_input_len=256,
98+
max_output_len=256,
99+
max_batch_size=1,
100+
max_multimodal_len=1024,
101+
dtype="bfloat16",
102+
)
103+
104+
mock_mllama.from_hugging_face.assert_called_once()
105+
mock_build_trtllm.assert_called_once()
106+
107+
108+
if __name__ == '__main__':
109+
unittest.main()
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
import os
17+
import tempfile
18+
import unittest
19+
from unittest.mock import MagicMock
20+
21+
import numpy as np
22+
import sentencepiece
23+
import torch
24+
25+
from nemo.export.sentencepiece_tokenizer import SentencePieceTokenizer
26+
27+
28+
class TestSentencePieceTokenizer(unittest.TestCase):
29+
@classmethod
30+
def setUpClass(cls):
31+
# Create a temporary directory for test files
32+
cls.test_dir = tempfile.mkdtemp()
33+
34+
# Create a simple sentencepiece model for testing
35+
with open(os.path.join(cls.test_dir, "test.txt"), "w") as f:
36+
f.write("Hello world\nThis is a test\n")
37+
38+
# Train a simple sentencepiece model
39+
sentencepiece.SentencePieceTrainer.Train(
40+
f'--input={os.path.join(cls.test_dir, "test.txt")} '
41+
f'--model_prefix={os.path.join(cls.test_dir, "test_model")} '
42+
'--vocab_size=55 --model_type=bpe'
43+
)
44+
45+
cls.model_path = os.path.join(cls.test_dir, "test_model.model")
46+
47+
@classmethod
48+
def tearDownClass(cls):
49+
# Clean up temporary files
50+
import shutil
51+
52+
shutil.rmtree(cls.test_dir)
53+
54+
def setUp(self):
55+
self.tokenizer = SentencePieceTokenizer(model_path=self.model_path)
56+
57+
def test_initialization(self):
58+
# Test initialization with model path
59+
tokenizer = SentencePieceTokenizer(model_path=self.model_path)
60+
self.assertIsNotNone(tokenizer.tokenizer)
61+
self.assertEqual(tokenizer.original_vocab_size, tokenizer.vocab_size)
62+
63+
# Test initialization with invalid model path
64+
with self.assertRaises(ValueError):
65+
SentencePieceTokenizer(model_path="nonexistent.model")
66+
67+
# Test initialization with both model_path and tokenizer
68+
mock_tokenizer = MagicMock()
69+
with self.assertRaises(ValueError):
70+
SentencePieceTokenizer(model_path=self.model_path, tokenizer=mock_tokenizer)
71+
72+
# Test initialization with neither model_path nor tokenizer
73+
with self.assertRaises(ValueError):
74+
SentencePieceTokenizer()
75+
76+
def test_text_to_tokens(self):
77+
text = "Hello world"
78+
tokens = self.tokenizer.text_to_tokens(text)
79+
self.assertIsInstance(tokens, list)
80+
self.assertTrue(all(isinstance(t, str) for t in tokens))
81+
82+
def test_encode(self):
83+
text = "Hello world"
84+
ids = self.tokenizer.encode(text)
85+
self.assertIsInstance(ids, list)
86+
self.assertTrue(all(isinstance(i, int) for i in ids))
87+
88+
def test_tokens_to_text(self):
89+
text = "Hello world"
90+
tokens = self.tokenizer.text_to_tokens(text)
91+
reconstructed_text = self.tokenizer.tokens_to_text(tokens)
92+
self.assertIsInstance(reconstructed_text, str)
93+
self.assertNotEqual(reconstructed_text, "") # Should not be empty
94+
95+
def test_batch_decode(self):
96+
text = "Hello world"
97+
ids = self.tokenizer.encode(text)
98+
99+
# Test with list
100+
decoded_text = self.tokenizer.batch_decode(ids)
101+
self.assertIsInstance(decoded_text, str)
102+
103+
# Test with numpy array
104+
ids_np = np.array(ids)
105+
decoded_text_np = self.tokenizer.batch_decode(ids_np)
106+
self.assertIsInstance(decoded_text_np, str)
107+
108+
# Test with torch tensor
109+
ids_torch = torch.tensor(ids)
110+
decoded_text_torch = self.tokenizer.batch_decode(ids_torch)
111+
self.assertIsInstance(decoded_text_torch, str)
112+
113+
def test_token_to_id(self):
114+
text = "Hello"
115+
tokens = self.tokenizer.text_to_tokens(text)
116+
token_id = self.tokenizer.token_to_id(tokens[0])
117+
self.assertIsInstance(token_id, int)
118+
119+
def test_ids_to_tokens(self):
120+
text = "Hello world"
121+
ids = self.tokenizer.encode(text)
122+
tokens = self.tokenizer.ids_to_tokens(ids)
123+
self.assertIsInstance(tokens, list)
124+
self.assertTrue(all(isinstance(t, str) for t in tokens))
125+
126+
def test_tokens_to_ids(self):
127+
text = "Hello"
128+
tokens = self.tokenizer.text_to_tokens(text)
129+
ids = self.tokenizer.tokens_to_ids(tokens)
130+
self.assertIsInstance(ids, list)
131+
self.assertTrue(all(isinstance(i, int) for i in ids))
132+
133+
def test_legacy_mode(self):
134+
special_tokens = ["[PAD]", "[BOS]", "[EOS]"]
135+
tokenizer = SentencePieceTokenizer(model_path=self.model_path, special_tokens=special_tokens, legacy=True)
136+
137+
# Test adding special tokens
138+
self.assertGreater(tokenizer.vocab_size, tokenizer.original_vocab_size)
139+
140+
# Test special token encoding
141+
text = "Hello [PAD] world"
142+
tokens = tokenizer.text_to_tokens(text)
143+
self.assertIn("[PAD]", tokens)
144+
145+
# Test special token decoding
146+
ids = tokenizer.encode(text)
147+
decoded_text = tokenizer.batch_decode(ids)
148+
self.assertIn("[PAD]", decoded_text)
149+
150+
def test_properties(self):
151+
# Test pad_id property
152+
self.assertIsInstance(self.tokenizer.pad_id, int)
153+
154+
# Test bos_token_id property
155+
self.assertIsInstance(self.tokenizer.bos_token_id, int)
156+
157+
# Test eos_token_id property
158+
self.assertIsInstance(self.tokenizer.eos_token_id, int)
159+
160+
# Test unk_id property
161+
self.assertIsInstance(self.tokenizer.unk_id, int)
162+
163+
def test_vocab_property(self):
164+
vocab = self.tokenizer.vocab
165+
self.assertIsInstance(vocab, list)
166+
self.assertTrue(all(isinstance(t, str) for t in vocab))
167+
168+
def test_convert_ids_to_tokens(self):
169+
text = "Hello world"
170+
ids = self.tokenizer.encode(text)
171+
tokens = self.tokenizer.convert_ids_to_tokens(ids)
172+
self.assertIsInstance(tokens, list)
173+
self.assertTrue(all(isinstance(t, str) for t in tokens))
174+
175+
def test_convert_tokens_to_string(self):
176+
text = "Hello world"
177+
tokens = self.tokenizer.text_to_tokens(text)
178+
string = self.tokenizer.convert_tokens_to_string(tokens)
179+
self.assertIsInstance(string, str)
180+
181+
def test_len(self):
182+
self.assertEqual(len(self.tokenizer), self.tokenizer.vocab_size)
183+
184+
def test_is_fast(self):
185+
self.assertTrue(self.tokenizer.is_fast)
186+
187+
def test_get_added_vocab(self):
188+
self.assertIsNone(self.tokenizer.get_added_vocab())
189+
190+
191+
if __name__ == '__main__':
192+
unittest.main()

0 commit comments

Comments
 (0)