Codex 개발시 불편한 것

Local LLM 시리즈

  1. (Local LLM) Ollama 시작하기
  2. Codex 와 Local LLM 연결하기
  3. Codex 로 개발시 불편한 것

프로젝트에 여러 폴더 포함하기

결론부터 말씀드리면, Lib_StockMart 폴더를 굳이 Stock-Agent 아래로 이동시키지 않아도 외부 폴더 상태 그대로 Codex CLI에 주입할 수 있는 방법이 있습니다.

현재 /home/qkboo/Projects/Lib_StockMart의 소스코드를 볼 수 없는 이유는, Codex CLI가 실행될 때 기본적으로 현재 터미널이 위치한 디렉터리(Stock-Agent)와 그 하위 폴더만 자동으로 컨텍스트에 스캔(인덱싱)하도록 설계되어 있기 때문입니다. 프로필에 등록하는 행위는 ‘이 경로를 신뢰한다’는 허가 선언일 뿐, 실행 시 자동으로 멀리 떨어진 다른 폴더를 끌고 오지는 않습니다.

이 구조적 한계를 깨고 독립된 두 폴더를 유기적으로 탐색하게 만드는 2가지 확실한 해결책을 제시해 드립니다.


리눅스(WSL)의 심볼릭 링크 기능을 사용하면, Lib_StockMart 폴더를 원래 위치에 그대로 둔 채 Stock-Agent 내부에 바로가기(폴더 링크)를 심을 수 있습니다. 이렇게 하면 Codex는 해당 링크를 일반 하위 디렉터리로 인식하여 완벽하게 파일을 읽고 @ 검색 대상에 포함시킵니다.

터미널을 열고 다음 명령어를 실행하세요:

1
2
3
4
5
6
# Stock-Agent 디렉터리로 이동
cd /home/qkboo/Projects/Stock-Agent

# 원래 위치에 있는 Lib_StockMart를 현재 폴더에 'lib'라는 이름의 링크로 연결
ln -s /home/qkboo/Projects/Lib_StockMart ./lib

이렇게 세팅하면 구조는 아래와 같이 유지되며, Lib_StockMart 내부 코드를 수정하면 양쪽 모두 실시간으로 반영됩니다:

  • /home/qkboo/Projects/Stock-Agent/lib ➡️ (실제로는 Lib_StockMart를 가리킴)

💡 중요 조치 사항: 심볼릭 링크를 생성한 후, Codex 대화창에서 /reload 명령어를 입력하거나 터미널에서 **codex --profile goyangi_coder --refresh**를 실행해 새롭게 연결된 외부 라이브러리 폴더의 인덱싱을 강제로 갱신해 주어야 @ 검색에 나타나기 시작합니다.


해결책 2: 실행 시점에 다중 파일/디렉터리 명시적 주입

만약 폴더 내부에 바로가기를 심는 구조가 마음에 들지 않는다면, Codex CLI를 실행하는 스크립트 단에서 두 디렉터리를 강제로 병합하여 인덱싱하도록 명령하는 방법이 있습니다.

현재 사용 중이신 단축어(alias cx="...") 설정을 아래와 같이 변경해 보세요.

1
2
3
# 임시 인덱싱 경로로 외부 라이브러리 폴더를 추가 주입하는 단축어 예시
alias cx="codex --profile goyangi_coder --file /home/qkboo/Projects/Lib_StockMart"

  • 동작 원리: 이렇게 실행하면 현재 위치한 Stock-Agent 소스 코드뿐만 아니라, --file 옵션으로 전달된 공통 라이브러리(Lib_StockMart) 내부의 소스 트리까지 통째로 로컬 모델(gemma-4)의 기억(Context) 속에 밀어 넣어주게 됩니다.

🧐 요약 및 퀀트 개발 환경 제언

가장 깔끔하고 현업 표준에 맞는 방식은 [해결책 1 (심볼릭 링크)]입니다. 주식 데이터 분석 엔진(Stock-Agent)을 개발하다가 공통 마켓 데이터 라이브러리(Lib_StockMart)의 내부 클래스나 유틸리티 함수를 호출해야 할 때, 코드를 물리적으로 복사할 필요 없이 @lib/ 형식으로 참조하여 곧바로 퀀트 로직 최적화 팁을 얻을 수 있습니다.

네, 사용할 수 있습니다.

