Skip to content

feat(rollout): add vLLM/vLLM-Ascend rollout path for DFlash OPD on Ascend NPU - #8

Open
curnane-lab wants to merge 45 commits into
Simplified-Reasoning:dev_vllmfrom
curnane-lab:add_vllm_support
Open

feat(rollout): add vLLM/vLLM-Ascend rollout path for DFlash OPD on Ascend NPU#8
curnane-lab wants to merge 45 commits into
Simplified-Reasoning:dev_vllmfrom
curnane-lab:add_vllm_support

Conversation

@curnane-lab

@curnane-lab curnane-lab commented Jul 28, 2026

Copy link
Copy Markdown

Summary

Adds a vLLM / vLLM-Ascend rollout engine path for DFlash on-policy distillation, as an alternative to the existing SGLang path (which is untouched). Validated end to end on Ascend NPU. Opened against the dev_vllm staging branch as requested in #7.

Engine versions (pinned as git submodules)

  • vllm: vllm-project/vllm @ ad7125a431e176d4161099480a66f0169609a690 (tag v0.21.0)
  • vllm-ascend: vllm-project/vllm-ascend @ 80610e4438dba05011b05f89fc45d91e96992671 (branch releases/v0.21.0rc, tag v0.21.0rc1)
  • The dflash speculative method is upstreamed in both; no fork patches are required.

Environment

  • Base image (batteries included — CANN, torch/torch_npu, vllm + vllm-ascend at the pinned versions):
    quay.nju.edu.cn/ascend/vllm-ascend:v0.21.0rc1-a3
  • Hardware: single node, 16x Ascend NPU (64 GiB HBM each), Atlas A3 series
  • Models: target Qwen3-4B, draft Qwen3-4B-DFlash-b16 (block_size 16), num_speculative_tokens=7
  • Software in image: CANN + torch/torch_npu stack matching vllm-ascend v0.21.0rc1, triton-ascend==3.2.1

Install & reproduce

# 1. base environment (CANN / torch_npu / vllm / vllm-ascend preinstalled)
docker pull quay.nju.edu.cn/ascend/vllm-ascend:v0.21.0rc1-a3
docker run --rm -it --net host --shm-size 64g \
  --device /dev/davinci0 --device /dev/davinci_manager \
  --device /dev/devmm_svm --device /dev/hisi_hdc \
  -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
  -v /usr/local/dcmi:/usr/local/dcmi \
  -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
  -w /workspace quay.nju.edu.cn/ascend/vllm-ascend:v0.21.0rc1-a3 bash

# 2. code + verl (engines in the image are reused as-is)
git clone --recursive git@github.com:curnane-lab/Draft-OPD.git
cd Draft-OPD && git checkout add_vllm_support
git submodule update --init --recursive
bash install_npu.sh          # skips already-installed engines; installs verl without extras

# 3. train
MAIN_MODEL_PATH=/path/to/Qwen3-4B \
DRAFT_MODEL_PATH=/path/to/Qwen3-4B-DFlash-b16 \
TRAIN_JSONL=/path/to/train.jsonl \
train_epochs=1 TEST_FREQ=50 \
bash verl/examples/on_policy_distillation_trainer/run_qwen_gsm8k_vllm_npu.sh \
  "data.val_files=[/path/to/gsm8k_prompt.jsonl]"

Key launcher defaults (all env-overridable): 16 cards (14 student + 2 teacher), graph mode with PIECEWISE capture [8,16,32,64], HYBRID sleep/wake, sdpa draft attention, OPD anchor cap 186, gradient checkpointing on.

Design & compatibility

  • SGLang path: unchanged. Engine choice is config-driven (ROLLOUT_NAME); the vLLM path is additive and shares the OPD trainer (dflash_student, losses, anchor plan).
  • vLLM path: one vLLMHttpServer per student worker (14 replicas, TP=1, AsyncLLM). OPD reject metadata: monkey-patched NPUModelRunner._sample records verify events into a per-process registry → on request finish the server pops them via collective_rpcdflash_* extra fields → OPD anchor plan in the composed student.
  • Weight sync: only the trainable draft subtree (58 tensors) is streamed each step (dflash_draft_only); the frozen target is loaded once at engine init. HYBRID sleep uses level 1 for the composed student so the frozen target survives sleep/wake.
  • Acceptance accounting handles both stock vLLM (placeholder-padded rows) and vllm-ascend (fully-filled rows) rejection-sampler output formats; the registry lookup tolerates engine-suffixed request ids.

