def compile_model(
    tensor: Union[Tensor, List[Tensor]],
    target: backend.target.Target,
    workdir: str,
    test_name: str,
    profile_devs: List[int] = None,
    dynamic_profiling_strategy: DynamicProfileStrategy = DynamicProfileStrategy.MAX,
    dll_name: str = "test.so",
    num_runtimes: int = AIT_DEFAULT_NUM_RUNTIMES,
    profile_dir: str = None,
    constants: Optional[Dict[str, TorchTensor]] = None,
    allocator_kind: Optional[AITemplateAllocatorKind] = None,
    debug_settings: AITDebugSettings = _DEBUG_SETTINGS,
    do_optimize_graph: bool = True,
    profile_timeout: int = 500,
) -> Model:
    """Compiles a model and generates a .so file.

    Parameters
    ----------
    tensor : Union[Tensor, List[Tensor]]
        An output Tensor, or a list of output Tensors.
        The compiled module will preserve the ordering of the outputs in its
        internal ordering.
    target : Target
        A compilation target. See comments for Target.
    workdir : str
        A workdir to store profiling and execution source codes, as well as the result .so file.
    test_name : str
        Name of the test. Used as the name of the subdir which stores the generated .so file.
    profile_devs : List[int], optional
        A list of profiling devices, by default device 0 will be used.
    dynamic_profiling_strategy: DynamicProfileStrategy, optional
        A DynamicProfileStrategy used for profiling. See comments for DynamicProfileStrategy.
    dll_name: str
        The output .so name.
    num_runtimes: int
        How many runtimes should be stored in the internal pool. This
        determines how many inferences can happen concurrently. By
        default, set to 1. Must be positive.
    profile_dir: str
        The base dir to generate profiling source codes. By default, workdir/test_name
    constants: Dict[str, TorchTensor], optional
        User-provided constants to bind to the graph. The constants can be folded and packaged into
        the final *.so.
    allocator_kind: AITemplateAllocatorKind, optional
        The GPU allocator to use. If none is specified, use the default allocator.
    debug_settings: AITDebugSettings
        specify debug settings such as where to dump AITemplate model Python file, etc.
    do_optimize_graph: bool
        Apply full list of graph optimizations. Default: True

    Returns
    -------
    Model
        A model object.
    """
    if constants is None:
        constants = {}

    recompile = os.getenv("AIT_RECOMPILE", "1")
    graph = None
    os.makedirs(workdir, exist_ok=True)  # explicitly ensure workdir exists
    # Super important: we cannot have commas in the test name.
    # We want to add a -Iworkdir/test_name flag to nvcc, but
    # if the name has a comma in it, it will be parsed as two
    # arguments (even if we put quotes around it)!!
    test_name = test_name.replace(",", "_")
    test_dir = os.path.join(workdir, test_name)
    _LOGGER.info(f"Start to compile AIT model. {test_dir=}")
    if profile_dir is None:
        profile_dir = workdir

    if int(recompile) == 1:
        os.makedirs(test_dir, exist_ok=True)
        with target:
            reset_name_counters()
            graph = compiler.transform.toposort(tensor)
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "toposort")

            output_tensors = [tensor] if isinstance(tensor, Tensor) else tensor
            _validate_tensor_args(graph, output_tensors)

            compiler.transform.bind_constants(graph, constants)
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "bind_constants")

            compiler.transform.remove_unused_ops(graph)
            graph_utils.dump_graph_debug_str_to_file(
                graph, test_dir, "remove_unused_ops"
            )

            compiler.transform.remove_no_ops(graph)
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "remove_no_ops")

            compiler.transform.name_graph(graph)
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "name_graph")

            if debug_settings.dump_ait_to_py:
                dump_program(tensor, debug_settings.dump_ait_to_py)

            compiler.transform.dedup_symbolic_name(graph)
            graph_utils.dump_graph_debug_str_to_file(
                graph, test_dir, "dedup_symbolic_name"
            )

            compiler.transform.mark_param_tensor(graph)
            graph_utils.dump_graph_debug_str_to_file(
                graph, test_dir, "mark_param_tensor"
            )

            start_t = datetime.now()
            graph = compiler.transform.optimize_graph(
                graph, test_dir, optimize=do_optimize_graph
            )
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "optimize_graph")
            _LOGGER.info(f"optimized graph elapsed time: {elapsed_dt_sec(start_t)}")

            compiler.transform.mark_special_views(graph)
            compiler.transform.refine_graph(graph)
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "refine_graph")

            if profile_devs is None:
                device_env = os.getenv(target.dev_select_flag(), None)
                if device_env is None:
                    profile_devs = [0]
                else:
                    profile_devs = device_env.split(",")
            compiler.transform.profile(
                graph,
                profile_dir,
                profile_devs,
                dynamic_profiling_strategy,
                profile_timeout,
            )
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "profile")

            start_t = datetime.now()
            constant_folding_workdir = os.path.join(workdir, test_name)
            os.makedirs(constant_folding_workdir, exist_ok=True)
            (
                graph,
                constant_folding_file_pairs,
                constant_folding_inputs,
            ) = compiler.transform.constant_folding(graph, workdir, test_name)
            graph_utils.dump_graph_debug_str_to_file(
                graph, test_dir, "constant_folding"
            )
            _LOGGER.info(f"folded constants elapsed time: {elapsed_dt_sec(start_t)}")

            compiler.transform.dedup_symbolic_name(graph)
            graph_utils.dump_graph_debug_str_to_file(
                graph, test_dir, "dedup_symbolic_name"
            )

            (
                max_blob,
                max_constant_blob,
                workspace,
            ) = compiler.transform.memory_planning(graph)
            _verify_outputs_still_in_graph(graph, output_tensors)
            _mark_isolated_int_vars(graph)
            graph_utils.dump_graph_debug_str_to_file(graph, test_dir, "memory_planning")

            file_pairs = backend.codegen.gen_function_src(graph, workdir, test_name)
            file_pairs.extend(constant_folding_file_pairs)

            # It's possible that the original output tensor has been replaced with a new tensor.
            # Preserve original output tensors' orders but use the new tensors.
            new_output_tensor_dict = {
                tensor._attrs["name"]: tensor
                for tensor in graph
                if tensor._attrs["is_output"]
            }
            output_tensors = [tensor] if isinstance(tensor, Tensor) else tensor
            output_tensors = [
                new_output_tensor_dict[tensor._attrs["name"]]
                for tensor in output_tensors
            ]

            main_pairs = backend.codegen.gen_library_src(
                graph,
                max_blob,
                max_constant_blob,
                workspace,
                workdir,
                output_tensors,
                test_name,
                additional_unbound_constants=constant_folding_inputs,
                debug_settings=debug_settings,
            )
            file_pairs.extend(main_pairs)

            start_t = datetime.now()
            compile_engine = backend.builder.get_compile_engine()
            compile_engine.make(
                file_pairs, dll_name, workdir, test_name, debug_settings
            )
            _LOGGER.info(
                f"compiled the final .so file elapsed time: {elapsed_dt_sec(start_t)}",
            )

    module = Model(
        os.path.join(workdir, test_name, dll_name), num_runtimes, allocator_kind
    )
    module.debug_sorted_graph = graph
    return module
Compiles a model and generates a .so file.

Parameters
----------
tensor : Union[Tensor, List[Tensor]]
An output Tensor, or a list of output Tensors.
The compiled module will preserve the ordering of the outputs in its
internal ordering.
target : Target
A compilation target. See comments for Target.
workdir : str
A workdir to store profiling and execution source codes, as well as the result .so file.
test_name : str
Name of the test. Used as the name of the subdir which stores the generated .so file.
profile_devs : List[int], optional
A list of profiling devices, by default device 0 will be used.
dynamic_profiling_strategy: DynamicProfileStrategy, optional
A DynamicProfileStrategy used for profiling. See comments for DynamicProfileStrategy.
dll_name: str
The output .so name.
num_runtimes: int
How many runtimes should be stored in the internal pool. This
determines how many inferences can happen concurrently. By
default, set to 1. Must be positive.
profile_dir: str
The base dir to generate profiling source codes. By default, workdir/test_name
constants: Dict[str, TorchTensor], optional
User-provided constants to bind to the graph. The constants can be folded and packaged into
the final *.so.
allocator_kind: AITemplateAllocatorKind, optional
The GPU allocator to use. If none is specified, use the default allocator.
debug_settings: AITDebugSettings
specify debug settings such as where to dump AITemplate model Python file, etc.
do_optimize_graph: bool
Apply full list of graph optimizations. Default: True

Returns
-------
Model
A model object.
python/aitemplate/compiler/compiler.py