kimodo-motion-api / kimodo /model /llm2vec /llm2vec_wrapper.py
cs686's picture
Deploy Kimodo ZeroGPU motion API (part 2)
7e976d8 verified
Raw
History Blame Contribute Delete
3.29 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""LLM2Vec encoder wrapper for Kimodo text conditioning."""
import os
import numpy as np
import torch
from .llm2vec import LLM2Vec
class LLM2VecEncoder:
"""LLM2Vec text embeddings."""
def __init__(
self,
base_model_name_or_path: str,
peft_model_name_or_path: str,
dtype: str,
llm_dim: int,
device: str = "auto",
) -> None:
torch_dtype = getattr(torch, dtype)
self.llm_dim = llm_dim
cache_dir = os.environ.get("HUGGINGFACE_CACHE_DIR")
if "TEXT_ENCODERS_DIR" in os.environ:
base_model_name_or_path = os.path.join(os.environ["TEXT_ENCODERS_DIR"], base_model_name_or_path)
peft_model_name_or_path = os.path.join(os.environ["TEXT_ENCODERS_DIR"], peft_model_name_or_path)
self.model = LLM2Vec.from_pretrained(
base_model_name_or_path=base_model_name_or_path,
peft_model_name_or_path=peft_model_name_or_path,
torch_dtype=torch_dtype,
cache_dir=cache_dir,
)
env_device = os.environ.get("TEXT_ENCODER_DEVICE")
if env_device:
device = env_device
if device == "auto":
device = "cuda" if torch.cuda.is_available() else "cpu"
self._device = device
if device is not None:
self.model = self.model.to(device)
self.model.eval()
for p in self.model.parameters():
p.requires_grad = False
def to(self, device: torch.device):
self.model = self.model.to(device)
self._device = str(device) if not isinstance(device, str) else device
return self
def eval(self):
self.model.eval()
return self
def get_device(self):
return self.model.model.device
def __call__(self, text: list[str] | str):
is_string = False
if isinstance(text, str):
text = [text]
is_string = True
with torch.no_grad():
encoded_text = self.model.encode(
text,
# IMPORTANT: different batch sizes unexpectedly change the output embeddings, so we always set it to 1
# here for repeatability no matter how many texts are being encoded. This
# is a fundamental issue with transformers, and is especially bad at lower
# precisions (https://github.com/huggingface/transformers/issues/25420#issuecomment-1775317535)
# note: this is an internal batch size used by llm2vec - the text list can still be of arbitrary length.
batch_size=1,
show_progress_bar=False,
device=self._device,
)
assert len(encoded_text.shape)
assert self.llm_dim == encoded_text.shape[-1]
encoded_text = encoded_text[:, None]
lengths = np.ones(len(encoded_text), dtype=int).tolist()
if is_string:
encoded_text = encoded_text[0]
lengths = lengths[0]
encoded_text = torch.tensor(encoded_text).to(self._device)
return encoded_text, lengths