Tests

CPU unit tests (no NPU needed):

python -m pytest verl/tests/workers/rollout/test_dflash_opd_vllm_on_cpu.py -v

Covers: registry position replay, engine-suffixed request-id matching, acceptance detection for both sampler output formats, and the dflash_* extra-fields contract.

Validation (16x NPU, ~550 steps / 55 logged points, first OPD end-to-end run)

Training dashboard (from the run's wandb history):

draft-opd

Readout of the curves:

  • Draft acceptance rising: opd/rollout/reject_token_count/mean 466 → ~360 and still declining — the OPD signal is being consumed as designed.
  • Loss converging: actor/distillation/loss 1.94 → ~1.2; forward_kl_loss 0.85 → ~0.7; rejected_draft_reverse_kl_loss 8.2 → 6.5 with rejected_draft_loss_clamp_fraction 0.71 → 0.49.
  • Stable optimization: grad_norm settles from early spikes (≤0.28) to ~0.065; empty_reject_ratio and opd_skipped_sample_ratio stay at 0.0 for the whole run; opd_effective_token_ratio ~0.3; anchors ~120/sample.
  • Healthy generation: response_length/clip_ratio ~0.19 (EOS restored; was 1.0 in the broken state), response_length/mean ~1400 → 1500.
  • Throughput improving: perf/throughput 40 → 46–48; timing_s/gen ~430 → ~380s as acceptance rises.
  • Engine: PIECEWISE aclgraph mode, finite logits, real rejection events throughout (OPD_PATCH_DEBUG sampling).
  • wandb offline run of this training: TODO(author) — attach run-*.wandb or link

Closes #7.

mingliangfu and others added 30 commits July 17, 2026 15:56
Mirror the DFLASH OPD contract of the SGLang rollout so the training side
(composed DFLASH student + distillation losses) stays engine-agnostic:

- map_opd_draft_weight_name / iter_opd_draft_weights: strip FSDP/wrapper
  prefixes and keep only the draft_model.* subtree, renamed to
  draft-relative keys the inference engine expects.
- build_dflash_extra_fields: build TokenOutput.extra_fields from
  engine-side reject metadata (anchor indices / offsets / token ids /
  teacher logprobs), matching the keys consumed by agent_loop.
- merge_dflash_metadata: tolerate None parts from TP ranks.
…apter

When the composed DFLASH/EAGLE3 OPD student is enabled, engine_workers
passes dflash_draft_only=True. The vLLM ServerAdapter now:

- filters the weight stream to the draft_model.* subtree (renamed to
  draft-relative keys via opd_utils.iter_opd_draft_weights);
- forwards draft_model_only=True to the worker extension so weights are
  loaded into the speculative draft model instead of the frozen target;
- counts synced tensors, raises on an empty stream, and returns
  opd/weight_sync/draft_tensor_count like the SGLang path.

Rejects the incompatible combination with LoRA adapter sync.
Extend vLLMColocateWorkerExtension.update_weights_from_ipc with a
draft_model_only flag: when set, received tensors are routed to
model_runner.drafter.model.load_weights() (e.g. DFlashQwen3ForCausalLM)
instead of the frozen target model, and target-only post-load transforms
(process_weights_after_loading / MoE weight-loader patch) are skipped.

The draft-relative key naming lines up with vllm's DFlash loader, which
re-adds the 'model.' prefix itself. Raises a clear error when the server
was started without a speculative_config.
OPD training needs per-request reject positions and teacher logprobs of
rejected draft tokens, which stock vLLM/vllm-ascend does not expose (the
SGLang path gets them from the sglang-dflash fork).

This module monkey-patches NPUModelRunner._sample: after each verify step
it replays acceptance per request (num_accepted = non-placeholder tokens -
 1, robust for random rejection sampling where the recovered token may
coincide with the draft token), records the rejected draft token id, its
1-based block offset, and the target logprob of the rejected token
(TP-aware gather from the raw target logits, before top-k/top-p).

Events accumulate in a per-request registry with cumulative response
position tracking (response starts with the prefill token at position 0;
each verify step appends num_accepted + 1 tokens). pop() materializes the
same anchor/offset/token_id/teacher_logprob contract the SGLang rollout
produces. Activated via VERL_DFLASH_OPD=1; failures never break serving.
- vLLMColocateWorkerExtension: apply dflash_opd_patch at worker init when
  VERL_DFLASH_OPD=1; expose get_dflash_opd_metadata / gc_dflash_opd_metadata
  for collective_rpc from the rollout frontend.
- vLLMHttpServer: detect the composed DFlash/EAGLE3 OPD student
  (hf_config.verl_composed_dflash_student), force load_format=auto when it
  is dummy (target weights must be real since only the draft is trained),
  and set VERL_DFLASH_OPD=1 before engine launch so workers inherit it.
- vLLMHttpServer.generate: after the final output, fetch the request's
  reject metadata via collective_rpc and merge it into extra_fields with
  opd_utils.build_dflash_extra_fields, producing the same dflash_* keys
  the SGLang rollout path emits (agent_loop / OPD losses unchanged).
- MtpConfig gains draft_model_path: a standalone draft checkpoint for
  methods that use a separate draft model (dflash/eagle). It is passed as
  speculative_config["model"]; None keeps target-integrated methods (mtp).
- vLLMHttpServer forwards it into speculative_config alongside method and
  num_speculative_tokens when mtp rollout is enabled.
- run_qwen_gsm8k.sh: ROLLOUT_NAME becomes overridable (default sglang).
- New example run_qwen_gsm8k_vllm_npu.sh: Draft-OPD on Ascend NPU with
  the vLLM(vllm-ascend) rollout engine (method=dflash, K=16 by default);
  whitelisted in .gitignore like the other public entrypoints.
Cover the engine-agnostic pieces that do not need vLLM/NPU hardware:

- map_opd_draft_weight_name / iter_opd_draft_weights: wrapper-prefix
  stripping, nested draft_model marker, target-weight rejection.
- DFlashOpdMetadataRegistry.pop: response-position replay across verify
  steps (prefill token at position 0, each verify appends accepted+1),
  offset/token/teacher-logprob passthrough, pop-once semantics.
- build_dflash_extra_fields: parity with the SGLang contract keys,
  out-of-range anchor filtering, empty/missing metadata, length checks.
Clone with --recursive now yields the full vLLM(vllm-ascend) engine stack:

- vllm @ 7c5deda2 (curnane-lab/vllm, domino_npu_v0.21.0)
- vllm-ascend @ f9fbb0a8 (curnane-lab/vllm-ascend, domino_npu_v0.21.0rc1,
  which carries the official DFlash speculative method plus the
  longer-draft buffer fixes the OPD rollouts rely on)

install.sh now runs git submodule update --init --recursive (so a plain
clone also works) and installs vllm (VLLM_TARGET_DEVICE=empty) and
vllm-ascend (with its nested custom-operator submodules) before
sglang-dflash and verl. README documents the submodule layout and the
Ascend NPU notes (CANN/torch-npu/triton-ascend prerequisites and the
run_qwen_gsm8k_vllm_npu.sh entrypoint).
dflash_opd_patch recorder:
- Batch the per-step device syncs: one (sampled != PLACEHOLDER).sum(dim=1)
  plus one draft_token_ids.tolist() per verify step instead of a
  per-request .item() (syncs are expensive on NPU).
- Record plain decode steps (num_draft_tokens == 0) as (0,0,-1) events so
  the response-position replay stays aligned when a request occasionally
  has a draft-free step.
- Remove duplicated slicing/check block left by the previous edit.

run_qwen_gsm8k_vllm_npu.sh:
- ROLLOUT_ENFORCE_EAGER (default True) for the first NPU bring-up so
  aclgraph failure modes are out of scope; flip to False after validation.
- ROLLOUT_FREE_CACHE_ENGINE knob (default True; vllm-ascend implements
  worker.sleep/wake_up) with the documented fallback for hybrid colocate.
- Comment the recommended PYTORCH_NPU_ALLOC_CONF=max_split_size_mb:32.
- INSTALL_VLLM / INSTALL_VLLM_ASCEND / INSTALL_SGLANG_DFLASH switches so
  container images that already ship the engines (e.g. editable installs
  under /vllm-workspace) can skip those steps instead of clobbering them.
- Warn against installing verl with extras: verl[vllm] pins vllm<=0.12.0
  and verl[sglang] pins torch==2.9.1, both incompatible with an NPU /
  vllm-ascend v0.21 stack. Plain 'pip install -e ./verl' is unpinned.
- requirements-npu.txt: triton-ascend 3.2.0 -> 3.2.1, matching the
  vllm-ascend v0.21.0rc1 installation guide.
Auto-detects each component with importlib.metadata and skips what is
already installed, so one script covers both pre-provisioned NPU
containers (vllm/vllm-ascend already present, e.g. /vllm-workspace) and
bare environments (engines installed from the in-repo submodules):

- vllm / vllm_ascend: skip when present; warn when the version is not
  the v0.21.x the OPD patches target. git submodule update only runs
  when an engine actually needs installing, so plain clones without
  --recursive work in pre-provisioned containers.
- triton-ascend: skip when present, else install ==3.2.1.
- sglang: skip when present, else install the vendored sglang-dflash;
  SKIP_SGLANG_DFLASH=1 opts out.
- verl: skip when already editable-installed from this repo; always
  installed without extras (verl[vllm]/[sglang] pin old engines and
  would break an NPU / vllm-ascend v0.21 stack).
- torch/torch_npu presence is checked and warned about but never
  auto-installed (must match the CANN toolkit).
- FORCE_INSTALL_ENGINES=1 forces engine reinstall from submodules.
The vLLM(vllm-ascend) rollout path does not use the sglang engine, and
installing the vendored CUDA-ecosystem sglang-dflash can disturb the
container's torch/torch_npu stack via dependency resolution. It is now
opt-in (INSTALL_SGLANG_DFLASH=1) for users who need the SGLang
reference path; the old SKIP_SGLANG_DFLASH switch is removed.
- install.sh is now a thin wrapper around install_npu.sh so there is a
  single adaptive implementation (previously it still installed the
  engines and sglang-dflash by default, which could clobber a
  pre-provisioned container or disturb its torch/torch_npu stack).
- README Install section now documents the adaptive behavior, the
  sglang opt-in (INSTALL_SGLANG_DFLASH=1), FORCE_INSTALL_ENGINES=1, and
  the no-extras rule for verl.
- CONTRIBUTING.md gains an Ascend NPU note: do not install the
  [vllm]/[sglang] extras (they pin vllm<=0.12.0 and sglang==0.5.8 +
  torch==2.9.1 and would downgrade the pre-installed engines).
vLLMHttpServer._init_config instantiates the rollout config via hydra
instantiate(_convert_="partial"). rollout.mtp is normally an
interpolation to the structured model.mtp (with _target_=MtpConfig), but
force-add (++) CLI overrides replace that node with a plain dict lacking
_target_, so instantiate leaves mtp as a dict and
"self.config.mtp.enable" raised AttributeError: 'dict' object has no
attribute 'enable'.

Two-sided fix:
- vllm_async_server: read mtp fields through _config_node_get, which
  handles dataclass / DictConfig / plain dict uniformly.
- run_qwen_gsm8k_vllm_npu.sh: pass
  ++actor_rollout_ref.rollout.mtp._target_=verl.workers.config.MtpConfig
  as the first mtp override so instantiate recreates a real MtpConfig.
DFlashQwen3ForCausalLM.load_weights returns None (it delegates to an
inner AutoWeightsLoader and finishes with _build_fused_kv_buffers), so
the draft-only sync crashed in the f-string after the load had already
succeeded. Count the received weight tensors instead.
The training side crashed with 'DFLASH OPD reject metadata batch size
mismatch: got 0 entries' when a rollout sample carried no dflash keys.
Three layered fixes:

1. SGLang-parity contract: _collect_dflash_opd_fields now returns the
   full dflash_* key set with empty values (new
   opd_utils.empty_dflash_extra_fields) when a request produced no
   verify events, instead of omitting the keys entirely. A single
   short-response sample (EOS right after prefill) can no longer kill
   the whole micro batch.
2. Startup probe: vLLMHttpServer runs a collective_rpc probe right after
   engine init when VERL_DFLASH_OPD=1 and raises immediately if the
   _sample patch is not applied or no drafter exists on any worker —
   fail fast at startup instead of failing at the first training step.
3. Robust activation: VERL_DFLASH_OPD=1 is now set whenever the
   effective speculative_config uses method=dflash (the composed-student
   flag is no longer the sole trigger), with a startup log line showing
   composed_student / speculative_config / env state; generate() gates
   collection on the env flag; the first missing-metadata case is logged.
Engine core processes died at startup once VERL_DFLASH_OPD=1 activated
apply_dflash_opd_patches for the first time. The most likely cause is a
vllm-ascend version drift: if the container's NPUModelRunner lacks the
_sample attribute the patch wraps, the AttributeError propagated through
the worker extension __new__ and killed the engine worker during
launch_core_engines.

- apply_dflash_opd_patches now catches every exception path (import
  failure, missing _sample attribute, anything else), logs clearly, and
  returns False instead of raising.
- The worker extension wraps the call in try/except as a second layer,
  so a patch failure can never kill an engine worker; the startup probe
  (_assert_dflash_opd_pipeline) is responsible for reporting the broken
  state with a clear error.
…ptance stats

Gated by OPD_PATCH_DEBUG=1. Prints per-request num_draft_tokens,
non-placeholder valid counts, the first sampled row, the draft token
ids and the derived rejected events for the first verify step, to
diagnose why recorded events carry no rejections (empty anchors).
…q id and logits magnitude

The single-shot dump fired during the engine startup dummy/profile run
(all-zero tensors), which is indistinguishable from real traffic without
the request id. Log the first 8 steps instead, include req0 and the raw
target logits abs-max to spot degenerate forward passes.
…se token sampling

- skip warmup/profile dummy requests at the recording entry so their
  all-zero rows do not pollute the registry
- OPD_PATCH_DEBUG now logs the first 8 verify steps plus every 500th,
  tagged with an allzero flag for the sampled/draft rows
- vLLMHttpServer logs the first 32 token ids of the first 3 finished
  responses per server, the decisive check for degenerate generation
Match the value validated by standalone vllm-ascend dflash serving;
16 remains available via the NUM_SPECULATIVE_TOKENS env override.
The composed DFLASH/EAGLE3 OPD student re-syncs only the trainable draft
subtree after wake (dflash_draft_only); the frozen target is loaded once at
engine init and never streamed again. _sleep_hybrid hard-coded sleep level 2,
which frees weight memory without a host copy, so after the first sleep the
target ran on uninitialized memory: target logits -> inf, argmax collapsed to
token 0, every draft token was trivially accepted (no rejection anchors), OPD
loss was identically 0 and generation degenerated (responses filled to
max_tokens, never emitting EOS).

Use sleep level 1 for the composed student (and, as before, for LoRA adapters)
so resident weights survive sleep/wake. This mirrors the SGLang path, which
already sets enable_weights_cpu_backup=True for the composed student.

Co-authored-by: curnane-lab <curnane-lab@users.noreply.github.com>
…eight-sleep-8e24

fix(rollout): keep frozen DFlash target weights across HYBRID sleep (vLLM OPD zero-loss root cause)
…cher

Matches the default 7-student + 1-teacher split (TRAIN_PROMPT_BSZ=21
stays divisible by 7 agent workers). ASCEND_RT_VISIBLE_DEVICES can
still be overridden from the launching environment.
…just PLACEHOLDER count

After the target-weight sleep fix, generation is healthy but OPD still records
zero rejections (opd/rollout/empty_reject_ratio=1.0, loss=0). The engine patch
inferred num_accepted purely from the PLACEHOLDER_TOKEN_ID padding of
sampled_token_ids (n_accepted = n_valid - 1). That relies on stock vLLM v1's
rejected-tail padding contract, which vllm-ascend's AscendRejectionSampler does
not follow: it returns a fully-filled row, so the placeholder count reports full
acceptance every step and every rejection anchor is dropped.

Add compute_dflash_num_accepted(), which also uses the matching-prefix signal
(an accepted speculative token equals the sampled token at that position; the
first mismatch is the rejection point) and takes min() of the two signals so it
never over-reports acceptance and never regresses the stock/GPU path. Also log
per-row accepted counts under OPD_PATCH_DEBUG.

Adds CPU tests covering stock-placeholder and ascend-no-placeholder cases.

Co-authored-by: curnane-lab <curnane-lab@users.noreply.github.com>
logits_absmax was always inf because compute_logits() masks the padded/OOV
vocab columns with -inf (monkey_patch_compute_logits), so it never reflected
forward health. Report the finite-only abs max instead, which is nan only when
the whole forward is genuinely non-finite.

Co-authored-by: curnane-lab <curnane-lab@users.noreply.github.com>
The dflash rollout path is validated end to end (healthy verify events,
real rejection anchors, finite logits). Eager mode costs 2-5x decode
time on NPU, so flip the default to graph mode; eager remains available
via the env override for bring-up/debugging.
…eights)

