새 모델 추가하기¶
이 가이드는 mlxcel에 새 모델 아키텍처를 추가하는 방법을 설명합니다. docs/MODEL_ADDITION_GUIDE.md(정식 레퍼런스)를 기반으로 하며 실제로 수정해야 하는 제어 지점을 구체적으로 설명합니다.
목차¶
- 목표
- 시작하기 전에
- 텍스트 모델 체크리스트
- VLM 체크리스트
- 새 family module을 만들 시점
- 모델 구현 패턴
- SafeTensors에서 가중치 로딩
- 모델 타입 감지
- LoadedModel 디스패치
- 자주 발생하는 문제
- 새 모델 테스트하기
- 공유 함수와 실행 정책
- Dry-run workflow 검증
- 리뷰 체크리스트
- 커밋 전략
목표¶
- 새 모델 추가 작업을 예측 가능하게 유지합니다.
- 새 one-off branch를 추가하기보다 기존 control-plane helper를 재사용합니다.
- 회귀가 나기 쉬운 integration point 옆에 테스트를 함께 추가합니다.
시작하기 전에¶
- 레퍼런스 구현을 확인합니다:
- 텍스트 모델: ml-explore/mlx-lm의
mlx_lm/models/ -
아키텍처를 분류합니다:
- 완전히 새로운 모델 계열 →
src/models/아래에 새 파일 생성 - 기존 계열의 형식 별칭 →
detection.rs의 감지 로직 확장 -
기존 텍스트 모델을 감싼 VLM 래퍼 →
src/vision/아래에 비전 인코더/커넥터를,src/loading/vlm_<family>.rs에 로더 추가 -
기존 로더 헬퍼가 이미 맞는지 확인합니다:
src/model_metadata.rs(수렴된 등록 테이블)부터 시작하십시오. 표준 텍스트 모델은src/loading/config_backed.rs가 동일한 진실의 원천을 사용하므로, 먼저 해당 등록 테이블을 확장해야 합니다. -
공유 실행 정책 영향을 확인합니다:
- 장치/환경 동작은
src/execution/runtime.rs - 샘플링 기본값은
src/execution/sampling.rs
텍스트 모델 체크리스트¶
다음 단계를 순서대로 따르십시오:
-
구현 파일을 추가합니다.
src/models/<family>.rs위치에. -
모듈을 등록하고 re-export합니다.
src/models/mod.rs에: -
ModelType변형을 추가합니다.src/models/mod.rs에: -
패턴에 맞으면 공유 헬퍼를 사용하십시오:get_model_type()을 확장합니다.src/models/detection.rs에: -
LoadedModel변형을 추가합니다.src/loaded_model.rs에. 새로운 반복적인 매치 암을 추가하는 대신delegate_language_model!매크로를 확장하는 것을 선호합니다. -
로딩을 연결합니다.
src/loading/mod.rs에. 표준 설정 기반 모델의 경우: src/model_metadata.rs를 종류, 어댑터 지원, 경로 선택에 맞게 업데이트합니다.-
모델이 표준 텍스트 모델 경로를 따른다면,
src/loading/config_backed.rs에 병렬 항목 목록을 추가하는 대신 공유 등록 표면을 확장합니다. -
LoRA/어댑터 지원 (해당하는 경우):
src/loading/mod.rs의load_model_from_weights()를 확인합니다. 비표준 어댑터 경로는src/loading/special.rs에 넣습니다.
VLM 체크리스트¶
- 비전 컴포넌트를 구현한다 (또는 기존 것을 재사용):
- 비전 인코더:
src/vision/encoders/ - 커넥터 (인코더 출력을 텍스트 모델 입력으로 연결):
src/vision/connectors/ -
프로세서 (이미지 전처리):
src/vision/processors/ -
VLM
ModelType감지를 추가합니다.src/models/detection.rs에. 텍스트 전용 및 VLM 변형이 모두 있는 계열의 경우detect_text_or_vlm()을 사용합니다. -
로더 항목을 추가합니다.
src/loading/vlm_<family>.rs에 (또는 가장 가까운 동료 계열 모듈에).src/loading/mod.rs의try_load_vlm_model_from_dir()에 디렉터리 진입점을 등록합니다. -
LoadedModel능력 헬퍼를 추가합니다.src/loaded_model_capabilities.rs에. 계열별 getter를 추가하는 대신VlmRuntimeRef를 확장하는 것을 선호합니다. -
가능한 경우 프롬프트 헬퍼를 재사용합니다:
- Qwen-VL 토큰 삽입:
src/multimodal/qwen_vl.rs - 일반 이미지 토큰 블록 확장:
src/multimodal/vlm_prompt.rs - 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() 헬퍼를 사용합니다:
이 헬퍼는 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 트레잇 메서드에 대한 디스패치 코드를 생성합니다:
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 또는
ModelTypevariant 누락 src/models/detection.rsget_model_type()alias 누락vision_config가 있을 때 text/VLM 오분류src/loading/mod.rsload_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 누락
VlmRuntimeRefcapability 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-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]로 표시된):
더 넓은 테스트 기대치¶
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.rssrc/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에서 중앙화된
SamplingConfigassembly - 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 structssrc/server/state.rs:AppState와 metrics containerssrc/server/model_provider.rs: public request/response channel APIsrc/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 폴백 assemblysrc/server/request_options.rs: request-default merging intoServerGenerateOptionssrc/server/media.rs:data:/file://image-source parsingsrc/server/streaming.rs: shared SSE channel과[DONE]emission helpers
Dry-run workflow 검증¶
코딩 전에 이 dry run으로 현재 구조가 새 model support 추가 시 ambiguity를 줄이는지 검증하십시오.
Dry Run A: 표준 텍스트 모델¶
새 텍스트 모델이 기존 config-backed loading path를 따른다고 가정합니다.
필수 수정 지점:
src/models/<family>.rssrc/models/mod.rssrc/models/detection.rssrc/model_metadata.rs: converged registration surface와static_model_descriptor()/model_load_policy()src/loading/config_backed.rs: shared config-backed loading behavior 자체가 바뀌는 경우에만src/loaded_model.rssrc/loaded_model_capabilities.rs: family가 multimodal capability exposure를 바꾸는 경우에만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을 필요로 한다고 가정합니다.
필수 수정 지점:
src/models/또는src/vision/아래 text model 및/또는 VLM wrappersrc/models/mod.rssrc/models/detection.rssrc/loading/vlm_<family>.rssrc/loading/vlm.rssrc/model_metadata.rs:static_model_descriptor()/model_load_policy()src/loaded_model.rssrc/loaded_model_capabilities.rs:VlmRuntimeRefsrc/multimodal/: prompt/runtime preparation이 실제로 새로울 때만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을 열기 전에 확인하십시오.
- loading policy가
src/model_metadata.rs를 통해 업데이트됨 LoadedModelwiring이 새 one-off family getter가 아니라src/loaded_model.rs와src/loaded_model_capabilities.rs안에 머무름- CLI와 server가 concrete model type 대신 shared helper에 의존함
- 새 policy 또는 normalization logic을 unit test가 커버함
- local model이 있으면 최소 하나의 smoke test가 있음
- loader와 모델이
println!이 아니라tracing으로 로그를 남김 - attention이
KVCache::attend/cache::attend_batched를 거치고, cache를 직접 소유하는 family는trim_state를 구현하거나supports_padded_prefill() == false를 반환함 - 배치 디코드를 의도적으로 결정함: 패밀리가
BatchedDecodeKind::Native를 보고하거나 per-row fallback이 허용 가능하고, 시퀀스별 상태를SequenceId로 구분해 보관함
커밋 전략¶
하나의 control-plane surface를 격리하는 작은 체크포인트를 선호하십시오.
- model detection
- loader normalization
- prompt preparation
- runtime initialization
이렇게 하면 회귀를 찾기 쉽고 향후 model addition과 비교하기 쉬워집니다.