LingArm's picture
Upload folder using huggingface_hub
372c993 verified
Raw
History Blame Contribute Delete
6.19 kB
"""
deploy.py — FrozenVLA 本地部署脚本
====================================
加载 Qwen3-VL-2B-Instruct 基座 + 训练好的 checkpoint → 本地推理
用法:
python deploy.py # 默认加载 epoch_3.pt
python deploy.py --checkpoint epoch_1.pt # 指定 checkpoint
python deploy.py --test-image cat.jpg # 用真实图片测试
环境: Windows + RTX 4060 Laptop 8GB, 使用 sdpa 注意力
"""
import sys
import argparse
from pathlib import Path
# 把 train 目录加入 sys.path 以导入 model 模块
sys.path.insert(0, str(Path(__file__).parent.parent / "train"))
import torch
from PIL import Image
from model import FrozenVLA
# === 路径配置 ===
BASE_MODEL_PATH = r"G:\LingArm\models\pretrained\Qwen3-VL-2B-Instruct"
CHECKPOINT_DIR = r"G:\LingArm\models\train\frozen_preview"
DEFAULT_CHECKPOINT = "epoch_4.pt"
def load_model(checkpoint_name: str = DEFAULT_CHECKPOINT, verbose: bool = True):
"""
加载完整部署模型: 基座 + checkpoint head
Returns:
model (FrozenVLA): 已加载权重、设为 eval 模式、在 GPU 上的模型
"""
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if verbose:
print("=" * 60)
print(" FrozenVLA 本地部署加载")
print("=" * 60)
print(f" Base model : {BASE_MODEL_PATH}")
print(f" Checkpoint : {CHECKPOINT_DIR}/{checkpoint_name}")
print(f" Device : {device}")
print(f" Attention : sdpa (Windows/4060)")
print("-" * 60)
# Step 1: 加载基座 Qwen3-VL-2B-Instruct(冻结)+ 初始化 MLP/Head
if verbose:
print("\n[1/3] Loading Qwen3-VL-2B-Instruct base model...")
model = FrozenVLA(
llm_name=BASE_MODEL_PATH,
mlp_hidden_dim=512,
mlp_depth=2,
action_dim=7,
attn_implementation="sdpa",
)
# Step 2: 加载训练好的 head checkpoint
if verbose:
print(f"\n[2/3] Loading checkpoint: {checkpoint_name}")
ckpt_path = Path(CHECKPOINT_DIR) / checkpoint_name
model.load_trainable(ckpt_path)
# Step 3: 移到 GPU + 统一 dtype(Qwen3-VL 输出 bf16,head 必须同 dtype)
if verbose:
print(f"\n[3/3] Moving to {device} + casting head to bfloat16...")
model = model.to(device)
# 将 MLP + ActionHead 转为 bf16 以匹配 Qwen3-VL 输出
model.mlp_projector = model.mlp_projector.to(dtype=torch.bfloat16)
model.action_head = model.action_head.to(dtype=torch.bfloat16)
model.translation_scale.data = model.translation_scale.data.to(torch.bfloat16)
model.rotation_scale.data = model.rotation_scale.data.to(torch.bfloat16)
model.eval()
if verbose:
trainable = model.count_trainable_params()
frozen = model.count_frozen_params()
print(f"\n Trainable params : {trainable:,}")
print(f" Frozen params : {frozen:,}")
print(f" Total params : {trainable + frozen:,}")
print(f" VRAM used : {torch.cuda.memory_allocated()/1024**3:.2f} GB")
print("=" * 60)
print(" [OK] Model loaded successfully!")
print("=" * 60)
return model
def predict(model: FrozenVLA, images, instructions):
"""
推理接口
Args:
model: 已加载的 FrozenVLA 模型
images: PIL Image 或 list of PIL Images
instructions: str 或 list of str(任务描述)
Returns:
action: numpy array, shape (N, 7), EEF delta [dx,dy,dz,ax,ay,az,gripper]
"""
# 统一转为 list
if isinstance(images, Image.Image):
images = [images]
if isinstance(instructions, str):
instructions = [instructions]
with torch.no_grad():
action = model(images, instructions)
return action.float().cpu().numpy() # bf16 -> fp32 -> numpy
def basic_test(model: FrozenVLA):
"""
基础冒烟测试:用两张假图片推理,验证 pipeline 通畅
"""
print("\n" + "=" * 60)
print(" Basic Smoke Test")
print("=" * 60)
dummy_images = [
Image.new("RGB", (448, 448), color=(128, 128, 128)),
Image.new("RGB", (448, 448), color=(64, 200, 64)),
]
instructions = [
"pick up the red block",
"place the spoon into the bowl",
]
print(f" Input: {len(dummy_images)} images, {len(instructions)} instructions")
action = predict(model, dummy_images, instructions)
print(f"\n Action shape : {action.shape}")
print(f" Action range : [{action.min():.4f}, {action.max():.4f}]")
print(f" Action mean : {action.mean():.4f}")
for i in range(len(action)):
print(f" Sample {i}: {action[i]}")
print("\n [OK] Inference pipeline OK!")
def main():
parser = argparse.ArgumentParser(description="FrozenVLA Local Deployment")
parser.add_argument("--checkpoint", default=DEFAULT_CHECKPOINT,
help=f"Checkpoint filename (default: {DEFAULT_CHECKPOINT})")
parser.add_argument("--test-image", type=str, default=None,
help="Path to a test image for real inference")
parser.add_argument("--skip-test", action="store_true",
help="Skip the basic smoke test")
args = parser.parse_args()
# 加载模型
model = load_model(args.checkpoint)
# 冒烟测试
if not args.skip_test:
basic_test(model)
# 真实图片测试(如果提供)
if args.test_image:
img_path = Path(args.test_image)
if not img_path.exists():
print(f"\n [ERROR] Image not found: {img_path}")
sys.exit(1)
print(f"\n Testing with real image: {img_path.name}")
image = Image.open(img_path).convert("RGB")
action = predict(model, image, "manipulate the object in front of the robot")
print(f" Predicted action (EEF delta 7D): {action[0]}")
print(f" dx={action[0,0]:.6f} dy={action[0,1]:.6f} dz={action[0,2]:.6f}")
print(f" ax={action[0,3]:.6f} ay={action[0,4]:.6f} az={action[0,5]:.6f}")
print(f" gripper={action[0,6]:.6f}")
print("\n [OK] Deployment complete!")
if __name__ == "__main__":
main()