The HYBRID sleep/wake cycle offloads and restores the full weight set
between NPU and host RAM on every step (sleep level=1 since f113a95),
which is a multi-second per-step cost per engine. With the 4B student
FSDP-sharded over the worker cards, rollout and training memory
typically coexist, so keep weights resident by default; flip back to
True if update_actor OOMs.
…nt engine

Graph capture with the verl default (FULL_AND_PIECEWISE, ~35 sizes)
fails with AclmdlRICaptureBegin 207005 (driver stream/queue resource
exhaustion) when the student engine colocates with the FSDP trainer.
Default to PIECEWISE-only with 4 capture sizes, override via
VLLM_COMPILATION_CONFIG. The JSON is quote-escaped so it survives as a
single hydra string override (raw JSON fails the override grammar).
…e OOM

Resident rollout weights (~27G) plus training state (~28G) leave no
room for the 8.5G backward allocation on a 61G card when gradient
checkpointing is off. Sleep level=1 is the correct colocate path;
document that resident weights require gradient checkpointing (or a
smaller rollout memory fraction) to fit.
mingliangfu and others added 11 commits July 22, 2026 21:43
STUDENT_WORLD_SIZE=14 + TEACHER_WORLD_SIZE=2 matches the 16-card
layout. TRAIN_PROMPT_BSZ defaults to 28 so the prompt batch stays
evenly divisible by the 14 agent loop workers (21 crashed the equal
chunk assertion with 14 workers).
…, kept rejects)

