Dev,AI

[LLM, Opensource Contribution ] SGlang VLM ๋ชจ๋ธ ์‚ฌ์šฉ ์ค‘ ์˜คํ”ˆ์†Œ์Šค ๊ธฐ์—ฌํ•˜๊ธฐ

Tae Jeong 2026. 6. 18. 13:04
728x90

 

 

OCR ํŒŒ์ดํ”„๋ผ์ธ์„ ๋งŒ์ง€๋‹ค๊ฐ€ SGLang์— ๋ฒ„๊ทธ ๋ฆฌํฌํŠธ๋ž‘ PR๊นŒ์ง€ ์˜ฌ๋ฆฌ๊ฒŒ ๋œ ๊ณผ์ •์„ ์ •๋ฆฌํ•ด๋ดค๋Š”๋ฐ์š”, ์ƒ๊ฐ๋ณด๋‹ค ์›์ธ์ด ์—ฌ๋Ÿฌ ๊ฒน์œผ๋กœ ์–ฝํ˜€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ฒซ์˜คํ”ˆ์†Œ์Šค ๊ธฐ์—ฌ( Openwebui vLLM Usage )  ์ดํ›„ ๋‘๋ฒˆ์งธ ์˜คํ”ˆ์†Œ์Šค ๊ธฐ์—ฌ์ธ๋ฐ, ์ง€๋‚œ๋ฒˆ์—๋Š” docs ์˜ usage ์ •๋„ ๊ธฐ์—ฌํ–ˆ์ง€๋งŒ ์ด๋ฒˆ์—๋Š” ๋ฒ„๊ทธ ํ•ด๊ฒฐ์„ ์ œ์•ˆํ•œ ๊ธฐ์—ฌ์—ฌ์„œ ๊ธฐ๋กํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

 

buf ๋กœ ์ด๋ฏธ์ง€ ๊ตฌํ˜„ํ•œ ๋ฐ์ดํ„ฐ ์ž…๋‹ˆ๋‹ค.

 

 

์‹œ์ž‘์€ ํ‰๋ฒ”ํ•œ OCR ์ž‘์—…์ด์—ˆ์Šต๋‹ˆ๋‹ค

Qwen3-VL-8B-Instruct๋ฅผ SGLang์œผ๋กœ ๋„์›Œ๋†“๊ณ  ๋ฌธ์„œ OCR์„ ๋Œ๋ฆฌ๊ณ  ์žˆ์—ˆ๋Š”๋ฐ์š”. ๊ตฌ์„ฑ์€ ์ด๋žฌ์Šต๋‹ˆ๋‹ค.

docker run -d --restart unless-stopped --gpus all -p 1011:30000 \
  -v /models:/models --ipc=host \
  lmsysorg/sglang:v0.5.10.post1-cu130 python3 -m sglang.launch_server \
  --model-path /models/qwen3-vl-8b-instruct --host 0.0.0.0 --port 30000 \
  --tp 1 --mem-fraction-static 0.3 --max-running-requests 256

 

PDF๋ฅผ 300DPI๋กœ ๋ž˜์Šคํ„ฐํ™”ํ•ด์„œ ์ด๋ฏธ์ง€๋กœ ๋งŒ๋“  ๋‹ค์Œ ๋ชจ๋ธ์— ๋„ฃ๋Š”, ๊ทธ๋ƒฅ ํ”ํ•œ ํ๋ฆ„์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์›๋ณธ PDF๋Š” 10MB ๋‚จ์ง“์ด์—ˆ๊ณ ์š”.

 

