UEmbed-9B / examples /transformers_example.py
songtingyu's picture
Update README.md.
ea380ef verified
Raw
History Blame Contribute Delete
3.03 kB
#!/usr/bin/env python
"""Compute UEmbed (Qwen3.5) embeddings with plain `transformers`.
The released checkpoint is a standard Qwen3.5 model whose tokenizer already
appends the N=16 sparse EOS tokens, so no `trust_remote_code` and no processor
patching are needed. This script shows both dense (`last.normal`) and sparse
(`splade.last`) embeddings for text- and image-containing inputs.
Usage:
python examples/transformers_example.py ./models/UEmbed-2B
"""
import argparse
import torch
import torch.nn.functional as F
from transformers import AutoModel, AutoProcessor
from qwen_vl_utils.vision_process import process_vision_info
NUM_EOS_TOKENS = 16 # from sparse_info.json
CONVERSATIONS = [
[{"role": "user", "content": [
{"type": "text", "text": "A woman playing with her dog on a beach at sunset."},
]}],
[{"role": "user", "content": [
{"type": "image", "image": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg"},
{"type": "text", "text": "A woman and her dog on the beach."},
]}],
]
def main():
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("model_path", nargs="?", default="./models/UEmbed-2B")
args = parser.parse_args()
model = AutoModel.from_pretrained(args.model_path, dtype=torch.bfloat16).eval().cuda()
processor = AutoProcessor.from_pretrained(args.model_path, padding_side="right")
texts = processor.apply_chat_template(CONVERSATIONS, add_generation_prompt=True, tokenize=False)
images, videos, video_kwargs = process_vision_info(
CONVERSATIONS, image_patch_size=16, return_video_metadata=True, return_video_kwargs=True
)
inputs = processor(
text=texts, images=images, videos=videos,
padding=True, return_tensors="pt", **(video_kwargs or {}),
).to(model.device)
with torch.no_grad():
hidden_state = model(**inputs).last_hidden_state
# Index of the last real token per sample (before right-padding).
last_idx = inputs.attention_mask.cumsum(dim=1).argmax(dim=1)
rows = torch.arange(len(last_idx))
# Dense: hidden state of the EOS right before the appended sparse tokens.
dense = F.normalize(hidden_state[rows, last_idx - NUM_EOS_TOKENS], p=2, dim=-1) # [B, 2048]
# Sparse: project the N appended EOS hidden states with the per-cluster heads.
sw = torch.load(f"{args.model_path}/sparse_weights.pt", map_location=model.device, weights_only=True)
logits = [
F.linear(hidden_state[rows, last_idx - (NUM_EOS_TOKENS - 1 - i)].to(head.dtype), head, bias)
for i, (head, bias) in enumerate(zip(sw["sparse_lm_heads"], sw["sparse_bias"]))
]
sparse = torch.log1p(F.relu(torch.cat(logits, dim=-1))) # [B, 184016]
print(f"dense: {tuple(dense.shape)}")
print("dense cosine matrix:\n", (dense.float() @ dense.float().T).cpu().numpy().round(4))
print(f"sparse: {tuple(sparse.shape)} nnz={ (sparse > 0).sum(-1).tolist() }")
if __name__ == "__main__":
main()