본문 바로가기
Engineering Log (실습 및 프로젝트)/Local AI Project

Local AI Project 세 번째 이야기 - QLoRA 학습을 위한 반도체 공정 답변 데이터셋 만들기

by 온semi로 2026. 7. 13.
반응형

이전 2편까지는 본격적인 학습을 하기 전에 Gemma 4에 질문을 던지고 기본 답변을 받아보았다.

그리고 baseline_answers.txt 에 저장을 해뒀음. 

 

이건 비교기준이고, 이번에는 직접 준비한 모범 질문 - 답변 예시를 통해 본격적인 QLoRA 학습을 시켜볼 것이다.

그럼 이제 어떤 모범 답안을 만들어 둘 것이냐... 했을때, 공정 엔지니어가 원하는 건 항상 깔끔한 정리된 답변일 것이란 생각에

 

1. 현상 요약
2. 관련 공정 변수
3. 가능한 원인 가설
4. 추가 확인 데이터
5. 개선 실험 제안
6. 양산기술 관점 결론

 

이러한 구조로 답변을 하게 만들 것이다. 그래서 data/train.jsonl (이게 이제 Gemma의 학습 교재가 되는 것)에 이런 답변 레퍼런스를 넣어두는 것이다.

 

이후 QLoRA 학습에서는 Gemma가 train.jsonl에 들어 있는 질문과 모범 답변의 패턴을 학습하게 된다.

학습이 끝난 뒤 답변을 생성할 때마다 train.jsonl 파일을 직접 찾아보는 것은 아니다. 학습 과정에서 반영된 LoRA adapter를 통해 답변 구조를 따르게 된다.

 

그리고 이러한 jsonl파일은 scripts/make_train_jsonl.py 코드를 통해 생성할 것이다.


* JSON과 JSONL의 차이

잠깐 개념 먼저 공부하고 가자면, JSON은 데이터를 저장하는 형식이다. Python의 dictionary랑 비슷하게 생겼다.

 

{
"user": "PVD 공정에서 step coverage가 나빠지는 이유를 설명해줘.",
"assistant": "1. 현상 요약..."
}

 

이런 식으로 key-value 구조를 가지게 된다.

여기서 key 는 "user", "assistant" 이고, value는 각각 질문과 답변이다.

 

JSONL 은 JSON Lines이다. 

한 줄에 JSON 하나씩 들어간다. 예를 들면 train.jsonl은 개념적으로 다음과 같이 생겼다.

 

{"messages": [...]}
{"messages": [...]}
{"messages": [...]}
{"messages": [...]}

 

각 줄이 하나의 학습 예제인 셈이다.


전체 코드는 다음과 같다.

 

import json
from pathlib import Path

output_path = Path("data/train.jsonl")
output_path.parent.mkdir(parents=True, exist_ok=True)

system_prompt = (
    "You are a semiconductor process engineering assistant. "
    "Answer in Korean using a structured manufacturing-engineering format. "
    "Always organize the answer into: "
    "1. 현상 요약, 2. 관련 공정 변수, 3. 가능한 원인 가설, "
    "4. 추가 확인 데이터, 5. 개선 실험 제안, 6. 양산기술 관점 결론."
)