๊ทธ๋Ÿฐ๋ฐ ์š”์ฒญ ํ•˜๋‚˜๋ฅผ ๋ณด๋ƒˆ๋”๋‹ˆ ์‘๋‹ต์ด 1๋ถ„ 50์ดˆ๊ฐ€ ๋„˜๊ฒŒ ๊ฑธ๋ฆฌ๋”๋ผ๊ณ ์š”. ๊ทธ๊ฒƒ๋„ ๋ชจ์ž๋ผ์„œ, ํ„ฐ๋ฏธ๋„ ๋กœ๊ทธ์ฐฝ์— base64 ์ธ์ฝ”๋”ฉ๋œ ์ด๋ฏธ์ง€ raw ๋ฐ์ดํ„ฐ๊ฐ€ ๋๋„ ์—†์ด ์ฃผ๋ฅด๋ฅต ์ฐํžˆ๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. AAAAAA... ๊ฐ™์€ ๊ฒŒ ๋ช‡ ๋ถ„ ๋™์•ˆ ํ™”๋ฉด์„ ๊ฐ€๋“ ์ฑ„์šฐ๋Š”๋ฐ, ์†”์งํžˆ ์ฒ˜์Œ์—” ์„œ๋ฒ„๊ฐ€ ์ฃฝ์€ ์ค„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค.

์žฌ๋ฐŒ๋Š” ๊ฑด ๊ฐ™์€ ํŒŒ์ผ์„ vLLM์— ๋„ฃ์—ˆ์„ ๋•Œ์˜€๋Š”๋ฐ์š”. vLLM์€ PIL์ด "decompression bomb์ด ์˜์‹ฌ๋œ๋‹ค"๋Š” ๊ฒฝ๊ณ ๋ฅผ ๋„์šฐ๊ณ  ๊น”๋”ํ•˜๊ฒŒ ๋„˜์–ด๊ฐ”์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ raw๊ฐ€ ๋กœ๊ทธ์— ํ† ํ•ด์ง€๋Š” ์ผ๋„ ์—†์—ˆ๊ณ ์š”. ๊ฐ™์€ ์ž…๋ ฅ์ธ๋ฐ ๋‘ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๋ฐ˜์‘์ด ์ด๋ ‡๊ฒŒ ๋‹ค๋ฅธ ๊ฒŒ ์ด์ƒํ•ด์„œ, ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ๊นŒ๋ณด๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

1์ฐจ ์šฉ์˜์ž: ์™œ raw ์ด๋ฏธ์ง€๊ฐ€ ๋กœ๊ทธ์— ์ฐํžˆ๋‚˜

๊ฐ€์žฅ ๋จผ์ € ์˜์‹ฌํ•œ ๊ฑด ์š”์ฒญ ๋กœ๊น…์ด์—ˆ์Šต๋‹ˆ๋‹ค. SGLang์—๋Š” --log-requests ์˜ต์…˜์ด ์žˆ์–ด์„œ ์š”์ฒญ ๋‚ด์šฉ์„ ๋กœ๊ทธ์— ๋‚จ๊ธธ ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”. ๊ทธ๋Ÿฐ๋ฐ ์ €๋Š” ๊ทธ ์˜ต์…˜์„ ์ผ  ์ ์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ˆ log_requests ๊ธฐ๋ณธ๊ฐ’์€ False์˜€๊ณ ์š”.

log_requests: bool = False

 

ํ”Œ๋ž˜๊ทธ๋ฅผ ์•ˆ ์ผฐ๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ์ด๋ฏธ์ง€๊ฐ€ ๋กœ๊ทธ์— ๋‚จ์•˜์„๊นŒ. ์—ฌ๊ธฐ์„œ ํ•œ์ฐธ ํ—ค๋งธ์Šต๋‹ˆ๋‹ค. ์ •์ƒ ๋กœ๊น… ๊ฒฝ๋กœ๋Š” ์ „๋ถ€ ์ด ํ”Œ๋ž˜๊ทธ ๋’ค์— ๊ฐ€๋ ค์ ธ ์žˆ์—ˆ์œผ๋‹ˆ๊นŒ์š”.

