콘텐츠로 이동

새 모델 추가하기

이 가이드는 mlxcel에 새 모델 아키텍처를 추가하는 방법을 설명합니다. docs/MODEL_ADDITION_GUIDE.md(정식 레퍼런스)를 기반으로 하며 실제로 수정해야 하는 제어 지점을 구체적으로 설명합니다.

목차


목표

  • 새 모델 추가 작업을 예측 가능하게 유지합니다.
  • 새 one-off branch를 추가하기보다 기존 control-plane helper를 재사용합니다.
  • 회귀가 나기 쉬운 integration point 옆에 테스트를 함께 추가합니다.

시작하기 전에

  1. 레퍼런스 구현을 확인합니다:
  2. 텍스트 모델: ml-explore/mlx-lm의 mlx_lm/models/
  3. VLM: Blaizzy/mlx-vlm의 mlx_vlm/models/

  4. 아키텍처를 분류합니다:

  5. 완전히 새로운 모델 계열 → src/models/ 아래에 새 파일 생성
  6. 기존 계열의 형식 별칭 → detection.rs의 감지 로직 확장
  7. 기존 텍스트 모델을 감싼 VLM 래퍼 → src/vision/ 아래에 비전 인코더/커넥터를, src/loading/vlm_<family>.rs에 로더 추가

  8. 기존 로더 헬퍼가 이미 맞는지 확인합니다: src/model_metadata.rs(수렴된 등록 테이블)부터 시작하십시오. 표준 텍스트 모델은 src/loading/config_backed.rs가 동일한 진실의 원천을 사용하므로, 먼저 해당 등록 테이블을 확장해야 합니다.

  9. 공유 실행 정책 영향을 확인합니다:

  10. 장치/환경 동작은 src/execution/runtime.rs
  11. 샘플링 기본값은 src/execution/sampling.rs

텍스트 모델 체크리스트

다음 단계를 순서대로 따르십시오:

  1. 구현 파일을 추가합니다. src/models/<family>.rs 위치에.

  2. 모듈을 등록하고 re-export합니다. src/models/mod.rs에:

    pub mod my_new_model;
    pub use my_new_model::MyNewModel;
    

  3. ModelType 변형을 추가합니다. src/models/mod.rs에:

    pub enum ModelType {
        // ... existing variants ...
        MyNewModel,
    }
    

  4. get_model_type()을 확장합니다. src/models/detection.rs에:

    "my_new_model" => Ok(ModelType::MyNewModel),
    
    패턴에 맞으면 공유 헬퍼를 사용하십시오:
    "my_family" => Ok(detect_text_or_vlm(&v, ModelType::MyFamily, ModelType::MyFamilyVLM)),
    

  5. LoadedModel 변형을 추가합니다. src/loaded_model.rs에. 새로운 반복적인 매치 암을 추가하는 대신 delegate_language_model! 매크로를 확장하는 것을 선호합니다.

  6. 로딩을 연결합니다. src/loading/mod.rs에. 표준 설정 기반 모델의 경우:

  7. src/model_metadata.rs를 종류, 어댑터 지원, 경로 선택에 맞게 업데이트합니다.
  8. 모델이 표준 텍스트 모델 경로를 따른다면, src/loading/config_backed.rs에 병렬 항목 목록을 추가하는 대신 공유 등록 표면을 확장합니다.

  9. LoRA/어댑터 지원 (해당하는 경우): src/loading/mod.rs의 load_model_from_weights()를 확인합니다. 비표준 어댑터 경로는 src/loading/special.rs에 넣습니다.