examples = [
    {
        "user": "PVD 공정에서 step coverage가 나빠지는 이유를 설명해줘.",
        "assistant": """1. 현상 요약
PVD는 target에서 방출된 입자가 기판에 도달해 박막을 형성하는 물리적 증착 공정입니다. 입자의 이동 방향성이 강하기 때문에 trench나 via처럼 깊고 좁은 구조에서는 내부까지 균일하게 막이 형성되기 어렵습니다.

2. 관련 공정 변수
입사각, chamber pressure, target-substrate distance, RF/DC power, 구조의 aspect ratio, wafer rotation 조건이 영향을 줄 수 있습니다.

3. 가능한 원인 가설
입자의 직진성이 강하면 상부에는 막이 잘 쌓이지만 sidewall이나 bottom까지 충분히 도달하지 못합니다. 이로 인해 bottom coverage 부족, sidewall thinning, void 형성 가능성이 커집니다.

4. 추가 확인 데이터
단면 SEM/TEM 이미지, 위치별 thickness profile, via/trench aspect ratio, chamber pressure trend, wafer edge와 center의 두께 차이를 확인해야 합니다.

5. 개선 실험 제안
chamber pressure 조정, ionized PVD 검토, collimated sputtering 적용, wafer bias 조건 변경, 또는 CVD/ALD 대체 가능성을 비교할 수 있습니다.

6. 양산기술 관점 결론
step coverage 문제는 open, void, resistance 증가로 이어질 수 있으므로 수율과 신뢰성 관점에서 조기 검출과 공정 조건 최적화가 필요합니다."""
    },
    {
        "user": "CVD 공정이 PVD보다 conformality 측면에서 유리한 이유를 설명해줘.",
        "assistant": """1. 현상 요약
CVD는 기체 precursor가 wafer 표면에서 화학 반응을 일으켜 박막을 형성하는 공정입니다. 기체가 구조 내부로 확산될 수 있기 때문에 PVD보다 복잡한 3D 구조에서 conformality가 유리한 경우가 많습니다.

2. 관련 공정 변수
precursor flow rate, chamber pressure, temperature, reaction rate, surface reaction probability, aspect ratio가 주요 변수입니다.

3. 가능한 원인 가설
PVD는 물리적 입자 이동 방향성이 강한 반면, CVD는 기체 precursor가 확산을 통해 sidewall과 bottom까지 도달할 수 있습니다. 따라서 고종횡비 구조에서도 상대적으로 균일한 막 형성이 가능합니다.

4. 추가 확인 데이터
단면 SEM/TEM, top/sidewall/bottom thickness ratio, precursor flow uniformity, wafer 내 위치별 두께 분포, deposition rate trend를 확인해야 합니다.

5. 개선 실험 제안
temperature 조건 최적화, precursor 공급량 조절, pressure 조건 변경, purge time 최적화, ALD와의 비교 실험을 진행할 수 있습니다.

6. 양산기술 관점 결론
conformality는 배선 단차, contact 구조, 3D 소자 구조에서 수율과 신뢰성에 직접 영향을 주므로 공정 선택과 조건 최적화가 중요합니다."""
    },
    {
        "user": "ALD 공정에서 self-limiting reaction이 중요한 이유를 설명해줘.",
        "assistant": """1. 현상 요약
ALD는 precursor를 순차적으로 공급하면서 표면에서 자기제한 반응을 통해 원자층 단위의 박막을 형성하는 공정입니다. self-limiting reaction은 일정량 이상 반응이 진행되지 않도록 해 막 두께 제어성을 높입니다.

2. 관련 공정 변수
precursor pulse time, purge time, chamber temperature, surface functional group, cycle 수, precursor saturation 여부가 중요합니다.

3. 가능한 원인 가설
self-limiting 특성이 충분하지 않으면 precursor가 과도하게 반응하거나 CVD-like growth가 발생할 수 있습니다. 이 경우 두께 균일도와 조성 제어가 나빠질 수 있습니다.

4. 추가 확인 데이터
cycle당 growth rate, GPC saturation curve, purge time별 impurity level, wafer 내 thickness uniformity, film composition 분석 결과를 확인해야 합니다.

5. 개선 실험 제안
pulse time saturation test, purge time split test, temperature window 평가, precursor 교체 검토, cycle 수에 따른 선형성 평가를 진행할 수 있습니다.

6. 양산기술 관점 결론
ALD의 self-limiting 특성은 고종횡비 구조에서 균일한 박막을 형성하고 원자층 수준의 두께 제어를 가능하게 하므로 첨단 공정에서 매우 중요합니다."""
    },
    {
        "user": "식각 공정에서 selectivity와 anisotropy의 차이를 설명해줘.",
        "assistant": """1. 현상 요약
selectivity는 식각 대상 물질과 마스크 또는 하부막 사이의 식각 속도 차이를 의미합니다. anisotropy는 식각이 수직 방향으로 얼마나 우세하게 진행되는지를 의미합니다.

2. 관련 공정 변수
plasma power, bias power, gas chemistry, pressure, temperature, mask material, etch time이 영향을 줍니다.

3. 가능한 원인 가설
selectivity가 낮으면 마스크 손실이나 하부막 손상이 발생할 수 있습니다. anisotropy가 낮으면 lateral etch가 증가해 CD 변화나 profile 불량이 발생할 수 있습니다.

4. 추가 확인 데이터
etch rate, mask loss, profile SEM, CD 변화량, sidewall angle, endpoint signal, 하부막 damage 여부를 확인해야 합니다.

5. 개선 실험 제안
gas ratio 변경, bias power 조정, pressure 최적화, hard mask 적용, over-etch time 조정, endpoint detection 조건 개선을 검토할 수 있습니다.

6. 양산기술 관점 결론
selectivity는 원하는 막만 선택적으로 제거하기 위한 조건이고, anisotropy는 패턴 형상을 유지하기 위한 조건입니다. 두 특성을 동시에 관리해야 CD uniformity와 수율을 안정화할 수 있습니다."""
    },
    {
        "user": "양산 공정에서 chamber pressure drift가 발생했을 때 어떤 데이터를 추가로 확인해야 하는지 설명해줘.",
        "assistant": """1. 현상 요약
chamber pressure drift는 공정 중 chamber 압력이 기준 조건에서 서서히 벗어나는 현상입니다. 압력 변화는 gas flow, pumping efficiency, leak, chamber condition 변화와 관련될 수 있습니다.

2. 관련 공정 변수
gas flow rate, throttle valve position, pump speed, chamber leak rate, RF power, temperature, recipe step별 pressure setpoint가 중요합니다.

3. 가능한 원인 가설
MFC 오차, throttle valve 동작 이상, pump 성능 저하, chamber 내부 오염, leak, PM 이후 condition 변화가 원인일 수 있습니다.

4. 추가 확인 데이터
time-series pressure log, MFC actual flow, throttle valve position trend, pump current, leak check 결과, PM 전후 비교, lot별 pressure 분포를 확인해야 합니다.

5. 개선 실험 제안
MFC calibration 확인, throttle valve 점검, pump 상태 점검, leak check, dummy wafer conditioning, PM 직후 안정화 recipe 평가를 수행할 수 있습니다.

6. 양산기술 관점 결론
pressure drift는 deposition rate, etch rate, plasma density, film uniformity에 영향을 줄 수 있으므로 FDC 관점에서 trend를 조기 감지하고 장비 상태와 공정 결과를 함께 분석해야 합니다."""
    },
    {
        "user": "공정 중 defect density가 증가했을 때 어떻게 원인을 좁혀가야 하는지 설명해줘.",
        "assistant": """1. 현상 요약
defect density 증가는 wafer 위의 particle, scratch, residue, pattern defect 등이 증가하는 현상입니다. 이는 수율 저하와 신뢰성 문제로 이어질 수 있습니다.

2. 관련 공정 변수
chamber condition, PM 이력, wafer handling, chemical 상태, gas purity, tool matching, lot history가 관련됩니다.

3. 가능한 원인 가설
chamber 내부 particle 증가, 부품 마모, cleaning 부족, chemical contamination, robot handling 문제, 특정 recipe step 조건 이상이 원인일 수 있습니다.

4. 추가 확인 데이터
defect map, defect review SEM, lot별 trend, chamber별 비교, PM 전후 defect 변화, wafer slot 위치, 공정 step별 defect inspection 결과를 확인해야 합니다.

5. 개선 실험 제안
chamber clean 진행, suspect part 점검, dummy wafer run, chemical 교체 전후 비교, 특정 chamber hold 또는 split lot test를 진행할 수 있습니다.

6. 양산기술 관점 결론
defect 문제는 단일 변수로 판단하기 어렵기 때문에 defect map, 장비 이력, 공정 조건, inspection 데이터를 함께 보고 원인을 단계적으로 좁혀가야 합니다."""
    },
    {
        "user": "공정 조건 변경 시 바로 양산에 적용하지 않고 DOE를 해야 하는 이유를 설명해줘.",
        "assistant": """1. 현상 요약
DOE는 여러 공정 변수를 체계적으로 변화시키며 결과에 미치는 영향을 분석하는 실험 설계 방법입니다. 공정 조건을 임의로 변경하면 예상하지 못한 부작용이 발생할 수 있습니다.

2. 관련 공정 변수
temperature, pressure, RF power, gas flow, time, bias, chemical concentration, wafer position 등이 DOE 변수로 설정될 수 있습니다.

3. 가능한 원인 가설
한 조건을 변경하면 target 특성은 개선될 수 있지만 uniformity, defect, CD, reliability 등 다른 지표가 악화될 수 있습니다. 변수 간 interaction도 존재할 수 있습니다.

4. 추가 확인 데이터
split 조건별 key metric, uniformity, defect count, CD shift, electrical test 결과, reliability 결과, chamber matching 데이터를 확인해야 합니다.

5. 개선 실험 제안
one-factor-at-a-time보다 factorial DOE 또는 response surface method를 검토하고, 주요 변수와 interaction을 분리해 분석할 수 있습니다.

6. 양산기술 관점 결론
DOE는 조건 변경의 효과와 리스크를 정량적으로 파악하기 위한 절차입니다. 양산 적용 전 DOE를 통해 process window와 품질 안정성을 확인해야 합니다."""
    },
    {
        "user": "공정에서 wafer edge non-uniformity가 발생했을 때 가능한 원인을 설명해줘.",
        "assistant": """1. 현상 요약
wafer edge non-uniformity는 wafer 중심부와 edge 영역의 두께, CD, etch rate, defect 수준이 다르게 나타나는 현상입니다. edge 영역은 공정 환경의 영향을 더 민감하게 받을 수 있습니다.

2. 관련 공정 변수
gas flow distribution, plasma density, chuck temperature, edge ring 상태, wafer centering, pressure, rotation 조건이 관련됩니다.

3. 가능한 원인 가설
edge ring 마모, wafer misalignment, plasma non-uniformity, temperature gradient, gas flow 불균일, chuck contact 불량이 원인일 수 있습니다.

4. 추가 확인 데이터
wafer map, radial profile, edge ring 사용 시간, chuck temperature map, chamber별 비교, wafer centering log, PM 전후 trend를 확인해야 합니다.

5. 개선 실험 제안
edge ring 교체 전후 비교, wafer centering 점검, gas flow 조건 split, chuck temperature calibration, chamber matching 평가를 진행할 수 있습니다.

6. 양산기술 관점 결론
edge non-uniformity는 die loss와 수율 저하로 이어질 수 있으므로 radial trend와 장비 부품 상태를 함께 분석해야 합니다."""
    },
    {
        "user": "FDC가 양산기술에서 중요한 이유를 설명해줘.",
        "assistant": """1. 현상 요약
FDC는 Fault Detection and Classification의 약자로, 장비와 공정 데이터를 실시간 또는 준실시간으로 모니터링해 이상 징후를 감지하고 분류하는 방법입니다.

2. 관련 공정 변수
temperature, pressure, RF power, gas flow, valve position, pump current, endpoint signal, plasma signal 등이 FDC 대상이 될 수 있습니다.

3. 가능한 원인 가설
공정 결과 이상은 장비 parameter drift, sensor 이상, 부품 열화, chamber condition 변화, recipe mismatch 등에서 시작될 수 있습니다.

4. 추가 확인 데이터
parameter trend, alarm log, lot history, PM history, chamber matching data, inline metrology 결과, yield trend를 함께 확인해야 합니다.

5. 개선 실험 제안
control limit 재설정, 이상 parameter ranking, chamber별 fingerprint 비교, PM 전후 model 재검증, false alarm과 missed detection 분석을 수행할 수 있습니다.

6. 양산기술 관점 결론
FDC는 불량이 대량으로 발생하기 전에 장비 이상을 조기에 감지하는 역할을 합니다. 양산에서는 수율과 품질을 안정적으로 유지하기 위해 FDC 기반 모니터링이 중요합니다."""
    },
    {
        "user": "공정 이상이 발생했을 때 원인 가설을 세우는 순서를 설명해줘.",
        "assistant": """1. 현상 요약
공정 이상이 발생하면 먼저 이상 현상을 명확히 정의하고, 관련 데이터를 바탕으로 가능한 원인을 단계적으로 좁혀야 합니다.

2. 관련 공정 변수
이상 발생 lot, wafer map, chamber ID, recipe step, 장비 parameter trend, inline metrology, defect inspection, PM 이력이 중요합니다.

3. 가능한 원인 가설
원인은 장비 상태 변화, recipe 조건 drift, material issue, wafer handling 문제, measurement error, upstream 공정 영향 등으로 나눌 수 있습니다.

4. 추가 확인 데이터
정상 lot과 이상 lot 비교, chamber별 차이, 시간 순서별 trend, PM 전후 변화, wafer position effect, 이전 공정 이력을 확인해야 합니다.

5. 개선 실험 제안
suspect chamber hold, split lot test, recipe parameter check, 장비 calibration, defect review, metrology 재측정을 진행할 수 있습니다.

6. 양산기술 관점 결론
원인 가설은 감으로 세우는 것이 아니라 데이터 기반으로 좁혀가야 합니다. 현상 정의, 데이터 비교, 가설 수립, 검증 실험 순서로 접근해야 재발 방지까지 연결할 수 있습니다."""
    },
]

