Files
ExperionCrawler/P&ID_병렬LLM_아키텍처_개선안_v2.md

23 KiB

P&ID 도면 파싱 병렬 LLM 아키텍처 개선안 (수정본 v3)

1. 기존 문제점 분석

1.1 DXF 파일 엔티티 분포

엔티티 타입 수량 비율 처리 방식
LINE 20,868 72.4% 기하학적 추출 (GPU 불필요)
TEXT 3,562 12.4% LLM 매핑 필요
ARC 1,324 4.6% 기하학적 추출 (GPU 불필요)
CIRCLE 1,275 4.4% 기하학적 추출 (GPU 불필요)
LWPOLYLINE 865 3.0% 기하학적 추출 (GPU 불필요)
MTEXT 363 1.3% LLM 매핑 필요
ELLIPSE 190 0.7% 기하학적 추출 (GPU 불필요)
HATCH 103 0.4% 기하학적 추출 (GPU 불필요)
INSERT 103 0.4% 기하학적 추출 (GPU 불필요)
SOLID 77 0.3% 기하학적 추출 (GPU 불필요)
SPLINE 65 0.2% 기하학적 추출 (GPU 불필요)
POINT 17 0.1% 기하학적 추출 (GPU 불필요)
POLYLINE 4 0.0% 기하학적 추출 (GPU 불필요)
LEADER 2 0.0% 기하학적 추출 (GPU 불필요)
OLE2FRAME 1 0.0% 기하학적 추출 (GPU 불필요)

총 엔티티 수: 28,819개

핵심 발견:

  • **LINE이 72.4%**를 차지 → 배관 처리가 핵심 병목
  • TEXT/MTEXT만 13.7% → LLM 매핑은 TEXT 중심
  • LINE은 기하학적 추출만으로 충분 (LLM 불필요)

1.2 현재 구조의 병목

단계 문제점 심각도
Phase 1 ezdxf로 28,000개 엔티티 처리 0.58초 (양호)
Phase 2 O(n²) 노드 병합 timeout (심각)
Phase 3 순차적 LLM API 호출 예측 불가능한 지연

1.2 test_dxf_extract_pid*.py의 성공적인 병렬 처리 구조

# test_dxf_extract_pid1.py, pid2.py, pid3.py의 공통 구조
chunks = [
    {
        'name': 'Field Instruments - Sensors',
        'system': 'Extract sensor tags only...',
        'user': 'Extract ALL tags of FT, FIT, LT, PT...'
    },
    {
        'name': 'Field Instruments - Valves',
        'system': 'Extract valve tags only...',
        'user': 'Extract ALL tags of FCV, TCV, LCV...'
    },
    {
        'name': 'System Tags',
        'system': 'Extract system tags only...',
        'user': 'Extract ALL tags of LI, PI, TI...'
    }
]

핵심 발견:

  • 청크 단위 분할: 태그 유형별로 프롬프트를 분리
  • 독립된 프로세스 실행: 각 청크를 별도의 Python 프로그램으로 실행
  • vLLM GPU 자원 최대화: 각 프로세스가 별도의 GPU에 할당됨

2. 정확한 병렬 처리 전략

2.1 vLLM의 tensor parallelism 이해

vLLM의 병렬 처리 방식:

  • tensor parallelism: 단일 프로세스 내에서 여러 GPU 카드를 사용
  • multi-process: 여러 프로세스가 각각 별도의 GPU 카드를 사용

문제점:

  • 하나의 프로세스에서 여러 요청을 보낼 경우 → 단일 GPU에만 할당
  • 여러 프로세스에서 각각 요청을 보낼 경우 → 각 GPU에 별도로 할당

해결책:

  • test_dxf_extract_pid*.py처럼 각 청크를 독립된 프로그램으로 실행
  • python pid_extractor_sensor.py & python pid_extractor_valve.py & python pid_extractor_system.py &

2.2 병렬 실행 구조

┌─────────────────────────────────────────────────────────────────────┐
│              P&ID 도면 파싱 파이프라인 (병렬 LLM + LINE 처리)         │
└─────────────────────────────────────────────────────────────────────┘

Phase 1: 기하학적 추출 (ezdxf)
├─ DXF 파일 로드 (0.84초)
├─ 엔티티별 BBox 계산 (0.58초)
│  ├─ LINE (20,868개) → 배관 선 추출
│  ├─ TEXT (3,562개) → 태그명 추출
│  ├─ ARC/CIRCLE (2,599개) → 기하학적 도형
│  └─ 기타 (1,790개) → 기하학적 도형
└─ 결과: 28,257개 GeometricEntity