Rollout verify events show real rejections in OPD_PATCH_DEBUG while
training metrics report empty_reject_ratio=1.0, so the break is
downstream of the registry. Log per finished request: whether pop()
returned metadata, the raw anchor count and how many rejects survive
build_dflash_extra_fields.
The vLLMHttpServer logger (logging.getLogger(__file__)) never surfaces
through this actor's logging setup, so the collect-path traces would be
invisible like the earlier 'DFLASH OPD rollout' lines. Print to stderr
instead so ray captures and forwards them.
Rollout-side recording works (rejected_events non-empty) but training metrics
still show empty_reject_ratio=1.0 / loss=0. Root cause: the engine records OPD
reject events keyed by model_runner.input_batch.req_ids, which vLLM suffixes as
'<base>-<tag>' (e.g. uuid4().hex + '-b50b54a4'), while the frontend pops with
the base request id it passed to engine.generate(). The mismatch makes pop()
return None, so _collect_dflash_opd_fields falls back to empty_dflash_extra_fields
and every sample looks like an empty reject.

Make the registry lookup suffix-tolerant: match the base id exactly, else fall
back to a '<base>-' prefix match (the base id is unique per request/turn).

Adds CPU tests for the suffixed-id and exact-vs-suffix cases.

Co-authored-by: curnane-lab <curnane-lab@users.noreply.github.com>
The first real OPD micro-batch crashes in FlexAttention: transformers
validates the device and raises ValueError (CUDA/CPU/HPU only). Two
layered fixes:

- dflash_student: the runtime fallback around the flex draft forward
  caught only RuntimeError; catch ValueError too so an unsupported
  device retries the micro-batch with sdpa instead of crashing.
- launchers: default DFLASH_ATTENTION_IMPL=sdpa for the vllm NPU
  script (exported so the exec'd base script sees it), and make the
  base script's flex_attention default env-overridable.
…e_anchors

Uncapped rejection counts (~450 mean, >1k on 4096-token responses at the
current acceptance rate) make the trainer's OPD forward process
context + anchors*draft_block_size tokens, OOMing the 61G card in
update_actor. The cap evenly subsamples anchor/boundary pairs per
sample, always keeping the last pair so the tail segment reaches the
response end; dropped pairs just merge segments already capped at
draft_block_size-1. The vllm NPU launcher defaults the cap to 186
(DFLASH_MAX_RESPONSE_ANCHORS, 0 disables).
…ncher

Even with the 186-anchor cap, the trainer process holds ~59G during the
composed-student OPD forward (uncached 4609-token target activations,
draft segments, vocab-sized loss tensors) and OOMs the 61G card in
update_actor. The composed student cascades gradient_checkpointing_enable
to its target and draft submodules, cutting activation memory at a
~20-30% update_actor compute cost. GRADIENT_CHECKPOINTING=False opts out.
The dflash speculative method is upstreamed in both projects, so track
the official repos instead of the curnane-lab forks:
- vllm         -> vllm-project/vllm         @ v0.21.0   (ad7125a431)
- vllm-ascend  -> vllm-project/vllm-ascend  @ releases/v0.21.0rc (80610e44)

README updated to match.
Keep this PR scoped to the vLLM rollout path; the installer and
contributing-doc changes are dropped from the diff.
Keep the submodule clone step, the no-extras warning, the engine
auto-skip behavior and the NPU trainer entrypoint; drop the opt-in
flags, CANN/triton details and duplicated notes.
install_npu.sh is the NPU + vLLM installer (skips pre-installed engines,
no verl extras); install.sh stays the standard SGLang-path installer.
Keeps every file in the diff referenced and the text accurate.
@curnane-lab curnane-lab changed the title Add vllm support feat(rollout): add vLLM/vLLM-Ascend rollout path for DFlash OPD on Ascend NPU Jul 28, 2026
Rewrites the README diff to pure additions: the original install text is
untouched; the submodule clone hint and the NPU installer note become a
single appended paragraph.
@bingyang-lei

Copy link
Copy Markdown
Collaborator

it seems good, but can you give a wandb metric show that accept length is improved in training? just like this:
170a91be9a038a8a743e6e791121eccb

…length)