llama-server가 라우터 모드(Router Mode, --models-dir 등을 사용하는 모드)로 동작할 때, 클라이언트가 복잡한 .gguf 전체 파일명 대신 깔끔한 단어(Alias)로 모델을 지정하게 만드는 방법은 크게 두 가지가 있습니다.

현재 llama-server가 업데이트되면서 가장 권장되는 정석적인 방법은 INI 설정 파일을 사용하는 것입니다.


1. 정석: --models-preset (INI 설정 파일) 사용하기

가장 깔끔하고 확실한 방법입니다. 라우터 모드를 켤 때 --models-dir 대신 --models-preset 플래그를 사용하여 각 GGUF 파일에 원하는 ID(Alias)를 매핑하는 models.ini 파일을 지정하는 방식입니다.

models.ini 파일 작성

원하는 경로에 설정 파일을 만들고, 대괄호 [...] 안에 클라이언트에서 사용할 Alias(ID)를 적어준 뒤 내부에서 실제 model 경로를 지정합니다.

1
2
3
4
5
6
7
8
9
10
[gemma-4]
model = /home/user/models/gemma-4-26b-it-q4_k_m.gguf
n_gpu_layers = 999
n_ctx = 8192

[qwen-coder]
model = /home/user/models/qwen3-coder-7b-instruct-q8_0.gguf
n_gpu_layers = 999
n_ctx = 16384

llama-server 실행

1
2
./llama-server --models-preset ./models.ini --port 8080

③ 클라이언트 호출

이제 OpenAI 호환 API나 클라이언트(파이썬, curl, Open WebUI 등)에서 모델 이름으로 복잡한 파일명 대신 지정한 Alias를 바로 넘겨주면 됩니다.

1
2
3
4
5
6
7
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-4",
"messages": [{"role": "user", "content": "안녕?"}]
}'


만약 별도의 INI 설정 파일을 관리하기 귀찮고, --models-dir을 통해 특정 디렉토리 안의 GGUF 파일들을 자동으로 탐색(Auto-discovery)하는 방식을 고수하고 싶다면 리눅스 심볼릭 링크를 사용하는 것이 가장 원시적이면서도 효과적입니다.

llama-server--models-dir 내부에 있는 파일명을 그대로 model ID로 인식하므로, 파일명을 alias 구조로 링크해 두면 됩니다.

① 심볼릭 링크 생성

1
2
3
4
5
6
# 실제 복잡한 파일이 있는 곳: /path/to/models/
# llama-server가 바라보는 곳: /path/to/router_dir/

ln -s /path/to/models/gemma-4-26b-it-q4_k_m.gguf /path/to/router_dir/gemma-4.gguf
ln -s /path/to/models/qwen3-coder-7b-instruct-q8_0.gguf /path/to/router_dir/qwen-coder.gguf

llama-server 실행

1
2
./llama-server --models-dir /path/to/router_dir/ --port 8080

③ 클라이언트 호출

이 경우 확장자를 포함한 gemma-4.gguf 또는 gemma-4 형식으로 호출이 가능해집니다.


요약 및 추천

  • 추천: --models-preset (INI 방식)을 사용하는 것이 좋습니다. 모델별로 상이한 컨텍스트 크기(--ctx), VRAM 할당량(--n-gpu_layers) 같은 하드웨어 가속 옵션을 개별적으로 튜닝해서 지정할 수 있기 때문입니다.
  • 단일 모델 전용으로 띄울 때 쓰던 개별 플래그인 --alias--models-dir 기반의 멀티 모델 라우터 모드에서는 전역 적용이 모호해지므로, 여러 모델을 스왑할 때는 위의 INI 형식을 쓰는 것이 현재 llama.cpp 생태계의 표준입니다.

llama-server에서 여러 모델을 동적으로 스위칭하며 서비스할 수 있는 라우터 모드(Router Mode) 기능인 --models-preset 설정 파일(일반적으로 .ini 또는 .toml 형식으로 파싱 가능)에 사용할 수 있는 주요 파라미터 구성입니다.

각 섹션 이름([model_id])은 클라이언트가 API 요청 시 model 필드에 지정할 고유 ID가 됩니다.


1. 모델 구성 및 메모리 맵핑 (Model & Memory)