VLM 체크리스트

  1. 비전 컴포넌트를 구현한다 (또는 기존 것을 재사용):
  2. 비전 인코더: src/vision/encoders/
  3. 커넥터 (인코더 출력을 텍스트 모델 입력으로 연결): src/vision/connectors/
  4. 프로세서 (이미지 전처리): src/vision/processors/

  5. VLM ModelType 감지를 추가합니다. src/models/detection.rs에. 텍스트 전용 및 VLM 변형이 모두 있는 계열의 경우 detect_text_or_vlm()을 사용합니다.

  6. 로더 항목을 추가합니다. src/loading/vlm_<family>.rs에 (또는 가장 가까운 동료 계열 모듈에). src/loading/mod.rs의 try_load_vlm_model_from_dir()에 디렉터리 진입점을 등록합니다.

  7. LoadedModel 능력 헬퍼를 추가합니다. src/loaded_model_capabilities.rs에. 계열별 getter를 추가하는 대신 VlmRuntimeRef를 확장하는 것을 선호합니다.

  8. 가능한 경우 프롬프트 헬퍼를 재사용합니다:

  9. Qwen-VL 토큰 삽입: src/multimodal/qwen_vl.rs
  10. 일반 이미지 토큰 블록 확장: src/multimodal/vlm_prompt.rs
  11. Phi3V 프롬프트 태그 처리: src/multimodal/phi3v_prompt.rs

새 family module을 만들 시점

기본적으로 새 파일을 만들지 마십시오. family가 뚜렷한 control-plane identity를 가질 때만 새 모듈을 만듭니다.

새 loader family module을 만드는 경우:

  • config normalization이 기존 family의 작은 변형이 아닌 경우
  • token 기본값이나 weight-key remapping에 전용 테스트가 필요한 경우
  • VLM wrapper가 다른 prompt/runtime assembly 경로를 사용하는 경우
  • 기존 family boundary를 흐리게 만들 정도로 inline logic이 커지는 경우

기존 모듈 안에 유지하는 경우:

  • 주로 alias 또는 작은 config delta인 경우
  • 기존 loader test가 이미 정책을 표현하는 경우
  • 변경 후에도 family 정체성이 명확히 유지되는 경우

애매하면 먼저 기존 family module을 확장하고 테스트 파일이나 router의 경계가 흐려질 때만 분리하십시오.


모델 구현 패턴

모든 모델 구현은 cxx 브리지를 통해 MLX C++ 바인딩을 위한 mlxcel-core를 사용합니다. LanguageModel 트레잇은 src/lib/mlxcel-core/src/generate.rs에 정의되어 있습니다:

pub trait LanguageModel {
    /// Forward pass: returns logits of shape [batch, seq_len, vocab_size]
    fn forward(
        &self,
        input_ids: &MlxArray,
        caches: &mut [KVCache],
        mask: Option<&MlxArray>,
    ) -> UniquePtr<MlxArray>;

    /// Create per-layer KV caches
    fn make_caches(&self) -> Vec<KVCache>;

    /// Number of transformer/SSM layers
    fn num_layers(&self) -> usize;

    /// EOS token IDs for this model
    fn eos_token_ids(&self) -> Vec<i32>;

    /// Whether tile-aligned padded prefill is safe (default: true).
    /// Recurrent hybrids (NemotronH, Jamba, Mamba) return false because
    /// pad positions corrupt their conv/SSM state.
    fn supports_padded_prefill(&self) -> bool { true }

    /// Drop `excess` pad positions from model-owned state after a padded
    /// prefill. The default has nothing to trim; families that keep their
    /// own caches and still answer `supports_padded_prefill() == true` must
    /// override it.
    fn trim_state(&self, seq: Option<SequenceId>, excess: i32) -> Result<(), String>;

    /// Whether the scheduler's decode lookahead can be rewound from this
    /// model's own per-sequence state (default: false, synchronous decode).
    fn supports_decode_lookahead_rewind(&self) -> bool { false }

    /// Forward with optional pre-computed embeddings (for VLM prefill).
    /// The default ignores the embeddings and calls `forward`.
    fn forward_with_embeddings(
        &self,
        input_ids: &MlxArray,
        input_embeddings: Option<&MlxArray>,
        caches: &mut [KVCache],
        mask: Option<&MlxArray>,
    ) -> UniquePtr<MlxArray>;
}