with output_path.open("w", encoding="utf-8") as f:
    for example in examples:
        record = {
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": example["user"]},
                {"role": "assistant", "content": example["assistant"]},
            ]
        }
        f.write(json.dumps(record, ensure_ascii=False) + "\n")

print(f"Saved {len(examples)} training examples to {output_path}")

 

우분투에 코드를 입력하자.

코드를 넣었다

이 코드는 길고 복잡해보이지만 data/train.jsonl 를 만드는 코드. 그게 전부다.

Process는

 

1. JSON 파일을 다루기 위한 라이브러리를 불러온다.
2. train.jsonl을 저장할 경로를 정한다.
3. 모델에게 줄 system prompt를 만든다.
4. user 질문과 assistant 모범 답변 예시 10개를 만든다.
5. 각 예제를 system-user-assistant 구조로 바꾼다.
6. JSONL 형식으로 data/train.jsonl에 저장한다.

 

가 된다. 

 

차근차근 보겠다. 


 

1. JSON 파일을 다루기 위한 라이브러리를 불러온다.

import json
from pathlib import Path

 

먼저 json은 Python에서 JSON 데이터를 다루기 위한 기본 라이브러리이다.

최종적으로 만들 파일인 train.jsonl 에는 다음과 같은 구조의 데이터가 들어간다.

 

