| 1 |
Logprob runtime/provenance |
Whether drift is arithmetic at all, or just two differently-configured processes |
✅ top-level logprob; recorded logprob.impl (mcore_unfused / mcore_native_fused / mcore_te_fused / slime_vocab_parallel / vllm_all_gather), logprob.tp_size, logprob.requested_backend, logprob.actual_backend, logprob.fallback_reason |
❌ no LogProbContract.provenance / LogProbBackendCapability |
IDENTICAL (V, TP size, model dtype) + SEMANTIC (transform chain, mask) + RECORDED (impl/version) |
⚠️ probe-only; nothing rejects a mismatch |
#241 PR1 contract + dispatch metadata, fail closed on missing vocab-shard bounds |
🙋 |
| 2 |
Vocab padding domain |
Whether dlogp is a systematic bias from padded rows in the denominator rather than rounding |
✅ logprob.vocab_padding_policy ∈ {include, mask}; recorded padded_vocab_size, org_vocab_size, num_pad_rows, pad_row_init |
❌ no LogProbVocabSpec (padded vs. real vocab, mask policy) |
SEMANTIC — the two sides must agree on which vocab entries are in the LSE |
❌ MCore has no mask; vLLM has no include-mode |
#241 PR1 make padded-vs-real vocab first-class; PR2 reference honors both modes; offline probe of lm_head[org_V:] norms |
🙋 |
| 3 |
Vocab shard ownership |
Whether partial-level disagreement is arithmetic or just different shard boundaries |
✅ logprob.vocab_shard_map; recorded per-rank (vocab_start, vocab_end) for both engines |
❌ no LogProbShardingSpec |
IDENTICAL (global vocab domain) + RECORDED (per-rank boundaries) |
❌ MCore VocabUtility and vLLM _get_indices disagree at TP≥2 for Qwen3 |
#241 PR1 shard map in metadata; PR4 cross-engine boundary reconciliation before partial comparison |
🙋 |
| 4 |
Reduction topology |
Whether drift comes from partial-LSE all-reduce vs. full-logit all-gather + single-pass softmax |
✅ logprob.reduce ∈ {allreduce_partials, allgather_partials_fixed_merge, allgather_logits}; recorded collective, engine |
❌ no LogProbReductionSpec |
SEMANTIC (order, acc_dtype) + RECORDED (collective, engine) |
❌ no fixed-order reference reduction exists |
#241 PR2 the deterministic vocab-parallel reference; PR4 run all three arms |
🙋 |
| 5 |
Merge order / NCCL as reducer |
Whether NCCL algorithm choice, not the math, moves the number |
✅ logprob.merge_order ∈ {nccl_algo, global_vocab_shard_index}; recorded NCCL_ALGO, NCCL_PROTO, world topology |
❌ no order field |
SEMANTIC (global_vocab_shard_index) + RECORDED (NCCL algo) |
⚠️ TE fused already pins order; MCore unfused/native do not |
#241 PR2 own the fixed-order merge; PR4 repeat-run bitwise assertion under varied NCCL_ALGO |
🙋 |
| 6 |
Precision and downcast boundary |
Whether drift comes from fp32 upcast position, head GEMM output dtype, or partial downcast |
✅ logprob.head_dtype, logprob.accum_dtype, logprob.downcast_at |
❌ no precision fields |
SEMANTIC (accum_dtype, downcast_at) + IDENTICAL (head GEMM output dtype) |
⚠️ selectable on both sides, not declarable |
#241 PR1 add precision fields; PR4 sweep head_dtype ∈ {bf16, fp32} against an fp32 oracle |
🙋 |
| 7 |
Logit transform chain and temperature |
Whether the two sides are computing the same distribution at all |
✅ logprob.transform_chain (ordered list); recorded temperature_applied, logprobs_mode, soft_cap, logit_scale |
❌ no LogProbTransformSpec |
SEMANTIC — an ordering mismatch here is a correctness bug, not drift |
⚠️ both sides configurable, neither declares |
#241 PR1 make the transform chain first-class and fail closed on mismatch |
🙋 |
| 8 |
Selected-logit gather and token alignment |
Whether drift comes from single-owner gather, ignore_index, OOV targets, or a shift-by-one |
✅ logprob.selected_gather ∈ {masked_sum_allreduce, local_gather}; recorded ignore_index, active_mask, shift_convention |
❌ no single-owner / mask fields |
SEMANTIC (single-owner invariant, pre-shifted input convention) |
⚠️ MCore masked-sum vs. vLLM local gather; no cross-check |
#241 PR2 masked single-owner gather + exact-tiling validation |
🙋 |
| 9 |
LM-head GEMM and materialization boundary |
Whether the number moved before any softmax ran — full-sequence training GEMM vs. logits_indices decode GEMM with CUDA-graph batch padding |
✅ logprob.head_gemm_kernel, logprob.materialization ∈ {full_prefill, chunked, last_token_only}; recorded padded batch size |
❌ no LogProbMaterializationSpec |
IDENTICAL (logical rows) + RECORDED (physical M, kernel, tile/split-K) |
❌ inherits #249 GEMM row 2; no shard reference at the head |
#239 PR2 head GEMM reference; #241 PR4 report head-GEMM drift separately from LSE drift |
🙋 |
| 10 |
Fusion tiling |
Whether TE's BLOCK_SIZE online-softmax tiling, native jit-fusion, or slime's sequence chunking changes the partial |
✅ logprob.fusion_impl ∈ {none, native, te, chunked_linear_ce}; recorded block_size, chunk_size |
❌ no fusion field |
RECORDED (measured, never forced equal) |
⚠️ all three exist upstream; none dispatchable through the catalog |
#241 PR4 run the fusion matrix against PR2's reference |
🙋 |
| 11 |
Training-only state (grad, entropy) |
Whether a forward-compatible logprob also produces a compatible dlogits and entropy |
✅ logprob.backward_impl, logprob.grad_downcast_at, logprob.entropy_reduce |
❌ no LogProbBackwardSpec |
SEMANTIC (dlogits, entropy reduction) + RECORDED (autograd path) |
❌ not implemented |
follow-up issue: vocab-parallel logprob backward + GRPO loss (blocked on #241) |
🙋 |
How to contribute
Below are areas that we have not yet created a dedicated knob to test whether the different implementation between training (megatron) and inference (vllm) caused the discrepancy in outputs.
If you want to claim for tasks in this PR, please comment below, as it is mainly reserved for internal collaborators.
You should base your PR on #230.
GEMM knobs
Collected from @frank-2077's study of how vLLM and Megatron implements GEMM kernel differently.
training.deterministic_mode,rollout.batch_invariant,gemm.compute_dtype,gemm.tf32,gemm.bf16_reduced_precision_reduction,training.sequence_parallel,training.tensor_parallel_sizeGemmDeterminismSpecdeterminism.pyto the GEMM-relevant flags and fail closed on the blocking onesdet_gemmgemm.kernelGemmContract.backend_idGemmBackendCapability+ thegemm.kernelknob; #239 PR2 the shard reference to compare againstdXall-reducedXsum, not the GEMM, moves the numbergemm.dgrad_reduceGemmReductionSpecorder,acc_dtype) + RECORDED (engine)dXcommunication operator; the contract fieldreduce_scatterand non-SPall_reduceare the same numbergemm.forward_reduceGemmReductionSpecorder,acc_dtype) + RECORDED (collective,engine)reduce_scatterarm has no operatordW = dY^T Xwith+= main_gradchanges the resultgemm.wgrad_accumulateGemmWgradSpecaccum_dtype) + RECORDED (fusion)det_gemm_db, with no accumulation wrapperdWoperator + explicit-accumulation reference; the contract fieldrollout.all_reduce_backendrollout_recorded_extraVllmProvenanceAdapter; #239 PR3 compare against the NCCL and reference armsAttention knobs
Collected from #235 Attention contract work and the Megatron/vLLM implementation study.
The user-facing knob should remain one top-level
attentionmismatch knob. The rows below are the dimensions thatattention=onmust record and isolate in the mismatch report; they are not necessarily separate user-facing knobs. When an optimized path is disabled for attribution, the fallback must be an RL-Kernel deterministic/custom reference path, not generic PyTorch SDPA.attention; recorded fieldsattention.requested_backend,attention.actual_backend,attention.mode,attention.fallback_reasonAttentionContract.provenance/AttentionBackendCapabilityattention; recorded fieldattention.materializationAttentionMaterializationSpecattention; recorded fieldsattention.rope_fusion_boundary,attention.q_rope_state,attention.k_cache_rope_stateAttentionContractposition_ids,cache_position, theta, rotary layout, tensor state) + RECORDED (fusion)attention; recorded fieldsattention.qkv_layout,attention.gqa_mapping,attention.tp_head_ownershipAttentionLayoutSpec/ TP head ownershipattention; recorded fieldsattention.paged_kv_policy,attention.cache_identityDecodeKVCacheMetadatain the main contract(out,lse)mismatch comes from missing LSE, natural-log vs. log2 LSE, wrong shape, or vocab-domain LSEattention; recorded fieldsattention.lse_export,attention.lse_domain,attention.lse_baseAttentionLseSpeclsedomain/base/shape/masked-row behavior)attention; recorded fieldsattention.merge_order,attention.cp_comm_type,attention.split_kv_policyAttentionReductionSpecglobal_block_index, acc dtype, merge order) + RECORDED (cp_comm_type, split policy, engine)attention; recorded fieldsattention.accum_dtype,attention.downcast_atAttentionReductionSpecaccum_dtype,downcast_at) + RECORDED (backend internal policy when observable)attention; recorded fieldsattention.backend_candidate,attention.backend_capabilitydq/dk/dvunder the training pathattention; recorded fieldattention.backward_backendAttentionBackwardSpec/ saved-state compatibility fielddq/dk/dv, saved forward state) + RECORDED (backend/autograd path)Important boundary: Megatron/TE and vLLM/FlashInfer may use different physical kernels, layouts, pages, chunks, and communication transports. They may not disagree on Attention semantics that affect the result: mask, scale, GQA mapping, RoPE position/state, cache identity, TP/CP ownership, LSE convention, split-K policy, reduction order, and final downcast boundary.
LogProb knobs
Collected from @KJLdefeated 's study of how vLLM and Megatron implements logp kernel differently.
logprob; recordedlogprob.impl(mcore_unfused/mcore_native_fused/mcore_te_fused/slime_vocab_parallel/vllm_all_gather),logprob.tp_size,logprob.requested_backend,logprob.actual_backend,logprob.fallback_reasonLogProbContract.provenance/LogProbBackendCapabilitydlogpis a systematic bias from padded rows in the denominator rather than roundinglogprob.vocab_padding_policy∈ {include,mask}; recordedpadded_vocab_size,org_vocab_size,num_pad_rows,pad_row_initLogProbVocabSpec(padded vs. real vocab, mask policy)lm_head[org_V:]normslogprob.vocab_shard_map; recorded per-rank(vocab_start, vocab_end)for both enginesLogProbShardingSpecVocabUtilityand vLLM_get_indicesdisagree at TP≥2 for Qwen3logprob.reduce∈ {allreduce_partials,allgather_partials_fixed_merge,allgather_logits}; recordedcollective,engineLogProbReductionSpecorder,acc_dtype) + RECORDED (collective,engine)logprob.merge_order∈ {nccl_algo,global_vocab_shard_index}; recordedNCCL_ALGO,NCCL_PROTO, world topologyorderfieldglobal_vocab_shard_index) + RECORDED (NCCL algo)NCCL_ALGOlogprob.head_dtype,logprob.accum_dtype,logprob.downcast_ataccum_dtype,downcast_at) + IDENTICAL (head GEMM output dtype)head_dtype∈ {bf16, fp32} against an fp32 oraclelogprob.transform_chain(ordered list); recordedtemperature_applied,logprobs_mode,soft_cap,logit_scaleLogProbTransformSpecignore_index, OOV targets, or a shift-by-onelogprob.selected_gather∈ {masked_sum_allreduce,local_gather}; recordedignore_index,active_mask,shift_conventionlogits_indicesdecode GEMM with CUDA-graph batch paddinglogprob.head_gemm_kernel,logprob.materialization∈ {full_prefill,chunked,last_token_only}; recorded padded batch sizeLogProbMaterializationSpecBLOCK_SIZEonline-softmax tiling, native jit-fusion, or slime's sequence chunking changes the partiallogprob.fusion_impl∈ {none,native,te,chunked_linear_ce}; recordedblock_size,chunk_sizedlogitsand entropylogprob.backward_impl,logprob.grad_downcast_at,logprob.entropy_reduceLogProbBackwardSpecdlogits, entropy reduction) + RECORDED (autograd path)