๊ฒฐ๊ตญ ๋‹ต์€ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ชฝ์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๋ฅผ ๋””์ฝ”๋”ฉํ•˜๋Š” base_processor.py์—์„œ, ์ œ๊ฐ€ ์“ฐ๋˜ v0.5.10.post1 ๋ฒ„์ „์€ ์ด๋ ‡๊ฒŒ ๋˜์–ด ์žˆ์—ˆ๊ฑฐ๋“ ์š”.

except Exception as e:
    raise RuntimeError(f"Error while loading data {data}: {e}")

 

์—ฌ๊ธฐ {data}๊ฐ€ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ๋กœ๋”ฉ์ด ์‹คํŒจํ•˜๋ฉด ๊ทธ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ์•ˆ์— ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ์ „์ฒด, ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ ์ˆ˜์‹ญ~๋ฐฑ MB์งœ๋ฆฌ base64 ๋ฌธ์ž์—ด์ด ํ†ต์งธ๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด RuntimeError๊ฐ€ ์œ„๋กœ ์ „ํŒŒ๋˜๋ฉด์„œ serving_base.py์˜

 

except Exception as e:
    logger.exception(f"Error in request: {e}")

์—ฌ๊ธฐ์„œ ๊ทธ๋Œ€๋กœ ๋กœ๊ทธ๋กœ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. --log-requests์™€๋Š” ์•„๋ฌด ์ƒ๊ด€ ์—†๋Š” ๊ฒฝ๋กœ์˜€๋˜ ๊ฑฐ์ฃ . ๋””์ฝ”๋”ฉ์ด ์‹คํŒจํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฌด์กฐ๊ฑด ์ฐํž™๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ•œ ๊ฐ€์ง€ ๋”. ์ œ๊ฐ€ 1๋ถ„ 50์ดˆ๋‚˜ ๊ธฐ๋‹ค๋ฆฐ ์ด์œ ๊ฐ€ ์‚ฌ์‹ค ์ด๋ฏธ์ง€ ๋””์ฝ”๋”ฉ์ด ์•„๋‹ˆ๋ผ ์ด ๋กœ๊น… ์ž์ฒด์˜€์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค. 100MB๊ฐ€ ๋„˜๋Š” ๋ฌธ์ž์—ด์„ f-string์œผ๋กœ ๋งŒ๋“ค๊ณ  stdout์œผ๋กœ ์Ÿ์•„๋‚ด๋Š” ๋™์•ˆ CPU ์ฝ”์–ด ํ•˜๋‚˜๊ฐ€ ๊ผฌ๋ฐ• 100%๋กœ ๋Œ๊ฑฐ๋“ ์š”. ์ถ”๋ก ์€ ์‹œ์ž‘๋„ ์•ˆ ํ–ˆ๋Š”๋ฐ ๋ง์ด์ฃ .

๊ทธ๋Ÿผ ์• ์ดˆ์— ์˜ˆ์™ธ๋Š” ์™œ ๋‚ฌ์„๊นŒ

์—ฌ๊ธฐ์„œ vLLM์ด ๋ณด์—ฌ์ค€ ๊ฒฝ๊ณ ๊ฐ€ ๊ฒฐ์ •์ ์ธ ํžŒํŠธ์˜€์Šต๋‹ˆ๋‹ค. "decompression bomb์ด ์˜์‹ฌ๋œ๋‹ค"๋Š” ๊ทธ ๋ฉ”์‹œ์ง€, ๋ฐ”๋กœ PIL ๋‚ด์žฅ ๋ณดํ˜ธ ์žฅ์น˜๊ฐ€ ๋„์šฐ๋Š” ๊ฒ๋‹ˆ๋‹ค.

PIL์€ ์ด๋ฏธ์ง€์˜ ํ”ฝ์…€ ์ˆ˜๊ฐ€ MAX_IMAGE_PIXELS(๊ธฐ๋ณธ๊ฐ’ 89,478,485)๋ฅผ ๋„˜์œผ๋ฉด ๊ฒฝ๊ณ ๋ฅผ, ๊ทธ 2๋ฐฐ์ธ 178,956,970์„ ๋„˜์œผ๋ฉด ์•„์˜ˆ DecompressionBombError๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค. ๋””์Šคํฌ์ƒ ํŒŒ์ผ ํฌ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์••์ถ•์„ ํ‘ผ ํ”ฝ์…€ ์ˆ˜ ๊ธฐ์ค€์ด๋ผ๋Š” ๊ฒŒ ํ•ต์‹ฌ์ธ๋ฐ์š”.

 