{"messages": [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]}

 

이런 데이터를 만들려면 Python dictionary를 JSON 문자열로 바꿔야 한다.

그때 쓰는게 json.dumps()이다.

여기서 dumps는 “dump string”이라고 보면 된다. Python 객체를 JSON 형식의 문자열로 바꾼다.

 

from pathlib import Path

Path는 파일 경로를 다루는 도구이다.

 

예를 들어 Path("data/train.jsonl") 가 있다고 하면, Python은 이걸 파일 경로로 인식한다.

문자열로 "data/train.jsonl"이라고 써도 되지만, Path를 쓰면 나중에 부모 폴더를 찾거나 파일을 열 때 편하다.


2. train.jsonl을 저장할 경로를 정한다.

output_path = Path("data/train.jsonl")
output_path.parent.mkdir(parents=True, exist_ok=True)

 

먼저 output_path = Path("data/train.jsonl") 은 결과 파일 위치를 정하는 코드이다. data안에 train.jsonl을 만들 것이다.

output_path가 data/train.jsonl 이므로 부모폴더는 data이다.

따라서 output_path.parentdata폴더를 의미한다.

 

output_path.parent.mkdir(parents=True, exist_ok=True) 는 data 폴더를 만드는 코드이다.

(make directory, mkdir는 이전에도 몇 번 쓴 함수이다)

