preflight-gpu 0.0.1.dev0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- preflight_gpu-0.1.1/.gitignore +232 -0
- preflight_gpu-0.1.1/LICENSE +21 -0
- preflight_gpu-0.1.1/PKG-INFO +207 -0
- preflight_gpu-0.1.1/docs/adr/0001-vram-measurement-over-static-calculation.md +18 -0
- preflight_gpu-0.1.1/docs/adr/0002-subprocess-isolation-for-canary.md +22 -0
- preflight_gpu-0.1.1/docs/adr/0003-relative-baseline-timing.md +20 -0
- preflight_gpu-0.1.1/docs/adr/0004-canary-size-scaled-by-hidden.md +45 -0
- preflight_gpu-0.1.1/docs/adr/0005-cli-exit-codes-for-ci-integration.md +34 -0
- preflight_gpu-0.1.1/docs/adr/0006-ram-recorded-internally-only.md +96 -0
- preflight_gpu-0.1.1/docs/adr/0007-driver-version-based-torch-cuda-wheel-selection.md +87 -0
- preflight_gpu-0.1.1/docs/adr/0008-no-auto-fix-for-user-judgment-causes.md +37 -0
- preflight_gpu-0.1.1/docs/adr/0009-blackwell-geforce-cuda-wheel-override.md +63 -0
- preflight_gpu-0.1.1/docs/adr/0010-pre-written-result-over-exit-code.md +53 -0
- preflight_gpu-0.1.1/docs/architecture.md +336 -0
- preflight_gpu-0.1.1/docs/contracts/canary-api.md +356 -0
- preflight_gpu-0.1.1/docs/contracts/cli.md +281 -0
- preflight_gpu-0.1.1/pyproject.toml +118 -0
- preflight_gpu-0.1.1/readme.md +171 -0
- preflight_gpu-0.1.1/src/preflight/__init__.py +10 -0
- preflight_gpu-0.1.1/src/preflight/__main__.py +18 -0
- preflight_gpu-0.1.1/src/preflight/canary/__init__.py +0 -0
- preflight_gpu-0.1.1/src/preflight/canary/engine.py +162 -0
- preflight_gpu-0.1.1/src/preflight/canary/judge.py +94 -0
- preflight_gpu-0.1.1/src/preflight/canary/model.py +633 -0
- preflight_gpu-0.1.1/src/preflight/canary/worker.py +654 -0
- preflight_gpu-0.1.1/src/preflight/cli.py +437 -0
- preflight_gpu-0.1.1/src/preflight/fix/__init__.py +0 -0
- preflight_gpu-0.1.1/src/preflight/fix/causes.py +199 -0
- preflight_gpu-0.1.1/src/preflight/fix/executor.py +480 -0
- preflight_gpu-0.1.1/src/preflight/gpu.py +59 -0
- preflight_gpu-0.1.1/src/preflight/report.py +911 -0
- preflight_gpu-0.1.1/src/preflight/reverify.py +42 -0
- preflight_gpu-0.1.1/tests/__init__.py +0 -0
- preflight_gpu-0.1.1/tests/conftest.py +17 -0
- preflight_gpu-0.1.1/tests/test_cli.py +892 -0
- preflight_gpu-0.1.1/tests/test_engine.py +686 -0
- preflight_gpu-0.1.1/tests/test_fix.py +1093 -0
- preflight_gpu-0.1.1/tests/test_gpu.py +106 -0
- preflight_gpu-0.1.1/tests/test_judge.py +274 -0
- preflight_gpu-0.1.1/tests/test_model.py +998 -0
- preflight_gpu-0.1.1/tests/test_report.py +1636 -0
- preflight_gpu-0.1.1/tests/test_reverify.py +485 -0
- preflight_gpu-0.0.1.dev0/.gitignore +0 -5
- preflight_gpu-0.0.1.dev0/PKG-INFO +0 -25
- preflight_gpu-0.0.1.dev0/README.md +0 -13
- preflight_gpu-0.0.1.dev0/pyproject.toml +0 -22
- preflight_gpu-0.0.1.dev0/src/preflight_check/__init__.py +0 -7
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
219
|
+
|
|
220
|
+
# Preflight — 현재 작업용 압축 브리프 (AGENTS.md 참고, Git 미추적)
|
|
221
|
+
.agent/
|
|
222
|
+
|
|
223
|
+
# AI 에이전트 CLI의 로컬 상태 (개인 설정·임시 워크트리). 팀원마다 쓰는 도구가 달라
|
|
224
|
+
# 저장소에 들어가면 안 된다 — AGENTS.md의 규칙 원본은 .agents/skills/ 쪽이다.
|
|
225
|
+
.claude/
|
|
226
|
+
|
|
227
|
+
# GitHub 이슈/PR 작성용 임시 파일
|
|
228
|
+
issue_body*
|
|
229
|
+
pr_body*
|
|
230
|
+
|
|
231
|
+
# 개인용 문서
|
|
232
|
+
docs/정성오/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Preflight team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: preflight-gpu
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: 파인튜닝을 시작하기 전, 지금 환경이 실제로 준비됐는지 확인하는 진단 CLI
|
|
5
|
+
Project-URL: Homepage, https://github.com/plzHynx500/preflight
|
|
6
|
+
Project-URL: Repository, https://github.com/plzHynx500/preflight
|
|
7
|
+
Project-URL: Issues, https://github.com/plzHynx500/preflight/issues
|
|
8
|
+
Project-URL: PyPI, https://pypi.org/project/preflight-gpu/
|
|
9
|
+
Author: Preflight team
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: bitsandbytes,cuda,diagnostics,fine-tuning,gpu,lora,qlora
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: GPU :: NVIDIA CUDA
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
|
+
Classifier: Topic :: System :: Systems Administration
|
|
27
|
+
Classifier: Topic :: Utilities
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Requires-Dist: nvidia-ml-py>=12
|
|
30
|
+
Requires-Dist: rich>=13.0
|
|
31
|
+
Requires-Dist: typer>=0.12
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# Preflight
|
|
38
|
+
|
|
39
|
+
> 파인튜닝을 시작하기 전, 지금 환경이 실제로 준비됐는지 확인합니다.
|
|
40
|
+
|
|
41
|
+
*오픈소스 · 파인튜닝 환경 진단 도구*
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install preflight-gpu
|
|
45
|
+
preflight check
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 요구 환경
|
|
51
|
+
|
|
52
|
+
| | |
|
|
53
|
+
|---|---|
|
|
54
|
+
| GPU | **NVIDIA GPU와 CUDA 드라이버** |
|
|
55
|
+
| OS | Linux, Windows (네이티브) |
|
|
56
|
+
| Python | 3.9 이상 |
|
|
57
|
+
|
|
58
|
+
**macOS(Apple Silicon)와 AMD ROCm은 아직 대상이 아닙니다.** 설치는 되지만 CUDA 경로만 보기 때문에 쓸 만한 답을 주지 못합니다. GPU가 아예 없는 환경에서도 "없다"는 것까지는 알려주지만, 그 뒤로 할 수 있는 일이 없습니다.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 왜 만들었나
|
|
63
|
+
|
|
64
|
+
QLoRA 같은 경량화 기법 덕분에 개인 GPU 한 장으로도 LLM 파인튜닝이 현실적인 선택지가 됐습니다. 하지만 드라이버·CUDA·PyTorch·학습 라이브러리로 이어지는 버전 호환성과 VRAM 산정은 여전히 까다롭고, 무엇보다 **에러 없이 조용히 실패하는 경우**가 흔합니다.
|
|
65
|
+
|
|
66
|
+
**조용한 실패.** GPU가 꽂혀 있어도 연산이 CPU로 도는 일이 있습니다. 느릴 뿐 에러가 없어서 몇 시간 뒤에야 눈치챕니다. `torch.cuda.is_available()`이 True이고 버전도 다 맞는데 조용히 CPU로 폴백돼 RAM만 먹던 사례를 직접 겪었습니다.
|
|
67
|
+
|
|
68
|
+
**VRAM 오판.** 학습에는 가중치·그래디언트·옵티마이저 상태·활성값이 동시에 올라갑니다. 앞의 셋은 모델 구조만 알면 계산되지만, 활성값과 그로 인한 allocator 단편화는 돌려봐야 압니다.
|
|
69
|
+
|
|
70
|
+
**버전 체인.** 드라이버·CUDA·PyTorch·학습 라이브러리가 서로 물려 있어 하나만 어긋나도 학습이 시작조차 안 됩니다. `pip check`가 통과시키는 조합에서도 그렇습니다.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 어떻게 확인하나
|
|
75
|
+
|
|
76
|
+
버전 문자열을 읽고 "괜찮아 보인다"고 말하지 않습니다. 실제로 4bit 레이어를 만들고 LoRA 어댑터를 붙여 **학습 스텝을 한 번 돌려봅니다.**
|
|
77
|
+
|
|
78
|
+
가중치나 데이터셋은 받지 않습니다. GPU 메모리 점유량은 텐서의 값이 아니라 shape와 dtype이 정하므로, 구조만 같은 랜덤 모델로도 같은 답이 나옵니다. 모델 config만 조회하고(수 KB) 같은 모양의 모델을 만들어 돌립니다.
|
|
79
|
+
|
|
80
|
+
canary는 **별도 프로세스에서** 돕니다. 진단이 필요한 상황이 곧 `import torch`가 죽는 상황이라, 같은 프로세스에서 돌리면 도구가 함께 죽어 아무것도 알려줄 수 없습니다.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
드라이버·CUDA → Python·학습 라이브러리 → [ Preflight ] → 실제 학습 실행
|
|
84
|
+
(OS 레벨) (환경 구성) (여기서 확인)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 무엇을 기준으로 재는가
|
|
88
|
+
|
|
89
|
+
이 진단은 **QLoRA(4bit 양자화 + LoRA 어댑터) + AdamW**를 기준으로 잽니다. 기본 체크는 batch=1 · seq=8 고정입니다.
|
|
90
|
+
|
|
91
|
+
그래서 4bit을 쓸 생각이 없는 사용자에게는 `⚠ 4bit 사용 불가` 같은 줄이 나올 수 있습니다. **bf16 + LoRA만 쓴다면 그 줄은 무시해도 됩니다.** 반대로 `device=cpu`나 VRAM 관련 판정은 어떤 학습 방식이든 그대로 해당됩니다.
|
|
92
|
+
|
|
93
|
+
기준을 바꾸는 옵션(`--quantization`, `--optimizer` 등)은 아직 없습니다. 다음 버전에서 다룰 예정입니다.
|
|
94
|
+
|
|
95
|
+
**진단하는 것은 환경입니다.** 드라이버·CUDA·torch 빌드·라이브러리 설치·VRAM까지가 대상이고, **학습 코드 자체의 오류나 라이브러리 API 변경은 잡지 않습니다.** preflight를 통과했는데 `TypeError: SFTConfig.__init__() got an unexpected keyword argument` 같은 것으로 죽는다면, 그건 환경이 아니라 코드 쪽 문제입니다.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 무엇을 하나
|
|
100
|
+
|
|
101
|
+
**확인 — Canary Check.** 작은 텐서 연산 하나로 device 배치, GPU 메모리 이동, CPU 대비 실행 속도를 함께 재서 조용한 실패를 잡습니다.
|
|
102
|
+
|
|
103
|
+
**확인 — VRAM 실측.** `--model`을 주면 가중치 다운로드 없이 그 모델 구조로 canary를 구성해 메모리 사용량을 잽니다. 계산이 아니라 실측이라 활성값과 단편화가 반영됩니다.
|
|
104
|
+
|
|
105
|
+
**수정 + 재확인.** 문제에 맞는 명령을 제시하고, `--yes`를 주면 실행한 뒤 다시 재서 실제로 고쳐졌는지 보여줍니다.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 설치
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
pip install preflight-gpu
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
설치되는 의존성은 `typer`·`rich`·`nvidia-ml-py` 셋뿐입니다. **torch·transformers·bitsandbytes는 일부러 넣지 않았습니다.** 사용자 환경에 이미 깔린 버전을 그대로 불러와 검증하는 것이 목적이라, 진단 도구의 설치가 진단 대상을 덮어쓰면 안 되기 때문입니다.
|
|
116
|
+
|
|
117
|
+
설치 후 `preflight` 명령을 못 찾으면(venv를 활성화하지 않았거나 `--user`로 설치한 경우 Windows에서 흔합니다) 모듈로 실행하면 됩니다.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
python -m preflight check
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> ⚠️ **패키지 이름은 `preflight-gpu`입니다. 실행 명령만 `preflight`입니다.**
|
|
124
|
+
>
|
|
125
|
+
> PyPI의 `preflight`는 **무관한 다른 패키지**(웹사이트 배포 점검 도구, BSD)입니다. `pip install preflight`는 **에러 없이 성공하고** 그 도구가 깔립니다 — 그쪽도 `preflight`라는 콘솔 명령을 설치하기 때문에, 같은 환경에 둘 다 있으면 나중에 설치한 쪽이 명령을 덮어씁니다.
|
|
126
|
+
>
|
|
127
|
+
> 헷갈릴 때는 [pypi.org/project/preflight-gpu](https://pypi.org/project/preflight-gpu/)에서 확인하세요.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 언제 쓰나요 — 학습 명령어 실행 직전
|
|
132
|
+
|
|
133
|
+
`git commit` 전에 `git status`를 확인하듯, 학습을 시작하기 전 한 줄이면 됩니다. 설치 직후, 드라이버 업데이트 직후, 팀 온보딩이나 CI 스크립트에 넣어두기를 권합니다.
|
|
134
|
+
|
|
135
|
+
아래는 RTX 4070 Ti에서 실제로 나온 출력입니다.
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
$ preflight check
|
|
139
|
+
|
|
140
|
+
✔ Canary 연산 실행 device=cuda · 메모리 이동 확인됨
|
|
141
|
+
✔ 실행 시간 2ms CPU 대비 5배 (정상 범위)
|
|
142
|
+
ℹ 4bit 레이어 폴백 nn.Linear로 대체 실행됨
|
|
143
|
+
⚠ 4bit 사용 불가 bitsandbytes가 없어 QLoRA(4bit)로 학습할 수 없습니다
|
|
144
|
+
이 상태로 QLoRA 학습을 시작하면 ImportError로 즉시 종료됩니다.
|
|
145
|
+
|
|
146
|
+
FIX: C:\venv\Scripts\python.exe -m pip install -U bitsandbytes>=0.46.1
|
|
147
|
+
재확인: preflight check --yes
|
|
148
|
+
|
|
149
|
+
4개 항목 확인 · 1개 문제 발견 · 소요 시간 4초
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
제안하는 명령은 PATH에서 찾은 `pip`이 아니라 **지금 preflight를 실행 중인 파이썬**을 가리킵니다. venv를 활성화하지 않았거나 pipx·전역 설치로 쓰는 환경에서, 진단한 곳과 다른 환경에 설치되는 것을 막기 위해서입니다.
|
|
153
|
+
|
|
154
|
+
`--model`을 주면 그 모델을 이 GPU에 올렸을 때가 나옵니다.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
$ preflight check --model google/gemma-4-12B-it
|
|
158
|
+
|
|
159
|
+
모델 체크: google/gemma-4-12B-it
|
|
160
|
+
✔ Canary 연산 실행 device=cuda · 메모리 이동 확인됨
|
|
161
|
+
✔ VRAM 실측 9.7GB / 4.6GB 가용 (총 12GB)
|
|
162
|
+
✔ 목표 배치 크기 적합 batch=1, seq=8 기준
|
|
163
|
+
⚠ VRAM 여유 가용 VRAM의 208% 소모 — 실제 학습 시 OOM 위험 높음
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### CI 연동과 종료 코드
|
|
167
|
+
|
|
168
|
+
CI 파이프라인이나 스크립트에서 결과를 분기할 수 있도록 종료 코드를 규격화했습니다. `--yes`로 재확인까지 했다면 재확인 결과로 교체된 전체를 다시 집계한 값이 기준입니다. 실행할 명령이 없었거나 명령이 실패해 재확인을 못 했으면 1차 판정이 기준입니다.
|
|
169
|
+
|
|
170
|
+
| 종료 코드 | 판정 | 설명 |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `0` | **PASS** | 모든 판정 항목이 정상 |
|
|
173
|
+
| `1` | **FAIL** | 최종 판정에 FAIL이 하나라도 포함된 경우 |
|
|
174
|
+
| `2` | **WARN** | FAIL 없이 WARN만 포함된 경우 |
|
|
175
|
+
|
|
176
|
+
`--json`을 주면 같은 내용이 구조화된 형태로 나옵니다.
|
|
177
|
+
|
|
178
|
+
> **주의** — GitHub Actions·Jenkins 등 대부분의 CI 도구는 `0`이 아닌 모든 종료 코드를 파이프라인 실패로 봅니다. WARN(`2`)에서 중단하지 않으려면 셸에서 분기해야 합니다.
|
|
179
|
+
> ```bash
|
|
180
|
+
> preflight check || [ $? -eq 2 ]
|
|
181
|
+
> ```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 누가 쓰면 좋은가
|
|
186
|
+
|
|
187
|
+
파인튜닝 도구 설치부터 막혀 학습 화면조차 못 본 사람, "문제가 있을 수도 있다"가 아니라 확정된 답이 필요한 사람, 드라이버를 갱신하거나 GPU를 바꾼 뒤 매번 다시 확인하고 싶은 사람입니다.
|
|
188
|
+
|
|
189
|
+
학습 도구를 무엇으로 고르든 그 앞단은 똑같이 드라이버·CUDA·PyTorch입니다. 그 공통 구간을 먼저 확인해서, 몇 시간짜리 학습이 환경 문제로 도중에 죽는 일을 줄이는 것이 목표입니다.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 프로젝트 정보
|
|
194
|
+
|
|
195
|
+
| | |
|
|
196
|
+
|---|---|
|
|
197
|
+
| 라이선스 | MIT — 재배포·임베딩 제약 없음 |
|
|
198
|
+
| 지원 플랫폼 | Linux, Windows (네이티브) · NVIDIA GPU 필요 |
|
|
199
|
+
| Python | 3.9 이상 |
|
|
200
|
+
| 설치 | `pip install preflight-gpu` (실행 명령은 `preflight`) |
|
|
201
|
+
| PyPI | [pypi.org/project/preflight-gpu](https://pypi.org/project/preflight-gpu/) |
|
|
202
|
+
| 실행 | `preflight check` — 명령을 못 찾으면 `python -m preflight check` |
|
|
203
|
+
| 종료 코드 | 0 = PASS · 1 = FAIL · 2 = WARN |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
*기여, 이슈 제보, 피드백을 환영합니다. 자세한 설계 배경은 저장소의 `docs/`를 참고해 주세요.*
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# ADR-0001: VRAM을 정적 계산이 아닌 실측으로 확인한다
|
|
2
|
+
|
|
3
|
+
**상태**: Accepted (2026-07-30)
|
|
4
|
+
|
|
5
|
+
## 맥락
|
|
6
|
+
|
|
7
|
+
학습 시 VRAM은 가중치·그래디언트·옵티마이저 상태·활성값(activation) 네 가지를 동시에 요구한다. 앞의 셋은 모델 구조와 옵티마이저 종류만 알면 계산으로 정확히 구할 수 있다. 하지만 활성값은 attention 구현 방식(naive/flash-attention 등) 같은 실행 시점의 코드 경로에 따라 달라지고, 여기에 더해 allocator fragmentation까지 겹쳐 실제로 돌려보지 않고는 예측할 수 없다.
|
|
8
|
+
|
|
9
|
+
## 결정
|
|
10
|
+
|
|
11
|
+
VRAM 사용량을 공식으로 계산하지 않고, 목표 모델과 동일한 구조의 랜덤 초기화 모델(config만 조회, 가중치 다운로드 없음)로 canary를 구성해 실제로 forward+backward+`optimizer.step()`을 실행하고 그 결과로 나온 메모리 델타를 그대로 사용한다.
|
|
12
|
+
|
|
13
|
+
## 결과
|
|
14
|
+
|
|
15
|
+
- 활성값과 fragmentation까지 포함된 신뢰도 높은 실측값을 얻는다.
|
|
16
|
+
- 계산 대비 실행 비용(수 초~수십 초, 모델 크기에 비례)이 발생한다.
|
|
17
|
+
- 실측 도중 발생하는 OOM은 진단 대상 자체이지 도구의 실패가 아니다 — 이 크래시를 안전하게 잡아내는 문제는 [ADR-0002](0002-subprocess-isolation-for-canary.md)에서 다룬다.
|
|
18
|
+
- MVP는 목표 크기 그대로 1세트만 실측한다. 여러 지점을 측정해 외삽하는 정밀화는 향후 확장(`architecture.md` §7)으로 미뤘다.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ADR-0002: Canary는 반드시 subprocess로 격리 실행한다
|
|
2
|
+
|
|
3
|
+
**상태**: Accepted (2026-07-30)
|
|
4
|
+
|
|
5
|
+
## 맥락
|
|
6
|
+
|
|
7
|
+
Canary 실행 중 프로세스가 죽을 수 있는 경로가 두 가지 있다.
|
|
8
|
+
|
|
9
|
+
- **Import 크래시** — `import torch`, `import bitsandbytes` 자체가 CUDA 라이브러리 꼬임으로 `.so` 로드 실패를 일으키면 그 프로세스가 그대로 죽을 수 있다.
|
|
10
|
+
- **OOM 크래시** — `--model` 목표 크기 실행 시 VRAM이 실제로 부족하면 `RuntimeError: CUDA out of memory`로 프로세스가 강제 종료된다.
|
|
11
|
+
|
|
12
|
+
Preflight는 진단 도구이므로, 진단 대상의 실패(OOM 등)가 진단 도구 자체를 죽여서 "결과 없음"으로 끝나서는 안 된다. 이런 크래시 자체가 유효한 진단 결과(FAIL)여야 한다.
|
|
13
|
+
|
|
14
|
+
## 결정
|
|
15
|
+
|
|
16
|
+
Canary는 항상 메인 CLI 프로세스가 아니라 별도 `subprocess`(또는 `multiprocessing`)에서 격리 실행한다. 부모 프로세스는 자식의 비정상 종료(exit code, OOM 포함)를 캐치해 정상적인 진단 결과(FAIL)로 포장한다. 원인 분류는 텍스트 파싱이 아니라 `torch.version.cuda`·`bitsandbytes.cextension.lib.compiled_with_cuda`·`sys.version_info` 같은 안정된 파이썬 속성으로 한다.
|
|
17
|
+
|
|
18
|
+
## 결과
|
|
19
|
+
|
|
20
|
+
- 어떤 크래시가 나도 CLI 자체는 항상 유효한 PASS/WARN/FAIL 결과를 반환한다.
|
|
21
|
+
- 프로세스 생성 오버헤드(수백 ms 수준)가 매 실행마다 발생하지만, 기본 체크 기준 이미 torch/bitsandbytes import 자체가 3~10초 걸리는 것에 비하면 무시할 수준이다.
|
|
22
|
+
- 이 격리 경계는 `run_canary_check()` 함수 안에 캡슐화된다 — 계약은 [contracts/canary-api.md](../contracts/canary-api.md) 참고.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# ADR-0003: 실행시간 판정은 절대 임계값이 아닌 CPU 대비 상대 배수로 한다
|
|
2
|
+
|
|
3
|
+
**상태**: Accepted (2026-07-30)
|
|
4
|
+
|
|
5
|
+
## 맥락
|
|
6
|
+
|
|
7
|
+
"조용한 실패"(GPU가 인식되지만 실제로는 CPU로 연산이 도는 경우)를 잡아내려면 실행시간이 비정상적으로 느린지 판정해야 한다. 그런데 GPU 세대(T4, A10G, L4, A100, H100 등)마다 동일 연산의 물리적 소요 시간이 크게 달라, "50ms 이상이면 느림" 같은 고정된 절대 임계값은 세대마다 오탐/미탐을 낸다.
|
|
8
|
+
|
|
9
|
+
## 결정
|
|
10
|
+
|
|
11
|
+
런타임에 CPU 강제 폴백으로 동일 연산을 1회 측정해 baseline으로 삼고, GPU 실행이 그 대비 몇 배 빠른가로 판정한다(`cpu_multiplier < 2`면 WARN). 절대 시간이 아니라 상대 배수만 본다.
|
|
12
|
+
|
|
13
|
+
## 결과
|
|
14
|
+
|
|
15
|
+
- GPU 세대와 무관하게 이식성 있는 판정 기준을 확보한다.
|
|
16
|
+
- CPU 강제 폴백 연산 자체가 추가 실행 비용(기본 체크 소요 시간에 포함)이다.
|
|
17
|
+
- CPU 강제 폴백이 bitsandbytes 4bit과 동일한 양자화 경로(진짜 dequant 연산)로 동작함이 실측으로 확인됐다 — 배수 비교 자체는 유효하다.
|
|
18
|
+
- 알려진 한계: canary 크기가 작을수록 배수가 실제보다 작게 나오는 문제가 실측으로 확인됐다. 지금 "기본 체크" 크기(batch=1, seq=8)는 device 확인용 PASS/FAIL 판정에는 문제없지만, WARN(배수 기반) 판정의 신뢰도는 낮을 수 있다 — `architecture.md` §7의 probe 기반 정밀화로 향후 개선 예정이다. (이 한계가 실제 오탐으로 확인돼 MVP 기본 체크에서 어떻게 대응했는지는 [ADR-0004](0004-canary-size-scaled-by-hidden.md) 참고. 이 ADR의 결정 자체는 그대로다.)
|
|
19
|
+
- WARN을 별도 등급으로 두는 것 자체와 두 조건(메모리 델타 15%·배수 2배 미만)은 팀 결정 완료. 두 숫자는 매직넘버로 우선 채택한 것이라 실측 데이터가 쌓이면 조정될 수 있다 — `docs/contracts/canary-api.md` 참고.
|
|
20
|
+
- `--model` 목표 크기 실행에서는 이 비교를 하지 않는다 — device placement만 직접 조회한다(불필요한 추가 실행 비용을 피하기 위함).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ADR-0004: 기본 체크 canary의 크기는 `hidden`으로 키운다
|
|
2
|
+
|
|
3
|
+
**상태**: Accepted (2026-08-01) · **관련 Issue**: #1 (W2, FR-01)
|
|
4
|
+
|
|
5
|
+
## 맥락
|
|
6
|
+
|
|
7
|
+
[ADR-0003](0003-relative-baseline-timing.md)에 따라 실행시간 판정은 CPU 강제 폴백 대비 상대 배수로 하고, `cpu_multiplier < 2`면 WARN이다. 그리고 같은 ADR이 "canary 크기가 작을수록 배수가 실제보다 작게 나온다"를 알려진 한계로 이미 적어두고 있었다.
|
|
8
|
+
|
|
9
|
+
W2 구현 중 이 한계가 **경고 수준이 아니라 실제 오탐**임이 확인됐다. `hidden=1024`로 구성한 기본 체크를 아무 문제 없는 RTX 4070 Ti에서 돌렸을 때 배수가 **1.83~1.96배**로 나와 WARN 임계값(2배)을 밑돌았다. 즉 정상 환경이 매번 WARN을 받고, WARN은 0이 아닌 종료 코드로 이어지므로(FR-07) CI 연동까지 함께 깨진다.
|
|
10
|
+
|
|
11
|
+
원인은 canary가 너무 작아서 **실제 연산이 아니라 커널 실행 고정 비용이 시간을 지배**하는 데 있다. GPU와 CPU 양쪽 모두 오버헤드가 지배적이면 두 값이 비슷해지고 배수는 1에 수렴한다.
|
|
12
|
+
|
|
13
|
+
## 결정
|
|
14
|
+
|
|
15
|
+
기본 체크 canary의 **`hidden` 크기를 4096으로 잡는다**(`canary/model.py`의 `MINIMAL_HIDDEN_SIZE`). `batch=1`·`seq=8`·블록 2개는 그대로 둔다.
|
|
16
|
+
|
|
17
|
+
## 대안
|
|
18
|
+
|
|
19
|
+
크기를 키울 수 있는 손잡이 네 개를 각각 4배로 늘려 같은 조건에서 비교했다.
|
|
20
|
+
|
|
21
|
+
> **측정 환경 (2026-08-01)** — 배수는 GPU와 CPU 성능의 비율이므로 양쪽을 함께 남긴다.
|
|
22
|
+
> GPU: RTX 4070 Ti (12GB) · driver 610.62 · CUDA 12.8 / CPU: Intel Core i5-13600K (14C/20T, base 3.5GHz) / RAM 32GB
|
|
23
|
+
> 스택: torch 2.11.0+cu128 · bitsandbytes 0.50.0 · Python 3.13
|
|
24
|
+
|
|
25
|
+
| 무엇을 4배로 | 배수 | GPU 시간 | 메모리 |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| (기준) hidden 1024 · 블록 2 · batch 1 · seq 8 | 1.96배 | 1.95ms | 23.8MB |
|
|
28
|
+
| **hidden** 1024 → 4096 | **22.62배** | **1.95ms** (변화 없음) | 131.7MB |
|
|
29
|
+
| 블록 수 2 → 8 | 2.42배 | 6.38ms (3배 증가) | 28.4MB |
|
|
30
|
+
| seq 8 → 32 | 2.20배 | 1.98ms | 28.1MB |
|
|
31
|
+
| batch 1 → 4 | 2.05배 | 2.47ms | 29.1MB |
|
|
32
|
+
|
|
33
|
+
- **`hidden`을 키운다 (채택)** — 행렬곱 연산량이 `hidden`의 제곱으로 늘어나는 반면 커널 실행 횟수는 그대로다. 그래서 오버헤드 대비 연산 비중만 급격히 올라가고 GPU 시간은 사실상 변하지 않는다.
|
|
34
|
+
- **블록(레이어) 수를 늘린다 (기각)** — 연산량이 선형으로만 늘고 커널 실행 횟수도 같이 늘어 오버헤드가 함께 증가한다. 실측에서 배수는 2.42배에 그쳤는데 GPU 시간은 3배 느려졌다 — 사용자 대기 시간만 늘고 문제는 안 풀린다.
|
|
35
|
+
- **`batch`·`seq`를 늘린다 (기각)** — 효과가 가장 작을 뿐 아니라(2.05~2.20배), 두 값은 [architecture.md](../architecture.md) §3이 "batch=1, seq=8 수준"으로 고정했고 Issue #1의 완료 조건에도 명시돼 있다. 바꾸려면 문서와 이슈 완료 조건 변경이 필요하다. 반면 `hidden`은 어느 문서에도 값이 정해져 있지 않은 자유 파라미터다.
|
|
36
|
+
- **WARN 임계값 2배를 낮춘다 (기각)** — 임계값은 판정(`judge_result`) 소관이라 담당이 다르고(WORKPLAN W5), 무엇보다 임계값을 낮추면 진짜 조용한 실패까지 함께 통과시키게 된다. 신호가 약한 것이 원인인데 신호 기준을 낮추는 것은 원인 해결이 아니다.
|
|
37
|
+
|
|
38
|
+
## 결과
|
|
39
|
+
|
|
40
|
+
- 정상 환경에서 배수가 안정적으로 **17~19배**로 나온다 — WARN 임계값과 충분한 거리가 확보돼 오탐이 사라진다.
|
|
41
|
+
- 메모리 사용이 23.8MB → 131.7MB로 늘지만, 기본 체크는 "OOM 위험 사실상 없음"(architecture.md §3)이라는 성격을 그대로 유지한다. 12GB급 개인 GPU 기준 1% 수준이다.
|
|
42
|
+
- GPU 실행 시간은 변하지 않으므로(1.95ms) 체감 소요 시간에 영향이 없다. 기본 체크의 소요 시간은 여전히 torch/bitsandbytes import 오버헤드(3~10초, §6-03)가 지배한다.
|
|
43
|
+
- `hidden=4096`은 8B급 모델의 실제 hidden 크기와 같아, "최소 대표 구조"라는 기본 체크의 성격과도 어긋나지 않는다.
|
|
44
|
+
- 이 값은 특정 GPU 한 대(RTX 4070 Ti)의 실측에 기반한다. 훨씬 빠른 GPU(H100 등)나 훨씬 느린 CPU에서는 배수가 달라질 수 있으므로, 다른 환경의 실측 데이터가 모이면 재검토한다.
|
|
45
|
+
- 배수 신호를 근본적으로 키우는 일은 여전히 [architecture.md](../architecture.md) §7의 probe 기반 정밀화(FR-08) 몫이다. 이 결정은 MVP 기본 체크의 오탐만 제거한다.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# ADR-0005: CI/CD 연동을 위한 CLI 종료 코드(Exit Code)를 0(PASS), 1(FAIL), 2(WARN)로 분리한다
|
|
2
|
+
|
|
3
|
+
**상태**: Proposed (2026-08-03)
|
|
4
|
+
**연관 이슈**: #8
|
|
5
|
+
|
|
6
|
+
## 맥락
|
|
7
|
+
|
|
8
|
+
Preflight는 터미널 사용자의 수동 확인뿐 아니라 CI/CD 파이프라인이나 온보딩 자동화 스크립트에서 프로그램적으로 진단 결과를 판별할 수 있어야 한다. 이때 모든 실패를 단일 종료 코드(1)로 반환할 것인지, 판정 단계(PASS/WARN/FAIL)에 따라 차등화된 종료 코드를 반환할 것인지를 결정해야 한다.
|
|
9
|
+
|
|
10
|
+
- `FAIL`: 모듈 로딩 실패, OOM 등 즉시 해결해야 학습을 실행할 수 있는 치명적 오류
|
|
11
|
+
- `WARN`: 메모리 델타 편차, CPU 대비 속도 2배 미만 등 실행은 가능하나 주의가 필요한 상태
|
|
12
|
+
- `PASS`: 모든 진단 정상
|
|
13
|
+
|
|
14
|
+
## 결정
|
|
15
|
+
|
|
16
|
+
CLI 명령어(`preflight check`) 종료 시 판정(`verdict`)에 따라 아래와 같이 3단계의 규격화된 종료 코드(Exit Code)를 반환한다.
|
|
17
|
+
- `0`: 모든 판정 항목이 **PASS**인 경우
|
|
18
|
+
- `1`: 최종 판정에 **FAIL**이 하나라도 포함된 경우
|
|
19
|
+
- `2`: FAIL 없이 **WARN**만 포함된 경우
|
|
20
|
+
- `--yes` 옵션으로 수정 후 재확인(`ReVerifier`) 수행 시에는 1차 판정이 아닌 재확인의 최종 판정 결과를 기준으로 반환한다.
|
|
21
|
+
|
|
22
|
+
## 대안 비교
|
|
23
|
+
|
|
24
|
+
- **대안 A: PASS(0) / 그 외 모든 문제(1)**
|
|
25
|
+
- 장점: 일반적인 UNIX 명령어 방식과 유사하며 구현이 단순함.
|
|
26
|
+
- 단점: 치명적인 에러(FAIL)와 단순 성능 경고(WARN)를 스크립트나 CI 파이프라인에서 종료 코드만으로 구별할 수 없음.
|
|
27
|
+
- **대안 B: PASS(0) / FAIL(1) / WARN(2) (채택)**
|
|
28
|
+
- 장점: 자동화 파이프라인에서 치명적 오류(`1`) 시 빌드·학습을 중단하고, 경고(`2`) 시에는 로그를 남기고 작업을 속행하는 등 유연한 제어가 가능함.
|
|
29
|
+
- 단점: 기본적으로 많은 CI 도구(GitHub Actions, Jenkins 등)는 0 이외의 종료 코드를 Error로 인식하므로, WARN을 속행하려면 스크립트에서 예외 처리(`preflight check || [ $? -eq 2 ]`)가 필요함.
|
|
30
|
+
|
|
31
|
+
## 결과
|
|
32
|
+
|
|
33
|
+
- CI/CD 파이프라인 및 자동화 스크립트가 Preflight의 판정 결과(FAIL vs WARN)를 명확히 구별하여 대응할 수 있다.
|
|
34
|
+
- 외부 CI 도구에서 WARN(`2`)이 빌드 실패로 취급될 수 있는 위험성을 `readme.md`와 `docs/contracts/cli.md`에 명시적으로 안내한다.
|