66import os
77import unittest
88
9- import numpy as np
10-
11- from modelbuilder .ext_test_case import ExtTestCase
9+ from modelbuilder .ext_test_case import ExtTestCase , hide_stdout , long_test
1210
1311MODEL_NAME = "arnir0/Tiny-LLM"
1412
1513
1614class TestTrainedTinyLLM (ExtTestCase ):
17- def _common_part (self , precision , dtype , int4 = False ):
15+ def _common_part (self , precision , dtype , provider = "cpu" , int4 = False ):
1816 from transformers import AutoModelForCausalLM
17+
1918 from modelbuilder .builder import create_model
2019
2120 # Use 4 layers so that both rope (layers 0-2) and no-rope (layer 3)
2221 # code paths are exercised with the default no_rope_layer_interval=4.
2322 output_dir , cache_dir = self .get_dirs (
24- f"test_trained_tiny_llm_{ 'int4' if int4 else precision } _cpu " , clean = False
23+ f"test_trained_tiny_llm_{ 'int4' if int4 else precision } _ { provider } " , clean = False
2524 )
2625 onnx_path = os .path .join (output_dir , "model.onnx" )
2726 if not os .path .exists (onnx_path ):
@@ -30,7 +29,7 @@ def _common_part(self, precision, dtype, int4=False):
3029 model_name = MODEL_NAME ,
3130 input_path = "" ,
3231 precision = "int4" if int4 else precision ,
33- execution_provider = "cpu" ,
32+ execution_provider = provider ,
3433 output_dir = output_dir ,
3534 cache_dir = cache_dir ,
3635 )
@@ -44,22 +43,20 @@ def _common_part(self, precision, dtype, int4=False):
4443 model .eval ()
4544 return onnx_path , model
4645
47- def test_trained_tiny_llm_fp32_discrepancies_cpu (self ):
48- """
49- Convert arnir0/Tiny-LLM to an fp32 ONNX model targeting the CPU execution
50- provider. Only one hidden layer is materialised (via the
51- ``num_hidden_layers`` extra option) to keep the test fast.
52- The test verifies that:
53- * ``create_model`` completes without error.
54- * The expected ``model.onnx`` file is written to the output directory.
55- * The produced ONNX file passes ``onnx.checker.check_model``.
56- """
46+ def _dtype_from_precision (self , precision ):
5747 import torch
5848
59- precision , dtype , np_dtype = "fp32" , torch .float32 , np .float32
49+ dtype_map = {"fp32" : torch .float32 , "fp16" : torch .float16 , "bf16" : torch .bfloat16 }
50+ return dtype_map .get (precision , torch .float32 )
6051
61- onnx_path , model = self ._common_part (precision , dtype )
62- sess = self ._check_with_ort (onnx_path , cpu = True )
52+ def common_discrepancies (self , precision , provider ):
53+ import torch
54+
55+ dtype = self ._dtype_from_precision (precision )
56+ np_dtype = self .get_input_np_dtype (precision )
57+
58+ onnx_path , model = self ._common_part (precision , dtype , provider = provider )
59+ sess = self ._check_with_ort (onnx_path , cpu = provider == "cpu" )
6360 config = model .config
6461
6562 batch_size = 1
@@ -70,7 +67,7 @@ def test_trained_tiny_llm_fp32_discrepancies_cpu(self):
7067 batch_size = batch_size ,
7168 seq_len = seq_len ,
7269 np_dtype = np_dtype ,
73- provider = "cpu" ,
70+ provider = provider ,
7471 head_size = head_size ,
7572 num_hidden_layers = config .num_hidden_layers ,
7673 num_key_value_heads = config .num_key_value_heads ,
@@ -89,31 +86,16 @@ def test_trained_tiny_llm_fp32_discrepancies_cpu(self):
8986 precision = precision ,
9087 model_id = MODEL_NAME ,
9188 experiment = "forward" ,
92- provider = "cpu" ,
93- test = "test_trained_tiny_llm_fp32_discrepancies_cpu " ,
89+ provider = provider ,
90+ test = f"test_trained_tiny_llm_ { precision } _discrepancies_ { provider } " ,
9491 input_type = "text" ,
92+ kind = "trained" ,
9593 )
9694 )
9795 self .log_results (disc )
9896 self .assertLess (disc ["max_abs_err" ], 0.05 )
9997
100- def test_trained_tiny_llm_genai_generate_cpu (self ):
101- """
102- Compare ``transformers.generate`` with ``onnxruntime-genai`` generate
103- on the ``arnir0/Tiny-LLM`` trained model.
104-
105- The test converts the model to fp32 ONNX (CPU), then:
106-
107- * Runs ``transformers.generate(do_sample=False)`` on a short prompt.
108- * Runs ``onnxruntime_genai`` greedy generation on the same prompt and
109- ONNX model.
110-
111- Both backends use greedy (argmax) decoding, so the generated token
112- sequences must be bit-for-bit identical.
113-
114- The test is skipped automatically when ``onnxruntime-genai`` is not
115- installed.
116- """
98+ def common_genai_generate (self , precision , provider , int4 = False ):
11799 try :
118100 import onnxruntime_genai as og
119101 except ImportError :
@@ -124,9 +106,9 @@ def test_trained_tiny_llm_genai_generate_cpu(self):
124106 import torch
125107 from transformers import AutoTokenizer
126108
127- precision , dtype = "fp16" , torch . float16
109+ dtype = self . _dtype_from_precision ( precision )
128110
129- onnx_path , model = self ._common_part (precision , dtype )
111+ onnx_path , model = self ._common_part (precision , dtype , provider = provider , int4 = int4 )
130112
131113 genai_config_path = os .path .join (os .path .dirname (onnx_path ), "genai_config.json" )
132114 self .assertExists (genai_config_path )
@@ -174,102 +156,65 @@ def test_trained_tiny_llm_genai_generate_cpu(self):
174156
175157 # Greedy decoding is deterministic: both backends must produce the
176158 # exact same token sequence (prompt + all generated tokens).
159+ test_precision = "int4" if int4 else precision
177160 disc = self .first_token_diff (pt_tokens [start_sequence :], og_tokens )
178161 disc .update (
179162 dict (
180- precision = precision ,
163+ precision = test_precision ,
181164 model_id = MODEL_NAME ,
182165 experiment = "generate" ,
183- provider = "cpu" ,
184- test = "test_trained_tiny_llm_genai_generate_cpu " ,
166+ provider = provider ,
167+ test = f"test_trained_tiny_llm_genai_generate_ { test_precision } _ { provider } " ,
185168 expected_text = tokenizer .decode (
186169 pt_tokens [start_sequence :], skip_special_tokens = False
187170 ),
188171 genai_text = tokenizer .decode (og_tokens , skip_special_tokens = False ),
189172 input_type = "text" ,
173+ kind = "trained" ,
190174 )
191175 )
192176 self .log_results (disc )
193177 self .assertEqual (pt_tokens [start_sequence :], og_tokens )
194178
195- def test_trained_tiny_llm_genai_generate_int4_cpu (self ):
196- try :
197- import onnxruntime_genai as og
198- except ImportError :
199- raise unittest .SkipTest (
200- "onnxruntime-genai is not installed; skipping genai comparison test."
201- )
202-
203- import torch
204- from transformers import AutoTokenizer
205-
206- precision , dtype = "fp32" , torch .float32
207-
208- onnx_path , model = self ._common_part (precision , dtype , int4 = True )
209-
210- genai_config_path = os .path .join (os .path .dirname (onnx_path ), "genai_config.json" )
211- self .assertExists (genai_config_path )
212-
213- tokenizer = AutoTokenizer .from_pretrained (MODEL_NAME )
214- prompt = "Once upon a time"
215- max_new_tokens = 20
179+ @long_test ()
180+ @hide_stdout ()
181+ def test_trained_tiny_llm_fp32_discrepancies_cpu (self ):
182+ """
183+ Convert arnir0/Tiny-LLM to an fp32 ONNX model targeting the CPU execution
184+ provider. Only one hidden layer is materialised (via the
185+ ``num_hidden_layers`` extra option) to keep the test fast.
186+ The test verifies that:
187+ * ``create_model`` completes without error.
188+ * The expected ``model.onnx`` file is written to the output directory.
189+ * The produced ONNX file passes ``onnx.checker.check_model``.
190+ """
191+ self .common_discrepancies ("fp32" , "cpu" )
216192
217- # ------------------------------------------------------------------
218- # transformers greedy generation (reference)
219- # ------------------------------------------------------------------
220- inputs = tokenizer (prompt , return_tensors = "pt" )
221- start_sequence = inputs ["input_ids" ].shape [1 ]
222- inputs = inputs .to ("cpu" )
223- with torch .no_grad ():
224- pt_output = model .generate (
225- ** inputs ,
226- max_new_tokens = max_new_tokens ,
227- do_sample = False ,
228- pad_token_id = tokenizer .eos_token_id ,
229- )
230- pt_tokens = pt_output [0 ].tolist ()
193+ @long_test ()
194+ @hide_stdout ()
195+ def test_trained_tiny_llm_genai_generate_cpu (self ):
196+ """
197+ Compare ``transformers.generate`` with ``onnxruntime-genai`` generate
198+ on the ``arnir0/Tiny-LLM`` trained model.
231199
232- # ------------------------------------------------------------------
233- # onnxruntime-genai greedy generation
234- # ------------------------------------------------------------------
235- og_model = og .Model (os .path .dirname (onnx_path ))
200+ The test converts the model to fp16 ONNX (CPU), then:
236201
237- params = og .GeneratorParams (og_model )
238- params .set_search_options (
239- do_sample = False ,
240- max_length = inputs ["input_ids" ].shape [1 ] + max_new_tokens ,
241- temperature = 1.0 ,
242- top_k = 1 ,
243- )
202+ * Runs ``transformers.generate(do_sample=False)`` on a short prompt.
203+ * Runs ``onnxruntime_genai`` greedy generation on the same prompt and
204+ ONNX model.
244205
245- generator = og . Generator ( og_model , params )
246- generator . append_tokens ( inputs [ "input_ids" ])
206+ Both backends use greedy (argmax) decoding, so the generated token
207+ sequences must be bit-for-bit identical.
247208
248- og_tokens = []
249- while not generator .is_done ():
250- generator .generate_next_token ()
251- og_token = generator .get_next_tokens ()[0 ]
252- og_tokens .append (int (og_token ))
209+ The test is skipped automatically when ``onnxruntime-genai`` is not
210+ installed.
211+ """
212+ self .common_genai_generate ("fp16" , "cpu" )
253213
254- # Greedy decoding is deterministic: both backends must produce the
255- # exact same token sequence (prompt + all generated tokens).
256- disc = self .first_token_diff (pt_tokens [start_sequence :], og_tokens )
257- disc .update (
258- dict (
259- precision = "int4" ,
260- model_id = MODEL_NAME ,
261- experiment = "generate" ,
262- provider = "cpu" ,
263- test = "test_trained_tiny_llm_genai_generate_int4_cpu" ,
264- expected_text = tokenizer .decode (
265- pt_tokens [start_sequence :], skip_special_tokens = False
266- ),
267- genai_text = tokenizer .decode (og_tokens , skip_special_tokens = False ),
268- input_type = "text" ,
269- )
270- )
271- self .log_results (disc )
272- self .assertEqual (pt_tokens [start_sequence :], og_tokens )
214+ @long_test ()
215+ @hide_stdout ()
216+ def test_trained_tiny_llm_genai_generate_int4_cpu (self ):
217+ self .common_genai_generate ("fp32" , "cpu" , int4 = True )
273218
274219
275220if __name__ == "__main__" :
0 commit comments