main
py 204 lines 7.04 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from abc import ABC, abstractmethod
4 from collections.abc import Set
5 from typing import TypeAlias
6
7 import torch
8 import torch.nn as nn
9
10 from vllm.model_executor.layers.pooler import ActivationFn, ClassifierFn, ProjectorFn
11 from vllm.tasks import PoolingTask
12 from vllm.v1.pool.metadata import PoolingMetadata
13
14 from .methods import SequencePoolingMethodOutput
15
16 SequencePoolerHeadOutput: TypeAlias = torch.Tensor | list[torch.Tensor]
17
18
19 class SequencePoolerHead(nn.Module, ABC):
20 @abstractmethod
21 def get_supported_tasks(self) -> Set[PoolingTask]:
22 raise NotImplementedError
23
24 @abstractmethod
25 def forward(
26 self,
27 pooled_data: SequencePoolingMethodOutput,
28 pooling_metadata: PoolingMetadata,
29 ) -> SequencePoolerHeadOutput:
30 raise NotImplementedError
31
32
33 class EmbeddingPoolerHead(SequencePoolerHead):
34 def __init__(
35 self,
36 projector: ProjectorFn | None = None,
37 head_dtype: torch.dtype | str | None = None,
38 activation: ActivationFn | None = None,
39 ) -> None:
40 super().__init__()
41
42 self.projector = projector
43 self.head_dtype = head_dtype
44 self.activation = activation
45
46 def extra_repr(self) -> str:
47 attrs = []
48 if self.head_dtype is not None:
49 attrs.append(f"head_dtype={self.head_dtype}")
50 if self.projector is not None:
51 attrs.append("projector=True")
52 if self.activation is not None:
53 attrs.append(f"activation={self.activation.__class__.__name__}")
54 return ", ".join(attrs)
55
56 def get_supported_tasks(self) -> Set[PoolingTask]:
57 return {"embed"}
58
59 def forward(
60 self,
61 pooled_data: SequencePoolingMethodOutput,
62 pooling_metadata: PoolingMetadata,
63 ) -> SequencePoolerHeadOutput:
64 pooling_params = pooling_metadata.pooling_params
65 if len(pooled_data) != len(pooling_params):
66 raise ValueError(
67 f"pooled_data length ({len(pooled_data)}) does not match "
68 f"pooling_params length ({len(pooling_params)})"
69 )
70
71 if isinstance(pooled_data, list):
72 pooled_data = torch.stack(pooled_data)
73 # pooled_data shape: [batchsize, hidden_size]
74
75 if self.head_dtype is not None:
76 pooled_data = pooled_data.to(self.head_dtype)
77
78 # Apply ST projector
79 if self.projector is not None:
80 embeddings = self.projector(pooled_data)
81 else:
82 embeddings = pooled_data
83 # embeddings shape: [batchsize, embedding_size]
84
85 # for matryoshka representation
86 dimensions_list = [pooling_param.dimensions for pooling_param in pooling_params]
87 if any(d is not None for d in dimensions_list):
88 # change the output dimension
89 if len(embeddings) != len(dimensions_list):
90 raise ValueError(
91 f"embeddings length ({len(embeddings)}) does not match "
92 f"dimensions_list length ({len(dimensions_list)})"
93 )
94 if len(set(dimensions_list)) == 1 and not isinstance(embeddings, list):
95 # if all dimensions are the same
96 d = dimensions_list[0]
97 embeddings = embeddings[..., :d]
98 else:
99 embeddings = [
100 vecs if d is None else vecs[..., :d]
101 for vecs, d in zip(embeddings, dimensions_list)
102 ]
103
104 # for normalize
105 if self.activation is not None:
106 flags = [p.use_activation for p in pooling_params]
107 if len(set(flags)) == 1:
108 if flags[0]:
109 embeddings = self.activation(embeddings)
110 else:
111 embeddings = [
112 self.activation(vecs) if f else vecs
113 for vecs, f in zip(embeddings, flags)
114 ]
115
116 # embeddings shape: [batchsize, embedding_size]
117 return embeddings
118
119
120 class ClassifierPoolerHead(SequencePoolerHead):
121 def __init__(
122 self,
123 classifier: ClassifierFn | None = None,
124 logit_mean: float | None = None,
125 logit_sigma: float | None = None,
126 head_dtype: torch.dtype | str | None = None,
127 activation: ActivationFn | None = None,
128 ) -> None:
129 super().__init__()
130
131 self.classifier = classifier
132 self.logit_mean = logit_mean
133 self.logit_sigma = logit_sigma
134 self.head_dtype = head_dtype
135 self.activation = activation
136
137 def extra_repr(self) -> str:
138 attrs = []
139 if self.head_dtype is not None:
140 attrs.append(f"head_dtype={self.head_dtype}")
141 if self.classifier is not None:
142 attrs.append("classifier=True")
143 if self.logit_mean is not None:
144 attrs.append(f"logit_mean={self.logit_mean}")
145 if self.logit_sigma is not None:
146 attrs.append(f"logit_sigma={self.logit_sigma}")
147 if self.activation is not None:
148 attrs.append(f"activation={self.activation.__class__.__name__}")
149 return ", ".join(attrs)
150
151 def get_supported_tasks(self) -> Set[PoolingTask]:
152 return {"classify"}
153
154 def forward(
155 self,
156 pooled_data: SequencePoolingMethodOutput,
157 pooling_metadata: PoolingMetadata,
158 ) -> SequencePoolerHeadOutput:
159 pooling_params = pooling_metadata.pooling_params
160 if len(pooled_data) != len(pooling_params):
161 raise ValueError(
162 f"pooled_data length ({len(pooled_data)}) does not match "
163 f"pooling_params length ({len(pooling_params)})"
164 )
165
166 if isinstance(pooled_data, list):
167 pooled_data = torch.stack(pooled_data)
168 # pooled_data shape: [batchsize, hidden_size]
169
170 if self.head_dtype is not None:
171 pooled_data = pooled_data.to(self.head_dtype)
172
173 if self.classifier is not None:
174 logits: SequencePoolerHeadOutput = self.classifier(pooled_data)
175 else:
176 logits = pooled_data
177
178 # logits shape: [batchsize, num_labels]
179 # Affine score calibration: activation((logit - mean) / sigma)
180 if self.logit_mean is not None:
181 logits = (
182 [value - self.logit_mean for value in logits]
183 if isinstance(logits, list)
184 else logits - self.logit_mean
185 )
186 if self.logit_sigma is not None:
187 logits = (
188 [value / self.logit_sigma for value in logits]
189 if isinstance(logits, list)
190 else logits / self.logit_sigma
191 )
192
193 if self.activation is not None:
194 flags = [p.use_activation for p in pooling_params]
195 if len(set(flags)) == 1:
196 logits = self.activation(logits) if flags[0] else logits
197 else:
198 logits = [
199 self.activation(vecs) if f else vecs
200 for vecs, f in zip(logits, flags)
201 ]
202
203 # logits shape: [batchsize, num_labels]
204 return logits