Phase 2: 위상 빌더 (공간 인덱스)
├─ 공간 인덱스 생성 (R-tree)
├─ 노드 병합 (O(n log n))
│  ├─ LINE 병합 → 배관 연결
│  └─ TEXT 병합 → 태그명 정제
└─ 결과: NetworkX 그래프

Phase 3: 병렬 LLM 매핑 (독립 프로그램 실행)
├─ 프로그램 1: pid_extractor_text.py (GPU 0)
│  ├─ 프롬프트: "Extract all tag names from TEXT entities"
│  └─ 결과: 3,562개 태그명
│
├─ 프로그램 2: pid_extractor_valve.py (GPU 1)
│  ├─ 프롬프트: "Extract valve tags (FCV, TCV, LCV, PCV, XV, ...)"
│  └─ 결과: 80개 태그
│
├─ 프로그램 3: pid_extractor_equipment.py (GPU 2)
│  ├─ 프롬프트: "Extract equipment tags (Pump, Tank, Heat Exchanger)"
│  └─ 결과: 50개 태그
│
├─ 프로그램 4: pid_extractor_system.py (GPU 3)
│  ├─ 프롬프트: "Extract system tags (FICQ, TICA, PICA, ...)"
│  └─ 결과: 120개 태그
│
└─ 결과 병합: 3,812개 매핑된 태그

2.3 LINE (배관) 처리 전략

문제점:

  • LINE이 20,868개 (72.4%)로 압도적으로 많음
  • LINE은 태그명이 없으므로 LLM 매핑 불필요
  • LINE은 기하학적 추출 + 공간 인덱스로 처리

해결책:

  1. Phase 1: ezdxf로 LINE 추출 → 좌표 저장
  2. Phase 2: R-tree로 LINE 병합 → 배관 연결
  3. Phase 3: LLM 매핑 불필요 (기하학적 연결만 사용)

구현 예시:

# LINE 추출 (ezdxf)
for entity in msp:
    if entity.dxftype() == 'LINE':
        start = entity.dxf.start
        end = entity.dxf.end
        line_data = {
            'entity_id': entity.dxf.handle,
            'entity_type': 'LINE',
            'start': (start.x, start.y),
            'end': (end.x, end.y),
            'length': ((end.x - start.x)**2 + (end.y - start.y)**2)**0.5
        }
        lines.append(line_data)

# LINE 병합 (R-tree)
# 인접 LINE을 연결하여 배관 경로 생성

2.4 병렬 실행 스크립트 (run_parallel_extract.sh)

#!/bin/bash
# P&ID 태그 추출 병렬 실행 스크립트

DXF_FILE="/path/to/pid.dxf"
OUTPUT_DIR="/path/to/output"

# Phase 1: 기하학적 추출 (ezdxf)
echo "Phase 1: 기하학적 추출 시작..."
python pid_geometric_extractor.py "$DXF_FILE" "$OUTPUT_DIR/geometric_data.json"
echo "Phase 1 완료: geometric_data.json 저장"

# Phase 2: 위상 빌더 (공간 인덱스)
echo "Phase 2: 위상 빌더 시작..."
python pid_topology_builder.py "$OUTPUT_DIR/geometric_data.json" "$OUTPUT_DIR/topology.json"
echo "Phase 2 완료: topology.json 저장"

# Phase 3: 병렬 LLM 매핑 (4개 프로그램 동시에 실행)
echo "Phase 3: 병렬 LLM 매핑 시작..."

# GPU 0: TEXT 태그 추출
python pid_extractor_text.py "$DXF_FILE" "$OUTPUT_DIR" &
# GPU 1: VALVE 태그 추출
python pid_extractor_valve.py "$DXF_FILE" "$OUTPUT_DIR" &
# GPU 2: EQUIPMENT 태그 추출
python pid_extractor_equipment.py "$DXF_FILE" "$OUTPUT_DIR" &
# GPU 3: SYSTEM 태그 추출
python pid_extractor_system.py "$DXF_FILE" "$OUTPUT_DIR" &

# 모든 프로세스가 완료될 때까지 대기
wait

# 결과 병합
echo "결과 병합 시작..."
python merge_results.py "$OUTPUT_DIR" "$OUTPUT_DIR/merged_tags.json"
echo "Phase 3 완료: merged_tags.json 저장"

