Skip to content

model: EmbeddingGemma and EmbeddingGemma 2 on a side seat #190

Closed

Description

@juliankang4

말씀해주신 대전제, 그러니까 하나에 붙인다는 그 방향에 맞춰서 준비한 계획입니다.

I'm starting work on embedding support for both EmbeddingGemma models, on DGX Spark. This issue holds the plan and tracks the PRs. #59 settled the shape for EmbeddingGemma 2: a side seat beside the chat model, with the plan taking its bytes out of the chat model's expert budget. The plan below follows that and adds EmbeddingGemma (v1, 300M) on the same path, since the two share most of the encoder.

Results from other hardware are welcome, and so is help on the parts this plan leaves out (audio and video, section 6).

1. The models

EmbeddingGemma 2 EmbeddingGemma (v1)
Weights google/embeddinggemma-2, Apache-2.0 google/embeddinggemma-300m, Gemma license, gated
GGUF I start from ggml-org/embeddinggemma-2-GGUF Q8_0 (309,855,456 B), mmproj BF16 (982,074,784 B) ggml-org/embeddinggemma-300M-GGUF Q8_0 (333,590,944 B)
general.architecture gemma-embedding2 gemma-embedding
Context 262144 in the GGUF, 8,192 on the model card 2048
llama.cpp ggml-org/llama.cpp#30054 (merged 2026-10-06), src/models/gemma-embedding2.cpp ggml-org/llama.cpp#15798 (merged 2025-09-04), src/models/gemma-embedding.cpp

2. What is new about them

Read from the GGUF headers above and the two llama.cpp files.

  • Non-causal attention. Both run bidirectional with a symmetric sliding window, and every sixth layer (5, 11, 17, 23) is global. v2's header says so (attention.causal = false, sliding_window_pattern); v1's carries neither key because llama.cpp sets both in code, so the v1 reader takes them from the architecture name. The window is 512 in v1's header and 1024 in v2's, while v2's Hugging Face config.json says 512. Whether llama.cpp reads that number as the full width or one side is open for both models, and the refsets (E3, E8) settle it.
  • Head sizes. v1 uses head 256 on every layer, with 3 query heads and 1 KV head. v2 uses head 256 on its 20 sliding layers (4 query heads, 2 KV heads) and head 512 on its 4 global layers (1 KV head). No attached model has a grouped-query head of 512 with separate K and V planes; the widest common GQA prefill entry is head 256 (flash_gqa). V4.1's attention walks 512-wide rows, but one latent row is both key and value there, so v2's global layers need their own entries, and so does v1's group of 3 (the head-256 entries take groups of 8, multiples of 4 and other even groups).
  • Feed-forward. GELU-gated (LLM_FFN_GELU, LLM_FFN_PAR), width 1152 (v1) and 2048 (v2).
  • v2 per-layer input. per_layer_model_proj (BF16, also in the Q8_0 file), and per layer inp_gate, proj, post_norm and layer_output_scale. A final output.weight projects 512 to 768.
  • v1 dense head. After pooling, dense_2 (768 to 3072) and dense_3 (3072 to 768).
  • Pooling. Mean (pooling_type = 1), then normalization. The model cards give MRL widths 768, 512, 256 and 128; the headers carry no width list.
  • Tokenizers. v1 is tokenizer.ggml.model = llama (SentencePiece). v2 is gemma4. bloomery-tokenizer takes gpt2 only today, so both are new.
  • v2 images. The mmproj is clip with a gemma4v vision projector (16 layers, patch 16, image 224, width 768, projection to 512). The same file also carries a gemma4a audio encoder.

The closest attached model is Clef-Flash: its decide seat is the one seat that answers from hidden rows instead of tokens, it takes images through the vision seat, and its Qwen3.5-9B backbone runs head-256 GQA through the common prefill entries.

The common owners from docs/contrib/new-model.md:

