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.
Files changed (37) hide show
  1. nemotron_extraction-1.0.0/CITATION.bib +8 -0
  2. nemotron_extraction-1.0.0/LICENSE +21 -0
  3. nemotron_extraction-1.0.0/MANIFEST.in +6 -0
  4. nemotron_extraction-1.0.0/NOTICE +5 -0
  5. nemotron_extraction-1.0.0/PKG-INFO +127 -0
  6. nemotron_extraction-1.0.0/README.md +104 -0
  7. nemotron_extraction-1.0.0/benchmarks/aggregate.py +69 -0
  8. nemotron_extraction-1.0.0/benchmarks/check_install.py +25 -0
  9. nemotron_extraction-1.0.0/benchmarks/schedule.py +52 -0
  10. nemotron_extraction-1.0.0/benchmarks/sweep.py +408 -0
  11. nemotron_extraction-1.0.0/benchmarks/targeted_validation.py +355 -0
  12. nemotron_extraction-1.0.0/benchmarks/validate.py +378 -0
  13. nemotron_extraction-1.0.0/pyproject.toml +31 -0
  14. nemotron_extraction-1.0.0/report.pdf +0 -0
  15. nemotron_extraction-1.0.0/report.tex +86 -0
  16. nemotron_extraction-1.0.0/setup.cfg +4 -0
  17. nemotron_extraction-1.0.0/src/nemotron_extraction/__init__.py +7 -0
  18. nemotron_extraction-1.0.0/src/nemotron_extraction/hf.py +239 -0
  19. nemotron_extraction-1.0.0/src/nemotron_extraction/hooks.py +110 -0
  20. nemotron_extraction-1.0.0/src/nemotron_extraction/transport.py +66 -0
  21. nemotron_extraction-1.0.0/src/nemotron_extraction/types.py +50 -0
  22. nemotron_extraction-1.0.0/src/nemotron_extraction/vllm.py +253 -0
  23. nemotron_extraction-1.0.0/src/nemotron_extraction/worker.py +205 -0
  24. nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/PKG-INFO +127 -0
  25. nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/SOURCES.txt +35 -0
  26. nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/dependency_links.txt +1 -0
  27. nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/requires.txt +15 -0
  28. nemotron_extraction-1.0.0/src/nemotron_extraction.egg-info/top_level.txt +1 -0
  29. nemotron_extraction-1.0.0/tests/test_aggregate.py +53 -0
  30. nemotron_extraction-1.0.0/tests/test_async.py +92 -0
  31. nemotron_extraction-1.0.0/tests/test_benchmark.py +114 -0
  32. nemotron_extraction-1.0.0/tests/test_hooks.py +235 -0
  33. nemotron_extraction-1.0.0/tests/test_schedule.py +47 -0
  34. nemotron_extraction-1.0.0/tests/test_sweep.py +42 -0
  35. nemotron_extraction-1.0.0/tests/test_targeted.py +96 -0
  36. nemotron_extraction-1.0.0/tests/test_worker.py +114 -0
  37. 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,6 @@
1
+ include LICENSE NOTICE CITATION.bib
2
+ include uv.lock
3
+ include report.tex report.pdf
4
+ recursive-include benchmarks *.py
5
+ prune outputs
6
+ prune internal_docs
@@ -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))