트레잇에는 기본 구현이 있는 메서드(청크 prefill, 배치 순전파, 시퀀스 지정 순전파 등)가 더 있으며, 위 발췌는 새 모델이 자주 건드리는 것만 보여줍니다. attention layer는 dense와 paged attention 중 하나를 직접 고르지 않고, 자신의 KVCache에 cache.attend(&q, k, v, scale, mask)를 호출합니다(배치 스텝이면 mlxcel_core::cache::attend_batched). 그러면 cache가 자신이 담고 있는 dense, rotating, chunked, paged 구현 중 맞는 쪽으로 보냅니다. 모델이 cache를 직접 소유하는 family는 padded prefill을 쓰려면 trim_state를, 서버의 lookahead decode를 쓰려면 supports_decode_lookahead_rewind와 rewind_decode_appends를 구현해야 하며, 구현하지 않으면 padding 없는 prefill과 동기식 decode로 동작합니다.

디코드는 CLI와 서버 모두 하나의 배치 네이티브 엔진(mlxcel_core::engine::Engine)에서 실행되고 시퀀스 하나는 크기 1의 배치이므로, 새 모델은 요청이 하나뿐일 때도 forward_batched / forward_batched_with_ids를 거쳐 디코드합니다. supports_batching()의 기본값은 true입니다. batched_decode_kind()의 기본값은 행마다 forward를 반복하는 BatchedDecodeKind::PerRowFallback이며, forward_batched를 오버라이드해 행들이 하나의 그래프를 공유하게 하고 BatchedDecodeKind::Native를 반환하면 서버가 시작할 때 decode path: native batched forward를 로그로 남깁니다. per-row fallback도 동작하지만 전체 처리량이 --parallel에 따라 늘지 않습니다. 상태를 모델 내부에 두는 패밀리(순환, 선형 attention, 모델 소유 캐시)는 그 상태를 SequenceId로 구분해 보관하고(sequence_state_layout, prepare_sequence_state, release_sequence_state_by_id, 그리고 id를 받는 forward_batched_with_ids) 그러면 supports_batching() == true도 보고할 수 있습니다. 한 번에 요청 하나만 처리하는 런타임(seq2seq나 duplex 음성 worker 등)만 false를 반환하며, 이 경우 서버가 max_batch_size를 1로 제한합니다.

최소한의 텍스트 모델 구현 예시:

use crate::ffi::{self, MlxArray, MlxStream};
use crate::layers::KVCache;
use cxx::UniquePtr;

pub struct MyNewModel {
    // Weight tensors held as UniquePtr<MlxArray>
    embed_tokens: UniquePtr<MlxArray>,
    layers: Vec<MyTransformerLayer>,
    norm: UniquePtr<MlxArray>,
    lm_head: UniquePtr<MlxArray>,
    n_layers: usize,
}

impl MyNewModel {
    /// Load from a weights map (SafeTensors key → MlxArray)
    pub fn from_weights(
        config: &MyModelConfig,
        weights: &std::collections::HashMap<String, UniquePtr<MlxArray>>,
    ) -> anyhow::Result<Self> {
        // Extract tensors from the weights map
        let embed_tokens = weights
            .get("model.embed_tokens.weight")
            .ok_or_else(|| anyhow::anyhow!("missing embed_tokens"))?;
        // ... load all layers ...
        Ok(Self { /* ... */ })
    }
}

impl LanguageModel for MyNewModel {
    fn forward(
        &self,
        input_ids: &MlxArray,
        caches: &mut [KVCache],
        mask: Option<&MlxArray>,
    ) -> UniquePtr<MlxArray> {
        // 1. Embed input tokens
        let mut h = ffi::take(input_ids, &self.embed_tokens, 0);
        // 2. Run through transformer layers
        for (i, layer) in self.layers.iter().enumerate() {
            h = layer.forward(&h, &mut caches[i], mask);
        }
        // 3. Apply final norm
        h = ffi::rms_norm(&h, &self.norm, 1e-5);
        // 4. Apply LM head
        ffi::linear(&h, &self.lm_head, None)
    }

    fn make_caches(&self) -> Vec<KVCache> {
        (0..self.n_layers).map(|_| KVCache::new()).collect()
    }

    fn num_layers(&self) -> usize {
        self.n_layers
    }

