모델 로딩¶
목차¶
- 모델 로드 시 무슨 일이 일어나는가
- 로딩 파이프라인 순서도
- 1단계: 탐지
- config.json 규약
- ModelType 열거형
- 특수 탐지 케이스
- 2단계: 정책 라우팅
- 디렉터리 라우트 패밀리
- 가중치 라우트 패밀리
- 3단계: 네 가지 로더
- ConfigBacked 로더
- Nonstandard 로더
- Special 로더
- VLM 로더
- LoadedModel 열거형
- 가중치 로딩 메커니즘
- SafeTensors 형식
- 묶인 임베딩 복원
- Config 정제
- 양자화 지원
- 로드 시점 Surgery 훅
- LoRA 어댑터 로딩
모델 로드 시 무슨 일이 일어나는가¶
mlxcel에 모델 경로를 전달하면 먼저 체크포인트가 생성, 임베딩, 재순위화, 오디오, 객체 검출 또는 다른 전용 과제 중 무엇인지 식별합니다. 생성 체크포인트는 이어서 세 단계를 수행합니다.
- 탐지:
config.json을 읽고 아키텍처를 식별하여ModelTypevariant를 생성합니다. - 정책 라우팅:
model_metadata레지스트리를 조회하여 어떤 로더 패밀리를 사용할지 결정합니다. - 구성: 선택된 로더를 실행하며 디스크에서 가중치를 읽고 MLX에서 모델의 레이어 그래프를 구성합니다.
생성 경로의 결과는 LoadedModel 열거형 값과 MlxcelTokenizer입니다. 임베딩과 재순위화 체크포인트는 대신 과제별 트레이트 객체와 토크나이저를 만들며 서버에서는 전용 작업자가 소유합니다. 생성하지 않는 점수 의미를 토큰 생성 열거형에 섞지 않는 구조입니다.
로딩 파이프라인 순서도¶
flowchart TD
START([Model path]) --> RESOLVE[Resolve directory\nif given a file path]
RESOLVE --> READ_CONFIG[Read config.json]
READ_CONFIG --> SANITIZE[Sanitize config JSON\nfix known formatting issues]
SANITIZE --> KIND{Model kind?}
KIND -- Embedding --> EMB_LOAD[load_embedding_model\npooling + normalization contract]
KIND -- Reranker --> RR_LOAD[load_reranker\npair or generative scoring]
KIND -- Generation --> DETECT[get_model_type\nmap model_type string\nto ModelType enum]
DETECT --> POLICY[model_load_policy\nconsult model_metadata registry]
POLICY --> ROUTE{Directory route?}
ROUTE -- ConfigBacked --> CB_LOAD[try_load_config_backed_model_from_dir\nconfig.json -> ModelArgs -> model.load]
ROUTE -- Nonstandard --> NS_LOAD[try_load_nonstandard_model_from_dir\nfamily-specific loader]
ROUTE -- VLM --> VLM_LOAD[try_load_vlm_model_from_dir\nvision encoder + text model]
ROUTE -- Mistral3Dynamic --> M3_CHECK{inner text_config\nmodel_type?}
M3_CHECK -- ministral3 --> M3_TEXT[Ministral3Wrapper]
M3_CHECK -- other --> M3_LLAMA[LlamaFallback]
CB_LOAD --> LOADED_MODEL
NS_LOAD --> LOADED_MODEL
VLM_LOAD --> LOADED_MODEL
M3_TEXT --> LOADED_MODEL
M3_LLAMA --> LOADED_MODEL
LOADED_MODEL[LoadedModel enum variant] --> TOK_LOAD[load_tokenizer\ntokenizer.json / tokenizer.model / .tiktoken]
TOK_LOAD --> DONE([LoadedModel + MlxcelTokenizer])
EMB_LOAD --> EMB_DONE([EmbeddingModel + tokenizer])
RR_LOAD --> RR_DONE([Reranker + tokenizer]) 1단계: 탐지¶
config.json 규약¶
대부분의 HuggingFace 체크포인트는 모델 디렉터리에 config.json을 제공합니다. mlxcel은 model_type을 기본 아키텍처 키로 사용한 뒤 architectures, 풀링 모듈, 양방향 표시, 분류 헤드, 모델 계열별 필드를 살펴 과제와 정확한 변형을 결정합니다.
src/models/detection.rs의 탐지 함수는 이 파일을 읽고 배포된 모델에서 알려진 형식 문제를 처리하기 위해 정제하며 model_type 문자열을 ModelType 열거형 variant로 매핑합니다.
config.json → { "model_type": "llama", ... } → ModelType::Llama
config.json → { "model_type": "gemma3", ... } → ModelType::Gemma3
config.json → { "model_type": "qwen2_vl", ... } → ModelType::Qwen2VL
사용 가능한 메타데이터로 지원되는 배치를 선택할 수 없으면 설명적인 오류와 함께 로딩이 실패합니다. 기본 아키텍처나 과제로 조용히 대체하지 않습니다.
ModelType 열거형¶
src/models/mod.rs의 ModelType은 등록된 모든 아키텍처를 열거합니다. 정확한 목록은 mlxcel arch가 출력하며 다음과 같은 계열로 구성됩니다.
| 카테고리 | 예시 |
|---|---|
| 표준 트랜스포머 | Llama, Llama4, Qwen2, Qwen3, Gemma, Gemma2, Gemma3, Phi, Phi3, Mistral, Mixtral |
| 한국어/중국어 모델 | ExaOne, ExaOne4, InternLM2, InternLM3, Baichuan, Glm4, Ernie45, Hunyuan |
| MoE 모델 | Qwen2Moe, Qwen3Moe, Mixtral, OLMoE, MiniMax, DeepSeekV2, DeepSeekV3 |
| SSM / RNN | Mamba, Mamba2, RecurrentGemma (Griffin), Rwkv7 |
| 하이브리드 | Jamba, NemotronH, NemotronNAS, Qwen3Next |
| VLM 및 멀티모달 | Gemma4Unified, Florence2, Qwen35VLM, Phi4MMVLM, MiniCPMOVLM, MuseGlimmer |
| 임베딩 및 재순위화 | BERT, XLM-RoBERTa, ModernBERT, EmbeddingGemma, EmbeddingGemma2, Qwen3 임베딩·VL 임베딩, ColIdefics3, ColQwen2.5, SequenceClassifier |
| 오디오 및 감지 | Whisper, Kokoro, NemotronVoiceChat, RtDetrV2, Yolo26 |
특수 탐지 케이스¶
일부 아키텍처는 model_type 문자열 읽기 이상의 작업이 필요합니다.
임베딩과 재순위화 분기: 인코더 전용 계열, 알려진 임베딩 architectures, sentence-transformers 풀링 모듈, 양방향 배치는 ModelKind::Embedding을 선택합니다. 지원되는 ForSequenceClassification 아키텍처는 같은 BERT 또는 ModernBERT 백본이 임베딩도 가능하더라도 재순위화를 선택합니다. 생성형 Qwen3와 Qwen3-VL 재순위화 모델은 설정만 보면 일반 생성 모델과 구분할 수 없으므로 --reranker-model이 필요합니다.
텍스트 vs VLM 분기: 일부 model_type 값은 동일한 아키텍처의 텍스트 전용 버전과 비전-언어 버전 모두에서 사용됩니다. 예를 들어, gemma3는 일반적으로 ModelType::Gemma3으로 매핑되지만 config에 vision_config 키가 있으면 ModelType::Gemma3VLM으로 매핑됩니다. detect_text_or_vlm() 헬퍼가 이 패턴을 일관되게 처리합니다. VLM model_type이라도 vision_config가 없거나 null 또는 빈 객체일 때, 최상위 language_model_only가 true일 때(비전 가중치가 함께 들어 있어도 이 값이 우선), 체크포인트에 vision_tower.*나 model.visual.* 가중치가 없을 때는 비전 타워 없이 텍스트 전용으로 로드됩니다. 이런 모델에 이미지나 비디오 요청을 보내면 model <path> was loaded without a vision tower (text-only checkpoint) 오류로 실패합니다.
Hunyuan 밀도: hunyuan 모델 타입은 num_experts를 사용하여 MoE(HunyuanMoe)와 dense(HunyuanV1Dense) 변형을 구별합니다.
Mistral3 중첩: Mistral3은 중첩된 text_config 객체를 래핑합니다. 탐지 결과는 ModelType::Mistral3이지만 정책 단계에서 나중에 내부 text_config.model_type을 검사하여 Ministral3 텍스트 타워를 로드할지 Llama로 폴백할지 결정합니다.
공유된 model_type 문자열: llama와 mistral은 모두 ModelType::Llama에 매핑됩니다. Mistral ½가 Llama ½와 동일한 아키텍처를 사용하기 때문입니다. 마찬가지로, mamba와 falcon_mamba는 모두 ModelType::Mamba에 매핑됩니다.
2단계: 정책 라우팅¶
ModelType이 확인되면, src/model_metadata.rs의 model_load_policy() 함수가 해당 타입의 StaticModelDescriptor를 조회하고 ModelLoadPolicy를 생성합니다. 이 정책은 두 가지 라우팅 결정을 캡처합니다.
- 디렉터리 라우트: 디렉터리에서 모델을 구성할 때 어떤 로더를 호출할지.
- 가중치 라우트: 사전 로드된 가중치에서 모델을 구성할 때 어떤 로더를 호출할지(LoRA 어댑터 경로에서 사용).
레지스트리는 for_each_model_registration! 매크로로 선언됩니다. 모든 모델 타입은 모든 라우팅 메타데이터와 함께 한 번씩 나열됩니다. 덕분에 새 모델 타입을 추가할 때 로딩 코드를 작성하기 전에 두 라우트 모두 선언해야 합니다.
디렉터리 라우트 패밀리¶
| 라우트 | 설명 | 예시 |
|---|---|---|
ConfigBacked | 표준 경로: config.json에서 ModelArgs를 역직렬화하고 model.load() 호출 | Llama, Qwen2, Gemma, 대부분의 텍스트 모델 |
Nonstandard | 표준 레지스트리 외부의 커스텀 디렉터리 로더 | Mamba, Jamba, Gemma3n, RWKV7 |
Vlm | 인코더와 텍스트 디코더를 모두 구축하는 비전-언어 모델 로더 | Gemma3VLM, LlavaVLM, Qwen2VL |
Mistral3Dynamic | 2단계: text_config를 검사한 후 Ministral3TextWrapper 또는 LlamaFallback으로 라우팅 | Mistral3 |
가중치 라우트 패밀리¶
| 라우트 | 설명 |
|---|---|
ConfigBacked | 표준 경로: ModelArgs를 역직렬화하고 from_weights() 호출 |
LlamaFamily | Llama와 Mistral3 텍스트 래퍼 폴백 경로 |
Special | 커스텀 정제 또는 소유 가중치 구성이 있는 아키텍처 |
VLM 패밀리에는 LoRA 어댑터 로딩이 지원되지 않기 때문에 VLM 모델 타입은 weight_route: None입니다.
3단계: 네 가지 로더¶
ConfigBacked 로더¶
src/loading/config_backed.rs의 config_backed 로더는 대부분의 텍스트 모델을 처리합니다. 이것은 for_each_model_registration! 매크로에 의해 완전히 생성되며 각 새로운 아키텍처에 대해 수작업으로 작성된 match arm이 필요하지 않습니다.
프로세스는 다음과 같습니다. 1. 디렉터리 경로 문자열과 함께 모델의 load() 함수를 호출합니다. 2. load() 함수는 config.json을 읽고 모델의 ModelArgs 구조체로 역직렬화하며 *.safetensors 가중치 파일을 로드하고 모델 레이어 그래프를 구성합니다. 3. 결과를 적절한 LoadedModel variant로 래핑합니다.
래퍼 타입을 사용하는 모델(Gemma3, ExaOne4, Ministral3, Llama4, GptOss)의 경우, 등록 항목의 wrap 클로저가 LoadedModel에 저장하기 전에 래퍼를 적용합니다.
Nonstandard 로더¶
src/loading/nonstandard.rs의 nonstandard 로더는 config 기반 패턴에 맞지 않는 특이한 가중치 명명, 비표준 config 구조, 또는 전처리 단계가 있는 아키텍처를 처리합니다. Mamba(표준 트랜스포머 config를 사용하지 않음), Jamba(Mamba와 어텐션 레이어를 결합), RWKV7, 특정 하이브리드 모델 등이 예시입니다.
각 비표준 아키텍처에는 명명된 자체 로딩 함수가 있습니다.
Special 로더¶
src/loading/special.rs의 special 로더는 디렉터리가 아닌 사전 로드된 가중치에서 모델을 로드할 때 호출됩니다. 이 경로는 베이스 모델 가중치와 어댑터 델타 가중치가 모델 구성 전에 메모리에서 융합되는 LoRA 어댑터 융합 워크플로우에서만 사용됩니다. 특이한 가중치 정제나 소유권 요구 사항이 있는 아키텍처가 여기로 라우팅됩니다.
VLM 로더¶
src/loading/vlm.rs의 vlm 로더는 비전-언어 모델을 처리합니다. 각 VLM 패밀리에는 다음을 수행하는 전용 함수가 있습니다. 1. 시각 인코더(예: SigLIPVisionModel, CLIPVisionModel, PatchViT)를 로드합니다. 2. VLM 특화 설정으로 텍스트 디코더를 로드합니다. 3. 크로스 모달 프로젝션 또는 커넥터를 로드합니다. 4. 세 컴포넌트 모두를 패밀리별 VLM 구조체로 래핑합니다.
try_load_vlm_model_from_dir() 함수가 ModelType에 따라 올바른 패밀리 로더로 분기합니다.
LoadedModel 열거형¶
src/loaded_model.rs의 LoadedModel은 로드된 모든 모델의 중앙 런타임 핸들입니다. 지원되는 모델 패밀리마다 variant가 하나씩 있는 Rust 열거형입니다.
pub enum LoadedModel {
Llama(models::Llama3Model),
Gemma3(models::Gemma3Wrapper),
Gemma3VLM(vision::VisionLanguageModel),
Qwen2VL(vision::Qwen2VLModel),
Mamba(models::MambaModel),
// ... 80+ variants
}
LoadedModel은 delegate_language_model! 매크로를 통해 각 메서드를 위임함으로써 LanguageModel 트레이트를 구현합니다. 이 매크로는 모든 variant에 대한 match arm을 생성하여 해당 내부 모델의 트레이트 구현을 호출합니다.
이 설계의 장점은 코드베이스의 나머지 부분인 생성 루프, 서버 스케줄러, CLI가 어떤 아키텍처가 로드되었는지 알 필요 없이 단일 &dyn LanguageModel 또는 &LoadedModel로 작업할 수 있다는 것입니다. 동시에, 기능 분기(예: VLM 임베딩 경로가 필요한지 확인)는 필요할 때 특정 variant에 매칭할 수 있습니다.
가중치 로딩 메커니즘¶
SafeTensors 형식¶
mlxcel은 HuggingFace에서 사용하는 것과 동일한 형식인 SafeTensors 형식에서만 모델 가중치를 로드합니다. mlxcel_core::weights::load_weights_from_dir() 함수는 모델 디렉터리에서 *.safetensors 파일을 스캔하고 MLX의 load_safetensors로 읽으며 WeightMap을 통해 텐서를 노출합니다.
각 생성 모델 로드 진입점은 패밀리 로더를 weight materialization 스코프 안에서 실행합니다(src/loading/materialize.rs의 with_weight_materialization, 정책은 mlxcel_core::weight_materialize). 로더가 반환하면 스코프가 모델이 보관한 체크포인트 텐서를 평가해 업로드하므로, Model loaded in ...는 가중치가 상주한 상태를 보고하고 첫 토큰은 가중치 I/O를 기다리지 않습니다. 두 부류는 지연 상태로 남습니다. 모달리티 타워 텐서(비전·오디오 타워는 첫 이미지나 클립에서 로드됩니다)와 로더가 버린 텐서(쓰지 않는 MTP 헤드, sanitize로 제거된 tied lm_head 등)입니다. 스코프 밖의 로드(파이프라인 스테이지, 임베딩 로더)도 지연 상태입니다.
MLXCEL_WEIGHT_MATERIALIZATION=lazy를 지정하면 텐서가 처음 필요한 평가 안에서 읽는 방식으로 돌아갑니다. CUDA에서 eager 업로드는 재사용하는 64 MiB pinned 호스트 버퍼 4개로 텐서를 옮기며, pinned 할당에 실패하면 경고를 한 번 출력하고 pageable 메모리로 처리합니다. MLXCEL_CUDA_PINNED_LOAD=0은 pageable 경로를 강제합니다. MLXCEL_PROFILE_TTFT를 설정하면 모델 로드마다 [TTFT] load 줄을 출력합니다.
외부 가중치 저장소¶
qwen4_exp의 두 구조는 체크포인트가 그렇게 선언하면 MLX 파라미터 밖에 둘 수 있습니다.
text_config.ple_storage를 선언한 체크포인트는 약 30 GiB인 PLE n-gram 테이블을 디스크에 두고, 파라미터에 들고 있지 않고 필요할 때 행을 읽습니다. mlx-vlm v2 매니페스트를 그대로 받으며(safetensors_ranges, interleaved_q4, affine 4/32, nvfp4 4/16), 검증 실패는 어느 필드인지 밝힙니다. 매니페스트와 행 파일은 모델 디렉터리 안으로 해석되어야 하므로 절대 경로 ple_storage.manifest는 거부하고, 각 safetensors_ranges 텐서는 해당 파일의 SafeTensors 헤더와 맞아야 합니다. 매니페스트 하나가 테이블 하나를 설명하므로 PLE 계층이 둘 이상이면 거부합니다. MLXCEL_PLE_IO는 mmap(기본값), direct, ram 중 하나를 고르고, MLXCEL_PLE_CACHE_BYTES 바이트(기본값 0.5 GiB)의 LRU 행 캐시가 같은 id를 재사용합니다. gather-ahead는 GPU가 현재 조각을 실행하는 동안 다음 prefill 조각의 행을 읽어 둡니다. mlxcel prepare-external-ple는 Python 없이 뷰를 만듭니다. mlxcel inspect와 --estimate-memory는 디스크의 테이블이 아니라 행 캐시(또는 ram 모드의 테이블)를 세고, 생성이 끝나면 hit, miss, byte, 시간을 통계 줄로 남깁니다. 알려진 제약이 둘 있습니다. mmap이 매핑한 파일을 자르면 SIGBUS가 나며(direct는 이를 피함), 디스크 읽기 오류는 오류 채널이 없는 경로라 forward를 패닉시킵니다.
라우팅된 expert는 SSD에서 계층별 슬롯 아레나로 스트리밍하고 나머지는 상주시킬 수 있습니다. 아레나는 필요할 때 채웁니다. 각 계층이 라우터의 top-k를 호스트에서 한 번 평가하고, 윈도가 축출할 수 없는 LRU 희생자에 miss를 읽어 넣고, 슬롯 인덱스로 gather_qmm을 실행합니다. 슬롯 텐서는 페이지 정렬된 호스트 할당을 복사 없이 MLX 배열로 감싼 것이고, 슬롯은 그 슬롯을 읽는 모든 명령이 끝난 뒤에만 다시 씁니다. 정렬 prefill은 상주 경로의 정렬을 재현하고, 상주 텐서가 받을 커널과 tile을 MLX가 고르도록 배치를 계획합니다. 그렇게 계획할 수 없는 윈도는 low bit가 다를 수 있다는 경고를 한 번 남깁니다. MLXCEL_EXPERT_STREAM은 auto(기본값), 1, 0을 받습니다. auto에서는 라우팅된 expert가 Metal working set의 60%를 넘을 때만 동작하므로 큰 머신은 상주 상태로 남습니다. MLXCEL_EXPERT_ARENA_BYTES가 아레나 크기를 고정하며, 지정하지 않으면 KV 캐시와 transient용으로 6 GiB를 남긴 가용 메모리에서 계산하고 최소 64 슬롯, 16의 배수로 잡아 모든 plane이 16 KiB 페이지 단위가 되게 합니다. mlxcel prepare-expert-stream은 이어 붙인 해시 검증 파일을 만들어 두면 로더가 자동으로 사용합니다. 기동 시 [Expert arena: ...] 줄을 남기고 mlxcel_expert_arena_* 메트릭이 동작을 보고하며, preflight 회계에 아레나가 포함되고, 96 GiB 미만 머신의 초안 모델은 경고를 남기며, 형식이 틀렸거나 디렉터리 밖을 가리키는 체크포인트는 거부합니다. 검증 호스트에서 100% hit rate일 때 계층별 동기화 비용은 토큰당 약 15 ms입니다.
묶인 임베딩 복원¶
일부 모델은 가중치 묶기(weight tying)를 사용합니다. 즉, 언어 모델 헤드(lm_head)가 토큰 임베딩 테이블(embed_tokens)과 동일한 텐서를 공유합니다. 이러한 모델이 직렬화될 때, 가중치는 한 번만 저장될 수 있습니다. mlxcel의 sanitize_tied_embeddings() 함수는 config의 tie_word_embeddings 플래그를 검사하여 이 케이스를 감지하고 헤드 가중치가 없으면 임베딩 텐서를 lm_head 키에 복사합니다.
이 단계는 모델이 가중치에서 구성되기 전에 실행되므로 모델 코드는 묶인 임베딩 시나리오를 특수 처리하지 않고도 항상 lm_head 가중치를 찾을 수 있습니다.
Config 정제¶
src/models/sanitize.rs의 sanitize_config_json() 함수는 역직렬화 전에 원시 config 문자열을 전처리합니다. 배포된 HuggingFace 모델에서 알려진 형식 문제들을 수정하는 과정입니다. 예를 들어, 숫자 필드에 예상되는 null 값이나 엄격한 타입을 유지하지 않고 config 파일을 수정한 파인튜너들이 도입한 아키텍처 특이 사항 등이 있습니다.
정제는 역직렬화 이후가 아닌 이전에 수행되므로, ModelArgs 구조체가 구성되면 타입 시스템의 보장이 유지됩니다.
양자화 지원¶
mlxcel-core의 가중치 로딩은 MLX의 네이티브 양자화 형식으로 저장된 양자화 모델을 지원합니다. 양자화된 가중치는 SafeTensors 파일의 양자화 메타데이터 존재 여부에 따라 자동으로 감지됩니다. mlxcel-core의 생성 루프와 레이어 수식은 MLX 연산 그래프를 통해 양자화된 텐서를 투명하게 처리합니다.
양자화 형식¶
| 형식 | Metal과 CUDA | ROCm | 참고 |
|---|---|---|---|
| FP16 / BF16 | 네이티브 | 네이티브 | 양자화되지 않은 bf16 가중치는 bf16을 유지하는 계열이 아니면 Apple Silicon에서 로드할 때 f16으로 변환됩니다. |
| 4비트·8비트 affine | 네이티브 | 네이티브 | mlx-community 체크포인트의 주된 형식입니다. |
1비트 affine(bits: 1, group_size 32·64·128, .scales와 .biases 포함) | Metal은 fused qmv(행 16개 미만)와 타일 qmm 커널, CUDA는 역양자화 그래프 | 역양자화 그래프 | 밀집 선형층과 임베딩에만 적용됩니다. 1비트 MoE 전문가 묶음, 1비트 MLA 헤드별 사영, .biases가 없는 1비트 평면은 로드 단계에서 거부됩니다. MLXCEL_ONE_BIT_KERNEL=0이면 Metal에서도 역양자화 그래프를 씁니다. prism-ml/Bonsai-1.7B-mlx-1bit와 prism-ml/Bonsai-8B-mlx-1bit(qwen3)로 검증했습니다. |
| MXFP4 / MXFP8 | 네이티브 | MoE gather_qmm을 포함해 네이티브 | 벤더 FP8 블록 체크포인트(quant_method: fp8, 128x128 블록)는 로드할 때 MXFP8로 재양자화되어 네이티브 MXFP8 커널로 실행됩니다. |
| NVFP4 | 네이티브 | 네이티브 커널 없음 | ROCm에서 ModelOpt NVFP4 체크포인트는 로드할 때 affine 4비트로 변환되고 로드 로그에 그 경로가 남습니다. 변환할 수 없는 레이어가 있으면 그 이름을 밝히며 로드에 실패합니다. MLX 네이티브 NVFP4 내보내기("mode": "nvfp4")는 ROCm에서 로드 단계에 거부됩니다. |
레이어별 비트 폭 재정의¶
일부 체크포인트는 한 모델 안에 여러 비트 폭을 섞어서 배포됩니다. 가중치의 대부분은 4비트이지만 선택된 몇 개 레이어(MoE 모델의 라우터 게이트, 공유 expert 게이트, 특정 임베딩 헤드 등)는 8비트로 양자화되어 있는 방식입니다. MLX는 nn.quantize(class_predicate=...)로 이런 구조를 만들고 mlx-community/Qwen3.5-35B-A3B-4bit와 이후의 Qwen3.6 파생 체크포인트가 이 패턴을 실제로 활용하는 첫 배포판입니다.
로더는 affine 양자화 가중치를 만들 때마다 infer_quantization_bits(weight_shape, scales_shape, group_size, caller_bits)를 호출합니다. MLX의 불변식 packed_in * 32 == bits * num_groups * group_size를 실제 텐서 shape에 대입해 검증하고 caller가 넘긴 bits와 결과가 다르면 추론된 비트 폭을 우선 적용합니다. forward 경로 자체는 QuantizedWeight.bits와 group_size를 진실의 소스로 사용하므로 이 probe가 영향을 주는 구간은 가중치 생성 단계뿐입니다. 순수 포맷 기반 양자화(mxfp4, nvfp4, mxfp8)는 모드별로 비트 폭이 고정되어 있어 추론 단계를 건너뜁니다.
실질적인 결과는, 4비트와 8비트 텐서가 섞여 있는 체크포인트가 별도 config 오버라이드 없이 바로 로드되고 실행된다는 점입니다. qwen3.6-35b-a3b-4bit(레이어별 8비트 게이트 재정의가 있는 기존 qwen3_5_moe 아키텍처)가 별다른 설정 없이 동작하게 된 이유도 여기에 있습니다. qwen3.5-35b-a3b-4bit와 llama-3.1-8b-4bit에서 측정한 로드 시간·decode throughput은 감지 가능한 차이가 없었고 비트 폭 추론 자체가 가중치당 상수 시간의 shape 검사이기 때문에 hot path에 부담을 주지 않습니다.
로드 시점 Surgery 훅¶
통합된 텍스트 및 VLM 가중치 로더에는 모델 그래프를 구성하기 전에 메모리상의 WeightMap을 수정할 수 있는 WeightTransform 훅이 있습니다. --surgery <FILE>과 MLXCEL_SURGERY는 YAML 파일에서 이 훅을 구현하는 프로세스 전역 pipeline을 설치합니다.
텍스트 모델에서는 묶인 임베딩 복원, 양자화 메타데이터 정규화 같은 표준 가중치 정제 이후, 그리고 Apple Silicon에서 비양자화 bf16 가중치를 f16으로 변환하기 전에 이 훅이 실행됩니다. VLM에서는 공통 VLM 가중치 로더 안에서 실행되므로, 규칙은 보통 이후의 패밀리별 remap 이름보다 체크포인트 또는 공통 로더 단계의 키 이름을 대상으로 작성하는 편이 안전합니다.
이 훅은 실패를 숨기지 않습니다. 연산은 YAML 순서대로 실행되고 대상 텐서가 매칭되지 않거나 shape/type 검증 또는 안전한 갱신에 실패하면 모델 로딩이 중단됩니다. 따라서 surgery 설정을 재현 가능하게 유지하고 일부만 수정된 모델이 조용히 서빙되는 상황을 피할 수 있습니다.
사용자용 YAML 스키마와 예시는 런타임 모델 변환을 참조하십시오.
LoRA 어댑터 로딩¶
mlxcel은 어댑터를 융합하거나 융합하지 않은 런타임 항으로 적용합니다. 서버 기본값은 융합하지 않는 경로이며, 각 어댑터를 스케일을 바꿀 수 있는 저랭크 항으로 유지합니다. 자세한 내용은 LoRA 어댑터를 참조하십시오. 융합 경로(mlxcel generate --adapter, 그리고 --lora-fuse 또는 파이프라인 병렬 처리를 쓰는 서버)에서는 mlxcel이 모델 구성 전에 가중치를 융합합니다.
load_weights_from_dir()를 사용하여 모델 디렉터리에서 베이스 모델 가중치를 로드합니다.- 어댑터 디렉터리에서 어댑터 가중치를 로드합니다.
lora::apply_lora_adapters()를 사용하여 두 가중치 세트를 융합합니다. 이 함수는 저랭크 델타 행렬을 해당 베이스 가중치 텐서에 더합니다.- 융합된
WeightMap을load_model_from_weights()에 전달하며 이 함수는 디렉터리 라우트 대신 가중치 라우트를 사용하여 모델을 구성합니다.
융합된 모델은 런타임에서 표준 모델과 구별할 수 없습니다. LoRA 구조는 융합 중에 해체되어 병합된 모델은 표준 가중치 형태가 됩니다.
VLM 모델 타입은 명시적인 오류 메시지로 어댑터 로딩을 거부합니다. 이 제한 사항은 각 VLM의 StaticModelDescriptor의 adapter_unsupported_message 필드에 명시되어 있습니다.