def test_iO16_default_fp16_io_with_multiple_outputs(
        self, float32_two_output_model, int32_float32_two_output_model
    ):
        """
        For the multiple outputs model, the converter only set the default dtype to  fp16 for
        outputs that satisfy
        1. dtype is None
        2. inferred dtype is fp32
        """
        # Case 1: first output is set to fp32
        mlmodel = ct.convert(
            float32_two_output_model,
            inputs=[ct.TensorType(shape=(10, 20))],
            outputs=[ct.TensorType(dtype=np.float32), ct.TensorType(dtype=None)],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="fp16")
        assert_output_dtype(mlmodel, expected_type_str="fp32", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["relu", "clip", "cast"])

        # Case 2: second output is set to fp32
        mlmodel = ct.convert(
            float32_two_output_model,
            inputs=[ct.TensorType(shape=(10, 20))],
            outputs=[ct.TensorType(dtype=None), ct.TensorType(dtype=np.float32)],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="fp16")
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp32", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["relu", "clip", "cast"])

        # Case 3: both outputs are set to fp32
        mlmodel = ct.convert(
            float32_two_output_model,
            inputs=[ct.TensorType(shape=(10, 20))],
            outputs=[ct.TensorType(dtype=np.float32), ct.TensorType(dtype=np.float32)],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="fp16")
        assert_output_dtype(mlmodel, expected_type_str="fp32", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp32", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["relu", "clip", "cast", "cast"])

        # Case 4: both outputs are not set
        mlmodel = ct.convert(
            float32_two_output_model,
            inputs=[ct.TensorType(shape=(10, 20))],
            outputs=[ct.TensorType(dtype=None), ct.TensorType(dtype=None)],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="fp16")
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["relu", "clip"])

        # Case 5: outputs is not provided at all
        mlmodel = ct.convert(
            float32_two_output_model,
            inputs=[ct.TensorType(shape=(10, 20))],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="fp16")
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["relu", "clip"])

        # Case 6: int32 and fp32 output. The fp32 defaults to fp32 while the int32 one remains unchanged.
        mlmodel = ct.convert(
            int32_float32_two_output_model,
            inputs=[
                ct.TensorType(shape=(10, 20), dtype=np.int32),
                ct.TensorType(shape=(10, 20), dtype=np.float32),
            ],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_input_dtype(mlmodel, expected_type_str="fp32", index=1)
        assert_output_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["add", "cast", "add"])

        # Case 7: int32 and fp32 output. The fp32 defaults to fp32 while the int32 one remains unchanged.
        mlmodel = ct.convert(
            int32_float32_two_output_model,
            inputs=[
                ct.TensorType(shape=(10, 20), dtype=np.int32),
                ct.TensorType(shape=(10, 20)),
            ],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_input_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_output_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["add", "add"])

        # Case 8: int32 and fp32 output. The fp32 defaults to fp32 while the int32 one remains unchanged.
        mlmodel = ct.convert(
            int32_float32_two_output_model,
            inputs=[
                ct.TensorType(shape=(10, 20), dtype=np.int32),
                ct.TensorType(shape=(10, 20)),
            ],
            outputs=[
                ct.TensorType(name="out1"),
                ct.TensorType(name="out2"),
            ],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_input_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_output_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_output_dtype(mlmodel, expected_type_str="fp16", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["add", "add"])

        # Case 9: two int32 outputs. Nothing changed.
        mlmodel = ct.convert(
            int32_float32_two_output_model,
            inputs=[
                ct.TensorType(shape=(10, 20), dtype=np.int32),
                ct.TensorType(shape=(10, 20), dtype=np.int32),
            ],
            minimum_deployment_target=ct.target.iOS16,
        )
        assert_input_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_input_dtype(mlmodel, expected_type_str="int32", index=1)
        assert_output_dtype(mlmodel, expected_type_str="int32", index=0)
        assert_output_dtype(mlmodel, expected_type_str="int32", index=1)
        assert_ops_in_mil_program(mlmodel, expected_op_list=["add", "add"])

        For the multiple outputs model, the converter only set the default dtype to  fp16 for
        outputs that satisfy
        1. dtype is None
        2. inferred dtype is fp32
        
coremltools/converters/mil/frontend/torch/test/test_torch_conversion_api.py