Skip to content

Commit b25b3b5

Browse files
authored
fix: address third-party deprecation warnings (#704)
## Summary - Replace `GLiNER.batch_predict_entities` (deprecated) with `.inference` - Rename `warmup_ratio` → `warmup_steps` in `TrainingHyperparams` to match the transformers API; `warmup_ratio` is kept as a deprecated alias that emits a `DeprecationWarning` and copies its value to `warmup_steps`; floats in (0, 1) are still interpreted as ratios by `TrainingArguments`, so `0.05` default is unchanged in behaviour - Suppress `huggingface_hub` `resume_download` `UserWarning` at the GLiNER load site (upstream gliner passes the deprecated arg) - Suppress `scipy` `ks_2samp` asymptotic fallback `RuntimeWarning` in the tutorial notebook via a scoped `with warnings.catch_warnings()` block around `builder.run()` - Pre-compile `torchao` and `range_regex` in the brev setup script so their one-time `SyntaxWarning`s go to the setup log instead of notebook output - Pin `BNB_CUDA_VERSION=128` in the brev kernelspec — bitsandbytes skips odd CUDA releases so 12.9 has no native binary and always falls back to 12.8; update to `130` when upgrading `CUDA_EXTRA` to `cu130+` ## Test plan - [x] Unit tests pass (`mise run test`) - [x] Re-run the tutorial notebook and confirm no red warning highlights in JupyterLab <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Training configuration now supports `warmup_steps` as a fixed count or fraction of total training steps, while maintaining compatibility with the deprecated setting. - GLiNER inference now supports updated APIs with fallback handling for older versions. - **Bug Fixes** - Reduced expected runtime and deprecation warnings during training, model loading, and tutorial execution. - Improved CUDA 12.9 compatibility and setup reliability. - **Documentation** - Updated the Safe Synthesizer tutorial to run without expected large-sample warnings. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Yunfeng Zhang <yunzhang@nvidia.com>
1 parent 5fe031e commit b25b3b5

10 files changed

Lines changed: 250 additions & 81 deletions

File tree

script/brev/README.md

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ NeMo Safe Synthesizer without setting up CUDA, drivers, or Python locally.
1010
Nothing in this directory is executed by the repo or by CI. A Brev Launchable is
1111
configured in the Brev web console, and the setup script is pasted into a form field
1212
there. This directory exists so that configuration is versioned and reviewable rather
13-
than living only in a browser. When you change `setup.sh`, you must also paste the new
14-
contents into the console for the change to take effect.
13+
than living only in a browser. When you change `setup.sh` or `welcome.md`, you must also
14+
update it in the console for the change to take effect.
1515

1616
### Files
1717

1818
- `setup.sh`: Pasted into the Launchable's Setup Script field. Installs the CUDA
1919
build of Safe Synthesizer into a dedicated venv, registers it as the default Jupyter
2020
kernel, and drops the tutorial notebooks in `$HOME`.
21-
- `welcome.md`: Becomes the customer's `$HOME/README.md`. Fetched at provisioning
22-
time from the same tarball as the tutorials, not baked into `setup.sh` -- it would
23-
otherwise consume a tenth of the 16 KiB script budget.
21+
- `welcome.md`: Added to the Launchable's Source files so it renders on the Launchable
22+
webpage and appears as the customer's `$HOME/welcome.md`.
2423

2524
### Console configuration
2625

@@ -33,7 +32,7 @@ Launchable with these settings.
3332
| Software | Install Jupyter on the host | Enabled |
3433
| Software | Run a Setup Script | Enabled, contents of `setup.sh` |
3534
| Software | Image ID | Leave blank |
36-
| Source | Code source | No code files (`setup.sh` downloads the tutorials itself) |
35+
| Source | Code source | `welcome.md` |
3736
| Hardware | GPU | 1× 80 GiB VRAM, single GPU |
3837
| Hardware | Disk | 200 GiB or more -- not resizable after creation |
3938
| Network | Ports | 8888, named `jupyter` |
@@ -67,8 +66,8 @@ there. Everything operational is a dotfile, which the browser hides by default.
6766
```text
6867
$HOME/
6968
tutorials/ the three tutorial notebooks and their datasets
70-
README.md where to start, rendered on double-click
71-
(SETUP-IN-PROGRESS.md until setup finishes)
69+
welcome.md where to start, rendered on double-click
70+
SETUP-IN-PROGRESS.md present only while setup is running or after failure
7271
7372
.nss-venv/ cu129 venv, registered as the default kernel
7473
.cache/huggingface/ model cache (Hugging Face's default location)
@@ -91,12 +90,12 @@ hard way on a real instance.
9190
them -- and they have to match the release being installed, not this repo's `main`.
9291
The script resolves the latest version from the PyPI JSON API, fetches that tag's
9392
`pyproject.toml`, and reads the CUDA index URLs out of it, then pins the install to
94-
that exact version so the two cannot drift. Selection is keyed on the URL containing
95-
`cu129`, not on the index name: the flashinfer entry was renamed
96-
`flashinfer-jit-cache``flashinfer-jit-cache-cu129` between 0.1.8 and 0.1.9, so
97-
names are not stable across releases. The parse runs inside a process substitution and
98-
therefore cannot fail the script, so the count of discovered indexes is what validates
99-
it.
93+
that exact version so the two cannot drift. Selection uses the CUDA extra in each
94+
index's name or URL and includes indexes referenced by `[tool.uv.sources]` for that
95+
extra. The source lookup matters for variant-neutral indexes such as
96+
`https://flashinfer.ai/whl/`, while the URL lookup handles names that changed between
97+
releases. The parse runs inside a process substitution and therefore cannot fail the
98+
script, so the count of discovered indexes is what validates it.
10099
- uv is installed from a checksum-verified tarball, not `curl | sh`. The
101100
`astral.sh/install.sh` path logs `no checksums to verify`, so nothing validated what
102101
it downloaded. The script fetches the pinned release tarball, compares it against the
@@ -106,12 +105,11 @@ hard way on a real instance.
106105
accepts connections well before this script finishes, so a user who opens it early
107106
would otherwise see an empty or half-populated file browser and assume the Launchable
108107
is broken. `SETUP-IN-PROGRESS.md` is written before any slow work, rewritten by the
109-
`ERR` trap if provisioning fails, and replaced by `README.md` on success.
110-
- The welcome text lives in `welcome.md`, not a heredoc. It is pulled from the
111-
same tarball as the tutorials, so the two always match, and it is staged as a dotfile
112-
until the final step so it never appears while setup is still running. The fetch is
113-
non-fatal: `script/brev/` exists in no released tag, so it resolves only from the
114-
`main` fallback until a release includes it.
108+
`ERR` trap if provisioning fails, and removed on success.
109+
- The welcome text lives in the Launchable's Source configuration, not a heredoc or
110+
release tarball. Brev renders it on the Launchable webpage and copies it to
111+
`$HOME/welcome.md`; keeping the console copy synchronized with this directory is a
112+
manual deployment step.
115113
- The setup script has a 16 KiB limit. Brev rejects anything larger, which is why
116114
the script carries short comments pointing here rather than full explanations. Check
117115
`wc -c script/brev/setup.sh` before pasting.

script/brev/setup.sh

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ readonly REPO_URL="https://github.com/NVIDIA-NeMo/Safe-Synthesizer"
2121

2222
: "${HOME:?HOME is not set}"
2323

24-
# $HOME is the file browser root: only tutorials/ and README.md are visible.
24+
# $HOME is the file browser root: only customer-facing files stay visible.
2525
readonly TUTORIALS_DIR="${HOME}/tutorials"
26-
readonly README_FILE="${HOME}/README.md"
26+
readonly WELCOME_FILE="${HOME}/welcome.md"
2727
readonly WAIT_FILE="${HOME}/SETUP-IN-PROGRESS.md"
28-
readonly WELCOME_STAGED="${HOME}/.nss-welcome.md"
2928

3029
readonly BIN_DIR="${HOME}/.local/bin"
3130
readonly VENV_DIR="${HOME}/.nss-venv"
@@ -61,7 +60,7 @@ NeMo Safe Synthesizer is still installing -- roughly 5-10 minutes from when
6160
the instance started. Files appear as it progresses, so a partly-filled file
6261
browser is expected. Nothing here is ready to run yet.
6362
64-
When setup finishes, this file is replaced by README.md. Refresh to check.
63+
When setup finishes, this file disappears. Open welcome.md to get started.
6564
EOF
6665

6766
export PATH="${BIN_DIR}:${PATH}"
@@ -123,8 +122,8 @@ else
123122
NSS_VERSION="$(curl -fsSL https://pypi.org/pypi/nemo-safe-synthesizer/json \
124123
| "${VENV_DIR}/bin/python" -c 'import json, sys; print(json.load(sys.stdin)["info"]["version"])')"
125124

