Reject native batch seeds outside the JSON-safe range

This commit is contained in:
Daniel Han 2026-07-13 02:09:15 +00:00
commit 5a17614b51
2 changed files with 35 additions and 0 deletions

View file

@ -2069,6 +2069,21 @@ class DiffusionGenerateRequest(BaseModel):
raise ValueError("must be a multiple of 16")
return value
@model_validator(mode = "after")
def _batch_seeds_json_safe(self) -> "DiffusionGenerateRequest":
# A batch derives per-image seeds as seed, seed+1, ... seed+batch_size-1 (sd.cpp and
# diffusers both advance the seed per image). The base seed is bounded to 2**53-1 so it
# round-trips through the JSON gallery recipe, but the derived top-of-batch seed is not:
# with an explicit seed near the cap it can exceed Number.MAX_SAFE_INTEGER, where the
# frontend rounds it and a restored recipe replays a different image. Reject at the
# boundary so an API client can't persist an unreplayable seed.
if self.seed is not None and self.seed + self.batch_size - 1 > 2**53 - 1:
raise ValueError(
"seed + batch_size - 1 must not exceed 2**53 - 1 so every per-image seed "
"stays JSON-safe (lower the seed or the batch_size)"
)
return self
class GalleryImage(BaseModel):
"""A persisted image's full generation recipe (embedded in the PNG too)."""

View file

@ -340,6 +340,26 @@ def test_generate_rejects_non_multiple_of_16(client):
assert ok.status_code == 200
def test_generate_rejects_batch_seed_past_json_safe_range(client):
client.post(
"/api/inference/images/load", json = {"model_path": "x/z-image", "gguf_filename": "q.gguf"}
)
# An explicit seed at the JS-safe cap with a batch derives per-image seeds
# (seed+1 ...) that exceed Number.MAX_SAFE_INTEGER and no longer round-trip
# through the gallery JSON recipe, so the request is rejected at the boundary.
over = client.post(
"/api/inference/images/generate",
json = {"prompt": "p", "seed": 2**53 - 1, "batch_size": 2},
)
assert over.status_code == 422
# The top-of-batch seed lands exactly on the cap: still JSON-safe, so accepted.
ok = client.post(
"/api/inference/images/generate",
json = {"prompt": "p", "seed": 2**53 - 2, "batch_size": 2},
)
assert ok.status_code == 200
def test_non_gguf_load_restricted_to_unsloth(client):
# gguf_filename is optional now; with none, the load is a full-pipeline kind, which
# is gated to unsloth/* repos. A non-unsloth repo (no filename) is rejected -> 400.