[mm] rust-server: native multimodal processing for Qwen VL (integrate sglang-mm, e2e) (#32365)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Claude Fable 5
Cursor
parent
dea07b348b
commit
32e5d788bd
@@ -34,15 +34,34 @@ def py_scaled_dims(
|
||||
return scale(width), scale(height)
|
||||
|
||||
|
||||
def pil_resize(arr: np.ndarray, tw: int, th: int) -> np.ndarray:
|
||||
return np.array(
|
||||
Image.fromarray(arr).resize((tw, th), resample=Image.Resampling.LANCZOS),
|
||||
dtype=np.uint8,
|
||||
def pil_resize(arr: np.ndarray, tw: int, th: int, filter=Image.Resampling.LANCZOS):
|
||||
return np.array(Image.fromarray(arr).resize((tw, th), resample=filter), np.uint8)
|
||||
|
||||
|
||||
def tv_resize(arr: np.ndarray, tw: int, th: int) -> np.ndarray:
|
||||
"""torchvision's uint8 antialias bicubic — ATen's fixed-point kernel."""
|
||||
import torch
|
||||
from torchvision.transforms.v2 import functional as F
|
||||
|
||||
tensor = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0)
|
||||
out = F.resize(
|
||||
tensor, [th, tw], interpolation=F.InterpolationMode.BICUBIC, antialias=True
|
||||
)
|
||||
return out[0].permute(1, 2, 0).numpy()
|
||||
|
||||
|
||||
def rs_resize(arr: np.ndarray, tw: int, th: int) -> np.ndarray:
|
||||
return _rs_common.resize_rgb(arr, tw, th).reshape(th, tw, 3)
|
||||
# Every resampler the Rust resize claims, and its reference: `aten_u8` for a
|
||||
# default server, `pil_bicubic` for --disable-fast-image-processor, `pil_lanczos`
|
||||
# for inkling.
|
||||
REFERENCES = {
|
||||
"pil_lanczos": lambda a, tw, th: pil_resize(a, tw, th, Image.Resampling.LANCZOS),
|
||||
"pil_bicubic": lambda a, tw, th: pil_resize(a, tw, th, Image.Resampling.BICUBIC),
|
||||
"aten_u8": tv_resize,
|
||||
}
|
||||
|
||||
|
||||
def rs_resize(arr, tw: int, th: int, resample: str = "pil_lanczos") -> np.ndarray:
|
||||
return _rs_common.resize_rgb(arr, tw, th, resample).reshape(th, tw, 3)
|
||||
|
||||
|
||||
CASES = [
|
||||
@@ -58,13 +77,36 @@ CASES = [
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resample", sorted(REFERENCES))
|
||||
@pytest.mark.parametrize(
|
||||
"h,w,th,tw", CASES, ids=[f"{h}x{w}->{th}x{tw}" for h, w, th, tw in CASES]
|
||||
)
|
||||
def test_resize_bit_exact(h, w, th, tw):
|
||||
def test_resize_bit_exact(h, w, th, tw, resample):
|
||||
rng = np.random.default_rng(h * 10000 + w)
|
||||
arr = rng.integers(0, 256, (h, w, 3), dtype=np.uint8)
|
||||
np.testing.assert_array_equal(rs_resize(arr, tw, th), pil_resize(arr, tw, th))
|
||||
np.testing.assert_array_equal(
|
||||
rs_resize(arr, tw, th, resample), REFERENCES[resample](arr, tw, th)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resample", sorted(REFERENCES))
|
||||
def test_resize_bit_exact_random_sweep(resample):
|
||||
"""`aten_u8`'s weight precision varies with the scale factor, so the fixed
|
||||
cases above are not enough coverage on their own."""
|
||||
rng = np.random.default_rng(7)
|
||||
for h, w, th, tw in rng.integers(1, 200, (40, 4)):
|
||||
arr = rng.integers(0, 256, (h, w, 3), dtype=np.uint8)
|
||||
np.testing.assert_array_equal(
|
||||
rs_resize(arr, tw, th, resample),
|
||||
REFERENCES[resample](arr, tw, th),
|
||||
err_msg=f"{h}x{w}->{th}x{tw} under {resample}",
|
||||
)
|
||||
|
||||
|
||||
def test_unknown_resample_rejected():
|
||||
arr = np.zeros((4, 4, 3), dtype=np.uint8)
|
||||
with pytest.raises(ValueError, match="unknown resample"):
|
||||
_rs_common.resize_rgb(arr, 2, 2, "nearest")
|
||||
|
||||
|
||||
def test_scaled_dims_sweep():
|
||||
|
||||
Reference in New Issue
Block a user