File size: 4,395 Bytes
a2ffd07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import os

import torch
from jaxtyping import Float
from loguru import logger
from torch import Tensor

from .types import TopK


# NOTE: Avoid this where possible to save memory!
def scatter_topk(topk: TopK, n_latents: int) -> Float[Tensor, "... n_latents"]:
    """
    Scatter the k largest latents into a new tensor of shape (..., n_latents).

    Args:
        topk (TopK): The k largest latents.

        n_latents (int): The number of latents.

    Returns:
        out (Float[Tensor, "... n_latents"]): The k largest latents.
    """

    # ... n_latents
    buffer = topk.values.new_zeros((*topk.indices.shape[:-1], n_latents))
    # ... k -> ... n_latents
    return buffer.scatter_(dim=-1, index=topk.indices, src=topk.values)


# Based on https://github.com/EleutherAI/sae/blob/19d95a401e9d17dbf7d6fb0fa7a91081f1b0d01f/sae/utils.py
def decode_triton(topk: TopK, weight: Tensor) -> Tensor:
    shape = topk.indices.shape[:-1]
    k = topk.indices.shape[-1]
    n_inputs, n_latents = weight.shape

    indices_flat = topk.indices.view(-1, k)
    values_flat = topk.values.view(-1, k)

    output: Tensor = TritonDecoderAutograd.apply(indices_flat, values_flat, weight)  # type: ignore

    return output.view(*shape, n_inputs)


def decode_cuda(topk: TopK, weight: Tensor, chunk_size: int = 1024) -> Tensor:
    shape = topk.indices.shape[:-1]
    k = topk.indices.shape[-1]
    n_inputs, n_latents = weight.shape

    indices_flat = topk.indices.view(-1, k)
    values_flat = topk.values.view(-1, k)

    batch_size = indices_flat.shape[0]

    output = torch.zeros(
        batch_size, n_inputs, device=topk.values.device, dtype=topk.values.dtype
    )

    for i in range(0, batch_size, chunk_size):
        indices_chunk = indices_flat[i : i + chunk_size]
        values_chunk = values_flat[i : i + chunk_size]

        chunk_sparse = torch.sparse_coo_tensor(
            indices=torch.cat(
                [
                    torch.arange(
                        indices_chunk.shape[0], device=indices_chunk.device
                    ).repeat_interleave(k),
                    indices_chunk.flatten(),
                ]
            ).view(2, -1),
            values=values_chunk.flatten(),
            size=(indices_chunk.shape[0], n_latents),
        )

        chunk_output = torch.sparse.mm(chunk_sparse, weight.t())

        output[i : i + chunk_size] = chunk_output

    return output.view(*shape, n_inputs)


# NOTE: 'sparse_coo_tensor' isn't supported yet for the MPS backend
def decode_mps(topk: TopK, weight: Tensor, chunk_size: int = 1024) -> Tensor:
    shape = topk.indices.shape[:-1]
    k = topk.indices.shape[-1]
    n_inputs, n_latents = weight.shape

    indices_flat = topk.indices.view(-1, k)
    values_flat = topk.values.view(-1, k)

    batch_size = indices_flat.shape[0]

    output = torch.zeros(
        batch_size, n_inputs, device=topk.values.device, dtype=topk.values.dtype
    )

    for i in range(0, batch_size, chunk_size):
        indices_chunk = indices_flat[i : i + chunk_size]
        values_chunk = values_flat[i : i + chunk_size]

        weight_mask = weight[:, indices_chunk.view(-1)].view(
            n_inputs, indices_chunk.shape[0], k
        )

        output_chunk = torch.bmm(
            values_chunk.unsqueeze(1), weight_mask.permute(1, 2, 0)
        ).squeeze(1)

        output[i : i + chunk_size] = output_chunk

    return output.view(*shape, n_inputs)


def decode(topk: TopK, weight: Tensor) -> Tensor:
    """
    Sparse decoder implementation.

    Args:
        topk (TopK): The k largest latents.

        weight (Float[Tensor, "n_inputs n_latents"]): The decoder weight matrix.

    Returns:
        out (Float[Tensor, "... n_inputs"]): The reconstructions.
    """
    ...


try:
    from .kernels import TritonDecoderAutograd
except ImportError:
    logger.info("Triton not found")
    if torch.backends.mps.is_available():
        logger.info("MPS backend, using 'bmm' decoder")
        decode = decode_mps
    else:
        logger.info("CPU/CUDA backend, using 'sparse_coo_tensor' decoder")
        decode = decode_cuda
else:
    logger.info("Triton found")
    if os.environ.get("USE_TRITON", "1") == "1":
        logger.info("Triton enabled, using Triton decoder")
        decode = decode_triton
    else:
        logger.info("Triton disabled, using 'sparse_coo_tensor' decoder")
        decode = decode_cuda