echo "전체 파이프라인 완료!"

3. 상세 구현 계획

3.1 Phase 1: 기하학적 추출 (변경 없음)

# pid_geometric_extractor.py (현재 그대로 사용)
class PidGeometricExtractor:
    def __init__(self, file_path: str):
        self.doc = ezdxf.readfile(file_path)
        self.msp = self.doc.modelspace()
    
    def extract_and_save(self, output_path: str):
        results = []
        for entity in self.msp:
            bbox_obj = self.get_bbox(entity)
            # ... 추출 로직
        return results

3.2 Phase 2: 위상 빌더 (공간 인덱스 도입)

# pid_topology_builder.py (개선안)
from rtree import index

class PidTopologyBuilder:
    def __init__(self, geometric_data: List[Dict[str, Any]]):
        self.data = geometric_data
        self.G = nx.DiGraph()
    
    def build_graph(self):
        # 1. 공간 인덱스 생성
        self._build_spatial_index()
        
        # 2. 노드 병합 (R-tree 사용)
        self._merge_nodes_spatial()
        
        # 3. 태그-설비 연결
        self._link_tags_to_equipment()
        
        # 4. 배관 연결
        self._link_pipes()
    
    def _build_spatial_index(self):
        """R-tree 공간 인덱스 생성"""
        p = index.Property()
        self.idx = index.Index(properties=p)
        for i, item in enumerate(self.data):
            bbox = item['bbox']
            self.idx.insert(i, (
                bbox['min_x'], bbox['min_y'],
                bbox['max_x'], bbox['max_y']
            ))
    
    def _merge_nodes_spatial(self):
        """공간 인덱스를 사용한 병합 (O(n log n))"""
        merge_threshold = 2.0
        merged = []
        visited = set()
        
        for i, item in enumerate(self.data):
            if i in visited:
                continue
            
            bbox = item['bbox']
            # 인접 노드만 검색
            neighbors = list(self.idx.intersection((
                bbox['min_x'] - merge_threshold,
                bbox['min_y'] - merge_threshold,
                bbox['max_x'] + merge_threshold,
                bbox['max_y'] + merge_threshold
            )))
            
            # ... 병합 로직

3.3 Phase 3: 병렬 LLM 매핑 (신규 구현)

3.3.0 pid_extractor_line.py (LINE 배관 추출 - GPU 불필요)

#!/usr/bin/env python3
"""
P&ID 도면에서 LINE (배관) 추출 (CPU 전용, GPU 불필요)
"""

import ezdxf
import json
import sys

def main():
    if len(sys.argv) != 3:
        print("Usage: python pid_extractor_line.py <dxf_file> <output_dir>")
        sys.exit(1)
    
    dxf_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    # DXF 파일 읽기
    doc = ezdxf.readfile(dxf_file)
    msp = doc.modelspace()
    
    # LINE 추출
    lines = []
    for entity in msp:
        if entity.dxftype() == 'LINE':
            start = entity.dxf.start
            end = entity.dxf.end
            line_data = {
                'entity_id': entity.dxf.handle,
                'entity_type': 'LINE',
                'start': (start.x, start.y),
                'end': (end.x, end.y),
                'length': ((end.x - start.x)**2 + (end.y - start.y)**2)**0.5
            }
            lines.append(line_data)
    
    # 결과 저장
    output_file = f'{output_dir}/line_data.json'
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(lines, f, indent=2, ensure_ascii=False)
    
    print(f'LINE 추출 완료: {len(lines)}개')

if __name__ == '__main__':
    main()

3.3.1 pid_extractor_text.py (TEXT 태그 추출 - GPU 0)

#!/usr/bin/env python3
"""
P&ID 도면에서 TEXT 태그 추출 (GPU 0 전용)
"""

import ezdxf
import json
import sys
from ezdxf.tools.text import plain_mtext
from openai import OpenAI

