Repository navigation
feat(server): align b10621 embedding and reranking mode and pooling - #1483
Merged
Merged
Conversation
b10621 controls its embedding and reranking surface with four options mlxcel accepted none of, and answers two rerank envelopes where mlxcel answered one. This lands the option surface, the envelopes and the resolved-capability report of issue #1452. `--embedding` / `--embeddings` and `--rerank` / `--reranking` are accepted on both server binaries, with `LLAMA_ARG_EMBEDDINGS` and `LLAMA_ARG_RERANKING` read through b10621's own truthy set rather than clap's wider boolish parser. They do the two things upstream's do: generation routes answer 501 naming the flag, and the matching side-model routes serve. They also do one thing upstream never needs, because mlxcel runs a dedicated worker rather than reusing one set of weights: a mode that resolves no worker fails at startup naming the flag, instead of booting a server whose only route answers 501 forever. `--pooling` maps `mean`, `cls` and `last` onto mlxcel's kernels and outranks both the checkpoint's `1_Pooling/config.json` and `MLXCEL_EMBEDDING_POOLING`. The other two values are not pooling kernels: `rank` is how b10621 selects its reranking path, so it is a synonym for `--reranking`, and `none` asks for one vector per token, which no mlxcel embedding family can return because each pools inside its own forward pass. `none` is refused at startup with that reason. `--embd-normalize` implements b10621's whole domain in `src/embeddings/normalize.rs`: `-1` none, `0` the max-absolute rescale into the signed int16 range, `1` taxicab, `2` euclidean, and any value above 2 that p-norm. Upstream's `norm = sum > 0 ? 1/sum : 0` rule is reproduced, so a zero vector normalizes to zeros rather than to a NaN the route would then refuse with a 500. `2` delegates to the L2 kernel every embedding response already used, so the default path returns exactly the numbers it returned before. The value is readable per request as `embd_normalize`, as upstream reads it, and `mlxcel embed` takes the same two flags so the offline and server surfaces resolve identically. The rerank envelope now follows the request. A `documents` list answers the Jina shape with b10621's top-level `"object": "list"`, which mlxcel omitted; a `texts` list answers b10621's TEI shape instead, a bare array of `{index, score}` with `text` echoed under `return_text`. The Cohere `return_documents` / `document` spelling keeps working, a body carrying both list spellings stays on the Jina envelope, and the shape errors on both families use upstream's own wording. `/props` gains a `capabilities` block reporting what was resolved rather than what was passed: whether generation is on, which mode flag restricted it, and each side model's id plus the pooling and `embd_normalize` really in force. Reporting the resolved pooling needed a new `EmbeddingModel::pooling` accessor, overridden by the nine families that store it. `/v1/models` labels each entry with `completion`, `embedding` or `rerank`, omitted when empty so a plain generation deployment keeps the OpenAI object shape. A server in one of the two modes also reports healthy once its worker is up. It has no chat worker to be "loaded", so `/health` answered 503 for the life of the process, which would have made the flag unusable behind a container probe. The change is scoped to the flags, so a server whose `-m` is an embedding checkpoint but that was started without them is unchanged and `GET /health`'s recorded divergences stay #1440's. Validation: 185 unit and route tests, plus `tests/embedding_pooling_normalize_real.rs` over `all-MiniLM-L6-v2` (mean), `bge-small-en-v1.5` (cls) and the `ms-marco-MiniLM-L6-v2` cross-encoder, and a live server across both modes. Six manifest entries flip to `supported`; the four option entries stay `deferred` with their divergences recorded, so #1452 stays open and owns them. Refs #1452
5 tasks done
11 tasks done
5 tasks
be-free-snowboy
pushed a commit
to freestylejs/snowbrain-mlxcel
that referenced
this pull request
Oct 27, 2026
…ablup#1483) ## Summary Lands issue lablup#1452's embedding and reranking surface: the four b10621 mode and shaping options, the two rerank response envelopes, the aligned request errors, and a resolved-capability report on `/props` and `/v1/models`. Six of the ten manifest entries this issue owns flip to `supported`; the four option entries stay `deferred` with their divergences recorded, so this PR does not close the issue. ## What changed - `src/cli/embedding_compat_args.rs` (new): `--embedding` / `--embeddings`, `--rerank` / `--reranking`, `--pooling` and `--embd-normalize`, defined once and flattened into both server binaries. `LLAMA_ARG_EMBEDDINGS` and `LLAMA_ARG_RERANKING` are read through b10621's own truthy set (`on`, `enabled`, `true`, `1`) rather than clap's boolish parser, which both accepts more and errors outside its vocabulary; `LLAMA_ARG_POOLING` binds through clap because it takes a value. - `src/embeddings/normalize.rs` (new): b10621's whole `--embd-normalize` domain with upstream's arithmetic, including its `norm = sum > 0 ? 1/sum : 0` rule so a zero vector normalizes to zeros rather than to a NaN the route would refuse with a 500. `2` delegates to the existing L2 kernel, so the default path is numerically unchanged. - `src/embeddings/pooling.rs`: a process-wide override cell for `--pooling`, installed at startup before any checkpoint loads, because every family resolves its mode inside its own constructor and carries no server context. Precedence is flag, then `MLXCEL_EMBEDDING_POOLING`, then `1_Pooling/config.json`, then the family default. - `src/embeddings/engine.rs`: `EmbedOptions` carries the normalization, and post-processing applies it (and re-applies it after a `dimensions` truncation, which breaks whatever invariant the first pass established). - `src/embeddings/model.rs` and nine families: a `pooling()` accessor returning the mode that was really resolved, so `/props` reports what happened rather than the family default. - `src/server/config.rs`: `EmbeddingServingMode`, the restriction half of the two mode flags, and the resolved `embd_normalize`. - `src/server/startup.rs`: installs the pooling override, then refuses a mode flag that resolved no worker able to serve it. - `src/server/routes/mod.rs`: generation routes answer 501 naming the mode flag, even when a chat model is loaded next to the side model. - `src/server/types/rerank.rs`, `routes/rerank.rs`: `object: "list"` on the Jina envelope, b10621's TEI array for a `texts` request, `return_text` / `text`, and upstream's wording for the three shape errors. A body carrying both list spellings drops the redundant one and stays on the Jina envelope, because serde would otherwise reject it as a duplicate field where upstream reads one and ignores the other. - `src/server/routes/embeddings.rs`: the per-request `embd_normalize`, and `"input" or "content" must be provided` for a body carrying neither. - `src/server/routes/props.rs`, `models.rs`: the resolved capability block and the per-entry `capabilities` array. - `src/commands/embed.rs`: `--pooling` and `--embd-normalize` on the offline path too, so it resolves identically to the server it documents itself against. - `src/server/routes/health.rs`: a server in one of the two modes reports on the worker the mode selected. It has no chat worker to be "loaded", so `/health` answered `503 {"status": "loading model"}` for the life of the process, which would have made the flag unusable behind a container probe. Scoped to the flags: a server whose `-m` is an embedding checkpoint but that was started without them is unchanged, so `GET /health`'s recorded divergences and its manifest entry stay lablup#1440's. ## Why the four option entries are still `deferred` Each is implemented and tested; each has one observable difference recorded on its manifest entry. - `--embedding` and `--rerank`: mlxcel additionally requires a checkpoint that can serve the mode, and fails at startup when none resolves. b10621 restricts whatever `-m` loaded and embeds or scores with it, and mlxcel has no path that produces embeddings from a causal chat checkpoint. `--reranking` also leaves `/v1/embeddings` answering upstream, where a mlxcel server in rerank mode has no embedding worker. - `--pooling`: `none` is refused rather than served, because every embedding family pools inside its forward pass and there is no unpooled hidden state left to return; `rank` selects the reranking worker rather than becoming a pooling type. - `--embd-normalize`: with the flag unset mlxcel follows the checkpoint's own `normalize` flag, which is euclidean for every checkpoint that does not set it and none for one that sets it to `false`. b10621 always defaults to `2`, because GGUF carries no such metadata. ## One observation worth recording `bge-small-en-v1.5` returns the same vector for `--pooling cls` and `--pooling last`, to 1.3e-5 relative. That is the checkpoint, not the pooling: `all-MiniLM-L6-v2` separates the two by 0.90 through the identical code path, the `pool()` kernel's last-token index is unit-tested against right padding, left padding and an all-padding row, and the coincidence reproduces on two unrelated prompts and through the offline `mlxcel embed` path. A BERT trained with CLS pooling collapses its `[SEP]` representation onto `[CLS]`. The real-checkpoint test asserts `mean` against each single-position mode on both checkpoints, and `cls` against `last` only where the two positions are known to differ, so it still fails if the override stops reaching the forward pass. ## Test plan - [x] `cargo test --profile test-fast --features metal,accelerate --lib -- embeddings::normalize embeddings::pooling cli::embedding_compat server::routes::embedding_rerank_mode server::routes::embeddings server::routes::rerank server::routes::props server::routes::models server::routes::health server::startup server::llama_compat`: 185 passed. - [x] `cargo test --test embedding_pooling_normalize_real --profile test-fast --features metal,accelerate`: every pooling and normalization path over `all-MiniLM-L6-v2` (mean pooling), `bge-small-en-v1.5` (cls pooling) and the `ms-marco-MiniLM-L6-v2` cross-encoder. - [x] `python3 scripts/ci/check_llama_compat_manifest.py` and `bash scripts/ci/check_llama_compat_manifest_test.sh` - [x] `cargo fmt --all -- --check` and `cargo clippy --lib --tests --features metal,accelerate -- -D warnings` - [x] Live server, `mlxcel-server` against the two real checkpoints: - `--embeddings --pooling cls --embd-normalize 1` over `all-MiniLM-L6-v2`: `/props` reported `{"generation": false, "serving_mode": "--embeddings", "embedding": {"pooling": "cls", "embd_normalize": 1, ...}}`, `/v1/models` labelled the entry `["embedding"]`, both embedding shapes served, and `/v1/chat/completions` and `/completion` answered 501 naming the flag. - `--reranking` over `ms-marco-MiniLM-L6-v2`: `documents` answered `{"model", "object": "list", "results": [{"index", "relevance_score", "document"}], "usage"}` and `texts` answered `[{"index", "score", "text"}]` on the same query, with the panda passage first at 0.9985 against 0.0000149 for the distractor; generation and `/v1/embeddings` answered 501. - `--pooling rank` selected the same reranking mode. - `--pooling none`, `--embd-normalize -2`, `--embeddings` on a reranker checkpoint and `--reranking` on an embedding checkpoint each failed at startup with their diagnostic. - `LLAMA_ARG_EMBEDDINGS=1 LLAMA_ARG_POOLING=mean` alone started the server and logged the pooling override, so the runtime environment bridge works without any command line. Refs lablup#1452
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lands issue #1452's embedding and reranking surface: the four b10621 mode and shaping options, the two rerank response envelopes, the aligned request errors, and a resolved-capability report on
/propsand/v1/models. Six of the ten manifest entries this issue owns flip tosupported; the four option entries staydeferredwith their divergences recorded, so this PR does not close the issue.What changed
src/cli/embedding_compat_args.rs(new):--embedding/--embeddings,--rerank/--reranking,--poolingand--embd-normalize, defined once and flattened into both server binaries.LLAMA_ARG_EMBEDDINGSandLLAMA_ARG_RERANKINGare read through b10621's own truthy set (on,enabled,true,1) rather than clap's boolish parser, which both accepts more and errors outside its vocabulary;LLAMA_ARG_POOLINGbinds through clap because it takes a value.src/embeddings/normalize.rs(new): b10621's whole--embd-normalizedomain with upstream's arithmetic, including itsnorm = sum > 0 ? 1/sum : 0rule so a zero vector normalizes to zeros rather than to a NaN the route would refuse with a 500.2delegates to the existing L2 kernel, so the default path is numerically unchanged.src/embeddings/pooling.rs: a process-wide override cell for--pooling, installed at startup before any checkpoint loads, because every family resolves its mode inside its own constructor and carries no server context. Precedence is flag, thenMLXCEL_EMBEDDING_POOLING, then1_Pooling/config.json, then the family default.src/embeddings/engine.rs:EmbedOptionscarries the normalization, and post-processing applies it (and re-applies it after adimensionstruncation, which breaks whatever invariant the first pass established).src/embeddings/model.rsand nine families: apooling()accessor returning the mode that was really resolved, so/propsreports what happened rather than the family default.src/server/config.rs:EmbeddingServingMode, the restriction half of the two mode flags, and the resolvedembd_normalize.src/server/startup.rs: installs the pooling override, then refuses a mode flag that resolved no worker able to serve it.src/server/routes/mod.rs: generation routes answer 501 naming the mode flag, even when a chat model is loaded next to the side model.src/server/types/rerank.rs,routes/rerank.rs:object: "list"on the Jina envelope, b10621's TEI array for atextsrequest,return_text/text, and upstream's wording for the three shape errors. A body carrying both list spellings drops the redundant one and stays on the Jina envelope, because serde would otherwise reject it as a duplicate field where upstream reads one and ignores the other.src/server/routes/embeddings.rs: the per-requestembd_normalize, and"input" or "content" must be providedfor a body carrying neither.src/server/routes/props.rs,models.rs: the resolved capability block and the per-entrycapabilitiesarray.src/commands/embed.rs:--poolingand--embd-normalizeon the offline path too, so it resolves identically to the server it documents itself against.src/server/routes/health.rs: a server in one of the two modes reports on the worker the mode selected. It has no chat worker to be "loaded", so/healthanswered503 {"status": "loading model"}for the life of the process, which would have made the flag unusable behind a container probe. Scoped to the flags: a server whose-mis an embedding checkpoint but that was started without them is unchanged, soGET /health's recorded divergences and its manifest entry stay feat(server): align props, slots, metrics, health, and slot persistence #1440's.Why the four option entries are still
deferredEach is implemented and tested; each has one observable difference recorded on its manifest entry.
--embeddingand--rerank: mlxcel additionally requires a checkpoint that can serve the mode, and fails at startup when none resolves. b10621 restricts whatever-mloaded and embeds or scores with it, and mlxcel has no path that produces embeddings from a causal chat checkpoint.--rerankingalso leaves/v1/embeddingsanswering upstream, where a mlxcel server in rerank mode has no embedding worker.--pooling:noneis refused rather than served, because every embedding family pools inside its forward pass and there is no unpooled hidden state left to return;rankselects the reranking worker rather than becoming a pooling type.--embd-normalize: with the flag unset mlxcel follows the checkpoint's ownnormalizeflag, which is euclidean for every checkpoint that does not set it and none for one that sets it tofalse. b10621 always defaults to2, because GGUF carries no such metadata.One observation worth recording
bge-small-en-v1.5returns the same vector for--pooling clsand--pooling last, to 1.3e-5 relative. That is the checkpoint, not the pooling:all-MiniLM-L6-v2separates the two by 0.90 through the identical code path, thepool()kernel's last-token index is unit-tested against right padding, left padding and an all-padding row, and the coincidence reproduces on two unrelated prompts and through the offlinemlxcel embedpath. A BERT trained with CLS pooling collapses its[SEP]representation onto[CLS]. The real-checkpoint test assertsmeanagainst each single-position mode on both checkpoints, andclsagainstlastonly where the two positions are known to differ, so it still fails if the override stops reaching the forward pass.Test plan
cargo test --profile test-fast --features metal,accelerate --lib -- embeddings::normalize embeddings::pooling cli::embedding_compat server::routes::embedding_rerank_mode server::routes::embeddings server::routes::rerank server::routes::props server::routes::models server::routes::health server::startup server::llama_compat: 185 passed.cargo test --test embedding_pooling_normalize_real --profile test-fast --features metal,accelerate: every pooling and normalization path overall-MiniLM-L6-v2(mean pooling),bge-small-en-v1.5(cls pooling) and thems-marco-MiniLM-L6-v2cross-encoder.python3 scripts/ci/check_llama_compat_manifest.pyandbash scripts/ci/check_llama_compat_manifest_test.shcargo fmt --all -- --checkandcargo clippy --lib --tests --features metal,accelerate -- -D warningsmlxcel-serveragainst the two real checkpoints:--embeddings --pooling cls --embd-normalize 1overall-MiniLM-L6-v2:/propsreported{"generation": false, "serving_mode": "--embeddings", "embedding": {"pooling": "cls", "embd_normalize": 1, ...}},/v1/modelslabelled the entry["embedding"], both embedding shapes served, and/v1/chat/completionsand/completionanswered 501 naming the flag.--rerankingoverms-marco-MiniLM-L6-v2:documentsanswered{"model", "object": "list", "results": [{"index", "relevance_score", "document"}], "usage"}andtextsanswered[{"index", "score", "text"}]on the same query, with the panda passage first at 0.9985 against 0.0000149 for the distractor; generation and/v1/embeddingsanswered 501.--pooling rankselected the same reranking mode.--pooling none,--embd-normalize -2,--embeddingson a reranker checkpoint and--rerankingon an embedding checkpoint each failed at startup with their diagnostic.LLAMA_ARG_EMBEDDINGS=1 LLAMA_ARG_POOLING=meanalone started the server and logged the pooling override, so the runtime environment bridge works without any command line.Refs #1452