8000 Add the measured single-GPU vLLM rollout backend by DaoyuanLi2816 · Pull Request #95 · DaoyuanLi2816/mini-verl · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ All notable changes to miniVERL are recorded here. The format follows
normalization, arbitrary reward code, GRPO claim or distributed execution
claim. No task-quality benchmark was run for this profile.

### Managed vLLM rollout

- Added an optional, miniVERL-managed vLLM 0.28 backend for direct GKD on the
qualified Linux/WSL2 CUDA path. It owns localhost startup, unique
content-bound LoRA identities, raw output IDs, bounded requests and complete
process-group teardown between rollout and training phases.
- Disabled persistent prefix caching and made every failed adapter refresh
quiesce and close the server, so a stale policy cannot serve after a failed
synchronization.
- Kept PG-k1 on `hf_cached`: the BF16 external engine and NF4 actor agreed on
greedy tokens in the qualification probe, but their sampled-token
log-probabilities did not satisfy the preregistered numerical threshold.

### Documentation narrative

- Rebuilt the English, Chinese and PyPI landing narratives around the local
Expand Down
35 changes: 32 additions & 3 deletions docs/rollout-runtime-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ remain fixed at `n=1`; the grouped profiles are conformance-only until a later
evidence stage measures them. Grouped rollouts currently require a Parquet
prompt source. Environment-backed recipes remain `n=1`.

The v0.11 baseline result remains the pre-change `hf_reference` measurement.
It is not evidence for `hf_cached` or grouped-rollout performance. External
engine support is a separate release-chain stage.
## Managed vLLM for direct GKD

Install the optional engine in the same Linux or WSL2 environment as the
training stack:

```bash
pip install "miniverl[train,rollout-vllm]"
```

Bind it into the immutable execution plan for the grouped direct-GKD profile:

```bash
miniverl plan \
--profile verl-opd-v0.8-single-gpu-grouped-v1 \
--config verl-opd.yaml \
--accept-local-reinterpretations \
--rollout-backend vllm \
--out plan.json
miniverl run --plan plan.json
```

miniVERL starts the pinned vLLM server on an ephemeral localhost port, exports
the live PEFT adapter under a policy-version-and-digest name, generates raw
token IDs, and terminates the complete process group before teacher scoring or
the actor update. Prefix caching is disabled, and a failed policy refresh
closes the server before another request can run.

This backend is qualified for direct GKD. Use `hf_cached` for PG-k1: vLLM's
BF16 rollout and the NF4 training actor are a recorded numerical-equivalence
class, not sampled-logprob identity. The release-chain qualification measured
throughput separately from the conformance probes; the frozen pre-change
`hf_reference` result remains the compatibility baseline.
3 changes: 3 additions & 0 deletions pyproject.toml
8000
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ dpo = [
# `bridge doctor --require-adapter-payload` can never be satisfied, so the extra
# that exists for bridge work has to carry it.
bridge = ["pyarrow>=15", "numpy>=1.24"]
rollout-vllm = [
"vllm==0.28.0; platform_system == 'Linux'",
]
# External alignment benchmarks. Deliberately separate from the core and from
# [train]: a plain `pip install miniverl` must not pull in dataset tooling,
# judge models or a network client. The judge models themselves are ordinary
Expand Down
7 changes: 6 additions & 1 deletion src/miniverl/bridge/opd_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def build_immutable_opd_plan(
*,
source: str | Path,
system_plan: OPDSystemPlan | None = None,
rollout_backend: str | None = None,
) -> ImmutableOPDPlan:
"""Scan local inputs and build a deterministic, weight-free execution artifact."""
acceptance = compiled.reinterpretation_acceptance
Expand All @@ -168,7 +169,11 @@ def build_immutable_opd_plan(
)
source_identity, base = _source_identity(source)
system = system_plan or build_system_plan(compiled)
native = compile_native_run_config(compiled, system_plan=system)
native = compile_native_run_config(
compiled,
system_plan=system,
rollout_backend=rollout_backend,
)
_resolve_data_paths(native, base)
student = _revision(
native.models.student.model_id,
Expand Down
37 changes: 32 additions & 5 deletions src/miniverl/bridge/opd_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ def compile_native_run_config(
compiled: CompiledLocalExecutionPlan,
*,
system_plan: OPDSystemPlan | None = None,
rollout_backend: str | None = None,
) -> RunConfig:
"""Translate the supported profile into an executable, validated RunConfig."""
source = compiled.source
Expand Down Expand Up @@ -339,6 +340,16 @@ def compile_native_run_config(
VERL_OPD_PG_K1_REWARDED_V08_PROFILE,
}
rewarded = compiled.profile == VERL_OPD_PG_K1_REWARDED_V08_PROFILE
if rollout_backend not in {None, "hf_reference", "hf_cached", "vllm"}:
raise ConfigError(
f"unsupported local rollout backend {rollout_backend!r}",
hint="choose hf_reference, hf_cached, or vllm",
)
if rollout_backend == "vllm" and pg_k1:
raise ConfigError(
"the measured vLLM backend is qualified for direct GKD only",
hint="use hf_cached for PG-k1 sampled-token log-probabilities",
)
rewarded_loss = cast(VerlRewardedPGK1LossConfig, source.distillation.distillation_loss)
loss_payload = (
{
Expand Down Expand Up @@ -447,11 +458,14 @@ def compile_native_run_config(
},
"rollout": {
"backend": (
"hf_cached"
if compiled.profile
in {VERL_OPD_GROUPED_V08_PROFILE, VERL_OPD_PG_K1_GROUPED_V08_PROFILE}
or rewarded
else "hf_reference"
rollout_backend
or (
"hf_cached"
if compiled.profile
in {VERL_OPD_GROUPED_V08_PROFILE, VERL_OPD_PG_K1_GROUPED_V08_PROFILE}
or rewarded
else "hf_reference"
)
),
"samples_per_prompt": source.actor_rollout_ref.rollout.n,
"max_turns": 1,
Expand All @@ -467,6 +481,19 @@ def compile_native_run_config(
"max_padded_tokens": source.actor_rollout_ref.rollout.max_num_batched_tokens
or (source.data.max_prompt_length + source.data.max_response_length)
* source.miniverl.batching.rollout_batch_size,
**(
{
"engine": {
"managed": True,
"host": "127.0.0.1",
"memory_fraction": (
source.actor_rollout_ref.rollout.gpu_memory_utilization
),
}
}
if rollout_backend == "vllm"
else {}
),
},
"selection": {"selector": "all_model_tokens"},
"loss": loss_payload,
Expand Down
40 changes: 36 additions & 4 deletions src/miniverl/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,11 @@ def plan_command(
profile: str = typer.Option(
"verl-opd-v0.8-single-gpu-v1", "--profile", help="Pinned compatibility profile."
),
rollout_backend: Optional[str] = typer.Option(
None,
"--rollout-backend",
help="Local execution backend: hf_reference, hf_cached, or vllm.",
),
overrides: list[str] = typer.Option([], "--set", help="Repeatable dotted key=value override."),
override_files: list[Path] = typer.Option(
[],
Expand Down Expand Up @@ -971,6 +976,14 @@ def plan_command(
)
plan = build_system_plan(compiled)
payload = plan.model_dump(mode="json")
if rollout_backend is not None:
from miniverl.bridge.opd_runtime import compile_native_run_config

payload["resolved_native_config"] = compile_native_run_config(
compiled,
system_plan=plan,
rollout_backend=rollout_backend,
).model_dump(mode="json")
artifact = None
if out is not None or probe:
from miniverl.bridge.opd_plan import (
Expand All @@ -979,7 +992,12 @@ def plan_command(
write_immutable_opd_plan,
)

artifact = build_immutable_opd_plan(compiled, source=config, system_plan=plan)
artifact = build_immutable_opd_plan(
compiled,
source=config,
system_plan=plan,
rollout_backend=rollout_backend,
)
if probe:
_require_training_stack("miniverl plan --probe")
from miniverl.bridge.opd_probe import run_hardware_probe
Expand Down Expand Up @@ -1051,6 +1069,11 @@ def verl_run_command(
profile: str = typer.Option(
"verl-opd-v0.8-single-gpu-v1", "--profile", help="Pinned compatibility profile."
),
rollout_backend: Optional[str] = typer.Option(
None,
"--rollout-backend",
help="Local execution backend: hf_reference, hf_cached, or vllm.",
),
overrides: list[str] = typer.Option([], "--set", help="Repeatable dotted key=value override."),
override_files: list[Path] = typer.Option(
[],
Expand All @@ -1077,7 +1100,11 @@ def verl_run_command(
if plan_path is not None and config is not None:
raise ConfigError("--plan and --config are mutually exclusive")
if plan_path is not None and (
overrides or override_files or ctx.args or accept_local_reinterpretations
overrides
or override_files
or ctx.args
or accept_local_reinterpretations
or rollout_backend is not None
):
raise ConfigError(
"--plan cannot be combined with config overrides or reinterpretation flags",
10BC8 Expand Down Expand Up @@ -1116,7 +1143,11 @@ def verl_run_command(
),
)
system_plan = build_system_plan(compiled)
native = compile_native_run_config(compiled, system_plan=system_plan)
native = compile_native_run_config(
compiled,
system_plan=system_plan,
rollout_backend=rollout_backend,
)
from miniverl.config.models import VerlParquetSourceConfig

if not isinstance(native.source, VerlParquetSourceConfig): # pragma: no cover
Expand Down Expand Up @@ -1145,7 +1176,8 @@ def verl_run_command(
console.print(f" data {_esc(', '.join(native.source.train_files))}")
console.print(f" actor {_esc(native.models.student.model_id)}")
console.print(
f" rollout local HF, n=1, max response "
f" rollout {_esc(native.rollout.backend.value)}, "
f"n={native.rollout.samples_per_prompt}, max response "
f"{_esc(native.source.max_response_length)}"
)
console.print(f" teacher {_esc(native.models.teacher.model_id)}")
Expand Down
17 changes: 16 additions & 1 deletion src/miniverl/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,21 @@ class RolloutConfig(_Base):
compile_backend: bool = False
engine: RolloutEngineConfig = Field(default_factory=RolloutEngineConfig)

@model_validator(mode="after")
def _external_engine_is_fail_closed(self) -> RolloutConfig:
if self.backend is not RolloutBackendKind.VLLM:
return self
if not self.engine.managed or self.engine.host != "127.0.0.1":
raise ValueError("rollout.backend=vllm requires a managed localhost engine")
if self.record_logprobs:
raise ValueError(
"rollout.backend=vllm is qualified for direct GKD only; its sampled-token "
"log-probabilities did not pass the PG numerical-conformance gate"
)
if self.compile_backend:
raise ValueError("rollout.compile_backend applies only to hf_cached")
return self


class SourceKind(str, Enum):
"""Where rollout inputs originate."""
Expand Down Expand Up @@ -875,7 +890,7 @@ def _validate_combination(self) -> RunConfig:
and self.rollout.backend is not RolloutBackendKind.HF_REFERENCE
):
raise ValueError(
"rollout.backend=hf_cached currently requires source.kind=verl_parquet; "
"non-reference rollout backends currently require source.kind=verl_parquet; "
"tool-environment episodes retain the hf_reference path"
)
if self.source.kind is SourceKind.VERL_PARQUET and self.environment is not None:
Expand Down
3 changes: 2 additions & 1 deletion src/miniverl/runtime/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

from miniverl.runtime.backends.hf_cached import HFCachedGenerationBackend
from miniverl.runtime.backends.hf_reference import HFReferenceGenerationBackend
from miniverl.runtime.backends.vllm import VLLMGenerationBackend

__all__ = ["HFCachedGenerationBackend", "HFReferenceGenerationBackend"]
__all__ = ["HFCachedGenerationBackend", "HFReferenceGenerationBackend", "VLLMGenerationBackend"]
Loading
0