sgl-project/sglang · error · ValueError

pairs must be a torch.Tensor of shape [N, 2]

Error message

pairs must be a torch.Tensor of shape [N, 2]

What it means

The 'quadratic_perp_bulge_swap' pair postprocess in FlowMatchPairScheduler requires its input to be a 2-D torch.Tensor with exactly 2 columns (one value per modality: [t, t]). The guard raises ValueError when the tensor is missing, is not a torch.Tensor, is 1-D/3-D, or has shape[1] != 2. It is thrown inside the closure installed by set_pair_postprocess_by_name('quadratic_perp_bulge_swap'), which is invoked on the timestep/sigma pairs produced by set_timesteps().

Source

Thrown at python/sglang/multimodal_gen/runtime/models/schedulers/flow_match_pair.py:260

                - amp: Float amplitude, default 150.0.

        Raises:
            ValueError: If name is unknown.
        """

        if name is None or str(name).lower() in ("none", "off", "false", "no"):
            self.set_pair_postprocess(None)
            return
        if name == "quadratic_perp_bulge_swap":
            amp = float(kwargs.get("amp", 150.0))

            def _quadratic_perp_bulge_swap(pairs: torch.Tensor):
                if (
                    not isinstance(pairs, torch.Tensor)
                    or pairs.ndim != 2
                    or pairs.shape[1] != 2
                ):
                    raise ValueError("pairs must be a torch.Tensor of shape [N, 2]")
                x = pairs[:, 0]
                T = float(self.num_train_timesteps)
                s = x / T
                d = 4.0 * amp * s * (1.0 - s)
                x2 = x + d
                y2 = x - d
                return torch.stack([x2, y2], dim=1)

            self.set_pair_postprocess(_quadratic_perp_bulge_swap)
            return
        if name == "v2a_sequential":

            def _v2a(pairs: torch.Tensor):
                if (
                    not isinstance(pairs, torch.Tensor)
                    or pairs.ndim != 2
                    or pairs.shape[1] != 2
                ):

View on GitHub (pinned to 0132848349)

Solutions

  1. Ensure the pairs tensor passed to the postprocess is created via torch.stack([t, t], dim=1) or .view(-1, 2) so it is [N, 2]
  2. If integrating with legacy single-timestep code, wrap the 1-D tensor: pairs = t.unsqueeze(1).expand(-1, 2).contiguous()
  3. Verify the value is actually a torch.Tensor (convert numpy with torch.from_numpy(...)) before the scheduler caches it
  4. Disable the postprocess via set_pair_postprocess_by_name(None) if pair reshaping is not needed for your run

Example fix

// before
timesteps = scheduler.set_timesteps(num_steps)  # returns flat [N]

# after
timesteps = scheduler.set_timesteps(num_steps)
pairs = torch.stack([timesteps, timesteps], dim=1)  # [N, 2]
Defensive patterns

Strategy: type-guard

Validate before calling

def valid_pairs(p) -> bool:
    return isinstance(p, torch.Tensor) and p.ndim == 2 and p.shape[1] == 2

Type guard

def is_pairs_tensor(p) -> TypeGuard[torch.Tensor]:
    return isinstance(p, torch.Tensor) and p.ndim == 2 and p.shape[1] == 2

Prevention

When it happens

Trigger: Calling set_pair_postprocess_by_name('quadratic_perp_bulge_swap', amp=...) and then set_timesteps()/refresh path where the cached pairs tensor is a flat 1-D tensor (e.g. a plain timestep linspace not reshaped to [N,2]), a list/numpy array, or a [N,1]/[N,3] tensor.

Common situations: Scheduler was constructed or initialized by code that predates the paired (dual-modality) API and still supplies single-column timesteps; custom model runners passing a Python list or numpy array instead of torch.Tensor; reshaping step (view(-1,2)/unsqueeze) omitted after migrating to joint audio-visual scheduling.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/3c92d1c6b794ac51. Report an issue: GitHub.