126-
# Indexes come from the installed release's pyproject. Match both generated
127-
# names and URLs because static index names do not enforce the CUDA suffix.
125+
# Indexes come from the installed release's pyproject. Match CUDA names and
126+
# URLs plus source-mapped indexes whose names are variant-neutral.
128127
pyproject="$(mktemp)"
129128
curl -fsSL "${REPO_URL}/raw/v${NSS_VERSION}/pyproject.toml" -o "${pyproject}"
130129
index_args=()
@@ -140,15 +139,29 @@ import sys
140139
import tomllib
141140
142141
with open(sys.argv[1], "rb") as handle:
143-
indexes = tomllib.load(handle)["tool"]["uv"]["index"]
142+
uv_config = tomllib.load(handle)["tool"]["uv"]
144143
144+
indexes = uv_config["index"]
145145
cuda_extra = os.environ["CUDA_EXTRA"]
146+
147+
# Some indexes carry no CUDA variant in their name or URL. Source config is
148+
# not wheel metadata, so collect indexes mapped to packages for this extra.
149+
source_indexes = {
150+
entry["index"]
151+
for value in uv_config.get("sources", {}).values()
152+
for entry in (value if isinstance(value, list) else [value])
153+
if isinstance(entry, dict)
154+
and entry.get("extra") == cuda_extra
155+
and "index" in entry
156+
}
157+
146158
print(
147159
"\n".join(
148160
index["url"]
149161
for index in indexes
150162
if index["name"].endswith(f"-{cuda_extra}")
151163
or f"/{cuda_extra}" in index["url"]
164+
or index["name"] in source_indexes
152165
)
153166
)
154167
PY
@@ -208,13 +221,6 @@ else
208221
# Written last; the guard keys on this, so partial runs are redone.
209222
: >"${TUTORIALS_DIR}/.fetched"
210223
log "tutorials extracted from ${ref}"
211-
# Same tarball as the tutorials. Non-fatal -- see README.
212-
if tar -xzf "${tarball}" -C "${tarball_dir}" --strip-components=3 \
213-
"${top}/script/brev/welcome.md" 2>/dev/null; then
214-
mv "${tarball_dir}/welcome.md" "${WELCOME_STAGED}"
215-
else
216-
log "WARNING: welcome.md not present in ${ref}"
217-
fi
218224
fetched=1
219225
break
220226
fi
@@ -343,19 +349,23 @@ if [[ "${registered}" -ne 1 ]]; then
343349
log "WARNING: kernel not registered; notebooks may open on the wrong Python"
344350
fi
345351

