File size: 6,188 Bytes
372c993 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
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()
|