Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
56ff5a8
feat(generation): add remote vLLM endpoint backend
binaryaaron Jun 7, 2026
58baef4
fix(generation): use vLLM structured_outputs field for remote backend
binaryaaron Jun 7, 2026
809352f
feat(generation): support structural_tag for the remote backend
binaryaaron Jun 7, 2026
f2a2447
feat(tools): add vllm_debug standalone uv script
binaryaaron Jun 7, 2026
2aca78c
docs(working-memory): chronicle remote backend work + serving roadmap
binaryaaron Jun 7, 2026
5ce236b
docs(working-memory): chronicle the modular state-machine architectur…
binaryaaron Jun 7, 2026
56a5045
feat(tools): default vllm_debug serve to CUDA graphs (eager opt-in)
binaryaaron Jun 7, 2026
8b458ff
chore: apply copyright headers to working-memory notes
binaryaaron Jun 7, 2026
70ed3f0
feat(generation): add remote dialect (vllm|openai) for strict OpenAI …
binaryaaron Jun 7, 2026
361c1a1
docs(working-memory): add NIM/runtime validation, dialect, perf to ch…
binaryaaron Jun 7, 2026
39d7cfb
docs: self-review fixes — module map + measured quality/perf in chron…
binaryaaron Jun 7, 2026
aacb958
feat(generation): harden remote backend with retries and defensive pa…
binaryaaron Jun 8, 2026
2a26b3f
fix(tools): harden vllm_debug structured-output input parsing
binaryaaron Jun 8, 2026
78559fd
test(smoke): add remote generation backend smoke tests
binaryaaron Jun 8, 2026
b6f88d9
chore(working-memory): relocate chronicle notes to the safe-synthesiz…
binaryaaron Jun 8, 2026
c226c79
refactor(llm): define typed local model host
binaryaaron Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Source code lives in `src/nemo_safe_synthesizer/`:
| `configurator/` | Pydantic-to-Click mapping, Parameter types, validators |
| `data_processing/` | Holdout, actions, assembler, records, shared token budget (`budget.py`), shared column validators (`validation.py`) |
| `evaluation/` | Evaluator, components (privacy, MI, AIA, PII replay), reports |
| `generation/` | GeneratorBackend, VllmBackend, regex manager, batch gen |
| `generation/` | GeneratorBackend (owns the concrete `generate()` batch-loop template method), VllmBackend, RemoteBackend (GPU-free; calls a vLLM/NIM OpenAI-compatible endpoint), regex manager, batch gen |
| `holdout/` | Train/test splitting |
| `llm/` | Model loading, metadata, memory management |
| `pii_replacer/` | NER-based PII detection and replacement |
Expand Down
3 changes: 2 additions & 1 deletion src/nemo_safe_synthesizer/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .differential_privacy import DifferentialPrivacyHyperparams
from .evaluate import EvaluationParameters
from .external_results import SafeSynthesizerSummary, SafeSynthesizerTiming
from .generate import GenerateParameters, StructuredGenerationParameters
from .generate import GenerateParameters, RemoteParameters, StructuredGenerationParameters
from .internal_results import SafeSynthesizerResults
from .job import SafeSynthesizerJobConfig
from .parameters import SafeSynthesizerParameters
Expand All @@ -26,6 +26,7 @@
"GenerateParameters",
"PiiReplacerConfig",
"PreflightParameters",
"RemoteParameters",
"SafeSynthesizerJobConfig",
"SafeSynthesizerParameters",
"SafeSynthesizerResults",
Expand Down
100 changes: 100 additions & 0 deletions src/nemo_safe_synthesizer/config/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
StructuredGenerationSchemaMethod = Literal["auto", "regex", "json_schema", "structural_tag"]
ResolvedStructuredGenerationSchemaMethod = Literal["regex", "json_schema", "structural_tag"]
StructuredGenerationBackend = Literal["auto", "xgrammar", "guidance", "outlines", "lm-format-enforcer"]
RemoteDialect = Literal["vllm", "openai"]

STRUCTURAL_TAG_COMPATIBLE_BACKENDS = frozenset({"auto", "xgrammar"})

