File size: 9,317 Bytes
fbd9366 | 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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | """Build a TensorRT engine from a DreamZero checkpoint.
Must be launched via build_trt_engine.sh (or with ENABLE_TENSORRT=true already
set) so that flash-attention compatibility mode is active before any groot model
modules are imported.
Launched via torchrun so that RANK / WORLD_SIZE / MASTER_* env vars exist for
GrootSimPolicy's distributed initialisation.
Calibration:
For quantized precisions (nvfp4, fp8), ModelOpt calibrates quantization
parameters by observing activation statistics during forward passes. Using
real dataset trajectories produces a significantly more accurate engine than
random dummy inputs. Pass --dataset-path to enable real calibration.
"""
import os
import sys
import argparse
import logging
from types import SimpleNamespace
# Verify ENABLE_TENSORRT was exported before any groot imports occur.
if os.getenv("ENABLE_TENSORRT", "").lower() != "true":
print(
"ERROR: ENABLE_TENSORRT must be 'true' before importing this script.\n"
"Use build_trt_engine.sh instead of calling this script directly.",
file=sys.stderr,
)
sys.exit(1)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
import numpy as np
import torch
import torch.distributed as dist
from tianshou.data import Batch
from torch.distributed.device_mesh import init_device_mesh
from groot.vla.data.schema import EmbodimentTag
from groot.vla.model.n1_5.sim_policy import GrootSimPolicy
from groot.control.tensorrt_utils import (
wan_trt_quantize_and_load_engine,
create_wan_test_inputs,
)
# DreamZero-DROID uses the ar_14B_droid model type in tensorrt_utils.
_MODEL_TYPE = "ar_14B_droid"
def _init_single_gpu_mesh():
"""Initialise a single-GPU device mesh (launched via torchrun --nproc_per_node=1)."""
dist.init_process_group("nccl")
rank = dist.get_rank()
world_size = dist.get_world_size()
torch.cuda.set_device(rank)
mesh = init_device_mesh(
device_type="cuda",
mesh_shape=(world_size,),
mesh_dim_names=("ip",),
)
return mesh
def _make_dummy_forward_loop():
"""Fallback calibration using random dummy inputs.
Acceptable for fp16 (no quantization), but may reduce accuracy for
nvfp4/fp8 since the activation distribution differs from real data.
Prefer _make_dataset_forward_loop when a dataset is available.
"""
def forward_loop(model):
trt_forward = getattr(model, "_forward_inference_trt_droid", model.forward)
test_inputs = create_wan_test_inputs(None, device="cuda", model_type=_MODEL_TYPE)
for _ in range(16):
with torch.no_grad():
trt_forward(*test_inputs)
return forward_loop
def _make_dataset_forward_loop(policy, dataset_path: str, num_calibration_trajs: int = 2):
"""Real-data calibration loop — mirrors the internal droid_video_pred.sh approach.
Loads ``num_calibration_trajs`` trajectories from the LeRobot dataset and
runs ``policy.lazy_joint_forward_causal`` at each action-horizon step,
exercising the DiT model with realistic activation distributions.
"""
from groot.vla.data.dataset.lerobot import LeRobotSingleDataset
def forward_loop(model):
logger.info(
"Calibration: loading dataset from %s (%d trajs)", dataset_path, num_calibration_trajs
)
dataset = LeRobotSingleDataset(
dataset_path=dataset_path,
modality_configs=policy.modality_configs,
embodiment_tag=policy.embodiment_tag,
video_backend="torchvision_av",
video_backend_kwargs=None,
transforms=None, # policy.lazy_joint_forward_causal applies transforms
use_global_metadata=False,
)
action_horizon = policy.trained_model.action_head.action_horizon
num_frame_per_block = policy.trained_model.action_head.num_frame_per_block
torch._dynamo.config.recompile_limit = 500
for traj_id in range(min(num_calibration_trajs, len(dataset.trajectory_lengths))):
logger.info("Calibration trajectory %d / %d", traj_id + 1, num_calibration_trajs)
traj_len = int(dataset.trajectory_lengths[traj_id])
latent_video = None
# Step through the trajectory at action-horizon intervals (same cadence as
# real inference) for up to 5 chunks — enough to cover the KV-cache build-up
# and the cached inference path that the TRT engine will handle.
max_steps = min(traj_len, 5 * action_horizon)
for step in range(0, max_steps, action_horizon):
# Clamp delta indices to valid range for this trajectory.
indices = {
k: np.clip(v + step, 0, traj_len - 1)
for k, v in dataset.delta_indices.items()
}
data_point = dataset.get_step_data(traj_id, indices)
batch = Batch(obs=data_point)
dist.barrier()
with torch.no_grad():
result_batch, video_pred = policy.lazy_joint_forward_causal(
batch, latent_video=latent_video
)
dist.barrier()
# Feed the last generated frame back as context for the next step,
# matching autoregressive inference behaviour.
if video_pred is not None:
latent_video = video_pred[:, :, -num_frame_per_block:]
# Reset AR state between trajectories.
policy.trained_model.action_head.current_start_frame = 0
policy.trained_model.action_head.kv_cache1 = None
policy.trained_model.action_head.kv_cache_neg = None
policy.trained_model.action_head.crossattn_cache = None
policy.trained_model.action_head.crossattn_cache_neg = None
return forward_loop
def main():
parser = argparse.ArgumentParser(
description="Build TensorRT engine for the DreamZero DiT model."
)
parser.add_argument("--model-path", required=True, help="Path to checkpoint directory.")
parser.add_argument(
"--tensorrt",
required=True,
choices=["nvfp4", "fp8", "fp16"],
help="TensorRT quantization / precision format.",
)
parser.add_argument(
"--dataset-path",
default=None,
help=(
"Path to a LeRobot-format DROID dataset for real calibration. "
"Strongly recommended for nvfp4/fp8 — random dummy inputs are used as "
"fallback but may reduce quantization accuracy."
),
)
parser.add_argument(
"--num-calibration-trajs",
type=int,
default=2,
help="Number of dataset trajectories used for calibration (default: 2).",
)
args = parser.parse_args()
if args.tensorrt in ("nvfp4", "fp8") and args.dataset_path is None:
logger.warning(
"No --dataset-path provided for %s quantization. "
"Falling back to random dummy inputs — this may reduce engine accuracy. "
"Re-run with --dataset-path <path/to/droid_lerobot> for best results.",
args.tensorrt,
)
engine_dir = os.path.join(args.model_path, "tensorrt", "wan")
engine_path = os.path.join(engine_dir, f"WanModel_{args.tensorrt}.trt")
onnx_path = os.path.join(engine_dir, f"CausalWanModel.onnx")
os.makedirs(engine_dir, exist_ok=True)
if os.path.exists(engine_path):
logger.info("TRT engine already exists: %s", engine_path)
logger.info("Delete it first if you want to rebuild.")
return
logger.info("Loading DreamZero policy from : %s", args.model_path)
logger.info("Target engine path : %s", engine_path)
logger.info("Quantization precision : %s", args.tensorrt)
device_mesh = _init_single_gpu_mesh()
policy = GrootSimPolicy(
embodiment_tag=EmbodimentTag("oxe_droid"),
model_path=args.model_path,
device="cuda" if torch.cuda.is_available() else "cpu",
device_mesh=device_mesh,
)
# Build calibration forward loop — prefer real data for quantized precisions.
if args.dataset_path is not None:
forward_loop = _make_dataset_forward_loop(
policy, args.dataset_path, args.num_calibration_trajs
)
logger.info(
"Calibration: using %d real trajectories from %s",
args.num_calibration_trajs,
args.dataset_path,
)
else:
forward_loop = _make_dummy_forward_loop()
logger.info("Calibration: using random dummy inputs (no --dataset-path given).")
# cfg mimics the Hydra config used by the internal eval script.
cfg = SimpleNamespace(inference_mode="trt_build", quantize_dtype=args.tensorrt)
logger.info("Building TensorRT engine (ONNX export + trtexec, may take 10-30 min) ...")
wan_trt_quantize_and_load_engine(
policy=policy,
cfg=cfg,
onnx_path=onnx_path,
engine_path=engine_path,
model_type=_MODEL_TYPE,
forward_loop=forward_loop,
)
logger.info("TRT engine saved to: %s", engine_path)
if __name__ == "__main__":
main()
|