    fn eos_token_ids(&self) -> Vec<i32> {
        vec![2] // </s> token, model-specific
    }
}

SafeTensors에서 가중치 로딩

모델 가중치는 SafeTensors 파일(.safetensors)로 배포됩니다. src/loading/의 로딩 파이프라인은 이 파일들을 읽고 from_weights()에 전달되는 가중치 맵을 빌드합니다.

가중치 맵은 HashMap<String, UniquePtr<MlxArray>>이며 키는 SafeTensors 파일에서 찾은 전체 가중치 이름입니다 (예: "model.layers.0.self_attn.q_proj.weight").

주요 헬퍼:

  • src/models/sanitize.rs: load_and_sanitize_weights(): 디렉터리에서 모든 .safetensors 파일을 로드하여 키 정규화 및 묶인 임베딩 처리를 적용합니다.
  • src/models/sanitize.rs: sanitize_tied_embeddings(): 모델이 묶인 임베딩을 사용하는 경우 임베딩 테이블을 LM head에 복사합니다.
  • src/models/sanitize.rs: sanitize_config_json(): 파싱 전에 config.json의 알려진 비표준 JSON 패턴을 정규화합니다.

mlxcel_core::weights(load_weights_from_dir과 필터 변형)로 읽은 가중치는 모든 모델 로드 진입점이 여는 weight materialization 스코프에 등록되므로, 새 패밀리는 별도 코드 없이 로드 시점의 eager 체크포인트 업로드를 그대로 얻습니다. 로더가 버린 텐서와 모달리티 타워 텐서는 지연 상태로 남습니다. 패밀리 로더에서 가중치를 eval해 상주시키려 하지 마십시오.

// Loading pattern used by most standard text models
let weights = load_and_sanitize_weights(&model_path)?;
let config: MyModelConfig = serde_json::from_str(&config_str)?;
let model = MyNewModel::from_weights(&config, &weights)?;

가중치 키 재매핑

가중치 파일이 구현이 예상하는 키 이름과 다른 이름을 사용하는 경우, 로더에 재매핑 단계를 추가합니다:

let weights: HashMap<String, _> = raw_weights
    .into_iter()
    .map(|(k, v)| {
        let new_key = k
            .replace("transformer.h.", "model.layers.")
            .replace("ln_f", "model.norm");
        (new_key, v)
    })
    .collect();

모델 타입 감지

src/models/detection.rs의 감지 함수는 모델 디렉터리에서 config.json을 읽고 model_type을 매칭합니다:

pub fn get_model_type(model_path: &Path) -> Result<ModelType> {
    let config: serde_json::Value = /* read and parse config.json */;
    let model_type = config["model_type"].as_str()?;
    match model_type {
        "my_new_model" => Ok(ModelType::MyNewModel),
        // ...
    }
}

동일한 model_type 문자열이 텍스트 전용 및 VLM 변형을 모두 참조할 수 있는 경우, detect_text_or_vlm() 헬퍼를 사용합니다:

"my_family" => Ok(detect_text_or_vlm(
    &config,
    ModelType::MyFamily,
    ModelType::MyFamilyVLM,
)),

이 헬퍼는 config.json이 vision tower를 선언하면(language_model_only: true 없이 비어 있지 않은 vision_config가 있으면) VLM 변형을, 그렇지 않으면 텍스트 전용 변형을 반환합니다. detect_text_or_vlm_by_weights는 checkpoint의 가중치 이름까지 확인해 vision tower 가중치가 없으면 텍스트 전용 변형을 고르므로, vision을 제거한 checkpoint는 텍스트 모델로 로드됩니다.

새 매핑을 커버하는 단위 테스트를 src/models/detection_tests.rs에 추가하십시오.


LoadedModel 디스패치

src/loaded_model.rs에는 지원되는 모델 타입당 하나의 변형을 보유하는 LoadedModel 열거형이 있습니다. delegate_language_model! 매크로는 LanguageModel 트레잇 메서드에 대한 디스패치 코드를 생성합니다:

delegate_language_model! {
    MyNewModel => LoadedModel::MyNewModel(m) => m,
    // other variants...
}

