    def _combine_profiler_sources(self, target_to_sources, num_builders):
        """
        Combine multiple profiler sources generated for different targets
        to optimize the overall compilation time, given the available number
        of builders (CPUs). The total number of sources (across all targets)
        is set equal to the `num_builders`. Single-source targets are kept
        as is; multi-source targetss' sources are possibly combined.

        Simplifying assumptions:

            - Individual split (multiple) sources per target take
              approximately equal time to compile across different
              targets (this is, in particular, not true for the main
              profiler source file vs kernel-specific source files:
              the former is typically larger than the latter);
            - Compilation time grows linearly in the number of
              separate sources combined into a single file.

        Parameters
        ----------
        target_to_soruces : dict[str, Iterable[str]]
            The mapping from each target name to the list of sources
            required to compile this target. There can be one or more
            sources for each target.
        num_builders : int
            The number of available builders (CPUs).

        Returns
        ----------
        target_to_combined_sources : dict[str, Iterable[str]]
            Like `target_to_sources`, but with some of the source paths
            in the values replaced by the paths to the respective combined
            source files. Whether and which of the sources are combined
            depends on the arguments.
        """
        num_total_sources = num_builders

        if (
            len(target_to_sources) >= num_total_sources
            or self._force_one_profiler_source_per_target()
        ):
            # there are at least as many targets as the total
            # number of sources required (or single source per
            # target is forced): combine everything
            return {
                target: [self._combine_sources(sources)]
                for target, sources in target_to_sources.items()
            }

        combine_candidates = {}  # multi-source targets
        num_multi_sources, num_single_sources = 0, 0
        for target, sources in target_to_sources.items():
            if len(sources) > 1:
                combine_candidates[target] = sources
                num_multi_sources += len(sources)
            else:
                num_single_sources += 1

        if num_multi_sources == 0:
            # all targets are single-source: nothing to combine
            return target_to_sources
        if num_multi_sources + num_single_sources <= num_total_sources:
            # there are fewer source files than the total
            # number of sources required: no need to combine
            return target_to_sources

        # number of sources we need for the multi-file targets
        num_combined_sources = num_total_sources - num_single_sources
        num_sources_per_target = {
            # the number of combined sources per multi-source target as a
            # fraction of num_combined_sources is proportional to the number of
            # multiple sources of the target (rounded down); ultimately, there
            # should be at least one source target (hence max(..., 1))
            target: max(int(len(sources) / num_multi_sources * num_combined_sources), 1)
            for target, sources in combine_candidates.items()
        }

        # do any sources remain after the above per-target distribution?
        remaining_sources = num_combined_sources - sum(num_sources_per_target.values())
        if remaining_sources > 0:
            # reverse-sort the targets by the remainder after rounding down:
            # prefer adding sources to the targets with a higher remainder
            # (i.e. the ones closest to getting another source)
            targets = sorted(
                num_sources_per_target.keys(),
                key=lambda target: (
                    (
                        len(target_to_sources[target])
                        / num_multi_sources
                        * num_combined_sources
                    )
                    - int(
                        len(target_to_sources[target])
                        / num_multi_sources
                        * num_combined_sources
                    )
                ),
                reverse=True,
            )
            target_id = 0
            while remaining_sources > 0:
                # increment the number of sources for the target
                num_sources_per_target[targets[target_id]] += 1
                target_id = (target_id + 1) % len(targets)
                remaining_sources -= 1

        result = {}
        for target in target_to_sources:
            if target in combine_candidates:
                # collect the sources of the target
                # in N batches by round robin
                num_sources = num_sources_per_target[target]
                # TODO: form the source batches by the total number
                # of lines instead of the number of sources for more
                # even distribution of the compilation time per batch
                batch_id = 0
                batches = [[] for _ in range(num_sources)]
                for source in target_to_sources[target]:
                    batches[batch_id].append(source)
                    batch_id = (batch_id + 1) % num_sources
                # conbine the sources in each batch
                result[target] = [self._combine_sources(b) for b in batches]
            else:
                # use the single-source profiler target as is
                result[target] = target_to_sources[target]
        return result
Combine multiple profiler sources generated for different targets
to optimize the overall compilation time, given the available number
of builders (CPUs). The total number of sources (across all targets)
is set equal to the `num_builders`. Single-source targets are kept
as is; multi-source targetss' sources are possibly combined.

Simplifying assumptions:

- Individual split (multiple) sources per target take
approximately equal time to compile across different
targets (this is, in particular, not true for the main
profiler source file vs kernel-specific source files:
the former is typically larger than the latter);
- Compilation time grows linearly in the number of
separate sources combined into a single file.

Parameters
----------
target_to_soruces : dict[str, Iterable[str]]
The mapping from each target name to the list of sources
required to compile this target. There can be one or more
sources for each target.
num_builders : int
The number of available builders (CPUs).

Returns
----------
target_to_combined_sources : dict[str, Iterable[str]]
Like `target_to_sources`, but with some of the source paths
in the values replaced by the paths to the respective combined
source files. Whether and which of the sources are combined
depends on the arguments.
python/aitemplate/backend/builder.py