[Perf] Split xdrope_positions H2D copy into per-row transfers (#53412)

Signed-off-by: Keyi Li <likey6688@gmail.com> Co-authored-by: Keyi Li <likey6688@gmail.com>

Keyi Li committed Aug 28, 2026 at 10:28 UTC 6f91e3d9537ea45128534ba9a234b81d6c843281
1 file changed +12 -5
vllm/v1/worker/gpu_model_runner.py
+12 -5
@@ -2227,11 +2227,18 @@ class GPUModelRunner(
2227 non_blocking=True,
2228 )
2229 elif self.uses_xdrope_dim > 0:
2230 - # Only relevant for models using XD-RoPE (e.g, HunYuan-VL)
2231 - self.xdrope_positions.gpu[:, :total_num_scheduled_tokens].copy_(
2232 - self.xdrope_positions.cpu[:, :total_num_scheduled_tokens],
2233 - non_blocking=True,
2234 - )
2230 + # Only relevant for models using XD-RoPE (e.g, HunYuan-VL).
2231 + # xdrope_positions is allocated as [uses_xdrope_dim, max_num_tokens
2232 + # + 1] with the same trailing-column trick as mrope_positions above,
2233 + # so cpu[:, :N] is a strided view of the pinned buffer and the
2234 + # single-slice copy_() runs into the same pageable-fallback silent
2235 + # sync described in PR #51841. Split into per-row copies for the
2236 + # same reason.
2237 + for row in range(self.xdrope_positions.gpu.shape[0]):
2238 + self.xdrope_positions.gpu[row, :total_num_scheduled_tokens].copy_(
2239 + self.xdrope_positions.cpu[row, :total_num_scheduled_tokens],
2240 + non_blocking=True,
2241 + )
2242 if self.use_async_spec_decode and (self.uses_mrope or self.uses_xdrope_dim > 0):
2243 drift = self.num_computed_tokens[req_indices_gpu].to(
2244 torch.int64