def main():
    if len(sys.argv) != 3:
        print("Usage: python pid_extractor_text.py <dxf_file> <output_dir>")
        sys.exit(1)
    
    dxf_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    # DXF 파일 읽기
    doc = ezdxf.readfile(dxf_file)
    msp = doc.modelspace()
    
    # 텍스트 추출
    texts = []
    for entity in msp:
        if entity.dxftype() == 'TEXT':
            texts.append(entity.dxf.text)
        elif entity.dxftype() == 'MTEXT':
            try:
                plain = plain_mtext(entity.dxf.text)
                if plain.strip():
                    texts.append(plain)
            except Exception:
                pass
    
    text = '\n'.join(texts)
    
    # OpenAI 클라이언트 생성
    llm = OpenAI(
        base_url='http://localhost:8000/v1',
        api_key='dummy',
        timeout=1800
    )
    
    # 프롬프트
    system = (
        'You are a P&ID expert. Extract all tag names from TEXT entities.\n'
        'Return ONLY a JSON array.\n'
        '\n'
        'Format: [{"tagNo":"FICQ-10101","confidence":0.95},...]\n'
    )
    
    user = f'Extract ALL tag names from the text below:\n\n{text[:100000]}'
    
    # LLM 호출
    resp = llm.chat.completions.create(
        model='Qwen/Qwen3-Coder-Next-FP8',
        messages=[
            {'role': 'system', 'content': system},
            {'role': 'user', 'content': user},
        ],
        max_tokens=65536,
        temperature=0.1,
        extra_body={'chat_template_kwargs': {'enable_thinking': False}},
    )
    
    # 결과 저장
    raw = (resp.choices[0].message.content or '').strip()
    # ... JSON 파싱 및 저장 로직
    
    output_file = f'{output_dir}/text_tags.json'
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(all_tags, f, indent=2, ensure_ascii=False)

if __name__ == '__main__':
    main()

3.3.2 pid_extractor_valve.py (Valve 태그 추출 - GPU 1)

#!/usr/bin/env python3
"""
P&ID 도면에서 Sensor 태그 추출 (GPU 0 전용)
"""

import ezdxf
import json
import sys
from ezdxf.tools.text import plain_mtext
from openai import OpenAI

def main():
    if len(sys.argv) != 3:
        print("Usage: python pid_extractor_sensor.py <dxf_file> <output_dir>")
        sys.exit(1)
    
    dxf_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    # DXF 파일 읽기
    doc = ezdxf.readfile(dxf_file)
    msp = doc.modelspace()
    
    # 텍스트 추출
    texts = []
    for entity in msp:
        if entity.dxftype() == 'TEXT':
            texts.append(entity.dxf.text)
        elif entity.dxftype() == 'MTEXT':
            try:
                plain = plain_mtext(entity.dxf.text)
                if plain.strip():
                    texts.append(plain)
            except Exception:
                pass
    
    text = '\n'.join(texts)
    
    # OpenAI 클라이언트 생성
    llm = OpenAI(
        base_url='http://localhost:8000/v1',
        api_key='dummy',
        timeout=1800
    )
    
    # 프롬프트
    system = (
        'You are a P&ID expert. Extract sensor tags only.\n'
        'Return ONLY a JSON array.\n'
        '\n'
        'Instrument types to extract: FT, FIT, LT, PT, TE, PG, LG, TG\n'
        'Format: [{"tagNo":"FT-10101","confidence":0.95},...]\n'
    )
    
    user = f'Extract ALL tags of FT, FIT, LT, PT, TE, PG, LG, TG from the text below:\n\n{text[:100000]}'
    
    # LLM 호출
    resp = llm.chat.completions.create(
        model='Qwen/Qwen3-Coder-Next-FP8',
        messages=[
            {'role': 'system', 'content': system},
            {'role': 'user', 'content': user},
        ],
        max_tokens=65536,
        temperature=0.1,
        extra_body={'chat_template_kwargs': {'enable_thinking': False}},
    )
    
    # 결과 저장
    raw = (resp.choices[0].message.content or '').strip()
    # ... JSON 파싱 및 저장 로직
    
    output_file = f'{output_dir}/sensor_tags.json'
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(all_tags, f, indent=2, ensure_ascii=False)

if __name__ == '__main__':
    main()

3.3.2 pid_extractor_valve.py (Valve 태그 추출)

#!/usr/bin/env python3
"""
P&ID 도면에서 Valve 태그 추출 (GPU 1 전용)
"""

import ezdxf
import json
import sys
from ezdxf.tools.text import plain_mtext
from openai import OpenAI

def main():
    if len(sys.argv) != 3:
        print("Usage: python pid_extractor_valve.py <dxf_file> <output_dir>")
        sys.exit(1)
    
    dxf_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    # ... 동일한 로직 (프롬프트만 다름)
    
    system = (
        'You are a P&ID expert. Extract valve tags only.\n'
        'Return ONLY a JSON array.\n'
        '\n'
        'Instrument types to extract: FCV, TCV, LCV, PCV, XV, FV, LV, PV, TV\n'
        'Format: [{"tagNo":"FCV-10101","confidence":0.95},...]\n'
    )
    
    # ... 나머지 로직