300DPI๋กœ ํ‚ค์šด PDF ํŽ˜์ด์ง€๋Š” ์ด ํ•œ๊ณ„๋ฅผ ๋„˜๊ธฐ๊ธฐ ๋”ฑ ์ข‹์Šต๋‹ˆ๋‹ค. ํฐ ์šฉ์ง€๋ฅผ ๊ณ ํ•ด์ƒ๋„๋กœ ๋ž˜์Šคํ„ฐํ™”ํ•˜๋ฉด 1์–ต~2์–ต ํ”ฝ์…€์€ ์šฐ์Šต๊ฒŒ ๋‚˜์˜ค๋‹ˆ๊นŒ์š”. ์›๋ณธ ํŒŒ์ผ์ด 10MB๋ผ๋„ ํ”ฝ์…€๋กœ ํŽผ์น˜๋ฉด ์ „ํ˜€ ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

  1. ํฐ ์ด๋ฏธ์ง€๊ฐ€ ๋“ค์–ด์˜ด → PIL์ด ํ”ฝ์…€ ์ˆ˜ ํ•œ๊ณ„ ์ดˆ๊ณผ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  DecompressionBombError๋ฅผ ๋˜์ง
  2. SGLang์ด ๊ทธ ์˜ˆ์™ธ๋ฅผ ์žก์•„์„œ base64 ์›๋ฌธ๊ณผ ํ•จ๊ป˜ ๋‹ค์‹œ ๋˜์ง
  3. logger.exception์ด ๊ทธ 100MB์งœ๋ฆฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋กœ๊ทธ๋กœ ์ถœ๋ ฅ → CPU ์ ์œ , ๋กœ๊ทธ ํญ๋ฐœ

vLLM์€ 1๋ฒˆ์—์„œ PIL ๋ฉ”์‹œ์ง€๋งŒ ๋ณด์—ฌ์ฃผ๊ณ  ๋๋ƒˆ๋Š”๋ฐ, SGLang(ํ•ด๋‹น ๋ฒ„์ „)์€ ๊ฑฐ๊ธฐ์— raw ๋ฐ์ดํ„ฐ๊นŒ์ง€ ์–น์–ด์„œ ํ† ํ•ด๋‚ธ ์…ˆ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋” ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ: ๋ฉ”๋ชจ๋ฆฌ์™€ DoS

์—ฌ๊ธฐ๊นŒ์ง€๋„ ์ถฉ๋ถ„ํžˆ ๊ณจ์น˜ ์•„ํ”ˆ๋ฐ, ํ…Œ์ŠคํŠธํ•˜๋‹ค ๋ณด๋‹ˆ ๋” ์œ„ํ—˜ํ•œ ๊ฒŒ ๋ณด์˜€์Šต๋‹ˆ๋‹ค. ์š”์ฒญ ํ•˜๋‚˜ ์ฒ˜๋ฆฌํ•  ๋•Œ๋งˆ๋‹ค RSS๊ฐ€ 1GB์”ฉ ์น˜์†Ÿ์•˜๋‹ค๊ฐ€ ๋๋‚˜๋ฉด ํ’€๋ฆฌ๋”๋ผ๊ณ ์š”.

