sgl-project/sglang · error · ValueError

Kimi GPU preprocessing expects raw uint8 pixels, got {image.

Error message

Kimi GPU preprocessing expects raw uint8 pixels, got {image.dtype}

What it means

Kimi K2.5 GPU preprocessing needs raw 0-255 uint8 pixels: the resize kernel rounds to integers and normalization folds in 1/255 scaling. A float tensor (e.g. already-normalized 0-1) would collapse to 0/1 after rounding, so the processor refuses it with ValueError instead of producing garbage.

Source

Thrown at python/sglang/srt/multimodal/processors/kimi_k25.py:134

def _ensure_chw_rgb(image: torch.Tensor) -> torch.Tensor:
    """Coerce an already-decoded (C, H, W) image tensor to 3-channel RGB.

    PIL inputs are RGB-normalized by _pil_to_cuda_chw, but pre-decoded
    tensor inputs (e.g. nvJPEG / cached CUDA tensors) keep their native
    channel count. Grayscale (1ch) or RGBA (4ch) images then break the
    downstream torch.cat over a batch of images, which requires a
    consistent channel dimension. Normalize every tensor to 3 channels.

    Also move the tensor to the GPU (matching _pil_to_cuda_chw) so a CPU
    input does not trip a device mismatch against the CUDA normalization
    constants downstream. No-op if already on the device.
    """
    if image.dtype != torch.uint8:
        # Raw 0-255 is load-bearing downstream: the resize rounds to integers
        # and the normalization folds in a 1/255 scale, so a normalized float
        # image would collapse to 0/1 and then be rescaled.
        raise ValueError(
            f"Kimi GPU preprocessing expects raw uint8 pixels, got {image.dtype}"
        )
    image = image.cuda()
    if image.dim() == 2:  # (H, W) grayscale -> (1, H, W)
        image = image.unsqueeze(0)
    c = image.shape[0]
    if c == 3:
        return image
    if c == 1:
        return image.repeat(3, 1, 1)
    # RGBA or other multi-channel layouts: keep the first 3 channels.
    return image[:3]


def _resize_bicubic_if_needed(
    image: torch.Tensor, target_height: int, target_width: int
) -> torch.Tensor:
    """Track the checkpoint processor's ``PIL.Image.resize(..., BICUBIC)``.

View on GitHub (pinned to 0132848349)

Solutions

  1. Pass raw uint8 images (PIL->np.asarray, cv2 BGR->RGB uint8) into the processor
  2. If you only have normalized floats, denormalize and rescale back to uint8 before sending
  3. Keep one canonical raw-image path and do normalization only inside the processor

Example fix

# before
img = torchvision.transforms.ToTensor()(pil)  # float 0-1 CHW
to_cuda_chw(img)

# after
img = torch.from_numpy(np.asarray(pil.convert("RGB")))  # uint8 HWC
to_cuda_chw(img)
Defensive patterns

Strategy: type-guard

Validate before calling

assert image.dtype == torch.uint8, f"need raw uint8 pixels, got {image.dtype}"

Type guard

def is_raw_uint8(img: torch.Tensor) -> bool:
    return img.dtype == torch.uint8

Try / catch

try:
    chw = _to_cuda_chw(image)
except ValueError as e:
    if "raw uint8" in str(e):
        image = (image.clamp(0, 1) * 255).to(torch.uint8)  # denormalize
        chw = _to_cuda_chw(image)
    else:
        raise

Prevention

When it happens

Trigger: Calling _to_cuda_chw / _ensure_chw_rgb with a float image tensor (already normalized or 0-1 floats) instead of a uint8 HxWx3/CHW tensor.

Common situations: Reusing images already preprocessed by another pipeline (HF processor output, torchvision ToTensor, normalized caches) and feeding them directly into Kimi's GPU path.

Related errors


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