3.3.3 pid_extractor_equipment.py (Equipment 태그 추출)

#!/usr/bin/env python3
"""
P&ID 도면에서 Equipment 태그 추출 (GPU 2 전용)
"""

import ezdxf
import json
import sys
from ezdxf.tools.text import plain_mtext
from openai import OpenAI

def main():
    if len(sys.argv) != 3:
        print("Usage: python pid_extractor_equipment.py <dxf_file> <output_dir>")
        sys.exit(1)
    
    dxf_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    # ... 동일한 로직 (프롬프트만 다름)
    
    system = (
        'You are a P&ID expert. Extract equipment tags only.\n'
        'Return ONLY a JSON array.\n'
        '\n'
        'Equipment types to extract: Pump, Tank, Heat Exchanger, Vessel, Column\n'
        'Format: [{"tagNo":"P-101","confidence":0.95},...]\n'
    )
    
    # ... 나머지 로직

3.3.4 pid_extractor_system.py (System 태그 추출)

#!/usr/bin/env python3
"""
P&ID 도면에서 System 태그 추출 (GPU 3 전용)
"""

import ezdxf
import json
import sys
from ezdxf.tools.text import plain_mtext
from openai import OpenAI

def main():
    if len(sys.argv) != 3:
        print("Usage: python pid_extractor_system.py <dxf_file> <output_dir>")
        sys.exit(1)
    
    dxf_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    # ... 동일한 로직 (프롬프트만 다름)
    
    system = (
        'You are a P&ID expert. Extract system tags only.\n'
        'Return ONLY a JSON array.\n'
        '\n'
        'System types to extract: FICQ, TICA, PICA, LICA, FIC, TIC, PIC, LIC\n'
        'Format: [{"tagNo":"FICQ-10101","confidence":0.95},...]\n'
    )
    
    # ... 나머지 로직

3.3.5 merge_results.py (결과 병합)

#!/usr/bin/env python3
"""
병렬 추출 결과 병합 스크립트
"""

import json
import sys
import glob

def main():
    if len(sys.argv) != 3:
        print("Usage: python merge_results.py <input_dir> <output_file>")
        sys.exit(1)
    
    input_dir = sys.argv[1]
    output_file = sys.argv[2]
    
    # 모든 JSON 파일 읽기
    all_tags = []
    seen_tags = set()
    
    for filepath in glob.glob(f'{input_dir}/*_tags.json'):
        with open(filepath, 'r', encoding='utf-8') as f:
            tags = json.load(f)
            for tag in tags:
                tag_no = tag.get('tagNo')
                if tag_no and tag_no not in seen_tags:
                    seen_tags.add(tag_no)
                    all_tags.append(tag)
    
    # 결과 저장
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(all_tags, f, indent=2, ensure_ascii=False)
    
    print(f'총 추출 태그 수: {len(all_tags)}개')

if __name__ == '__main__':
    main()

4. 성능 예측

4.1 Phase 1: 기하학적 추출

  • 현재: 1.4초
  • 개선 후: 1.4초 (변화 없음)

4.2 Phase 2: 위상 빌더

  • 현재: timeout (O(n²))
  • 개선 후: 2-3초 (R-tree O(n log n))

4.3 Phase 3: 병렬 LLM 매핑

  • 현재: 예측 불가 (순차적 API 호출)
  • 개선 후: 5-10초 (4개 프로그램 병렬 실행)

예상 속도 향상:

  • Phase 2: 100배 이상 (timeout → 2-3초)
  • Phase 3: 3-5배 (순차적 → 병렬)

4.4 LINE (배관) 처리 특징

  • **LINE이 20,868개 (72.4%)**로 압도적으로 많음
  • ezdxf로 기하학적 추출만으로 충분 (LLM 불필요)
  • R-tree로 공간 인덱스 생성 → 배관 연결
  • GPU 자원 소모 없음 (CPU 전용 처리)

병렬 실행 구조:

Phase 1: ezdxf로 28,000개 엔티티 추출 (1.4초)
├─ LINE (20,868개) → CPU 전용 추출
├─ TEXT (3,562개) → CPU 전용 추출
└─ 기타 (3,589개) → CPU 전용 추출