์ƒ๊ฐํ•ด๋ณด๋ฉด ๋‹น์—ฐํ•ฉ๋‹ˆ๋‹ค. HTTP ๋ฐ”๋””(133MB) → UTF-8 ๋ฌธ์ž์—ด → base64 ๋””์ฝ”๋“œ → PIL ๋””์ฝ”๋”ฉ(1.4์–ต ํ”ฝ์…€ RGB๋ฉด ๊ทธ๊ฒƒ๋งŒ 430MB๊ฐ€ ๋„˜์Šต๋‹ˆ๋‹ค) → ๋ฆฌ์‚ฌ์ด์ฆˆ ๋ณต์‚ฌ๋ณธ… ๋‹จ๊ณ„๋งˆ๋‹ค ํฐ ๊ฐ์ฒด๊ฐ€ ์ƒˆ๋กœ ์ƒ๊ธฐ๋‹ˆ๊นŒ์š”.

๋ฌธ์ œ๋Š” ์ œ๊ฐ€ --max-running-requests 256์œผ๋กœ ๋„์›Œ๋†จ๋‹ค๋Š” ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์š”์ฒญ์ด ๋™์‹œ์— ๋ช‡ ๊ฐœ๋งŒ ๋“ค์–ด์™€๋„ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์„ ํ˜•์œผ๋กœ ๋ถˆ์–ด๋‚ฉ๋‹ˆ๋‹ค. 8๊ฐœ๋ฉด 8GB, 16๊ฐœ๋ฉด 16GB. GPU ์ถ”๋ก ์— ๋‹ฟ๊ธฐ๋„ ์ „์— ์„œ๋ฒ„๊ฐ€ OOM์œผ๋กœ ์ฃฝ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋œป์ด์ฃ .

์ด๊ฒŒ ๋ฌด์„œ์šด ๊ฑด, ๊ณต๊ฒฉ์ž ์ž…์žฅ์—์„œ GPU ํ•œ ํ†จ ์“ธ ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๊ฒ๋‹ˆ๋‹ค. ์œ ํšจํ•œ ์ด๋ฏธ์ง€์ผ ํ•„์š”๋„ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ ํฐ base64 ๋ฌธ์ž์—ด์„ /v1/chat/completions์— ๋ช‡ ๋ฒˆ ๋˜์ง€๋Š” ๊ฒƒ๋งŒ์œผ๋กœ CPU๋ž‘ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ฐ‰์•„๋จน์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ์š”. ๋‹จ์ˆœํ•œ ์„ฑ๋Šฅ ์ด์Šˆ๋ฅผ ๋„˜์–ด ์‚ฌ์‹ค์ƒ ์ถ”๋ก  ์ด์ „ ๋‹จ๊ณ„์˜ DoS ์ทจ์•ฝ์ ์ž…๋‹ˆ๋‹ค.

 

์ด๋ฏธ ๊ณ ์ณ์ง„ ๋ถ€๋ถ„๊ณผ ์•„์ง ์•ˆ ๊ณ ์ณ์ง„ ๋ถ€๋ถ„

์ด์Šˆ๋ฅผ ์ •๋ฆฌํ•˜๋‹ค๊ฐ€ ์•Œ๊ฒŒ ๋œ ๊ฑด๋ฐ์š”, ์œ„์—์„œ ๋ณธ ๊ทธ ๋ฌด์ œํ•œ {data} ๋กœ๊น…์€  PR #27451์—์„œ ์ด๋ฏธ ์†์„ ๋ดค๋”๋ผ๊ณ ์š”. ๋‹ค๋งŒ ์ œ๊ฐ€ ์“ฐ๋˜ v0.5.10.post1์—๋Š” ์•„์ง ์•ˆ ๋“ค์–ด๊ฐ„ ์ƒํƒœ์˜€์Šต๋‹ˆ๋‹ค. ์ตœ์‹  main์—์„œ๋Š” ์ด๋ ‡๊ฒŒ 100์ž๋กœ ์ž˜๋ฆฝ๋‹ˆ๋‹ค.

 

Classify malformed-multimodal rejects as invalid_request by merrymercy · Pull Request #27451 · sgl-project/sglang

Summary When a multimodal request fails validation (e.g. bad base64), return a structured error with type=invalid_request_error and code=400 instead of a bare error message. Detect client disconne...