괄호 안의 내용인 

parents=True 는 중간 폴더가 없으면 함께 만들라는 뜻이고

exist_ok=True 는 이미 폴더가 있어도 오류 내지 말라는 뜻이다.

 

즉, train.jsonl을 저장할 data 폴더가 없으면 만들고, 이미 있으면 그대로 사용한다.


3. 모델에게 줄 system prompt를 만든다.

system_prompt = (
    "You are a semiconductor process engineering assistant. "
    "Answer in Korean using a structured manufacturing-engineering format. "
    "Always organize the answer into: "
    "1. 현상 요약, 2. 관련 공정 변수, 3. 가능한 원인 가설, "
    "4. 추가 확인 데이터, 5. 개선 실험 제안, 6. 양산기술 관점 결론."
)

 

이 부분은 모델에게 줄 공통 지시문이다.

 


4. user 질문과 assistant 모범 답변 예시 10개를 만든다.

examples = [
    {
        "user": "PVD 공정에서 step coverage가 나빠지는 이유를 설명해줘.",
        "assistant": """1. 현상 요약
        ...
        """
    },
]

examples는 여러 개의 학습 예제를 담는 Python 리스트이다.
대괄호 [ ] 안에는 총 10개의 학습 예제가 들어 있고, 각 예제는 중괄호 { }로 표현한 dictionary 형태로 작성했다.
각 dictionary에는 user와 assistant라는 두 개의 key가 있다.

user에는 모델에게 입력할 질문이 들어간다.
assistant에는 해당 질문을 받았을 때 모델이 따라야 할 모범 답변이 들어간다.

즉, 질문 하나와 모범 답변 하나가 학습 예제 하나를 구성한다.

개념적으로는 다음과 같다.

examples
- 학습 예제 1
  - user 질문
  - assistant 모범 답변
- 학습 예제 2
  - user 질문
  - assistant 모범 답변
- ...
- 학습 예제 10
  - user 질문
  - assistant 모범 답변


assistant 답변은 여러 줄로 구성되어 있기 때문에 일반 따옴표가 아닌 triple quote인 """ """를 사용했다.

일반 따옴표는 보통 한 줄 문자열을 작성할 때 사용한다.
"한 줄 문장"

반면 triple quote를 사용하면 다음처럼 줄바꿈이 포함된 긴 문자열을 작성할 수 있다.

"""첫 번째 문장
두 번째 문장
세 번째 문장"""


이번 모범 답변은 1번 현상 요약부터 6번 양산기술 관점 결론까지 여러 문단으로 구성되기 때문에 triple quote가 필요했다.

이번에 만든 학습 예제의 주제는 다음과 같다.

1. PVD 공정의 step coverage
2. CVD 공정의 conformality
3. ALD 공정의 self-limiting reaction
4. 식각 공정의 selectivity와 anisotropy
5. Chamber pressure drift
6. Defect density 증가
7. DOE가 필요한 이유
8. Wafer edge non-uniformity
9. FDC의 중요성
10. 공정 이상 발생 시 원인 가설을 세우는 순서

10개의 데이터만으로 Gemma가 새로운 반도체 공정 지식을 충분히 학습한다고 보기는 어렵다.

이번 단계의 목적은 대규모 성능 향상이 아니라, 작은 데이터셋을 이용해 QLoRA 학습 과정이 정상적으로 작동하는지 먼저 확인하는 것이다.

또한 모든 assistant 답변을 같은 6개 항목으로 작성해, 모델이 답변 내용과 함께 일정한 출력 구조를 학습하도록 했다.


5. 각 예제를 system-user-assistant 구조로 바꾼다.

record = {
    "messages": [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": example["user"]},
        {"role": "assistant", "content": example["assistant"]},
    ]
}