Owner This model
Placement plan Does not attach: the encoder is dense with no experts, so its bytes enter the chat plan as a named reserve (section 3)
Kernel table Already attached for the Q8_0 and F32 dense sites. dense has no BF16 row, so per_layer_model_proj loads as Bf16AsF32 and calls the f32 gemv directly, as Qwen3.8's indexer projections do
Card expert leg, Residency, Expert tier Do not attach: no experts
Prompt walk, Resident slots Do not attach: one non-causal pass over the whole input, with no cache kept between requests
Serve seat Attaches as a side seat beside the chat model's seat
Timing Already attached, through the serve seat

3. Proposed shape

  1. bloomery-serve keeps one chat model as its seat and takes the embedding model as a side seat in the same process, given by --embd-model PATH or --embd-hf repo:quant, plus --embd-mmproj for images. These spellings are a proposal; see section 7.
  2. The side seat follows the image encoder's seat rule as it is: the smallest card outside the plan, which costs the chat plan nothing, or else a named reserve on the stage card, whose bytes the chat plan takes out of the expert budget. image_plan::reserve_on fixes the name "image encoder" today; it would take the name as an argument, so both seats enter their own reserve through it and reserve_in reads each by name. v2's image tower sits inside the embedding reserve.
  3. The embedding model runs on its own thread and stream. The decoder's engine thread does not change.
  4. The model side runs the encoder through the hidden-row form of the media call (MediaBody::prefill_hidden_media, which the decide seat reaches through Hidden), and the encoder body refuses the token form by name. It also owns pooling and the learned projections around it (v2's output, v1's dense head) and returns one pooled vector per input. A new serve::embed module owns the rest: /v1/embeddings (OpenAI shape) and /embedding (llama-server shape), normalization ported from llama-server's common_embd_normalize, the MRL cut with renormalization (dimensions), and base64 output.
  5. Errors: 501 when no side seat is loaded, 400 for a bad dimensions, 400 exceed_context_size_error for an input past the context, as bloomery's completions already answer, and -m refuses an embedding file by name. llama-server checks its physical batch (-ub) first and answers 500 past it, so it gives that 400 only when -ub covers the context. The side seat runs an input in one call with no batch limit of its own, and docs/serving.md says so.

4. Proposed PR sequence

E1 to E4 do not depend on each other.

The oracle is mainline because ik_llama.cpp has neither architecture; the GLM-5.3 image sets already use mainline the same way.

  • A second reference: pinned sentence-transformers output from the original EmbeddingGemma 2 weights, with an independent ceiling, checked on DGX Spark and RTX 5090. Merged as #413 (1f262bd4).

The serve gate runs beside Qwen3-30B-A3B-Instruct-2507 Q4_K_M. The plain run's BLOOMERY_CARD_BUDGET leaves some experts on the host, and the side seat's run adds the embedding reserve to it. The gate checks that:

  • /v1/embeddings vectors match the llama-server vectors inside a band (cosine and largest absolute difference);
  • both plans name the same card_experts and host_experts, and the chat model's greedy tokens are the same bit for bit, with and without the side seat and while embedding requests run at the same time;
  • at the plain run's budget, the side seat's plan puts fewer experts on the card;
  • the plan record names the embedding reserve and its bytes, and the cards term of Plan::pool_need counts it;
  • the refusals in section 3 hold.

Every clause is broken on purpose first to see it fail.

5. Owner and test machine

I'm taking this on myself. The test machine is DGX Spark (GB10, cc 12.1, 128 GB unified memory). Kernel gates, the e2e and serve gates and the mainline oracle all run there. Pure tests run on macOS and Linux.

6. Left out of this plan

7. Open questions

  1. A new kernel entry's cc 8.6 ptx-shapes row (the JIT column) needs a cc 8.6 card. The PRs fill the ptxas column and the cc 12.1 tables. Must the cc 8.6 JIT pin be filled before such a PR merges, or can it follow?
  2. docs/research/tierengine.md names no flags for a second model in one server. Is there a planned spelling the --embd-* flags should follow?
  3. For v2's context (section 1; model: EmbeddingGemma 2 (gemma-embedding2, 0.74B dense) #59 asked this too), should the side seat enforce the model card's 8,192 tokens, or allow an explicit override up to the GGUF's 262144?
  4. Should the reader describe an encoder with an EncoderSpec beside ModelSpec (an encoder has pooling and no chat surface), or with an encoder variant of ModelSpec? E1 follows the answer.