__all__ = [
"GenerateParameters",
"RemoteDialect",
"RemoteParameters",
"ResolvedStructuredGenerationSchemaMethod",
"StructuredGenerationParameters",
"StructuredGenerationBackend",
Expand Down Expand Up @@ -176,6 +179,91 @@ def _validate_structural_tag_backend(self) -> Self:
return self


class RemoteParameters(Parameters, BaseModel):
"""Connection to an external vLLM OpenAI-compatible inference server.

When set on :class:`GenerateParameters`, generation issues HTTP requests
to this endpoint instead of loading a local vLLM engine. The server must
already serve the base model with the fine-tuned LoRA adapter attached,
registered under ``model``. No GPU is used locally.

Structured generation maps to vLLM's ``structured_outputs`` request field
(``regex`` / ``json`` / ``structural_tag``), so all schema methods are
supported against a vLLM 0.20+ server.
"""

dialect: Annotated[
RemoteDialect,
Field(
title="dialect",
description=(
"Which request fields to send. 'vllm' (default) adds vLLM's sampling extensions "
"(repetition_penalty, top_k, min_p, skip_special_tokens, include_stop_str_in_output, "
"ignore_eos). 'openai' sends only the universal OpenAI fields, for stricter servers "
"(e.g. NIM / TensorRT-LLM) that reject those extensions."
),
),
] = "vllm"

endpoint_url: Annotated[
str,
Field(
title="endpoint_url",
description="Base URL of the OpenAI-compatible server, e.g. 'http://localhost:8000/v1'.",
),
]

model: Annotated[
str,
Field(
title="model",
description="Model name as registered on the server (the served base model or LoRA adapter name).",
),
]

api_key_env: Annotated[
str | None,
Field(
title="api_key_env",
description=(
"Name of the environment variable holding the bearer token for the endpoint. "
"When unset, no Authorization header is sent."
),
),
] = None

timeout_seconds: Annotated[
float,
ValueValidator(value_func=lambda v: v > 0),
Field(
title="timeout_seconds",
description="Per-request timeout in seconds. Must be > 0.",
),
] = 300.0

max_concurrency: Annotated[
int,
ValueValidator(value_func=lambda v: v >= 1),
Field(
title="max_concurrency",
description="Maximum number of concurrent in-flight requests per batch. Must be >= 1.",
),
] = 16

max_retries: Annotated[
int,
ValueValidator(value_func=lambda v: v >= 0),
Field(
title="max_retries",
description=(
"Number of retry attempts per request for transient failures (connection drops, "
"timeouts, and HTTP 408/409/425/429/500/502/503/504), using exponential backoff with "
"full jitter and honoring a Retry-After header. 0 disables retries. Must be >= 0."
),
),
] = 4


class GenerateParameters(Parameters, BaseModel):
"""Configuration parameters for synthetic data generation.

Expand Down Expand Up @@ -260,6 +348,18 @@ class GenerateParameters(Parameters, BaseModel):
default_factory=ValidationParameters,
)

remote: Annotated[
RemoteParameters | None,
Field(
title="remote",
description=(
"When set, generate by calling an external vLLM OpenAI-compatible server instead of "
"loading a local vLLM engine. The server must already serve the trained LoRA adapter. "
"Not supported for time-series generation."
),
),
] = None

attention_backend: Annotated[
str | None,
Field(
Expand Down
12 changes: 12 additions & 0 deletions src/nemo_safe_synthesizer/config/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ def check_timeseries_group_column(self) -> Self:
)
return self

@model_validator(mode="after")
def check_remote_not_timeseries(self) -> Self:
"""Reject remote generation for time-series datasets at config time.

The remote backend has no equivalent of the grouped time-series
generation loop, so the combination is unsupported. Catching it here
fails fast instead of after training completes.
"""
if self.generation.remote is not None and self.time_series is not None and self.time_series.is_timeseries:
raise ParameterError("Remote generation is not supported for time-series datasets.")
return self

@classmethod
@override
def from_params(cls, **kwargs: object) -> "SafeSynthesizerParameters":
Expand Down
Loading
Loading