VLM의 경우, src/loaded_model_capabilities.rs에도 능력 연결을 추가합니다:

impl VlmRuntimeRef<'_> {
    pub fn encode_images(&self, images: &[ImageData]) -> Result<UniquePtr<MlxArray>> {
        match self {
            Self::MyFamilyVLM(m) => m.encode_images(images),
            // ...
        }
    }
}

자주 발생하는 문제

증상 가능한 원인
로드 시 Unsupported model type: my_new_model detection.rs의 항목 누락 또는 오타
model.layers.0.self_attn.q_proj.weight 미발견으로 패닉 가중치 키 불일치; SafeTensors 키 이름 확인
잘못된 토큰 ID (깨진 출력) eos_token_ids()의 잘못된 EOS 토큰 ID
VLM이 텍스트 전용으로 로드됨 vision_config가 없거나 null 또는 {}임, config.json에 language_model_only: true가 있음, vision tower 가중치(vision_tower.*, model.visual.*, visual.*, vision_model.*)가 없음, 또는 감지에 detect_text_or_vlm() 미사용
stdout을 파싱하는 도구의 출력이 깨짐 loader가 진행 상황을 println!으로 출력함. 모델 loader는 tracing::info!로 로그를 남기고 stdout은 생성 출력에만 씀
LoRA 어댑터 로드 실패 model_metadata.rs의 어댑터 지원 선언 누락
서버에서 새 모델 타입을 허용하지 않음 LoadedModel 변형 누락 또는 delegate_language_model! 미업데이트
설정 기반 로더가 새 모델을 우회 공유 등록 표면 대신 load_model()에 직접 새 모델 추가
VLM에서 양자화(예: 4비트)가 상속되지 않음 VLM 로더에서 최상위 양자화 상속 누락

회귀가 자주 나는 지점

대부분의 회귀는 decoder math 자체보다 등록, routing, capability dispatch 주변에서 발생합니다. 다음 파일을 명시적으로 확인하십시오.

  • src/models/mod.rs
  • module export 또는 ModelType variant 누락
  • src/models/detection.rs
  • get_model_type() alias 누락
  • vision_config가 있을 때 text/VLM 오분류
  • src/loading/mod.rs
  • load_model()과 load_model_from_weights()의 분기 불일치
  • 표준 config-backed 모델을 공유 loader helper 대신 one-off special case로 추가
  • VLM을 try_load_vlm_model_from_dir() 대신 load_model()에 직접 추가
  • src/model_metadata.rs
  • loader wiring 전에 text/VLM kind, adapter support, route policy, shared standard-text registration 누락
  • src/loading/config_backed.rs
  • 공유 registration surface 우회
  • Llama4, Gemma3, Ministral3 같은 wrapper constructor 누락
  • src/loading/nonstandard.rs
  • directory-only loader family를 non-standard registry가 아니라 src/loading/mod.rs에 남김
  • src/loading/special.rs
  • adapter/owned-weight special handling을 special-weight registry 대신 inline으로 추가
  • Qwen3.5 text-config normalization 또는 owned-weight sanitization 누락
  • src/loading/vlm.rs와 src/loading/ 아래 sibling family modules
  • 잘못된 기본 token ID
  • top-level quantization inheritance 누락
  • text tower와 vision tower 사이의 weight-key remapping 오류
  • src/loaded_model.rs와 src/loaded_model_capabilities.rs
  • 새 variant dispatch arm 누락
  • VlmRuntimeRef capability wiring 누락
  • all-model dispatch macro만 갱신하고 multimodal switchboard를 잊음

src/loading/mod.rs는 route selection에 집중하게 유지하십시오. 새 model family가 상당한 construction logic을 추가한다면 load_model() 또는 load_model_from_weights()를 키우는 대신 dedicated sibling module을 만들고 router에서 호출하는 편이 좋습니다.

매우 큰 model family는 main decoder stack과 다른 이유로 변경되는 helper hotspot을 focused sibling helper module로 추출하십시오. 현재 예시는 src/models/gemma3n_helpers.rs, src/models/llama4_helpers.rs, src/loading/ 아래 VLM family modules입니다.


