Checklist / 检查清单
Bug Description / Bug 描述
Template._save_pil_image() currently uses SHA256(image.tobytes()) as the image cache key. PIL.Image.tobytes() returns only the flattened pixel byte stream and does not include the image mode, width, or height.
As a result, two visually different images with identical raw pixel bytes but different dimensions can produce the same cache path. If the first image has already been cached, the second image reuses the first image's PNG file instead of saving its own content.
This can cause multimodal inference or training to receive the wrong image. For example, a later sample may submit image B while the model actually reads the previously cached image A.
How to Reproduce / 如何复现
Environment:
- ms-swift current
main branch
- Python environment with Pillow installed
Steps:
- Create two RGB images from the same raw bytes with different dimensions, for example
120x80 and 80x120.
- Save the first image with
Template._save_pil_image().
- Save the second image with
Template._save_pil_image() using the same cache directory.
- Observe that the current implementation returns the same cache path for both calls.
Minimal reproduction:
from PIL import Image
from swift.template.base import Template
width_a, height_a = 120, 80
width_b, height_b = 80, 120
assert width_a * height_a == width_b * height_b
pixels = bytearray()
for i in range(width_a * height_a):
row = i // width_a
pixels.extend((255, 60, 60) if row % 10 < 5 else (60, 60, 255))
img_bytes = bytes(pixels)
image_a = Image.frombytes('RGB', (width_a, height_a), img_bytes)
image_b = Image.frombytes('RGB', (width_b, height_b), img_bytes)
path_a = Template._save_pil_image(image_a)
path_b = Template._save_pil_image(image_b)
assert path_a != path_b
Expected behavior:
- Images with different dimensions should produce different cache paths.
- Each cached file should preserve the dimensions of the corresponding input image.
Actual behavior:
- The two images produce the same hash/cache path because the current hash input only includes flattened pixel bytes.
Additional Information / 补充信息
Root cause:
image.tobytes() does not include image dimensions or mode metadata.
- Including
image.mode, image.width, and image.height in the hash input prevents cache key collisions across images with different dimensions.
Suggested fix:
img_meta = f'{image.mode}:{image.width}:{image.height}:'.encode()
img_hash = hashlib.sha256(img_meta + image.tobytes()).hexdigest()
Suggested regression test:
- Construct two images from identical raw bytes with dimensions
120x80 and 80x120.
- Verify that
_save_pil_image() returns different paths for the two images.
- Verify that both cached files preserve the correct dimensions.
Checklist / 检查清单
Bug Description / Bug 描述
Template._save_pil_image()currently usesSHA256(image.tobytes())as the image cache key.PIL.Image.tobytes()returns only the flattened pixel byte stream and does not include the image mode, width, or height.As a result, two visually different images with identical raw pixel bytes but different dimensions can produce the same cache path. If the first image has already been cached, the second image reuses the first image's PNG file instead of saving its own content.
This can cause multimodal inference or training to receive the wrong image. For example, a later sample may submit image B while the model actually reads the previously cached image A.
How to Reproduce / 如何复现
Environment:
mainbranchSteps:
120x80and80x120.Template._save_pil_image().Template._save_pil_image()using the same cache directory.Minimal reproduction:
Expected behavior:
Actual behavior:
Additional Information / 补充信息
Root cause:
image.tobytes()does not include image dimensions or mode metadata.image.mode,image.width, andimage.heightin the hash input prevents cache key collisions across images with different dimensions.Suggested fix:
Suggested regression test:
120x80and80x120._save_pil_image()returns different paths for the two images.