github.com

 

except Exception as e:
    data_str = str(data)
    if len(data_str) > 100:
        data_str = data_str[:100] + "..."
    raise RuntimeError(f"Error while loading data {data_str}: {e}") from e

์ด๊ฑธ ๋ณด๊ณ  "์•„ ๊ทธ๋Ÿผ ํ•ด๊ฒฐ๋๋„ค" ํ•˜๊ณ  ๋„˜์–ด๊ฐˆ ๋ป”ํ–ˆ๋Š”๋ฐ, ๊ณฑ์”น์–ด๋ณด๋‹ˆ ์ด truncation์ด ๋ง‰๋Š” ๊ฑด ๊นจ์ง„ ์ž…๋ ฅ ์ผ€์ด์Šค๋ฟ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰ Image.open์ด ์‹คํŒจํ•ด์„œ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์—๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค๋ฆฌ๋˜ ๊ฒฝ๋กœ์š”.

์ •์ž‘ ์ œ ์‹ค์ œ ์ƒํ™ฉ, ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ ๋ฉ€์ฉกํ•˜์ง€๋งŒ ๊ทธ๋ƒฅ ๊ฑฐ๋Œ€ํ•œ ์ด๋ฏธ์ง€๋Š” ์˜ˆ์™ธ๋ฅผ ์•ˆ ๋ƒ…๋‹ˆ๋‹ค. ๋””์ฝ”๋”ฉ์— ์„ฑ๊ณตํ•˜๊ฑฐ๋“ ์š”. ๋‹ค๋งŒ ๊ทธ ๋””์ฝ”๋”ฉ์ด ๋А๋ฆฌ๊ณ  ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋งŽ์ด ๋จน์„ ๋ฟ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ๋กœ๋Š” ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ์•ˆ ๊ฑฐ์น˜๋‹ˆ๊นŒ truncation๊ณผ๋Š” ๋ฌด๊ด€ํ•˜๊ณ , ์ตœ์‹  ๋ฒ„์ „์—์„œ๋„ ๊ทธ๋Œ€๋กœ ๋А๋ฆฝ๋‹ˆ๋‹ค. DoS ๊ฐ€๋Šฅ์„ฑ๋„ ์‚ด์•„ ์žˆ๊ณ ์š”.

๊ทธ๋ž˜์„œ ์ด์Šˆ์— "#27451์ด ๊ณ ์นœ ๊ฒƒ๊ณผ ๋ชป ๊ณ ์นœ ๊ฒƒ"์„ ๋ช…ํ™•ํžˆ ๋‚˜๋ˆ ์„œ ์ ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ˆ„๊ฐ€ ๋ด๋„ "์–ด ์ด๊ฑฐ ์ด๋ฏธ ๊ณ ์ณ์ง„ ๊ฑฐ ์•„๋‹ˆ์•ผ?"๋ผ๊ณ  ํ•  ๋งŒํ•œ ๋ถ€๋ถ„์ด๋ผ์„œ์š”.

์ œ์•ˆํ•œ ์ˆ˜์ •: ๋””์ฝ”๋”ฉ ์ „์— ๋ง‰์ž

ํ•ด๊ฒฐ ๋ฐฉํ–ฅ์€ ๋‹จ์ˆœํ•ฉ๋‹ˆ๋‹ค. PIL์˜ Image.open์€ ํ—ค๋”๋งŒ ์ฝ์Šต๋‹ˆ๋‹ค. ์ฆ‰ ์‹ค์ œ ํ”ฝ์…€์„ ๋””์ฝ”๋”ฉํ•˜๊ธฐ ์ „์— ์ด๋ฏธ์ง€์˜ ๊ฐ€๋กœ·์„ธ๋กœ ํฌ๊ธฐ๋ฅผ ์ด๋ฏธ ์•Œ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์ฃ . ๊ทธ๋Ÿฌ๋‹ˆ ๋น„์‹ผ ๋””์ฝ”๋”ฉ์— ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ํ”ฝ์…€ ์ˆ˜๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๊ณ , ํ•œ๊ณ„๋ฅผ ๋„˜์œผ๋ฉด ๊น”๋”ํ•˜๊ฒŒ ๊ฑฐ๋ถ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