앞에서 작성한 examples 리스트의 예제는 user 질문과 assistant 답변만 가지고 있었다.
for문에서는 examples 리스트에 들어 있는 예제를 하나씩 꺼낸다.


for example in examples:

첫 번째 반복에서는 PVD 질문과 모범 답변을 가져오고, 두 번째 반복에서는 CVD 질문과 모범 답변을 가져온다.

 

같은 방식으로 총 10개의 예제를 차례대로 처리한다.
현재 예제의 질문은 다음 코드로 가져온다.

example["user"]

현재 예제의 모범 답변은 다음 코드로 가져온다.

example["assistant"]

이후 record라는 새로운 dictionary를 만들고, 하나의 예제를 messages 구조로 변환한다.
system에는 앞에서 만든 공통 지시문인 system_prompt가 들어간다.

user에는 현재 예제의 질문이 들어간다.
assistant에는 현재 예제의 모범 답변이 들어간다.

따라서 모델이 학습 과정에서 보게 되는 하나의 데이터는 개념적으로 다음과 같다.

system
너는 반도체 공정 엔지니어링 어시스턴트이며, 지정된 6개 항목으로 답변해야 한다.

user
PVD 공정에서 step coverage가 나빠지는 이유를 설명해줘.

assistant
1. 현상 요약
2. 관련 공정 변수
3. 가능한 원인 가설
4. 추가 확인 데이터
5. 개선 실험 제안
6. 양산기술 관점 결론


여기서 system은 질문에 대한 정답이 아니라 모델이 따라야 할 공통 규칙이다.
user는 모델에 입력되는 질문이고, assistant는 모델이 예측하고 따라야 할 목표 답변이다.

이런 구조를 반복해서 보여주면 모델은 반도체 공정 질문과 6개 항목 답변 사이의 패턴을 학습하게 된다.


6. JSONL 형식으로 data/train.jsonl에 저장한다.

with output_path.open("w", encoding="utf-8") as f:
    for example in examples:
        record = {
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": example["user"]},
                {"role": "assistant", "content": example["assistant"]},
            ]
        }
        f.write(json.dumps(record, ensure_ascii=False) + "\n")

print(f"Saved {len(examples)} training examples to {output_path}")

 

여기서
with output_path.open("w", encoding="utf-8") as f:
이 코드는 앞에서 지정한 data/train.jsonl 파일을 쓰기 모드로 연다.

"w"는 write mode를 의미한다.
파일이 없으면 새로 생성하고, 같은 이름의 파일이 이미 있다면 기존 내용을 덮어쓴다.
따라서 코드를 다시 실행하면 기존 train.jsonl 내용은 새로 작성한 데이터로 바뀐다.

encoding="utf-8"은 한글이 깨지지 않도록 하기 위한 문자 인코딩 설정이다.

for example in examples:
이 for문을 통해 examples 리스트에 들어 있는 10개의 예제를 하나씩 처리한다.

각 예제를 system-user-assistant 구조의 record로 만든 뒤, 다음 코드로 파일에 저장한다.
f.write(json.dumps(record, ensure_ascii=False) + "\n")
record는 Python 내부의 dictionary이다.
이를 JSONL 파일에 저장하려면 JSON 형식의 문자열로 변환해야 한다.

json.dumps(record)
는 Python 객체를 JSON 문자열로 변환한다.
여기서 dumps는 Python 객체를 문자열 형태로 내보내는 함수라고 이해하면 된다.
ensure_ascii=False는 한글을 유니코드 escape 형태로 바꾸지 않고 실제 한글로 저장하기 위한 옵션이다.

이 옵션을 사용하지 않으면 한글이 다음과 같이 보일 수 있다.
"\ud604\uc0c1 \uc694\uc57d"
컴퓨터가 데이터를 읽는 데는 문제가 없지만, 사람이 확인하기는 어렵다.

ensure_ascii=False를 사용하면 다음과 같이 한글이 그대로 저장된다.

"현상 요약"

마지막의 + "\n"은 예제 하나를 저장한 뒤 줄바꿈을 추가하는 부분이다.

JSONL에서는 한 줄에 JSON 객체 하나를 저장해야 한다.

따라서 저장 구조는 다음과 같다.

첫 번째 줄
첫 번째 학습 예제

두 번째 줄
두 번째 학습 예제

세 번째 줄
세 번째 학습 예제


이와 같은 방식으로 총 10줄의 학습 데이터가 저장된다.

마지막으로 다음 코드를 통해 저장한 예제의 개수와 파일 경로를 출력한다.

print(f"Saved {len(examples)} training examples to {output_path}")

len(examples)는 examples 리스트 안에 있는 데이터 개수를 계산한다.

이번에는 예제가 10개이므로 다음 결과가 출력된다.


