from transformers import pipeline
model_id = "openai/clip-vit-large-patch14"
pipe = pipeline("zero-shot-image-classification", model=model_id)
labels = ["bee on a flower", "bee in the hive", "bee in the sky"]
pipe(image="https://oreil.ly/ne2UQ", candidate_labels=labels)
[{'score': 0.8133557438850403, 'label': 'bee on a flower'},
{'score': 0.1714152991771698, 'label': 'bee in the hive'},
{'score': 0.015228924341499805, 'label': 'bee in the sky'}]
from PIL import Image
import requests
from transformers import AutoProcessor,#AutoProcessor 负责预处理(图像 + 文本)
AutoModelForZeroShotImageClassification #自动加载零样本分类模型
model = AutoModelForZeroShotImageClassification.from_pretrained
("openai/clip-vit-large-patch14").to("cuda")
processor = AutoProcessor.from_pretrained("openai/clip-vit-largepatch14")#processor:负责把原始图像和文本转成模型需要的张量格式
url =
"https://huggingface.co/datasets/vlmbook/images/resolve/main/bee.jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(text=["a bee on a flower", "a bee in a hive"],
images=image, return_tensors="pt", padding=True).to("cuda")
outputs = model(**inputs)
probs = outputs.logits_per_image.softmax(dim=1)
print(probs)
import torch
from transformers import AutoProcessor, AutoModel, AutoTokenizer
device = torch.device('cuda' if torch.cuda.is_available() else "cpu")
model_id = "google/siglip-base-patch16-224"
model = AutoModel.from_pretrained(model_id, device_map="auto").eval()
processor = AutoProcessor.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id)
import torch
import requests
from PIL import Image
from transformers import (
AutoProcessor,
AutoModelForZeroShotObjectDetection,
)
# ----------------------------
# 1. 选择设备
# ----------------------------
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {DEVICE}")
# ----------------------------
# 2. 加载模型和处理器 (OWLv2)
# ----------------------------
model_id = "google/owlv2-base-patch16-ensemble"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForZeroShotObjectDetection.from_pretrained(model_id).to(DEVICE)
model.eval()
# ----------------------------
# 3. 加载图像
# ----------------------------
image_url = "https://huggingface.co/datasets/vlmbook/images/resolve/main/bee.jpg"
image = Image.open(requests.get(image_url, stream=True).raw).convert("RGB")
# ----------------------------
# 4. 定义查询文本
# OWLv2 约定: 外层 list 是 batch, 内层 list 是该图的多个类别
# ----------------------------
text_queries = [["bee on the flower", "bee in the hive"]]
# ----------------------------
# 5. 预处理输入
# ----------------------------
inputs = processor(images=image, text=text_queries, return_tensors="pt").to(DEVICE)
# ----------------------------
# 6. 前向推理
# ----------------------------
with torch.no_grad():
outputs = model(**inputs)
# ----------------------------
# 7. 后处理: 得到 boxes / scores / labels
# ----------------------------
target_sizes = torch.tensor([image.size[::-1]]) # (height, width)
results = processor.post_process_grounded_object_detection(
outputs,
threshold=0.3,
target_sizes=target_sizes,
)
# ----------------------------
# 8. 打印结果
# ----------------------------
result = results[0]
for box, score, label in zip(result["boxes"], result["scores"], result["labels"]):
box = [round(coord, 2) for coord in box.tolist()]
print(f"Detected '{label}' with confidence {score:.2f} at {box}")
from datasets import load_dataset
dataset = load_dataset("HuggingFaceM4/FineVisionMax",
split="train",
streaming=True)
print(next(iter(dataset)))
from PIL import Image
def convert_images_to_rgb(example):
image = example["images"][0] # this dataset only has one image per
sample
if isinstance(image, Image.Image):
if image.mode != "RGB":
image = image.convert("RGB")
example["images"] = [image]
return example
train_ds = train_ds.map(convert_images_to_rgb, num_proc=8)
test_ds = test_ds.map(convert_images_to_rgb, num_proc=8)
import torch
from transformers import AutoModelForImageTextToText, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForImageTextToText.from_pretrained(
"Qwen/Qwen3-VL-2B-Instruct",
device_map="auto",
torch_dtype=torch.bfloat16,
quantization_config=bnb_config,
)
peft_config = LoraConfig(
r=8,
lora_alpha=8,
lora_dropout=0.1,
target_modules=[
"down_proj", "o_proj", "k_proj", "q_proj",
"gate_proj", "up_proj", "v_proj",
],
use_dora=True,
init_lora_weights="gaussian",
)
peft_model = get_peft_model(model, peft_config)
from trl import DPOConfig, DPOTrainer
training_args = DPOConfig(
output_dir="Qwen3-VL-2B-MPO",
loss_type=["sigmoid", "bco_pair", "sft"],
loss_weights=[0.8, 0.2, 1.0],
per_device_train_batch_size=4,
per_device_eval_batch_size=4,
gradient_accumulation_steps=8,
num_train_epochs=1,
dataset_num_proc=1,
dataloader_num_workers=8,
logging_steps=10,
report_to="trackio",
push_to_hub=True,
save_strategy="steps",
save_steps=10,
save_total_limit=1,
eval_steps=10,
eval_strategy="steps",
)
trainer = DPOTrainer(
model=peft_model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=test_dataset,
)
trainer.train()
test_dataset[0]["images"][0]
test_dataset[0]["prompt"][0]["content"][1]["text"]
from transformers import pipeline
pipe = pipeline("image-text-to-text", "merve/Qwen3-VL-2B-MPO")
messages = [
{
"role": "user",
"content": [
{
"type": "image",
"image": test_dataset[0]["images"][0],
},
{
"type": "text",
"text": test_dataset[0]["prompt"][0]["content"][1]
["text"],
},
],
}
]
output_text = pipe(messages)
import importlib.util
import numpy as np
import faiss-cpu
import torch
from huggingface_hub import hf_hub_download
from typing import Dict, List, Tuple
# We get the Qwen3-VL Embedding model again and
# we prepare the functions to embed text/video
script_path = hf_hub_download(
repo_id="Qwen/Qwen3-VL-Embedding-2B",
filename="scripts/qwen3_vl_embedding.py",
)
spec = importlib.util.spec_from_file_location("qwen3_vl_embedding",
script_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
# Explicitly add the module to sys.modules
sys.modules['qwen3_vl_embedding'] = mod
Qwen3VLEmbedder = mod.Qwen3VLEmbedder
embedder = Qwen3VLEmbedder(
model_name_or_path="Qwen/Qwen3-VL-Embedding-2B",
torch_dtype=torch.float16
)
# Embedding helpers
def embed_text(text: str) -> np.ndarray:
"""Embed a text query. Returns (D,) numpy array, L2-normalized."""
emb = embedder.process([{"text": text}])
return emb[0].detach().cpu().float().numpy()
def embed_video_segment(
path: str, fps: float = 1.0, max_frames: int = 32) -> np.ndarray:
"""Embed a video segment. Returns (D,) numpy array, L2-normalized."""
emb = embedder.process([{"video": path, "fps": fps, "max_frames":
max_frames}])
return emb[0].detach().cpu().float().numpy()
#Segment, embed, and index our demo videos
all_segments: List[str] = []
segment_meta: List[Dict] = []
for vid in [spaghetti_mp4, minecraft_mp4]:
out_dir = Path("video_segments") / Path(vid).stem
segs = segment_video(vid, str(out_dir), segment_seconds=5)
for seg in segs:
all_segments.append(seg)
segment_meta.append({"source_video": vid, "segment_path": seg})
# Embed all segments (offline step --- do this once)
vectors = np.stack(
[embed_video_segment(seg) for seg in all_segments], axis=0
) # (N, D)
# Build FAISS index. Since embeddings are L2-normalized,
# inner product (IndexFlatIP) equals cosine similarity.
index = faiss.IndexFlatIP(vectors.shape[1])
index.add(vectors.astype(np.float32))
# Query function
def search_segments(query: str, k: int = 5) -> List[Tuple[float, Dict]]:
"""Retrieve top-k segments for a text query."""
q = embed_text(query)[None, :] # (1, D)
scores, ids = index.search(q.astype(np.float32), k)
return [
(float(s), segment_meta[i])
for s, i in zip(scores[0], ids[0])
if i >= 0
]
# Try it
for q in ["someone eating pasta", "a videogame"]:
print(f"\nQuery: {q}")
for score, meta in search_segments(q, k=3):
print(f" score={score:.3f} seg={meta['segment_path']}")

键值缓存(KV Cache)加速推理的原理
一、先看问题:自回归生成为什么慢?
Transformer 生成文本是**自回归(autoregressive)**的------一次生成一个 token,然后把新 token 拼到输入后面,再预测下一个:
```
第1步: 输入 A → 预测 B
第2步: 输入 A, B → 预测 C
第3步: 输入 A, B, C → 预测 D
...
```
**朴素实现**的致命问题:每一步都要把**整个序列**重新送进模型算一遍。生成第 N 个 token 时,前面 N-1 个 token 的 Key/Value 被**重复计算了 N 次**。序列越长,浪费越大,总计算量约为 O(N\^2)(准确说是 O(N\^3) 级别的前缀重复计算)。
二、核心洞察:历史 token 的 K、V 不会变
在自注意力(Self-Attention)里,每个 token 会产生三个向量:
Q = XW_Q,\\quad K = XW_K,\\quad V = XW_V
注意力计算:
\\text{Attention}(Q,K,V) = \\text{softmax}\\left(\\frac{QK\^\\top}{\\sqrt{d}}\\right)V
关键点:
> **第 t 步生成时,前 t-1 个 token 的 Key 和 Value 只取决于它们自己和它们之前的位置------与后面新来的 token 无关,因此它们的 K、V 是完全确定的、不会改变的。**
既然不变,为什么每次都要重算?**把它们缓存起来复用即可。** 这就是 KV Cache 的全部思想。
三、KV Cache 怎么工作
有缓存时
**Prefill 阶段(处理 prompt)**:
-
一次性把整个 prompt 送进模型
-
计算出每一层的 K、V,**存入缓存**
-
同时得到第一个生成 token
**Decode 阶段(逐 token 生成)**:
-
每步**只输入最新生成的 1 个 token**
-
用当前 token 算出它的 q_t, k_t, v_t
-
把 k_t, v_t **追加**到缓存的 K、V 后面
-
注意力计算时,Q 只来自当前 token,而 K, V 直接用完整的缓存
-
得到下一个 token,循环
```
无缓存: 每步输入 A, B, C, ... 全部 ← 重复算
有缓存: 每步输入 最新token ← K,V 从缓存取
```
四、计算量对比
设序列长度为 n,隐藏维度为 d,共 L 层。
| 方案 | 生成 n 个 token 的总计算量 |
|---|---|
| **无 KV Cache** | 每步重算全序列 → O(n\^2 \\cdot d)(注意力部分) |
| **有 KV Cache** | 每步只算 1 个新 token → O(n \\cdot d) |
**从 O(n\^2) 降到 O(n)**,长序列时加速比可达数十倍甚至上百倍。
直观理解:
-
无缓存时,生成第 1000 个 token 要算 1000 个 token 的注意力
-
有缓存时,生成第 1000 个 token 只算 1 个 token 的注意力
五、缓存的形状
每层、每个注意力头都需要存 K 和 V。典型形状:
```
K_cache: batch, num_heads, seq_len, head_dim
V_cache: batch, num_heads, seq_len, head_dim
```
随着生成进行,`seq_len` 不断增长------这就是为什么 KV Cache 的**显存占用会随生成长度线性增长**。
显存估算(近似):
\\text{显存} \\approx 2 \\times L \\times H \\times d_{head} \\times n \\times \\text{batch} \\times \\text{dtype\\_size}
其中因子 2 来自 K 和 V 各一份。这在大模型长上下文场景下非常可观(几十 GB 级别)。
六、代价与权衡
KV Cache 是**用显存换时间**:
| 收益 | 代价 |
|---|---|
| 计算量 O(n\^2) \\to O(n) | 显存占用随序列线性增长 |
| 生成速度大幅提升 | 长上下文/大 batch 时可能 OOM |
| 每步延迟显著降低 | 实现上需要管理缓存指针 |
正因如此,衍生出大量**优化技术**,都是为了压缩 KV Cache:
-
**MQA / GQA**(Multi-Query / Grouped-Query Attention):多个头共享 K、V,直接减少缓存份数
-
**PagedAttention**(vLLM):像操作系统分页一样管理 KV 缓存,减少碎片
-
**KV Cache 量化**:用 INT8/INT4 存 K、V
-
**滑动窗口 / StreamingLLM**:只保留最近若干 token 的缓存
-
**H2O、SnapKV 等**:丢弃"不重要"的 token 的 K、V
七、一个重要区分:Prefill vs Decode
| 阶段 | 输入 | 特点 | 瓶颈 |
|---|---|---|---|
| **Prefill**(预填充) | 整个 prompt | 并行计算,算力密集 | 计算(compute-bound) |
| **Decode**(解码) | 每次 1 个 token | 串行,每步都要读完整 KV Cache | 显存带宽(memory-bound) |
KV Cache 主要加速的是 **Decode 阶段**。而且 Decode 阶段因为要反复读取庞大的 KV Cache,往往**受显存带宽限制**而非算力限制------这也是为什么很多推理优化(如量化、GQA)在 decode 阶段收益特别明显。
八、一句话总结
> Transformer 自回归生成时,历史 token 的 Key/Value 一旦算出就不再改变。**KV Cache 把它们缓存下来,让每一步只需计算新 token 的注意力,避免重复计算整个前缀**,从而把生成复杂度从 O(n\^2) 降到 O(n),大幅提升推理速度------代价是显存占用随序列长度线性增长。
这正是几乎所有现代 LLM 推理框架(vLLM、TensorRT-LLM、llama.cpp、HF `generate`)默认开启 KV Cache 的原因。
PEFT:
低秩适应
(LoRA) 和权重分解低秩适应 (DoRA)
SFT监督微调
RLHF 从人类反馈中进行强化学习
假设您的权重形状为 d × k。当进行完全微调时,您对此权重矩阵所做的所有更改都将导
致 d × k 次更新。 LoRA 不是微调所有权重,而是参数化两个矩阵 B 和 A 中的权重更新,
其中 B 具有形状 d × r,A 具有形状 r × k。将 BA 相乘并与原始权重矩阵相加即可得到
更新的权重。 B 和 A 是低阶矩阵(即 r << min(d, k)),我们冻结原始权重。因此,在
应用 LoRA 时,我们的可训练参数较少。
部署:
docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
--env "HF_TOKEN= $HF_TOKEN " \
-p 8000 :8000 \
--ipc = host \
vllm/vllm-openai:latest \
--model Qwen/Qwen2.5-VL-3B-Instruct \
--max-model-len 4096 \
--limit-mm-per-prompt '{"image":2}
TRL 是 Hugging Face 推出的一个**全栈后训练库**,全称是 **Transformers Reinforcement Learning**。它的核心目标是让开发者能够方便地对基础模型进行**后训练(Post-training)**,也就是在预训练模型的基础上,通过指令微调、偏好对齐或强化学习,让模型更符合人类偏好、更擅长特定任务。
🎯 核心功能:覆盖后训练全流程
TRL 把模型后训练的不同阶段都封装成了简单的"训练器"(Trainer),你只需要调用对应的类,就能用几行代码启动训练。
| 训练阶段 | 核心训练器 | 用途说明 |
| :--- | :--- | :--- |
| **监督微调 (SFT)** | `SFTTrainer` | 最基础的起点,用示例数据教会模型遵循指令。 |
| **偏好对齐 (DPO/KTO)** | `DPOTrainer` / `KTOTrainer` | 直接利用"好/坏"的偏好数据优化模型,无需单独的奖励模型。DPO 曾是 Llama 3 的训练方法之一。 |
| **强化学习 (GRPO/PPO)** | `GRPOTrainer` / `PPOTrainer` | 通过奖励函数进行在线优化。其中 GRPO 比 PPO 更省显存,DeepSeek-R1 就用了这个方法。 |
| **奖励建模** | `RewardTrainer` | 训练一个给模型输出打分的"奖励模型",为在线 RL 方法提供信号。 |
⚡ 上手方式:代码或命令行
你可以选择用 Python 代码精细控制,也可以直接用命令行快速启动。
**Python 代码示例(以 SFT 为例)**:
```python
from trl import SFTTrainer
from datasets import load_dataset
dataset = load_dataset("trl-lib/Capybara", split="train")
trainer = SFTTrainer(
model="Qwen/Qwen2.5-0.5B",
train_dataset=dataset,
)
trainer.train()
```
**命令行示例**:
```bash
trl sft --model_name_or_path Qwen/Qwen2.5-0.5B --dataset_name trl-lib/Capybara
```
🔗 与 Hugging Face 生态深度集成
TRL 并非孤立存在,它和 HF 的其他库是无缝衔接的:
* **底层基础**:每个 TRL 训练器本质上都是对 `Transformers` 训练器(`Trainer`)的轻量包装,因此天然支持 DDP、DeepSpeed、FSDP 等分布式训练方式。
* **省显存**:和 `PEFT` 库集成,可以用 LoRA/QLoRA 在消费级显卡上微调大模型。
* **加速训练**:支持集成 `Unsloth` 等优化库来进一步加速。
* **生态认可**:TRL 在社区中应用广泛,每月下载量达数百万次,Hugging Face Hub 上有超过 13 万个用 TRL 训练过的公开模型,像 Unsloth 和 Axolotl 这样的项目也直接构建在它之上。
如果你对某个具体的训练器(比如 GRPO 或 DPO)感兴趣,我可以再展开讲讲它的原理和使用方式。