code: def finish_subtask(self, subtask_info: SubtaskScheduleInfo):
      subtask = subtask_info.subtask
      grouped_finished_subtasks = self._grouped_finished_subtasks[
          subtask.logic_key
      ]
      grouped_finished_subtasks[subtask.subtask_id] = subtask_info
      self._grouped_unfinished_subtasks[subtask.logic_key].pop(
          subtask.subtask_id, None
      )
      if len(grouped_finished_subtasks) == subtask.logic_parallelism:
          self._grouped_finished_subtasks.pop(subtask.logic_key)
          self._grouped_unfinished_subtasks.pop(subtask.logic_key, None)
          logger.info(
              "Subtask group with logic key %s parallelism %s finished.",
              subtask.logic_key,
              subtask.logic_parallelism,
          )
  
file_path: mars/mars/services/scheduling/supervisor/speculation.py
imports: import asyncio
  import logging
  import time
  import numpy
  from collections import defaultdict
  from typing import Dict
  from ....utils import parse_readable_size, create_task_with_error_log
  from ..errors import NoAvailableBand
  from .manager import SubtaskScheduleInfo
label_desc: Use image node_id 3 for calling the SpeculativeScheduler obj's underlying member method code with example usage: obj.finish_subtask(subtask_info) without return types