이제 작성한 Python 코드를 실행한다.
python scripts/make_train_jsonl.py
코드가 정상적으로 실행되면 다음 문장이 출력된다.


Saved 10 training examples to data/train.jsonl

이라는 결과는 examples 리스트에 들어 있던 총 10개의 예제가 data/train.jsonl 파일에 저장되었다는 의미이다.

다음으로 파일이 실제로 만들어졌는지 확인한다.

ls -lh data/train.jsonl

입력


출력 결과에서 data/train.jsonl 파일이 표시되므로 파일이 정상적으로 생성된 것을 확인할 수 있다.
18K는 생성된 파일의 크기가 약 18KB라는 의미이다.
앞부분의 -rw-r--r--는 Linux 파일 권한을 나타내고, localai는 파일의 소유자와 그룹을 나타낸다.
이번 단계에서는 파일이 생성되었는지와 크기가 0이 아닌지만 확인해도 충분하다.

다음으로 train.jsonl 안에 실제 데이터가 저장되었는지 확인한다.

head -n 2 data/train.jsonl

head는 파일의 앞부분을 출력하는 Linux 명령어이다.

-n 2는 파일의 첫 2줄만 출력하라는 뜻이다.

 

JSONL에서는 한 줄이 학습 예제 하나이므로, 이 명령어를 실행하면 첫 번째와 두 번째 학습 예제가 출력된다.
화면에서는 한 줄이 매우 길게 이어져 있어 처음에는 파일이 잘못 만들어진 것처럼 보일 수 있다.
하지만 이는 정상적인 결과이다.

한 학습 예제 안에는 system 지시문, user 질문, assistant 모범 답변이 모두 들어 있기 때문에 한 줄의 길이가 길어진다.
또한 assistant 답변에 작성했던 실제 줄바꿈은 JSON 문자열 안에서 \n으로 표시된다.

예를 들어 다음과 같이 보일 수 있다.

"content": "1. 현상 요약\nPVD는...\n\n2. 관련 공정 변수\n..."

학습 코드가 이 JSON 문자열을 불러오면 \n을 다시 줄바꿈으로 해석할 수 있다.



파일이 생성되고 내용이 출력되었다고 해서 데이터 구조가 모두 올바르다고 확신할 수는 없다.
JSON의 따옴표나 괄호가 잘못되었거나, messages 안의 role 순서가 바뀌면 이후 QLoRA 학습 코드에서 오류가 발생할 수 있다.

그래서 train.jsonl 파일을 한 줄씩 읽어 구조를 검사했다.

검증에 사용한 코드는 다음과 같다.

import json

path = "data/train.jsonl"

with open(path, "r", encoding="utf-8") as f:
    for i, line in enumerate(f, start=1):
        obj = json.loads(line)
        assert "messages" in obj
        assert len(obj["messages"]) == 3
        assert obj["messages"][0]["role"] == "system"
        assert obj["messages"][1]["role"] == "user"
        assert obj["messages"][2]["role"] == "assistant"

print("JSONL validation passed.")

 

검증 코드를 실행하면

 

JSONL validation passed.

라는 결과가 출력되었다.
이 코드를 블록별로 보면 다음과 같다.

path = "data/train.jsonl"
검사할 파일의 경로를 지정한다.

with open(path, "r", encoding="utf-8") as f:
train.jsonl 파일을 읽기 모드로 연다.

"r"은 read mode를 의미한다.

for i, line in enumerate(f, start=1):
파일을 한 줄씩 읽는다.

JSONL에서는 한 줄이 학습 예제 하나이므로, 총 10개의 예제를 하나씩 검사하는 과정이다.

enumerate는 각 줄에 번호를 붙여준다.

start=1을 사용했기 때문에 첫 번째 줄의 번호는 1부터 시작한다.

obj = json.loads(line)
파일에서 읽어온 line은 아직 문자열이다.
json.loads는 JSON 형식의 문자열을 Python dictionary로 변환한다.

앞에서 파일을 저장할 때 사용한 json.dumps와 반대 방향이다.

json.dumps
Python 객체를 JSON 문자열로 변환한다.

json.loads
JSON 문자열을 Python 객체로 변환한다.

그다음 assert를 이용해 데이터 구조를 확인한다.
assert "messages" in obj
각 예제 안에 messages라는 key가 존재하는지 검사한다.

assert len(obj["messages"]) == 3
messages 안에 정확히 3개의 메시지가 들어 있는지 확인한다.
이번 데이터셋에서는 system, user, assistant가 하나씩 들어가야 하므로 길이가 3이어야 한다.


assert obj["messages"][0]["role"] == "system"
assert obj["messages"][1]["role"] == "user"
assert obj["messages"][2]["role"] == "assistant"

각 메시지의 role이 의도한 순서대로 들어 있는지도 확인한다.

