12 KiB
P&ID 재설계 계획 - Step 1: 현황 분석
작성일: 2026-05-04 상태: 완료
시스템의 본질 목적
ExperionCrawler의 핵심:
- Honeywell Experion HS R530과 OPC UA 통신
- 각 태그의 실시간 값 + 이력 데이터 저장/활용
- P&ID 파싱은 부가 기능 (도면 ↔ 실시간 데이터 연결)
현재 문제
No-10_Plant_PID.dxf처리 시 30분 타임아웃- 원인: 전체 DXF(28,819개 엔티티)를 한 번에 LLM에 전달
- LLM 응답이 1800초를 초과
DXF 파일 분석 결과
파일: src/Web/uploads/pid/No-10_Plant_PID.dxf
파일 내 페이지 구성
-
PFD (PIPE & FLOW DIAGRAM) : 위치 PFD 텍스트 우측 최상단 2 페이지
- ROLE : 플랜트의 장비 구성 및 성격, 처리해야할 물질 정보, 플랜트 유체 흐름 개략도 ,#10 PLANT 의 중요 정보를 표시한 도면,
- Information : Full of Equipment Information & Role, Pump : Size,Type,Capacity,Material
-
SYMBOL & LEGEND : 1 페이지,
| 항목 | 값 |
|---|---|
| 총 엔티티 | 28,819개 |
| LINE | 20,868개 |
| TEXT | 3,562개 |
| ARC | 1,324개 |
| CIRCLE | 1,275개 |
| LWPOLYLINE | 865개 |
| 레이어 | 33개 |
| 전체 좌표 | X: 1,543 |
- #10 PLANT P&ID : 9 PAGES, (X:1630,Y54) 약 9개 도면이 수평으로 배치:
| 도면 | X 범위 | 내용 |
|---|---|---|
| #1 | -176~2,000 | SYMBOL & LEGEND (레전드) |
| #2 | 2,000~2,800 | P&ID 도면 |
| #3 | 2,800~3,600 | P&ID 도면 |
| #4~9 | 3,600~5,600 | P&ID 도면 |
레전드 페이지 정점: X=1644.26, Y=5875.98
P&ID 재설계 계획 - Step 2: 성공 테스트 분석
작성일: 2026-05-04 상태: 완료
성공한 테스트 스크립트
| 파일 | 추출 대상 | 계측기 유형 |
|---|---|---|
| test_dxf_extract_pid1.py | Sensor | FT, FIT, LT, PT, TE, PG, LG, TG |
| test_dxf_extract_pid2.py | Valve | FCV, TCV, LCV, PCV, XV, FV, LV, PV, TV |
| test_dxf_extract_pid3.py | System | LI, PI, TI, FIQ, FICQ, TICA, PICA, LICA, FIC, TIC, PIC, LIC |
| test_dxf_extract_pid-PG.py | Gauge | PG, TG, LG |
| Pump | P-10101, VP-10117, DP-10101 |
성공 요인
- 계측기 유형별 분할 - 각 호출당 특정 태그만 추출
- 짧고 명확한 프롬프트 - "Extract valve tags only" 등
- max_tokens=65536 - 충분한 출력 공간
- 직접 vLLM 호출 - MCP 서버 경유 X (오버헤드 없음)
- 동시 실행 - 4개 스크립트 병렬 → GPU 최대 활용
현재 시스템과의 차이
| 테스트 (성공) | 현재 (실패) | |
|---|---|---|
| 호출 경로 | 직접 vLLM | C# → MCP → pid_worker → vLLM |
| 프롬프트 | 유형별 분할 | 모든 태그 한 번에 |
| max_tokens | 65536 | 32768 |
| 병렬 처리 | 4개 동시 | 1개 순차 |
핵심 통찰
- 분할 + 병렬 = 빠른 처리 + 높은 정확도
- LLM이 처리해야 할 범위가 좁을수록 정확도 향상
- vLLM이 여러 요청을 병렬로 처리하면 GPU 활용도 최대화
P&ID 재설계 계획 - Step 3: 레전드 페이지 분석
작성일: 2026-05-04 상태: 완료
레전드 페이지 위치
- DXF 파일 내 첫 번째 도면 영역
- 정점: X=1644.26, Y=5875.98
- 영역: X: -176 ~ 2,000
INSTRUMENT IDENTIFICATION TABLE
ISA-5.1 표준 기반 계측기 식별 테이블:
| 첫 글자 | 의미 | 후속 글자 | 의미 |
|---|---|---|---|
| A | ANALYSIS, AUTO | A | ALARM |
| B | BURNER COMBUSTION | B | BISTABLE |
| C | CONDUCTIVITY | C | CONTROL |
| D | DENSITY | D | DIFFERENTIAL, DETECTOR |
| E | VOLTAGE, HEAT ENERGY | E | PRIMARY ELEMENT |
| F | FLOW RATE | F | RATIO |
| G | GAUGE, GAS | G | GAUGE, GLASS |
| H | HAND | H | HIGH, HOLE |
| I | CURRENT | I | INDICATOR |
| K | TIME | K | CONTROL STATION |
| L | LEVEL | L | LOW, PILOT LIGHT |
| M | MAN, MOTOR | M | MOTOR OPERATOR |
| P | PRESSURE | ||
| T | TEMPERATURE |
EQUIPMENT IDENTIFICATION
| 글자 | 장비 유형 |
|---|---|
| C | COLUMN |
| K | COMPRESSOR |
| T | TANK |
| D | DRUM |
| E | HEAT EXCHANGER |
생성된 파일
mcp-server/pipeline/legend_parser.py- 계측기 유형별 그룹 정의
- 태그에서 AREA 번호 추출 로직
P&ID 재설계 계획 - Step 4: 해결 방향
작성일: 2026-05-04 상태: 논의 중
목표
사용자가 "TI-6111C가 어디에 있나?"라고 물으면:
- "6호 정제탑, 밑에서 3번째 온도 센서입니다. 현재 온도 105도"
핵심 전략
- 도면 분할 - 한 DXF 파일 내 여러 도면을 좌표 기반으로 분리
- 계측기 유형별 병렬 LLM 호출 - Sensor/Valve/System/Gauge/Pump 동시 처리
- 위치 정보 저장 - 태그의 DXF 상 좌표 + 근처 텍스트
- AREA 그룹핑 - 태그명에서 플랜트 번호 유추
구현 단계
Step 5: 도면 분할 로직
- TITLE 레이어 LINE으로 도면 경계 감지
- 각 도면별 X/Y 범위 추출
- 레전드 페이지는 제외 (X < 2000)
Step 6: 계측기 유형별 병렬 LLM
- pid_worker.py 수정
- asyncio.gather()로 4개 동시 호출
- max_tokens=65536 적용
Step 7: 위치 정보 추출
- 태그 근처 텍스트 함께 추출
- 상하좌우 위치 파악
Step 8: AREA 그룹핑
- 태그명 파싱 (FICQ-6XXX → 6호)
- DB 스키마 수정 또는 동적 그룹핑
Step 9: Experion 태그 매핑
- 기존 match_pid_tags 활용
- 실시간 값 연동
Step 10: UI 연동
- 태그 검색 시 위치 + 현재 값 표시
유지할 것
- topology.py의 배관-설비 연결 로직 (향후 활용)
- 기존 DB 스키마 (pid_equipment 테이블)
변경할 것
-
pid_worker.py: 병렬 LLM 호출
-
server.py: 도면 분할 후 전달
-
extractor.py: 도면별 텍스트 추출
이렇게 해도 안된다면 나중에 고려할것,
- ProcessPoolExecutor 사용: asyncio 대신 multiprocessing을 사용하여 5개의 CPU 프로세스에서 각각 요청을 날리게 합니다. (이것이 사실상 5개 프로그램을 띄우는 것과 가장 유사한 물리적 구조입니다.)
- vLLM 서버 튜닝:--max-num-batched-tokens: 이 값을 Blackwell 메모리에 맞게 더 크게 키우세요 (예: 32768 이상).
- --gpu-memory-utilization: 0.95 정도로 높여서 KV 캐시를 최대한 확보하세요.
- HTTP 클라이언트 튜닝: httpx나 aiohttp 사용 시 limits 옵션을 수정하여 동시 연결 수를 강제로 늘려야 합니다. (기본값에 걸려있을 확률이 큽니다.)
아래는 C# 일때의 참고사항 -- 패스해
C#(.NET)에서는 파이썬의 multiprocessing과 유사하게 병렬 처리를 위한 여러 가지 방법을 제공합니다. 하지만 C#은 기본적으로 멀티스레딩에 매우 강력하며, CPU 집약적인 작업을 분산하기 위해 Task 기반의 병렬 처리나 별도의 프로세스 생성을 활용합니다.사용자님의 상황(LLM API에 5개의 요청을 동시에, 최대한 독립적으로 빠르게 던지기)에 가장 적합한 세 가지 구현 방식을 소개합니다.1. Parallel.ForEachAsync (가장 권장하는 방식).NET 6 이상에서 지원하며, 별도의 프로세스를 띄우지 않고도 내부적으로 스레드 풀을 활용해 최적의 병렬성을 유지하며 비동기 HTTP 요청을 보냅니다. 파이썬의 asyncio.gather보다 시스템 자원을 더 공격적으로 사용합니다.csharpusing System.Net.Http.Json;
var client = new HttpClient(); var prompts = new[] { "질문 1", "질문 2", "질문 3", "질문 4", "질문 5" };
// 동시에 실행할 최대 병렬 수 설정 (5개) var options = new ParallelOptions { MaxDegreeOfParallelism = 5 };
await Parallel.ForEachAsync(prompts, options, async (prompt, cancellationToken) => { var requestData = new { model = "Qwen2.5-27B-Instruct-FP8", messages = new[] { new { role = "user", content = prompt } } };
var response = await client.PostAsJsonAsync("http://localhost:8000/v1/chat/completions", requestData);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"응답 수신 완료: {result.Substring(0, 50)}...");
}); 코드를 사용할 때는 주의가 필요합니다.2. Task.Run을 이용한 물리적 스레드 분리파이썬의 멀티프로세싱과 가장 유사한 느낌으로, 각 요청을 완전히 별도의 스레드에 할당하여 실행합니다. CPU 오버헤드를 분산시키는 데 효과적입니다.csharpvar tasks = prompts.Select(prompt => Task.Run(async () => { // 여기서 API 호출 로직 수행 return await CallVllmApiAsync(prompt); }));
// 5개의 작업을 동시에 시작하고 모두 끝날 때까지 대기 var results = await Task.WhenAll(tasks); 코드를 사용할 때는 주의가 필요합니다.3. 실제 별도 프로세스 실행 (Process.Start)만약 파이썬에서 프로그램 5개를 따로 실행했던 것과 물리적으로 완전히 동일한 환경을 만들고 싶다면, C# 메인 프로그램에서 자기 자신(또는 별도 CLI)을 5번 실행하는 방식을 씁니다.csharpfor (int i = 0; i < 5; i++) { ProcessStartInfo startInfo = new ProcessStartInfo { FileName = "MyLlmClient.exe", // 실행할 프로그램명 Arguments = $"--prompt "질문{i}"", UseShellExecute = true // 별도 창에서 실행하여 독립성 확보 }; Process.Start(startInfo); } 코드를 사용할 때는 주의가 필요합니다.왜 C# 구현이 파이썬보다 유리할 수 있나요?No GIL: C#은 파이썬과 달리 GIL이 없어 하나의 프로세스 내에서도 여러 CPU 코어를 100% 독립적으로 활용합니다. 따라서 Parallel.ForEachAsync만 써도 파이썬에서 프로그램 5개를 띄운 것과 흡사한 성능이 나옵니다.HttpClient 커넥션 풀: .NET의 HttpClient는 내부적으로 소켓 연결을 효율적으로 관리하므로, 요청이 꽂히는 속도가 파이썬보다 훨씬 빠르고 정교합니다.팁: 성능 차이를 줄이려면C#에서 호출할 때 HttpClient 인스턴스를 하나만 생성(정적 또는 싱글톤)해서 재사용하세요. 매번 생성하면 소켓 고갈 및 핸드셰이크 지연으로 인해 토큰 생성 속도가 느려질 수 있습니다.위 방법 중 어떤 방식(단일 프로그램 내 병렬 vs 실제 별도 창 실행)이 현재 프로젝트 구조에 더 적합해 보이시나요? 상황에 맞는 더 디테일한 코드를 짜드릴 수 있습니다.
너는 어떤게 더 효율적이고 안전하다고 생각해? 안전성과 관리 효율성, 그리고 실제 성능을 종합해 볼 때 저는 1번 방식(Parallel.ForEachAsync)이 가장 우수하다고 생각합니다.그 이유를 세 가지 측면에서 분석해 드립니다.1. 효율성 (Resource Efficiency)프로세스 방식: 프로그램 5개를 따로 띄우면 런타임(Runtime) 메모리가 5배로 들고, OS 스케줄러가 5개의 프로세스를 왔다 갔다 하며 컨텍스트 스위칭(Context Switching) 비용이 발생합니다.Parallel 방식: 하나의 프로세스 안에서 .NET의 고성능 스레드 풀을 사용합니다. 불필요한 메모리 낭비 없이 CPU 코어를 가장 효율적으로 사용하여 vLLM에 요청을 전달합니다.2. 안전성 및 제어 (Control & Safety)병렬 수 제어: MaxDegreeOfParallelism 옵션을 통해 동시 요청 수를 코드 한 줄로 조절할 수 있습니다. (예: 5개에서 10개로 늘리거나, 서버 부하 시 2개로 줄이는 작업이 매우 쉽습니다.)예외 처리: 5개 중 하나가 실패했을 때, 개별 프로그램 방식은 로그를 일일이 확인해야 하지만, Parallel 방식은 try-catch 하나로 모든 요청의 성공/실패를 통합 관리할 수 있어 시스템이 훨씬 안정적입니다.3. 실제 성능 (Throughput)사용자께서 파이썬에서 경험하셨던 '속도 차이'는 파이썬의 구조적 한계(GIL) 때문일 가능성이 큽니다. C#의 Parallel은 물리적인 멀티코어를 직접 활용하기 때문에, 굳이 번거롭게 프로그램을 5개 띄우지 않아도 그에 못지않은(혹은 더 빠른) 성능을 낼 수 있습니다.