nemotron-extraction 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nemotron_extraction-1.0.0/CITATION.bib +8 -0
- nemotron_extraction-1.0.0/LICENSE +21 -0
- nemotron_extraction-1.0.0/MANIFEST.in +6 -0
- nemotron_extraction-1.0.0/NOTICE +5 -0
- nemotron_extraction-1.0.0/PKG-INFO +127 -0
- nemotron_extraction-1.0.0/README.md +104 -0
- nemotron_extraction-1.0.0/benchmarks/aggregate.py +69 -0
- nemotron_extraction-1.0.0/benchmarks/check_install.py +25 -0
- nemotron_extraction-1.0.0/benchmarks/schedule.py +52 -0
- nemotron_extraction-1.0.0/benchmarks/sweep.py +408 -0
- nemotron_extraction-1.0.0/benchmarks/targeted_validation.py +355 -0
- nemotron_extraction-1.0.0/benchmarks/validate.py +378 -0
- nemotron_extraction-1.0.0/pyproject.toml +31 -0
- nemotron_extraction-1.0.0/report.pdf +0 -0
- nemotron_extraction-1.0.0/report.tex +86 -0
- nemotron_extraction-1.0.0/setup.cfg +4 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction/__init__.py +7 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction/hf.py +239 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction/hooks.py +110 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction/transport.py +66 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction/types.py +50 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction/vllm.py +253 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction/worker.py +205 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/PKG-INFO +127 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/SOURCES.txt +35 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/dependency_links.txt +1 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/requires.txt +15 -0
- nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/top_level.txt +1 -0
- nemotron_extraction-1.0.0/tests/test_aggregate.py +53 -0
- nemotron_extraction-1.0.0/tests/test_async.py +92 -0
- nemotron_extraction-1.0.0/tests/test_benchmark.py +114 -0
- nemotron_extraction-1.0.0/tests/test_hooks.py +235 -0
- nemotron_extraction-1.0.0/tests/test_schedule.py +47 -0
- nemotron_extraction-1.0.0/tests/test_sweep.py +42 -0
- nemotron_extraction-1.0.0/tests/test_targeted.py +96 -0
- nemotron_extraction-1.0.0/tests/test_worker.py +114 -0
- nemotron_extraction-1.0.0/uv.lock +6902 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@misc{lobo_santos2026nemotron_extraction,
|
|
2
|
+
author = {Lobo-Santos, Antonio},
|
|
3
|
+
title = {{Nemotron Extraction}},
|
|
4
|
+
year = {2026},
|
|
5
|
+
howpublished = {Software, version 1.0.0},
|
|
6
|
+
institution = {Deutsches Forschungszentrum f\"ur K\"unstliche Intelligenz (DFKI)},
|
|
7
|
+
note = {Version 1.0.0}
|
|
8
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nemotron-extraction contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
The worker hooks, residual reconstruction, attention projection resolution,
|
|
2
|
+
and token-row assembly are adapted from the MIT-licensed nemotron-sae
|
|
3
|
+
sae_pipeline extraction and hook implementation (2026 contributors).
|
|
4
|
+
This package uses vLLM's worker-extension API directly; it does not vendor
|
|
5
|
+
or require IBM vLLM-Hook. Model weights and datasets retain their own licenses.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nemotron-extraction
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Small, token-aligned activation hooks for Nemotron in PyTorch and vLLM
|
|
5
|
+
Author: Antonio Lobo-Santos
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Organization, https://www.dfki.de/
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Requires-Dist: torch>=2.7
|
|
13
|
+
Requires-Dist: safetensors>=0.4.5
|
|
14
|
+
Provides-Extra: hf
|
|
15
|
+
Requires-Dist: transformers<6,>=4.57.3; extra == "hf"
|
|
16
|
+
Requires-Dist: accelerate>=1.0; extra == "hf"
|
|
17
|
+
Provides-Extra: vllm
|
|
18
|
+
Requires-Dist: vllm<0.30,>=0.20.1; sys_platform == "linux" and extra == "vllm"
|
|
19
|
+
Provides-Extra: benchmark
|
|
20
|
+
Requires-Dist: datasets<6,>=3; extra == "benchmark"
|
|
21
|
+
Requires-Dist: huggingface-hub<2,>=0.25; extra == "benchmark"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# Nemotron extraction 1.0
|
|
25
|
+
|
|
26
|
+
Activations describe a model's internal state as it processes tokens.
|
|
27
|
+
This package captures them from the official
|
|
28
|
+
[`nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16`](https://huggingface.co/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16)
|
|
29
|
+
checkpoint through two backends: Hugging Face Transformers, running with PyTorch,
|
|
30
|
+
and vLLM, an inference engine.
|
|
31
|
+
|
|
32
|
+
A hook captures activations at a chosen location (site).
|
|
33
|
+
In vLLM, hooks run inside worker processes on the graphics processor (GPU).
|
|
34
|
+
The driver installs hooks through `collective_rpc` and submits token IDs.
|
|
35
|
+
Workers copy captures into pinned host buffers on a separate CUDA stream and
|
|
36
|
+
save Safetensors files, allowing transfers and writes to overlap inference.
|
|
37
|
+
Request IDs preserve sequence order, including repeats.
|
|
38
|
+
|
|
39
|
+
Hooks reconstruct residuals and clone tensors before later operations overwrite them.
|
|
40
|
+
|
|
41
|
+
## Install and use
|
|
42
|
+
|
|
43
|
+
Python 3.10+; vLLM requires Linux and an NVIDIA GPU.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv venv
|
|
47
|
+
uv pip install 'nemotron-extraction[hf,vllm,benchmark]==1.0.0'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import torch
|
|
52
|
+
from nemotron_extraction import HookSite, VLLMExtractor
|
|
53
|
+
|
|
54
|
+
sites = [HookSite(12, "resid_post"), HookSite(12, "attn_out_prelinear")]
|
|
55
|
+
with VLLMExtractor(
|
|
56
|
+
"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", sites,
|
|
57
|
+
) as extractor:
|
|
58
|
+
batch = extractor.extract(torch.tensor([[1, 2, 3]], dtype=torch.long))
|
|
59
|
+
print(batch.activations["L12_resid_post"].shape) # (3, hidden_width)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Both support `await extract_async(token_ids)` and
|
|
63
|
+
`stream_to_disk(token_batches, output_dir)`, yielding saved-file metadata.
|
|
64
|
+
At most two batches await saving. Layers are zero-based. Sites:
|
|
65
|
+
`resid_pre`, `resid_post`, `mamba_out`, `moe_out`, `attn_out`,
|
|
66
|
+
`attn_out_prelinear`; mixer-specific sites require a compatible layer.
|
|
67
|
+
Inputs are rectangular int64 batches without padding; outputs in host memory have shape
|
|
68
|
+
`(batch × sequence_length, site_width)` in sequence-major order.
|
|
69
|
+
Use separate calls for different lengths.
|
|
70
|
+
|
|
71
|
+
Extraction requires eager execution, tensor parallelism of one, and disabled
|
|
72
|
+
prefix caching and chunked prefill. Only prompt activations are captured.
|
|
73
|
+
|
|
74
|
+
## Validation
|
|
75
|
+
|
|
76
|
+
One script freezes inputs, compares both backends sequentially for English,
|
|
77
|
+
German and Spanish, and saves JSON results and Markdown reports.
|
|
78
|
+
uv installs the package and dependencies automatically:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv run benchmarks/validate.py all
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Hugging Face Transformers and vLLM produce close activations, but only 3/18 sites
|
|
85
|
+
passed all agreement checks per language. Interchangeability for sparse autoencoder training
|
|
86
|
+
remains unvalidated.
|
|
87
|
+
|
|
88
|
+
The table summarises a batch-8 validation sample with 16,384 tokens/language from
|
|
89
|
+
FineWeb-Edu (English) and FineWeb2 (German/Spanish). Cosine measures direction
|
|
90
|
+
(1 means aligned). Normalised mean squared error (MSE) divides squared error by
|
|
91
|
+
the reference's squared norm. Both backends share the Hugging Face Transformers
|
|
92
|
+
reference root mean square as their scale.
|
|
93
|
+
It retains magnitude differences; 0 means identical. Values are mean +/- population standard deviation (SD)
|
|
94
|
+
across 18 equally weighted sites.
|
|
95
|
+
|
|
96
|
+
| Language | Mean cosine +/- SD | Normalised MSE +/- SD |
|
|
97
|
+
|---|---:|---:|
|
|
98
|
+
| English | 0.999002 +/- 0.001144 | 0.001879 +/- 0.002171 |
|
|
99
|
+
| German | 0.998623 +/- 0.001719 | 0.002650 +/- 0.003230 |
|
|
100
|
+
| Spanish | 0.998701 +/- 0.001619 | 0.002435 +/- 0.002989 |
|
|
101
|
+
|
|
102
|
+
The discrepancy remains unresolved. The default benchmark uses exactly
|
|
103
|
+
1,000,000 tokens per language.
|
|
104
|
+
See the [report](report.pdf) and [source](report.tex).
|
|
105
|
+
|
|
106
|
+
Runs used Python 3.12, Hugging Face Transformers 4.57.6/PyTorch 2.8.0a0, and
|
|
107
|
+
vLLM 0.27.1/PyTorch 2.14.0a0.
|
|
108
|
+
|
|
109
|
+
## Contributing
|
|
110
|
+
|
|
111
|
+
Include a reproducer and preserve hook semantics, token ordering and attribution.
|
|
112
|
+
Run `uv sync --group dev`, `uv run pytest` and `uv build`.
|
|
113
|
+
Compile the report with `pdflatex report.tex`.
|
|
114
|
+
|
|
115
|
+
## Citation
|
|
116
|
+
|
|
117
|
+
Antonio Lobo-Santos, DFKI. Download [BibTeX](CITATION.bib):
|
|
118
|
+
|
|
119
|
+
```bibtex
|
|
120
|
+
@misc{lobo_santos2026nemotron_extraction,
|
|
121
|
+
author = {Lobo-Santos, Antonio},
|
|
122
|
+
title = {{Nemotron Extraction}},
|
|
123
|
+
year = {2026},
|
|
124
|
+
howpublished = {Software, version 1.0.0},
|
|
125
|
+
note = {DFKI}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Nemotron extraction 1.0
|
|
2
|
+
|
|
3
|
+
Activations describe a model's internal state as it processes tokens.
|
|
4
|
+
This package captures them from the official
|
|
5
|
+
[`nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16`](https://huggingface.co/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16)
|
|
6
|
+
checkpoint through two backends: Hugging Face Transformers, running with PyTorch,
|
|
7
|
+
and vLLM, an inference engine.
|
|
8
|
+
|
|
9
|
+
A hook captures activations at a chosen location (site).
|
|
10
|
+
In vLLM, hooks run inside worker processes on the graphics processor (GPU).
|
|
11
|
+
The driver installs hooks through `collective_rpc` and submits token IDs.
|
|
12
|
+
Workers copy captures into pinned host buffers on a separate CUDA stream and
|
|
13
|
+
save Safetensors files, allowing transfers and writes to overlap inference.
|
|
14
|
+
Request IDs preserve sequence order, including repeats.
|
|
15
|
+
|
|
16
|
+
Hooks reconstruct residuals and clone tensors before later operations overwrite them.
|
|
17
|
+
|
|
18
|
+
## Install and use
|
|
19
|
+
|
|
20
|
+
Python 3.10+; vLLM requires Linux and an NVIDIA GPU.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv venv
|
|
24
|
+
uv pip install 'nemotron-extraction[hf,vllm,benchmark]==1.0.0'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import torch
|
|
29
|
+
from nemotron_extraction import HookSite, VLLMExtractor
|
|
30
|
+
|
|
31
|
+
sites = [HookSite(12, "resid_post"), HookSite(12, "attn_out_prelinear")]
|
|
32
|
+
with VLLMExtractor(
|
|
33
|
+
"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16", sites,
|
|
34
|
+
) as extractor:
|
|
35
|
+
batch = extractor.extract(torch.tensor([[1, 2, 3]], dtype=torch.long))
|
|
36
|
+
print(batch.activations["L12_resid_post"].shape) # (3, hidden_width)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Both support `await extract_async(token_ids)` and
|
|
40
|
+
`stream_to_disk(token_batches, output_dir)`, yielding saved-file metadata.
|
|
41
|
+
At most two batches await saving. Layers are zero-based. Sites:
|
|
42
|
+
`resid_pre`, `resid_post`, `mamba_out`, `moe_out`, `attn_out`,
|
|
43
|
+
`attn_out_prelinear`; mixer-specific sites require a compatible layer.
|
|
44
|
+
Inputs are rectangular int64 batches without padding; outputs in host memory have shape
|
|
45
|
+
`(batch × sequence_length, site_width)` in sequence-major order.
|
|
46
|
+
Use separate calls for different lengths.
|
|
47
|
+
|
|
48
|
+
Extraction requires eager execution, tensor parallelism of one, and disabled
|
|
49
|
+
prefix caching and chunked prefill. Only prompt activations are captured.
|
|
50
|
+
|
|
51
|
+
## Validation
|
|
52
|
+
|
|
53
|
+
One script freezes inputs, compares both backends sequentially for English,
|
|
54
|
+
German and Spanish, and saves JSON results and Markdown reports.
|
|
55
|
+
uv installs the package and dependencies automatically:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv run benchmarks/validate.py all
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Hugging Face Transformers and vLLM produce close activations, but only 3/18 sites
|
|
62
|
+
passed all agreement checks per language. Interchangeability for sparse autoencoder training
|
|
63
|
+
remains unvalidated.
|
|
64
|
+
|
|
65
|
+
The table summarises a batch-8 validation sample with 16,384 tokens/language from
|
|
66
|
+
FineWeb-Edu (English) and FineWeb2 (German/Spanish). Cosine measures direction
|
|
67
|
+
(1 means aligned). Normalised mean squared error (MSE) divides squared error by
|
|
68
|
+
the reference's squared norm. Both backends share the Hugging Face Transformers
|
|
69
|
+
reference root mean square as their scale.
|
|
70
|
+
It retains magnitude differences; 0 means identical. Values are mean +/- population standard deviation (SD)
|
|
71
|
+
across 18 equally weighted sites.
|
|
72
|
+
|
|
73
|
+
| Language | Mean cosine +/- SD | Normalised MSE +/- SD |
|
|
74
|
+
|---|---:|---:|
|
|
75
|
+
| English | 0.999002 +/- 0.001144 | 0.001879 +/- 0.002171 |
|
|
76
|
+
| German | 0.998623 +/- 0.001719 | 0.002650 +/- 0.003230 |
|
|
77
|
+
| Spanish | 0.998701 +/- 0.001619 | 0.002435 +/- 0.002989 |
|
|
78
|
+
|
|
79
|
+
The discrepancy remains unresolved. The default benchmark uses exactly
|
|
80
|
+
1,000,000 tokens per language.
|
|
81
|
+
See the [report](report.pdf) and [source](report.tex).
|
|
82
|
+
|
|
83
|
+
Runs used Python 3.12, Hugging Face Transformers 4.57.6/PyTorch 2.8.0a0, and
|
|
84
|
+
vLLM 0.27.1/PyTorch 2.14.0a0.
|
|
85
|
+
|
|
86
|
+
## Contributing
|
|
87
|
+
|
|
88
|
+
Include a reproducer and preserve hook semantics, token ordering and attribution.
|
|
89
|
+
Run `uv sync --group dev`, `uv run pytest` and `uv build`.
|
|
90
|
+
Compile the report with `pdflatex report.tex`.
|
|
91
|
+
|
|
92
|
+
## Citation
|
|
93
|
+
|
|
94
|
+
Antonio Lobo-Santos, DFKI. Download [BibTeX](CITATION.bib):
|
|
95
|
+
|
|
96
|
+
```bibtex
|
|
97
|
+
@misc{lobo_santos2026nemotron_extraction,
|
|
98
|
+
author = {Lobo-Santos, Antonio},
|
|
99
|
+
title = {{Nemotron Extraction}},
|
|
100
|
+
year = {2026},
|
|
101
|
+
howpublished = {Software, version 1.0.0},
|
|
102
|
+
note = {DFKI}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = ["nemotron-extraction==1.0.0"]
|
|
4
|
+
# ///
|
|
5
|
+
"""Aggregate aligned range receipts into exactly 1M validated tokens/language."""
|
|
6
|
+
import argparse
|
|
7
|
+
import importlib.util
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
from safetensors.torch import load_file
|
|
13
|
+
|
|
14
|
+
spec = importlib.util.spec_from_file_location("validate", Path(__file__).with_name("validate.py"))
|
|
15
|
+
validate = importlib.util.module_from_spec(spec)
|
|
16
|
+
spec.loader.exec_module(validate)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def aggregate(paths, output, expected=1_000_000):
|
|
20
|
+
receipts = []
|
|
21
|
+
for path in paths:
|
|
22
|
+
hf = json.loads((path / "hf.json").read_text())
|
|
23
|
+
vllm = json.loads((path / "vllm.json").read_text())
|
|
24
|
+
validate.report(argparse.Namespace(output=path))
|
|
25
|
+
receipts.append((hf["start_token"], path, hf, vllm))
|
|
26
|
+
receipts.sort()
|
|
27
|
+
first = receipts[0]
|
|
28
|
+
end = 0
|
|
29
|
+
combined = {}
|
|
30
|
+
for start, path, hf, vllm in receipts:
|
|
31
|
+
if start != end:
|
|
32
|
+
raise ValueError("Ranges must be contiguous, non-overlapping and begin at zero")
|
|
33
|
+
for engine, record, baseline in (("hf", hf, first[2]), ("vllm", vllm, first[3])):
|
|
34
|
+
for key in ("language", "manifest_sha256", "batch_size", "sequence_length", "topology", "software", "script_sha256", "source", "gpu", "gpu_memory_utilization", "kv_cache_memory_bytes", "cuda_graphs_enabled"):
|
|
35
|
+
if record[key] != baseline[key]:
|
|
36
|
+
raise ValueError(f"Ranges disagree on {engine} {key}")
|
|
37
|
+
end += hf["tokens"]
|
|
38
|
+
cosines = load_file(str(path / "cosines.safetensors"))
|
|
39
|
+
for key, metric in vllm["agreement"].items():
|
|
40
|
+
if cosines[key].numel() != hf["tokens"]:
|
|
41
|
+
raise ValueError("Cosine rows do not match counted tokens")
|
|
42
|
+
target = combined.setdefault(key, validate.Agreement())
|
|
43
|
+
target.cosines.append(cosines[key])
|
|
44
|
+
target.error2 += metric["error_squared_sum"]
|
|
45
|
+
target.reference2 += metric["reference_squared_sum"]
|
|
46
|
+
target.max_error = max(target.max_error, metric["maximum_absolute_error"])
|
|
47
|
+
if end != expected:
|
|
48
|
+
raise ValueError(f"Expected exactly {expected:,} tokens, got {end:,}")
|
|
49
|
+
output.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
for engine, column in (("hf", 2), ("vllm", 3)):
|
|
51
|
+
result = dict(first[column])
|
|
52
|
+
result.update(tokens=end, start_token=0, ranges=len(receipts))
|
|
53
|
+
for key in ("model_load_seconds", "hook_install_seconds", "startup_seconds", "warmup_seconds", "inference_seconds", "capture_copy_seconds", "driver_read_seconds", "extraction_seconds", "comparison_seconds", "persistence_seconds", "wall_seconds"):
|
|
54
|
+
result[key] = sum(row[column][key] for row in receipts)
|
|
55
|
+
result["tokens_per_second"] = end / result["extraction_seconds"]
|
|
56
|
+
result["peak_gpu_memory_bytes"] = max(row[column]["peak_gpu_memory_bytes"] for row in receipts)
|
|
57
|
+
if engine == "vllm":
|
|
58
|
+
result["agreement"] = {key: value.result() for key, value in combined.items()}
|
|
59
|
+
result["passed"] = all(value["passed"] for value in result["agreement"].values())
|
|
60
|
+
validate.write_json(output / f"{engine}.json", result)
|
|
61
|
+
validate.report(argparse.Namespace(output=output))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
66
|
+
parser.add_argument("--outputs", nargs="+", type=Path, required=True)
|
|
67
|
+
parser.add_argument("--output", type=Path, required=True)
|
|
68
|
+
args = parser.parse_args()
|
|
69
|
+
aggregate(args.outputs, args.output)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Verify installed worker discovery and engine interfaces outside the checkout."""
|
|
2
|
+
import importlib.metadata
|
|
3
|
+
import inspect
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import nemotron_extraction
|
|
6
|
+
from nemotron_extraction.worker import ActivationHookExtension
|
|
7
|
+
|
|
8
|
+
print("package:", Path(nemotron_extraction.__file__).resolve())
|
|
9
|
+
print("worker:", ActivationHookExtension.__module__)
|
|
10
|
+
from vllm import AsyncEngineArgs
|
|
11
|
+
from vllm.v1.engine.async_llm import AsyncLLM
|
|
12
|
+
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
|
|
13
|
+
from vllm.v1.engine.input_processor import InputProcessor
|
|
14
|
+
|
|
15
|
+
print("vllm:", importlib.metadata.version("vllm"))
|
|
16
|
+
for name in ("collective_rpc", "generate", "shutdown", "abort"):
|
|
17
|
+
print(name, inspect.signature(getattr(AsyncLLM, name)))
|
|
18
|
+
for name in ("max_num_seqs", "kv_cache_memory_bytes", "worker_extension_cls", "enforce_eager"):
|
|
19
|
+
assert name in inspect.signature(AsyncEngineArgs).parameters, name
|
|
20
|
+
source = inspect.getsource(GPUModelRunner)
|
|
21
|
+
for fragment in ("self.input_batch", "self.query_start_loc", "num_computed_tokens_cpu"):
|
|
22
|
+
assert fragment in source, f"Unsupported runner: missing {fragment}"
|
|
23
|
+
print("request-ID assignment:")
|
|
24
|
+
print(inspect.getsource(InputProcessor.assign_request_id))
|
|
25
|
+
print("Installed package and worker interfaces verified")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Estimate tight job bounds from the three completed pilot receipts.
|
|
2
|
+
|
|
3
|
+
Prints a sequential job schedule; it does not submit jobs.
|
|
4
|
+
uv run --no-project benchmarks/schedule.py --pilot PATH --batch-size 4
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def schedule(pilot, batch_size):
|
|
13
|
+
jobs = []
|
|
14
|
+
records = {}
|
|
15
|
+
for language in ("en", "de", "es"):
|
|
16
|
+
path = pilot / f"{language}-0"
|
|
17
|
+
hf = json.loads((path / "hf.json").read_text())
|
|
18
|
+
vllm = json.loads((path / "vllm.json").read_text())
|
|
19
|
+
if hf["tokens"] != 16384 or vllm["tokens"] != 16384:
|
|
20
|
+
raise ValueError("Need complete 16,384-token pilot receipts")
|
|
21
|
+
if hf["batch_size"] != batch_size or vllm["batch_size"] != batch_size:
|
|
22
|
+
raise ValueError("Both pilot backends must use the selected batch size")
|
|
23
|
+
records[language] = (hf, vllm)
|
|
24
|
+
# A new allocation can have cold weights even if later pilot languages were warm.
|
|
25
|
+
startup = max(pair[0]["startup_seconds"] for pair in records.values())
|
|
26
|
+
startup += max(pair[1]["startup_seconds"] for pair in records.values())
|
|
27
|
+
for language, (hf, vllm) in records.items():
|
|
28
|
+
# Wall minus startup includes warmup, cleanup, persistence and comparison.
|
|
29
|
+
fixed = max(0, hf["wall_seconds"] - hf["startup_seconds"] - hf["extraction_seconds"] - hf["persistence_seconds"])
|
|
30
|
+
fixed += max(0, vllm["wall_seconds"] - vllm["startup_seconds"] - vllm["extraction_seconds"] - vllm["comparison_seconds"])
|
|
31
|
+
per_token = (hf["extraction_seconds"] + hf["persistence_seconds"] +
|
|
32
|
+
vllm["extraction_seconds"] + vllm["comparison_seconds"]) / 16384
|
|
33
|
+
available = (3600 - 120) / 1.25 - startup - fixed
|
|
34
|
+
maximum = int(available / per_token / 2048) * 2048
|
|
35
|
+
if maximum < 2048:
|
|
36
|
+
raise ValueError("Model startup leaves no useful work in a one-hour job")
|
|
37
|
+
start = 0
|
|
38
|
+
while start < 1_000_000:
|
|
39
|
+
count = min(maximum, 1_000_000 - start)
|
|
40
|
+
minutes = math.ceil((1.25 * (startup + fixed + count * per_token) + 120) / 300) * 5
|
|
41
|
+
jobs.append({"language": language, "start": start, "tokens": count,
|
|
42
|
+
"minutes": min(60, minutes), "batch_size": batch_size})
|
|
43
|
+
start += count
|
|
44
|
+
return jobs
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
49
|
+
parser.add_argument("--pilot", type=Path, required=True)
|
|
50
|
+
parser.add_argument("--batch-size", type=int, choices=(1, 2, 4, 8, 16, 32, 64, 128), default=4)
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
print(json.dumps(schedule(args.pilot, args.batch_size), indent=2))
|