Mean tokens advanced per verify step (accepted prefix + bonus token),
matching sglang's spec_accept_length semantics: response_len /
num_verify_steps per request, aggregated as opd/rollout/accept_length/mean.
Rises as the draft distills toward the target, giving a first-class wandb
signal for acceptance improvement during OPD training.
@curnane-lab

curnane-lab commented Jul 29, 2026

Copy link
Copy Markdown
Author

it seems good, but can you give a wandb metric show that accept length is improved in training? just like this: 170a91be9a038a8a743e6e791121eccb

Thanks for the quick review!

Evidence from the current run (see the attached figure): mean accept length per verify step, derived from per-request OPD metadata sampling (accepted prefix + bonus token per verify step, K7). It rises steadily over the run, consistent with opd/rollout/reject_token_count/mean declining 466 → ~360 over the same period (fewer rejections = longer accepted prefixes).

Metric now first-class: op/rollout/accept_length/mean (per-request response_len / num_verify_steps, same semantics as sglang_spec_accept_length) has been added to the rollout metrics — see 0179b25. Future runs will report it to wand directly per step; a per-dataset breakdown (test_rollout/*/vllm_spec_accept_length) can also be added for parity with the SGLang panels.

Longer runs are coming: several more experiments will be launched in the next few days much longer horizons (5k–6k steps, similar to the setup in your example), including the acceptance-length curve above as a native wandb metric. Updated figures will be posted here as soon as those trainings complete.

… validation

The reviewer's reference panels (test_rollout/<dataset>/sglang_spec_accept_length)
aggregate sglang-specific output fields that the vLLM path never emits, so
the val loop produced no accept-length curve on the vLLM path. Export
dflash_num_verify_steps alongside dflash_accept_length and aggregate
sum(response tokens)/sum(verify steps) per benchmark as
test_rollout/<benchmark>/vllm_spec_accept_length (token-weighted, same
semantics as the sglang panel).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants