@@ -183,3 +183,227 @@ def test_invalid_layernorm_pattern():
183183
184184 # Verify no LayerNorm transformation occurred
185185 assert not any (node .op_type == "LayerNormalization" for node in sanitizer .model .graph .node )
186+
187+
188+ def test_convert_fp64_initializers ():
189+ """Test conversion of FP64 initializers to FP32."""
190+ # Create a model with FP64 initializers
191+ x = helper .make_tensor_value_info ("X" , TensorProto .FLOAT , [2 , 3 ])
192+ y = helper .make_tensor_value_info ("Y" , TensorProto .FLOAT , [2 , 3 ])
193+
194+ # Create FP64 initializers
195+ fp64_weights = np .array ([[1.5 , 2.5 , 3.5 ], [4.5 , 5.5 , 6.5 ]], dtype = np .float64 )
196+ fp64_bias = np .array ([0.1 , 0.2 , 0.3 ], dtype = np .float64 )
197+ fp32_weights = np .array ([[1.0 , 2.0 ], [3.0 , 4.0 ]], dtype = np .float32 )
198+
199+ initializers = [
200+ numpy_helper .from_array (fp64_weights , name = "fp64_weights" ),
201+ numpy_helper .from_array (fp64_bias , name = "fp64_bias" ),
202+ numpy_helper .from_array (fp32_weights , name = "fp32_weights" ),
203+ ]
204+
205+ # Verify the FP64 initializers have correct data type
206+ assert initializers [0 ].data_type == TensorProto .DOUBLE
207+ assert initializers [1 ].data_type == TensorProto .DOUBLE
208+ assert initializers [2 ].data_type == TensorProto .FLOAT
209+
210+ add_node = helper .make_node ("Add" , ["X" , "fp64_weights" ], ["Y" ])
211+
212+ graph = helper .make_graph (
213+ nodes = [add_node ], name = "fp64_test" , inputs = [x ], outputs = [y ], initializer = initializers
214+ )
215+
216+ model = helper .make_model (graph )
217+ sanitizer = GraphSanitizer (model )
218+
219+ # Test the conversion
220+ result = sanitizer ._convert_fp64_initializers ()
221+ assert result is True
222+
223+ # Verify all initializers are now FP32
224+ for init in sanitizer .model .graph .initializer :
225+ if init .name in ["fp64_weights" , "fp64_bias" ]:
226+ assert init .data_type == TensorProto .FLOAT
227+ # Verify data integrity
228+ converted_data = numpy_helper .to_array (init )
229+ assert converted_data .dtype == np .float32
230+ elif init .name == "fp32_weights" :
231+ assert init .data_type == TensorProto .FLOAT
232+
233+
234+ def test_convert_fp64_io_types ():
235+ """Test conversion of FP64 input/output types to FP32."""
236+ # Create inputs and outputs with FP64 types
237+ x_fp64 = helper .make_tensor_value_info ("X_fp64" , TensorProto .DOUBLE , [2 , 3 ])
238+ y_fp64 = helper .make_tensor_value_info ("Y_fp64" , TensorProto .DOUBLE , [2 , 3 ])
239+ x_fp32 = helper .make_tensor_value_info ("X_fp32" , TensorProto .FLOAT , [2 , 3 ])
240+
241+ # Create value_info with FP64 type
242+ value_info_fp64 = helper .make_tensor_value_info ("intermediate" , TensorProto .DOUBLE , [2 , 3 ])
243+ value_info_fp32 = helper .make_tensor_value_info ("intermediate2" , TensorProto .FLOAT , [2 , 3 ])
244+
245+ add_node = helper .make_node ("Add" , ["X_fp64" , "X_fp32" ], ["Y_fp64" ])
246+
247+ graph = helper .make_graph (
248+ nodes = [add_node ],
249+ name = "fp64_io_test" ,
250+ inputs = [x_fp64 , x_fp32 ],
251+ outputs = [y_fp64 ],
252+ value_info = [value_info_fp64 , value_info_fp32 ],
253+ )
254+
255+ model = helper .make_model (graph )
256+ sanitizer = GraphSanitizer (model )
257+
258+ # Test the conversion
259+ result = sanitizer ._convert_fp64_io_types ()
260+ assert result is True
261+
262+ # Verify inputs are converted
263+ assert sanitizer .model .graph .input [0 ].type .tensor_type .elem_type == TensorProto .FLOAT
264+ assert sanitizer .model .graph .input [1 ].type .tensor_type .elem_type == TensorProto .FLOAT
265+
266+ # Verify outputs are converted
267+ assert sanitizer .model .graph .output [0 ].type .tensor_type .elem_type == TensorProto .FLOAT
268+
269+ # Verify value_info are converted
270+ for vi in sanitizer .model .graph .value_info :
271+ assert vi .type .tensor_type .elem_type == TensorProto .FLOAT
272+
273+
274+ def test_convert_fp64_nodes ():
275+ """Test conversion of specific node types from FP64 to FP32."""
276+ x = helper .make_tensor_value_info ("X" , TensorProto .FLOAT , [2 , 3 ])
277+ y = helper .make_tensor_value_info ("Y" , TensorProto .FLOAT , [2 , 3 ])
278+
279+ # Create FP64 constant tensor for ConstantOfShape and Constant nodes
280+ fp64_value = numpy_helper .from_array (np .array ([1.5 ], dtype = np .float64 ))
281+ fp64_shape_value = numpy_helper .from_array (np .array ([2.5 ], dtype = np .float64 ))
282+
283+ # Create nodes that use FP64
284+ cast_node = helper .make_node ("Cast" , ["X" ], ["cast_out" ], to = TensorProto .DOUBLE )
285+ constant_node = helper .make_node ("Constant" , [], ["const_out" ], value = fp64_value )
286+ constant_shape_node = helper .make_node (
287+ "ConstantOfShape" , ["shape" ], ["shape_out" ], value = fp64_shape_value
288+ )
289+ add_node = helper .make_node ("Add" , ["cast_out" , "const_out" ], ["Y" ])
290+
291+ # Shape input for ConstantOfShape
292+ shape_init = numpy_helper .from_array (np .array ([2 , 3 ], dtype = np .int64 ), name = "shape" )
293+
294+ graph = helper .make_graph (
295+ nodes = [cast_node , constant_node , constant_shape_node , add_node ],
296+ name = "fp64_nodes_test" ,
297+ inputs = [x ],
298+ outputs = [y ],
299+ initializer = [shape_init ],
300+ )
301+
302+ model = helper .make_model (graph )
303+ sanitizer = GraphSanitizer (model )
304+
305+ # Test the conversion
306+ result = sanitizer ._convert_fp64_nodes ()
307+ assert result is True
308+
309+ # Verify Cast node is converted
310+ cast_nodes = [n for n in sanitizer .model .graph .node if n .op_type == "Cast" ]
311+ assert len (cast_nodes ) == 1
312+ cast_attr = next (attr for attr in cast_nodes [0 ].attribute if attr .name == "to" )
313+ assert cast_attr .i == TensorProto .FLOAT
314+
315+ # Verify Constant node is converted
316+ constant_nodes = [n for n in sanitizer .model .graph .node if n .op_type == "Constant" ]
317+ assert len (constant_nodes ) == 1
318+ const_attr = next (attr for attr in constant_nodes [0 ].attribute if attr .name == "value" )
319+ assert const_attr .t .data_type == TensorProto .FLOAT
320+
321+ # Verify ConstantOfShape node is converted
322+ const_shape_nodes = [n for n in sanitizer .model .graph .node if n .op_type == "ConstantOfShape" ]
323+ assert len (const_shape_nodes ) == 1
324+ shape_attr = next (attr for attr in const_shape_nodes [0 ].attribute if attr .name == "value" )
325+ assert shape_attr .t .data_type == TensorProto .FLOAT
326+
327+
328+ def test_convert_fp64_to_fp32_integration ():
329+ """Test the main convert_fp64_to_fp32 method with mixed FP64/FP32 content."""
330+ # Create a model with mixed FP64 and FP32 content
331+ x_fp64 = helper .make_tensor_value_info ("X" , TensorProto .DOUBLE , [2 , 3 ])
332+ y_fp32 = helper .make_tensor_value_info ("Y" , TensorProto .FLOAT , [2 , 3 ])
333+
334+ # FP64 initializer
335+ fp64_weights = numpy_helper .from_array (
336+ np .array ([[1.5 , 2.5 ], [3.5 , 4.5 ]], dtype = np .float64 ), name = "weights"
337+ )
338+
339+ # FP64 constant value
340+ fp64_const_value = numpy_helper .from_array (np .array ([0.5 ], dtype = np .float64 ))
341+
342+ # Create nodes
343+ cast_node = helper .make_node ("Cast" , ["X" ], ["cast_out" ], to = TensorProto .DOUBLE )
344+ constant_node = helper .make_node ("Constant" , [], ["const_out" ], value = fp64_const_value )
345+ add_node = helper .make_node ("Add" , ["cast_out" , "const_out" ], ["Y" ])
346+
347+ graph = helper .make_graph (
348+ nodes = [cast_node , constant_node , add_node ],
349+ name = "mixed_fp64_test" ,
350+ inputs = [x_fp64 ],
351+ outputs = [y_fp32 ],
352+ initializer = [fp64_weights ],
353+ )
354+
355+ model = helper .make_model (graph )
356+ sanitizer = GraphSanitizer (model )
357+
358+ # Test the main conversion method
359+ sanitizer .convert_fp64_to_fp32 ()
360+
361+ # Verify all FP64 content has been converted
362+ # Check input types
363+ assert sanitizer .model .graph .input [0 ].type .tensor_type .elem_type == TensorProto .FLOAT
364+
365+ # Check initializers
366+ for init in sanitizer .model .graph .initializer :
367+ assert init .data_type == TensorProto .FLOAT
368+
369+ # Check Cast node
370+ cast_nodes = [n for n in sanitizer .model .graph .node if n .op_type == "Cast" ]
371+ cast_attr = next (attr for attr in cast_nodes [0 ].attribute if attr .name == "to" )
372+ assert cast_attr .i == TensorProto .FLOAT
373+
374+ # Check Constant node
375+ constant_nodes = [n for n in sanitizer .model .graph .node if n .op_type == "Constant" ]
376+ const_attr = next (attr for attr in constant_nodes [0 ].attribute if attr .name == "value" )
377+ assert const_attr .t .data_type == TensorProto .FLOAT
378+
379+
380+ def test_convert_fp64_no_changes_needed ():
381+ """Test that conversion methods return False when no FP64 content exists."""
382+ # Create a model with only FP32 content
383+ x = helper .make_tensor_value_info ("X" , TensorProto .FLOAT , [2 , 3 ])
384+ y = helper .make_tensor_value_info ("Y" , TensorProto .FLOAT , [2 , 3 ])
385+
386+ fp32_weights = numpy_helper .from_array (
387+ np .array ([[1.0 , 2.0 ], [3.0 , 4.0 ]], dtype = np .float32 ), name = "weights"
388+ )
389+ fp32_const_value = numpy_helper .from_array (np .array ([0.5 ], dtype = np .float32 ))
390+
391+ cast_node = helper .make_node ("Cast" , ["X" ], ["cast_out" ], to = TensorProto .FLOAT )
392+ constant_node = helper .make_node ("Constant" , [], ["const_out" ], value = fp32_const_value )
393+ add_node = helper .make_node ("Add" , ["cast_out" , "const_out" ], ["Y" ])
394+
395+ graph = helper .make_graph (
396+ nodes = [cast_node , constant_node , add_node ],
397+ name = "fp32_only_test" ,
398+ inputs = [x ],
399+ outputs = [y ],
400+ initializer = [fp32_weights ],
401+ )
402+
403+ model = helper .make_model (graph )
404+ sanitizer = GraphSanitizer (model )
405+
406+ # Test that no conversions are needed
407+ assert sanitizer ._convert_fp64_initializers () is False
408+ assert sanitizer ._convert_fp64_io_types () is False
409+ assert sanitizer ._convert_fp64_nodes () is False
0 commit comments