main
py 127 lines 4.59 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 import torch
6 from transformers import PreTrainedConfig
7
8 from vllm.config.lora import LoRAConfig
9 from vllm.platforms import current_platform
10
11 from .replicated_linear import ReplicatedLinearWithLoRA
12
13
14 class ClassificationHeadWithLoRA(ReplicatedLinearWithLoRA):
15 def create_lora_weights(
16 self,
17 max_loras: int,
18 lora_config: LoRAConfig,
19 model_config: PreTrainedConfig | None = None,
20 ) -> None:
21 # Preserve ordinary LoRA A/B support for classification heads.
22 super().create_lora_weights(max_loras, lora_config, model_config)
23
24 self.max_lora_cls_labels = lora_config.max_lora_cls_labels or self.output_size
25 self.padded_num_labels = max(self.output_size, self.max_lora_cls_labels)
26 self.full_weight_stacked = torch.zeros(
27 max_loras,
28 1,
29 self.padded_num_labels,
30 self.input_size,
31 dtype=lora_config.lora_dtype,
32 device=self.device,
33 )
34 self.full_bias_stacked = torch.zeros(
35 max_loras,
36 self.padded_num_labels,
37 dtype=self.base_layer.params_dtype,
38 device=self.device,
39 )
40 self.full_module_enabled = torch.zeros(
41 max_loras, dtype=torch.bool, device=self.device
42 )
43 # Zero marks an unused slot; positive values are unpadded label counts.
44 self.full_module_num_labels = [0] * max_loras
45 self._output_lora_indices: tuple[int, ...] = ()
46
47 def set_output_mapping(self, slot_indices: tuple[int, ...]) -> None:
48 self._output_lora_indices = slot_indices
49
50 def reset_module_to_save(self, index: int) -> None:
51 self.full_weight_stacked[index].zero_()
52 self.full_bias_stacked[index].zero_()
53 self.full_module_enabled[index] = False
54 self.full_module_num_labels[index] = 0
55
56 def set_module_to_save(
57 self,
58 index: int,
59 weight: torch.Tensor,
60 bias: torch.Tensor | None,
61 ) -> None:
62 self.reset_module_to_save(index)
63 num_labels = weight.size(0)
64 self.full_weight_stacked[index, 0, :num_labels].copy_(weight, non_blocking=True)
65 if bias is not None:
66 self.full_bias_stacked[index, :num_labels].copy_(bias, non_blocking=True)
67 self.full_module_num_labels[index] = num_labels
68 self.full_module_enabled[index] = True
69
70 def forward(self, input_: torch.Tensor) -> torch.Tensor | list[torch.Tensor]:
71 # TODO base_result maybe don't need compute truly.
72 base_result = super().forward(input_)
73 base_result = base_result[0] if isinstance(base_result, tuple) else base_result
74
75 slot_indices = self._output_lora_indices
76 assert len(slot_indices) == base_result.size(0), (
77 "Classification rows do not match LoRA request mapping"
78 )
79 num_labels = [
80 self.full_module_num_labels[index] if index >= 0 else 0
81 for index in slot_indices
82 ]
83
84 if not any(num_labels):
85 return base_result
86
87 output = base_result.new_zeros(base_result.size(0), self.padded_num_labels)
88 output[:, : self.output_size].copy_(base_result)
89 self.punica_wrapper.apply_lora_full_linear(
90 output,
91 input_.to(self.full_weight_stacked.dtype),
92 self.full_weight_stacked,
93 self.full_bias_stacked,
94 self.full_module_enabled,
95 )
96
97 # Rows without a full head keep the base classifier output size.
98 num_labels = [size or self.output_size for size in num_labels]
99 if len(set(num_labels)) == 1:
100 return output[:, : num_labels[0]]
101
102 output = [output[row, :size] for row, size in enumerate(num_labels)]
103 return output
104
105 def _apply_lora_to_output(
106 self, x: torch.Tensor, output: torch.Tensor
107 ) -> torch.Tensor:
108 original_shape = output.shape if output.ndim == 3 else None
109 if x.ndim == 3 and output.ndim == 3:
110 output = output.flatten(0, 1)
111 x = x.flatten(0, 1)
112
113 # Classification outputs are request-level, so use prompt-based LoRA routing.
114 lora_output: torch.Tensor | None = self.punica_wrapper.add_lora_logits(
115 output,
116 x.to(self.lora_a_stacked[0].dtype),
117 self.lora_a_stacked[0],
118 self.lora_b_stacked[0],
119 1.0,
120 )
121 if not current_platform.can_update_inplace():
122 output = lora_output
123
124 if original_shape is not None:
125 output = output.reshape(original_shape)
126
127 return output