NeMo-Export-Deploy 0.7.0__tar.gz → 0.8.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.
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/PKG-INFO +3 -3
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/SOURCES.txt +1 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/requires.txt +2 -2
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/PKG-INFO +3 -3
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/inference_base.py +19 -6
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/tron_utils.py +2 -2
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/megatronllm_deployable.py +36 -3
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/megatronllm_deployable_ray.py +2 -0
- nemo_export_deploy-0.8.0/nemo_deploy/multimodal/image_url_validator.py +173 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/multimodal/megatron_multimodal_deployable.py +9 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/multimodal/query_multimodal.py +4 -3
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/service/fastapi_interface_to_pytriton.py +1 -1
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/service/fastapi_interface_to_pytriton_multimodal.py +1 -1
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/model_adapters/embedding/embedding_adapter.py +1 -1
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/utils/lora_converter.py +1 -1
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/utils/utils.py +2 -2
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/vllm_exporter.py +0 -4
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export_deploy_common/import_utils.py +1 -1
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export_deploy_common/package_info.py +1 -1
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/pyproject.toml +42 -14
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/LICENSE +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/dependency_links.txt +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/top_level.txt +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/README.md +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/deploy_base.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/deploy_pytriton.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/deploy_ray.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/hf_deployable.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/hf_deployable_ray.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/nemo_io.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/nemo_utils.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/query_llm.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/multimodal/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/multimodal/megatron_multimodal_deployable_ray.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/package_info.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/ray_utils.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/service/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/triton_deployable.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/utils.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/model_adapters/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/model_adapters/embedding/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/model_adapters/reranker/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/model_adapters/reranker/reranker_adapter.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/onnx_llm_exporter.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/package_info.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/sentencepiece_tokenizer.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/tarutils.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/tiktoken_tokenizer.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/utils/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/utils/_mock_import.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/utils/constants.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/utils/model_loader.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export_deploy_common/__init__.py +0 -0
- {nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: NeMo-Export-Deploy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: NeMo Export and Deploy - a library to export and deploy LLMs and MMs
|
|
5
5
|
License: Apache 2.0
|
|
6
6
|
Requires-Python: <3.13,>=3.12
|
|
@@ -31,7 +31,7 @@ Requires-Dist: torchmetrics>=0.11.0
|
|
|
31
31
|
Requires-Dist: wandb==0.27.2
|
|
32
32
|
Requires-Dist: webdataset>=0.2.86
|
|
33
33
|
Requires-Dist: nvidia-pytriton; platform_system != "Darwin"
|
|
34
|
-
Requires-Dist: flashinfer-python
|
|
34
|
+
Requires-Dist: flashinfer-python; platform_system != "Darwin"
|
|
35
35
|
Requires-Dist: Pillow; platform_system != "Darwin" and platform_machine != "aarch64"
|
|
36
36
|
Requires-Dist: decord; platform_system != "Darwin" and platform_machine != "aarch64"
|
|
37
37
|
Requires-Dist: pyparsing>2.0.2
|
|
@@ -42,7 +42,7 @@ Requires-Dist: ijson
|
|
|
42
42
|
Requires-Dist: pyarrow<21.0.0,>=20.0.0
|
|
43
43
|
Provides-Extra: inframework
|
|
44
44
|
Provides-Extra: vllm
|
|
45
|
-
Requires-Dist: vllm~=0.
|
|
45
|
+
Requires-Dist: vllm~=0.29.0; extra == "vllm"
|
|
46
46
|
Requires-Dist: pandas; extra == "vllm"
|
|
47
47
|
Requires-Dist: timm; extra == "vllm"
|
|
48
48
|
Provides-Extra: trt-onnx
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/SOURCES.txt
RENAMED
|
@@ -26,6 +26,7 @@ nemo_deploy/llm/inference/nemo_io.py
|
|
|
26
26
|
nemo_deploy/llm/inference/nemo_utils.py
|
|
27
27
|
nemo_deploy/llm/inference/tron_utils.py
|
|
28
28
|
nemo_deploy/multimodal/__init__.py
|
|
29
|
+
nemo_deploy/multimodal/image_url_validator.py
|
|
29
30
|
nemo_deploy/multimodal/megatron_multimodal_deployable.py
|
|
30
31
|
nemo_deploy/multimodal/megatron_multimodal_deployable_ray.py
|
|
31
32
|
nemo_deploy/multimodal/query_multimodal.py
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/requires.txt
RENAMED
|
@@ -28,7 +28,7 @@ pyarrow<21.0.0,>=20.0.0
|
|
|
28
28
|
|
|
29
29
|
[:platform_system != "Darwin"]
|
|
30
30
|
nvidia-pytriton
|
|
31
|
-
flashinfer-python
|
|
31
|
+
flashinfer-python
|
|
32
32
|
|
|
33
33
|
[:platform_system != "Darwin" and platform_machine != "aarch64"]
|
|
34
34
|
Pillow
|
|
@@ -48,6 +48,6 @@ onnxscript>=0.6.0
|
|
|
48
48
|
transformers==4.51.3
|
|
49
49
|
|
|
50
50
|
[vllm]
|
|
51
|
-
vllm~=0.
|
|
51
|
+
vllm~=0.29.0
|
|
52
52
|
pandas
|
|
53
53
|
timm
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: NeMo-Export-Deploy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: NeMo Export and Deploy - a library to export and deploy LLMs and MMs
|
|
5
5
|
License: Apache 2.0
|
|
6
6
|
Requires-Python: <3.13,>=3.12
|
|
@@ -31,7 +31,7 @@ Requires-Dist: torchmetrics>=0.11.0
|
|
|
31
31
|
Requires-Dist: wandb==0.27.2
|
|
32
32
|
Requires-Dist: webdataset>=0.2.86
|
|
33
33
|
Requires-Dist: nvidia-pytriton; platform_system != "Darwin"
|
|
34
|
-
Requires-Dist: flashinfer-python
|
|
34
|
+
Requires-Dist: flashinfer-python; platform_system != "Darwin"
|
|
35
35
|
Requires-Dist: Pillow; platform_system != "Darwin" and platform_machine != "aarch64"
|
|
36
36
|
Requires-Dist: decord; platform_system != "Darwin" and platform_machine != "aarch64"
|
|
37
37
|
Requires-Dist: pyparsing>2.0.2
|
|
@@ -42,7 +42,7 @@ Requires-Dist: ijson
|
|
|
42
42
|
Requires-Dist: pyarrow<21.0.0,>=20.0.0
|
|
43
43
|
Provides-Extra: inframework
|
|
44
44
|
Provides-Extra: vllm
|
|
45
|
-
Requires-Dist: vllm~=0.
|
|
45
|
+
Requires-Dist: vllm~=0.29.0; extra == "vllm"
|
|
46
46
|
Requires-Dist: pandas; extra == "vllm"
|
|
47
47
|
Requires-Dist: timm; extra == "vllm"
|
|
48
48
|
Provides-Extra: trt-onnx
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/inference_base.py
RENAMED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import atexit
|
|
17
17
|
import logging
|
|
18
18
|
import os
|
|
19
|
+
import socket
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from typing import List, Optional, Tuple, Union
|
|
21
22
|
|
|
@@ -414,6 +415,22 @@ class MCoreEngineWithCleanup:
|
|
|
414
415
|
return getattr(self.mcore_engine, name)
|
|
415
416
|
|
|
416
417
|
|
|
418
|
+
def _default_coordinator_host() -> str:
|
|
419
|
+
"""Return a bindable address for the data-parallel inference coordinator.
|
|
420
|
+
|
|
421
|
+
MCore defaults the coordinator host to ``socket.gethostname()``, which is a
|
|
422
|
+
*name*, not an address. Inside Docker it resolves to the container ID and ZMQ
|
|
423
|
+
cannot bind it (``ZMQError: No such device``). Resolving that name to its IP
|
|
424
|
+
yields an address that is both bindable and reachable from other containers,
|
|
425
|
+
which is what the Ray path already gets by exporting the node IP as
|
|
426
|
+
``MASTER_ADDR``. Fall back to loopback only if resolution fails.
|
|
427
|
+
"""
|
|
428
|
+
try:
|
|
429
|
+
return socket.gethostbyname(socket.gethostname())
|
|
430
|
+
except OSError:
|
|
431
|
+
return "127.0.0.1"
|
|
432
|
+
|
|
433
|
+
|
|
417
434
|
def create_mcore_engine(
|
|
418
435
|
path: Path,
|
|
419
436
|
num_devices: Optional[int] = None,
|
|
@@ -519,14 +536,10 @@ def create_mcore_engine(
|
|
|
519
536
|
buffer_size_gb=int(buffer_size_gb),
|
|
520
537
|
max_requests=max_batch_size,
|
|
521
538
|
block_size_tokens=block_size_tokens,
|
|
522
|
-
|
|
523
|
-
# per-request runtime toggle (as _infer_fn used to do) only ever reaches the primary
|
|
524
|
-
# rank's engine. Prompt log probs require this to be False on every rank, so it's set
|
|
525
|
-
# here unconditionally at construction time instead.
|
|
526
|
-
materialize_only_last_token_logits=False,
|
|
539
|
+
materialize_only_last_token_logits=True,
|
|
527
540
|
)
|
|
528
541
|
|
|
529
|
-
coordinator_host = os.environ.get("MASTER_ADDR")
|
|
542
|
+
coordinator_host = os.environ.get("MASTER_ADDR") or _default_coordinator_host()
|
|
530
543
|
|
|
531
544
|
llm = MegatronLLM(
|
|
532
545
|
model=model,
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/tron_utils.py
RENAMED
|
@@ -261,7 +261,7 @@ def _set_random_seed(
|
|
|
261
261
|
te_rng_tracker: bool = False,
|
|
262
262
|
inference_rng_tracker: bool = False,
|
|
263
263
|
) -> None:
|
|
264
|
-
"""Set random seed for
|
|
264
|
+
"""Set random seed for reproducibility.
|
|
265
265
|
|
|
266
266
|
Args:
|
|
267
267
|
seed_ (int): Base random seed to use
|
|
@@ -332,7 +332,7 @@ def _initialize_tp_communicators(model_config: Union[GPTConfig, T5Config], micro
|
|
|
332
332
|
bootstrap_backend=bootstrap_backend,
|
|
333
333
|
)
|
|
334
334
|
except TypeError:
|
|
335
|
-
# Fallback for older
|
|
335
|
+
# Fallback for older Transformer Engine versions
|
|
336
336
|
if bootstrap_backend != "mpi":
|
|
337
337
|
LOGGER.info("Warning: Transformer Engine may only support MPI bootstrap backend")
|
|
338
338
|
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/megatronllm_deployable.py
RENAMED
|
@@ -69,7 +69,7 @@ class MegatronLLMDeployable(ITritonDeployable):
|
|
|
69
69
|
context_parallel_size (int): context parallelism.
|
|
70
70
|
expert_model_parallel_size (int): expert parallelism.
|
|
71
71
|
params_dtype (torch.dtype): max input length.
|
|
72
|
-
inference_batch_times_seqlen_threshold (int):
|
|
72
|
+
inference_batch_times_seqlen_threshold (int): sequence threshold.
|
|
73
73
|
inference_max_seq_length (int): max_seq_length for inference. Required by MCoreEngine (>=0.12). Defaults to
|
|
74
74
|
4096.
|
|
75
75
|
max_batch_size (int): max batch size for inference. Defaults to 32.
|
|
@@ -205,6 +205,12 @@ class MegatronLLMDeployable(ITritonDeployable):
|
|
|
205
205
|
stop_words=stop_words,
|
|
206
206
|
)
|
|
207
207
|
|
|
208
|
+
if log_probs:
|
|
209
|
+
dynamic_engine = getattr(self.mcore_engine, "engine", None)
|
|
210
|
+
if dynamic_engine is not None:
|
|
211
|
+
dynamic_engine.materialize_only_last_token_logits = False
|
|
212
|
+
dynamic_engine.context.config.materialize_only_last_token_logits = False
|
|
213
|
+
|
|
208
214
|
self.generate(prompts, inference_params)
|
|
209
215
|
else:
|
|
210
216
|
return
|
|
@@ -394,6 +400,22 @@ class MegatronLLMDeployable(ITritonDeployable):
|
|
|
394
400
|
if apply_chat_template:
|
|
395
401
|
prompts = [self.apply_chat_template(prompt) for prompt in prompts]
|
|
396
402
|
|
|
403
|
+
if torch.distributed.is_initialized():
|
|
404
|
+
if torch.distributed.get_world_size() > 1:
|
|
405
|
+
torch.distributed.broadcast(torch.tensor([0], dtype=torch.long, device="cuda"), src=0)
|
|
406
|
+
broadcast_list(prompts, src=0)
|
|
407
|
+
broadcast_list(
|
|
408
|
+
data=[
|
|
409
|
+
temperature,
|
|
410
|
+
top_k,
|
|
411
|
+
top_p,
|
|
412
|
+
num_tokens_to_generate,
|
|
413
|
+
log_probs,
|
|
414
|
+
stop_words,
|
|
415
|
+
],
|
|
416
|
+
src=0,
|
|
417
|
+
)
|
|
418
|
+
|
|
397
419
|
# cast top_k,top_p to native int, float since typecheck assert statements added in MCore0.13 error otherwise
|
|
398
420
|
# skip_prompt_log_probs=False (default) includes prompt tokens in top-N logprobs when top_logprobs>0.
|
|
399
421
|
inference_params = SamplingParams(
|
|
@@ -407,6 +429,17 @@ class MegatronLLMDeployable(ITritonDeployable):
|
|
|
407
429
|
stop_words=stop_words,
|
|
408
430
|
)
|
|
409
431
|
|
|
432
|
+
# Mcore's dynamic inference engine defaults materialize_only_last_token_logits=True for
|
|
433
|
+
# performance, but prompt log probs require all token logits to be materialized
|
|
434
|
+
# (prompt log probs are required for logprob eval benchmarks).
|
|
435
|
+
# Toggle it on both the engine and the context config (controls the
|
|
436
|
+
# model forward pass and log prob calculations).
|
|
437
|
+
dynamic_engine = getattr(self.mcore_engine, "engine", None)
|
|
438
|
+
needs_all_logits = log_probs or bool(top_logprobs)
|
|
439
|
+
if dynamic_engine is not None and needs_all_logits:
|
|
440
|
+
dynamic_engine.materialize_only_last_token_logits = False
|
|
441
|
+
dynamic_engine.context.config.materialize_only_last_token_logits = False
|
|
442
|
+
|
|
410
443
|
results = self.generate(prompts, inference_params)
|
|
411
444
|
# Handle DynamicInferenceRequestRecord objects by merging them into a single request
|
|
412
445
|
results = [
|
|
@@ -422,7 +455,7 @@ class MegatronLLMDeployable(ITritonDeployable):
|
|
|
422
455
|
if log_probs:
|
|
423
456
|
output_log_probs = []
|
|
424
457
|
for r in results:
|
|
425
|
-
# Convert to torch tensor and then move to cpu as generated_log_probs is a list and
|
|
458
|
+
# Convert to torch tensor and then move to cpu as generated_log_probs is a list and cannot be moved
|
|
426
459
|
# to cpu otherwise
|
|
427
460
|
if echo:
|
|
428
461
|
lp = torch.tensor(r.prompt_log_probs + r.generated_log_probs).cpu().detach().numpy()
|
|
@@ -448,7 +481,7 @@ class MegatronLLMDeployable(ITritonDeployable):
|
|
|
448
481
|
if top_logprobs:
|
|
449
482
|
output_top_n_log_probs = []
|
|
450
483
|
for r in results:
|
|
451
|
-
# Convert to torch tensor and then move to cpu as generated_log_probs is a list and
|
|
484
|
+
# Convert to torch tensor and then move to cpu as generated_log_probs is a list and cannot be moved
|
|
452
485
|
# to cpu otherwise.
|
|
453
486
|
# top_logprobs for input tokens is supported with MCore 0.13 and above.
|
|
454
487
|
if echo:
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/megatronllm_deployable_ray.py
RENAMED
|
@@ -104,6 +104,8 @@ class ModelWorker:
|
|
|
104
104
|
legacy_model_format=legacy_model_format,
|
|
105
105
|
**model_config_kwargs,
|
|
106
106
|
)
|
|
107
|
+
if rank != 0:
|
|
108
|
+
self.model.generate_other_ranks()
|
|
107
109
|
except Exception as e:
|
|
108
110
|
LOGGER.error(f"Replica {replica_id} - Failed to initialize model for rank {rank}: {str(e)}")
|
|
109
111
|
raise
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
import ipaddress
|
|
19
|
+
import socket
|
|
20
|
+
from io import BytesIO
|
|
21
|
+
from urllib.parse import urljoin, urlparse
|
|
22
|
+
|
|
23
|
+
import urllib3
|
|
24
|
+
|
|
25
|
+
# Ranges that must never be reachable via a request-controlled image URL.
|
|
26
|
+
# 169.254.0.0/16 is the cloud IMDS range (AWS/GCP/Azure 169.254.169.254) —
|
|
27
|
+
# the primary SSRF target in cloud deployments.
|
|
28
|
+
_BLOCKED_NETWORKS = [
|
|
29
|
+
ipaddress.ip_network("127.0.0.0/8"), # loopback — server's own local services
|
|
30
|
+
ipaddress.ip_network("10.0.0.0/8"), # RFC 1918 private
|
|
31
|
+
ipaddress.ip_network("172.16.0.0/12"), # RFC 1918 private
|
|
32
|
+
ipaddress.ip_network("192.168.0.0/16"), # RFC 1918 private
|
|
33
|
+
ipaddress.ip_network("169.254.0.0/16"), # link-local / cloud IMDS
|
|
34
|
+
ipaddress.ip_network("::1/128"), # IPv6 loopback
|
|
35
|
+
ipaddress.ip_network("fc00::/7"), # IPv6 unique-local
|
|
36
|
+
ipaddress.ip_network("fe80::/10"), # IPv6 link-local (IPv6 cloud IMDS equivalent)
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _is_blocked_ip(ip: ipaddress.IPv4Address | ipaddress.IPv6Address) -> bool:
|
|
41
|
+
"""Return True if ip must not be reachable via an image URL fetch."""
|
|
42
|
+
if any(ip in net for net in _BLOCKED_NETWORKS):
|
|
43
|
+
return True
|
|
44
|
+
return not ip.is_global or ip.is_multicast or ip.is_reserved or ip.is_unspecified
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _resolve_and_validate(hostname: str) -> list[tuple[int, str]]:
|
|
48
|
+
"""Resolve hostname to every A/AAAA address and reject if any is blocked.
|
|
49
|
+
|
|
50
|
+
Returns a list of (address_family, ip_string) tuples for all resolved
|
|
51
|
+
addresses. Validating every returned address (rather than just the
|
|
52
|
+
first) guards against DNS answers that mix a public address with a
|
|
53
|
+
private/link-local one (DNS rebinding / multi-answer SSRF).
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
infos = socket.getaddrinfo(hostname, None)
|
|
57
|
+
except socket.gaierror as exc:
|
|
58
|
+
raise ValueError(f"Cannot resolve image URL hostname '{hostname}': {exc}") from exc
|
|
59
|
+
resolved: list[tuple[int, str]] = []
|
|
60
|
+
seen: set[str] = set()
|
|
61
|
+
for family, _, _, _, sockaddr in infos:
|
|
62
|
+
ip_str = sockaddr[0]
|
|
63
|
+
if ip_str in seen:
|
|
64
|
+
continue
|
|
65
|
+
seen.add(ip_str)
|
|
66
|
+
ip = ipaddress.ip_address(ip_str)
|
|
67
|
+
if _is_blocked_ip(ip):
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Image URL hostname '{hostname}' resolves to a blocked address ({ip}). "
|
|
70
|
+
"Private, loopback, link-local, and other non-global addresses are not allowed."
|
|
71
|
+
)
|
|
72
|
+
resolved.append((family, ip_str))
|
|
73
|
+
if not resolved:
|
|
74
|
+
raise ValueError(f"Cannot resolve image URL hostname '{hostname}': no addresses returned.")
|
|
75
|
+
return resolved
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_image_url(url: str) -> None:
|
|
79
|
+
"""Raise ValueError if url is not a safe http/https URL.
|
|
80
|
+
|
|
81
|
+
Rejects file://, non-http(s) schemes, and URLs that resolve to
|
|
82
|
+
private/link-local/loopback ranges (SSRF guard). This is a point-in-time
|
|
83
|
+
check; use fetch_image_bytes_safely()/fetch_image_data_uri_safely() to
|
|
84
|
+
actually retrieve the image, since those pin validation to the
|
|
85
|
+
connection itself and guard redirects.
|
|
86
|
+
"""
|
|
87
|
+
parsed = urlparse(url)
|
|
88
|
+
if parsed.scheme not in ("http", "https"):
|
|
89
|
+
raise ValueError(f"Unsupported image URL scheme '{parsed.scheme}'. Only http and https are allowed.")
|
|
90
|
+
hostname = parsed.hostname
|
|
91
|
+
if not hostname:
|
|
92
|
+
raise ValueError("Image URL has no hostname.")
|
|
93
|
+
_resolve_and_validate(hostname)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _pinned_request(url: str, hostname: str, ip: str, family: int, timeout: float) -> urllib3.HTTPResponse:
|
|
97
|
+
"""Issue a GET request connected directly to ip, without following redirects.
|
|
98
|
+
|
|
99
|
+
Connecting to the pre-validated IP directly (instead of letting the HTTP
|
|
100
|
+
client re-resolve hostname) closes the TOCTOU window between validation
|
|
101
|
+
and connection that DNS rebinding exploits. The Host header and TLS SNI
|
|
102
|
+
still use the original hostname so virtual hosting and certificate
|
|
103
|
+
validation behave normally.
|
|
104
|
+
"""
|
|
105
|
+
parsed = urlparse(url)
|
|
106
|
+
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
|
107
|
+
path = parsed.path or "/"
|
|
108
|
+
if parsed.query:
|
|
109
|
+
path = f"{path}?{parsed.query}"
|
|
110
|
+
pool_host = f"[{ip}]" if family == socket.AF_INET6 else ip
|
|
111
|
+
headers = {"Host": hostname}
|
|
112
|
+
if parsed.scheme == "https":
|
|
113
|
+
pool = urllib3.HTTPSConnectionPool(
|
|
114
|
+
pool_host,
|
|
115
|
+
port=port,
|
|
116
|
+
timeout=timeout,
|
|
117
|
+
assert_hostname=hostname,
|
|
118
|
+
server_hostname=hostname,
|
|
119
|
+
cert_reqs="CERT_REQUIRED",
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
pool = urllib3.HTTPConnectionPool(pool_host, port=port, timeout=timeout)
|
|
123
|
+
try:
|
|
124
|
+
return pool.request("GET", path, headers=headers, redirect=False, preload_content=True)
|
|
125
|
+
finally:
|
|
126
|
+
pool.close()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def fetch_image_bytes_safely(url: str, timeout: float = 5, max_redirects: int = 5) -> bytes:
|
|
130
|
+
"""Safely fetch image bytes from url, guarding against SSRF.
|
|
131
|
+
|
|
132
|
+
Unlike validate_image_url() followed by a separate request, this
|
|
133
|
+
resolves and validates every hostname (including redirect targets)
|
|
134
|
+
immediately before connecting, connects directly to the validated IP
|
|
135
|
+
address, and never auto-follows redirects — each hop is re-validated
|
|
136
|
+
from scratch.
|
|
137
|
+
"""
|
|
138
|
+
current_url = url
|
|
139
|
+
for _ in range(max_redirects + 1):
|
|
140
|
+
parsed = urlparse(current_url)
|
|
141
|
+
if parsed.scheme not in ("http", "https"):
|
|
142
|
+
raise ValueError(f"Unsupported image URL scheme '{parsed.scheme}'. Only http and https are allowed.")
|
|
143
|
+
hostname = parsed.hostname
|
|
144
|
+
if not hostname:
|
|
145
|
+
raise ValueError("Image URL has no hostname.")
|
|
146
|
+
resolved = _resolve_and_validate(hostname)
|
|
147
|
+
family, ip = resolved[0]
|
|
148
|
+
response = _pinned_request(current_url, hostname, ip, family, timeout)
|
|
149
|
+
if response.status in (301, 302, 303, 307, 308):
|
|
150
|
+
location = response.headers.get("Location")
|
|
151
|
+
if not location:
|
|
152
|
+
raise ValueError(f"Redirect response from '{current_url}' is missing a Location header.")
|
|
153
|
+
current_url = urljoin(current_url, location)
|
|
154
|
+
continue
|
|
155
|
+
if response.status >= 400:
|
|
156
|
+
raise ValueError(f"Failed to fetch image URL '{current_url}': HTTP {response.status}")
|
|
157
|
+
return response.data
|
|
158
|
+
raise ValueError(f"Exceeded max redirects ({max_redirects}) while fetching image URL '{url}'.")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def fetch_image_data_uri_safely(url: str, timeout: float = 5, max_redirects: int = 5) -> str:
|
|
162
|
+
"""Safely fetch an image and return it as a base64 data URI.
|
|
163
|
+
|
|
164
|
+
Useful for handing an image to code (e.g. third-party libraries) that
|
|
165
|
+
would otherwise perform its own, unguarded network fetch given a raw
|
|
166
|
+
URL — the network I/O happens here, under the SSRF guard, instead.
|
|
167
|
+
"""
|
|
168
|
+
from PIL import Image
|
|
169
|
+
|
|
170
|
+
data = fetch_image_bytes_safely(url, timeout=timeout, max_redirects=max_redirects)
|
|
171
|
+
image_format = (Image.open(BytesIO(data)).format or "JPEG").lower()
|
|
172
|
+
encoded = base64.b64encode(data).decode("ascii")
|
|
173
|
+
return f"data:image/{image_format};base64,{encoded}"
|
|
@@ -173,6 +173,15 @@ class MegatronMultimodalDeployable(ITritonDeployable):
|
|
|
173
173
|
if isinstance(self.inference_wrapped_model, QwenVLInferenceWrapper):
|
|
174
174
|
from qwen_vl_utils import process_vision_info
|
|
175
175
|
|
|
176
|
+
from nemo_deploy.multimodal.image_url_validator import fetch_image_data_uri_safely
|
|
177
|
+
|
|
178
|
+
# data: URIs are inline base64 and never trigger a network request.
|
|
179
|
+
# All other values are treated as URLs: fetch them ourselves under the SSRF
|
|
180
|
+
# guard and hand qwen_vl_utils a data URI, since it would otherwise perform
|
|
181
|
+
# its own unguarded network fetch for a raw URL.
|
|
182
|
+
if not image_source.startswith("data:"):
|
|
183
|
+
image_source = fetch_image_data_uri_safely(image_source)
|
|
184
|
+
|
|
176
185
|
messages = [
|
|
177
186
|
{
|
|
178
187
|
"role": "user",
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/multimodal/query_multimodal.py
RENAMED
|
@@ -17,7 +17,6 @@ from io import BytesIO
|
|
|
17
17
|
from typing import List, Optional
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
|
-
import requests
|
|
21
20
|
|
|
22
21
|
from nemo_deploy.utils import str_list2numpy
|
|
23
22
|
from nemo_export_deploy_common.import_utils import (
|
|
@@ -100,8 +99,10 @@ class NemoQueryMultimodal:
|
|
|
100
99
|
raise UnavailableError(MISSING_PIL_MSG)
|
|
101
100
|
|
|
102
101
|
if input_media.startswith("http") or input_media.startswith("https"):
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
from nemo_deploy.multimodal.image_url_validator import fetch_image_bytes_safely
|
|
103
|
+
|
|
104
|
+
image_bytes = fetch_image_bytes_safely(input_media, timeout=5)
|
|
105
|
+
media = Image.open(BytesIO(image_bytes)).convert("RGB")
|
|
105
106
|
else:
|
|
106
107
|
media = Image.open(input_media).convert("RGB")
|
|
107
108
|
return np.expand_dims(np.array(media), axis=0)
|
|
@@ -276,7 +276,7 @@ async def chat_completions_v1(request: ChatCompletionRequest):
|
|
|
276
276
|
prompts = request.messages
|
|
277
277
|
if not isinstance(request.messages, list):
|
|
278
278
|
prompts = [request.messages]
|
|
279
|
-
# Serialize the dictionary to a JSON string
|
|
279
|
+
# Serialize the dictionary to a JSON string representation to be able to convert to numpy array
|
|
280
280
|
# (str_list2numpy) and back to list (str_ndarray2list) as required by PyTriton. Using the dictionaries directly
|
|
281
281
|
# with these methods is not possible as they expect string type.
|
|
282
282
|
json_prompts = [dict_to_str(prompts)]
|
|
@@ -298,7 +298,7 @@ async def chat_completions_v1(request: MultimodalChatCompletionRequest):
|
|
|
298
298
|
# Remove image_url field
|
|
299
299
|
content.pop("image_url", None)
|
|
300
300
|
|
|
301
|
-
# Serialize the dictionary to a JSON string
|
|
301
|
+
# Serialize the dictionary to a JSON string representation to be able to convert to numpy array
|
|
302
302
|
# (str_list2numpy) and back to list (str_ndarray2list) as required by PyTriton. Using the dictionaries directly
|
|
303
303
|
# with these methods is not possible as they expect string type.
|
|
304
304
|
json_prompts = [dict_to_str(prompts)]
|
|
@@ -255,7 +255,7 @@ def get_llama_bidirectional_hf_model(
|
|
|
255
255
|
# configure pooling
|
|
256
256
|
pooling_module = Pooling(pooling_mode=pooling_mode)
|
|
257
257
|
|
|
258
|
-
# NV-Embed-v1 model has
|
|
258
|
+
# NV-Embed-v1 model has separate embedding model and a built-in pooling module
|
|
259
259
|
if (
|
|
260
260
|
model.__class__.__name__ == "NVEmbedModel"
|
|
261
261
|
and hasattr(model, "latent_attention_model")
|
|
@@ -201,7 +201,7 @@ def convert_lora_nemo_to_canonical(lora_nemo, save_path, hf_format=False, donor_
|
|
|
201
201
|
new_key = replace_number_add_offset(key, layer_offset)
|
|
202
202
|
lora_state_dict[tp][new_key] = value
|
|
203
203
|
|
|
204
|
-
# TODO: currently
|
|
204
|
+
# TODO: currently support tp=1
|
|
205
205
|
lora_state_dict = lora_state_dict[0]
|
|
206
206
|
if lora_config["peft"]["lora_tuning"].get("variant", "nemo") == "nemo":
|
|
207
207
|
lora_config["peft"]["lora_tuning"]["variant"] = "canonical"
|
|
@@ -120,10 +120,10 @@ def get_example_inputs(
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def validate_fp8_network(network) -> None:
|
|
123
|
-
"""Checks the network to ensure it's compatible with fp8
|
|
123
|
+
"""Checks the network to ensure it's compatible with fp8 precision.
|
|
124
124
|
|
|
125
125
|
Raises:
|
|
126
|
-
ValueError if
|
|
126
|
+
ValueError if network doesn't container Q/DQ FP8 layers
|
|
127
127
|
"""
|
|
128
128
|
import tensorrt as trt
|
|
129
129
|
|
|
@@ -112,7 +112,6 @@ class vLLMExporter(ITritonDeployable):
|
|
|
112
112
|
quantization: str = None,
|
|
113
113
|
seed: int = 0,
|
|
114
114
|
gpu_memory_utilization: float = 0.9,
|
|
115
|
-
swap_space: float = 4,
|
|
116
115
|
cpu_offload_gb: float = 0,
|
|
117
116
|
enforce_eager: bool = False,
|
|
118
117
|
task: Literal["auto", "generate", "embedding"] = "auto",
|
|
@@ -132,7 +131,6 @@ class vLLMExporter(ITritonDeployable):
|
|
|
132
131
|
quantization (str, optional): Quantization type. Defaults to None.
|
|
133
132
|
seed (int, optional): Random seed. Defaults to 0.
|
|
134
133
|
gpu_memory_utilization (float, optional): Fraction of GPU memory to use. Defaults to 0.9.
|
|
135
|
-
swap_space (float, optional): Amount of swap space (in GB) to use. Defaults to 4.
|
|
136
134
|
cpu_offload_gb (float, optional): Amount of CPU offload memory (in GB). Defaults to 0.
|
|
137
135
|
enforce_eager (bool, optional): Whether to enforce eager execution. Defaults to False.
|
|
138
136
|
task (Literal["auto", "generate", "embedding"], optional): Task type for vLLM. Defaults to "auto".
|
|
@@ -203,7 +201,6 @@ class vLLMExporter(ITritonDeployable):
|
|
|
203
201
|
quantization=quantization,
|
|
204
202
|
seed=seed,
|
|
205
203
|
gpu_memory_utilization=gpu_memory_utilization,
|
|
206
|
-
swap_space=swap_space,
|
|
207
204
|
cpu_offload_gb=cpu_offload_gb,
|
|
208
205
|
enforce_eager=enforce_eager,
|
|
209
206
|
runner=task,
|
|
@@ -219,7 +216,6 @@ class vLLMExporter(ITritonDeployable):
|
|
|
219
216
|
quantization=quantization,
|
|
220
217
|
seed=seed,
|
|
221
218
|
gpu_memory_utilization=gpu_memory_utilization,
|
|
222
|
-
swap_space=swap_space,
|
|
223
219
|
cpu_offload_gb=cpu_offload_gb,
|
|
224
220
|
enforce_eager=enforce_eager,
|
|
225
221
|
runner=task,
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export_deploy_common/import_utils.py
RENAMED
|
@@ -394,7 +394,7 @@ def gpu_only_import(module, *, alt=None) -> Tuple[object, bool]:
|
|
|
394
394
|
|
|
395
395
|
return safe_import(
|
|
396
396
|
module,
|
|
397
|
-
msg=f"{module} is not enabled in non GPU-enabled installations or
|
|
397
|
+
msg=f"{module} is not enabled in non GPU-enabled installations or environments. {GPU_INSTALL_STRING}",
|
|
398
398
|
alt=alt,
|
|
399
399
|
)
|
|
400
400
|
|
|
@@ -67,7 +67,7 @@ dependencies = [
|
|
|
67
67
|
"wandb==0.27.2",
|
|
68
68
|
"webdataset>=0.2.86",
|
|
69
69
|
"nvidia-pytriton ; platform_system != 'Darwin' ",
|
|
70
|
-
"flashinfer-python
|
|
70
|
+
"flashinfer-python; platform_system != 'Darwin'",
|
|
71
71
|
"Pillow ; platform_system != 'Darwin' and platform_machine != 'aarch64'",
|
|
72
72
|
"decord ; platform_system != 'Darwin' and platform_machine != 'aarch64'",
|
|
73
73
|
"pyparsing>2.0.2",
|
|
@@ -80,7 +80,7 @@ dependencies = [
|
|
|
80
80
|
|
|
81
81
|
[project.optional-dependencies]
|
|
82
82
|
inframework = []
|
|
83
|
-
vllm = ["vllm~=0.
|
|
83
|
+
vllm = ["vllm~=0.29.0", "pandas", "timm"]
|
|
84
84
|
trt-onnx = [
|
|
85
85
|
"tensorrt==10.16.1.11",
|
|
86
86
|
"onnx==1.21.0",
|
|
@@ -106,11 +106,15 @@ nemo-run = ["nemo-run"]
|
|
|
106
106
|
[tool.uv.sources]
|
|
107
107
|
xformers = [{ index = "pytorch-cu130" }]
|
|
108
108
|
torch = [{ index = "pytorch-cu130" }]
|
|
109
|
+
# flashinfer-cubin isn't published to PyPI since 0.6.14; both packages are pulled from
|
|
110
|
+
# flashinfer's own wheel index, same as vLLM's requirements/cuda.txt.
|
|
111
|
+
flashinfer-python = [{ index = "flashinfer" }]
|
|
112
|
+
flashinfer-cubin = [{ index = "flashinfer" }]
|
|
109
113
|
vllm = [
|
|
110
114
|
{ index = "pytorch-cu130", marker = "python_version < '3.9' and platform_machine == 'x86_64'" },
|
|
111
115
|
{ index = "pypi", marker = "platform_machine == 'aarch64'" },
|
|
112
116
|
]
|
|
113
|
-
megatron-bridge = { git = "https://github.com/NVIDIA-NeMo/Megatron-Bridge.git", rev = "
|
|
117
|
+
megatron-bridge = { git = "https://github.com/NVIDIA-NeMo/Megatron-Bridge.git", rev = "02fcce5a103ca0c88a945319075348729e0b666e" }
|
|
114
118
|
# Use the released nvidia-resiliency-ext (>=0.6.0) rather than a git dev build:
|
|
115
119
|
# megatron-core's runtime is_nvrx_min_version() asserts the installed package
|
|
116
120
|
# version is >=0.6.0, and a git build reports 0.6.0.devN (< 0.6.0), which fails
|
|
@@ -150,7 +154,6 @@ override-dependencies = [
|
|
|
150
154
|
"transformer-engine[pytorch]>=2.16.0a0,<2.19.0",
|
|
151
155
|
"open-clip-torch>=3.2.0",
|
|
152
156
|
"megatron-energon[av-decode]>=6.0,<7.dev0",
|
|
153
|
-
"flashinfer-python==0.6.8.post1",
|
|
154
157
|
"datasets>=3.3.0",
|
|
155
158
|
"flash-linear-attention>=0.3.0,<0.4.dev0",
|
|
156
159
|
"patchelf; sys_platform=='never'",
|
|
@@ -162,10 +165,16 @@ override-dependencies = [
|
|
|
162
165
|
"transformers==5.8.1",
|
|
163
166
|
"protobuf~=6.33.5",
|
|
164
167
|
"opencv-python-headless; sys_platform == 'never'",
|
|
165
|
-
"cryptography>=
|
|
168
|
+
"cryptography>=50.0.0,<51",
|
|
166
169
|
"onnx<1.20.0",
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
# flashinfer-python and flashinfer-cubin must be kept in lockstep (flashinfer-python does a
|
|
171
|
+
# runtime version-match check); flashinfer-cubin isn't published to PyPI since 0.6.14, so both
|
|
172
|
+
# are sourced from flashinfer's own wheel index below. Pin matches vLLM's own requirements/cuda.txt.
|
|
173
|
+
"flashinfer-python==0.6.18.post1",
|
|
174
|
+
"flashinfer-cubin==0.6.18.post1",
|
|
175
|
+
"fast-hadamard-transform @ git+https://github.com/Dao-AILab/fast-hadamard-transform.git@v1.1.0", # pin 1.1.0; PyPI sdist is incomplete, install from git tag (see DSv4/GLM-5 READMEs)
|
|
176
|
+
"nvidia-cudnn-frontend==1.26.0",
|
|
177
|
+
"nvidia-cutlass-dsl[cu13]; sys_platform == 'never'", # Rely on system site packages install
|
|
169
178
|
]
|
|
170
179
|
prerelease = "allow"
|
|
171
180
|
|
|
@@ -173,6 +182,16 @@ prerelease = "allow"
|
|
|
173
182
|
name = "nvidia-resiliency-ext"
|
|
174
183
|
version = "0.6.0"
|
|
175
184
|
|
|
185
|
+
[[tool.uv.dependency-metadata]]
|
|
186
|
+
name = "fast-hadamard-transform"
|
|
187
|
+
version = "1.1.0"
|
|
188
|
+
requires-dist = ["torch", "packaging", "ninja"]
|
|
189
|
+
|
|
190
|
+
# torch-memory-saver's build backend reads TMS_CUDA_MAJOR at build time. Keep this in
|
|
191
|
+
# sync with docker/Dockerfile's BASE_IMAGE CUDA major version (currently cuda13.x).
|
|
192
|
+
[tool.uv.extra-build-variables]
|
|
193
|
+
torch-memory-saver = { TMS_CUDA_MAJOR = "13" }
|
|
194
|
+
|
|
176
195
|
[[tool.uv.index]]
|
|
177
196
|
name = "pypi"
|
|
178
197
|
url = "https://pypi.org/simple"
|
|
@@ -183,6 +202,19 @@ name = "pytorch-cu130"
|
|
|
183
202
|
url = "https://download.pytorch.org/whl/cu130"
|
|
184
203
|
explicit = true
|
|
185
204
|
|
|
205
|
+
[[tool.uv.index]]
|
|
206
|
+
name = "flashinfer"
|
|
207
|
+
url = "https://flashinfer.ai/whl/"
|
|
208
|
+
explicit = true
|
|
209
|
+
|
|
210
|
+
# `[tool.uv.sources]`/`[[tool.uv.index]]` above are only honored by uv's project interface
|
|
211
|
+
# (uv sync/lock/add). The release CI builds the wheel via the pip-compatible interface
|
|
212
|
+
# (`uv pip install -e .`), which does NOT read that config and only reads `[tool.uv.pip]`.
|
|
213
|
+
# flashinfer-cubin/flashinfer-python aren't published on PyPI since 0.6.14, so without this
|
|
214
|
+
# extra index `uv pip install -e .` can't resolve megatron-bridge's flashinfer-cubin pin.
|
|
215
|
+
[tool.uv.pip]
|
|
216
|
+
extra-index-url = ["https://flashinfer.ai/whl/"]
|
|
217
|
+
|
|
186
218
|
[tool.pytest.ini_options]
|
|
187
219
|
addopts = "--durations=15 -s -rA -x"
|
|
188
220
|
testpaths = ["tests"]
|
|
@@ -191,18 +223,14 @@ python_files = "test_*.py"
|
|
|
191
223
|
|
|
192
224
|
[tool.coverage.run]
|
|
193
225
|
concurrency = ["thread", "multiprocessing"]
|
|
226
|
+
relative_files = true
|
|
194
227
|
omit = [
|
|
195
|
-
"
|
|
196
|
-
"
|
|
197
|
-
"/workspace/nemo_export_deploy_common/*",
|
|
198
|
-
"/workspace/*.py",
|
|
228
|
+
"*/tests/*",
|
|
229
|
+
"*/nemo_export_deploy_common/*",
|
|
199
230
|
"*_ray.py",
|
|
200
231
|
"*package_info.py",
|
|
201
232
|
]
|
|
202
233
|
|
|
203
|
-
[tool.coverage.paths]
|
|
204
|
-
source = [".", "/workspace", "/home/runner/work/Export-Deploy/Export-Deploy"]
|
|
205
|
-
|
|
206
234
|
[tool.ruff]
|
|
207
235
|
line-length = 120
|
|
208
236
|
|
|
File without changes
|
|
File without changes
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/NeMo_Export_Deploy.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_deploy/llm/inference/nemo_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/model_adapters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nemo_export_deploy-0.7.0 → nemo_export_deploy-0.8.0}/nemo_export/sentencepiece_tokenizer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|