새 모델 테스트하기

감지 테스트

src/models/detection_tests.rs에 model_type 문자열이 올바른 ModelType 변형에 매핑되는지 확인하는 단위 테스트를 추가합니다:

#[test]
fn test_my_new_model_detection() {
    let config = serde_json::json!({"model_type": "my_new_model"});
    // write config.json to a temp dir and call get_model_type()
}

정제 테스트

모델에 가중치 키 재매핑 또는 설정 정규화가 필요한 경우, src/models/sanitize_tests.rs에 테스트를 추가합니다.

로더 테스트

로더 정규화 또는 토큰 기본값 로직의 경우, src/loading/tests.rs 또는 전용 src/loading/<family>_tests.rs 근처에 테스트를 추가합니다.

스모크 테스트 (로컬 모델 필요)

로컬에 모델 가중치가 있다면, CLI를 통해 스모크 테스트를 실행합니다:

mlxcel generate -m ./models/my-new-model -p "Hello, world!" -n 20

또는 서버를 통해:

mlxcel-server --model ./models/my-new-model --port 8080 &
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "my-new-model", "messages": [{"role": "user", "content": "Hello"}]}'

무시된 테스트 실행

실제 모델 가중치가 필요한 테스트(#[ignore]로 표시된):

cargo test --test model_loading -- --ignored --test-threads=1

더 넓은 테스트 기대치

model/control-plane 변경과 같은 slice에 테스트를 추가하십시오.

  • model detection helper: src/models/detection.rs 근처 테스트
  • sanitization helper: src/models/sanitize.rs 근처 테스트
  • loader normalization 또는 token-default logic: src/loading/tests.rs 또는 관련 src/loading/vlm*_tests.rs
  • shared vision merge contract: src/vision/merge_tests.rs
  • prompt/token expansion logic: src/multimodal/qwen_vl_tests.rs, src/multimodal/vlm_prompt_tests.rs, src/multimodal/phi3v_prompt_tests.rs 같은 전용 helper test
  • runtime validation: scripts/run_quality_gate.sh와, matching model이 있으면 최소 하나의 local smoke test

MLX-heavy ignored helper test를 건드렸다면 --ignored --test-threads=1로 명시 실행하십시오.


공유 함수와 실행 정책

여러 model family가 쓰는 공유 함수를 수정할 때는 AGENTS.md에 설명된 usage comment를 업데이트하십시오. 특히 다음 파일이 중요합니다.

  • src/lib/mlxcel-core/src/layers.rs
  • src/lib/mlxcel-core/src/utils.rs

이 주석은 향후 공유 함수 변경의 retest list 역할을 합니다.

CLI, 서버, 향후 frontend에서 동일해야 하는 entry-point policy는 공유 execution layer에 두십시오.

  • src/execution/runtime.rs
  • 환경 기반 device selection (MLXCEL_DEVICE)
  • GPU wired-memory limit setup
  • src/execution/sampling.rs
  • resolved request defaults에서 중앙화된 SamplingConfig assembly
  • greedy / non-greedy branching 공유

새 frontend 또는 request type에 다른 default가 필요하면 edge에서 default를 resolve하고 최종 conversion은 src/execution/에 유지하십시오.

CLI 전용 prompt formatting과 terminal output 동작은 shared loading 또는 server module로 옮기지 말고 src/commands/generate.rs에 두십시오.

서버 전용 boot behavior는 src/server/mod.rs를 키우지 말고 src/server/startup.rs에 유지하십시오.

  • API key / chat-template resolution precedence
  • startup-time normalization of CLI-compatible flags
  • warmup behavior
  • Unix-socket vs TCP binding

공유 server type도 focused module에 유지하십시오.

  • src/server/config.rs: request/default configuration structs
  • src/server/state.rs: AppState와 metrics containers
  • src/server/model_provider.rs: public request/response channel API
  • src/server/model_worker.rs: long-lived worker thread, VLM request prep, decode state

둘 이상의 endpoint가 같은 동작을 필요로 하면 server edge adapter를 route file 밖으로 분리하십시오.

  • src/server/chat_request.rs: OpenAI chat message flattening과 prompt 폴백 assembly
  • src/server/request_options.rs: request-default merging into ServerGenerateOptions
  • src/server/media.rs: data: / file:// image-source parsing
  • src/server/streaming.rs: shared SSE channel과 [DONE] emission helpers

Dry-run workflow 검증

코딩 전에 이 dry run으로 현재 구조가 새 model support 추가 시 ambiguity를 줄이는지 검증하십시오.

Dry Run A: 표준 텍스트 모델

새 텍스트 모델이 기존 config-backed loading path를 따른다고 가정합니다.

필수 수정 지점:

  1. src/models/<family>.rs
  2. src/models/mod.rs
  3. src/models/detection.rs
  4. src/model_metadata.rs: converged registration surface와 static_model_descriptor() / model_load_policy()
  5. src/loading/config_backed.rs: shared config-backed loading behavior 자체가 바뀌는 경우에만
  6. src/loaded_model.rs
  7. src/loaded_model_capabilities.rs: family가 multimodal capability exposure를 바꾸는 경우에만
  8. src/models/detection_tests.rs, src/models/sanitize_tests.rs, src/loading/tests.rs 근처 테스트

하지 말아야 할 것:

  • load_model() 안에 새 one-off construction branch 추가
  • user-visible behavior 변화 없이 CLI 또는 server 직접 수정
  • 기존 capability로 충분한데 LoadedModel에 family-specific getter 추가

이 방식은 route selection을 중앙화하고 adapter support를 하나의 policy surface에 선언하며 코딩 전에 예상 edit list를 리뷰 가능한 크기로 유지합니다.

Dry Run B: 새 VLM family

새 VLM family가 자체 token default와 weight-key remapping을 필요로 한다고 가정합니다.

필수 수정 지점:

  1. src/models/ 또는 src/vision/ 아래 text model 및/또는 VLM wrapper
  2. src/models/mod.rs
  3. src/models/detection.rs
  4. src/loading/vlm_<family>.rs
  5. src/loading/vlm.rs
  6. src/model_metadata.rs: static_model_descriptor() / model_load_policy()
  7. src/loaded_model.rs
  8. src/loaded_model_capabilities.rs: VlmRuntimeRef
  9. src/multimodal/: prompt/runtime preparation이 실제로 새로울 때만
  10. src/loading/vlm_<family>_tests.rs 근처 테스트와 새 multimodal helper test file

하지 말아야 할 것:

  • CLI 또는 server request path에 concrete model-type check 추가
  • family-specific loading logic을 src/loading/mod.rs에 직접 추가
  • CLI와 server에 prompt-rewrite logic 중복

리뷰 체크리스트

새 모델 또는 VLM family PR을 열기 전에 확인하십시오.

  1. loading policy가 src/model_metadata.rs를 통해 업데이트됨
  2. LoadedModel wiring이 새 one-off family getter가 아니라 src/loaded_model.rs와 src/loaded_model_capabilities.rs 안에 머무름
  3. CLI와 server가 concrete model type 대신 shared helper에 의존함
  4. 새 policy 또는 normalization logic을 unit test가 커버함
  5. local model이 있으면 최소 하나의 smoke test가 있음
  6. loader와 모델이 println!이 아니라 tracing으로 로그를 남김
  7. attention이 KVCache::attend / cache::attend_batched를 거치고, cache를 직접 소유하는 family는 trim_state를 구현하거나 supports_padded_prefill() == false를 반환함
  8. 배치 디코드를 의도적으로 결정함: 패밀리가 BatchedDecodeKind::Native를 보고하거나 per-row fallback이 허용 가능하고, 시퀀스별 상태를 SequenceId로 구분해 보관함

커밋 전략

하나의 control-plane surface를 격리하는 작은 체크포인트를 선호하십시오.

  • model detection
  • loader normalization
  • prompt preparation
  • runtime initialization

이렇게 하면 회귀를 찾기 쉽고 향후 model addition과 비교하기 쉬워집니다.