[Feature] Add --disable-normalize-embedding server arg - #39031
fortunecookiee wants to merge 4 commits into
Conversation
|
Updated with unit tests ( Also clarified in the help text that models whose pooler does not go through For context on alternatives considered: |
|
/tag-run-ci-label |
|
/rerun-failed-ci |
Embedding models in SGLang hard-code whether their Pooler L2-normalizes the pooled hidden state (`Pooler(pooling_type=..., normalize=True)`). Serving a checkpoint whose downstream consumer expects un-normalized embeddings currently requires editing the model file. Add a `--disable-normalize-embedding` server arg. When set, `Pooler.__init__` overrides `normalize=True` to `False`, so the L2 normalization step is skipped at pool time. No per-model change is needed: every model that constructs a `Pooler` with `normalize=True` picks up the override automatically. The override is deliberately one-way. A model that already sets `normalize=False` is unaffected, and there is no path to force-enable normalization on a model that did not opt in. This matches the flag name and avoids surprising behavior changes. The field lives on the `model` namespace next to `is_embedding`, and the pooler reads it through `get_model()` like other resolved-config readers.
- Cover the flag in test_pooler_score_and_pool.py: the override turns off normalization for a normalize=True Pooler, leaves it on by default, and is one-way (a normalize=False model is never force-normalized). The tests publish a context with override_server_args, which also pins the fact that Pooler.__init__ now reads the model config bag. - Say in the help text that models whose pooler does not go through Pooler (cross-encoder, BERT pooler, vision poolers) are unaffected by the flag.
83b6060 to
45e2f03
Compare
|
Rebased onto current |
Pooler.__init__ read get_model().disable_normalize_embedding whenever
normalize=True. Config bags fail closed until publish/set_server_args
has projected them, so this coupled normalized Pooler construction to a
published runtime config: any standalone/offline path constructing
Pooler(normalize=True) outside a published server context would raise
ValueError("config namespace 'model' not published") where the prior
code touched no global config.
Gate the read on runtime_context.is_config_published("model") and fall
back to the default of keeping normalization when the namespace is
absent -- with nothing published there is no override to honor. The new
helper deliberately skips the role namespace check so a role violation
still raises from the subsequent config_bag read instead of being
reported as unpublished. Production model init runs after publish, so
serving behavior is unchanged.
Motivation
Embedding models in SGLang hard-code whether their
PoolerL2-normalizes the pooled hidden state, e.g.Pooler(pooling_type=PoolingType.LAST, normalize=True). Serving a checkpoint whose downstream consumer expects un-normalized embeddings (raw pooled vectors, e.g. for a scorer or a retrieval index that does its own normalization) currently requires editing the model file.This adds a server-level opt-out so the same checkpoint can be served either way without touching model code.
Supersedes #23282, which was auto-closed by the stale bot. This is the same change rebased onto current
mainand adapted to thearg_groups/ runtime-context config layout that landed in the meantime.Modifications
arg_groups/fields/model.py: newdisable_normalize_embedding: bool = Falsefield on themodelnamespace, declared next tois_embedding. The CLI flag--disable-normalize-embeddingis generated from the annotation like every other boolean arg.layers/pooler.py:Pooler.__init__reads the resolved value viaget_model()and, when set, overridesnormalize=TruetoFalse, so the L2 normalization step is skipped at pool time.Design notes:
Poolerwithnormalize=Truepicks up the override automatically.normalize=Falseis unaffected, and there is no path to force-enable normalization on a model that did not opt in. This matches the flag name and avoids surprising behavior changes.modelconfig bag (get_model().disable_normalize_embedding) rather thanget_global_server_args(), matching the current resolved-config reader convention (same as the existingget_model().is_embeddingreads).Accuracy Tests
Default path is unchanged: with the flag unset,
normalizekeeps the value the model passed, so pooled outputs are bit-identical tomain.With
--disable-normalize-embedding, the returned embedding is the pre-normalization pooled vector; normalizing it client-side reproduces the default output.Speed Tests and Profiling
Not applicable — the flag is read once in
Pooler.__init__(construction time), not on the forward path. When set, it removes work (one L2 normalization per pooled batch).Checklist
CI States
Latest PR Test (Base): 🚫 Run #34899602609
Latest PR Test (Extra): ❌ Run #34899602318
Latest PR Test (AMD ROCm 10): ❌ Run #34899602455