def _check_image_pixels(width: int, height: int) -> None:
    max_pixels = envs.SGLANG_IMAGE_MAX_DECODE_PIXELS.get()
    num_pixels = width * height
    if max_pixels > 0 and num_pixels > max_pixels:
        raise ValueError(
            f"Image is too large to decode: {width}x{height} = {num_pixels} pixels "
            f"exceeds the limit of {max_pixels} pixels. Raise "
            f"SGLANG_IMAGE_MAX_DECODE_PIXELS to allow larger images."
        )

๊ทธ๋ฆฌ๊ณ  ์ด๋ฏธ์ง€๋ฅผ ์—ฌ๋Š” ์ง€์ ์—์„œ ๋””์ฝ”๋”ฉ(.convert())์ด ์ผ์–ด๋‚˜๊ธฐ ์ „์— ์ด ๊ฒ€์‚ฌ๋ฅผ ๋ผ์›Œ ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค.

image = Image.open(BytesIO(image_bytes))
_check_image_pixels(image.width, image.height)
return image

ํ•œ๊ณ„๊ฐ’์€ SGLANG_IMAGE_MAX_DECODE_PIXELS ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋นผ๋’€์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ PIL์˜ bomb ์ž„๊ณ„๊ฐ’๊ณผ ๋™์ผํ•œ 89,478,485๋กœ ์žก์•˜๊ณ ์š”. OCR์ฒ˜๋Ÿผ ์ •๋ง ํฐ ์ด๋ฏธ์ง€๋ฅผ ๋‹ค๋ค„์•ผ ํ•˜๋Š” ๋ถ„๋“ค์€ ์ด ๊ฐ’์„ ์˜ฌ๋ฆฌ๋ฉด ๋˜๊ณ , 0์œผ๋กœ ๋‘๋ฉด ๊ฒ€์‚ฌ๋ฅผ ๋Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฑฐ๋Œ€ํ•œ ์ด๋ฏธ์ง€๊ฐ€ ๋“ค์–ด์™€๋„ ๋””์ฝ”๋”ฉ ์ „์— ๋ช…ํ™•ํ•œ ValueError๋กœ ๋ง‰ํžˆ๋‹ˆ๊นŒ, 430MB์งœ๋ฆฌ ํ”ฝ์…€ ๋ฒ„ํผ๋ฅผ ๋งŒ๋“ค ์ผ๋„, raw ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๊ทธ์— ํ† ํ•  ์ผ๋„ ์—†์Šต๋‹ˆ๋‹ค. ๋А๋ ค์งˆ ์ผ๋„, ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ„ฐ์งˆ ์ผ๋„ ์—†๊ณ ์š”. vLLM์ด PIL ๊ฒฝ๊ณ ๋กœ ์งง๊ฒŒ ๋Š๊ณ  ๊ฐ€๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•œ ๋™์ž‘์ด ๋ฉ๋‹ˆ๋‹ค.

๋ฌผ๋ก  ํ•œ๊ณ„๋„ ์†”์งํžˆ ์ ์–ด๋’€์Šต๋‹ˆ๋‹ค. ์ด ๊ฐ€๋“œ๋Š” ์œ ํšจํ•œ ํ—ค๋”๊ฐ€ ์žˆ์–ด์•ผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์•„์˜ˆ ๊นจ์ง„ base64๋Š” Image.open ์ž์ฒด๊ฐ€ ๋จผ์ € ํ„ฐ์ง€๋‹ˆ๊นŒ์š”. ๊ทธ์ชฝ์€ ์•ž์„œ ๋ณธ #27451์ด ๋‹ด๋‹นํ•˜๋Š” ์˜์—ญ์ด๊ณ , ์ œ PR์€ "๋ฉ€์ฉกํ•˜์ง€๋งŒ ๋„ˆ๋ฌด ํฐ" ์ผ€์ด์Šค๋ฅผ ๋งก๋Š” ์‹์œผ๋กœ ์—ญํ• ์ด ๊ฐˆ๋ฆฝ๋‹ˆ๋‹ค.

