RTX 5070 Ti로 Qwen2.5-VL 산업 검사 배포한 기록
데모가 아니라 실제 생산 라인 투입을 목표로, QR 코드 결함 검사 데이터 파이프라인에서 Qwen2.5-VL-7B-AWQ를 RTX 5070 Ti에 배포한 경험을 정리했다.
중국어 원문을 AI로 번역했습니다. 고유명사와 수치는 원문 표기를 우선하며, 중요한 판단에는 아래 출처 원문을 함께 확인하세요.
배경: 데모가 아니라 생산 라인에 올려야 한다
최근 QR 코드 결함 품질 검사 데이터 파이프라인을 만들고 있는데, 그중 한 계층(L3 정제 계층)에서는 VLM이 두 가지를 판단해야 한다:
- 코드 종류 식별: QR / DPM 도트 코드 / 기타 바코드 / 코드 없음 / 판단 불가
- 가시성 판정: 완전함 / 부분 가시 / 희미하게 식별 가능 / 완전히 보이지 않음
판정 결과는 하위 라벨링 데이터의 정제와 등급 분류를 직접 결정한다. 하드웨어는 RTX 5070 Ti(16GB VRAM) 한 장이고, 모델은 Qwen2.5-VL-7B-Instruct의 AWQ 양자화 버전을 선택했다.
이것은 데모가 아니라 매일 수만 장의 크롭 이미지를 처리해야 하는 배치 작업이므로, "결과가 나오는가"보다 안정성과 VRAM 제어가 훨씬 더 중요하다.
왜 7B + AWQ인가
7B 파라미터를 bf16으로 저장하면 약 14GB이고, 여기에 추론 시의 KV cache와 비전 인코더 오버헤드까지 더하면 16GB 카드에는 거의 들어가지 않으며, 컨텍스트가 길어지면 당연히 OOM이 난다. AWQ 양자화 후 모델 파일은 6.5GB에 불과해 KV cache와 배치 추론을 위한 공간이 충분히 남는다. "가시성 판정" 같은 분류 작업에서는 양자화로 인한 정밀도 손실을 감수할 수 있다. 어차피 prompt에 열거 정의를 고정해 두었으니 모델은 규칙적인 판단만 하면 되고, 긴 텍스트 생성이 필요 없기 때문이다.
최종적으로 돌아간 환경 레시피
먼저 결론부터 말하면, 이 조합은 5070 Ti에서 정상 동작했다:
구성 요소 버전 시스템 Ubuntu 24.04 GPU RTX 5070 Ti 16GB / 드라이버 580.159.03 CUDA Toolkit (nvcc) 12.0 Python 3.11.16 torch 2.13.0+cu130 transformers 5.17.0 vllm 0.30.0 flashinfer-python 0.6.18 모델 Qwen2.5-VL-7B-Instruct-AWQ(6.5GB)
삽질 기록
함정 1: flashinfer sampler는 반드시 꺼야 한다
가장 실질적인 함정이다. 코드에 영문 모를 환경 변수 한 줄이 있는데, 삽질 끝에 추가한 것이다:
import os os.environ.setdefault( "VLLM_USE_FLASHINFER_SAMPLER" , "0" )
vLLM은 기본적으로 flashinfer의 샘플링 kernel을 활성화하는데, 현재 이 버전 조합(vllm 0.30 + flashinfer 0.6.18 + cu130)에서는 추론 이상을 일으킨다. 끄고 나면 모든 게 정상이다.
비슷한 환경에서 vllm 생성 결과가 이상하거나, 멈추거나, 크래시가 난다면 가장 먼저 이 스위치를 0으로 설정하라. 반나절의 삽질을 아낄 수 있다.
함정 2: CUDA Toolkit 버전과 torch 버전은 "맞출" 필요가 없다
많은 튜토리얼이 사람들을 오도하는 부분이다. 머신에서 nvcc --version은 CUDA 12.0을 보여주지만 torch는 2.13.0+cu130(CUDA 13.0으로 컴파일)이다 — 이것은 정상이며, 권장되는 방식이기도 하다.
PyTorch는 자체 CUDA 런타임을 내장하므로, 추론에는 시스템 nvcc와 torch의 CUDA 버전이 일치할 필요가 없다. 시스템 nvcc는 커스텀 CUDA 연산자(예: 직접 만든 flash-attn)를 컴파일할 때만 맞춰야 한다. 당신의 시나리오가 단순히 "모델을 로드해 추론을 돌리는 것"이라면, 절대 "버전 맞추기"를 위해 시스템 CUDA를 재설치하지 마라. 순전히 스스로 일을 만드는 짓이다.
함정 3: 16GB VRAM의 핵심 파라미터
16GB 카드에서 7B 멀티모달 모델을 돌리려면, 이 몇 개의 파라미터가 살아남을 수 있느냐를 결정한다:
llm = LLM( model=MODEL, max_model_len= 4096 , # 컨텍스트를 너무 크게 열지 마라, KV cache가 VRAM을 먹는다 gpu_memory_utilization= 0.85 , # 15%는 비전 인코더와 단편화를 위해 남겨라 limit_mm_per_prompt={ "image" : 1 }, # 핵심! prompt당 이미지를 최대 1장으로 제한 )
limit_mm_per_prompt는 vLLM 멀티모달의 숨은 함정이다: 제한하지 않으면 다중 이미지 입력 시 VRAM 피크를 제어할 수 없다. 우리 시나리오는 샘플마다 크롭 이미지 한 장이므로 1로 제한하는 것이 가장 안전하다.
또한 이미지 입력에는 해상도 상한도 제어해야 한다:
{ "type" : "image" , "image" : "file://" + cpath, "max_pixels" : 602112 }
602112(약 784×768)는 "코드 이미지가 선명한지 판단" 같은 작업에는 충분하며, 더 크게 하면 VRAM만 낭비된다.
함정 4: 멀티모달 입력은 문자열을 그냥 이어붙이면 안 된다
이것은 vLLM + Qwen-VL의 흔한 실수다. 올바른 방법은 먼저 processor로 템플릿을 적용하고, 그다음 process_vision_info로 이미지를 처리하는 것이다:
from vllm import LLM, SamplingParams from transformers import AutoProcessor from qwen_vl_utils import process_vision_info processor = AutoProcessor.from_pretrained(MODEL) messages = [{ "role" : "user" , "content" : [ { "type" : "image" , "image" : "file://" + cpath, "max_pixels" : 602112 }, { "type" : "text" , "text" : PROMPT}, ]}] prompt = processor.apply_chat_template(messages, tokenize= False , add_generation_prompt= True ) img_data, _ = process_vision_info(messages) inputs = { "prompt" : prompt, "multi_modal_data" : { "image" : img_data[ 0 ]}} outs = llm.generate([inputs], SamplingParams(temperature= 0.0 , max_tokens= 200 ))
이미지 경로를 그냥 prompt 문자열에 쑥 집어넣으면, 모델은 그저 "상상" 속의 이미지 한 장을 그려놓고 헛소리를 늘어놓는다.
함정 5: 출력 파싱은 정규식 요행이 아니라 prompt 규율에 달려 있다
판정 작업에서 가장 두려운 것은 모델이 출력을 제멋대로 늘어놓는 것이다. 세 가지 제약을 반드시 prompt에 적어 넣어야 한다:
- **열거값 정의를 확정해서** 명시할 것, 경계 상황까지 포함해서. 예를 들어 "매우 흐릿하지만 코드 구조를 식별할 수 있으면 no_code가 아니라 qr로 판정", "DataMatrix에는 回자형 위치 검출 코너가 없다"——이런 설명이 모델이 스스로 추측하는 것보다 훨씬 유용하다.
- **한 줄의 JSON만 출력**, temperature=0, max_tokens=200과 함께 사용.
- **파싱 실패에는 반드시 안전장치(폴백)**를 둘 것: 우리는 실패 샘플을 별도로 로그에 기록하며, 조용히 버리지 않는다.
함정 6: 엔지니어링 디테일이 파라미터 튜닝보다 실행 완료 여부에 더 큰 영향을 준다
실제로 수만 건의 데이터를 돌릴 때, 이 몇 가지 처리가 목숨을 구해준다:
- **이어달리기(체크포인트 재개)**: 출력 jsonl을 (이미지, 목표 id) 기준으로 중복 제거하여, 중단 후 다시 돌려도 중복 계산하지 않는다
- **회전 박스 자르기**: QR코드 어노테이션은 회전 박스라서, 외접 사각형으로 그냥 자르면 배경이 잔뜩 따라온다. 각도대로 회전시킨 뒤 자르고 + 10% padding을 주면 효과가 더 좋다
- **실패가 전체를 막지 않게**: 단일 샘플의 자르기 실패나 파싱 실패는 기록 후 건너뛰고, 전체 배치를 중단하지 않는다
효과와 결론
이 구성은 5070 Ti에서 잘라낸 이미지의 배치 추론을 돌릴 때 안정적으로 사용 가능하다. "코드 체계 분류 + 가시성 등급화" 같은 작업에서, 7B AWQ 모델의 판정 결과는 바로 사람의 표본 검사(샘플링 검수) 공정에 투입할 수 있어, 기존의 순수 수작업 검토 작업량을 대체했다.
몇 가지 경험 정리:
- **16GB 단일 카드로 7B 멀티모달 배치 추론이 충분히 가능**, 핵심은 AWQ 양자화 + VRAM 파라미터 조이기
- **vLLM의 함정은 버전 조합에 몰려 있다**, 이상한 문제를 만나면 먼저 VLLM_USE_FLASHINFER_SAMPLER=0을 시도하라
- **멀티모달 추론에서 processor 단계를 아끼지 마라**
- **prompt에 열거형과 경계를 명확히 적는 것이 더 큰 모델로 바꾸는 것보다 효과적**
이 파이프라인은 이후 문서 인식, 영수증 인식, 결함 분류 등의 시나리오에 재사용할 수 있다——본질적으로 모두 "이미지 한 장 + 분류 규칙 하나를 주고, VLM이 구조화된 판정을 출력하게 하는" 것이다.
태그 제안: Qwen2.5-VL, vLLM, 멀티모달, RTX 5070 Ti, 모델 배포