Frequently Asked Questions
===========================================

Is ``depyf`` a general purpose Python bytecode decompiler?
---------------------------------------------------------------------------

This package is intended to understand the generated PyTorch bytecode, and does not aim to fully cover all the syntax of Python. For example, async operations like ``async/await`` are not supported.

All the bytecode generated by PyTorch when benchmarking timm and huggingface transformers are collected `here <https://github.com/thuml/learn_torch.compile>`_. We can make several observations:

- No while loops (no jump back instructions).
- try-except-finally only has try-finally.
- No complicated conditions like ``if a and b or c or (d and e)``.

Then, we can overfit the decompiler to work for the bytecode generated by PyTorch. How? Pure labor work. Implement all bytecode for all the supported Python versions, one by one. Yes, that's it.

How do I know the set of supported bytecode?
----------------------------------------------------

You can see the coverage report by simply running ``python python_coverage.py``. The script is located in the `repository <https://github.com/thuml/depyf/blob/master/python_coverage.py>`_.

What is the difference between ``depyf`` and ``TORCH_COMPILE_DEBUG``?
-----------------------------------------------------------------------

That's a very good question.

Let's take this simple example code from readme:

.. code-block:: python

    # test.py
    import torch
    from torch import _dynamo as torchdynamo
    from typing import List

    @torch.compile
    def toy_example(a, b):
        x = a / (torch.abs(a) + 1)
        if b.sum() < 0:
            b = b * -1
        return x * b

    def main():
        for _ in range(100):
            toy_example(torch.randn(10), torch.randn(10))

    if __name__ == "__main__":
        main()

When you run ``TORCH_COMPILE_DEBUG=1 python test.py``, you will get a directory named ``torch_compile_debug/run_2024_02_05_23_02_45_552124-pid_9520``. Inside the directory:

.. code-block:: text

    .
    ├── torchdynamo
    │   └── debug.log
    └── torchinductor
        ├── aot_model___0_debug.log
        ├── aot_model___10_debug.log
        ├── aot_model___11_debug.log
        ├── model__4_inference_10.1
        │   ├── fx_graph_readable.py
        │   ├── fx_graph_runnable.py
        │   ├── fx_graph_transformed.py
        │   ├── ir_post_fusion.txt
        │   ├── ir_pre_fusion.txt
        │   └── output_code.py
        ├── model__5_inference_11.2
        │   ├── fx_graph_readable.py
        │   ├── fx_graph_runnable.py
        │   ├── fx_graph_transformed.py
        │   ├── ir_post_fusion.txt
        │   ├── ir_pre_fusion.txt
        │   └── output_code.py
        └── model___9.0
            ├── fx_graph_readable.py
            ├── fx_graph_runnable.py
            ├── fx_graph_transformed.py
            ├── ir_post_fusion.txt
            ├── ir_pre_fusion.txt
            └── output_code.py

When you use ``depyf`` with the following code:

.. code-block:: python

    # test.py
    import torch
    from torch import _dynamo as torchdynamo
    from typing import List

    @torch.compile
    def toy_example(a, b):
        x = a / (torch.abs(a) + 1)
        if b.sum() < 0:
            b = b * -1
        return x * b

    def main():
        for _ in range(100):
            toy_example(torch.randn(10), torch.randn(10))

    if __name__ == "__main__":
        import depyf
        with depyf.prepare_debug("depyf_debug_dir"):
            main()

After running ``python test.py``, you get a directory ``depyf_debug_dir``, under which are these files:

.. code-block:: text

    .
    ├── __compiled_fn_0 AFTER POST GRAD 0.py
    ├── __compiled_fn_0 Captured Graph 0.py
    ├── __compiled_fn_0 Forward graph 0.py
    ├── __compiled_fn_0 kernel 0.py
    ├── __compiled_fn_3 AFTER POST GRAD 0.py
    ├── __compiled_fn_3 Captured Graph 0.py
    ├── __compiled_fn_3 Forward graph 0.py
    ├── __compiled_fn_3 kernel 0.py
    ├── __compiled_fn_4 AFTER POST GRAD 0.py
    ├── __compiled_fn_4 Captured Graph 0.py
    ├── __compiled_fn_4 Forward graph 0.py
    ├── __compiled_fn_4 kernel 0.py
    ├── __transformed_code_0_for_torch_dynamo_resume_in_toy_example_at_8.py
    ├── __transformed_code_0_for_toy_example.py
    ├── __transformed_code_1_for_torch_dynamo_resume_in_toy_example_at_8.py
    └── full_code_for_toy_example_0.py

So what's the difference?

1. When you use ``TORCH_COMPILE_DEBUG``, the ``torchdynamo/debug.log`` is long and difficult to understand. ``depyf`` helps to decompile the bytecode into readable source code in ``__transformed_code_xx.py`` file.
2. When you use ``TORCH_COMPILE_DEBUG``, the ``torchinductor/model__5_inference_11.2`` etc names are very difficult to understand. Users have to manually figure out which function inside ``torchdynamo/debug.log`` corresponds to which directory. Meanwhile, in ``depyf``, ``__compiled_fn_0`` and other functions have exactly the same names as they appear in ``torchdynamo/debug.log``.

In summary, ``depyf`` is a much more improved version of debugging information for ``torch.compile`` than ``TORCH_COMPILE_DEBUG``.