352+
# Pre-compile third-party packages that emit SyntaxWarnings on first import so
353+
# the warnings go into the setup log rather than appearing in notebook output.
354+
log "pre-compiling packages"
355+
"${VENV_DIR}/bin/python" -W ignore::SyntaxWarning \
356+
-c "import torchao, range_regex" 2>/dev/null || true
357+
346358
# Smoke check -- fail provisioning loudly rather than handing over a broken VM.
347359

348360
log "verifying install"
349361
"${VENV_DIR}/bin/safe-synthesizer" --version
350362
"${VENV_DIR}/bin/python" \
351363
-c "import torch; print('cuda available:', torch.cuda.is_available())"
352364

353-
# Hand over: swap the "please wait" file for the welcome text.
365+
# Hand over: the Source-provided welcome stays visible after setup completes.
354366

355-
if [[ -f "${WELCOME_STAGED}" ]]; then
356-
mv "${WELCOME_STAGED}" "${README_FILE}"
357-
else
358-
log "WARNING: no welcome.md staged; skipping ${README_FILE}"
367+
if [[ ! -f "${WELCOME_FILE}" ]]; then
368+
log "WARNING: ${WELCOME_FILE} is missing; check the Launchable Source files"
359369
fi
360370
rm -f "${WAIT_FILE}"
361371

