def get_most_upvoted_and_downvoted_tokens_for_nodes(
    model_context: ModelContext,
    token_writes_tensor: torch.Tensor,
    top_t_tokens: int,
    flip_upvoted_and_downvoted: bool = False,
) -> list[TopTokens]:
    """
    Convert a 2D token_writes_tensor to the most positive (upvoted) and most negative (downvoted) vocab tokens per row,
    with weights corresponding to how upvoted or downvoted each token is. Return a list (indexed by row index) of
    TopTokens, each of which contains a list of TokenAndScalar for the most upvoted tokens and
    a list of TokenAndScalar for the most downvoted tokens.

    Note that the scalars in TokenAndScalar are referred to as 'weights', despite being held in an object called
    TokenAndScalar. The weights returned here include normalized versions (normalized to max(abs(weight))).
    """

    if model_context.get_encoding().name == "gpt2":
        # for GPT-2, we exclude tokens string-matching to the least common tokens
        # from the top_t tokens displayed
        least_common_tokens_as_ints = model_context.encode_token_str_list(
            LEAST_COMMON_GPT2_TOKEN_STRS
        )
    else:
        least_common_tokens_as_ints = None
    (
        top_t_upvoted_token_weights_tensor,
        top_t_upvoted_token_ints_tensor,
    ) = get_top_t_tokens_maybe_excluding_least_common(
        token_writes_tensor,
        top_t_tokens,
        largest=True,
        least_common_tokens_as_ints=least_common_tokens_as_ints,
    )
    (
        top_t_downvoted_token_weights_tensor,
        top_t_downvoted_token_ints_tensor,
    ) = get_top_t_tokens_maybe_excluding_least_common(
        token_writes_tensor,
        top_t_tokens,
        largest=False,
        least_common_tokens_as_ints=least_common_tokens_as_ints,
    )
    max_abs_token_writes_tensor = torch.max(
        top_t_upvoted_token_weights_tensor[:, 0:1].abs(),
        top_t_downvoted_token_weights_tensor[:, 0:1].abs(),
    )

    def safe_divide(
        numerator_tensor: torch.Tensor, denominator_tensor: torch.Tensor
    ) -> torch.Tensor:
        assert torch.isfinite(
            numerator_tensor
        ).all(), "Numerator tensor should only contain finite values"
        assert torch.isfinite(
            denominator_tensor
        ).all(), "Denominator tensor should only contain finite values"
        return torch.where(
            denominator_tensor == 0,
            torch.zeros_like(numerator_tensor),
            numerator_tensor / denominator_tensor,
        )

    norm_top_t_upvoted_token_weights_tensor = safe_divide(
        top_t_upvoted_token_weights_tensor, max_abs_token_writes_tensor
    )
    norm_top_t_downvoted_token_weights_tensor = safe_divide(
        top_t_downvoted_token_weights_tensor, max_abs_token_writes_tensor
    )

    normalized_most_upvoted_tokens_for_all_nodes = package_top_t_tokens(
        model_context,
        top_t_upvoted_token_ints_tensor,
        top_t_upvoted_token_weights_tensor,
        norm_top_t_upvoted_token_weights_tensor,
    )
    normalized_most_downvoted_tokens_for_all_nodes = package_top_t_tokens(
        model_context,
        top_t_downvoted_token_ints_tensor,
        top_t_downvoted_token_weights_tensor,
        norm_top_t_downvoted_token_weights_tensor,
    )
    # zip the results into a list of TopTokens
    top_tokens_list = []
    for node_index in range(len(normalized_most_upvoted_tokens_for_all_nodes)):
        if flip_upvoted_and_downvoted:
            normalized_most_upvoted_tokens_for_node = (
                normalized_most_downvoted_tokens_for_all_nodes[node_index]
            )
            normalized_most_downvoted_tokens_for_node = (
                normalized_most_upvoted_tokens_for_all_nodes[node_index]
            )
        else:
            normalized_most_upvoted_tokens_for_node = normalized_most_upvoted_tokens_for_all_nodes[
                node_index
            ]
            normalized_most_downvoted_tokens_for_node = (
                normalized_most_downvoted_tokens_for_all_nodes[node_index]
            )
        top_tokens_list.append(
            TopTokens(
                top=normalized_most_upvoted_tokens_for_node,
                bottom=normalized_most_downvoted_tokens_for_node,
            )
        )

    assert len(top_tokens_list) == token_writes_tensor.shape[0]
    return top_tokens_list

    Convert a 2D token_writes_tensor to the most positive (upvoted) and most negative (downvoted) vocab tokens per row,
    with weights corresponding to how upvoted or downvoted each token is. Return a list (indexed by row index) of
    TopTokens, each of which contains a list of TokenAndScalar for the most upvoted tokens and
    a list of TokenAndScalar for the most downvoted tokens.

    Note that the scalars in TokenAndScalar are referred to as 'weights', despite being held in an object called
    TokenAndScalar. The weights returned here include normalized versions (normalized to max(abs(weight))).
    
neuron_explainer/activations/derived_scalars/tokens.py