async def find_failing_ops(
        self,
        inputs: Tuple[torch.tensor],
        compare_outputs: Callable[[proto.MIL_pb2.Operation, np.array, np.array], bool],
        output_nodes: Optional[List[torch.fx.Node]] = None,
    ) -> Iterable[torch.fx.Node]:
        """
        Identifies operations that produce different outputs in the reference and target models.

        This method compares the outputs of the source and target models for specified operations,
        identifying those that fail the comparison criteria.

        Parameters
        ----------
        inputs: Tuple[torch.tensor]
            Input data for the model.

        compare_outputs : Callable[[proto.MIL_pb2.Operation, np.array, np.array], bool])
            A function to compare outputs of an operation between the two models.

        outputs: Optional[List[torch.fx.Node]]
            Specific outputs to compare. If None, all model outputs are compared. Defaults to None.

        Notes
        -----
        - The method uses a breadth-first search strategy to traverse the operation graph.
        - An operation is considered a failure source if it fails comparison while its direct inputs do not.

        Returns
        -------
        List[proto.MIL_pb2.Operation]
            A list of operations that failed the comparison.

        Examples
        --------

        .. sourcecode:: python
        
            class Model(torch.nn.Module):
                def forward(self, x, y):
                    return x + y


            model = Model()
            input1 = torch.full((1, 10), 1, dtype=torch.float)
            input2 = torch.full((1, 10), 2, dtype=torch.float)
            inputs = (input1, input2)
            exported_program = torch.export.export(model, inputs)

            comparator = (
                coremltools.models.ml_program.experimental.torch.TorchExportMLModelComparator(
                    model=exported_program,
                    inputs=[
                        coremltools.TensorType(name="x", shape=inputs[0].shape, dtype=np.float16),
                        coremltools.TensorType(name="y", shape=inputs[1].shape, dtype=np.float16),
                    ],
                    minimum_deployment_target=coremltools.target.iOS16,
                    compute_units=coremltools.ComputeUnit.ALL,
                )
            )


            def compare_outputs(op, reference_output, target_output):
                return np.allclose(reference_output, target_output, atol=0.01)


            ops = await comparator.find_failing_ops(inputs=inputs, compare_outputs=compare_outputs)
        """
        if output_nodes is None:
            output_nodes = []
            for node in self.source_model.graph.nodes:
                if node.op == "output":
                    output_nodes.append(node)

        source_outputs = self._source_model_inspector.run(inputs=inputs)
        self._source_outputs = {
            node: value if isinstance(value, (list, tuple)) else [value]
            for node, value in source_outputs.items()
        }

        target_inputs = {}
        input_names = _get_model_input_names(self.target_model, "main")
        for name, input in zip(input_names, inputs):
            target_inputs[name] = input.detach().cpu().numpy()

        queue = deque(output_nodes)
        result = set()
        statuses = {}

        with tqdm.tqdm(
            total=len(self.source_model.graph.nodes), desc="\033[1mAnalyzing nodes...\033[0m"
        ) as pbar:
            while len(queue) > 0:
                node = queue[0]
                status = statuses.get(node, None)
                if status is not None:
                    if self._is_failure_source(
                        node=node,
                        statuses=statuses,
                    ):
                        """
                        The node did not pass comparison check, but its dependencies did.
                        This suggests that the failure likely comes from this node.
                        """
                        result.add(node)

                    queue.popleft()
                else:
                    status = await self._process_node(
                        node=node,
                        target_inputs=target_inputs,
                        queue=queue,
                        compare_outputs=compare_outputs,
                    )

                    pbar.set_description(
                        desc=f"\033[1mAnalyzed node: {node.name}, type: {node.op}\033[0m"
                    )
                    pbar.update(n=1)
                statuses[node] = status

            self._visited = set()
            self._reference_outputs = {}
            self._target_outputs = {}

        return list(result)

        Identifies operations that produce different outputs in the reference and target models.

        This method compares the outputs of the source and target models for specified operations,
        identifying those that fail the comparison criteria.

        Parameters
        ----------
        inputs: Tuple[torch.tensor]
            Input data for the model.

        compare_outputs : Callable[[proto.MIL_pb2.Operation, np.array, np.array], bool])
            A function to compare outputs of an operation between the two models.

        outputs: Optional[List[torch.fx.Node]]
            Specific outputs to compare. If None, all model outputs are compared. Defaults to None.

        Notes
        -----
        - The method uses a breadth-first search strategy to traverse the operation graph.
        - An operation is considered a failure source if it fails comparison while its direct inputs do not.

        Returns
        -------
        List[proto.MIL_pb2.Operation]
            A list of operations that failed the comparison.

        Examples
        --------

        .. sourcecode:: python
        
            class Model(torch.nn.Module):
                def forward(self, x, y):
                    return x + y


            model = Model()
            input1 = torch.full((1, 10), 1, dtype=torch.float)
            input2 = torch.full((1, 10), 2, dtype=torch.float)
            inputs = (input1, input2)
            exported_program = torch.export.export(model, inputs)

            comparator = (
                coremltools.models.ml_program.experimental.torch.TorchExportMLModelComparator(
                    model=exported_program,
                    inputs=[
                        coremltools.TensorType(name="x", shape=inputs[0].shape, dtype=np.float16),
                        coremltools.TensorType(name="y", shape=inputs[1].shape, dtype=np.float16),
                    ],
                    minimum_deployment_target=coremltools.target.iOS16,
                    compute_units=coremltools.ComputeUnit.ALL,
                )
            )


            def compare_outputs(op, reference_output, target_output):
                return np.allclose(reference_output, target_output, atol=0.01)


            ops = await comparator.find_failing_ops(inputs=inputs, compare_outputs=compare_outputs)
        
coremltools/models/ml_program/experimental/torch/debugging_utils.py