Done

AI help

AI 코딩 에이전트가 자료를 조사해 계획과 이슈 초안을 썼습니다. 계획과 이슈 초안 리뷰는 제가 직접 했습니다.

Activity

  1. added
    modelThe CPU engine and model files: crates/model, models, hf, gguf, tokenizer, jinja
    on Oct 14, 2026
  2. changed the title [-]model: EmbeddingGemma and EmbeddingGemma 2 (gemma-embedding, gemma-embedding2) on a side seat[/-] [+]model: EmbeddingGemma and EmbeddingGemma 2 on a side seat[/+] on Oct 14, 2026
  3. midagedev commented on Oct 15, 2026

    @midagedev
    Owner

    계획 잘 봤습니다. #59에서 말한 방향(한 서버에 여러 모델, 임베딩은 채팅 모델 옆 side seat, 바이트는 expert 예산에서 차감)과 그대로 맞습니다. 이미지 인코더 reserve 규칙을 재사용하는 것, 채팅 토큰이 side seat 유무와 상관없이 비트 단위로 같은지 게이트로 보는 것 모두 좋습니다.

    열린 질문에 대한 답입니다.

    1. cc 8.6 JIT 핀은 머지 뒤에 채워도 됩니다. 저희 3090/A6000에서 채울 수 있으니 PR에 표시만 남겨 주세요.
    2. 두 번째 모델 플래그 이름은 정해 둔 게 없습니다. 제안하신 --embd-model, --embd-hf, --embd-mmproj로 가 주세요.
    3. v2 컨텍스트는 기본을 모델 카드의 8,192로 하고, 명시적으로 주면 GGUF의 262,144까지 허용해 주세요. ctx를 명시하면 그대로 따르고 기본값만 보수적으로 잡는 지금 방침과 같습니다.
    4. ModelSpec의 변형보다 EncoderSpec을 따로 두는 쪽이 좋겠습니다. 채팅 표면이 없고 pooling이 있어서 분리하는 게 깔끔합니다.

    순서는 v2(E1~E7)를 먼저 끝내고, v1(E8)은 수요를 보고 나중에 해도 될 것 같습니다. v1은 gated 라이선스에 컨텍스트 2048이고, 그룹 3 전용 head-256 entry가 추가로 필요해서 들이는 비용에 비해 얻는 게 적습니다. 그리고 저희는 지금 0.3.3 마무리 중이라, 이 작업은 0.3.3 이후 릴리즈에 들어가는 것으로 보겠습니다.


    이 답글은 bloomery AI 어시스턴트가 작성했고, 메인테이너가 확인한 뒤 올렸습니다.

  4. midagedev commented on Oct 15, 2026

    @midagedev
    Owner

    덧붙여, Clef(SystemOne) 자리를 붙였던 경험에 비춰 보면 보강할 만한 점이 네 가지 있습니다. Clef도 토큰 대신 hidden row로 답하는 모델이라 구조가 많이 닮았습니다.

    1. 두 번째 기준 출력. Clef는 llama.cpp가 아니라 공식 Python 출력과 맞췄습니다. 이번 계획의 기준은 mainline llama.cpp인데, 인용하신 llama.cpp#30416과 #30461이 바로 llama.cpp 임베딩 버그를 고친 PR입니다. HF 공식 sentence-transformers 출력도 기준으로 함께 두면 좋겠습니다. 슬라이딩 윈도우가 전체 폭인지 한쪽 폭인지 하는 열린 질문도 이걸로 바로 정해집니다.
    2. GPU 없이 테스트되는 순수 crate. Clef는 요청 읽기, 인코딩, 헤드 계산을 bloomery-decision이라는 순수 crate로 빼서 Mac에서도 Python과 바이트 단위로 대조했습니다. 임베딩도 pooling, normalize, MRL 자르기와 재정규화, 요청 파싱, base64 출력을 순수 crate로 떼면 GPU 없이 llama-server와 맞춰 볼 수 있습니다.
    3. 기존 서버 부분 재사용. Clef 때 /v1/models를 공통 모듈(serve::models)로 모았으니, 임베딩 자리가 그 목록에 두 번째 모델로 나오면 됩니다. 잘못된 파일을 이름 붙여 거부하는 방식과 이미지 501 응답도 Clef가 정한 그대로 쓰면 됩니다.
    4. 모델 카드의 작업 프리픽스. 계획에서 빼 두신 query/document 프리픽스는, Clef가 모델 카드에서 head 저장소를 읽었던 것처럼 config_sentence_transformers.json의 prompts를 읽어서 prompt_name으로 고르게 하면 llama.cpp보다 나은 지점이 됩니다. 나중 PR이어도 괜찮습니다.

    (수정) toktape는 토큰 출력만 녹화해서 임베딩 응답은 담을 수 없습니다. 릴리즈 전 실제 서빙 확인은 llama-server 벡터와의 비교 결과(cosine, 최대 절대 차이)를 남기는 것으로 하고, toktape는 side seat를 붙였을 때 채팅 모델 녹화가 그대로인지 보는 용도로만 쓰면 되겠습니다.


    이 답글은 bloomery AI 어시스턴트가 작성했고, 메인테이너가 확인한 뒤 올렸습니다.

  5. juliankang4 commented on Oct 15, 2026

    @juliankang4
    CollaboratorAuthor

    Thanks, this settles the open questions. The stack already matches answers 3 and 4: the encoder seat has its own EncoderSpec, separate from ModelSpec, and the default context is the card's 8,192 (CARD_CTX), with the GGUF's 262,144 reachable when it is set explicitly. For answer 1, the cc 8.6 rows will carry a note in each PR that they came from ptxas and wait for a reading on a 3090 or A6000.

    On the four suggestions from Clef: each one widens the plan the open PRs were built and reviewed against. I'd like to keep the current stack (E1 to E7) at its present scope, get it merged, and then take them as follow-up PRs on top:

    1. The HF sentence-transformers output as a second reference, including the sliding-window width.
    2. Pooling, normalize, MRL truncation and renormalize, request parsing and base64 output moved into a pure crate that runs without a GPU.
    3. The embedding seat listed through serve::models as the second model in /v1/models, with the named-file refusal and the image 501 shared with it.
    4. The query and document prefixes from config_sentence_transformers.json, chosen by prompt_name.

    Thanks for the toktape correction. The serve check in the image PR compares against llama-server vectors (cosine and max abs diff), and toktape is used only to show the chat model's recording is unchanged with the embedding seat loaded.

  6. midagedev commented on Oct 15, 2026

    @midagedev
    Owner

    참고용으로 EmbeddingGemma 2 기준 녹화를 하나 올려둡니다.

    EmbeddingGemma 2 검색 데모 테이프

    • llama-server mainline, EmbeddingGemma 2 Q8_0, RTX 3090
    • 문서 20개, 질문 6개(한국어·영어 섞음), query/document 프롬프트는 모델 카드 그대로 사용
    • recall@1 6/6, 요청 p50 6.6 ms
    • MRL 768/256/128 차원별 순위 비교, 마지막에 클러스터 보기

    bloomery 쪽 side seat가 올라오면 같은 입력으로 녹화해서 이 테이프와 비교하는 기준으로 쓰면 될 것 같습니다. 녹화는 toktape embed --api llama로 할 수 있습니다.


    이 댓글은 bloomery AI 어시스턴트가 작성했습니다.

  7. juliankang4 commented on Oct 15, 2026

    @juliankang4
    CollaboratorAuthor

    Thanks, that replaces what I wrote above about toktape. The image PR's serve check will record the side seat with toktape embed on the same 20 documents and 6 questions, sending the same input text as this tape, and compare the two: recall@1 and the ranks at MRL 768, 256 and 128. The llama-server vector comparison (cosine and max abs diff) stays beside it. Choosing the prompt by prompt_name in the engine stays with the follow-up for item 4.

  8. juliankang4 commented on Oct 15, 2026

    @juliankang4
    CollaboratorAuthor

    Here's the side seat recorded with toktape embed and published: https://tape.midagedev.com/r/jkq8iipaxrx748vrze4w

    It's the same built-in set as the reference llama-server tape (20 documents, 6 questions, 52 requests with and without the prompts), sent to the EmbeddingGemma 2 seat on an RTX 5090 through POST /embedding.

    reference tape side seat
    engine llama-server, /v1/embeddings bloomery, /embedding
    card RTX 3090 RTX 5090
    recall@1 6/6 6/6
    recall@1 without prompts 6/6 6/6
    request p50 6.60 ms 4.40 ms
    request max 60.86 ms 13.38 ms

    The two runs used different cards and engines, so the latency rows are not a speed comparison.

    toktape compare against the reference tape matches all 52 inputs, with the lowest cosine 0.998881 on q-starter. For MRL I cut the stored vectors of both tapes to 768, 256 and 128 dimensions and ranked the documents by cosine. The right document is first for all six questions at every width on both tapes. Some lower places differ.

    One thing about the tape's header: the MODEL line reads Qwen3.6-35B-A3B-Q4_K_M.gguf. That's the chat model the server was started with; the seat runs beside it. The REQUEST line shows the model that answered, embeddinggemma-2-Q8_0.gguf. toktape takes MODEL from /props model_path, and --model only sets the request field, so I couldn't label it from the client side. Could toktape embed use --model (or the model named in the response) for that line? Then a side seat tape would name the embedding model at the top.

  9. midagedev commented on Oct 15, 2026

    @midagedev
    Owner

    MODEL 줄 확인했어요. toktape embed가 MODEL을 /props의 model_path에서만 가져와서, 옆에 붙은 seat가 답해도 서버를 띄운 채팅 모델이 찍혔어요.

    toktape #6에서 고쳤어요. 이제 응답의 model을 먼저 쓰고, 없으면 --model, 그것도 없으면 /v1/models에 여러 개가 있을 때 그중 embedding 모델을 써요. toktape main으로 다시 빌드해서 지금처럼 --model embeddinggemma-2-Q8_0.gguf를 주고 녹화하면 MODEL 줄에 임베딩 모델이 나와요.

    (bloomery AI 어시스턴트가 작성)

  10. midagedev commented on Oct 15, 2026

    @midagedev
    Owner

    E6 엔드포인트 관련해서 계획에 세 가지만 더 넣으면 좋겠습니다. bloomery는 API를 OpenAI와 최대한 호환되게 맞추는 방향이라서요.

    1. /v1/embeddings 응답의 model에는 채팅 모델이 아니라 임베딩 seat의 이름(alias나 파일명)을 넣었으면 합니다. 요청의 model이 채팅 모델을 가리키면 조용히 임베딩하지 말고 OpenAI 형식의 에러(400/404)로 돌려주면 좋겠습니다.
    2. /v1/models의 data에 임베딩 seat를 별도 항목으로 추가해 주세요. 지금 models::listing은 id를 하나만 받으니 seat 목록을 받도록 바꿔야 할 것 같습니다. meta에 차원과 컨텍스트 길이가 있으면 클라이언트가 두 모델을 구분하기 쉽습니다.
    3. (선택) /props에 embedding_model 필드(alias, path)를 두면 llama-server 쪽 도구에서도 임베딩 모델을 찾을 수 있습니다.

    (bloomery AI 어시스턴트가 작성)

  11. juliankang4 commented on Oct 15, 2026

    @juliankang4
    CollaboratorAuthor

    Thanks. To keep the endpoint PR on time for tonight's release, I'll take item 1 there: /v1/embeddings answers with the embedding seat's name in model, and a request whose model names the chat model gets a 400 or 404 in the /v1 error shape instead of a silent embedding.

    Items 2 and 3 change the existing /v1/models listing and /props, so they go in a follow-up PR after the endpoint lands: models::listing takes the seats, the embedding seat gets its own data entry with dimension and context length in meta, and /props gets an embedding_model field with alias and path.

  12. juliankang4 commented on Oct 15, 2026

    @juliankang4
    CollaboratorAuthor

    Thanks for the toktape fix. I recorded the side seat again with the fixed toktape embed and published it: https://tape.midagedev.com/r/ktnmzyeqwmqj5gqct4cy

    The MODEL line now reads embeddinggemma-2-Q8_0.gguf, the same name as the REQUEST line. Same server build, same built-in set (20 documents, 6 questions, 52 requests) on the RTX 5090 through POST /embedding.

    side seat
    recall@1 6/6
    recall@1 without prompts 6/6
    request p50 4.59 ms
    request max 5.22 ms

    toktape compare against the reference tape still matches all 52 inputs, with the lowest cosine 0.998881 on q-starter. The earlier link can be ignored.

  13. midagedev commented on Oct 15, 2026

    @midagedev
    Owner

    깜고님, side seat 녹화에 쓰신 POST /embedding 서버 코드가 아직 저장소에는 올라오지 않은 것 같습니다. 엔드포인트도 이번 릴리즈에 같이 넣고 싶어서 태그를 잠시 미뤄 두었는데, PR은 언제쯤 열 수 있을지 대략 알려 주시면 그에 맞춰 기다리겠습니다.

    (bloomery AI 어시스턴트가 작성)

  14. juliankang4 commented on Oct 15, 2026

    @juliankang4
    CollaboratorAuthor

    @midagedev 답변이 늦어져서 죄송합니다 ㅠㅠ 엔드포인트는 E6에 들어가고 지금 노드 여러대에서 마지막 테스트 중입니다. E5와 E6 곧 스택해서 PR 올릴 것 같아요!!! 그 이후에 톡테이프도 다시 찍겠습니다.

  15. juliankang4 commented on Oct 16, 2026

    @juliankang4
    CollaboratorAuthor

    @midagedev 정말정말 죄송합니다. 테스트에서 뮤턴트가 계속 나와서 이제야 클린 상황이고 e5 푸시했습니다 죄송해요!!!!

  16. juliankang4 commented on Oct 16, 2026

    @juliankang4
    CollaboratorAuthor

    New E6 side-seat recording for #323, build b75dff1a03dda54c4d4e606b3e8691e12a54a1e7: https://tape.midagedev.com/r/md5dubhjmtxxvm4p6jsm

    The MODEL line reads embeddinggemma-2-Q8_0.gguf. EmbeddingGemma 2 Q8_0 sits beside Qwen3.6-35B-A3B Q4_K_M on an RTX 5090 (driver 595.99.02, 400 W cap). The same built-in set runs through POST /embedding: 20 documents, 6 questions, 52 requests. The input text and task prefixes match the reference tape byte for byte. Warm-up requests are excluded; the CPU governor was set to performance.

    E6 side seat
    recall@1 6/6
    recall@1 without prompts 6/6
    request p50 4.520893 ms
    request max 5.087039 ms

    toktape compare against the reference llama-server tape matches all 52 inputs. The lowest cosine is 0.998881 and the maximum absolute vector difference is 0.007111, both on the prompted q-starter. The reference tape records llama-server commit 843e7dc on an RTX 3090. These are separate recordings on different cards and engines, not an interleaved speed comparison.

    At MRL widths 768, 256 and 128, all 6 questions rank the expected document first on both tapes (computed from the saved f32 vector prefixes, renormalized, with f64 cosine). The full document rankings do differ.

  17. juliankang4 commented on Oct 17, 2026

    @juliankang4
    CollaboratorAuthor

    @midagedev 님, EmbeddingGemma와 EmbeddingGemma 2 지원 작업을 완료했습니다. #190 계획에 있던 PR은 오늘 머지된 여섯 개까지 모두 main에 들어갔습니다. 이 이슈에서 제가 드린 약속도 하나씩 다시 확인했고 모두 지켰습니다.

    아래에 오늘 머지된 PR, 약속별 결과, RTX 5090에서 llama.cpp와 비교한 벤치, 결과 캐시 측정을 정리했습니다. 계획이 모두 끝났으니 이 이슈는 닫겠습니다. #59도 보시고 괜찮으시면 닫아 주셔도 될 것 같습니다.

    1. 오늘 머지된 PR

    PR 내용 머지 커밋
    #414 prompt_name으로 query와 document 프리픽스 선택 (Clef 제안 4) a6fa3eec
    #413 HF sentence-transformers 기준 출력과 sliding window 폭 확인 (Clef 제안 1) 1f262bd4
    #417 결과 캐시 --embd-cache-bytes (#406) d8959941
    #412 GPU 없이 도는 bloomery-embed crate (Clef 제안 2) c990f476
    #418 오디오(Gemma4a) 전처리와 local attention 속도 개선 4d8afaff
    #419 이미지와 비디오의 RGB 변환, rotary 준비 재사용 b8dcc2af

    E1부터 E11, V1부터 V3까지 나머지 PR은 그 전에 머지되었고 본문 체크리스트에 커밋과 함께 적어 두었습니다.

    2. 이 이슈에서 드린 약속

    약속 결과
    새 커널 entry의 cc 8.6 행이 ptxas 값이라고 PR마다 표시 #233, #234, #237, #299, #351 본문에 적었습니다. tools/ref/ptx-shapes.tsv에도 같은 주석이 6곳 있습니다
    Clef 제안 1: HF sentence-transformers 출력을 두 번째 기준으로, sliding window 폭까지 #413입니다. HF의 sliding_window=512는 한쪽으로 닿는 폭이고 GGUF의 1024는 전체 폭이라서 bloomery가 원래 2로 나누던 방식이 HF와 맞습니다. HF 대비 최소 cosine은 bloomery 0.9992535192, 저장해 둔 llama.cpp 벡터 0.9991290441로 DGX Spark와 RTX 5090에서 같았습니다
    Clef 제안 2: pooling, normalize, MRL 자르기, 요청 파싱, base64 출력을 GPU 없는 crate로 #412입니다. 고정한 llama.cpp fixture와 비교해 응답 4개의 canonical JSON 바이트 차이가 0, MRL 폭 3개의 f32 바이트 차이가 0이었습니다
    Clef 제안 3: 임베딩 seat가 /v1/models에 두 번째 모델로 나오고 파일 거부와 이미지 501을 같이 씀 후속 PR로 미루겠다고 말씀드렸는데 E6(#323)에 함께 들어갔습니다
    Clef 제안 4: prompt_name으로 프리픽스 선택 #414입니다. 두 카드와 두 모델(v2, v1)의 92개 사례에서 sentence-transformers와 입력 바이트 차이 0, 직접 프리픽스를 붙인 요청과 벡터 차이 0이었습니다
    E6 항목 1: 응답 model에 임베딩 seat 이름, 채팅 모델 이름이면 조용히 임베딩하지 않고 에러 #323입니다. 응답 model은 seat 이름이고 다른 모델 이름은 400 invalid_request_error로 거절합니다
    E6 항목 2, 3: /v1/models에 seat 항목과 meta, /props에 embedding_model /v1/models는 #323에서 meta에 n_ctx, n_embd가 들어갔고 #414에서 prompt_names가 더해졌습니다. /props의 embedding_model(alias, path)은 #366입니다
    toktape embed 녹화로 recall@1과 MRL 768, 256, 128 순위 비교, llama-server 벡터 비교 병행, E6 뒤 다시 녹화 위 코멘트에 올렸습니다. 마지막 E6 빌드(b75dff1a) 녹화는 recall@1 6/6, 52개 입력 모두 일치, 최소 cosine 0.998881, 최대 절대 차이 0.007111입니다
    #406 반복 입력 #417 결과 캐시로 처리했고 #406은 닫혔습니다

    3. RTX 5090에서 llama.cpp와 비교

    A는 mainline llama.cpp 6868a52c(E3 기준과 같은 핀), B는 bloomery main b8dcc2af입니다. 둘 다 EmbeddingGemma 2 Q8_0과 BF16 projector, context 8192를 썼습니다. bloomery는 Qwen3-30B-A3B 채팅 모델 옆 side seat로 띄웠습니다. 입력은 seed 20261017로 고정한 subset입니다. 텍스트는 nanoscifact 문서 150개와 질의 50개, 이미지는 flickr30k 200개, 비디오는 msrvtt 50개와 Bunny 발췌 3개, 오디오는 clotho 200개입니다.

    한 세션에서 A, A, A, B, B, A 순서로 돌렸고 약 7분 08초 걸렸습니다. 비율은 두 A/B 쌍의 전체 처리 시간 비율(llama.cpp 시간 / bloomery 시간) 평균이라 1보다 크면 bloomery가 빠릅니다.

    데이터셋 batch llama.cpp p50/p95 ms bloomery p50/p95 ms llama.cpp items/s bloomery items/s A/A 잡음 % 비율
    nanoscifact 1 6.73/11.20 6.12/8.25 139.93 161.33 0.16 1.153
    nanoscifact 8 53.46/72.42 48.94/59.38 155.89 169.06 0.79 1.085
    flickr30k 1 19.78/24.49 16.09/18.12 49.98 61.27 0.08 1.226
    flickr30k 8 154.19/172.17 123.54/128.80 51.39 64.55 0.03 1.256
    msrvtt 1 134.35/216.15 131.43/222.91 6.88 7.03 0.14 1.022
    msrvtt 8 1109.37/1461.12 1086.86/1447.98 6.96 7.08 0.18 1.017
    clotho 1 47.57/59.59 39.85/49.80 20.99 24.88 0.30 1.185
    clotho 8 373.24/413.94 310.94/339.59 21.27 25.55 1.04 1.201
    bunny 1 66.37/161.17 68.14/150.21 12.22 12.97 0.46 1.062
    bunny 8 231.60/232.42 227.73/227.85 12.92 13.16 0.24 1.018

    Bunny 두 행은 입력이 발췌 3개뿐이라, #419에서 같은 llama.cpp 핀과 같은 코드(6e78c890, main에서는 b8dcc2af)로 잰 값을 넣었습니다.

    검색 정확도는 B 빼기 A의 최대 절대 차이가 nDCG@10 0.00117821, Recall@1 0.004, Recall@5 0.002, Recall@10 0.001입니다.

    4. 결과 캐시 (#417)

    --embd-cache-bytes 67108864를 켠 쪽과 main 8d4f29d9를 세 라운드 번갈아 쟀습니다. 요청은 워크로드마다 라운드당 32개입니다. 아래는 p50 ms입니다.

    워크로드 GB10 main GB10 캐시 GB10 변화 GB10 A/A 잡음 RTX 5090 main RTX 5090 캐시 RTX 5090 변화 RTX 5090 A/A 잡음
    same-text 7.397 0.593 -91.98% 4.98% 5.934 0.509 -91.42% 1.05%
    same-image 12.320 1.610 -86.93% 6.77% 10.367 1.257 -87.87% 1.81%
    image-new-text 12.382 6.381 -48.46% 5.30% 10.374 5.607 -45.95% 1.00%
    all-new 12.689 11.853 -6.59% 8.39% 10.441 10.299 -1.36% 1.72%

    모든 arm의 전체 폭 벡터는 비트 단위로 같았습니다.

    긴 계획 동안 답변과 기준 녹화, toktape 수정까지 챙겨 주셔서 감사합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    modelThe CPU engine and model files: crates/model, models, hf, gguf, tokenizer, jinjanew-modelAdding a model architecture

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions