def METHOD_NAME(
    num_rounds: int,
    h_starting_state: TMessageBlock,
    block: bytes,
    t_offset_counters: Tuple[int, int],
    final_block_flag: bool,
) -> bytes:
    """
    'F Compression' from section 3.2 of RFC 7693:
    https://tools.ietf.org/html/rfc7693#section-3.2
    """
    # Dereference these for [very small] speed improvement.
    # Perhaps more than anything, this makes the code
    # easier to read.
    MASKBITS = Blake2b.MASKBITS
    WORDBITS = Blake2b.WORDBITS
    IV = Blake2b.IV
    ROT1 = Blake2b.ROT1
    ROT2 = Blake2b.ROT2
    ROT3 = Blake2b.ROT3
    ROT4 = Blake2b.ROT4
    WB_ROT1 = WORDBITS - ROT1
    WB_ROT2 = WORDBITS - ROT2
    WB_ROT3 = WORDBITS - ROT3
    WB_ROT4 = WORDBITS - ROT4
    sigma_schedule = Blake2b.sigma_schedule
    sigma_schedule_len = len(sigma_schedule)
    # convert block (bytes) into 16 LE words
    m = struct.unpack_from("<16%s" % Blake2b.WORDFMT, bytes(block))
    v = [0] * 16
    v[0:8] = h_starting_state
    v[8:12] = IV[:4]
    v[12] = t_offset_counters[0] ^ IV[4]
    v[13] = t_offset_counters[1] ^ IV[5]
    if final_block_flag:
        v[14] = Blake2b.MASKBITS ^ IV[6]
    else:
        v[14] = IV[6]
    # The original code had a mechanism to turn on a "tree mode",
    # setting f[1] to MASKBITS here.
    # There seems to be no reference to that bit flip in the 3.2 section of RFC 7693,
    # and there is no such setting in EIP-152. So the bit flip option is removed.
    v[15] = IV[7]
    # Within the confines of the Python language, this is a
    # highly optimized version of G().  It differs some from
    # the formal specification and reference implementation.
    def G(a: int, b: int, c: int, d: int) -> None:
        # dereference v[] for another small speed improvement
        va = v[a]
        vb = v[b]
        vc = v[c]
        vd = v[d]
        va = (va + vb + msri2) & MASKBITS
        w = vd ^ va
        vd = (w >> ROT1) | (w << (WB_ROT1)) & MASKBITS
        vc = (vc + vd) & MASKBITS
        w = vb ^ vc
        vb = (w >> ROT2) | (w << (WB_ROT2)) & MASKBITS
        va = (va + vb + msri21) & MASKBITS
        w = vd ^ va
        vd = (w >> ROT3) | (w << (WB_ROT3)) & MASKBITS
        vc = (vc + vd) & MASKBITS
        w = vb ^ vc
        vb = (w >> ROT4) | (w << (WB_ROT4)) & MASKBITS
        # re-reference v[]
        v[a] = va
        v[b] = vb
        v[c] = vc
        v[d] = vd
    # time to ChaCha
    for r in range(num_rounds):
        # resolve as much as possible outside G() and
        # don't pass as argument, let scope do its job.
        # Result is a 50% speed increase, but sadly,
        # "slow" divided by 1.5 is still "slow".  :-/
        sr = sigma_schedule[r % sigma_schedule_len]
        msri2 = m[sr[0]]
        msri21 = m[sr[1]]
        G(0, 4, 8, 12)
        msri2 = m[sr[2]]
        msri21 = m[sr[3]]
        G(1, 5, 9, 13)
        msri2 = m[sr[4]]
        msri21 = m[sr[5]]
        G(2, 6, 10, 14)
        msri2 = m[sr[6]]
        msri21 = m[sr[7]]
        G(3, 7, 11, 15)
        msri2 = m[sr[8]]
        msri21 = m[sr[9]]
        G(0, 5, 10, 15)
        msri2 = m[sr[10]]
        msri21 = m[sr[11]]
        G(1, 6, 11, 12)
        msri2 = m[sr[12]]
        msri21 = m[sr[13]]
        G(2, 7, 8, 13)
        msri2 = m[sr[14]]
        msri21 = m[sr[15]]
        G(3, 4, 9, 14)
    result_message_words = (h_starting_state[i] ^ v[i] ^ v[i + 8] for i in range(8))
    return struct.pack(f"<8{Blake2b.WORDFMT}", *result_message_words)