def get_kv_info(io_config: dict) -> Optional[dict]:
        """Return the kv_info dictionary containing information about past keys and values.

        :param io_config: A dictionary containing the input and output names and shapes.
        :return: A dictionary with keys "past_names", "present_to_past", "num_kv_heads", and "head_size".
            If no kv_info is found, returns None. Only dynamic shapes are accepted currently.
        """
        # assuming batch_size, num_kv_heads, past_seq_len, head_size
        kv_options = {
            r"past_key_values.(\d+).key": {
                "past_key": "past_key_values.%d.key",
                "past_value": "past_key_values.%d.value",
                "present_key": "present.%d.key",
                "present_value": "present.%d.value",
            },
            r"past_key_(\d+)": {
                "past_key": "past_key_%d",
                "past_value": "past_value_%d",
                "present_key": "present_key_%d",
                "present_value": "present_value_%d",
            },
        }

        # Find the format of the past keys and values
        # only accept dynamic shapes for now
        kv_format = None
        for idx, i_name in enumerate(io_config["input_names"]):
            for pattern in kv_options:
                if re.match(pattern, i_name) and not isinstance(io_config["input_shapes"][idx][2], int):
                    kv_format = pattern
                    break
            if kv_format:
                break

        if kv_format is None:
            return None

        # find the number of layers
        num_layers = 0
        for i_name in io_config["input_names"]:
            num_layers += int(re.match(kv_format, i_name) is not None)
        logger.debug("Found %d layers with past keys/values", num_layers)

        past_names = []
        present_to_past = {}
        for k in ["key", "value"]:
            past_names.extend([kv_options[kv_format][f"past_{k}"] % i for i in range(num_layers)])
            present_to_past.update(
                {
                    kv_options[kv_format][f"present_{k}"] % i: kv_options[kv_format][f"past_{k}"] % i
                    for i in range(num_layers)
                }
            )

        past_shape = io_config["input_shapes"][io_config["input_names"].index(past_names[0])]

        return {
            "past_names": past_names,
            "present_to_past": present_to_past,
            "num_kv_heads": past_shape[1],
            "head_size": past_shape[3],
        }
Return the kv_info dictionary containing information about past keys and values.

        :param io_config: A dictionary containing the input and output names and shapes.
        :return: A dictionary with keys "past_names", "present_to_past", "num_kv_heads", and "head_size".
            If no kv_info is found, returns None. Only dynamic shapes are accepted currently.
        
olive/data/component/dataloader.py