    def _profile_dynamic_dim(self, workdir):
        """Profiles with dynamic shapes."""

        # extract dynamic dim from exec_path
        def _extract_dynamic_dim(exec_keys):
            var_dims = [[], [], [], []]
            for key in exec_keys:
                dims = self._invert_exec_key(key)
                for i, v in enumerate(dims):
                    var_dims[i].append(v)
            return var_dims

        dim_lbs = self._attrs["dim_lower_bounds"]
        dims = _extract_dynamic_dim(self._attrs["exec_path"].keys())
        dim0_lb = dim_lbs[0]
        dim1_lb = dim_lbs[1]
        dim2_lb = dim_lbs[2]
        # dims' upper bounds are the same except the batch dimension
        dim1_ub = dims[1][0]
        dim2_ub = dims[2][0]
        dim3 = dims[3][0]

        num_exec_path = len(self._attrs["exec_path"])
        if num_exec_path < 1:
            return
        algos = list(self._attrs["exec_path"].values())
        if num_exec_path == 1 or len(set(algos)) <= 1:
            # all exec paths point to the same algo
            new_exec_paths = OrderedDict()
            # Because we have a single algo, it's safe to just take the upper
            # bound of dim0 (i.e. batch dim) values.
            dim0_ub = max(dims[0])
            # we need to generate new exec paths that ensure the ranges of
            # likely dynamic heights and weights
            new_key = self._gen_dyn_exec_key(
                dim0_lb, dim0_ub, dim1_lb, dim1_ub, dim2_lb, dim2_ub, dim3
            )
            new_exec_paths[new_key] = algos[0]
            self._attrs["exec_path"] = new_exec_paths
            return

        target = backend.target.Target.current()
        if target.use_dummy_profiling_results():
            return

        profiler_prefix = os.path.join(workdir, "profiler", self._attrs["op"])
        runner = backend.profiler_runner.Runner([0], self._attrs["name"])
        # generate region
        regions = []  # lb, ub, lb_algos, ub_algos
        for i in range(len(dims[0]) - 1):
            regions.append([dims[0][i], dims[0][i + 1], algos[i], algos[i + 1]])
        # for each region,
        #   binary search to find cutting point
        #   generate new exec
        special_cases = OrderedDict()
        new_exec_paths = OrderedDict()
        for lb, ub, lb_algo, ub_algo in regions:
            mid = (lb + ub) // 2
            origin_lb = lb
            origin_ub = ub
            last_mid = mid
            while mid > lb and mid < ub:
                mid = (lb + ub) // 2
                mid_shape = [mid, dim1_ub, dim2_ub, dim3]
                _LOGGER.info(
                    "current: lb_algo: {lb_algo}, LB:{lb} MID:{mid} UB:{ub}".format(
                        lb_algo=lb_algo, lb=lb, mid=mid, ub=ub
                    ),
                )

                # run the profiler binary with all ops on the mid_shape
                # and fetch the results only for the lb_algo and ub_algo
                profiler_filename = get_profiler_filename(self._attrs, "conv")
                profiler_cmd = self._gen_profile_cmd(
                    profiler_prefix, profiler_filename, mid_shape
                )
                runner.push(
                    idx=profiler_filename,
                    cmd=profiler_cmd,
                    return_ops=[str(lb_algo), str(ub_algo)],
                )
                runner.join()
                result = runner.pull()
                result_dict = {res.op_config: res for res in result[0][1]}

                assert len(result_dict) >= 1
                # if there is only one result, assume ub algo failed.
                if len(result_dict) == 1:
                    assert str(ub_algo) not in result_dict
                    # last_lb = lb
                    lb = mid + 1
                # if there are two result, compare to decide new lb/ub
                else:
                    lb_time = result_dict[str(lb_algo)].duration
                    ub_time = result_dict[str(ub_algo)].duration
                    if lb_time < ub_time:
                        # lb algo can work with larger batch
                        # last_lb = lb
                        lb = mid + 1
                    else:
                        # ub algo can work with smaller batch
                        # last_ub = ub
                        ub = mid - 1
                last_mid = mid
                mid = (lb + ub) // 2
            lo_region_key = self._gen_dyn_exec_key(
                origin_lb, last_mid, dim1_lb, dim1_ub, dim2_lb, dim2_ub, dim3
            )
            up_region_key = self._gen_dyn_exec_key(
                last_mid, origin_ub, dim1_lb, dim1_ub, dim2_lb, dim2_ub, dim3
            )
            new_exec_paths[lo_region_key] = lb_algo
            new_exec_paths[up_region_key] = ub_algo
            # find special cases
            # This code is kept in case need fully tested dynamic code
            # So far I find binary search works well.
            # def _find_special_case(lb, ub, algo):
            #     for i in range(lb + 1, ub + 1):
            #         x_shape = [i, dim1, dim2, dim3]
            #         cmd = self._gen_profile_cmd(profiler_prefix, str(algo), x_shape)
            #         runner.push(0, cmd)
            #         runner.join()
            #         out = runner.pull()
            #         if len(out) == 0:
            #             _LOGGER.info("Find specail case: batch=%d" % i)
            #             algo = self._profile_single_workload(profiler_prefix, x_shape, [0])
            #             special_cases[self._gen_exec_key(x_shape)] = algo

            # _LOGGER.info(
            #     "Searching for specail cases between [{lb}, {ub}]".format(lb=origin_lb,
            #         ub=last_mid))
            # _find_special_case(origin_lb, last_mid, lb_algo)
            # _LOGGER.info(
            #     "Searching for specail cases between [{lb}, {ub}]".format(lb=last_mid + 1,
            #         ub=origin_ub))
            # _find_special_case(last_mid, origin_ub, ub_algo)
        special_cases.update(new_exec_paths)
        self._attrs["exec_path"] = special_cases
Profiles with dynamic shapes.
python/aitemplate/compiler/ops/conv/conv2d.py