Improve performance of encoding Moss prompts with a batch size > 1

This commit is contained in:
Jaret Burkett committed 2026-10-19 10:15:45 -06:00
1 parent 2d4c89d392
commit 04733873cf
1 file changed
+41 -14
@@ -117,6 +117,30 @@ def _hub_file_folder(repo_id: str, filename: str) -> str:
return os.path.dirname(path) return os.path.dirname(path)
@torch.no_grad()
def encode_text_batch(text_encoder, tokenizer, texts, max_length, device) -> List[torch.Tensor]:
"""Chat-templated prompts -> per-item (L_i, D) final states from ONE forward.
Right-padded, no attention mask: for the causal model the pads sit after
each prompt's real tokens, so neither causal attention nor the gated-delta
recurrence can reach them -- each item's first L_i states equal its
unpadded forward (the HF mask path only zeroes padded states, which is
left-padding hygiene, and would also drop attention off its fast path).
Default arange positions are right for right padding."""
enc = tokenizer(
texts,
return_tensors="pt",
padding=True,
padding_side="right",
truncation=True,
max_length=max_length,
)
lengths = enc["attention_mask"].sum(1).tolist()
states = text_encoder(
input_ids=enc["input_ids"].to(device), use_cache=False
).last_hidden_state
return [states[i, :n] for i, n in enumerate(lengths)]
def _drop_repeats(images): def _drop_repeats(images):
"""ctrl_img_1 defaults to ctrl_img, so a single reference can arrive twice.""" """ctrl_img_1 defaults to ctrl_img, so a single reference can arrive twice."""
kept = [] kept = []
@@ -559,21 +583,24 @@ class MossImageModel(BaseModel):
else: else:
control_images = [None] * len(prompt) control_images = [None] * len(prompt)
embeds = [] # text-only prompts go through ONE right-padded forward (exact, see
# one unpadded forward per prompt, like the vendor (padding would leak # encode_text_batch); prompts with references stay one forward each
# into the gated-delta recurrence) # (their image-pad runs differ per prompt)
for text, images in zip(prompt, control_images): embeds: List[Optional[torch.Tensor]] = [None] * len(prompt)
text_only = [i for i, images in enumerate(control_images) if not images]
if text_only:
states = encode_text_batch(
self.text_encoder,
self.tokenizer,
[self._chat(prompt[i]) for i in text_only],
self.max_text_length,
self.device_torch,
)
for i, st in zip(text_only, states):
embeds[i] = st.to(self.torch_dtype)
for i, (text, images) in enumerate(zip(prompt, control_images)):
if images: if images:
states = self._encode_with_images(text, images) embeds[i] = self._encode_with_images(text, images).to(self.torch_dtype)
else:
input_ids = self.tokenizer(
self._chat(text),
return_tensors="pt",
truncation=True,
max_length=self.max_text_length,
)["input_ids"].to(self.device_torch)
states = self.text_encoder(input_ids=input_ids, use_cache=False).last_hidden_state[0]
embeds.append(states.to(self.torch_dtype))
return AdvancedPromptEmbeds(text_embeds=embeds) return AdvancedPromptEmbeds(text_embeds=embeds)
def get_loss_target(self, *args, **kwargs): def get_loss_target(self, *args, **kwargs):