async def test_tdb_request_act_times_grad_from_intermediate_autoencoder_node(
    standard_model_context: StandardModelContext,
    standard_autoencoder_context: AutoencoderContext,
) -> None:
    """
    Test that the top attention act * grad returned by the BatchedTdbRequest when tracing
    from an intermediate autoencoder latent matches the activation value stored using hooks
    on the Transformer forward pass directly.

    This test compares the simplest way to get activations from a Transformer, with the
    way that is used in TDB.
    """
    transformer = standard_model_context.get_or_create_model()

    prompt = "This is a test"
    input_token_ints = torch.tensor(
        standard_model_context.encode(prompt), device=standard_model_context.device
    ).unsqueeze(0)

    node_index_for_bwd = MirroredNodeIndex(
        node_type=NodeType.AUTOENCODER_LATENT,
        layer_index=5,
        tensor_indices=(1, 0),
        pass_type=PassType.FORWARD,
    )

    tdb_request = TdbRequestSpec(
        prompt=prompt,
        target_tokens=[],
        distractor_tokens=[],
        component_type_for_mlp=ComponentTypeForMlp.AUTOENCODER_LATENT,
        component_type_for_attention=ComponentTypeForAttention.ATTENTION_HEAD,
        top_and_bottom_k_for_node_table=25,
        hide_early_layers_when_ablating=False,
        node_ablations=None,
        upstream_node_to_trace=NodeToTrace(
            node_index=node_index_for_bwd,
            attention_trace_type=None,
            downstream_trace_config=None,
        ),
        downstream_node_to_trace=None,
    )
    batched_tdb_request = BatchedTdbRequest(sub_requests=[tdb_request])

    interactive_model = InteractiveModel.from_standard_model_context_and_autoencoder_context(
        standard_model_context, standard_autoencoder_context
    )

    batched_tdb_response = await interactive_model.handle_batched_tdb_request(batched_tdb_request)

    top_k_components_response = batched_tdb_response.inference_sub_responses[
        0
    ].processing_response_data_by_name["topKComponents"]
    assert isinstance(top_k_components_response, MultipleTopKDerivedScalarsResponseData)
    top_node_indices = top_k_components_response.node_indices
    top_act_times_grads = top_k_components_response.activations_by_group_id[GroupId.ACT_TIMES_GRAD]
    top_acts = top_k_components_response.activations_by_group_id[GroupId.ACTIVATION]

    node_index, tdb_act, tdb_act_times_grad = next(
        (
            (top_node_index, top_act, top_act_times_grad)
            for top_node_index, top_act, top_act_times_grad in zip(
                top_node_indices, top_acts, top_act_times_grads
            )
            if top_node_index.node_type == NodeType.ATTENTION_HEAD and top_act_times_grad > 0.0
        ),
        (None, None, None),
    )  # get the first latent index returned by the TDB request
    assert node_index is not None

    hooks = TransformerHooks()
    stored_act = {}
    stored_grad = {}
    stored_act_for_bwd = {}

    def act_saving_hook_fn(act: torch.Tensor, **kwargs: Any) -> torch.Tensor:
        stored_act["value"] = act[
            (0,) + node_index.tensor_indices
        ]  # 0 batch index, keep token index from node_index
        return act  # store the activation value for the latent in question

    def grad_saving_hook_fn(grad: torch.Tensor, **kwargs: Any) -> torch.Tensor:
        stored_grad["value"] = grad[(0,) + node_index.tensor_indices]
        return grad

    autoencoder = standard_autoencoder_context.get_autoencoder(node_index_for_bwd.layer_index)

    def act_grabbing_hook_fn(act: torch.Tensor, **kwargs: Any) -> torch.Tensor:
        # apply autoencoder
        token_index_for_bwd, activation_index_for_bwd = node_index_for_bwd.tensor_indices
        assert isinstance(token_index_for_bwd, int)
        assert isinstance(activation_index_for_bwd, int)
        stored_act_for_bwd["value"] = autoencoder.encode_pre_act(
            act[0, token_index_for_bwd : token_index_for_bwd + 1]
        )[0, activation_index_for_bwd]
        return act

    def detach_fn(act: torch.Tensor, **kwargs: Any) -> torch.Tensor:
        return act.detach()

    hooks = (
        hooks.append_to_path(
            "attn.qk_probs.fwd",
            AtLayers([assert_not_none(node_index.layer_index)]).append(act_saving_hook_fn),
        )
        .append_to_path(
            "attn.qk_probs.bwd",
            AtLayers([assert_not_none(node_index.layer_index)]).append(grad_saving_hook_fn),
        )
        .append_to_path(
            "resid.torso.ln_mlp.scale.fwd",
            AtLayers([assert_not_none(node_index_for_bwd.layer_index)]).append(detach_fn),
        )  # detach layer norm scale
        .append_to_path(
            "mlp.post_act.fwd",  # autoencoder is applied to mlp.post_act
            AtLayers([assert_not_none(node_index_for_bwd.layer_index)]).append(
                act_grabbing_hook_fn
            ),
        )
    )

    transformer(input_token_ints, hooks=hooks)

    stored_act_for_bwd["value"].backward()

    assert isinstance(tdb_act, float)
    assert math.isclose(
        tdb_act,
        stored_act["value"].item(),
        rel_tol=1e-4,
    )

    assert isinstance(tdb_act_times_grad, float)
    assert math.isclose(
        tdb_act_times_grad,
        (stored_act["value"] * stored_grad["value"]).item(),
        rel_tol=1e-4,
    )

    Test that the top attention act * grad returned by the BatchedTdbRequest when tracing
    from an intermediate autoencoder latent matches the activation value stored using hooks
    on the Transformer forward pass directly.

    This test compares the simplest way to get activations from a Transformer, with the
    way that is used in TDB.
    
neuron_explainer/tests/test_interactive_model.py