Phase 2: R-tree로 위상 빌더 (2-3초)
├─ LINE 병합 → 배관 연결
└─ TEXT 병합 → 태그명 정제

Phase 3: 4개 프로그램 병렬 실행 (5-10초)
├─ pid_extractor_text.py (GPU 0) → 3,562개 태그
├─ pid_extractor_valve.py (GPU 1) → 80개 태그
├─ pid_extractor_equipment.py (GPU 2) → 50개 태그
└─ pid_extractor_system.py (GPU 3) → 120개 태그

5. GPU 자원 활용 전략

5.1 vLLM의 GPU 할당 방식

단일 프로세스 (현재 구조):

Python 프로세스 A
├─ LLM Request 1 → GPU 0 (100% 사용)
├─ LLM Request 2 → GPU 0 (대기)
└─ LLM Request 3 → GPU 0 (대기)

→ GPU 1, 2, 3은 놀고 있음

다중 프로세스 (개선안):

Python 프로세스 A → GPU 0 (100% 사용)
Python 프로세스 B → GPU 1 (100% 사용)
Python 프로세스 C → GPU 2 (100% 사용)
Python 프로세스 D → GPU 3 (100% 사용)

→ 모든 GPU 카드를 최대한 활용

5.2 병렬 실행 명령어

# 4개 프로그램을 동시에 실행
python pid_extractor_sensor.py /path/to/pid.dxf /path/to/output &
python pid_extractor_valve.py /path/to/pid.dxf /path/to/output &
python pid_extractor_equipment.py /path/to/pid.dxf /path/to/output &
python pid_extractor_system.py /path/to/pid.dxf /path/to/output &

# 모든 프로세스가 완료될 때까지 대기
wait

# 결과 병합
python merge_results.py /path/to/output /path/to/output/merged_tags.json

6. 구현 우선순위

순위 작업 예상 시간 영향도
1 R-tree 공간 인덱스 도입 1일 HIGH
2 pid_extractor_line.py 구현 (LINE 추출) 0.5일 HIGH
3 pid_extractor_text.py 구현 (TEXT 추출) 0.5일 HIGH
4 pid_extractor_valve.py 구현 0.5일 HIGH
5 pid_extractor_equipment.py 구현 0.5일 HIGH
6 pid_extractor_system.py 구현 0.5일 HIGH
7 merge_results.py 구현 0.5일 LOW
8 run_parallel_extract.sh 구현 0.5일 LOW
9 테스트 및 벤치마크 0.5일 LOW

총 예상 시간: 4.5일

핵심 포인트:

  • LINE (20,868개): ezdxf로 기하학적 추출만으로 충분 (LLM 불필요)
  • TEXT (3,562개): LLM 매핑 필요 (GPU 0)
  • 기타 (3,589개): LLM 매핑 필요 (GPU 1-3)

7. 결론

7.1 핵심 개선 포인트

  1. Phase 1: ezdxf로 28,000개 엔티티 추출 (1.4초)
    • LINE (20,868개): 기하학적 추출만으로 충분 (LLM 불필요)
    • TEXT (3,562개): LLM 매핑 필요
  2. Phase 2: R-tree 공간 인덱스로 O(n²) → O(n log n) 개선
  3. Phase 3: test_dxf_extract_pid*.py의 병렬 처리 구조 도입
  4. 독립 프로그램 실행: 각 청크를 별도의 Python 프로그램으로 실행
  5. GPU 자원 최대화: 4개 프로그램이 각각 별도의 GPU에 할당
  6. LINE 처리 전략: ezdxf + R-tree로 CPU 전용 처리 (GPU 불필요)

7.2 예상 성능

  • 현재: timeout (Phase 2에서 멈춤)
  • 개선 후: 약 7-13초 (28,000개 엔티티 기준)
  • 속도 향상: 100배 이상 (Phase 2), 3-5배 (Phase 3)

7.3 구현 전략

  1. 먼저 Phase 2 (공간 인덱스) 구현 → Phase 2 timeout 해결
  2. Phase 3 (병렬 LLM) 구현 → test_dxf_extract_pid*.py 구조 참고
  3. 전체 파이프라인 통합 → 벤치마크 테스트

7.4 GPU 활용 전략

  • 4개의 독립된 Python 프로그램을 동시에 실행
  • 각 프로그램이 vLLM의 별도 GPU에 할당됨
  • 모든 GPU 카드를 100% 활용하여 처리 속도 최대화