feat: add PPLX Qwen3 bidirectional embeddings (perplexity-ai/pplx-embed-v1-4b) - #78
Open
StartupBros wants to merge 1 commit into
Open
feat: add PPLX Qwen3 bidirectional embeddings (perplexity-ai/pplx-embed-v1-4b)#78StartupBros wants to merge 1 commit into
StartupBros wants to merge 1 commit into
Conversation
…ed-v1-4b)
A stock Qwen3 decoder run with FULL bidirectional attention and mean
pooling. Upstream's modeling.py is a thin Qwen3Model subclass whose entire
job is to set layer.self_attn.is_causal = False on every layer and OR a
bidirectional mask over the causal one, so the causal triangle disappears
and every token sees every non-padded token. The model itself does no
pooling; 1_Pooling/config.json selects pooling_mode_mean_tokens.
Three shape details are load-bearing, and each is documented in the module:
* head_dim is 128 while hidden_size is 2560, so 32 heads x 128 = 4096 and
q_proj is 2560 -> 4096. Deriving head_dim as hidden_size // num_heads
gives 80 and loads nothing.
* Qwen3 applies per-head RMSNorm to queries and keys before RoPE. Qwen2
does not, and carries q/k/v biases instead; attention_bias is false here.
* tie_word_embeddings is true, so there is no separate lm_head tensor.
The bidirectional mask is keyed on columns only, so a padded ROW still
attends to real tokens and every softmax row stays finite; padded rows are
discarded by mean pooling regardless.
Config is validated rather than assumed: the pinned revision ships
use_sliding_window=false with all 36 layer_types set to full_attention, and
the model args raise if a checkpoint disagrees instead of silently ignoring
sliding-window settings.
Verified end to end against the PyTorch reference on an M3 Ultra, in float32,
mean-pooled identically on both sides: min cosine 1.0 across 4 comparisons
covering a single unpadded text and a padded batch. Parameter count is
4,022,468,096, matching the published count exactly.
Serving-verified: loaded under oMLX 0.6.3 and returned a 2560-dimensional
vector over /v1/embeddings.
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.
Adds support for
perplexity-ai/pplx-embed-v1-4b(model_type: bidirectional_pplx_qwen3), #9 on the MTEB Code leaderboard.What it is
A stock Qwen3 decoder run with full bidirectional attention and mean pooling. Upstream's
modeling.pyis a thinQwen3Modelsubclass whose entire job is to setlayer.self_attn.is_causal = Falseon every layer and OR a bidirectional mask over the causal one — so the causal triangle disappears and every token sees every non-padded token. The model does no pooling itself;1_Pooling/config.jsonselectspooling_mode_mean_tokens.Shape details worth reviewing
Three things are load-bearing, each documented in the module:
head_dimis 128 whilehidden_sizeis 2560, so 32 × 128 = 4096 andq_projis 2560 → 4096. Derivinghead_dimashidden_size // num_headsgives 80 and loads nothing.attention_biasis false here.tie_word_embeddingsis true, so there is no separatelm_headtensor.The bidirectional mask is keyed on columns only, so a padded row still attends to real tokens and every softmax row stays finite. Padded rows are discarded by mean pooling regardless.
Config is validated rather than assumed: the pinned revision ships
use_sliding_window: falsewith all 36layer_typesset tofull_attention, andModelArgsraises if a checkpoint disagrees instead of silently ignoring sliding-window settings.Verification
End to end against the PyTorch reference on an M3 Ultra, in float32, mean-pooled identically on both sides:
min_cosine: 1.0across 4 comparisons. Parameter count is 4,022,468,096, matching the published count exactly.Also serving-verified: loaded under oMLX 0.6.3 and returned a 2560-dimensional vector over
/v1/embeddings.