@@ -365,7 +375,7 @@ cat <<EOF
365375
366376
Safe Synthesizer is ready.
367377
368-
Start here : ${README_FILE}
378+
Start here : ${WELCOME_FILE}
369379
Tutorials : ${TUTORIALS_DIR}/
370380
Kernel : "Safe Synthesizer" -- already the default
371381
Setup log : ${LOG_FILE}

src/nemo_safe_synthesizer/config/training.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
from __future__ import annotations
55

66
import importlib
7+
import math
8+
import warnings
79
from enum import StrEnum
810
from typing import (
911
TYPE_CHECKING,
1012
Annotated,
1113
Literal,
1214
)
1315

14-
from pydantic import Field
16+
from pydantic import Field, model_validator
1517

1618
from ..configurator.parameters import (
1719
Parameters,
@@ -151,6 +153,19 @@ def _mxfp4_config() -> QuantizationConfigMixin:
151153
ValueGTZero = ValueValidator(lambda p: range_validator(p, lambda v: v >= 0))
152154

153155

156+
def is_valid_warmup(value: float) -> bool:
157+
"""Whether a warmup setting is a usable ratio or step count.
158+
159+
Mirrors how transformers interprets ``warmup_steps``: ``0`` disables warmup,
160+
values below 1 are a ratio of total training steps, and values of 1 or more
161+
are an absolute step count. Fractional values of 1 or more are rejected
162+
because transformers truncates them (``1.5`` silently becomes ``1``), and
163+
non-finite values are rejected because they raise ``OverflowError`` once
164+
converted to an integer.
165+
"""
166+
return math.isfinite(value) and value >= 0 and (value < 1 or float(value).is_integer())
167+
168+
154169
class TrainingHyperparams(Parameters):
155170
"""Hyperparameters that control the training process behavior.
156171
@@ -210,15 +225,43 @@ class TrainingHyperparams(Parameters):
210225
),
211226
] = 0.01
212227

213-
warmup_ratio: Annotated[
228+
warmup_steps: Annotated[
214229
float,
215-
ValueValidator(value_func=lambda v: v > 0),
230+
ValueValidator(value_func=is_valid_warmup),
216231
Field(
217-
title="warmup_ratio",
218-
description="Ratio of total training steps used for a linear warmup from 0 to the learning rate. Must be > 0.",
232+
title="warmup_steps",
233+
description=(
234+
"Linear warmup from 0 to the learning rate. "
235+
"A whole number of 1 or more sets the exact number of warmup steps; "
236+
"a float in (0, 1) is treated as a ratio of total training steps; "
237+
"0 disables warmup. "
238+
"Must be finite and >= 0, and cannot be fractional at or above 1."
239+
),
219240
),
220241
] = 0.05
221242

243+
warmup_ratio: Annotated[
244+
float | None,
245+
ValueValidator(value_func=lambda v: v is None or is_valid_warmup(v)),
246+
Field(
247+
title="warmup_ratio",
248+
description="Deprecated. Use warmup_steps instead.",
249+
exclude=True,
250+
),
251+
] = None
252+
253+
@model_validator(mode="after")
254+
def _migrate_warmup_ratio(self) -> TrainingHyperparams:
255+
if self.warmup_ratio is not None:
256+
warnings.warn(
257+
"warmup_ratio is deprecated and will be removed in a future release. Use warmup_steps instead.",
258+
DeprecationWarning,
259+
stacklevel=2,
260+
)
261+
if "warmup_steps" not in self.model_fields_set:
262+
self.warmup_steps = self.warmup_ratio
263+
return self
264+
222265
lr_scheduler: Annotated[
223266
str,
224267
Field(

src/nemo_safe_synthesizer/evaluation/components/text_semantic_similarity.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from __future__ import annotations
55

66
import logging
7+
import warnings
8+
from contextlib import contextmanager
79
from functools import cached_property
810
from typing import TYPE_CHECKING
911

@@ -36,11 +38,32 @@
3638
from . import multi_modal_figures as figures
3739

3840
if TYPE_CHECKING:
41+
from collections.abc import Iterator
42+
3943
from sentence_transformers import SentenceTransformer
4044

4145
logger = get_logger(__name__)
4246

4347

48+
@contextmanager
49+
def _suppress_ks_exact_fallback() -> Iterator[None]:
50+
"""Silence SciPy's notice that ``ks_2samp`` fell back to the asymptotic method.
51+
52+
``method="auto"`` attempts the exact calculation and falls back to the
53+
asymptotic approximation once the samples are large, which is the intended
54+
behaviour here -- the resulting p-values are still valid. The notice is not
55+
actionable, so keep it out of CLI, SDK, and notebook output rather than
56+
suppressing it separately in each caller.
57+
"""
58+
with warnings.catch_warnings():
59+
warnings.filterwarnings(
60+
"ignore",
61+
message="ks_2samp: Exact calculation unsuccessful",
62+
category=RuntimeWarning,
63+
)
64+
yield
65+
66+
4467
class TextSemanticSimilarityDatum(BaseModel):
4568
"""Per-column text semantic similarity scores and PCA projections."""
4669

@@ -401,12 +424,13 @@ def _get_text_semantic_similarity(
401424
# the minimum (most negative) difference between the empirical
402425
# distribution functions of the samples. The range of this statistic is
403426
# [0, 1], where 0 indicates no overfitting.
404-
ks_test_overfitting = ks_2samp(
405-
training_synth_similarity_matrix.max(axis=0), # F(x)
406-
training_similarity_matrix.max(axis=0), # G(x)
407-
alternative="less",
408-
method="auto",
409-
)
427+
with _suppress_ks_exact_fallback():
428+
ks_test_overfitting = ks_2samp(
429+
training_synth_similarity_matrix.max(axis=0), # F(x)
430+
training_similarity_matrix.max(axis=0), # G(x)
431+
alternative="less",
432+
method="auto",
433+
)
410434

411435
# Underfitting is measured as the extent to which the synthetic
412436
# data is less similar to the test data than the test data is to
@@ -417,12 +441,13 @@ def _get_text_semantic_similarity(
417441
# the minimum (most negative) difference between the empirical
418442
# distribution functions of the samples. The range of this statistic is
419443
# [0, 1], where 0 indicates no underfitting.
420-
ks_test_underfitting = ks_2samp(
421-
test_synth_similarity_matrix.max(axis=0), # F(x)
422-
test_similarity_matrix.max(axis=0), # G(x)
423-
alternative="greater",
424-
method="auto",
425-
)
444+
with _suppress_ks_exact_fallback():
445+
ks_test_underfitting = ks_2samp(
446+
test_synth_similarity_matrix.max(axis=0), # F(x)
447+
test_similarity_matrix.max(axis=0), # G(x)
448+
alternative="greater",
449+
method="auto",
450+
)
426451

427452
# The overall semantic similarity score combines underfitting and overfitting
428453
# The range of this score is [0.37, 1], where 1 indicates perfect model and

0 commit comments

Comments
 (0)