Python 리스트의 번호는 0부터 시작하므로 다음과 같이 대응된다.

messages[0]
첫 번째 메시지인 system

messages[1]
두 번째 메시지인 user

messages[2]
세 번째 메시지인 assistant


assert 뒤의 조건이 맞지 않으면 프로그램은 AssertionError를 발생시키고 중단된다.

이번에는 모든 줄이 조건을 통과했기 때문에 마지막까지 실행되어 JSONL validation passed.가 출력되었다.



head 명령어로 본 JSONL은 한 줄이 너무 길어 사람이 구조를 파악하기 어려웠다.

그래서 다음 코드를 이용해 첫 번째 예제만 들여쓰기해서 출력했다.

import json

with open("data/train.jsonl", "r", encoding="utf-8") as f:
    first = json.loads(f.readline())

print(json.dumps(first, ensure_ascii=False, indent=2))

f.readline()은 파일에서 첫 번째 줄만 읽는다.

json.loads를 사용해 첫 번째 줄의 JSON 문자열을 Python 객체로 변환한다.

마지막 json.dumps에서는 indent=2라는 옵션을 추가했다.
indent=2는 JSON 구조에 맞춰 두 칸씩 들여쓰기해 출력하라는 의미이다.

 

 

출력 결과를 보면 하나의 학습 예제가 다음 구조로 이루어진 것을 확인할 수 있다.

messages
- system
- user
- assistant


system의 content에는 모델의 역할과 6개 답변 항목에 대한 지시가 들어 있다.
user의 content에는 PVD 공정의 step coverage에 대한 질문이 들어 있다.
assistant의 content에는 1번 현상 요약부터 6번 양산기술 관점 결론까지 작성한 모범 답변이 들어 있다.

head 명령어로 확인했을 때는 모든 내용이 한 줄로 이어져 있었지만, indent=2를 적용하니 데이터의 계층 구조를 보다 쉽게 확인할 수 있었다.



이번 단계에서는 아직 Gemma 4의 QLoRA 학습을 실행하지 않았다.

현재까지 진행한 작업은 학습에 사용할 질문과 모범 답변을 만들고, 이를 train.jsonl 파일로 저장한 것이다.
비유하면 train.jsonl은 모델이 공부할 교재에 해당한다.

다음 단계에서는 이 교재를 실제 학습 코드로 불러와 Gemma 4의 LoRA adapter를 학습시킨다.

이번에 만든 데이터셋은 총 10개의 예제로 구성되어 있다.
따라서 이 데이터만으로 Gemma 4에 방대한 반도체 지식을 새롭게 학습시켰다고 평가할 수는 없다.

또한 현재 모범 답변은 직접 작성한 예시이므로, 실제 논문 실험에 사용할 때는 공정 지식의 정확성, 표현의 일관성, 데이터 편향 여부를 추가로 검토해야 한다.

이번 단계에서는 다음 항목을 확인하는 데 의미가 있다.

1. JSONL 데이터셋 생성 코드가 정상적으로 작동하는가
2. system-user-assistant 구조가 올바르게 저장되는가
3. 한글 질문과 답변이 깨지지 않는가
4. 이후 QLoRA 학습 코드가 읽을 수 있는 형태인가



이번 train.jsonl에는 실제 반도체 Fab의 원본 공정 데이터를 사용하지 않았다.
PVD, CVD, ALD, 식각, FDC와 관련된 일반적인 공정 질문과 직접 작성한 모범 답변만 넣었다.
실제 회사의 공정 데이터에는 장비 조건, 레시피, 수율, 불량 이력과 같은 민감한 정보가 포함될 수 있다.
이런 데이터를 그대로 LLM 학습 데이터에 넣으면 모델의 답변이나 학습 산출물을 통해 정보가 노출될 가능성을 검토해야 한다.

향후 프로젝트에서는 다음과 같은 구조를 목표로 한다.

원본 공정 데이터

머신러닝 분석 모듈만 접근

정제된 분석 결과

민감정보 제거와 권한 확인

로컬 LLM

정제된 결과만 전달받아 설명과 보고서 생성


즉, QLoRA adapter에는 공정 엔지니어형 답변 형식과 문제 분석 흐름을 학습시키고, 원본 공정 데이터에 대한 접근은 별도의 권한 통제 계층에서 관리하는 방향으로 발전시킬 예정이다.



이번에는 Gemma 4의 QLoRA 학습을 진행하기 전에, 모델이 참고할 질문과 모범 답변 데이터셋을 만들었다.

이번 데이터셋은 QLoRA 학습 파이프라인을 검증하기 위한 작은 데이터셋이다.

다음 이야기에서는 이번에 만든 train.jsonl을 불러와 NF4 4bit 상태의 Gemma 4에 LoRA adapter를 붙이고, 실제 QLoRA 학습을 진행해볼 것이다.