Skip to content

feat(refit): add digest verification for vLLM IPC weight transfers - #3961

Open
NolenLiang wants to merge 6 commits into
NVIDIA-NeMo:mainfrom
NolenLiang:feat/refit-weight-digest-verify
Open

feat(refit): add digest verification for vLLM IPC weight transfers#3961
NolenLiang wants to merge 6 commits into
NVIDIA-NeMo:mainfrom
NolenLiang:feat/refit-weight-digest-verify

Conversation

@NolenLiang

@NolenLiang NolenLiang commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What does this PR do ?

Adds opt-in sender/receiver digest verification for colocated vLLM CUDA-IPC weight refits.

Policy and vLLM workers independently digest each transferred parameter's bytes, dtype, and shape. The receiver returns its digests with the final acknowledgment, and the sender either logs mismatches or fails the refit. The digest is a deterministic, ordered two-channel tree fold that runs on the tensor's device and remains stable across CPU/CUDA devices and chunk boundaries.

This PR also validates verification configuration at setup, rejects unsupported transports and generation backends instead of silently ignoring the setting, preserves the existing IPC wire format when verification is disabled, and updates the refit guide and example configurations.

Issues

N/A — this is a proactive feature and is not linked to an existing issue.

Usage

policy:
  generation:
    backend: "vllm"
    colocated:
      enabled: true
    refit_transport: null
    refit_cfg:
      verify:
        mode: "enforce"  # "off" | "log" | "enforce"
  • "off" is the default: no digest computation and no IPC wire-format change.
  • "log" reports mismatched parameters and continues.
  • "enforce" raises on a mismatch.

Verification is currently supported only for colocated vLLM CUDA-IPC refits (colocated.enabled=true and refit_transport=null). Enabling it for collective, NCCL reshard, sparse/NIXL/plugin transports, or another generation backend fails during setup.

Before your PR is "Ready for review"

Pre checks:

  • Make sure you read and followed Contributor guidelines
  • Did you write any new necessary tests?
  • Did you run the unit tests and functional tests locally? Visit our Testing Guide for how to run tests
  • Did you add or update any necessary documentation? Visit our Document Development Guide for how to write, build and test the docs.

Focused unit tests were run locally; GPU functional coverage is left to CI.

Additional Information

Validation performed:

  • Focused digest/config/refit regression suite: 25 passed.
  • Worker/refit signature-contract suite: 50 passed.
  • Repository-pinned Ruff 0.9.9 lint and format checks passed.
  • Changed example/reference YAML files parse successfully, and git diff --check passes.

Risk and scope:

  • Verification is disabled by default, so existing refits retain the current byte-ACK protocol and do not run hashing kernels.
  • Enabled modes add per-parameter device-side hashing and one final digest materialization/acknowledgment step.
  • The digest targets accidental transfer corruption and software or metadata desynchronization; it is not a cryptographic integrity mechanism for malicious inputs.
  • Verification covers the transferred tensor view before vLLM loads it. Post-load fusion, TP sharding, and quantization transformations are intentionally outside scope.

Adds opt-in verification that the bytes loaded by vLLM workers during a
colocated CUDA-IPC refit are exactly the bytes the policy workers sent,
configured via policy.generation.refit_cfg.verify.mode (off|log|enforce,
default off with zero overhead).

Each side hashes its view of the transfer with a polynomial rolling hash
over the raw bytes computed in int64 with wraparound (mod 2^64). Integer
modular arithmetic is associative and commutative, so the digest is
bit-identical regardless of reduction order, chunking, or device --
unlike a floating-point checksum, which would be subject to the very
nondeterminism this check is meant to catch. Digests stay on-device
during streaming (no per-tensor sync) and the receiver returns them with
the final COMPLETE ACK as a pyobj, so worker return values and the
synchronizer's success handling are unchanged. The sender compares and
warns (log) or raises with the mismatched parameter names (enforce).

Because the receiver slices the staged buffer from its own
prepare_refit_info metadata, digest equality also implicitly validates
that the refit metadata still matches what the exporter streams.

Scope: colocated IPC/ZMQ transport only; the NCCL collective path is a
follow-up. Sparse-delta refit already has its own payload checksums.

Signed-off-by: Nolen Liang <nliang@nvidia.com>
- Replace the linear polynomial digest with position-salted SplitMix64
  mixing: the linear form cancels deterministically when two int64 lanes
  both flip their top bit, since 2^63 * (R^a + R^b) is 0 mod 2^64 for
  odd R.
- Fold dtype and shape into the digest seed so equal-size metadata drift
  is detected, not just byte corruption.
- Group digest materialization by device to support mixed CPU/CUDA
  parameter streams (e.g. scalar KV scales).
- Reject verify_digests on generation backends without support (Dynamo,
  TensorRT-LLM, SGLang) and fail at synchronizer construction for
  non-colocated transports instead of silently skipping verification.
- Drop repeated "off" parameter defaults along the call chain; the
  mode is resolved once from the config model and passed explicitly.
- Add digest.py to the Pyrefly check list and extend the
  signature-contract tests to the IPC update path of every generation
  backend.

Signed-off-by: Nolen Liang <nliang@nvidia.com>
- Widen the digest to two independently-parameterized 64-bit channels
  with positions injected through the nonlinear mixer. A linear position
  salt is absorbable: corrupted lanes can soak up the salt difference
  between two positions and permute the salted values, which a
  commutative sum cannot see -- and for any single bijective per-lane
  transform such a permutation is constructible in closed form. With two
  channels the same corrupted lanes must simultaneously preserve both
  nonlinearly coupled sums, for which no closed-form construction
  exists. Adds the two-lane construction as a regression test; digests
  serialize as 32-hex strings.
- Validate refit verification support at VllmGeneration construction:
  collective/sparse/PPO/distillation paths construct synchronizers
  directly (bypassing the weight-sync factory), which previously let
  verify.mode=enforce be silently ignored on unsupported topologies.
- Make VllmRefitVerifyConfig extra="forbid" and stop coercing explicit
  invalid values (verify: false, misspelled keys) into the "off"
  default; only absent fields default. Adds validation tests.
- Sync the verify block into the PPO and distillation exemplar YAMLs
  and their reference configs.

Signed-off-by: Nolen Liang <nliang@nvidia.com>
…rify config

- Replace commutative-sum digest channels with an ordered binary tree
  fold. k commutative channels impose only k multiset constraints while
  n lanes provide n degrees of freedom, so lane permutations satisfying
  every channel simultaneously exist and were constructed for the one-
  and two-channel variants; in a tree a lane's position is bound to its
  path, leaving no permutation freedom. Both children pass through the
  mixer (distinct tweaks) before the linear combination -- a bare linear
  combination leaves 2^63 * (odd + odd) == 0 mod 2^64 open, which the
  paired-top-bit regression caught. Each level is elementwise over
  disjoint pairs, so the fold stays parallel and device-independent;
  chunk roots are order-chained. The chunk size is now an algorithm
  constant. Adds the six-lane dual-channel collision pair (int64 and
  bf16-reinterpreted) as regressions.
- Only a truly absent verify field defaults to off: explicit
  "verify: null" now fails in Pydantic, and unknown top-level
  refit_cfg keys (e.g. "verfiy") are rejected at VllmGeneration
  construction, scoped to allow custom checkpoint-engine selector keys.

Signed-off-by: Nolen Liang <nliang@nvidia.com>
…atter

- resolve_refit_verify_config now feeds any explicit non-null refit_cfg
  to Pydantic as-is, so non-mapping values ([] / "" / false) fail
  loudly instead of silently defaulting verification to off; only a
  truly absent refit_cfg defaults.
- The unknown-key check now also covers VllmRefitConfig.model_extra:
  paths that call normalize_vllm_refit_config before constructing
  VllmGeneration (sparse/NIXL/plugin transports) validate and write the
  model back, so a typo like "verfiy" previously slipped into
  model_extra and bypassed the dict-based check. Adds regressions in
  the real normalize -> construct order, including the plugin-selector
  allowance.
- normalize_vllm_refit_config no longer coerces falsy refit_cfg values
  into an empty config.
- Reformat with the repository-pinned ruff (0.9.9); an unpinned newer
  ruff had reformatted one test file differently.

Signed-off-by: Nolen Liang <nliang@nvidia.com>
@NolenLiang
NolenLiang requested review from a team as code owners September 2, 2026 06:51
@copy-pr-bot

copy-pr-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the Documentation Improvements or additions to documentation label Sep 2, 2026
@NolenLiang NolenLiang added the CI:L1 Run doctests, unit tests, and functional tests label Sep 2, 2026
@NolenLiang

Copy link
Copy Markdown
Contributor Author

/ok to test 18adf3d

Signed-off-by: Nolen Liang <nliang@nvidia.com>
@NolenLiang

Copy link
Copy Markdown
Contributor Author

/ok to test 3740298

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI:L1 Run doctests, unit tests, and functional tests Documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant