    def gen_profiler(
        self, workdir: str = None, dynamic_profiling_strategy=DynamicProfileStrategy.MAX
    ) -> None:
        """Generate profilers for this gemm op.

        Parameters
        ----------
        workdir : str, optional
            Output dir of profilers, by default None
        dynamic_profiling_strategy: DynamicProfileStrategy, optional
            A dynamic profiling strategy, used to filter generated profiles at compile time.
            See also: :func:`~aitemplate.compiler.transform.profile.profile`
        """
        target = backend.target.Target.current()
        # init candidate ops
        func_key = "{target}.{op}.config".format(
            target=target.name(), op=self._attrs["op"]
        )
        func = registry.get(func_key)
        func(self._attrs, dtype=self._attrs["inputs"][0]._attrs["dtype"])

        # init exec path
        self._extract_exec_path(dynamic_profiling_strategy)
        # init compile-time filter
        workloads = list(self._attrs["exec_path"].keys())
        ab_alignments = sorted({self._get_ab_alignment(wkl) for wkl in workloads})
        assert 1 == len(
            ab_alignments
        ), f"ab_alignments should be the same among all workloads, got {ab_alignments=}"
        func_key = "{target}.{op}.filter".format(
            target=target.name(), op=self._attrs["op"]
        )

        # Update epilogue alignment here because it may be different depending on the profiling strategy.
        # Note that this alignment is only used in profiling and will be updated
        # during the final codegen.
        # gemm_permute ops have special output alignment rules, skip here.
        if "layout" not in self._attrs:
            output_shape = self._attrs["output_accessors"][0].original_shapes
            self._extract_epilogue_alignment(output_shape, dynamic_profiling_strategy)

        if not self._attrs["op_instance"]:
            raise RuntimeError(
                f"No GEMM op instances were generated for {self._attrs['op']}."
            )

        filter_func = registry.get(func_key)
        # run compile-time filter
        new_op_instance = OrderedDict(
            (k, v)
            for k, v in self._attrs["op_instance"].items()
            if filter_func(k, self._attrs, ab_alignments[0])
        )
        _LOGGER.debug(
            f"Filtered profiler kernels for {self._attrs['op']}: reduced the "
            f"number of generated kernels from {len(self._attrs['op_instance'])} "
            f"to {len(new_op_instance)}",
        )
        self._attrs["op_instance"] = new_op_instance

        if not self._attrs["op_instance"]:
            raise RuntimeError(
                f"No GEMM op instances are left after filtering for {self._attrs['op']}. "
                "This is probably due to incompatible alignment requirements."
            )

        build_profiler = self._should_build_profiler(workloads, new_op_instance)
        if build_profiler:
            # generate profiler
            func_key = "{target}.{op}.gen_profiler".format(
                target=target.name(), op=self._attrs["op"]
            )
            func = registry.get(func_key)
            profiler_filename = self._get_profiler_filename()
            _LOGGER.info(f"generating {profiler_filename=}")
            return func(
                self._attrs,
                workdir,
                profiler_filename,
                self._extract_dims(for_profiling=True),
            )
Generate profilers for this gemm op.

Parameters
----------
workdir : str, optional
Output dir of profilers, by default None
dynamic_profiling_strategy: DynamicProfileStrategy, optional
A dynamic profiling strategy, used to filter generated profiles at compile time.
See also: :func:`~aitemplate.compiler.transform.profile.profile`
python/aitemplate/compiler/ops/gemm_universal/gemm_common.py