handwoven8588 commited on
Commit
581207a
·
verified ·
1 Parent(s): 22d2b3c

v2: bf16 weights (547->274MB) + from_pretrained torch_dtype fix (loads bf16 natively) + corrected model tree (base_model=nomic-ai/CodeRankEmbed only) + bf16-derivative README

Browse files
Files changed (4) hide show
  1. README.md +54 -43
  2. config.json +2 -2
  3. model.safetensors +2 -2
  4. modeling_hf_nomic_bert.py +14 -0
README.md CHANGED
@@ -1,7 +1,6 @@
1
  ---
2
  base_model:
3
  - nomic-ai/CodeRankEmbed
4
- - Snowflake/snowflake-arctic-embed-m-long
5
  library_name: sentence-transformers
6
  license: mit
7
  tags:
@@ -9,36 +8,46 @@ tags:
9
  - code-retrieval
10
  - sentence-transformers
11
  - nomic-bert
 
 
 
 
12
  ---
13
 
14
  # CodeRankEmbed-flash-attn
15
 
16
- A drop-in derivative of [`nomic-ai/CodeRankEmbed`](https://huggingface.co/nomic-ai/CodeRankEmbed)
17
- with **flash-attention baked natively into the modeling file**. Same weights, same tokenizer,
18
- same config — **identical embeddings** — but attention runs via `flash_attn` varlen (unpadded
19
- packing) instead of the original eager `O(seq²)` matmul.
 
 
 
 
 
 
20
 
21
  ## Why
22
 
23
  `nomic-ai/CodeRankEmbed` loads through `trust_remote_code`, and its attention path is **eager
24
- only** — activation memory grows as `batch × heads × seq²`, which OOMs at large batches on a
25
- 24 GB card even though the model is only 137M params. `flash_attn`'s varlen path computes the
26
- same attention in `O(N)` memory by packing unpadded sequences, so you can run the large batches
27
- that OOM the eager path — with **parity embeddings** (no quality change).
28
-
29
- This repo exists so you get that flash path **without a runtime monkeypatch**: the
30
- `modeling_hf_nomic_bert.py` here is one we own, with the flash-varlen forward written in
31
- directly. Load it like any other model; `trust_remote_code` now runs *this* audited file.
32
-
33
- ## Behavior (works everywhere)
34
-
35
- - **CUDA + `flash_attn` installed** → flash-varlen path (fast, low VRAM).
 
 
 
 
36
  - **CPU, or no `flash_attn`** → the original eager path, **byte-for-byte unchanged**, so the
37
- model loads and encodes correctly on any host. The forward picks the path automatically.
38
-
39
- `flash_attn` requires half precision; if the model is loaded as fp32 (sentence-transformers'
40
- default — it drops `config.torch_dtype`), the flash branch casts the attention tensors to bf16
41
- internally and casts the result back, so **no external dtype cast is required**.
42
 
43
  ## Usage
44
 
@@ -56,39 +65,41 @@ q = model.encode(queries, normalize_embeddings=True)
56
  d = model.encode(codes, normalize_embeddings=True)
57
  ```
58
 
59
- ## Parity vs `nomic-ai/CodeRankEmbed`
60
 
61
- Same weights + tokenizer, so embeddings match the original. Measured (RTX 4060, `flash_attn`
62
- 2.8.3, CLS pooling, L2-normalized):
63
 
64
- | path | comparison | cosine (same input) |
65
  | --- | --- | --- |
66
- | flash-varlen (this repo, CUDA) | vs original fp32 eager | **0.99999** |
67
- | eager fallback (this repo, CPU) | vs original fp32 eager | **1.000000** (bit-identical) |
 
68
 
69
- The eager fallback is byte-for-byte the original code, so on CPU the two repos are numerically
70
- identical. On CUDA the only difference is bf16 rounding inside attention (cosine 0.99999) —
71
- retrieval-quality-neutral.
72
 
73
  ## What changed vs the source repo
74
 
75
- Only two methods were modified in `modeling_hf_nomic_bert.py`; everything else is the upstream
76
- file unchanged:
77
-
78
- - `NomicBertModel.forward` — skips `get_extended_attention_mask` on the flash path (flash derives
79
- sequence lengths from `cu_seqlens`, not an additive mask); calls it as before on the eager path.
80
- - `NomicBertAttention.forward` — adds the `unpad → flash_attn_varlen_qkvpacked_func(causal=False)
81
- → repad` branch; keeps the original eager block as the fallback.
82
-
83
- Rotary embeddings are applied to the dense `[B, S, 3, H, D]` tensor **before** unpadding — the
84
- correctness keystone (unpadding first would corrupt per-sequence positions).
 
85
 
86
  ## License & attribution
87
 
88
  MIT — same license as `nomic-ai/CodeRankEmbed` (see `NOTICE`). The weights, tokenizer, and the
89
  bulk of the modeling file are a verbatim derivative of `nomic-ai/CodeRankEmbed`; the modeling file
90
- itself derives from Tri Dao's BERT implementation, and `CodeRankEmbed` was trained by the CoRNStack
91
- team (Suresh et al., 2025). If you find the model useful, please cite their work:
92
 
93
  ```bibtex
94
  @misc{suresh2025cornstackhighqualitycontrastivedata,
 
1
  ---
2
  base_model:
3
  - nomic-ai/CodeRankEmbed
 
4
  library_name: sentence-transformers
5
  license: mit
6
  tags:
 
8
  - code-retrieval
9
  - sentence-transformers
10
  - nomic-bert
11
+ - bf16
12
+ language:
13
+ - en
14
+ pipeline_tag: feature-extraction
15
  ---
16
 
17
  # CodeRankEmbed-flash-attn
18
 
19
+ A **bf16 derivative of [`nomic-ai/CodeRankEmbed`](https://huggingface.co/nomic-ai/CodeRankEmbed)**
20
+ with **flash-attention baked natively into a modeling file we own.** Not a finetune —
21
+ the weights are the original CodeRankEmbed weights cast to bf16 (no further training), and the
22
+ `modeling_hf_nomic_bert.py` here is a derivative that runs attention via `flash_attn` varlen
23
+ instead of the original eager `O(seq²)` path.
24
+
25
+ > **About the model tree:** this repo is a derivative of `nomic-ai/CodeRankEmbed` (same weights,
26
+ > bf16-cast + owned flash-attn code), **not** a finetune of `Snowflake/snowflake-arctic-embed-m-long`.
27
+ > The Snowflake model is CodeRankEmbed's *grandparent* (nomic-ai initialized from it); we derive
28
+ > directly from nomic-ai/CodeRankEmbed, which is also where the fp32 reference weights live.
29
 
30
  ## Why
31
 
32
  `nomic-ai/CodeRankEmbed` loads through `trust_remote_code`, and its attention path is **eager
33
+ only** — activation memory grows as `batch × heads × seq²`, which OOMs at large batches even
34
+ though the model is only 137M params. `flash_attn`'s varlen path computes the same attention in
35
+ `O(N)` memory by packing unpadded sequences, so you can run the large batches that OOM the eager
36
+ path — with **parity embeddings** (no quality change). This repo ships that flash path in the
37
+ modeling file itself, so you get it **without a runtime monkeypatch**.
38
+
39
+ ## Behavior
40
+
41
+ - **Loads bf16 by default.** Weights are stored bf16 (half the download: 274 MB vs 547 MB) and
42
+ `config.json` declares `torch_dtype: bfloat16`. We patched the upstream `from_pretrained`
43
+ (which silently dropped `torch_dtype` and always loaded fp32) to honor it — so the model now
44
+ loads bf16 natively, like any normal HF model. Pass `torch_dtype=torch.float32` if you want
45
+ fp32 (note: the stored weights are bf16-precision, so this only widens the dtype, not the
46
+ precision).
47
+ - **CUDA + `flash_attn`** → flash-varlen path (fast, low VRAM). The forward casts attention
48
+ tensors to bf16 internally as a safety net, but with the default bf16 load this is a no-op.
49
  - **CPU, or no `flash_attn`** → the original eager path, **byte-for-byte unchanged**, so the
50
+ model loads and encodes on any host. The forward picks the path automatically.
 
 
 
 
51
 
52
  ## Usage
53
 
 
65
  d = model.encode(codes, normalize_embeddings=True)
66
  ```
67
 
68
+ ## Parity & performance
69
 
70
+ Same weights (bf16-cast) → embeddings match the fp32 original. Measured on an RTX 3090 Ti,
71
+ `flash_attn` 2.8.3, CLS pooling, L2-normalized, batch size 64:
72
 
73
+ | metric | `nomic-ai/CodeRankEmbed` (fp32, eager) | this repo (bf16, flash-varlen) |
74
  | --- | --- | --- |
75
+ | cosine vs fp32 reference | 1.000000 | **0.9987** |
76
+ | peak VRAM | _measuring_ | _measuring_ |
77
+ | throughput (tok/s) | _measuring_ | _measuring_ |
78
 
79
+ The cosine is hardware-independent (the flash-varlen forward is numerically identical to the
80
+ merged penstock #264 monkeypatch, verified at parity > 0.997). VRAM and throughput rows are
81
+ populated from a 3090 Ti sweep — see the model card history for the commit that fills them in.
82
 
83
  ## What changed vs the source repo
84
 
85
+ 1. **Weights**: fp32 → bf16 (lossy cast, but parity-neutral — the production serving path was
86
+ already bf16). Halves the download.
87
+ 2. **`from_pretrained` fix**: the upstream custom `from_pretrained` instantiated the model fp32
88
+ and `load_state_dict`-ed the checkpoint into fp32 params, **ignoring `torch_dtype`** (it was
89
+ swallowed in `**kwargs`). We added the standard transformers dtype resolution (explicit arg →
90
+ `config.torch_dtype` → checkpoint dtype) so the model loads in its declared dtype.
91
+ 3. **Flash-varlen forward**: `NomicBertAttention.forward` gains an `unpad →
92
+ flash_attn_varlen_qkvpacked_func(causal=False) → repad` branch (CUDA+flash_attn); the original
93
+ eager block is kept verbatim as the fallback. `NomicBertModel.forward` skips
94
+ `get_extended_attention_mask` on the flash path. Rotary embeddings are applied to the dense
95
+ `[B, S, 3, H, D]` tensor **before** unpadding — the correctness keystone.
96
 
97
  ## License & attribution
98
 
99
  MIT — same license as `nomic-ai/CodeRankEmbed` (see `NOTICE`). The weights, tokenizer, and the
100
  bulk of the modeling file are a verbatim derivative of `nomic-ai/CodeRankEmbed`; the modeling file
101
+ derives from Tri Dao's BERT implementation, and `CodeRankEmbed` was trained by the CoRNStack team
102
+ (Suresh et al., 2025). Cite their work:
103
 
104
  ```bibtex
105
  @misc{suresh2025cornstackhighqualitycontrastivedata,
config.json CHANGED
@@ -46,7 +46,7 @@
46
  "summary_proj_to_labels": true,
47
  "summary_type": "cls_index",
48
  "summary_use_proj": true,
49
- "torch_dtype": "float32",
50
  "transformers_version": "4.45.1",
51
  "type_vocab_size": 2,
52
  "use_cache": true,
@@ -54,4 +54,4 @@
54
  "use_rms_norm": false,
55
  "use_xentropy": true,
56
  "vocab_size": 30528
57
- }
 
46
  "summary_proj_to_labels": true,
47
  "summary_type": "cls_index",
48
  "summary_use_proj": true,
49
+ "torch_dtype": "bfloat16",
50
  "transformers_version": "4.45.1",
51
  "type_vocab_size": 2,
52
  "use_cache": true,
 
54
  "use_rms_norm": false,
55
  "use_xentropy": true,
56
  "vocab_size": 30528
57
+ }
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:827529bcd58aef0d9082e66eeff7e7d53a02f62bd005f841a26b3d3e2fb17ebe
3
- size 546938168
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:919b7d8b4352a2ede5c99e43c856d935173882471b4332bd61568d239e222676
3
+ size 273474944
modeling_hf_nomic_bert.py CHANGED
@@ -369,6 +369,20 @@ class NomicBertPreTrainedModel(PreTrainedModel):
369
  state_dict = filter_shapes(state_dict, model)
370
 
371
  load_return = model.load_state_dict(state_dict, strict=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  logger.warning(load_return)
373
  return model
374
 
 
369
  state_dict = filter_shapes(state_dict, model)
370
 
371
  load_return = model.load_state_dict(state_dict, strict=True)
372
+ # Honor torch_dtype like transformers' native from_pretrained does. Our custom
373
+ # override above bypassed it (it instantiates fp32 and load_state_dict upcasts
374
+ # the checkpoint into the fp32 params, ignoring torch_dtype). Resolve explicit
375
+ # kwarg > config.torch_dtype > checkpoint dtype, then cast the model.
376
+ import torch as _torch
377
+ _td = kwargs.get("torch_dtype")
378
+ if _td is None:
379
+ _td = getattr(config, "torch_dtype", None)
380
+ if _td == "auto" or _td is None:
381
+ _td = next(iter(state_dict.values())).dtype
382
+ if isinstance(_td, str):
383
+ _td = getattr(_torch, _td)
384
+ if _td is not None:
385
+ model = model.to(_td)
386
  logger.warning(load_return)
387
  return model
388