๋งˆ์น˜๋ฉฐ

์ฒ˜์Œ์—” ๊ทธ๋ƒฅ "๋กœ๊ทธ๊ฐ€ ์ข€ ๋งŽ์ด ์ฐํžˆ๋„ค?" ์ •๋„๋กœ ๋ณด์˜€๋Š”๋ฐ, ํŒŒ๊ณ ๋“ค์ˆ˜๋ก ์„ฑ๋Šฅ ๋ฌธ์ œ → ์ •๋ณด ๋…ธ์ถœ → DoS ๊ฐ€๋Šฅ์„ฑ๊นŒ์ง€ ์„ธ ๊ฒน์œผ๋กœ ์—ฎ์—ฌ ์žˆ๋˜ ์‚ฌ๋ก€์˜€์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  "์ด๋ฏธ PR๋กœ ๊ณ ์ณ์ง„ ๊ฒƒ ๊ฐ™๋‹ค"๋Š” ์ง€์ ์—์„œ ํ•œ ๋ฒˆ ๋” ์˜์‹ฌํ•ด๋ณธ ๊ฒŒ ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋งž์•˜๊ณ ์š”. ๊ฒ‰๋ณด๊ธฐ ์ฆ์ƒ ํ•˜๋‚˜์— ์›์ธ์ด ํ•˜๋‚˜๋งŒ ์žˆ๋Š” ๊ฑด ์•„๋‹ˆ๋ผ๋Š” ๊ฑธ ๋‹ค์‹œ ๋А๊ผˆ์Šต๋‹ˆ๋‹ค.

์ž‘์—…ํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜์— ์˜ฌ๋ ค๋’€์Šต๋‹ˆ๋‹ค.

 

Guard image decode against oversized (decompression-bomb) images by hhhhhhhhhhhhhhhhho · Pull Request #28588 · sgl-project/sgl

Motivation Closes #28587. _load_image (python/sglang/srt/utils/common.py) decodes images via Image.open(BytesIO(...)) with no pixel-count guard. The per-model cap (SGLANG_IMAGE_MAX_PIXELS, used by ...

github.com

 

 

[Bug] No pixel-count guard on image decode: oversized images cause ~1GB RSS + 100% CPU per request (pre-inference DoS risk) · I

Checklist I searched related issues but found no solution. The bug persists in the latest version. Issues without environment info and a minimal reproducible demo are hard to resolve and may receiv...

github.com

 

ํ˜น์‹œ ๋น„์Šทํ•˜๊ฒŒ VLM ์„œ๋น™์—์„œ ํฐ ์ด๋ฏธ์ง€๋ฅผ ๋‹ค๋ฃจ์‹ ๋‹ค๋ฉด, ๋“ค์–ด์˜ค๋Š” ์ด๋ฏธ์ง€์˜ ํ”ฝ์…€ ์ˆ˜์— ์ƒํ•œ์„ ๊ฑธ์–ด๋‘์‹œ๋Š” ๊ฑธ ๊ถŒํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ ํฌ๊ธฐ๋งŒ ๋ณด๋ฉด ์•ˆ ๋˜๊ณ ์š”, ์••์ถ•์„ ํ’€์—ˆ์„ ๋•Œ์˜ ํ”ฝ์…€ ์ˆ˜๊ฐ€ ์ง„์งœ ๋น„์šฉ์ด๋ผ๋Š” ์ ์„ ๊ผญ ๊ธฐ์–ตํ•˜์‹œ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

728x90