Repository navigation
Decision: batch the prompts of a request with slice_logits_to_keep #50548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
449a401
adef6cc
05b1f14
3f92efc
ae0829d
4e05e94
99c0040
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,17 +19,49 @@ | |
| from pathlib import Path | ||
|
|
||
| import torch | ||
| from torch.nn.utils.rnn import pad_sequence | ||
|
|
||
| from ..configuration_utils import PreTrainedConfig | ||
| from ..utils import cached_file | ||
| from .configuration_utils import DecisionConfig | ||
| from .template_utils import Question | ||
|
|
||
|
|
||
| def process_scores(decision_config, input_ids, marks, questions, scores) -> list[torch.Tensor]: | ||
| """The logits processors of the decision config on the option scores of each question of a prompt.""" | ||
| tail = input_ids[0, int(marks[-1]) :] # the tokens after the last mark, where the templates write the state | ||
| return [decision_config.logits_processors[q.type](tail, s.float()) for q, s in zip(questions, scores)] | ||
| def batch_prompts(prompts: list[tuple[dict, list[Question]]]) -> dict[str, torch.Tensor]: | ||
| """ | ||
| The prompts of a request as one right-padded batch, with `logits_to_keep` the mask of the positions read: the | ||
| token before each mark, so the read positions of a batch are its questions in order. | ||
| """ | ||
| inputs = [prompt for prompt, _ in prompts] | ||
| reads = [] | ||
| for prompt in inputs: | ||
| read = torch.zeros(prompt["input_ids"].shape[-1], dtype=torch.bool, device=prompt["input_ids"].device) | ||
| read[prompt["marks"] - 1] = True | ||
| reads.append(read) | ||
| batch = { | ||
| "attention_mask": pad_sequence([torch.ones_like(read, dtype=torch.long) for read in reads], batch_first=True), | ||
| "logits_to_keep": pad_sequence(reads, batch_first=True), | ||
| } | ||
| for key in inputs[0].keys() - {"marks", "attention_mask"}: | ||
| values = [prompt[key] for prompt in inputs] | ||
| if values[0].shape[-1] == inputs[0]["input_ids"].shape[-1]: # per token: `input_ids`, `mm_token_type_ids`... | ||
| batch[key] = pad_sequence([value[0] for value in values], batch_first=True) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fun gotcha here: if a processor's output's last dimension can equal the length of the first prompt, then this won't work well as it won't concatenate stuff together but instead treat it as per-token and pad it, keeping only its first row per prompt. example of where it fails: import torch
from transformers.decision.modeling_utils import batch_prompts
def prompt(seq_len):
return {"input_ids": torch.ones(1, seq_len, dtype=torch.long), "marks": torch.tensor([seq_len]),
"pixel_values": torch.randn(4, 1176)}
print(batch_prompts([(prompt(1175), []), (prompt(1175), [])])["pixel_values"].shape) # (8, 1176): correct
print(batch_prompts([(prompt(1176), []), (prompt(1176), [])])["pixel_values"].shape) # (2, 1176): incorrect(1176 was chosen here as that's the value Qwen2.5 VL uses) |
||
| else: # ponytail: per image, already flat over images (`pixel_values`, `image_grid_thw`) like Qwen-VL | ||
|
ArthurZucker marked this conversation as resolved.
Resolved
|
||
| batch[key] = torch.cat(values) | ||
| return batch | ||
|
|
||
|
|
||
| def process_scores(decision_config, batch, questions, scores) -> list[list[torch.Tensor]]: | ||
| """ | ||
| The logits processors of the decision config on the option scores of each question of each prompt of a batch from | ||
| [`batch_prompts`], `scores` being those of all its questions in order. | ||
| """ | ||
| scores, processed = iter(scores), [] | ||
| lengths = batch["attention_mask"].sum(-1).tolist() | ||
| for input_ids, read, length, group in zip(batch["input_ids"], batch["logits_to_keep"], lengths, questions): | ||
| tail = input_ids[int(read.nonzero()[-1]) + 1 : length] # the tokens after the last mark: the state | ||
| processed.append([decision_config.logits_processors[q.type](tail, next(scores).float()) for q in group]) | ||
| return processed | ||
|
|
||
|
|
||
| def _hub_kwargs(kwargs: dict) -> dict: | ||
|
|
@@ -53,6 +85,8 @@ def from_pretrained(cls, pretrained_model_name_or_path, *args, config=None, deci | |
| head_config, decision_config = cls.checkpoint_config(pretrained_model_name_or_path, **kwargs) | ||
| if head_config is None and config is not None: | ||
| raise ValueError(f"{pretrained_model_name_or_path} has no {cls.__name__} head next to its weights.") | ||
| if head_config is not None and config is not None: # the auto classes set `attn_implementation` on it | ||
| kwargs.setdefault("attn_implementation", config._attn_implementation_internal) | ||
| config = head_config or config | ||
| model = super().from_pretrained(pretrained_model_name_or_path, *args, config=config, **kwargs) | ||
| if decision_config is None: # the decision config of the repo, read now like `generation_config` | ||
|
|
@@ -100,29 +134,28 @@ def save_pretrained(self, save_directory, *args, **kwargs): | |
| def decide( | ||
| self, | ||
| input_ids: torch.LongTensor, | ||
| marks: torch.LongTensor, | ||
| decision_questions: list[Question], | ||
| logits_to_keep: torch.BoolTensor, | ||
| decision_questions: list[list[Question]], | ||
| decision_config: DecisionConfig | None = None, | ||
| **kwargs, | ||
| ) -> list[torch.Tensor]: | ||
| ) -> list[list[torch.Tensor]]: | ||
| """ | ||
| Scores the options of each question of a prompt from `decision_config.prepare_inputs`: one forward pass, | ||
| then the logits processors of the decision config. Returns the scores of each question, which | ||
| Scores the options of each question of a batch of prompts from [`batch_prompts`]: one forward pass, then the | ||
| logits processors of the decision config. Returns the scores of each question of each prompt, which | ||
| `Question.answer` turns into answers. | ||
| """ | ||
| decision_config = decision_config or self.decision_config | ||
| inputs = {"input_ids": input_ids, **kwargs} | ||
| num_options = torch.tensor([[len(question.options) for question in decision_questions]]) | ||
| question_types = [Question.questions_types.index(question.type) for question in decision_questions] | ||
| batch = {"input_ids": input_ids, "logits_to_keep": logits_to_keep, **kwargs} | ||
| batch = {key: value.to(self.device) if hasattr(value, "to") else value for key, value in batch.items()} | ||
| questions = [question for group in decision_questions for question in group] | ||
| num_options = [len(question.options) for question in questions] | ||
| question_types = [Question.questions_types.index(question.type) for question in questions] | ||
| scores = self( | ||
| marks=marks[None].to(self.device), | ||
| num_options=num_options.to(self.device), | ||
| question_type_ids=torch.tensor([question_types], device=self.device), | ||
| **{key: value.to(self.device) if hasattr(value, "to") else value for key, value in inputs.items()}, | ||
| ) | ||
| return process_scores( | ||
| decision_config, inputs["input_ids"], marks, decision_questions, scores.split(num_options[0].tolist()) | ||
| num_options=torch.tensor(num_options, device=self.device), | ||
| question_type_ids=torch.tensor(question_types, device=self.device), | ||
| **batch, | ||
| ) | ||
| return process_scores(decision_config, batch, decision_questions, scores.split(num_options)) | ||
|
|
||
|
|
||
| __all__ = ["DecisionMixin"] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only looks at the first input for its keys, but it should arguably look at all of them?
Right now if a single input has an image and it is the first of the batch, then it will fail on the following ones. If it's not the first of the batch, then that input's image will be skipped.