async def handle_batched_request(self, batched_request: BatchedRequest) -> BatchedResponse:
        """For high level overview, see STEP 0, 1, 2 below"""
        async with self._batched_request_lock:
            # STEP 0: infer the derived scalar types and configs needed for each group ID, by
            # examining the processing_request_spec for each group ID, and the information in the
            # inference_request_spec shared by all group IDs.
            batched_dst_and_config_list_by_processing_step = []
            for inference_request in batched_request.inference_sub_requests:
                batched_dst_and_config_list_by_processing_step.append(
                    self._get_dst_and_configs_by_processing_step_for_singular_request(
                        inference_request
                    )
                )

            # Confirm that the prompts for all of the batched (multiple) top-k sub-requests have the
            # the same length (in tokens). If they don't, we can't aggregate node indices across
            # them. They should also be less than PROMPT_LENGTH_LIMIT tokens long.
            prompt_lengths = []
            batched_tokens_as_ints = []
            for inference_request in batched_request.inference_sub_requests:
                tokens_as_ints = self._standard_model_context.encode(
                    inference_request.inference_request_spec.prompt
                )
                if any(
                    isinstance(spec, MultipleTopKDerivedScalarsRequestSpec)
                    for spec in inference_request.processing_request_spec_by_name.values()
                ):
                    batched_tokens_as_ints.append(tokens_as_ints)
                    prompt_lengths.append(len(tokens_as_ints))

            if any(prompt_length > PROMPT_LENGTH_LIMIT for prompt_length in prompt_lengths):
                raise HTTPException(
                    status_code=400,
                    detail=(
                        f"Prompts must be less than {PROMPT_LENGTH_LIMIT} tokens long for batched top-k requests. "
                        f"Got these prompt lengths: {prompt_lengths}"
                    ),
                )

            if len(set(prompt_lengths)) > 1:
                # Build an error message that gives the tokenized prompts (as strings) with their
                # lengths.
                tokens_as_strings_list = [
                    self._standard_model_context.decode_token_list(tokens_as_ints)
                    for tokens_as_ints in batched_tokens_as_ints
                ]
                prompt_lengths_str = ", ".join(
                    f"{tokens_as_strings} ({len(tokens_as_strings)} tokens)\n"
                    for tokens_as_strings in tokens_as_strings_list
                )
                raise HTTPException(
                    status_code=400,
                    detail=(
                        f"All prompts must have the same length for batched top-k requests. "
                        f"Got these prompts:\n{prompt_lengths_str}"
                    ),
                )

            # STEP 1: run the forward pass, saving the activations and metadata required for each of
            # the derived scalar types and configs needed for each group ID; then compute the
            # derived scalars for each group ID from the common set of activations and metadata
            # objects.
            (
                batched_tokens_as_ints,
                batched_multi_group_ds_store_by_processing_step,
                batched_inference_data,
            ) = self._compute_multi_group_ds_store(
                batched_inference_request_spec=[
                    inference_request.inference_request_spec
                    for inference_request in batched_request.inference_sub_requests
                ],
                batched_dst_and_configs_by_processing_step=batched_dst_and_config_list_by_processing_step,
            )

            # Identify the node indices of the top (and possibly bottom) k activations for each
            # group sub-request, collated by spec name. We need to know all of the indices before we
            # can put together the results, since we ensure that all sub-requests cover all nodes
            # that were in the top k for any sub-request.
            # NOTE: all_node_indices is separated by spec name (i.e. processing step) but cumulative
            # across the batch index
            all_node_indices_by_spec_name: dict[str, list[NodeIndex]] = defaultdict(list)
            for (
                request,
                multi_group_ds_store_by_processing_step,
            ) in zip(
                batched_request.inference_sub_requests,
                batched_multi_group_ds_store_by_processing_step,
            ):
                for (
                    processing_spec_name,
                    processing_request_spec,
                ) in request.processing_request_spec_by_name.items():
                    if isinstance(processing_request_spec, MultipleTopKDerivedScalarsRequestSpec):
                        (
                            transform_activations_fn_for_top_k,
                            transform_indices_fn_for_top_k,
                        ) = maybe_construct_transform_fns_for_top_k(
                            processing_request_spec.token_index,
                        )
                        multi_group_ds_store = multi_group_ds_store_by_processing_step[
                            processing_spec_name
                        ]
                        # if top_and_bottom_k is not provided, returns all elements
                        node_indices, _ = multi_group_ds_store.topk(
                            top_and_bottom_k=processing_request_spec.top_and_bottom_k,
                            transform_activations_fn=transform_activations_fn_for_top_k,
                            transform_indices_fn=transform_indices_fn_for_top_k,
                        )
                        all_node_indices_by_spec_name[processing_spec_name] += node_indices
                    elif isinstance(
                        processing_request_spec,
                        (ScoredTokensRequestSpec, TokenPairAttributionRequestSpec),
                    ):
                        # These request specs always depend on another spec in the top-level
                        # request and use the same node indices.
                        all_node_indices_by_spec_name[
                            processing_spec_name
                        ] = all_node_indices_by_spec_name[
                            processing_request_spec.depends_on_spec_name
                        ]
            # Dedupe the node indices, maintaining the original order.
            all_node_indices_by_spec_name = {
                spec_name: _unique_list_in_original_order(all_node_indices)
                for spec_name, all_node_indices in all_node_indices_by_spec_name.items()
            }

            # STEP 2: compute the response data for each sub request spec; this can be simply
            # extracting a particular derived scalar at a particular activation index, or can be
            # computing the top k derived scalars, or can be computing the top k of multiple
            # scalars, and combining them.
            batched_inference_responses = []
            assert (
                len(batched_request.inference_sub_requests)
                == len(batched_multi_group_ds_store_by_processing_step)
                == len(batched_dst_and_config_list_by_processing_step)
                == len(batched_tokens_as_ints)
                == len(batched_inference_data)
            )
            for (
                inference_sub_request,
                multi_group_ds_store_by_processing_step,
                tokens_as_ints,
                inference_data,
            ) in zip(
                batched_request.inference_sub_requests,
                batched_multi_group_ds_store_by_processing_step,
                batched_tokens_as_ints,
                batched_inference_data,
            ):
                processing_response_data_by_name: dict[str, ProcessingResponseData] = {}
                for (
                    processing_spec_name,
                    processing_request_spec,
                ) in inference_sub_request.processing_request_spec_by_name.items():
                    relevant_multi_group_ds_store = multi_group_ds_store_by_processing_step[
                        processing_spec_name
                    ]
                    # The key may not present in the case where a non-top-k request uses a spec name
                    # that is not present in any of the (multi) top-k requests, or in the case where
                    # there are no (multi) top-k requests at all.
                    all_node_indices: list[NodeIndex] = all_node_indices_by_spec_name.get(
                        processing_spec_name, []
                    )
                    processing_response_data = await self._handle_processing_request(
                        processing_request_spec=processing_request_spec,
                        multi_group_ds_store=relevant_multi_group_ds_store,
                        all_node_indices=all_node_indices,
                        tokens_as_ints=tokens_as_ints,
                    )
                    expected_response_data_class = get_corresponding_object(
                        type(processing_request_spec), "request_spec_class", "response_data_class"
                    )
                    assert isinstance(expected_response_data_class, type)
                    assert isinstance(
                        processing_response_data,
                        expected_response_data_class,
                    )
                    processing_response_data_by_name[
                        processing_spec_name
                    ] = processing_response_data
                # put the sub responses together in one object to be returned
                combined_response = InferenceResponseAndResponseDict(
                    inference_response=InferenceResponse(
                        inference_and_token_data=self._get_inference_response_data(
                            tokens_as_ints=tokens_as_ints,
                            inference_data=inference_data,
                        ),
                    ),
                    processing_response_data_by_name=processing_response_data_by_name,
                )
                # check that the type of each sub response data is as expected given the type of the
                # corresponding sub request spec
                for name in combined_response.processing_response_data_by_name.keys():
                    processing_request_spec = inference_sub_request.processing_request_spec_by_name[
                        name
                    ]
                    expected_response_data_class = get_corresponding_object(
                        type(processing_request_spec), "request_spec_class", "response_data_class"
                    )
                    assert isinstance(expected_response_data_class, type)
                    assert isinstance(
                        combined_response.processing_response_data_by_name[name],
                        expected_response_data_class,
                    ), "Response data class mismatch; perhaps cast to wrong type by Pydantic?"
                batched_inference_responses.append(combined_response)

            return BatchedResponse(inference_sub_responses=batched_inference_responses)
For high level overview, see STEP 0, 1, 2 below
neuron_explainer/activation_server/interactive_model.py