Files
kebab/docs/superpowers/specs/2026-06-01-embed-candle-track-spec.md
altair823 6ec4e6809f fix(embed-candle): address round-1 review
- commit track-spec + meta-spec/plan into branch (HIGH: dangling `amends:` ref)
- inline parity evidence (cosine 1.0, max_abs_diff 2.01e-7) into HOTFIXES +
  release notes; drop refs to deleted IMPL_REPORT/SPIKE_REPORT (MEDIUM)
- model guard: reject non-e5-large `model` before the 2GB download so
  model_id() can't mislabel vectors (MEDIUM) + unit test
- parity test now covers BOTH query: and passage: prefixes (MEDIUM)
- guard encodings.first() index; document zero-attention/pooling invariant;
  clarify embed_batch prefixing doc (LOW)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 16:54:20 +00:00

5.9 KiB
Raw Blame History

Track 1 Spec — candle e5-large 임베딩 provider (NUMA-안전)

  • 날짜: 2026-06-01
  • 우산: meta-spec / meta-plan
  • 선행: Phase 0 스파이크 PASS+독립검증 (cosine 1.000000, 스레드 캡 가능, latency ~4×). 커밋 76841af.
  • 브랜치: feat/embed-candle

1. 목표

fastembed(onnxruntime) 의 "intra-op 스레드 48 하드코딩 → NUMA 힙 손상" 을 회피하기 위해, 동일 모델 multilingual-e5-largecandle(순수 Rust) 로 돌리는 임베딩 provider 를 추가한다. opt-in, 품질 중립, NUMA 스레드 캡 가능.

2. 확정 결정 (사용자 승인 2026-06-01)

  • D-reindex: embedding_version 유지(재색인 0) 를 목표. 구현 중 candle vs onnxruntime 벡터의 차원별 max 절대오차를 측정해 사실상 동일(예: max abs diff < 1e-5)함을 확인하고, 골든 스위트로 회귀 0 을 실측해 확정. 유의미한 차이가 나오면만 version bump + 재색인.
  • D-default: 글로벌 default provider 는 onnxruntime 유지, candle 은 opt-in (models.embedding.provider = "candle").
  • 조기 종료: candle 이 골든 baseline 충족 시 ollama/A2 트랙 생략 (A1 stopgap 문서만 별도).

3. 아키텍처

  • 신규 crate kebab-embed-candlekebab_core::Embedder 구현. candle 의 큰 의존성 트리를 이 crate 에 격리.
    • 허용 deps: candle-core/candle-nn/candle-transformers (0.10.x), tokenizers, hf-hub, kebab-core, kebab-config, anyhow, tracing. 다른 kebab-* 의존 금지(core/config 외) — design §8 경계.
  • 주입 분기: kebab-app/src/app.rsembedder() (현 :829-837, FastembedEmbedder::new 무조건 생성) 를 config.models.embedding.provider 로 분기:
    • "fastembed" | "onnx" | (빈값/기존) → FastembedEmbedder (default, 기존 동작 유지).
    • "candle"CandleEmbedder.
    • 알 수 없는 값 → 명확한 에러.
  • facade 규칙 준수: UI crate 는 kebab-app 만. kebab-appkebab-embed-candle 의존 추가.

4. CandleEmbedder 동작 (스파이크에서 검증된 파이프라인)

  • 모델: intfloat/multilingual-e5-largemodel.safetensors + config.json + tokenizer.jsonhf-hub 으로 {model_dir}/candle/ (config storage.model_dir) 아래에 캐시.
  • candle_transformers::models::xlm_roberta::{Config, XLMRobertaModel} 로 로드 (CPU Device::Cpu).
  • embed(): e5 프리픽스(query: /passage: , EmbeddingInput kind 기준 — kebab-embed-localprefix_input 규약과 동일) → 토크나이즈(max_len 512, batch-longest 패딩, special tokens) → forward → attention-mask 가중 mean poolingL2 정규화.
  • dimensions() = 1024, model_id/model_version = config 값(기존과 동일 식별자 유지).
  • 스레드 캡: config 신규 필드 models.embedding.num_threads(u32, 0=auto) + env KEBAB_EMBED_THREADS. CandleEmbedder::new 에서 rayon::ThreadPoolBuilder::new().num_threads(n).build_global() 1회 적용(이미 초기화 시 무시). 0/auto 면 미설정(rayon 기본). NUMA 노드 바인딩은 numactl(A1) 과 조합 — 문서화.
  • Mutex<XLMRobertaModel> 또는 forward 가 &self 면 불필요 — candle forward 는 &self 가능, 단 내부 가변 없으면 Send+Sync 보장 확인.

5. config 변경

  • EmbeddingModelCfgnum_threads: u32(default 0) 추가. env KEBAB_EMBED_THREADS.
  • provider 허용값 문서화: fastembed(default)/candle.
  • default toml + Config::default() 갱신, 기존 테스트 영향 확인.

6. 버전/캐스케이드

  • D-reindex 에 따라 embedding_version 유지 (벡터 동일). cascade(design §9) 트리거 안 함 — 기존 색인 재사용. (max abs diff 확인 실패 시에만 bump.)
  • wire schema 변경 없음.

7. 테스트 (산출물)

  • 단위(kebab-embed-candle): dimensions()==1024; embed() 출력 L2≈1; 빈 입력 빈 출력; 프리픽스 적용 확인.
  • 패리티 테스트(#[ignore], 모델 2GB+네트워크 필요): candle vs FastembedEmbedder 동일 문장 cosine ≥ 0.9999 + max abs diff 보고. CI 기본 제외, 수동/도그푸딩에서 실행.
  • 통합(kebab-cli 또는 kebab-app): provider="candle" 로 소량 fixture ingest → 청크/임베딩 카운트 > 0, 검색 1건 성공. (모델 필요 → #[ignore] 또는 feature.)
  • 스레드 캡: num_threads=4 설정 시 rayon::current_num_threads()==4 확인.
  • 회귀: 기존 fastembed 경로 default 동작 불변(provider 미지정 시).
  • clippy -D warnings, 빌드 직렬 -j 4.

8. 품질 게이트 (머지 전)

  • kebab-eval 골든 스위트(/build/dogfood/golden_queries.yaml) 를 provider=candle 로 실행 → MRR/hit@k ≥ 현 baseline (회귀 0). feedback_search_quality_dogfood
  • 패러프레이즈 robustness(#195/#196) 스폿 확인.

9. 문서/릴리스 (머지 시 동일 PR)

  • README: Configuration 에 provider=candle + num_threads/KEBAB_EMBED_THREADS 추가. SMOKE config 예시 동기화. feedback_readme_sync_rule
  • ARCHITECTURE: crate 그래프 + 디렉터리에 kebab-embed-candle 추가.
  • HANDOFF: 머지 후 한 줄(임베딩 백엔드 다변화).
  • HOTFIXES: 본 날짜 dated entry (NUMA double-free 진단 + candle provider 도입 + 스파이크 패리티 증거).
  • 버전 bump: 신규 config surface(provider=candle, num_threads) = pre-1.0 minor bump (0.21.1 → 0.22.0), release notes.

10. 범위 밖 / 후속

  • candle crate feature-gate 로 빌드 비용 격리 (후속).
  • NUMA 노드 자동 바인딩(현재는 numactl 외부 조합).
  • ollama/A2/A1 트랙 (candle 게이트 통과 시 생략).

11. 잔여 게이트 (사용자 실행, Claude 불가)

  • 그 듀얼소켓 NUMA 서버에서 provider=candle 로 5150-doc ingest double-free 없이 EXIT=0 완주. PR 머지 전/후 검증 예약. (meta-spec §4.3)