    def _profile_single_workload(
        self, profiler_prefix, exec_key, profiler_runner, force_cache
    ):
        """
        Schedule profilers for given profiler path and gemm shape (exec_key)
        or get the result from cache
        or use dummy result in CI
        """
        target = backend.target.Target.current()
        tmp_key = next(iter(self._attrs["op_instance"].keys()))
        tmp_op = self._attrs["op_instance"][tmp_key]
        exec_entry_sha1 = sha1(exec_key.encode("utf-8")).hexdigest()
        split_k = 1 if self._attrs["split_k"] is None else self._attrs["split_k"]
        # Because we call gen_profiler to generate and compile all profilers
        # before running any of them, we won't be able to update the exec_path
        # in gen_profiler even if two gemms have the same problem size (assume that
        # we don't have a cache entry for this problem size). Consequently,
        # we still need to query the cache here to ensure we won't re-profile
        # the second gemm with the same problem size. Note that if we already
        # have a cache entry for the problem size before gen_profiler, we will
        # setup exec_path correctly in gen_profiler, so we won't get here at all.
        query = GemmQueryEntry(
            # 1 is subtracted from the type enum values for consistency with the existing
            # cache databases; due to the "void" type being added to the DataType enum as
            # the very first enum member (and shifting the values of other enum members) in
            # https://github.com/NVIDIA/cutlass/commit/7c04f954151f606e60608061e891785fba229ae2
            dtype_a=tmp_op.A.element.value - 1,
            dtype_b=tmp_op.B.element.value - 1,
            dtype_c=tmp_op.C.element.value - 1,
            dtype_acc=tmp_op.accumulator_type().value - 1,
            major_a=tmp_op.A.layout.value,
            major_b=tmp_op.B.layout.value,
            major_c=tmp_op.C.layout.value,
            op_type=self._attrs["op"],
            device=target._arch,
            epilogue=tmp_op.epilogue_functor.value,
            exec_entry_sha1=exec_entry_sha1,
            pshape=self._attrs["permute_shape"],
        )
        cache_value = target.query_profile_cache("gemm", query.__dict__)
        if cache_value is not None and not target.force_profile():
            _LOGGER.debug(
                f'Load profiling result for {self._attrs["name"]} '
                f"from cache: {cache_value}",
            )
            self._attrs["exec_path"][exec_key].algo = cache_value[0]
            self._attrs["workspace"] = max(self._attrs["workspace"], cache_value[1])
            self._attrs["split_k"] = cache_value[2]
            return
        if cache_value is None and force_cache:
            op_type = self._attrs["op"]
            raise RuntimeError(
                "force_cache is enabled but we could not find the following cache ",
                f"available on device {target._arch=}, {op_type=}, {exec_entry_sha1=}",
            )
        if target.use_dummy_profiling_results():
            op_type = self._attrs["op"]
            raise Exception(
                "This is a CI run but we could not find the following cache ",
                f"available on device {target._arch}\n",
                f"{op_type} {exec_entry_sha1}.\n",
                "Please adjust target.select_minimal_algo function.",
            )

        profiler_filename = self._get_profiler_filename()

        def _gen_callback(split_k):
            def process_result_callback(result, postprocessing_delegate):
                postprocessing_delegate.add_instance(
                    (result, self._attrs, profiler_filename, exec_key, split_k)
                )

            return process_result_callback

        command = self._gen_profile_cmd(profiler_prefix, profiler_filename, exec_key)

        if self._attrs["op"].startswith("group_gemm") or self._attrs["op"].startswith(
            "bmm"
        ):
            profiler_runner.push(command, _gen_callback(split_k=1))
        else:
            m, n, k = gemm_inverse_key_func(exec_key)[-3:]
            if "split_k_hints" in self._attrs:
                split_k_search_space = self._attrs["split_k_hints"]
            else:
                split_k_search_space = self._split_k_search_space(m, n, k)
            for split_k in split_k_search_space:
                gemm_command = command + [str(split_k)]
                profiler_runner.push(gemm_command, _gen_callback(split_k))
Schedule profilers for given profiler path and gemm shape (exec_key)
or get the result from cache
or use dummy result in CI
python/aitemplate/compiler/ops/gemm_universal/gemm_common.py