파라미터명 설명
model (또는 m) 로컬 시스템에 저장된 GGUF 모델 파일의 절대 경로를 지정합니다.
hf Hugging Face 저장소에서 모델을 직접 지정할 때 사용합니다. (예: ggml-org/Qwen2.5-7B-Instruct-GGUF)
mmap Memory-map 사용 여부 (1 또는 0). 활성화 시 모델 로드가 빨라지지만 OS 페이지 아웃이 발생할 수 있습니다.
mlock 모델을 RAM에 강제로 잠금 처리하여 스왑(Swap) 영역으로 빠지는 것을 방지합니다. (1 or 0)

2. 하드웨어 가속 및 연산 제어 (Hardware Allocation)

파라미터명 설명
n-gpu-layers (또는 ngl) GPU로 오프로드할 레이어 수입니다. VRAM 용량에 맞춰 조절하며, 전체 레이어를 올리려면 대개 99 같은 큰 값을 지정합니다.
main-gpu (또는 mg) 멀티 GPU 환경에서 기본 가속 및 중간 연산 결과(KV)를 처리할 메인 GPU 인덱스(0, 1 등)를 지정합니다.
split-mode (또는 sm) 멀티 GPU 분할 방식을 정의합니다. (none, layer, row, tensor)
threads (또는 t) GPU에 올라가지 않고 CPU 영역에 남은 레이어를 연산할 때 사용할 쓰레드 수입니다.
threads-batch (또는 tb) 프롬프트 배치 가공 시 사용할 CPU 쓰레드 수입니다. (일반적으로 threads와 동일하게 지정)

3. 컨텍스트 및 배치 관리 (Context & Batching)

파라미터명 설명
ctx-size (또는 c) 프롬프트 및 컨텍스트 윈도우 크기를 토큰 단위로 지정합니다. (기본값 0 시 모델 메타데이터값 추종)
batch-size (또는 b) 텍스트 처리를 위한 논리적 최대 배치 크기입니다. (기본값 2048)
ubatch-size (또는 ub) 물리적 최대 배치 크기로, 청크 단위 연산 크기를 제어합니다. (기본값 512)
flash-attn (또는 fa) Flash Attention 활성화 여부입니다. 지원하는 아키텍처에서 연산 속도와 VRAM 절약에 유용합니다.

4. 추론 및 샘플링 파라미터 (Sampling Parameters)

⚠️ 주의: llama.cpp 버전 및 클라이언트 프레임워크 연동에 따라 일부 샘플링 파라미터는 프리셋 파일에 직접 선언하기보다 클라이언트 요청(API Payload) 단에서 동적으로 전달하는 것이 구동 안정성에 더 유리할 수 있습니다.

파라미터명 설명
temp 생성 텍스트의 다양성(확률 분포 평탄화)을 조절하는 온도 값입니다.
top-p 누적 확률 분포 상위 P 범위 내의 토큰들만 후보군으로 압축합니다.
top-k 확률이 가장 높은 상위 K개의 토큰으로 후보군을 제한합니다.
min-p 가장 유력한 토큰 확률 대비 최소 임계치 비율 이하인 토큰을 배제합니다.
predict (또는 n) 한 번의 요청으로 모델이 생성할 최대 토큰 수를 지정합니다. (-1 지정 시 무한대 생성)

💡 구성 예시 (Configuration Example)

동일한 장비(VRAM) 안에서 가볍게 서빙할 작은 모델과 대형 모델의 리소스를 차등 분배하여 작성한 프리셋 예시입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 전역 설정 필드 (필요시 최상단 배치)
version = 1

[qwen-7b-ins]
model = "/home/user/models/Qwen2.5-7B-Instruct-Q8_0.gguf"
ctx-size = 16384
n-gpu-layers = 99
flash-attn = 1
temp = 0.7
top-p = 0.95

[gemma-27b-heavy]
model = "/home/user/models/gemma-2-27b-it-Q4_K_M.gguf"
ctx-size = 8192
n-gpu-layers = 32 # 3080 Ti / 4060 등 VRAM 제약 시 일부만 GPU 이관
threads = 8 # CPU로 남은 레이어 연산을 위한 스레드 할당
mlock = 1 # 시스템 스왑 방지하여 레이턴시 저하 방어
temp = 0.3
min-p = 0.05


서버 시작

1
2
3
4
5
6
7
8
9
10
11
12
$ llama-server --models-preset llamaserver_preset.toml --port 8001 --host 0.0.0.0
0.00.110.622 I log_info: verbosity = 3 (adjust with the `-lv N` CLI arg)
0.00.110.649 I system_info: n_threads = 8 (n_threads_batch = 8) / 16 | CUDA : ARCHS = 860 | USE_GRAPHS = 1 | PEER_MAX_BATCH_SIZE = 128 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 |
0.00.110.650 I srv main: n_parallel is set to auto, using n_parallel = 4 and kv_unified = true
0.00.110.658 I srv init: running without SSL
0.00.110.676 I srv init: using 15 threads for HTTP server
0.00.111.579 I srv load_models: Loaded 0 cached model presets
0.00.111.743 I srv load_models: Loaded 2 custom model presets from llamaserver_preset.toml
0.00.111.769 I srv operator(): Available models (2) (*: custom preset)
0.00.111.769 I srv operator(): * g4coder
0.00.111.770 I srv operator(): * gemma4-26b
0.00.111.836 I srv main: starting router server, no model will be loaded in this process

확인

서버에 접속해

1
2
3
4
5
6
7
8
9
10
11
12
3.19.587.902 I srv  ensure_model: waiting until model name=gemma4-26b is fully loaded...
[50679] 0.00.032.099 I log_info: verbosity = 3 (adjust with the `-lv N` CLI arg)
[50679] 0.00.032.100 I device_info:
[50679] 0.00.170.204 I - CUDA0 : NVIDIA GeForce RTX 3080 Ti (11910 MiB, 11635 MiB free)
[50679] 0.00.170.209 I - CPU : AMD Ryzen 7 5800X3D 8-Core Processor (62168 MiB, 62168 MiB free)
[50679] 0.00.170.240 I system_info: n_threads = 8 (n_threads_batch = 8) / 16 | CUDA : ARCHS = 860 | USE_GRAPHS = 1 | PEER_MAX_BATCH_SIZE = 128 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 |
[50679] 0.00.170.242 I srv main: n_parallel is set to auto, using n_parallel = 4 and kv_unified = true
[50679] 0.00.170.254 I srv init: running without SSL
[50679] 0.00.170.272 I srv init: using 15 threads for HTTP server
[50679] 0.00.170.361 I srv start: binding port with default address family
[50679] 0.00.171.553 I srv main: loading model
[50679] 0.00.171.557 I srv load_model: loading model '/home/Models/gemma-4-26B-A4B-it-UD-Q5_K_S.gguf'

모델 정보

curl http://localhost:8080/v1/models

1
{"data":[{"id":"g4coder","aliases":[],"tags":[],"object":"model","owned_by":"llamacpp","created":1780772059,"status":{"value":"unloaded","args":["/home/qkboo/.local/bin/llama-server","--host","127.0.0.1","--port","0","--repeat-penalty","1.1","--temperature","0.1","--top-k","64","--top-p","0.1","--alias","g4coder","--ctx-size","65536","--model","/home/qkboo/Models/unsloth/gemma-4-26B-A4B-it-UD-Q5_K_S.gguf","--n-gpu-layers","-1"],"preset":"[g4coder]\nrepeat-penalty = 1.1\ntemperature = 0.1\ntop-k = 64\ntop-p = 0.1\nctx-size = 65536\nmodel = /home/qkboo/Models/unsloth/gemma-4-26B-A4B-it-UD-Q5_K_S.gguf\nn-gpu-layers = -1\n\n"},"architecture":{"input_modalities":["text"],"output_modalities":["text"]}},{"id":"gemma4-26b","aliases":[],"tags":[],"object":"model","owned_by":"llamacpp","created":1780772059,"status":{"value":"unloaded","args":["/home/qkboo/.local/bin/llama-server","--host","127.0.0.1","--port","0","--repeat-penalty","1.1","--temperature","1.0","--top-k","64","--top-p","0.95","--alias","gemma4-26b","--ctx-size","12288","--model","/home/quboo/Models/unsloth/gemma-4-26B-A4B-it-UD-Q5_K_S.gguf","--n-gpu-layers","-1"],"preset":"[gemma4-26b]\nrepeat-penalty = 1.1\ntemperature = 1.0\ntop-k = 64\ntop-p = 0.95\nctx-size = 12288\nmodel = /home/quboo/Models/unsloth/gemma-4-26B-A4B-it-UD-Q5_K_S.gguf\nn-gpu-layers = -1\n\n"},"architecture":{"input_modalities":["text"],"output_modalities":["text"]}}],"object":"list"}