From f98c1ae35a06fcbe9950ed61971a2a8a10729340 Mon Sep 17 00:00:00 2001 From: inaniloquentee <3051000145@qq.com> Date: Thu, 18 Jun 2026 17:15:37 +0800 Subject: [PATCH 1/5] Add RL-Kernel integration flags Signed-off-by: inaniloquentee <3051000145@qq.com> --- tests/test_rl_kernel_args.py | 64 +++++++++++++++++++++++++++++ vime/utils/arguments.py | 22 ++++++++++ vime/utils/rl_kernel.py | 79 ++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 tests/test_rl_kernel_args.py create mode 100644 vime/utils/rl_kernel.py diff --git a/tests/test_rl_kernel_args.py b/tests/test_rl_kernel_args.py new file mode 100644 index 00000000..59e16546 --- /dev/null +++ b/tests/test_rl_kernel_args.py @@ -0,0 +1,64 @@ +from argparse import Namespace + +import pytest + +from vime.utils.rl_kernel import is_rl_kernel_op_enabled, normalize_rl_kernel_args, parse_rl_kernel_ops + +NUM_GPUS = 0 + + +@pytest.mark.unit +def test_parse_rl_kernel_ops_defaults_to_logp(): + assert parse_rl_kernel_ops(None) == ("logp",) + assert parse_rl_kernel_ops("") == ("logp",) + + +@pytest.mark.unit +def test_parse_rl_kernel_ops_deduplicates_comma_and_space_separated_values(): + assert parse_rl_kernel_ops("logp, ratio_kl logp") == ("logp", "ratio_kl") + + +@pytest.mark.unit +def test_parse_rl_kernel_ops_rejects_unknown_ops(): + with pytest.raises(ValueError, match="Unsupported RL-Kernel op"): + parse_rl_kernel_ops("logp,moe") + + +@pytest.mark.unit +def test_normalize_rl_kernel_args_keeps_default_disabled(): + args = Namespace(enable_rl_kernel=False, rl_kernel_ops="logp", rl_kernel_strict=False) + + normalize_rl_kernel_args(args) + + assert args.enable_rl_kernel is False + assert args.rl_kernel_ops == ("logp",) + assert is_rl_kernel_op_enabled(args, "logp") is False + + +@pytest.mark.unit +def test_normalize_rl_kernel_args_accepts_env_enable(monkeypatch): + monkeypatch.setenv("VIME_RL_KERNEL", "1") + args = Namespace(enable_rl_kernel=False, rl_kernel_ops="logp", rl_kernel_strict=False) + + normalize_rl_kernel_args(args) + + assert args.enable_rl_kernel is True + assert args.rl_kernel_ops == ("logp",) + assert is_rl_kernel_op_enabled(args, "logp") is True + + +@pytest.mark.unit +def test_normalize_rl_kernel_args_rejects_future_ops_for_current_integration(): + args = Namespace(enable_rl_kernel=True, rl_kernel_ops="logp,ratio_kl", rl_kernel_strict=False) + + with pytest.raises(ValueError, match="currently supports only logp"): + normalize_rl_kernel_args(args) + + +@pytest.mark.unit +def test_normalize_rl_kernel_args_rejects_bad_env_bool(monkeypatch): + monkeypatch.setenv("VIME_RL_KERNEL", "maybe") + args = Namespace(enable_rl_kernel=False, rl_kernel_ops="logp", rl_kernel_strict=False) + + with pytest.raises(ValueError, match="VIME_RL_KERNEL"): + normalize_rl_kernel_args(args) diff --git a/vime/utils/arguments.py b/vime/utils/arguments.py index 494d2c05..34ffa666 100644 --- a/vime/utils/arguments.py +++ b/vime/utils/arguments.py @@ -12,6 +12,7 @@ from vime.backends.vllm_utils.arguments import vllm_parse_args from vime.utils.eval_config import EvalDatasetConfig, build_eval_dataset_configs, ensure_dataset_list from vime.utils.logging_utils import configure_logger +from vime.utils.rl_kernel import normalize_rl_kernel_args logger = logging.getLogger(__name__) @@ -157,6 +158,24 @@ def add_train_arguments(parser): parser.add_argument( "--log-probs-chunk-size", type=int, default=-1, help="Chunk size to compute log probs to save memory" ) + parser.add_argument( + "--enable-rl-kernel", + action="store_true", + default=False, + help="Enable optional RL-Kernel acceleration for supported vime training/forward-only paths.", + ) + parser.add_argument( + "--rl-kernel-ops", + type=str, + default="logp", + help="Comma-separated RL-Kernel ops to enable. Current production integration supports: logp.", + ) + parser.add_argument( + "--rl-kernel-strict", + action="store_true", + default=False, + help="Raise instead of falling back when an enabled RL-Kernel op is unavailable or unsupported.", + ) parser.add_argument( "--only-train-params-name-list", type=str, @@ -1606,6 +1625,8 @@ def _resolve_eval_datasets(args) -> list[EvalDatasetConfig]: def vime_validate_args(args): + normalize_rl_kernel_args(args) + args.eval_datasets = _resolve_eval_datasets(args) if args.kl_coef != 0 or args.use_kl_loss: @@ -1859,6 +1880,7 @@ def vime_validate_args(args): if not hasattr(args, "_vllm_user_provided"): args._vllm_user_provided = set() args._vllm_user_provided.add(k) + normalize_rl_kernel_args(args) if args.eval_max_context_len is None: logger.info( diff --git a/vime/utils/rl_kernel.py b/vime/utils/rl_kernel.py new file mode 100644 index 00000000..c01e02db --- /dev/null +++ b/vime/utils/rl_kernel.py @@ -0,0 +1,79 @@ +import os +from argparse import Namespace +from collections.abc import Iterable + + +RL_KERNEL_SUPPORTED_OPS = ("logp", "ratio_kl", "grpo_loss") +RL_KERNEL_INTEGRATED_OPS = ("logp",) +_TRUE_VALUES = {"1", "true", "yes", "on"} +_FALSE_VALUES = {"0", "false", "no", "off"} + + +def parse_rl_kernel_ops(value: str | Iterable[str] | None) -> tuple[str, ...]: + """Parse a comma/space separated RL-Kernel op list.""" + if value is None: + return ("logp",) + + if isinstance(value, str): + raw_items = value.replace(",", " ").split() + else: + raw_items = [] + for item in value: + raw_items.extend(str(item).replace(",", " ").split()) + + ops: list[str] = [] + for item in raw_items: + op = item.strip().lower() + if not op: + continue + if op not in RL_KERNEL_SUPPORTED_OPS: + supported = ", ".join(RL_KERNEL_SUPPORTED_OPS) + raise ValueError(f"Unsupported RL-Kernel op '{op}'. Supported ops: {supported}.") + if op not in ops: + ops.append(op) + + return tuple(ops) if ops else ("logp",) + + +def _env_bool(name: str) -> bool | None: + value = os.getenv(name) + if value is None: + return None + normalized = value.strip().lower() + if normalized in _TRUE_VALUES: + return True + if normalized in _FALSE_VALUES: + return False + raise ValueError(f"{name} must be one of: 1/0, true/false, yes/no, on/off.") + + +def normalize_rl_kernel_args(args: Namespace) -> Namespace: + """Apply environment overrides and validate RL-Kernel integration args.""" + env_enabled = _env_bool("VIME_RL_KERNEL") + if env_enabled is not None: + args.enable_rl_kernel = env_enabled + + env_strict = _env_bool("VIME_RL_KERNEL_STRICT") + if env_strict is not None: + args.rl_kernel_strict = env_strict + + env_ops = os.getenv("VIME_RL_KERNEL_OPS") + if env_ops is not None: + args.rl_kernel_ops = env_ops + + args.rl_kernel_ops = parse_rl_kernel_ops(getattr(args, "rl_kernel_ops", None)) + + if getattr(args, "enable_rl_kernel", False): + unsupported = [op for op in args.rl_kernel_ops if op not in RL_KERNEL_INTEGRATED_OPS] + if unsupported: + integrated = ", ".join(RL_KERNEL_INTEGRATED_OPS) + raise ValueError( + "This vime RL-Kernel integration currently supports only " + f"{integrated}. Requested future ops: {', '.join(unsupported)}." + ) + + return args + + +def is_rl_kernel_op_enabled(args: Namespace, op: str) -> bool: + return getattr(args, "enable_rl_kernel", False) and op in getattr(args, "rl_kernel_ops", ()) From 743948d0835d4ba75946780954a75b12554aa4d4 Mon Sep 17 00:00:00 2001 From: inaniloquentee <3051000145@qq.com> Date: Thu, 18 Jun 2026 17:15:37 +0800 Subject: [PATCH 2/5] Integrate RL-Kernel logp for forward-only paths Signed-off-by: inaniloquentee <3051000145@qq.com> --- tests/_unit_stubs.py | 1 + tests/test_rl_kernel_logp_integration.py | 181 ++++++++++++++++++++++ vime/backends/megatron_utils/loss.py | 21 ++- vime/backends/megatron_utils/rl_kernel.py | 95 ++++++++++++ 4 files changed, 290 insertions(+), 8 deletions(-) create mode 100644 tests/test_rl_kernel_logp_integration.py create mode 100644 vime/backends/megatron_utils/rl_kernel.py diff --git a/tests/_unit_stubs.py b/tests/_unit_stubs.py index ed986355..7fe27a24 100644 --- a/tests/_unit_stubs.py +++ b/tests/_unit_stubs.py @@ -223,4 +223,5 @@ def install_triton_stub() -> None: def install_vime_distributed_utils_stub() -> None: vime_utils = types.ModuleType("vime.utils.distributed_utils") vime_utils.get_gloo_group = MagicMock(return_value="gloo") + vime_utils.distributed_masked_whiten = MagicMock(side_effect=lambda values, *args, **kwargs: values) sys.modules.setdefault("vime.utils.distributed_utils", vime_utils) diff --git a/tests/test_rl_kernel_logp_integration.py b/tests/test_rl_kernel_logp_integration.py new file mode 100644 index 00000000..4941e01c --- /dev/null +++ b/tests/test_rl_kernel_logp_integration.py @@ -0,0 +1,181 @@ +from __future__ import annotations + +import sys +import types +from argparse import Namespace +from pathlib import Path + +import pytest +import torch + +_tests_root = Path(__file__).resolve().parent +if str(_tests_root) not in sys.path: + sys.path.insert(0, str(_tests_root)) + +import _unit_stubs + +_unit_stubs.install_megatron_mpu_stub() +_unit_stubs.install_vime_distributed_utils_stub() + +from megatron.core import mpu # noqa: E402 + +from vime.backends.megatron_utils import loss as loss_mod # noqa: E402 +from vime.backends.megatron_utils import rl_kernel as rlk_mod # noqa: E402 + + +NUM_GPUS = 0 + + +class _FakeLogpOp: + calls = 0 + + def apply_fp32(self, logits: torch.Tensor, token_ids: torch.Tensor) -> torch.Tensor: + type(self).calls += 1 + return torch.gather(torch.log_softmax(logits.float(), dim=-1), -1, token_ids.long().unsqueeze(-1)).squeeze(-1) + + +def _reset_rl_kernel_state(): + rlk_mod._LOGP_OP = None + rlk_mod._LOGP_OP_LOAD_ERROR = None + rlk_mod._WARNED_FALLBACK_REASONS.clear() + _FakeLogpOp.calls = 0 + + +def _install_fake_rl_engine(monkeypatch): + rl_engine = types.ModuleType("rl_engine") + kernels = types.ModuleType("rl_engine.kernels") + registry = types.ModuleType("rl_engine.kernels.registry") + registry.kernel_registry = types.SimpleNamespace(get_op=lambda name: _FakeLogpOp()) + monkeypatch.setitem(sys.modules, "rl_engine", rl_engine) + monkeypatch.setitem(sys.modules, "rl_engine.kernels", kernels) + monkeypatch.setitem(sys.modules, "rl_engine.kernels.registry", registry) + + +def _make_args(**overrides) -> Namespace: + values = { + "enable_rl_kernel": True, + "rl_kernel_ops": ("logp",), + "rl_kernel_strict": False, + "allgather_cp": False, + "qkv_format": "thd", + "rollout_temperature": 1.0, + "log_probs_chunk_size": -1, + } + values.update(overrides) + return Namespace(**values) + + +@pytest.fixture(autouse=True) +def reset_parallelism(): + _reset_rl_kernel_state() + mpu.get_tensor_model_parallel_world_size.return_value = 1 + mpu.get_tensor_model_parallel_group.return_value = None + mpu.get_context_parallel_world_size.return_value = 1 + mpu.get_context_parallel_rank.return_value = 0 + yield + _reset_rl_kernel_state() + + +@pytest.mark.unit +def test_rl_kernel_logp_matches_vime_response_slicing(monkeypatch): + _install_fake_rl_engine(monkeypatch) + args = _make_args() + vocab_size = 257 + total_lengths = [9, 7, 11] + response_lengths = [4, 3, 6] + torch.manual_seed(17) + unconcat_tokens = [torch.randint(0, vocab_size, (length,), dtype=torch.long) for length in total_lengths] + logits = torch.randn(1, sum(total_lengths), vocab_size, dtype=torch.float32) + + with torch.no_grad(): + _, result = loss_mod.get_log_probs_and_entropy( + logits, + args=args, + unconcat_tokens=unconcat_tokens, + total_lengths=total_lengths, + response_lengths=response_lengths, + with_entropy=False, + ) + + expected = [] + offset = 0 + log_probs = torch.log_softmax(logits.squeeze(0), dim=-1) + for tokens, total_length, response_length in zip(unconcat_tokens, total_lengths, response_lengths, strict=False): + start = offset + total_length - response_length + end = offset + total_length + shifted_tokens = tokens[-response_length:] + expected.append(torch.gather(log_probs[start - 1 : end - 1], -1, shifted_tokens.unsqueeze(-1)).squeeze(-1)) + offset += total_length + + assert _FakeLogpOp.calls == 1 + assert len(result["log_probs"]) == len(expected) + for actual, expected_item in zip(result["log_probs"], expected, strict=True): + torch.testing.assert_close(actual, expected_item, rtol=1e-6, atol=1e-6) + + +@pytest.mark.unit +def test_rl_kernel_logp_falls_back_when_entropy_requested(monkeypatch): + _install_fake_rl_engine(monkeypatch) + args = _make_args() + logits = torch.randn(8, 16, dtype=torch.float32) + tokens = torch.randint(0, 16, (8,), dtype=torch.long) + + with torch.no_grad(): + actual = rlk_mod.maybe_compute_logp(logits, tokens, args=args, with_entropy=True) + + assert actual is None + assert _FakeLogpOp.calls == 0 + + +@pytest.mark.unit +def test_rl_kernel_logp_falls_back_for_tensor_parallel_vocab(monkeypatch): + _install_fake_rl_engine(monkeypatch) + mpu.get_tensor_model_parallel_world_size.return_value = 2 + args = _make_args() + logits = torch.randn(8, 16, dtype=torch.float32) + tokens = torch.randint(0, 16, (8,), dtype=torch.long) + + with torch.no_grad(): + actual = rlk_mod.maybe_compute_logp(logits, tokens, args=args, with_entropy=False) + + assert actual is None + assert _FakeLogpOp.calls == 0 + + +@pytest.mark.unit +def test_rl_kernel_logp_strict_mode_raises_on_unsupported_parallelism(monkeypatch): + _install_fake_rl_engine(monkeypatch) + mpu.get_tensor_model_parallel_world_size.return_value = 2 + args = _make_args(rl_kernel_strict=True) + logits = torch.randn(8, 16, dtype=torch.float32) + tokens = torch.randint(0, 16, (8,), dtype=torch.long) + + with pytest.raises(RuntimeError, match="tensor-parallel vocab shards"): + with torch.no_grad(): + rlk_mod.maybe_compute_logp(logits, tokens, args=args, with_entropy=False) + + +@pytest.mark.unit +def test_rl_kernel_logp_falls_back_when_optional_package_missing(monkeypatch): + import builtins + + for name in list(sys.modules): + if name == "rl_engine" or name.startswith("rl_engine."): + monkeypatch.delitem(sys.modules, name, raising=False) + + original_import = builtins.__import__ + + def fail_rl_engine_import(name, *args, **kwargs): + if name == "rl_engine" or name.startswith("rl_engine."): + raise ModuleNotFoundError("No module named 'rl_engine'") + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", fail_rl_engine_import) + args = _make_args() + logits = torch.randn(8, 16, dtype=torch.float32) + tokens = torch.randint(0, 16, (8,), dtype=torch.long) + + with torch.no_grad(): + actual = rlk_mod.maybe_compute_logp(logits, tokens, args=args, with_entropy=False) + + assert actual is None diff --git a/vime/backends/megatron_utils/loss.py b/vime/backends/megatron_utils/loss.py index 3f6ab29c..6a9cbf0d 100644 --- a/vime/backends/megatron_utils/loss.py +++ b/vime/backends/megatron_utils/loss.py @@ -29,6 +29,7 @@ get_sum_of_sample_mean, slice_log_prob_with_cp, ) +from .rl_kernel import maybe_compute_logp def get_responses( @@ -431,14 +432,18 @@ def get_log_probs_and_entropy( T, device, unconcat_tokens, total_lengths, response_lengths, qkv_format, max_seq_lens, args.allgather_cp ) - # --- compute on full [T,V] logits at once via calculate_log_probs_and_entropy --- - log_prob_full, entropy_full = calculate_log_probs_and_entropy( - logits, - full_tokens, - tp_group, - with_entropy=with_entropy, - chunk_size=chunk_size, - ) + # --- compute on full [T,V] logits at once --- + log_prob_full = maybe_compute_logp(logits, full_tokens, args=args, with_entropy=with_entropy) + if log_prob_full is None: + log_prob_full, entropy_full = calculate_log_probs_and_entropy( + logits, + full_tokens, + tp_group, + with_entropy=with_entropy, + chunk_size=chunk_size, + ) + else: + entropy_full = None log_prob_full = log_prob_full.squeeze(-1) # [T, 1] -> [T] # --- extract per-sample response portions --- diff --git a/vime/backends/megatron_utils/rl_kernel.py b/vime/backends/megatron_utils/rl_kernel.py new file mode 100644 index 00000000..7f712b32 --- /dev/null +++ b/vime/backends/megatron_utils/rl_kernel.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import logging +from argparse import Namespace + +import torch +from megatron.core import mpu + +from vime.utils.rl_kernel import is_rl_kernel_op_enabled + +logger = logging.getLogger(__name__) + +_LOGP_OP = None +_LOGP_OP_LOAD_ERROR: Exception | None = None +_WARNED_FALLBACK_REASONS: set[str] = set() + + +def _warn_fallback(args: Namespace, reason: str) -> None: + if getattr(args, "rl_kernel_strict", False): + raise RuntimeError(f"RL-Kernel logp is enabled but unavailable: {reason}") + if reason not in _WARNED_FALLBACK_REASONS: + logger.warning("Falling back to vime logprob path because RL-Kernel logp is unavailable: %s", reason) + _WARNED_FALLBACK_REASONS.add(reason) + + +def _get_logp_op(args: Namespace): + global _LOGP_OP, _LOGP_OP_LOAD_ERROR + if _LOGP_OP is not None: + return _LOGP_OP + if _LOGP_OP_LOAD_ERROR is not None: + _warn_fallback(args, str(_LOGP_OP_LOAD_ERROR)) + return None + + try: + from rl_engine.kernels.registry import kernel_registry + + _LOGP_OP = kernel_registry.get_op("logp") + logger.info("Using RL-Kernel logp op: %s", type(_LOGP_OP).__name__) + return _LOGP_OP + except Exception as exc: # pragma: no cover - exercised with missing optional package in integration envs + _LOGP_OP_LOAD_ERROR = exc + _warn_fallback(args, str(exc)) + return None + + +def maybe_compute_logp( + logits: torch.Tensor, + tokens: torch.Tensor, + *, + args: Namespace, + with_entropy: bool, +) -> torch.Tensor | None: + """Return selected log-probs from RL-Kernel when this runtime is safe. + + The first integration deliberately limits itself to forward-only logprob + precompute paths: no autograd, no vocab tensor parallelism, no CP + redistribution, and no entropy. Unsupported cases fall back to vime's + Megatron-aware implementation. + """ + if not is_rl_kernel_op_enabled(args, "logp"): + return None + + if with_entropy: + _warn_fallback(args, "entropy is requested") + return None + + if logits.requires_grad or torch.is_grad_enabled(): + _warn_fallback(args, "autograd is enabled") + return None + + if mpu.get_tensor_model_parallel_world_size() != 1: + _warn_fallback(args, "tensor-parallel vocab shards are not supported by RL-Kernel logp") + return None + + if mpu.get_context_parallel_world_size() != 1 or getattr(args, "allgather_cp", False): + _warn_fallback(args, "context parallel logprob redistribution is not supported by RL-Kernel logp") + return None + + if logits.size(0) == 0: + return logits.new_zeros((0,), dtype=torch.float32) + + op = _get_logp_op(args) + if op is None: + return None + + try: + if hasattr(op, "apply_fp32"): + log_prob = op.apply_fp32(logits, tokens) + else: + log_prob = op(logits, tokens).float() + except Exception as exc: + _warn_fallback(args, str(exc)) + return None + + return log_prob.reshape(-1) From 72bd5201e55da44c3c91e3e85921e3c3a9090d70 Mon Sep 17 00:00:00 2001 From: inaniloquentee <3051000145@qq.com> Date: Sat, 27 Jun 2026 16:18:42 +0800 Subject: [PATCH 3/5] Add RL-Kernel linear_logp benchmark integration Signed-off-by: inaniloquentee <3051000145@qq.com> --- scripts/run-qwen3-30B-A3B.sh | 71 +++- tests/test_rl_kernel_args.py | 49 ++- .../test_rl_kernel_linear_logp_integration.py | 378 ++++++++++++++++++ tests/test_rl_kernel_logp_integration.py | 4 + vime/backends/megatron_utils/loss.py | 142 ++++++- vime/backends/megatron_utils/model.py | 83 +++- vime/backends/megatron_utils/rl_kernel.py | 245 +++++++++++- vime/utils/arguments.py | 4 +- vime/utils/rl_kernel.py | 8 +- 9 files changed, 904 insertions(+), 80 deletions(-) create mode 100644 tests/test_rl_kernel_linear_logp_integration.py diff --git a/scripts/run-qwen3-30B-A3B.sh b/scripts/run-qwen3-30B-A3B.sh index 3597c115..726a929b 100644 --- a/scripts/run-qwen3-30B-A3B.sh +++ b/scripts/run-qwen3-30B-A3B.sh @@ -24,9 +24,40 @@ else fi echo "HAS_NVLINK: $HAS_NVLINK (detected $NVLINK_COUNT NVLink references)" +if command -v nvidia-smi >/dev/null 2>&1; then + DETECTED_GPUS=$(nvidia-smi -L 2>/dev/null | wc -l | tr -d ' ') + DETECTED_GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -n 1) +else + DETECTED_GPUS=0 + DETECTED_GPU_NAME="unknown" +fi +NUM_GPUS=${NUM_GPUS:-8} +if [ -z "$NUM_GPUS" ] || [ "$NUM_GPUS" -le 0 ]; then + NUM_GPUS=8 +fi +if [ "$DETECTED_GPUS" -gt 0 ] && [ "$NUM_GPUS" -gt "$DETECTED_GPUS" ]; then + echo "Requested NUM_GPUS=$NUM_GPUS but only detected $DETECTED_GPUS GPUs" >&2 + exit 1 +fi +echo "BENCHMARK_GPU: ${DETECTED_GPU_NAME}" +echo "NUM_GPUS: $NUM_GPUS" + SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +VIME_ROOT="$(cd -- "${SCRIPT_DIR}/.." &>/dev/null && pwd)" source "${SCRIPT_DIR}/models/qwen3-30B-A3B.sh" +MEGATRON_TP=${MEGATRON_TP:-4} +MEGATRON_EP=${MEGATRON_EP:-${NUM_GPUS}} +MEGATRON_CP=${MEGATRON_CP:-1} +MAX_TOKENS_PER_GPU=${MAX_TOKENS_PER_GPU:-20480} +NUM_ROLLOUT=${NUM_ROLLOUT:-3000} +ROLLOUT_BATCH_SIZE=${ROLLOUT_BATCH_SIZE:-32} +N_SAMPLES_PER_PROMPT=${N_SAMPLES_PER_PROMPT:-8} +ROLLOUT_MAX_RESPONSE_LEN=${ROLLOUT_MAX_RESPONSE_LEN:-8192} +GLOBAL_BATCH_SIZE=${GLOBAL_BATCH_SIZE:-$((ROLLOUT_BATCH_SIZE * N_SAMPLES_PER_PROMPT))} +ROLLOUT_NUM_GPUS_PER_ENGINE=${ROLLOUT_NUM_GPUS_PER_ENGINE:-${NUM_GPUS}} +VLLM_GPU_MEMORY_UTILIZATION=${VLLM_GPU_MEMORY_UTILIZATION:-0.7} + CKPT_ARGS=( --hf-checkpoint /root/Qwen3-30B-A3B #--hf-checkpoint /root/Qwen3-30B-A3B-FP8 @@ -43,13 +74,13 @@ ROLLOUT_ARGS=( --apply-chat-template --rollout-shuffle --rm-type deepscaler - --num-rollout 3000 - --rollout-batch-size 32 - --n-samples-per-prompt 8 - --rollout-max-response-len 8192 + --num-rollout "${NUM_ROLLOUT}" + --rollout-batch-size "${ROLLOUT_BATCH_SIZE}" + --n-samples-per-prompt "${N_SAMPLES_PER_PROMPT}" + --rollout-max-response-len "${ROLLOUT_MAX_RESPONSE_LEN}" --rollout-temperature 1 - --global-batch-size 256 + --global-batch-size "${GLOBAL_BATCH_SIZE}" --balance-data ) @@ -62,11 +93,11 @@ EVAL_ARGS=( ) PERF_ARGS=( - --tensor-model-parallel-size 4 + --tensor-model-parallel-size "${MEGATRON_TP}" --sequence-parallel --pipeline-model-parallel-size 1 - --context-parallel-size 1 - --expert-model-parallel-size 8 + --context-parallel-size "${MEGATRON_CP}" + --expert-model-parallel-size "${MEGATRON_EP}" --expert-tensor-parallel-size 1 --recompute-granularity full @@ -75,7 +106,7 @@ PERF_ARGS=( # --micro-batch-size 1 --use-dynamic-batch-size - --max-tokens-per-gpu 20480 + --max-tokens-per-gpu "${MAX_TOKENS_PER_GPU}" ) GRPO_ARGS=( @@ -109,8 +140,9 @@ WANDB_ARGS=( ) VLLM_ARGS=( - --rollout-num-gpus-per-engine 8 - --vllm-gpu-memory-utilization 0.7 + --rollout-num-gpus-per-engine "${ROLLOUT_NUM_GPUS_PER_ENGINE}" + --vllm-gpu-memory-utilization "${VLLM_GPU_MEMORY_UTILIZATION}" + --vllm-enable-expert-parallel --vllm-cudagraph-capture-sizes 1 2 4 8 $(seq 16 8 256) ) @@ -125,14 +157,22 @@ MISC_ARGS=( --attention-backend flash ) +RLK_ARGS=() +if [[ "${VIME_RL_KERNEL:-0}" == "1" ]]; then + RLK_ARGS+=(--enable-rl-kernel --rl-kernel-ops "${VIME_RL_KERNEL_OPS:-linear_logp}") + if [[ "${VIME_RL_KERNEL_STRICT:-0}" == "1" ]]; then + RLK_ARGS+=(--rl-kernel-strict) + fi +fi + # launch the master node of ray in container export MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"} -ray start --head --node-ip-address ${MASTER_ADDR} --num-gpus 8 --disable-usage-stats --dashboard-host=0.0.0.0 --dashboard-port=8265 +ray start --head --node-ip-address ${MASTER_ADDR} --num-gpus ${NUM_GPUS} --disable-usage-stats --dashboard-host=0.0.0.0 --dashboard-port=8265 # Build the runtime environment JSON with proper variable substitution RUNTIME_ENV_JSON="{ \"env_vars\": { - \"PYTHONPATH\": \"/root/Megatron-LM/\", + \"PYTHONPATH\": \"${VIME_ROOT}:/root/Megatron-LM/\", \"CUDA_DEVICE_MAX_CONNECTIONS\": \"1\", \"NCCL_NVLS_ENABLE\": \"${HAS_NVLINK}\" } @@ -142,7 +182,7 @@ ray job submit --address="http://127.0.0.1:8265" \ --runtime-env-json="${RUNTIME_ENV_JSON}" \ -- python3 train.py \ --actor-num-nodes 1 \ - --actor-num-gpus-per-node 8 \ + --actor-num-gpus-per-node ${NUM_GPUS} \ --colocate \ ${MODEL_ARGS[@]} \ ${CKPT_ARGS[@]} \ @@ -153,4 +193,5 @@ ray job submit --address="http://127.0.0.1:8265" \ ${PERF_ARGS[@]} \ ${EVAL_ARGS[@]} \ ${VLLM_ARGS[@]} \ - ${MISC_ARGS[@]} + ${MISC_ARGS[@]} \ + ${RLK_ARGS[@]} diff --git a/tests/test_rl_kernel_args.py b/tests/test_rl_kernel_args.py index 59e16546..cd3916c4 100644 --- a/tests/test_rl_kernel_args.py +++ b/tests/test_rl_kernel_args.py @@ -8,57 +8,76 @@ @pytest.mark.unit -def test_parse_rl_kernel_ops_defaults_to_logp(): - assert parse_rl_kernel_ops(None) == ("logp",) - assert parse_rl_kernel_ops("") == ("logp",) +def test_parse_rl_kernel_ops_defaults_to_linear_logp(): + assert parse_rl_kernel_ops(None) == ("linear_logp",) + assert parse_rl_kernel_ops("") == ("linear_logp",) @pytest.mark.unit def test_parse_rl_kernel_ops_deduplicates_comma_and_space_separated_values(): - assert parse_rl_kernel_ops("logp, ratio_kl logp") == ("logp", "ratio_kl") + assert parse_rl_kernel_ops("linear_logp, linear_logp") == ("linear_logp",) @pytest.mark.unit def test_parse_rl_kernel_ops_rejects_unknown_ops(): with pytest.raises(ValueError, match="Unsupported RL-Kernel op"): - parse_rl_kernel_ops("logp,moe") + parse_rl_kernel_ops("linear_logp,moe") @pytest.mark.unit def test_normalize_rl_kernel_args_keeps_default_disabled(): - args = Namespace(enable_rl_kernel=False, rl_kernel_ops="logp", rl_kernel_strict=False) + args = Namespace(enable_rl_kernel=False, rl_kernel_ops="linear_logp", rl_kernel_strict=False) normalize_rl_kernel_args(args) assert args.enable_rl_kernel is False - assert args.rl_kernel_ops == ("logp",) - assert is_rl_kernel_op_enabled(args, "logp") is False + assert args.rl_kernel_ops == ("linear_logp",) + assert is_rl_kernel_op_enabled(args, "linear_logp") is False @pytest.mark.unit def test_normalize_rl_kernel_args_accepts_env_enable(monkeypatch): monkeypatch.setenv("VIME_RL_KERNEL", "1") - args = Namespace(enable_rl_kernel=False, rl_kernel_ops="logp", rl_kernel_strict=False) + args = Namespace(enable_rl_kernel=False, rl_kernel_ops="linear_logp", rl_kernel_strict=False) normalize_rl_kernel_args(args) assert args.enable_rl_kernel is True - assert args.rl_kernel_ops == ("logp",) - assert is_rl_kernel_op_enabled(args, "logp") is True + assert args.rl_kernel_ops == ("linear_logp",) + assert is_rl_kernel_op_enabled(args, "linear_logp") is True @pytest.mark.unit -def test_normalize_rl_kernel_args_rejects_future_ops_for_current_integration(): - args = Namespace(enable_rl_kernel=True, rl_kernel_ops="logp,ratio_kl", rl_kernel_strict=False) +def test_normalize_rl_kernel_args_rejects_non_linear_logp_ops(): + args = Namespace(enable_rl_kernel=True, rl_kernel_ops="logp", rl_kernel_strict=False) - with pytest.raises(ValueError, match="currently supports only logp"): + with pytest.raises(ValueError, match="Unsupported RL-Kernel op"): + normalize_rl_kernel_args(args) + + +@pytest.mark.unit +@pytest.mark.parametrize("op", ["ratio_kl", "grpo_loss", "sampling"]) +def test_normalize_rl_kernel_args_rejects_out_of_scope_ops(op): + args = Namespace(enable_rl_kernel=True, rl_kernel_ops=op, rl_kernel_strict=False) + + with pytest.raises(ValueError, match="Unsupported RL-Kernel op"): normalize_rl_kernel_args(args) +@pytest.mark.unit +def test_normalize_rl_kernel_args_accepts_linear_logp(): + args = Namespace(enable_rl_kernel=True, rl_kernel_ops="linear_logp", rl_kernel_strict=False) + + normalize_rl_kernel_args(args) + + assert args.rl_kernel_ops == ("linear_logp",) + assert is_rl_kernel_op_enabled(args, "linear_logp") is True + + @pytest.mark.unit def test_normalize_rl_kernel_args_rejects_bad_env_bool(monkeypatch): monkeypatch.setenv("VIME_RL_KERNEL", "maybe") - args = Namespace(enable_rl_kernel=False, rl_kernel_ops="logp", rl_kernel_strict=False) + args = Namespace(enable_rl_kernel=False, rl_kernel_ops="linear_logp", rl_kernel_strict=False) with pytest.raises(ValueError, match="VIME_RL_KERNEL"): normalize_rl_kernel_args(args) diff --git a/tests/test_rl_kernel_linear_logp_integration.py b/tests/test_rl_kernel_linear_logp_integration.py new file mode 100644 index 00000000..4c0d1448 --- /dev/null +++ b/tests/test_rl_kernel_linear_logp_integration.py @@ -0,0 +1,378 @@ +from __future__ import annotations + +import builtins +import sys +import types +from argparse import Namespace +from pathlib import Path + +import pytest +import torch +import torch.nn.functional as F + +_tests_root = Path(__file__).resolve().parent +if str(_tests_root) not in sys.path: + sys.path.insert(0, str(_tests_root)) + +import _unit_stubs + +_unit_stubs.install_megatron_mpu_stub() +_unit_stubs.install_vime_distributed_utils_stub() + +from megatron.core import mpu # noqa: E402 + +from vime.backends.megatron_utils import loss as loss_mod # noqa: E402 +from vime.backends.megatron_utils import rl_kernel as rlk_mod # noqa: E402 + +NUM_GPUS = 0 + + +class _FakeLinearLogpOp: + calls: list[dict] = [] + + def __call__( + self, + hidden: torch.Tensor, + weight: torch.Tensor, + target_ids: torch.Tensor, + bias: torch.Tensor | None = None, + **kwargs, + ) -> torch.Tensor: + type(self).calls.append( + { + "hidden_shape": tuple(hidden.shape), + "weight_shape": tuple(weight.shape), + "target_shape": tuple(target_ids.shape), + "bias": bias is not None, + "kwargs": kwargs, + } + ) + logits = F.linear(hidden.float(), weight.float(), None if bias is None else bias.float()) + return torch.gather(torch.log_softmax(logits, dim=-1), -1, target_ids.long().unsqueeze(-1)).squeeze(-1) + + +class _FakeLegacyLinearLogpOp: + def __call__( + self, + hidden: torch.Tensor, + weight: torch.Tensor, + target_ids: torch.Tensor, + bias: torch.Tensor | None = None, + ) -> torch.Tensor: + logits = F.linear(hidden.float(), weight.float(), None if bias is None else bias.float()) + return torch.gather(torch.log_softmax(logits, dim=-1), -1, target_ids.long().unsqueeze(-1)).squeeze(-1) + + +def _reset_rl_kernel_state(): + rlk_mod._LOGP_OP = None + rlk_mod._LOGP_OP_LOAD_ERROR = None + rlk_mod._LINEAR_LOGP_OP = None + rlk_mod._LINEAR_LOGP_OP_LOAD_ERROR = None + rlk_mod._WARNED_FALLBACK_REASONS.clear() + rlk_mod._FALLBACK_COUNTS.clear() + rlk_mod._FALLBACK_COUNTS.update({"logp": 0, "linear_logp": 0}) + _FakeLinearLogpOp.calls.clear() + + +def _install_fake_rl_engine(monkeypatch): + rl_engine = types.ModuleType("rl_engine") + kernels = types.ModuleType("rl_engine.kernels") + registry = types.ModuleType("rl_engine.kernels.registry") + registry.kernel_registry = types.SimpleNamespace(get_op=lambda name: _FakeLinearLogpOp()) + monkeypatch.setitem(sys.modules, "rl_engine", rl_engine) + monkeypatch.setitem(sys.modules, "rl_engine.kernels", kernels) + monkeypatch.setitem(sys.modules, "rl_engine.kernels.registry", registry) + + +def _install_fake_legacy_rl_engine(monkeypatch): + rl_engine = types.ModuleType("rl_engine") + kernels = types.ModuleType("rl_engine.kernels") + registry = types.ModuleType("rl_engine.kernels.registry") + registry.kernel_registry = types.SimpleNamespace(get_op=lambda name: _FakeLegacyLinearLogpOp()) + monkeypatch.setitem(sys.modules, "rl_engine", rl_engine) + monkeypatch.setitem(sys.modules, "rl_engine.kernels", kernels) + monkeypatch.setitem(sys.modules, "rl_engine.kernels.registry", registry) + + +def _make_args(**overrides) -> Namespace: + values = { + "enable_rl_kernel": True, + "rl_kernel_ops": ("linear_logp",), + "rl_kernel_strict": False, + "allgather_cp": False, + "qkv_format": "thd", + "rollout_temperature": 1.0, + "log_probs_chunk_size": -1, + "entropy_coef": 0.0, + "sequence_parallel": False, + "padded_vocab_size": None, + "vocab_size": None, + } + values.update(overrides) + return Namespace(**values) + + +@pytest.fixture(autouse=True) +def reset_parallelism(): + _reset_rl_kernel_state() + mpu.get_tensor_model_parallel_world_size.return_value = 1 + mpu.get_tensor_model_parallel_rank.return_value = 0 + mpu.get_tensor_model_parallel_group.return_value = None + mpu.get_context_parallel_world_size.return_value = 1 + mpu.get_context_parallel_rank.return_value = 0 + mpu.get_virtual_pipeline_model_parallel_world_size.return_value = None + mpu.is_pipeline_last_stage.return_value = True + yield + _reset_rl_kernel_state() + + +def _reference_logp(hidden: torch.Tensor, weight: torch.Tensor, target: torch.Tensor, bias: torch.Tensor | None): + logits = F.linear(hidden.float(), weight.float(), None if bias is None else bias.float()) + return torch.gather(torch.log_softmax(logits, dim=-1), -1, target.long().unsqueeze(-1)).squeeze(-1) + + +def _cpu_calculate_log_probs_and_entropy( + logits: torch.Tensor, + tokens: torch.Tensor, + tp_group, + *, + with_entropy: bool, + chunk_size: int, +): + del tp_group, chunk_size + log_probs = torch.log_softmax(logits.float(), dim=-1) + selected = torch.gather(log_probs, -1, tokens.long().unsqueeze(-1)).squeeze(-1) + entropy = None + if with_entropy: + probs = torch.softmax(logits.float(), dim=-1) + entropy = -(probs * log_probs).sum(dim=-1) + return selected, entropy + + +@pytest.mark.unit +def test_maybe_compute_linear_logp_passes_tensor_parallel_metadata(monkeypatch): + _install_fake_rl_engine(monkeypatch) + args = _make_args() + torch.manual_seed(1) + hidden = torch.randn(6, 5) + weight = torch.randn(8, 5) + bias = torch.randn(8) + target = torch.randint(0, 8, (6,)) + context = rlk_mod.LinearLogpContext( + lm_head_weight=weight, + bias=bias, + tp_group="tp", + vocab_start_index=16, + global_vocab_size=32, + ) + + actual = rlk_mod.maybe_compute_linear_logp(hidden, target, context=context, args=args, with_entropy=False) + + torch.testing.assert_close(actual, _reference_logp(hidden, weight, target, bias)) + assert _FakeLinearLogpOp.calls == [ + { + "hidden_shape": (6, 5), + "weight_shape": (8, 5), + "target_shape": (6,), + "bias": True, + "kwargs": { + "tp_group": "tp", + "vocab_start_index": 16, + "global_vocab_size": 32, + }, + } + ] + + +@pytest.mark.unit +def test_linear_logp_matches_vime_response_slicing_from_hidden_states(monkeypatch): + _install_fake_rl_engine(monkeypatch) + args = _make_args() + vocab_size = 23 + hidden_size = 7 + total_lengths = [5, 4, 6] + response_lengths = [2, 3, 4] + torch.manual_seed(2) + unconcat_tokens = [torch.randint(0, vocab_size, (length,), dtype=torch.long) for length in total_lengths] + hidden = torch.randn(sum(total_lengths), 1, hidden_size) + weight = torch.randn(vocab_size, hidden_size) + bias = torch.randn(vocab_size) + context = rlk_mod.LinearLogpContext(lm_head_weight=weight, bias=bias, tp_group=None) + + _, result = loss_mod.get_log_probs_and_entropy( + hidden, + args=args, + unconcat_tokens=unconcat_tokens, + total_lengths=total_lengths, + response_lengths=response_lengths, + with_entropy=False, + rl_kernel_linear_logp_context=context, + ) + + full_tokens = torch.zeros(sum(total_lengths), dtype=torch.long) + offset = 0 + for tokens, total_length in zip(unconcat_tokens, total_lengths, strict=False): + full_tokens[offset : offset + total_length - 1] = tokens[1:total_length] + offset += total_length + full_logp = _reference_logp(hidden.squeeze(1), weight, full_tokens, bias) + + expected = [] + offset = 0 + for total_length, response_length in zip(total_lengths, response_lengths, strict=False): + end = offset + total_length + start = end - response_length + expected.append(full_logp[start - 1 : end - 1]) + offset += total_length + + assert len(_FakeLinearLogpOp.calls) == 1 + for actual, expected_item in zip(result["log_probs"], expected, strict=True): + torch.testing.assert_close(actual, expected_item, rtol=1e-6, atol=1e-6) + + +@pytest.mark.unit +def test_linear_logp_materializes_logits_fallback_when_optional_package_missing(monkeypatch): + monkeypatch.setattr(loss_mod, "calculate_log_probs_and_entropy", _cpu_calculate_log_probs_and_entropy) + for name in list(sys.modules): + if name == "rl_engine" or name.startswith("rl_engine."): + monkeypatch.delitem(sys.modules, name, raising=False) + + original_import = builtins.__import__ + + def fail_rl_engine_import(name, *args, **kwargs): + if name == "rl_engine" or name.startswith("rl_engine."): + raise ModuleNotFoundError("No module named 'rl_engine'") + return original_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", fail_rl_engine_import) + args = _make_args() + vocab_size = 19 + hidden_size = 5 + total_lengths = [4, 5] + response_lengths = [2, 3] + torch.manual_seed(3) + unconcat_tokens = [torch.randint(0, vocab_size, (length,), dtype=torch.long) for length in total_lengths] + hidden = torch.randn(1, sum(total_lengths), hidden_size) + weight = torch.randn(vocab_size, hidden_size) + bias = torch.randn(vocab_size) + context = rlk_mod.LinearLogpContext(lm_head_weight=weight, bias=bias, tp_group=None) + + _, result = loss_mod.get_log_probs_and_entropy( + hidden, + args=args, + unconcat_tokens=unconcat_tokens, + total_lengths=total_lengths, + response_lengths=response_lengths, + with_entropy=False, + rl_kernel_linear_logp_context=context, + ) + + logits = F.linear(hidden.squeeze(0).float(), weight.float(), bias.float()) + expected = [] + offset = 0 + for tokens, total_length, response_length in zip(unconcat_tokens, total_lengths, response_lengths, strict=False): + end = offset + total_length + start = end - response_length + target = tokens[-response_length:] + expected.append( + torch.gather(torch.log_softmax(logits[start - 1 : end - 1], dim=-1), -1, target.unsqueeze(-1)).squeeze(-1) + ) + offset += total_length + + assert rlk_mod.get_rl_kernel_fallback_count("linear_logp") == 1 + for actual, expected_item in zip(result["log_probs"], expected, strict=True): + torch.testing.assert_close(actual, expected_item, rtol=1e-6, atol=1e-6) + + +@pytest.mark.unit +def test_linear_logp_falls_back_when_op_lacks_tp_interface(monkeypatch): + _install_fake_legacy_rl_engine(monkeypatch) + args = _make_args() + hidden = torch.randn(3, 4) + weight = torch.randn(6, 4) + target = torch.randint(0, 6, (3,)) + context = rlk_mod.LinearLogpContext( + lm_head_weight=weight, + bias=None, + tp_group="tp_group", + vocab_start_index=6, + global_vocab_size=12, + ) + + actual = rlk_mod.maybe_compute_linear_logp(hidden, target, context=context, args=args, with_entropy=False) + + assert actual is None + assert rlk_mod.get_rl_kernel_fallback_count("linear_logp") == 1 + + +@pytest.mark.unit +def test_linear_logp_context_from_model_uses_tp_vocab_offsets(): + mpu.get_tensor_model_parallel_world_size.return_value = 4 + mpu.get_tensor_model_parallel_rank.return_value = 2 + mpu.get_tensor_model_parallel_group.return_value = "tp_group" + + output_layer = types.SimpleNamespace( + weight=torch.empty(8, 4), + bias=None, + sequence_parallel=True, + ) + model = types.SimpleNamespace(output_layer=output_layer, post_process=True) + args = _make_args(padded_vocab_size=32, sequence_parallel=False) + + context = rlk_mod.get_linear_logp_context_from_model(args, model) + + assert context is not None + assert context.lm_head_weight is output_layer.weight + assert context.tp_group == "tp_group" + assert context.vocab_start_index == 16 + assert context.global_vocab_size == 32 + assert context.sequence_parallel is True + + +@pytest.mark.unit +def test_linear_logp_context_uses_covered_padded_vocab_when_padded_vocab_size_missing(): + mpu.get_tensor_model_parallel_world_size.return_value = 4 + mpu.get_tensor_model_parallel_rank.return_value = 1 + mpu.get_tensor_model_parallel_group.return_value = "tp_group" + + output_layer = types.SimpleNamespace(weight=torch.empty(8, 4), bias=None) + model = types.SimpleNamespace(output_layer=output_layer, post_process=True) + args = _make_args(padded_vocab_size=None, vocab_size=30) + + context = rlk_mod.get_linear_logp_context_from_model(args, model) + + assert context is not None + assert context.vocab_start_index == 8 + assert context.global_vocab_size == 32 + + +@pytest.mark.unit +def test_return_hidden_states_for_linear_logp_restores_post_process_flag(): + args = _make_args() + model = types.SimpleNamespace(post_process=True) + context = rlk_mod.LinearLogpContext( + lm_head_weight=torch.empty(4, 3), + bias=None, + tp_group=None, + ) + + with rlk_mod.return_hidden_states_for_linear_logp(args, model, context) as enabled: + assert enabled is True + assert model.post_process is False + + assert model.post_process is True + + +@pytest.mark.unit +def test_policy_loss_only_skips_entropy_when_linear_logp_context_is_active(): + args = _make_args(enable_rl_kernel=True, rl_kernel_ops=("linear_logp",), entropy_coef=0.0) + context = rlk_mod.LinearLogpContext( + lm_head_weight=torch.empty(4, 3), + bias=None, + tp_group=None, + ) + + assert loss_mod._policy_loss_needs_entropy(args, None) is True + assert loss_mod._policy_loss_needs_entropy(args, context) is False + + args.entropy_coef = 0.01 + assert loss_mod._policy_loss_needs_entropy(args, context) is True diff --git a/tests/test_rl_kernel_logp_integration.py b/tests/test_rl_kernel_logp_integration.py index 4941e01c..5d444768 100644 --- a/tests/test_rl_kernel_logp_integration.py +++ b/tests/test_rl_kernel_logp_integration.py @@ -37,7 +37,11 @@ def apply_fp32(self, logits: torch.Tensor, token_ids: torch.Tensor) -> torch.Ten def _reset_rl_kernel_state(): rlk_mod._LOGP_OP = None rlk_mod._LOGP_OP_LOAD_ERROR = None + rlk_mod._LINEAR_LOGP_OP = None + rlk_mod._LINEAR_LOGP_OP_LOAD_ERROR = None rlk_mod._WARNED_FALLBACK_REASONS.clear() + rlk_mod._FALLBACK_COUNTS.clear() + rlk_mod._FALLBACK_COUNTS.update({"logp": 0, "linear_logp": 0}) _FakeLogpOp.calls = 0 diff --git a/vime/backends/megatron_utils/loss.py b/vime/backends/megatron_utils/loss.py index 6a9cbf0d..e205a45f 100644 --- a/vime/backends/megatron_utils/loss.py +++ b/vime/backends/megatron_utils/loss.py @@ -29,7 +29,7 @@ get_sum_of_sample_mean, slice_log_prob_with_cp, ) -from .rl_kernel import maybe_compute_logp +from .rl_kernel import LinearLogpContext, get_rl_kernel_fallback_count, maybe_compute_linear_logp, maybe_compute_logp def get_responses( @@ -384,6 +384,65 @@ def _extract_per_sample( return log_probs_list, entropy_list +def _gather_sequence_parallel_hidden_if_needed( + hidden_states: torch.Tensor, + context: LinearLogpContext | None, +) -> torch.Tensor: + if context is None or not context.sequence_parallel: + return hidden_states + + from megatron.core import tensor_parallel + + return tensor_parallel.gather_from_sequence_parallel_region(hidden_states, tensor_parallel_output_grad=False) + + +def _flatten_logprob_model_output( + output_tensor: torch.Tensor, + *, + qkv_format: str, + max_seq_lens: list[int] | None, + linear_logp_context: LinearLogpContext | None, +) -> torch.Tensor: + if qkv_format == "thd": + assert len(output_tensor.shape) == 3, f"{output_tensor.shape}" + if output_tensor.size(0) == 1: + return output_tensor.squeeze(0) + if linear_logp_context is not None and output_tensor.size(1) == 1: + return output_tensor.squeeze(1) + assert output_tensor.size(0) == 1, f"{output_tensor.shape}" + else: + assert max_seq_lens is not None + return output_tensor.view(-1, output_tensor.size(-1)) + + raise AssertionError(f"Unsupported output tensor shape: {output_tensor.shape}") + + +def _materialize_linear_logits( + hidden_states: torch.Tensor, + *, + context: LinearLogpContext, + args: Namespace, +) -> torch.Tensor: + logits = F.linear(hidden_states, context.lm_head_weight, context.bias) + rollout_temperature = getattr(args, "rollout_temperature", 1.0) + if rollout_temperature != 1.0: + logits = logits / rollout_temperature + return logits.float() + + +def _policy_loss_needs_entropy( + args: Namespace, + rl_kernel_linear_logp_context: LinearLogpContext | None, +) -> bool: + if rl_kernel_linear_logp_context is None: + return True + return not ( + getattr(args, "enable_rl_kernel", False) + and "linear_logp" in getattr(args, "rl_kernel_ops", ()) + and getattr(args, "entropy_coef", 0.0) == 0 + ) + + def get_log_probs_and_entropy( logits: torch.Tensor, *, @@ -394,6 +453,7 @@ def get_log_probs_and_entropy( with_entropy: bool = False, non_loss_data: bool = True, max_seq_lens: list[int] | None = None, + rl_kernel_linear_logp_context: LinearLogpContext | None = None, ) -> dict[str, list[torch.Tensor]]: """Compute per-token log-probabilities (and optionally entropy) on responses. @@ -407,21 +467,28 @@ def get_log_probs_and_entropy( assert non_loss_data qkv_format = args.qkv_format - assert logits.dtype == torch.float32, f"{logits.dtype}" assert len(logits.shape) == 3, f"{logits.shape}" - if qkv_format == "thd": - assert logits.size(0) == 1, f"{logits.shape}" - logits = logits.squeeze(0) + linear_logp_context = rl_kernel_linear_logp_context + if linear_logp_context is not None: + logits = _gather_sequence_parallel_hidden_if_needed(logits, linear_logp_context) else: - assert max_seq_lens is not None - logits = logits.view(-1, logits.size(-1)) + assert logits.dtype == torch.float32, f"{logits.dtype}" + + logits = _flatten_logprob_model_output( + logits, + qkv_format=qkv_format, + max_seq_lens=max_seq_lens, + linear_logp_context=linear_logp_context, + ).contiguous() + + if linear_logp_context is None: + # Apply rollout temperature scaling to logits to match rollout-time log-probs. + rollout_temperature = getattr(args, "rollout_temperature", 1.0) + if rollout_temperature != 1.0: + logits = logits / rollout_temperature + logits = logits.contiguous() - # Apply rollout temperature scaling to logits to match rollout-time log-probs. - rollout_temperature = getattr(args, "rollout_temperature", 1.0) - if rollout_temperature != 1.0: - logits = logits / rollout_temperature - logits = logits.contiguous() T = logits.size(0) device = logits.device tp_group = mpu.get_tensor_model_parallel_group() @@ -433,7 +500,20 @@ def get_log_probs_and_entropy( ) # --- compute on full [T,V] logits at once --- - log_prob_full = maybe_compute_logp(logits, full_tokens, args=args, with_entropy=with_entropy) + log_prob_full = None + if linear_logp_context is not None: + log_prob_full = maybe_compute_linear_logp( + logits, + full_tokens, + context=linear_logp_context, + args=args, + with_entropy=with_entropy, + ) + if log_prob_full is None: + logits = _materialize_linear_logits(logits, context=linear_logp_context, args=args).contiguous() + else: + log_prob_full = maybe_compute_logp(logits, full_tokens, args=args, with_entropy=with_entropy) + if log_prob_full is None: log_prob_full, entropy_full = calculate_log_probs_and_entropy( logits, @@ -807,6 +887,7 @@ def policy_loss_function( batch: RolloutBatch, logits: torch.Tensor, sum_of_sample_mean: Callable[[torch.Tensor], torch.Tensor], + rl_kernel_linear_logp_context: LinearLogpContext | None = None, ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: """Compute policy loss (PPO/GSPO) and metrics. @@ -839,14 +920,17 @@ def policy_loss_function( total_lengths = batch["total_lengths"] max_seq_lens = batch.get("max_seq_lens", None) + need_entropy = _policy_loss_needs_entropy(args, rl_kernel_linear_logp_context) + _, log_probs_and_entropy = get_log_probs_and_entropy( logits, args=args, unconcat_tokens=batch["unconcat_tokens"], total_lengths=total_lengths, response_lengths=response_lengths, - with_entropy=True, + with_entropy=need_entropy, max_seq_lens=max_seq_lens, + rl_kernel_linear_logp_context=rl_kernel_linear_logp_context, ) log_probs = log_probs_and_entropy["log_probs"] @@ -968,9 +1052,12 @@ def policy_loss_function( ppo_kl = sum_of_sample_mean(ppo_kl) # entropy loss - entropy = log_probs_and_entropy["entropy"] - entropy = torch.cat(entropy, dim=0) - entropy_loss = sum_of_sample_mean(entropy) + if need_entropy: + entropy = log_probs_and_entropy["entropy"] + entropy = torch.cat(entropy, dim=0) + entropy_loss = sum_of_sample_mean(entropy) + else: + entropy_loss = log_probs.new_zeros(()) loss = pg_loss - args.entropy_coef * entropy_loss @@ -1038,6 +1125,7 @@ def value_loss_function( batch: RolloutBatch, logits: torch.Tensor, sum_of_sample_mean: Callable[[torch.Tensor], torch.Tensor], + rl_kernel_linear_logp_context: LinearLogpContext | None = None, ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: """Compute clipped value loss and metrics. @@ -1056,6 +1144,7 @@ def value_loss_function( Tuple of `(loss, metrics)` where `loss` is a scalar tensor and `metrics` contains detached scalars "value_loss" and "value_clipfrac". """ + del rl_kernel_linear_logp_context old_values = torch.cat(batch["values"], dim=0) _, values = get_values( @@ -1096,6 +1185,7 @@ def sft_loss_function( batch: RolloutBatch, logits: torch.Tensor, sum_of_sample_mean: Callable[[torch.Tensor], torch.Tensor], + rl_kernel_linear_logp_context: LinearLogpContext | None = None, ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: """Compute supervised fine-tuning loss over response tokens. @@ -1124,6 +1214,7 @@ def sft_loss_function( response_lengths=response_lengths, with_entropy=False, max_seq_lens=batch.get("max_seq_lens", None), + rl_kernel_linear_logp_context=rl_kernel_linear_logp_context, ) log_probs = log_probs_and_entropy["log_probs"] @@ -1148,6 +1239,7 @@ def loss_function( num_microbatches: int, step_global_batch_size: int, logits: torch.Tensor, + rl_kernel_linear_logp_context: LinearLogpContext | None = None, ) -> tuple[torch.Tensor, int | torch.Tensor, dict[str, list[str] | torch.Tensor]]: """Dispatch to the configured loss and rescale for Megatron integration. @@ -1200,10 +1292,22 @@ def loss_function( case _: raise ValueError(f"Unknown loss type: {args.loss_type}") + if func in {policy_loss_function, value_loss_function, sft_loss_function}: + func_args = (args, batch, logits, sum_of_sample_mean, rl_kernel_linear_logp_context) + else: + func_args = (args, batch, logits, sum_of_sample_mean) + if args.recompute_loss_function: - loss, log = checkpoint(func, args, batch, logits, sum_of_sample_mean, use_reentrant=False) + loss, log = checkpoint(func, *func_args, use_reentrant=False) else: - loss, log = func(args, batch, logits, sum_of_sample_mean) + loss, log = func(*func_args) + + if getattr(args, "enable_rl_kernel", False): + log["rl_kernel_fallback_count"] = torch.tensor( + get_rl_kernel_fallback_count(), + device=logits.device, + dtype=torch.float32, + ) # With allgather-CP, some CP ranks may have no loss-contributing tokens (e.g., all # padding). Without this, gradient doesn't flow through their attention path, so diff --git a/vime/backends/megatron_utils/model.py b/vime/backends/megatron_utils/model.py index 6b602fc1..14f80f53 100644 --- a/vime/backends/megatron_utils/model.py +++ b/vime/backends/megatron_utils/model.py @@ -27,14 +27,22 @@ from megatron.core.pipeline_parallel.utils import unwrap_model except ImportError: from megatron.core.utils import unwrap_model + from vime.utils import logging_utils from vime.utils.memory_utils import clear_memory +from vime.utils.rl_kernel import is_rl_kernel_op_enabled from .checkpoint import load_checkpoint, save_checkpoint from .cp_utils import reduce_train_step_metrics from .data import DataIterator, get_batch -from .loss import loss_function +from .loss import get_log_probs_and_entropy, loss_function from .model_provider import get_model_provider_func +from .rl_kernel import ( + get_linear_logp_context_from_model, + return_hidden_states_for_linear_logp, + should_use_linear_logp_model_output, + warn_linear_logp_fallback, +) logger = logging.getLogger(__name__) @@ -73,6 +81,35 @@ def wrapped_forward_step(*args, **kwargs): return wrapped_forward_step +def _forward_only_should_return_hidden_for_linear_logp( + f: Callable[..., dict[str, list[torch.Tensor]]], + args: Namespace, +) -> bool: + return f is get_log_probs_and_entropy and should_use_linear_logp_model_output( + args, + with_entropy=args.use_rollout_entropy, + ) + + +def _train_should_return_hidden_for_linear_logp(args: Namespace, *, return_schedule_plan: bool) -> bool: + if not is_rl_kernel_op_enabled(args, "linear_logp"): + return False + + if args.loss_type not in {"policy_loss", "sft_loss"}: + return False + + if return_schedule_plan: + warn_linear_logp_fallback(args, "schedule-plan forward path is not supported") + return False + + if getattr(args, "enable_mtp_training", False): + warn_linear_logp_fallback(args, "MTP training path is not supported") + return False + + with_entropy = args.loss_type == "policy_loss" and getattr(args, "entropy_coef", 0.0) != 0 + return should_use_linear_logp_model_output(args, with_entropy=with_entropy) + + def _iter_critic_output_layers(model: Sequence[DDP]): for chunk_id, module in enumerate(unwrap_model(model)): output_layer = getattr(module, "output_layer", None) @@ -342,17 +379,25 @@ def forward_step( } if batch["multimodal_train_inputs"] is not None: forward_kwargs.update(batch["multimodal_train_inputs"]) - output_tensor = model(**forward_kwargs) + linear_logp_context = None + if _forward_only_should_return_hidden_for_linear_logp(f, args): + linear_logp_context = get_linear_logp_context_from_model(args, model) - return output_tensor, partial( - f, - args=args, - unconcat_tokens=unconcat_tokens, - total_lengths=total_lengths, - response_lengths=response_lengths, - with_entropy=args.use_rollout_entropy, - max_seq_lens=batch.get("max_seq_lens", None), - ) + with return_hidden_states_for_linear_logp(args, model, linear_logp_context): + output_tensor = model(**forward_kwargs) + + callback_kwargs = { + "args": args, + "unconcat_tokens": unconcat_tokens, + "total_lengths": total_lengths, + "response_lengths": response_lengths, + "with_entropy": args.use_rollout_entropy, + "max_seq_lens": batch.get("max_seq_lens", None), + } + if f is get_log_probs_and_entropy: + callback_kwargs["rl_kernel_linear_logp_context"] = linear_logp_context + + return output_tensor, partial(f, **callback_kwargs) # Turn on evaluation mode which disables dropout. for model_module in model: @@ -512,6 +557,10 @@ def forward_step(data_iterator: DataIterator, model: GPTModel, return_schedule_p old_stage = os.environ["ROUTING_REPLAY_STAGE"] os.environ["ROUTING_REPLAY_STAGE"] = "replay_forward" + linear_logp_context = None + if _train_should_return_hidden_for_linear_logp(args, return_schedule_plan=return_schedule_plan): + linear_logp_context = get_linear_logp_context_from_model(args, model) + if return_schedule_plan: assert not args.enable_mtp_training, "MTP training should not be enabled when using combined 1f1b" position_ids = None @@ -539,12 +588,20 @@ def forward_step(data_iterator: DataIterator, model: GPTModel, return_schedule_p if args.enable_mtp_training: forward_kwargs["mtp_kwargs"] = {"mtp_labels": batch["tokens"]} - output_tensor = model(**forward_kwargs) + with return_hidden_states_for_linear_logp(args, model, linear_logp_context): + output_tensor = model(**forward_kwargs) if os.environ.get("ENABLE_ROUTING_REPLAY", "0") == "1": os.environ["ROUTING_REPLAY_STAGE"] = old_stage - return output_tensor, partial(loss_function, args, batch, num_microbatches, step_global_batch_size) + return output_tensor, partial( + loss_function, + args, + batch, + num_microbatches, + step_global_batch_size, + rl_kernel_linear_logp_context=linear_logp_context, + ) # Forward pass. forward_backward_func = get_forward_backward_func() diff --git a/vime/backends/megatron_utils/rl_kernel.py b/vime/backends/megatron_utils/rl_kernel.py index 7f712b32..aca8706d 100644 --- a/vime/backends/megatron_utils/rl_kernel.py +++ b/vime/backends/megatron_utils/rl_kernel.py @@ -2,6 +2,9 @@ import logging from argparse import Namespace +from contextlib import contextmanager +from dataclasses import dataclass +from typing import Any import torch from megatron.core import mpu @@ -12,15 +15,36 @@ _LOGP_OP = None _LOGP_OP_LOAD_ERROR: Exception | None = None +_LINEAR_LOGP_OP = None +_LINEAR_LOGP_OP_LOAD_ERROR: Exception | None = None _WARNED_FALLBACK_REASONS: set[str] = set() +_FALLBACK_COUNTS: dict[str, int] = {"logp": 0, "linear_logp": 0} -def _warn_fallback(args: Namespace, reason: str) -> None: +@dataclass(frozen=True) +class LinearLogpContext: + lm_head_weight: torch.Tensor + bias: torch.Tensor | None + tp_group: Any + vocab_start_index: int = 0 + global_vocab_size: int | None = None + sequence_parallel: bool = False + + +def get_rl_kernel_fallback_count(op: str | None = None) -> int: + if op is not None: + return _FALLBACK_COUNTS.get(op, 0) + return sum(_FALLBACK_COUNTS.values()) + + +def _warn_fallback(args: Namespace, op: str, reason: str) -> None: + _FALLBACK_COUNTS[op] = _FALLBACK_COUNTS.get(op, 0) + 1 if getattr(args, "rl_kernel_strict", False): - raise RuntimeError(f"RL-Kernel logp is enabled but unavailable: {reason}") - if reason not in _WARNED_FALLBACK_REASONS: - logger.warning("Falling back to vime logprob path because RL-Kernel logp is unavailable: %s", reason) - _WARNED_FALLBACK_REASONS.add(reason) + raise RuntimeError(f"RL-Kernel {op} is enabled but unavailable: {reason}") + warning_key = f"{op}: {reason}" + if warning_key not in _WARNED_FALLBACK_REASONS: + logger.warning("Falling back to vime logprob path because RL-Kernel %s is unavailable: %s", op, reason) + _WARNED_FALLBACK_REASONS.add(warning_key) def _get_logp_op(args: Namespace): @@ -28,7 +52,7 @@ def _get_logp_op(args: Namespace): if _LOGP_OP is not None: return _LOGP_OP if _LOGP_OP_LOAD_ERROR is not None: - _warn_fallback(args, str(_LOGP_OP_LOAD_ERROR)) + _warn_fallback(args, "logp", str(_LOGP_OP_LOAD_ERROR)) return None try: @@ -39,10 +63,155 @@ def _get_logp_op(args: Namespace): return _LOGP_OP except Exception as exc: # pragma: no cover - exercised with missing optional package in integration envs _LOGP_OP_LOAD_ERROR = exc - _warn_fallback(args, str(exc)) + _warn_fallback(args, "logp", str(exc)) + return None + + +def _get_linear_logp_op(args: Namespace): + global _LINEAR_LOGP_OP, _LINEAR_LOGP_OP_LOAD_ERROR + if _LINEAR_LOGP_OP is not None: + return _LINEAR_LOGP_OP + if _LINEAR_LOGP_OP_LOAD_ERROR is not None: + _warn_fallback(args, "linear_logp", str(_LINEAR_LOGP_OP_LOAD_ERROR)) + return None + + try: + from rl_engine.kernels.registry import kernel_registry + + _LINEAR_LOGP_OP = kernel_registry.get_op("linear_logp") + logger.info("Using RL-Kernel linear_logp op: %s", type(_LINEAR_LOGP_OP).__name__) + return _LINEAR_LOGP_OP + except Exception as exc: # pragma: no cover - exercised with missing optional package in integration envs + _LINEAR_LOGP_OP_LOAD_ERROR = exc + _warn_fallback(args, "linear_logp", str(exc)) return None +def _unwrap_model_chunk(model): + while hasattr(model, "module"): + model = model.module + return model + + +def _is_pipeline_last_stage_for_model(model) -> bool: + module = _unwrap_model_chunk(model) + vp_stage = getattr(module, "vp_stage", None) + try: + vp_world_size = mpu.get_virtual_pipeline_model_parallel_world_size() + except Exception: + vp_world_size = None + + try: + if vp_world_size is not None and vp_stage is not None: + return bool(mpu.is_pipeline_last_stage(ignore_virtual=False, vp_stage=vp_stage)) + return bool(mpu.is_pipeline_last_stage(ignore_virtual=True)) + except Exception: + return True + + +def _get_lm_head_weight(model, output_layer) -> torch.Tensor | None: + shared_weight = getattr(model, "shared_embedding_or_output_weight", None) + if callable(shared_weight): + try: + weight = shared_weight() + if isinstance(weight, torch.Tensor): + return weight + except Exception: + logger.debug("Unable to read shared embedding/output weight for RL-Kernel linear_logp.", exc_info=True) + + weight = getattr(output_layer, "weight", None) + return weight if isinstance(weight, torch.Tensor) else None + + +def get_linear_logp_context_from_model(args: Namespace, model) -> LinearLogpContext | None: + if not is_rl_kernel_op_enabled(args, "linear_logp"): + return None + + if not _is_pipeline_last_stage_for_model(model): + return None + + module = _unwrap_model_chunk(model) + output_layer = getattr(module, "output_layer", None) + if output_layer is None: + _warn_fallback(args, "linear_logp", "model output_layer is unavailable") + return None + + weight = _get_lm_head_weight(module, output_layer) + if weight is None: + _warn_fallback(args, "linear_logp", "LM-head weight is unavailable") + return None + + bias = getattr(output_layer, "bias", None) + if not isinstance(bias, torch.Tensor): + bias = None + + tp_world_size = int(mpu.get_tensor_model_parallel_world_size()) + tp_group = mpu.get_tensor_model_parallel_group() if tp_world_size > 1 else None + vocab_start_index = 0 + global_vocab_size = None + if tp_world_size > 1: + local_vocab_size = int(weight.size(0)) + vocab_start_index = int(mpu.get_tensor_model_parallel_rank()) * local_vocab_size + global_vocab_size = getattr(args, "padded_vocab_size", None) + if global_vocab_size is None: + global_vocab_size = local_vocab_size * tp_world_size + + return LinearLogpContext( + lm_head_weight=weight, + bias=bias, + tp_group=tp_group, + vocab_start_index=vocab_start_index, + global_vocab_size=None if global_vocab_size is None else int(global_vocab_size), + sequence_parallel=bool(getattr(output_layer, "sequence_parallel", getattr(args, "sequence_parallel", False))), + ) + + +def _linear_logp_runtime_blocker(args: Namespace, *, with_entropy: bool) -> str | None: + if with_entropy: + return "entropy is requested" + if getattr(args, "qkv_format", "thd") != "thd": + return "only qkv_format=thd is supported by RL-Kernel linear_logp" + if mpu.get_context_parallel_world_size() != 1 or getattr(args, "allgather_cp", False): + return "context parallel logprob redistribution is not supported by RL-Kernel linear_logp" + if getattr(args, "rollout_temperature", 1.0) <= 0: + return "rollout_temperature must be positive" + return None + + +def should_use_linear_logp_model_output(args: Namespace, *, with_entropy: bool) -> bool: + if not is_rl_kernel_op_enabled(args, "linear_logp"): + return False + reason = _linear_logp_runtime_blocker(args, with_entropy=with_entropy) + if reason is not None: + _warn_fallback(args, "linear_logp", reason) + return False + return True + + +def warn_linear_logp_fallback(args: Namespace, reason: str) -> None: + _warn_fallback(args, "linear_logp", reason) + + +@contextmanager +def return_hidden_states_for_linear_logp(args: Namespace, model, context: LinearLogpContext | None): + if context is None: + yield False + return + + module = _unwrap_model_chunk(model) + if not hasattr(module, "post_process"): + _warn_fallback(args, "linear_logp", "model post_process flag is unavailable") + yield False + return + + old_post_process = module.post_process + module.post_process = False + try: + yield True + finally: + module.post_process = old_post_process + + def maybe_compute_logp( logits: torch.Tensor, tokens: torch.Tensor, @@ -61,19 +230,19 @@ def maybe_compute_logp( return None if with_entropy: - _warn_fallback(args, "entropy is requested") + _warn_fallback(args, "logp", "entropy is requested") return None if logits.requires_grad or torch.is_grad_enabled(): - _warn_fallback(args, "autograd is enabled") + _warn_fallback(args, "logp", "autograd is enabled") return None if mpu.get_tensor_model_parallel_world_size() != 1: - _warn_fallback(args, "tensor-parallel vocab shards are not supported by RL-Kernel logp") + _warn_fallback(args, "logp", "tensor-parallel vocab shards are not supported by RL-Kernel logp") return None if mpu.get_context_parallel_world_size() != 1 or getattr(args, "allgather_cp", False): - _warn_fallback(args, "context parallel logprob redistribution is not supported by RL-Kernel logp") + _warn_fallback(args, "logp", "context parallel logprob redistribution is not supported by RL-Kernel logp") return None if logits.size(0) == 0: @@ -89,7 +258,59 @@ def maybe_compute_logp( else: log_prob = op(logits, tokens).float() except Exception as exc: - _warn_fallback(args, str(exc)) + _warn_fallback(args, "logp", str(exc)) return None return log_prob.reshape(-1) + + +def maybe_compute_linear_logp( + hidden_states: torch.Tensor, + target_ids: torch.Tensor, + *, + context: LinearLogpContext | None, + args: Namespace, + with_entropy: bool, +) -> torch.Tensor | None: + if not is_rl_kernel_op_enabled(args, "linear_logp"): + return None + + reason = _linear_logp_runtime_blocker(args, with_entropy=with_entropy) + if reason is not None: + _warn_fallback(args, "linear_logp", reason) + return None + + if context is None: + _warn_fallback(args, "linear_logp", "hidden-state linear_logp context is unavailable") + return None + + if target_ids.numel() == 0: + return hidden_states.new_zeros((0,), dtype=torch.float32) + + op = _get_linear_logp_op(args) + if op is None: + return None + + weight = context.lm_head_weight + bias = context.bias + rollout_temperature = float(getattr(args, "rollout_temperature", 1.0)) + if rollout_temperature != 1.0: + weight = weight / rollout_temperature + if bias is not None: + bias = bias / rollout_temperature + + try: + log_prob = op( + hidden_states, + weight, + target_ids.long(), + bias, + tp_group=context.tp_group, + vocab_start_index=context.vocab_start_index, + global_vocab_size=context.global_vocab_size, + ) + except Exception as exc: + _warn_fallback(args, "linear_logp", str(exc)) + return None + + return log_prob.float().reshape(-1) diff --git a/vime/utils/arguments.py b/vime/utils/arguments.py index 34ffa666..a186a756 100644 --- a/vime/utils/arguments.py +++ b/vime/utils/arguments.py @@ -167,8 +167,8 @@ def add_train_arguments(parser): parser.add_argument( "--rl-kernel-ops", type=str, - default="logp", - help="Comma-separated RL-Kernel ops to enable. Current production integration supports: logp.", + default="linear_logp", + help="Comma-separated RL-Kernel ops to enable. Current production integration supports: linear_logp.", ) parser.add_argument( "--rl-kernel-strict", diff --git a/vime/utils/rl_kernel.py b/vime/utils/rl_kernel.py index c01e02db..e0e4a0b7 100644 --- a/vime/utils/rl_kernel.py +++ b/vime/utils/rl_kernel.py @@ -3,8 +3,8 @@ from collections.abc import Iterable -RL_KERNEL_SUPPORTED_OPS = ("logp", "ratio_kl", "grpo_loss") -RL_KERNEL_INTEGRATED_OPS = ("logp",) +RL_KERNEL_SUPPORTED_OPS = ("linear_logp",) +RL_KERNEL_INTEGRATED_OPS = ("linear_logp",) _TRUE_VALUES = {"1", "true", "yes", "on"} _FALSE_VALUES = {"0", "false", "no", "off"} @@ -12,7 +12,7 @@ def parse_rl_kernel_ops(value: str | Iterable[str] | None) -> tuple[str, ...]: """Parse a comma/space separated RL-Kernel op list.""" if value is None: - return ("logp",) + return ("linear_logp",) if isinstance(value, str): raw_items = value.replace(",", " ").split() @@ -32,7 +32,7 @@ def parse_rl_kernel_ops(value: str | Iterable[str] | None) -> tuple[str, ...]: if op not in ops: ops.append(op) - return tuple(ops) if ops else ("logp",) + return tuple(ops) if ops else ("linear_logp",) def _env_bool(name: str) -> bool | None: From e32bd5e3e980e26b84135c9151391e6dcfd766c6 Mon Sep 17 00:00:00 2001 From: inaniloquentee <3051000145@qq.com> Date: Sat, 27 Jun 2026 16:28:45 +0800 Subject: [PATCH 4/5] Add RL-Kernel vime benchmark runbook Signed-off-by: inaniloquentee <3051000145@qq.com> --- vime-RLK.md | 312 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 vime-RLK.md diff --git a/vime-RLK.md b/vime-RLK.md new file mode 100644 index 00000000..42cf234d --- /dev/null +++ b/vime-RLK.md @@ -0,0 +1,312 @@ +# vime + RL-Kernel linear_logp 主宣传实验 + +## 0. 我们要做什么 + +本轮只保留一个主宣传实验: + +```text +baseline: vime benchmark branch, Qwen3-30B-A3B, 8xH100 colocate, RL-Kernel off +candidate: vime benchmark branch + RL-Kernel linear_logp, Qwen3-30B-A3B, 8xH100 colocate +``` + +目标:证明 RL-Kernel 的 `linear_logp` 接入 vime 后,在同一套 Qwen3-30B-A3B MoE 训练配置下,不降低训练质量,并降低 selected-logprob 路径耗时或显存压力。 + +范围收口: + +- 只测 `linear_logp`。 +- 只跑 Qwen3-30B-A3B 主宣传实验。 +- 不跑 Qwen3-4B smoke、R3 单独对比、GLM-4.5、GB200/H200 硬件对照。 +- 不测 `logp`、`ratio_kl`、`grpo_loss`、`sampling` 的 vime 端到端收益。 +- 不做训推一致性专项 benchmark。 +- 不接 MoE expert/router 算子。 + +## 1. H100 支持结论 + +vime 支持 H100:当前 vime 文档已有 `Qwen3-30B-A3B with 8xH100` 和 `Qwen3-4B with 8xH100` 示例,代码里也有 H100 hardware mapping。 + +所以本轮主方案使用: + +```text +8xH100 +``` + +A100 不作为本轮主宣传配置。 + +## 2. 当前代码边界 + +vime candidate 只暴露一个 RL-Kernel op: + +```text +RL_KERNEL_SUPPORTED_OPS = ("linear_logp",) +RL_KERNEL_INTEGRATED_OPS = ("linear_logp",) +--rl-kernel-ops linear_logp +VIME_RL_KERNEL_OPS=linear_logp +``` + +主实验脚本: + +```text +scripts/run-qwen3-30B-A3B.sh +``` + +该脚本已经按 8 卡主宣传实验参数化: + +```text +NUM_GPUS=8 +MEGATRON_TP=4 +MEGATRON_EP=8 +MEGATRON_CP=1 +ROLLOUT_NUM_GPUS_PER_ENGINE=8 +ROLLOUT_BATCH_SIZE=32 +N_SAMPLES_PER_PROMPT=8 +GLOBAL_BATCH_SIZE=256 +MAX_TOKENS_PER_GPU=20480 +VLLM_GPU_MEMORY_UTILIZATION=0.7 +``` + +如遇 OOM,先降低: + +```text +MAX_TOKENS_PER_GPU=8192 +VLLM_GPU_MEMORY_UTILIZATION=0.55 +ROLLOUT_BATCH_SIZE=4 +GLOBAL_BATCH_SIZE=32 +``` + +## 3. 上卡准备 + +从官方仓库开始,不依赖当前本地目录: + +```bash +cd /workspace +git clone https://github.com/RL-Align/vime.git vime-main +git clone https://github.com/RL-Align/vime.git vime-benchmark +git clone https://github.com/RL-Align/vime.git vime-rlk-integration +git clone https://github.com/RL-Align/RL-Kernel.git RL-Kernel +``` + +RL-Kernel 必须使用含 TP 版 `linear_logp` 接口的版本。vime 这边会调用: + +```text +op(hidden, weight, target_ids, bias, tp_group=..., vocab_start_index=..., global_vocab_size=...) +``` + +当前使用 `RL-Align/RL-Kernel#189` 提供 TP 版 `linear_logp`。上卡后在 `/workspace/RL-Kernel` 里 checkout 该 PR 后再安装: + +```bash +cd /workspace/RL-Kernel +git checkout main +git pull origin main +gh pr checkout 189 +``` + +`vime-main` 只作为干净参考,不直接跑实验: + +```bash +cd /workspace/vime-main +git checkout main +git pull origin main +``` + +vime candidate 已经准备成 draft PR,baseline 仍然要保持 benchmark-only,避免把 baseline 和 candidate 混在一起: + +```text +vime-rlk-benchmark-8h100 +只包含 8xH100 benchmark harness,不包含 RL-Kernel 集成代码。 + +RL-Align/vime#1 +draft PR,基于 benchmark harness,再加入 RL-Kernel linear_logp 集成代码和测试。 +``` + +baseline 从干净 main 新建 benchmark 分支: + +```bash +cd /workspace/vime-benchmark +git checkout main +git pull origin main +git checkout -b vime-rlk-benchmark-8h100 +# 只应用 benchmark harness 改动,例如 scripts/run-qwen3-30B-A3B.sh 的 8xH100 参数化。 +# 不加入 --enable-rl-kernel、vime/utils/rl_kernel.py、megatron_utils/rl_kernel.py 等 RL-Kernel 集成改动。 +``` + +candidate 直接 checkout draft PR `RL-Align/vime#1`: + +```bash +cd /workspace/vime-rlk-integration +git checkout main +git pull origin main +gh pr checkout 1 +``` + +安装: + +```bash +cd /workspace/RL-Kernel +pip install -e . +python setup.py build_ext --inplace -v + +cd /workspace/vime-benchmark +pip install -e . + +cd /workspace/vime-rlk-integration +pip install -e . +``` + +下载模型和数据: + +```bash +pip install -U "huggingface_hub[cli]" +huggingface-cli login + +hf download Qwen/Qwen3-30B-A3B --local-dir /root/Qwen3-30B-A3B + +hf download --repo-type dataset zhuzilin/dapo-math-17k \ + --local-dir /root/dapo-math-17k + +hf download --repo-type dataset zhuzilin/aime-2024 \ + --local-dir /root/aime-2024 +``` + +转换 Megatron `torch_dist` checkpoint: + +```bash +cd /workspace/vime-benchmark +source scripts/models/qwen3-30B-A3B.sh + +PYTHONPATH=/root/Megatron-LM torchrun --nproc-per-node 8 \ + tools/convert_hf_to_torch_dist.py \ + ${MODEL_ARGS[@]} \ + --hf-checkpoint /root/Qwen3-30B-A3B \ + --save /root/Qwen3-30B-A3B_torch_dist +``` + +## 4. 运行主宣传实验 + +两边使用同一套 8 卡 colocate 环境变量: + +```bash +export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 +export NUM_GPUS=8 +export MEGATRON_TP=4 +export MEGATRON_EP=8 +export MEGATRON_CP=1 +export ROLLOUT_NUM_GPUS_PER_ENGINE=8 +export ROLLOUT_BATCH_SIZE=32 +export N_SAMPLES_PER_PROMPT=8 +export GLOBAL_BATCH_SIZE=256 +export MAX_TOKENS_PER_GPU=20480 +export VLLM_GPU_MEMORY_UTILIZATION=0.7 +``` + +baseline: + +```bash +cd /workspace/vime-benchmark +unset VIME_RL_KERNEL VIME_RL_KERNEL_OPS VIME_RL_KERNEL_STRICT +bash scripts/run-qwen3-30B-A3B.sh +``` + +candidate: + +```bash +cd /workspace/vime-rlk-integration +export VIME_RL_KERNEL=1 +export VIME_RL_KERNEL_OPS=linear_logp +export VIME_RL_KERNEL_STRICT=1 +bash scripts/run-qwen3-30B-A3B.sh +``` + +每组至少跑 3 次;每次丢弃前 5-10 step warmup 后统计。 + +## 5. 必须记录 + +每个 run 保存: + +```text +hardware +gpu_name +num_gpus +model +dataset +vime_commit +rl_kernel_commit +candidate_enabled +enabled_rl_kernel_ops +selected_rl_kernel_backend +tp +ep +cp +rollout_batch_size +n_samples_per_prompt +global_batch_size +max_tokens_per_gpu +mean_step_time_s +p50_step_time_s +p90_step_time_s +mean_log_probs_time_s +p50_log_probs_time_s +p90_log_probs_time_s +peak_vram_gb +raw_reward_mean +train_rollout_logprob_abs_diff_mean +rl_kernel_fallback_count +``` + +验收线: + +```text +candidate 日志出现 RL-Kernel linear_logp backend +rl_kernel_fallback_count = 0 +candidate raw_reward 不低于 baseline 同量级 +candidate train_rollout_logprob_abs_diff 不持续高于 baseline +candidate mean_log_probs_time_s 或 peak_vram_gb 有可解释下降 +``` + +## 6. 最终图表 + +只输出主宣传图: + +1. `Qwen3-30B-A3B 8xH100 raw_reward` +2. `Qwen3-30B-A3B 8xH100 train_rollout_logprob_abs_diff` +3. `Qwen3-30B-A3B 8xH100 Step Time` +4. `Qwen3-30B-A3B 8xH100 Logprob Time / Peak VRAM` + +图表风格对齐 `vime_blog.md`:白底、虚线网格、baseline 蓝色、candidate 红色。 + +## 7. 本地验证 + +当前无 GPU 环境已完成: + +```text +# linear_logp 主路径与公共工具 +pytest tests/test_rl_kernel_args.py tests/test_rl_kernel_linear_logp_integration.py tests/test_value_temperature.py tests/test_metric_report.py -q +结果:39 passed + +# legacy logp compatibility regression,不属于本轮 benchmark 范围 +pytest tests/test_rl_kernel_logp_integration.py tests/test_rl_kernel_args.py tests/test_rl_kernel_linear_logp_integration.py -q +结果:24 passed + +pre-commit run --files <本轮 vime 相关文件> +结果:Passed +``` + +上卡后必须补跑: + +```text +8xH100 baseline: /workspace/vime-benchmark, benchmark-only branch +8xH100 candidate: /workspace/vime-rlk-integration, RL-Align/vime#1 +``` + +## 8. 宣传口径 + +英文: + +```text +RL-Kernel integrates with vime to accelerate the Qwen3-30B-A3B GRPO selected-logprob path through linear_logp. On the same 8xH100 setup, it reduces logprob-path cost while keeping reward and train-rollout logprob alignment stable. +``` + +中文: + +```text +RL-Kernel 接入 vime 后,通过 linear_logp 加速 Qwen3-30B-A3B GRPO selected-logprob 路径。在相同 8xH100 配置下,RL-Kernel 降低 logprob 路径开销,同时保持 reward 和 train-rollout logprob alignment 稳定。 +``` From ee3fa1e49e3ccb2ab1502f5ef8349af6a947516d Mon Sep 17 00:00:00 2001 From: Takumi <3051000145@qq.com> Date: Sat, 27 Jun 2026 12:39:13 +0100 Subject: [PATCH 5/5] Add TP8 RL-Kernel linear_logp experiment results --- .../.gitignore | 1 + .../RESULTS.md | 262 ++++++++++++++++++ .../charts/logprob_alignment.png | Bin 0 -> 51951 bytes .../charts/logprob_time_peak_vram.png | Bin 0 -> 73593 bytes .../charts/performance_breakdown.png | Bin 0 -> 45013 bytes .../charts/raw_reward.png | Bin 0 -> 36571 bytes .../charts/step_time.png | Bin 0 -> 38772 bytes .../data/metrics.csv | 3 + .../run_one.sh | 105 +++++++ scripts/run-qwen3-30B-A3B.sh | 42 ++- .../test_rl_kernel_linear_logp_integration.py | 45 +++ vime-RLK.md | 4 +- vime/backends/megatron_utils/rl_kernel.py | 7 +- vime/ray/actor_group.py | 1 + 14 files changed, 460 insertions(+), 10 deletions(-) create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/.gitignore create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/RESULTS.md create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/logprob_alignment.png create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/logprob_time_peak_vram.png create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/performance_breakdown.png create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/raw_reward.png create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/step_time.png create mode 100644 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/data/metrics.csv create mode 100755 experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/run_one.sh diff --git a/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/.gitignore b/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/.gitignore new file mode 100644 index 00000000..483fb84a --- /dev/null +++ b/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/.gitignore @@ -0,0 +1 @@ +/runs/ diff --git a/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/RESULTS.md b/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/RESULTS.md new file mode 100644 index 00000000..f2f839dc --- /dev/null +++ b/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/RESULTS.md @@ -0,0 +1,262 @@ +# RL-Kernel linear_logp Qwen3-30B-A3B 8xH100 实验记录 + +更新时间: 2026-06-27 11:35 UTC + +## 结论摘要 + +本轮只记录成功完成的 TP=8 smoke validation,不记录失败 run。baseline 使用 benchmark-only vime,candidate 使用当前 PR 分支并只启用 `RL-Kernel linear_logp`。 + +在相同 Qwen3-30B-A3B、8xH100、TP=8/EP=8/CP=1、单 prompt/单 sample/单 step smoke 配置下: + +| 项目 | baseline | candidate | 变化 | +| --- | ---: | ---: | ---: | +| raw_reward | 0.000000 | 0.000000 | 持平 | +| rollout_log_probs | -0.181073 | -0.181073 | 持平 | +| ref_log_probs | -0.182800 | -0.182881 | 差异 -0.000081 | +| train_rollout_logprob_abs_diff | 0.011258 | 0.013460 | +0.002202 | +| ref_log_probs_time | 12.980901 s | 10.400161 s | -19.88% | +| actor_train_time | 36.332478 s | 33.124115 s | -8.83% | +| train_time | 49.632797 s | 43.838564 s | -11.67% | +| step_time | 96.997870 s | 90.671674 s | -6.52% | +| actor_train_tok_per_s | 9.000212 | 9.871962 | +9.69% | +| peak_vram | 64363 MiB | 64363 MiB | 持平 | +| RL-Kernel fallback count | N/A | 0 | 命中主路径 | + +当前数据能证明:TP=8 下 `linear_logp` 集成可成功跑通,candidate 命中 SM90 fused backend 且没有 fallback;selected-logprob/ref-logprob 路径耗时下降,训练/rollout logprob alignment 仍在同一量级。该结果是 smoke validation,不替代 `vime-RLK.md` 中要求的多 run、多 step 正式宣传 benchmark。 + +## 图表 + +![Raw Reward](charts/raw_reward.png) + +![Logprob Alignment](charts/logprob_alignment.png) + +![Step Time](charts/step_time.png) + +![Logprob Time / Peak VRAM](charts/logprob_time_peak_vram.png) + +![Performance Breakdown](charts/performance_breakdown.png) + +结构化指标同时保存为 [data/metrics.csv](data/metrics.csv)。 + +## 实验范围 + +| 项目 | 设置 | +| --- | --- | +| baseline | `/workspace/vime-benchmark`,RL-Kernel off | +| candidate | `/workspace/vime-rlk-integration`,PR 分支 `vime-rlk-integration` | +| enabled RL-Kernel ops | `linear_logp` only | +| 模型 | `/root/Qwen3-30B-A3B` | +| Megatron checkpoint | `/root/Qwen3-30B-A3B_torch_dist` | +| 训练数据 | `/root/dapo-math-17k/dapo-math-17k.jsonl` | +| Eval 数据 | `/root/aime-2024/aime-2024.jsonl` | +| GPU | 8 x NVIDIA H100 80GB HBM3 | +| CUDA_HOME | `/usr/local/lib/python3.11/dist-packages/nvidia/cu13` | +| Python | 3.11.10 | +| Torch | 2.11.0+cu130 | +| vLLM | 0.22.0 | + +后续所有 `linear_logp` 实验均以 `TP=8` 为准。`scripts/run-qwen3-30B-A3B.sh` 和 `vime-RLK.md` 中默认 TP 已同步为 8。 + +## Run 配置 + +| 配置 | baseline | candidate | +| --- | --- | --- | +| run_name | `smoke-baseline-tp8` | `smoke-candidate-tp8` | +| Ray job | `raysubmit_rUd8WjeLXv4eZxcb` | `raysubmit_rbz7TUKU3T7U4GPm` | +| 状态 | succeeded | succeeded | +| TP / EP / CP | 8 / 8 / 1 | 8 / 8 / 1 | +| rollout engines | 1 engine, 8 GPU per engine | 1 engine, 8 GPU per engine | +| NUM_ROLLOUT | 1 | 1 | +| ROLLOUT_BATCH_SIZE | 1 | 1 | +| N_SAMPLES_PER_PROMPT | 1 | 1 | +| GLOBAL_BATCH_SIZE | 1 | 1 | +| ROLLOUT_MAX_RESPONSE_LEN | 128 | 128 | +| MAX_TOKENS_PER_GPU | 4096 | 4096 | +| VLLM_GPU_MEMORY_UTILIZATION | 0.7 | 0.7 | +| VIME_VLLM_ENFORCE_EAGER | 1 | 1 | +| VIME_NO_GRAD_ACCUM_FUSION | 1 | 1 | +| VIME_SKIP_EVAL_BEFORE_TRAIN | 1 | 1 | +| VIME_RL_KERNEL | 0 | 1 | +| VIME_RL_KERNEL_OPS | empty | `linear_logp` | +| VIME_RL_KERNEL_STRICT | 0 | 1 | + +本地原始日志目录位于 `experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/runs/`,该目录被 `.gitignore` 屏蔽,不随 PR 提交。报告和图表使用日志中抽取出的成功指标;原始 checkpoint 在指标抽取后已清理。 + +## Candidate backend 命中情况 + +candidate 日志确认: + +```text +Using RL-Kernel linear_logp op: FusedLinearLogpSM90Op +Successfully linked to precompiled _C.fused_linear_logp_sm90 kernel +train/rl_kernel_fallback_count: 0.0 +Job 'raysubmit_rbz7TUKU3T7U4GPm' succeeded +``` + +这说明本次 candidate 走的是 RL-Kernel `linear_logp` fused backend,没有落回 vime materialized logits 路径。 + +## 关键修复 + +### TP 统一为 8 + +`linear_logp` 后续实验统一按 `TP=8` 执行。脚本默认值和实验文档中的命令均已改为: + +```text +MEGATRON_TP=8 +MEGATRON_EP=8 +MEGATRON_CP=1 +``` + +### CUDA 13 torch_memory_saver preload + +当前环境安装的是 CUDA 13 版本 preload so: + +```text +torch_memory_saver_hook_mode_preload_cu13.abi3.so +``` + +原代码只查 CUDA 12 或通用 preload so,会导致 Megatron actor 初始化阶段找不到 preload 库。已在 `vime/ray/actor_group.py` 增加 CUDA 13 preload 文件名支持。 + +### gradient accumulation fusion + +TP=8 smoke 在当前 Torch/CUDA 13/Apex 组合下,Megatron fused gradient accumulation 可能触发 cuBLAS 初始化错误。实验脚本加入: + +```text +VIME_NO_GRAD_ACCUM_FUSION=1 +--no-gradient-accumulation-fusion +``` + +该配置下 baseline 和 candidate 均成功完成。 + +### 禁用实验 checkpoint 保存 + +30B checkpoint 很大,本次 smoke 初始运行每个 run 会产生约 400GB checkpoint。为避免后续复现实验继续写入大文件,`scripts/run-qwen3-30B-A3B.sh` 新增: + +```text +VIME_DISABLE_SAVE=1 +``` + +该开关启用时不传 `--save` 和 `--save-interval`,`save_interval=None` 会关闭训练循环中的周期保存。实验 runner `run_one.sh` 默认设置 `VIME_DISABLE_SAVE=1`。 + +### untied LM Head 权重 + +Qwen3 使用 untied embedding/output weights。`linear_logp` 的数学路径是: + +```text +logp = log_softmax(hidden_states @ output_layer.weight.T + bias)[target_token] +``` + +因此 `linear_logp` 需要 LM Head 权重,但没有额外启用独立的 RL-Kernel LM Head op。修复前在 PP=1 且 `pre_process=True` 的模型上,辅助函数可能优先拿到 `shared_embedding_or_output_weight()` 返回的 embedding weight,而不是 untied `output_layer.weight`,这会造成 train/ref/rollout logprob 不一致。 + +已修复为优先使用 `output_layer.weight`,仅在该权重不可用时才回退到 shared embedding/output weight。新增单测覆盖: + +```text +test_linear_logp_context_prefers_output_layer_weight_for_untied_pp1_model +test_linear_logp_context_uses_shared_weight_when_output_layer_weight_is_missing +``` + +## 指标明细 + +| 指标 | baseline | candidate | +| --- | ---: | ---: | +| rollout/raw_reward | 0.000000 | 0.000000 | +| rollout/rewards | 0.000000 | 0.000000 | +| rollout/response_lengths | 128.000000 | 128.000000 | +| rollout/truncated | 1.000000 | 1.000000 | +| rollout/rollout_log_probs | -0.181073 | -0.181073 | +| rollout/ref_log_probs | -0.182800 | -0.182881 | +| rollout/kl | 0.000000 | 0.000000 | +| train/loss | 0.000000 | 0.000000 | +| train/entropy_loss | 0.153894 | 0.000000 | +| train/train_rollout_logprob_abs_diff | 0.011258 | 0.013460 | +| train/grad_norm | 0.000000 | 0.000000 | +| train/rl_kernel_fallback_count | N/A | 0.000000 | +| perf/rollout_time | 7.401981 s | 7.559618 s | +| perf/ref_log_probs_time | 12.980901 s | 10.400161 s | +| perf/actor_train_time | 36.332478 s | 33.124115 s | +| perf/train_time | 49.632797 s | 43.838564 s | +| perf/update_weights_time | 35.229457 s | 35.257555 s | +| perf/step_time | 96.997870 s | 90.671674 s | +| perf/actor_train_tok_per_s | 9.000212 | 9.871962 | +| peak_vram | 64363 MiB | 64363 MiB | + +`train/entropy_loss` 在 candidate 中为 0 是预期行为:当前 `entropy_coef=0` 且 `linear_logp` context 激活时,policy loss 路径不再为指标额外 materialize logits 计算 entropy,避免抵消 `linear_logp` 的收益。该值不用于本轮质量对齐判断。 + +## 验证记录 + +已执行: + +```bash +pytest tests/test_rl_kernel_args.py tests/test_rl_kernel_linear_logp_integration.py tests/test_value_temperature.py tests/test_metric_report.py -q +``` + +结果: + +```text +41 passed, 15 warnings in 5.51s +``` + +已执行: + +```bash +pytest tests/test_rl_kernel_logp_integration.py tests/test_rl_kernel_args.py tests/test_rl_kernel_linear_logp_integration.py -q +``` + +结果: + +```text +26 passed, 15 warnings in 5.28s +``` + +已执行: + +```bash +bash -n scripts/run-qwen3-30B-A3B.sh +bash -n experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/run_one.sh +git diff --check +``` + +结果:均通过。 + +图表文件已用 PIL 检查尺寸和像素方差,均为非空 PNG。 + +## 复现实验 + +本实验目录提供统一入口: + +```bash +cd /workspace/vime-rlk-integration + +CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \ +NUM_GPUS=8 \ +MEGATRON_TP=8 \ +MEGATRON_EP=8 \ +MEGATRON_CP=1 \ +ROLLOUT_NUM_GPUS_PER_ENGINE=8 \ +ROLLOUT_BATCH_SIZE=1 \ +N_SAMPLES_PER_PROMPT=1 \ +GLOBAL_BATCH_SIZE=1 \ +MAX_TOKENS_PER_GPU=4096 \ +ROLLOUT_MAX_RESPONSE_LEN=128 \ +NUM_ROLLOUT=1 \ +VLLM_GPU_MEMORY_UTILIZATION=0.7 \ +VIME_DISABLE_SAVE=1 \ +experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/run_one.sh \ + candidate \ + smoke-candidate-tp8 \ + /workspace/vime-rlk-integration \ + /workspace/vime-rlk-integration/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/runs/smoke-candidate-tp8 +``` + +baseline 将第一个参数改为 `baseline`,repo 改为 `/workspace/vime-benchmark`,并使用不同 run 目录即可。 + +## 后续正式 benchmark 建议 + +当前 smoke validation 已满足接入正确性、backend 命中、无 fallback、TP=8 跑通和单 step 性能趋势确认。若要作为主宣传数字,仍应按 `vime-RLK.md` 做更长 run: + +| 项目 | 建议 | +| --- | --- | +| run 次数 | baseline/candidate 每组至少 3 次 | +| step 数 | 丢弃前 5-10 step warmup 后统计 | +| 统计项 | mean/p50/p90 step time、logprob time、peak VRAM、reward、logprob alignment | +| 口径 | 保持 TP=8/EP=8/CP=1,candidate 仅启用 `linear_logp` | diff --git a/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/logprob_alignment.png b/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/logprob_alignment.png new file mode 100644 index 0000000000000000000000000000000000000000..3ef756d5765f85fa5dc5fe16a56ee1ce72c5d451 GIT binary patch literal 51951 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ʞ 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 jr;spp!KHHYGk?x_lsFR3`7USHO%-qbMG-%!$% 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 I`}l?b0hH&&=>Px# literal 0 HcmV?d00001 diff --git a/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/performance_breakdown.png b/experiments/rlk-linear-logp-qwen3-30b-a3b-8h100/charts/performance_breakdown.png new file mode 100644 index 0000000000000000000000000000000000000000..f0507261a465948aa577aa1a504afe3ea8653cfe GIT binary patch literal 45013 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

Eb+6Y zb9@gp3-3_0n0Yac$|m9S=W~Y?6*GgJW73wo{khHh$EzWhR|%^AG?=@$(l&Joqw8NQgRFo|BP4DGtaG_z(7EbldX+4 zgQJ3OU;XagGn#oAv9rO}IWZ}zCbdki$s%xi$g`{R@sf}S`9hG7Ovlo_Jx0J*DOxbRd`DpqiR?~{U(vO5i_raM8wJ!GJ`i$ZWYN(X;k zC>&SOK$IYyIJ@FD#fjF1lbiP0I7nS^1WHTJ9o5dv^BJ8M-nOkAZ>#PW&%yWkfR86a z5D34{G-WNxp%PI>Qy;0-f&2oC3ExH5COKIpK+h>KK0aRRMX3MkU+X<*!l#=qP4;D~ zD5hi>v3awyw?c!-8Yx=m9QF8dpyz%UuxTZ2X^#P(hJIU0`6#h~Qf~Q$#o|)rzVO%P z_jimSQWSpwczN|raJwk(Yu}ZYN=a6|Lh$h-hLB4O zNg;fkaNyol@?Yyw_v%1ZAM%{vRy!Fz_nWLd&JXoZ!BHz<9m5Jy$#?d#118ZJepXUy}HqjuzMYjX$(>?=m>BYBz zYMN))2q$v`3cSPMyw!AdtrB&!Dj!JNM}O+;vsT)>Wy_Wj8xd$ZiL#cpyL{U5!@(n8>>2ydXac;3X=wSBX#T6pmeeo3{meKwzf`EgFbs{ygeYP^ey6A>BGWo$DWm* zo}LQy3wVH1yTau?^Ri|Ztd=S7cSmq6r3wgS<%W-1>q2g|NlHa^$G>}b`5mL-f!5aV z;8SnndGiYj7G1>X1S&h?zva-zY z$a&<=k%@^V1l!IEs!B(W>{XHKqNd*3iD$~%Q&Br^JaA*W=hnWK&T_Xi`yk1K72)Q- z^fCj=S9}&zFzn)^nrApqA9N)yem&EMog%hZRP+{vw#eAnclFPVZ+vSHFyP8$ItA~+ z3nCvqdfg=f^0vx?jb-G{D?|Cp+H;QG`20d-l`G`zy-h$51>}ut8XAUQIyyFyolt>Q zH3U;|82sht_s_9(+!kpv`o9S4*0bD*j#*GwS9iyVDk!*V#V@;dA31X5=7$eQ4ByiQ z3Q4#f?x`!=-yItXZml{_qbYF-#k)c#ii=Wy1F8rY(FV^F{ir;LO+9$C34 zp-V=fRj3_5{t;rK$bR}wWU7&866$%G+=LR{Fw9SpU){VHXFLt zE0lcyelhal!&kd+vZn`KB2r#mUa|>TJfSMP--EB$fPg;*rCNPZ5&OWgyS=nVnZ%4u zNU%(|Idtd>SzpncM24)g)&&L1lFUW!514E|J+FMIOnzY0ZNs{C?RL`P9usj;0r&!X zz#97{{eCYwzQTVr&l7cKUc(H)m$-o~)YjR`HN2vtq7d?@S%~{+gLkzP#b|@~9*E)g zOw=SX*8SN%$(l{|sivxvP9E}o%e?Tl84=XYn_qnrwFa)^*zBK>GB{`#(`1HKKsm&4 zf@XNT%1P+*xBB{10q(-W!V($VZlkKht!IJRf_aS>C0*rK3DWck` z#zs#^!A-mZiad@dawd1uW@csxxqz%Vf_kloQ14#z7D$~awgC)o1Pc-7IpabEQJ_}< ziRH_et3#-8gjQ*ZljJ(abZ+nIX|iB$vVEZjK@CVgfH*3U6k?hP5lm`T)Jp_z@_78I z?JV7sn3@K5mU*%57T4}GUhpfd140D3(*x?VCP>_J?WRpl?EtQ*7)@0i*pTNQba>&i z1Syf{#|6a}4`dy92GCL6h*PylI*qJm(MfxWW$c;W%ndY#{dvcBZl%egp`j|opIx*M z@89p~n4g*hf62(?Kz`33OL)tnTF2l%L4t3SlF~-@eNHMvS)^YgpA;8GxIqz#x~sC6 z-5NUfFexVDijR*asvXyle=KpRKkM_pwbcM3AD4vH1ERGM(vc@@b;*4kECaZVs*D5l zqTjqZI6gkUU0nR6s7XmVzFi3|ao$TkiGr^mQ|;$bJa-@hb4fcTqR7|^)-e|Xb&!-} zd`!+F_`^Q_%1N0=(SBcAT8k1ps@0RNhetrrzC+ zM~qad$`du+rcMVU3&Nu?4Qz$x#f#5zpu`=!PXOpg=uFOim&9wF2l~DOB$E2)S%Z}; z+zh~c)9U=NGeyiml}4)|WCNHok^CujxEJhlSkEuC4VQ?Pb)uY9KXvMX;=k^L@=h5w z#-IX#&ZWX3=zaf*ummYj@hiBM+>5G|_-kV}4xP z3zr7ophtmc2>{6fif&g;03MdHsj1~yKa-;vab)efbpkYe`x6KpRa~?F3i!(O8yXr^ znhqX5yhBXv1pQuca01wK9|`O-q)R##?C*AY`Bw9!=g;>qTD>TXC0`uVV^5cgNA3j0{huJYqd~`>S9=%Jgsjb}sU}Bzx zEdriIi4IxugpN*6d0|a}av=t22Kke%$ccgo4NtXw9#cW-rd^_TLk`}d#Pvu6)I z3S}v5Qc_9x>GUn&72%TE0mx+@43!>d^QZ*}2dHT7#)~d46t5YcQjh^^}{F z^K5As0$dF2Nj?&Ca&jhL8*86(Z`zaqwU?$2^a|OLyAdSU4!B3un~Lnz)#;%0j5l~A z@3p1fk}i5SoNu3He&YD?GamgJ)q|&!C=ge4C-r4 zdT-IQFB2iGiRzm@8luUnjOTLVZU6ks(D2CnnHi^bybA`R5q7ojZdF!4a^!mP~NGm2h1YZ>(R$}fCAL79s`o;D@(zHD`S-Mgdxo>BC8pG%OI z>cHPefS+Pl1t*n&`0Dlc;vo*ut>{s(5Do{;9a%4V`T4)@13Qs`SV8|X>>uy#z1STk zF+B>-(vg3>D#Gdc*ViiqXsR;b{wi3ggQx?0Nf`Vn@c6yaEXE^Gx2V|xTDY{$ectR@ z%3ZBG&(a5?MHc{SEs|;Ug|@;j9Br)=C+_8qvo^_sLZwHcpr{LxW%eTvSEkqLClI45 zN|hvk&}&dRg4wp=W?9f*U{~5}ApiL)zqag1LgYU`BZjbSM9H7{UYI}CE<(Z=oJHEX za;Bx+0rQSSu-4hkKrNf-$FIoiU?BBpjMwZfmZ$ES9~(;WxOKy`LkLgLG*k4G5Wq+J znzVpJ>|JtB>gtBTSo!$gy|q5Z&;e{1RVcEnPWKJ-{#tNu;ZL*QSa#Fh0}E#V zbc!2!g#38DC0U33{P(Z^dnNuidt&@z_5xXm5dI!B%$06ba(I6oVU9XVhaubnQX5L|yvlce{38H>&Mu6h zQfXC~sr+CKml#A(Dx?NSA1gZ@CJLDTLZ3>u|{UA}xdv^FE3my`38<`018$VtgE$5I0q z-tqPIy-n&P>)SGeO0>NlAGife=*Jh(4xfW)@499<~h~2yQ_z z+QL!TrfJYSG`0Wk-Mf*f3Hw@do6eqAQo4$gib}h3 zcy3BklA~E$M~5lzCeUw%K6$?)Dp)z=Jm*O*E5P^e2Ymd-qewmq> zP(SVm26m+>1LI0cL6J1TDdHebO$hNjXwcF%k+7fn&a#FJyJ&Ett2sENQR8A`mEdwwX@V>Tb7;EDF2#cfx4}nBzxNOL z!g8tmH+*3Xofj7q+n=^<<;oLSs;CU#_NXW-cHW2GD`9*uGT-EU+qlcK!$D6kbhMw@ zL(mMtE6<))ws)8o+c(w_JR`h)yBdvltxCI}yBHQN*3u6!3nD~FrJ1|zs)H^C(ZDP2NZ>hXtbS&WI5aXq3l*Sb;?i69^UseU26ZF$_=6h= zbM4+vDqxl}-!#T)9*3_7f$&Kp;SO3(XY~lEdMwGXh169-5%cksK^yRw&JWmzUWO7H0Ecvxr;O@9t~P z(g0v5mLeRt$F_OnhiCbI!qUSis+};xDhZ5)2UBx;a;y&4RO!bo@E#-R%Q;HlsuB`= zS4HZ5$U;}>lqi(w=-xs2IbXi{ryoG5Je8A@UWNBLLfhyGnx2d z)289X8v(i`+zRQHka`@=M3Vxcm!=`X#;2rc!H05NZo-U+860;NoY(%qXtJB;{lsw< z3K@R9!fw$b=62_<+jGC7y!`y*9w`6)tN&h!*=_Ql_rxU`<{x25Q8eOBhG2mN^q5%n zL3Nx!W>>V3fOQat&j}Pnq(%hF*e)xp2OXQIe>K^Clvxxg4uAFa}evf`8Y|nVhRU65pa-@>7?}@LD3{q7RH%fsuUm z?A{B&a#IN_?g-TDcW{fq?NI@WsYXa#o;_tB3eZRn38>Rjv@;28-MRyPO9q#|UD?9V zpYGJxc)4W&oHUK`+q&6X?Q~h{)p!=Qc=oqKlB*wsM(GBM*b_h)R#!+cH4uf+!l8#Y z1m%?KiXgCaQjq&jpreYgdP6LR1}r{VFR)-R=2vu9M6PztU*w@97!G^hJ;fj(O!a2-Yy{UcJq`URYW93Wtr#f+Ygi}Qock(p_qn2 znob(Hiq5(Kmm(kt(n#lFK%Fjlu_%_>?~^|Avu~jY?OB!9~`QZ?~W74V7)0bP&>I=d{Lru*u8HRo!U30i4h(8|(A{r!=8RS*gt7Wr0 z+f^`ZJ}X+_sJ|J`0r+c&z?U;U2JFhj)2U+Sl~i{eeLYQkxUQuJ-|@pKT2JPb{oB8=!@hiUa@?68bop$+A~}O>eATx34*st*CgFY zMRSg3a}_~>jjGQ(isjePyp_!AHH;x(jCDF-e)<|T?0j49X4z9Jtup4QDUqi z)2EOYjV)VVzi96{W0`Re{by(qvc!uZK~xdF2t78A7h6Y*mi{_>Jy(IlC z_P?Qv{+mnt-z?57mqr{$ph&Ef^mDZV#JZa@Q-~xCTH7U!g)jl0%$Y_f+%Abm1%LBj zXGK>Qe030Xr4!AYa5PX=xc!OlOjKzZ^;GCQMcno`p=*oKg3u`fv-b_BvA2N)8$q_v zjH#XjV$2&lFB>1i24@;_!5bHGAX*k-!U<}yOwKxt^va&7%O}$hT%%Zo;2-I!vqVVc zVam>)hcjx;^J_VXBLXxGk<5uG3h$I3HPo#?i+mxX!pp)>Lz=pvSAgU5WKSAQxAC#d zPipBsCa{7~R3SWbAsb+ z+Ql59jXE4^=?=Ed17&6A<~+w?4$s%)r4SsdVt|&tTA0=ZX^_q; zp^t*kPqS{4fCzV#4huSI^-b=7q?2LWDoi6XI)Fu$1p0t%YBCZmX8`(0sqBbY)`MOc zM@vjC!B50k*C3^nxB)LFK~W$UO9VJl;eyutV3JC+;O!eCy=bDQr6=J2CFOLDp2Ja) zFx2fkc6=}i;S+p=}>J z>;OB!@tuG(xJu&Fr%%gQuMVKLcXU7;{P6G=tOV*B8Ui%9nIMlrYXp4?j82dGF9aJk z4g(snr&KKe6K12MGC@JaN<-gM5DezQMRo@d{snP~N<+~Al;mXA*4Abi=4{PV3=Rkn zoL+rtlL=ga3c)X59(eoqEzM%`{~ovPkvAOcD1FNP%P%G~^B$o?5dtuWwKi`t6xa_8 zhNJdQSQ+{38(~9sWv$W@ur72mJy*?T>@MwE7o4OR#T5 z1Ns#IVVw?#V#9_SNp$Z;z<@+kKYcn7UpS=p8DBBI$3EykYl9F9ORDMwCnuXu$_%c3 z0xyFqB=2fePAO;+4E*Fp(bVwXQjX`P$>VS& zS`beS&O+A6+&N2&D;E62X)B&B+OT#A2{~@m$Bx+{BcqEROJR+Ieu6tlWePWqu*R?_ z=E7uyfMl$YU6X=V?Pm3hcQ};({=ui$6x#? z+aLg=hM52#r87NylayMPpQM-Y^N;gsl~6}gpeIP~VuR9hELgQK&7#K~XNwCyCaQVM z3m1-J=lW++{8D4ry(Eh{# zv@a4O5RBhITF~5z^$#jhs5DMaPS@#?x)IVCjAV3IGMkD>M6A!isxmd{61kuPP@+^| z7zBg!7+nlbNk*u&7V!AEM)lmZrYA%1gw7CGBdSh6 zVgZI=IR{5AKsU&|ozO>WF^Xp$ECSGqvza~8q(Pnh3|sLo^Fsc^Xa&L5R$M#>&>@1K z17$3~7_Y}sQA%5mlfQ#RK?x|1E!}7fyT^HYAC1EtiIrLtzeM zG8d&S$oyL=cKNdX%zFKc4}VusIH|7w2-#B+1;&p7OCcGgKPM;4XZDJe+E0F*2PA@% z^!fsKHKvlDRQ!;;;XHtJJo?6Dj$m{Z@V!a-z3lbB;IFUDqLp)3U>4z?(g{;?^X5u4 zAw9Pebsg@s>76TJywWf3q7WOLO?KDTX>ip`LcRp%RL{Lts+50TIjqFQz(j&B*d^5C zcc>(PL;lwwUN>`Ck|C*lJ-Ge{)mgxAPa$q3A|Rq2KLHl(;1mqbC_{9pvBxMbIJaTF zGKWhotaHok2YDG^q*9*wNFiOvke(o8;F*0b7b9U=6ed8$_474BnTkVmH&y&qiIV0# zpiis3vsr=qe&ru9ca%tw?Mdx|YuI_;7{}}jUuw(zOljyC`9?ac&{BVtcwS61AU*j- zX=YxXyW5CqyogBj^fc2lURotAc2sfsjU_gyl;~iAd>!VN>E&J-dh$&kX-#^X>xc!d z5{6u18Vr>=X=qm@oG|I(q|(spIQZ{{0DD%!+@zP|lyqB?wSfe>3B2;;sZ+7&b%Z#U zb5a^>fe~5GQs3UJ$;r84wi#!D4!czQr?9z&xs5MNr2#9!N=598M7Ziic&S6nFAO>f zfO_iC5b{YcrmDkgAv7(LHVmC*sar&JE4| zL+*7P{?rC5R(@4w! zEb~Ek4;I+}i)NRRTjl^l33ETBMSp5cHTBIxFxNp_CxLG=^$P#KTNz4YD>j-KToy#; zL9(Pr$)8^D#)Ccw^5ZH2W848UnkJomFxsq-`tMQ$Vu@m9{YjM2GN-wx9^o^jL^*N# zbR3Kv5B?|vNyku=BZ7kgdqOJ|4f!M*lMwUI?X;GY^C4OrXzHMG2(Z^skvxR9x9jMH zk*N@vy#Y!w!j{o$Polk&2`XgP63~il#T{~1lvUjOk9T!- zNwwe8Z~#u;xp(igoSa4LA1ve|7xW5aM5@j!W-!G0*m?L$_JgJ%tcX3&SKn|? z^b`8}$)s4uc_Y2s2#(bjveLab zlV|RdA3x%tl1&VOlUQ&9*T|B2BWv{L{`rXvE`ctR+N+%WU;6s`t}*87m*E|Vl_a~V zTKzJ+nh{zP8ZwI#!PX*c02YlnXw9wiuA<0!<%)dnQMmYakQcBD98pr*9YB^m`wRUj zB+%YhymN~7@Fm_AUKrB0n817xv00ZcaSt3MW9x{|SXFL3YEpWRT{yEDpHCbDx$qr< zO)E$9;Uid`eI!WoBFydFr+4o6JvM(DMbr^`*u~GwNaR*&TDf8O0s1gnqMdZ|=U$fh zZti5mz;zNK5Cs{==e0I-1}oEz22@}Xf}RcYZ9;Z-c7OjCZ6#2nRH(i#&jshcJimN?GzHaPX&qOU{Lg972fgBI}VQ;l@K<Eb8|svg zBCzcG$iHN6{1EOs8)Wv*H-kGRtbW(*1rMYEM4Ew_`=5T%c}522WO|g2P1ajO_j;I= z3B>v*TYw&IwAiFEGcz62Oi%-oVQlW`5F+BseV8dgnDfBkpN+;AbkRS!%v7HJxHFu* zfeSj%)EE4uiZ07Cxhe>z#6K<;(5iedDpoYX3!cvNx4W(k=?BsNnebsR52` z`1Q$f0r1;*z&t>0uQV24Rd5{TRnLzkHxJ~-em@op<{FcZZaq5!cgK3sN<-JSxX;~p$)sn1_vu) z6QkG8Vi%?Y(hk zKg_<>Q(|du%0O@CqNS&===0eCk5+2)H4wJXl$nS-fn?$Df1=KoyA>xS6_zXSPL^A@9CU}XjW ze{Yge$Cx%1%XFC-J)UBrvMy9y7<#imseQJDkG^_!DNP;qCT11!G^WY;V-6j>*ga{Q zF!PZ4YBXXAM={FX_}p!mAeH(Kod>f>GG^#g)<2ED|0l0Z6srdY4!-wVsdKt1UL7>H zE?GPMgLyEz5@>X>OW(iRB#Z+>cBgnwC!n#6KuI3=;R+k;O|x$t=0$=78`tv5aaBR* zXj48^5?2-ZX$M**A~=tK3Rqa=!vKujM0ZTiV;0g=JVNgwNDiSMO>>PAPdETY2pbEK zHWH9Ob!e!g%vn-_9nQdXx2-fX!jm{h?4zg15KLr8I#uuDrz;$pmbsuOq`uryz#P%% zDhcC%kN6^u5TG)$WV`2b<**!br7P*hNi)|-@RLDdss_~-V_k@}BEfg7p9WwbKGVvc z^rQ#zS#Lz^nA{N&Sk;L8n2*zJAF!^TTVFb<`!MFyrF_=?1092A;jz|8XP^dUeu?5< zr?vm`zl>iz7r9Bo>Nw6Bfz$+k9}N($A$DlAJS(S~midE2JA8A)*~PyT_$rv)RZX~H zgflWZpbi!9NjUGJr0Si2wTQ5qD1c#H+_Y&E!xMJ;cpPoxKHkwg`{u&>q8TyYKiDX& zOLljn??P1ZrIKgMPiR6s1`_=GwQIyGSL_675$y~2pmJhfEJwBp^fQcLWOf_Q@^8U9 z)_Q0E_B1zzZ>7+qz-tl!kMfle705>XU1ggnBfl&>F!%Vac(WFjB%|CU)(1==PJ`_R zrs}u45@zKQ^2|Pi*_Tmd{S@a%V9wQXR00(sd%%|cN&{FdC!P3zBlrT1l(|=z?;%%} z6J$Uae+Z1yc~3*70%xR!$@B_-K36LPlk?BJgC&$qQ3V6aP?K*f^3`&1aBy~Bvg98` z0A(BO0X6&|od;T&%^QQ23w&1Z>zKhsiN2hZ<5s`=Z{eva{PXrR`Rp0D1jUz_uAxN7 zkRrllV8&{5fow1HUqEMO?Y!eh!JxUKv6N6JU;ihN?<==hpG0|v;wuq6^!({jorHF5 zxNpbHD8mP#u+C0`#e>51I4JVwQplxb7uuvWFnf>_9S$O2koGeIIfy_GDV#X2Fx~Vx zC|Rar~VNQ=r@t%&=?D~666A!vj6()uML3ldivxt zAb_qC>dl*4P*hZOD#yt>!I+p6KlSujD(%%w)@^8?{jU24yJz0fNEZ?jiOeyh2^zD~ zddjJiKNqc_(nzmu!B4Fq0kwf_2xUSngtKo&jd=SByY~Vamj{j|$)6O5L3@p_Q@dcP z>lq}AUB1aLkJ9-KWcwT;2=bHlq!~3f287)E4d>C;X^d#94g_~?If?C)W4bh6emWyi#499$yXbq(Y zh5uY}pDYdee)z%x!};NRMom3)ap3WhHp;CP6?0$Bv~z2Eyw3tiE&5Dm7m!Jd z_ySIDY|8L1j5ni4VaJnJE$h4Y?+egi#~=9D0~gv z01flq48eOHP`yj1KSI+Lt&)s$%@_w=AV9-VfrA0Hz5OHD22vkpL=TzH^!{s&sT9ag z_o%4nkI>PLMI~)Ydz$JM7i3Kw-A_p)Q@3fAFIa6F% z3FL+ZZ*OnSV)ETE21?1I^A0dAa6F%S7OEpCO2$i^?)QO1apgya2UIIYIQIl%MU}*b&61-2iI4iyVLbLS|GUS zxGfx#n+U%xOF1|&zZ&w+9^~4NC#5KxFoOWrDXFAr!P?oNfr2Pl-tEoXw;Ks_&AEQP zY2GN*2&u#&jQDs^3) zlGMQoAHe|S1__W*mv``^9spL}8_Q{MM^pXZ!kJ8geFUqdGAw$&5W7rb8m z4<*|6&-*Rj1rc44Sh3Fk18jg$c$%816rI)YXmOJ9U27iw@X@lROI_0pa&57!3iCrg z;9;y|gZlTsXxEI-%iTq%wXr<6@0;;S8)tp6>wGIo1zqrV8*!8WglvHm#$sRl*94-d+ z5-&@K<;F?Y7PZ=MHNR_{ppX$GMgU!nkg;A~rQqULP;?z~w!*DmJpRusDc9)7bIOZ}JxJoH1)~|Nb-K zQ749MPgw(=%#nqXgUEX_*EpdY9?-^xO?<~A@ZJ|+yiAxC2J0tbW_Rvm*ukF!kAP$7 zMChHUAC1MLmE9koQKnH67&D{=R!6-b*9S6Oqo@_XQSCVlgKK`Ueu3XAPp`OY$12;O z)}yYdI3*#2REF6AwyFDTBxVwDGe+1=vJ6)5$gceQOzf9G*=0?CFhfvDg6hhw?4V>a z6i&}Ah>qat((bi6lTckFgsSm7C28P}%ht9~Hc@)L*dcgOs)h;8eU<06YS{C1LG>kz z0VE4G)tVzKDE&mnVr;a4C&N{%gvx9d3k+mjFEqzdmM3Qma~SVb6oDa181J=>sUIs< zB8oK9PcEXmj6kPMdO>8C{ZDT(tfQ+5V#OKq;?S|39?&o3(y3lfxu5q>lFGEt7sQ-? zft&{rkKmfOVn-RL{WZ$77@7>q8)*!@VTs~a=8ZVGek0AaBx&^MQD|qNW8M12lW4x$ z|L}t0Z3RkyHv|7}i*7B4eyc%T>kp>JLU2U<)j(tol1sVF0Ohj4!63(4e_~HpE!MKm zO`d&GS)hQE>~sX#SqPOy#eY2Ac-mTq@FZL#dsR9GS0ca$kXk{5BK9;4RYypb5CV^C z*689G?P)mgg*Z~ehN%elDhH5v0L+_D+82!kEzv=wGG*965Mc93s1*q$MLmU*2=rZ? z2THrl-4ir)ZpIApkv48^?q{83Wr9rbrjn_Hf0U0+P`Pjz0 zokhVXbA%QSDIgvTh@`Jd$Q{Wf4$S@(VR#)Rxx?9YZSs#T^IudE`wu0=9PpVlpgO9n z1fG>0efm}J&6yaM`>3cw9>Z7|qo?R&P&`npy&TWZ2zM_hH9`qdkY*VoK-_-KbWO?} zgFbfrI(2YcrREYSBo6`sooY6i4+U4KMgSq=v)p9{)22?9JF1$URMPve#U=Nqo11S2 z=Zy1daPnV)y|CoNtRGFi=f-Yuqo+Dq+`MtqCV_~R11R_@<2yN$qyc8GREcaGXfjJ~ zkPLxQ(lJIHJ9cG{8RQRd+egK!B7x%H0$SH5qsUTxF4+|Y0CzYuLFF5zL9+sgR_bcGVJ$8@yGwRO(cBKf47PJi=iZY zm?ET_jZyfQts=#L$!oQW%;bWp^T%$ddB0mxF$jA;T9kv1g+|-IdmfgBM)3V{;rz>@ zU0E=)1naL_c;uc@Rr!1;FY_H+x1RX4`(BfjHKQ(iipTaSKF*#N+rpVsY?Ea z4|G#O9vH~8Ln*Vf(jVM*ZH7B|-;dYaXHHs700E5o#A6KR4u z?lD+NtL*^X)%$-QaWYJka7230UQNVFbL@Be4ibe4k)H$5;EX)jTy$?cZ|V+iO!Zx^Mms9)mQ{^|mQii!%iE0emGq4CYUer#DCu(IpI zCt4EF`^@4$Da^vhS`pwe?ofB~0)no6I%pn`kJr$Z(`cWav*S(db8>QIa+pARqK&Z! zgLJ$xTl)-qV^}FUfH+nwKJt9D)c+pWLGaf;A|oo_9t_BCd}ot>EgxJ8?3F`zqH6v8 z9#X@+($(@^l}Mv{Sa(?qJ|QVVY~8pvj%ELqY9`7mX&yURd2BERu|zOzAG~0%0OM>b zBWwgZ<&W)Q3p^tWkip8pD4t_Y#ysWG9yV=Xj2l7|;Az^OqU$pqi;BfR*?4slttnL{ z&K~WFh+eogiK!_U1EJ^*L#lSyhrA%uaru=2A?0ozFz3&BxQM8|E1^{x z9sQ*%p=rN~uDb%v+PVbGC0i`by2dsQVUaHe4fTFXdjk6O{;cIg*BS88&Os|Li z;J7NOkV=XQD4PAjJF*1Zn~*6})O}~{ny%35eUz*JI6ny#O$Pg*r9D6yBd!JcSDB7M zjbVUOPUi9G9&QBHD5r#&Md0#e53LV)KGc%QDKyEFfgl=ZNeK|%kS8&QGNOzMU@qxr zGVanTW&vK}BhiUd;k0tm2d^ELq@_Cte?`cOm^D;a0Z8{V!{OVQ_U+o0W8(S--=}EC z5Gi>$q)Es;+6!;C4iTr^DAHa=0I$1*`Wj16vw9>6_5SyOZAAA-U9&mc${-m<%xO{p z6WQb42%Alev(h|?nMt?|?NFOa_z8p~l~qY>+N{Yv`>14WO2W0l$V!R6m5G$I*ESav z7v}fX{Ewx+!g`|^)L`Y0AFX~CkLWqxz98Fx%V>@5KuDqXz7=9qPF;bk0dymqgovKo z!Bk|3g&NP=AxXqEkeMDTaRzcmg~f09g$gKbLo_NJ%QLyZR{R+F+kvRyS?JeKhM{uv zkbN_|HSx{^$Bf1>4CjU1ck? zQ=#}&j(VxU|BvhHwk_!<_*a%8StVDWrlYT7~A=dzmL0OHqV6 zmd;}J!ql*3MntL5La6uuO1g=}3aLdrjK{<%b~Q!Mlqo?V)=IIcl47a}Y#oYnJr*>gD>O=Z|+mvyoAnv&h-8 zL59QZG8!c#ZI5i|+;5 zXAQq(yR_%f&Mro?P3*gy9MJ#BG4@LQzMNq@`+i#NU*VI5;5)I8i%!| znY~mV-v^`TP^p3N^CBK;-csSF)L^q{0&n|fe}!Y`S!oUZfB z;~j?~BWbBmNX|yCCowD{p9VESxW-E}$eX-JkV=AflN|4` zWnk2DgaibjC=0hy&G??u?s8aZw(xN!L|O~n?=&96nVv&YOaiD zt!r7O7WGry{9CK+nJm=*rzMEqvQ1@`6V4_9?538vMiAZ>Hs(#ug&KSqx4$v#pzV?B z=CcFOjR3$l_UW+B!g%eoQ_4SW<~GH?QPFRA&C#|U6#1HrN>?~7bKQ@nLuBB@v=>(3<%4@41&O1F}vftoF!enydMj zsD#AdjS??`)9D_sokwDo|FyQ6#xIXooVtK@T%6%Jt=#Ho8vnq|MzJgKkNEP@^aECAT!`zEo_!{T9|AdpHFu|QUlXq1=(dkBZe^5y^8hvQ5Fg|@ z@u@%QUKNbbLuyKBe3Dx2L5v!DFp&J1Or}UBp@KuQVP!EufA^MqL)UI8a>_;9YMDay z5zDV7ZWTGXnOgil(j&8HNuJ2enVDl8Jbrk>1@cJrsyWa}Z)_w2tO$#k#o`e4^z_{5 zIAfRTVKHl|*&@Cp+=KjO&)6Z%&6ExA+iwqH2SC_8t$n8+Gcj37WWbVhe>se%wxP!& zz8~{Xg>VrqFB^dKF-N5FopenP(c=;wPo*ff14$Cnv*9 z9jAHLXUcm*y?Q!Xbd71wes`|ihk)l0FuG7&T)Zy>>9m7bRIk{;03YCq(ydAfc*lk+ z$}~UR)a&Y$5tPzV#(oV?fk3Ad;#T0sH)*7%Ui9>7zVTS@v+bd%C<8=fd1oW1MH@8t|d zfB9yYE`}!Yh|5A4$dwqU^M^l-kV%{J!Y#|82n(Ac7oX0bG6K_DMUb~+mTU?Rc>EXai#Y;Ec(szwH0uOnF33^gv zz)evHyFI!+>2!U*1AdEa0?DkZ!psj!b~o!$h%qrtfV*;}9-!ux;Vm==*#NqhzJAag z<>(u>D@B?`xr*2L3O?tp;o&hd*@w`aY`;gh&o=>aA`5Ewn#?6bf3}A*O#GxGZR+1Y zsK_a~x-s0u!yJhk@j$s^21_c6=0)PIyb|bF+nZX>>uG%DlFjrkrlzBu3TIM{v5H8( zVVMG9MlU`c!PY>COeww2JgJ_FyyiABnL#A$EJ!;Zm_)ypL}7gSDO`m?sP*#hSIgjG zDsTZzM!Hwdha!{FuLsS;2+;UOUb=3b!lIcwJcx*5!jXj0JJVcLotWbY*f5K8FQg>@ z26v+d)v2TnBRCoclV5d<(9Pm)g@ zgZpLL8JnzsXdS~Mi^^j? z+6O5C7;hUs=OrPuroAtnVz7}@OTG!q`)11x#*6g6R9l{%TA3f0S{0gAHWHfLka{4R zCDc=2WOr$t`i1#9xw#<_>F=mYXuI4@a%0<0QSUVIh(5`_Qc=kEY{pZ#Wx1jAjmjKn z9Pw$<1+0Z7eh)v_J7FL1-hQS}MVw8#s9@D1f}1`KwQ>1EpDu5{c_M7k;K8m{r*3c> zY&1|ff4}Ig!t*;8=7+S&E-fa|(YTAYv*k%92|#{gx?9=RN#p@Yx(KFm0qJZ}Z^@wn zMcS@Y4>U3Z!$zMG(>Q{B))c7)T9 z|2{}GE*10HL&Qn;zEvVD{S+Ev8JZ!vGWHE3d(Nw1rQ6{nDbI-tshE$P{04)NR1X}2 z01rifrZ&xWBxbycQl3D-n0Xm>e&t0Op^KJo64(pTKyFOK_2C2^Mv{J{S{m@>;c*A7~~sUaWCsg|BI8kq`;SNTGEeEzzp- zzZQ1s?%}X-m}^Cj84F7m&t7H94t-ZNqg4)^H+R{)Z(oS-Di=+nB_^jiDO3i! zn>+V2C%SbZmVHU=T9lpAZJ$g>qT%N_ON&40W9FJr?OW|re)YUd;CL5@AYulmvhasQ z7XaEDxL01ESFGzcuI_UCjG3@teyJWo9 zedH8%;hB@wSn2untyyJTi<~SV;88(YIzJb2{`;0EcB7552*)47BX5hcb?S^6JCp*u z@_2w-94FJW+}?-HQRU%&{pnCz!-k$m8FpsqbILAPomJ*)^6aSg7t)7JQ&YMIzh)gQ z!>N(+TB=8Wa#n#2gUp9Zxee)oen75|=T94Hm)iXMi*(Sy!E%_CNo)Ls84XOrH6RWv=#oFS+$r8@Kl5_|BxdDcnqBV1paY6I-jD1ThD!y~)D*FI z?*}P?dIYO}Kc;}R?@~%7nJ6~-_4ib8ay<>>iP8j6Ie6YJWcUHITnFdV_30<=CeO{P zvMkQ2%_~0THHT&!WX_?^QsI1l;_fz{VH6F-5M8|UMCQ+tl<$U+yQ=U2(^E(DgL9AsM};m)d0hXfjbdGW zNAgO)=-64QLXDpTA~q)9GRfV{BHD6G?Q@5{vYSVCZ9#h-{A`C{Ol*%P|jF0=qgdnSAKP!x&4qi*wbzOoqE+%W=WE~lE z2r(qfipaFxjHK^BXwWouECWSE9F(8TYz|}Z+Cxlkh!#)*@dv7iOiX!#Z}DMb3eRUI z&mh7P0OgSFNY49;gaVn3%?8MSx8+ogZoVvs0BL2@Xizy{W&ZdZZ@jTQp}_6gQ!kFh zK~RD+dmA}L)68*4CNKGvpp+JR-8!ZcTL}e2teb&vlPl+DDG!HyD?_KkLuinQYAYxE zc_vsK?_A6HUpt-wTFC^O``V=97IU(yF%80M=RF!dXAsHV40E#%B{ z2@mi4@O9ItqcYl#uK^g_q^zMB3}H*^&hgOuQk4VtA33O_J_b2^n_+5`7x5pCV^yeu z)OcHEO;3NJ+cK0#W`y-kZazTFOowm6!>io`*v^9)&Za5``;95Kp_gcq}k8Ev@=UwJE znelOH32R@yeDUHi^<^A;!Ka^rO$R9T$u$ggk$5hugPIdJkkMY=J89!4b zZR;cT&QZ>b{jWJ>ga!=nYhaMQF9S-ZET;smZQlU{4xcyJo@T8N0P9fi()QGAChHWQ z0Wh0JJv#Bgms=z^Z9`7K8&e05hfJ$r@W2`$6N_)?1!&*wP(kM>uT6$l6oScY=ZHyy zq2Swim(p*nZ_|{-n!X#U=z1Eh`A}jRfH6dON}i=1aFvic6;XdOt8`C#R(o(<8AP1u z1I)vWCC-lLYX+tt;kMZE3!7>y^X7E&KX+%_gCUyhSaxNfe~(W74!R9e<2bxDp zK|@aa(Y(l8+gvWP&e#>jm>hX7FhvS_DoIe(L4*4Bn=D@^Pn=0>M)#2MQzrWt`;-h; zN&E`lbhn#l+mJLtn{Q7A>Ey^;7FWXTd5W*xk5=Bs+Irsn?x+z+g4L36eil~ z6ABc{QV$Ac@r#wq@k!r~RcrBwnC;1nwu%;4ZS8fe^(p6dY%NVKY)y@>?6{_HZDV9% zeu(ejLB0b#I}B}YEp5d3`OW_4H~1{94fq2}o?754t1M5e*-$839+LkpqS;;dr%(dF z&`umxatP~hwRhQ||E*+r=-H&!N~5v1RgVPoA6*eWvP|w|Z6s6b73n@lr@LJC9!`s| zY-a9%wByn3)z#6HvMX)05{f$4kKcbg=R;Y!@V6J=|9|)>EOmNvi2vT(C+m01t&=8)FftmW|d&p-eC-rXJZ^QWcVkB>*~+ZtSY%g5A80_Nex^eU7n6vzaN`72&Dppp*TeFkK`x^Qtnk`gEyDBEA-nhsxYI?XdC^|#zAA9rJntE%yAijih#XJ5T)m3(()xZPx{y-7u=5LZEarGHQm zw}3!(QL8(nPn$`d|Ii`DLsvh19LR1@_V?fLwxp!Iq~z4&$B%p43iAhx=iSVjGUD3A z#O!*%ThB9QXZ`Qrf3)-XtsjMxZHoH(ou!{OY1P^7ZV7!U#ag`T85fQm@xVjZWf&=H zUTf43m2#b?wbp+!p2$^o>hW>UmO@s_ zWxnN#a+u3N~X* z7S9V66c%dMTMfV6%zE*4UPE!N|THkYtss_yGua%YU2w{cT` z+2}6)gA#Ug?dy*}K1GfHi1Lkp{`|u5@NlSz)st)epC98H_4W0QzP`GdH#gnKNxQd+ zw>q~wB7uojI48Gr@M}*`oK2OaDu2Pxzp;8B4K!yb#KxX(Y;06dy>fFfr(>O?gM;Dx z9AlfH;r+Dq^wy#z$I-5y2y9%^QfcZrmXBq8*wXNPCaf>H1&*5rz}XZqx4Ya_nn``)r;5YvF2`o3-|zPQk&seEr;ZOatFGC^b3sKV zaAag8!)3~vi9_lV-Ry*FMRApFQBe`rNYG&6q?YT?mn)j`>=K+X+p%J2&YrbT?QY54^CM3^z`tNZFB<9 z^h9UN^jM8tYo<8m^17HqW{W9dTeaM#^k)tyc1OAwJ&24PO6&L9dZ@qsOt#^tSMSTq z#l3kMqrnGs{$@x@*0r~Bm}qR=xKXA#%Z!O8V{M&Q(>}D@SK>;F?x!aw*Oi<2QbGld z=;c|7YU%HuU*x$(?Vz9K6Mf8x=arOfZ5xVk8KLbK5&3*yuf=(&kn7Uq%j+w=pFe-@ zbn1zqp5Qrs{imOb-Q8zqGE5qhJB;P5tWv)Y4OyHPXv#3^uvV&9R@KyRugCWHG0_+o zZQSh<)>9KB7bQa}obFASF#8ZDTB9@d?EJ&E+S=_-r#!>F#C$q2M`;!vb2CFJ+5K5& z&0hHbO+5mKyeaWj!3TA>%S=%yvW4j#+$$*_tr3#WV;G&Ybh@9Pkn@D;vK1@xd8Dtr zSy_lpPmgZRw{LDZshQp8l)z|@af+@@%@T5-b1`eqI!`CRC(d9GD)$!*!KAN3h!+~XXt_^^*s7|dSP@iAN{rh#|d}o;WsSffo2G!t{Iytlo*yp z*yQ?q&IMnNJUxN?WO`D@9e+IbtWDEDORGjS@%Qu7)xK8FoBjSu>RwLHw>^0lh2w#) z$EpzDMR-RM*~;(_$Q7M$-XOev8gI}h5q4^7s*!7q(cC_#GQ_JoKQ~!C&>UpSvZffZ zqPHnCVeyhB8`;^D@9`I1Yw0-d8$&(FRw!|iD=J$36X-n#qQU#va-5G-Hln?%g=A|(`o)! ziFE(*3F+CN$7lKwIg4C(eL)`a9KXfPVXn=Oy^FWE+^cBATJiDmakfA1 zKX8%4@!3A@`LqU`u^ZJ)h09D^^Xw)xUJMqDR(3Q$j!L|k`bwu6VNT}UIWMFy0loLD z>LRf;Q~Ho6t<<0G%s#HHth{x}^0m(=2d2%RN(3VLG~CzAFl(;g%g1+3y$gw4n7S1M zu`c_Hd0Ro_%9Se*P@BKEsU+0tFfg@^_f1(Y^J^7+)|m>qw&ruQMX^~*d)=_vmRlHB z!=dZ;EmkG8l&GCZL1yihWdYo*g>Dtpl5vJeK5svOQC53VS8sRG%%DvZ$C)!{`t{qK z`jW(otD@Wii}dg#=u~{ksGs+8N$Ao%{qn$DR9yt-cL=zHBa*p&$@yzvAMpD7@5MnZ z*PNZ5S=-&`3(K=UL`t{czI{7(Qj$YJ?=~L9!Np=7CG6PJBNWR`jkz{4j3vE@hjp=b z7+=+yyv+F{NGa*Xb4Y@*+wj5@%yo3Hr)Pue_bDnV>1hi)j(is@-@ku{d@B2>c-nKk-q?K2IMAItyy#E+EClT$HAsWAH5H^8B(d#EgWvY9zO^O z(Dx4v+=hgoRu$ASFkoidUR3yUh^D0#k?O{9>cDO|=;GqCmiBI*(LOKaz<6i)A=8^V6yw*GBgC)hDHUDqwjWaQpRj#cpx&W`u>qD#@3OytW*Cbzkq- zuU}h$t3qnTkU6eeR1q+3(0Gc&RZqLC_l}u?I%Vw3b>^l%>3YAaE4sQ&w0Fq2a!zba z@+>#b3LwVU$Bv#h^A1^JI6c-gVW#CW{Px}K)TsCFQ$AY&lineU_1;i8HM!z-EGq>N z=>ziOJER`3B#rECPoF-0R~atuZPoFv@p|eUV}=!Lxg=Bjl(_Puk9ZUFrOTFW1zr^p zHg7QmK-ijZKO~H(V$|OD_p&u!pQ28&Vw&HH4;AR@>FaOhH$bO(HbHegj7xU-M+AgWGA;4{oeXRwKHsSgy zg%hEY!B*n~jIXafy}y6g!|5U zdy%WRuk>6_n;mPPLzXgDDZhQQS$3VqJe~7wzdo02*hK@i;~Yykwrw*;aUdz%-q0bh zqH=Zqn(NOmFYN*uNTm}4MH5XXC5Z_W966cpPoko{Pq90`d0$p$ookL?btjk{QMmKY zV3y97TdD5zZXG~~LdT9DXUpp7?9?Z~wbGavymtRn#($Yq5 zOf)uD#jW%(&+FOa%&3%ES6`oNV{qJh`}j2hR2gY$0#QIK6J`LlTNu;hhX}^$Kz1|y zIsEA(W$*6Yui3}itjl@%%2C!--q%~Ybg9VLWR$OOOoUUJ5nCq5Hp{e1tC;;1`rMXI zMGb<1id>zK(?7*4bHA>ts=AnM9)3JGrFgDMs|s_wy*IW|7U0<%C_Ki@aj0;z-;PJS zXf}9eIdZxUWzh?@=!q~69UUES^}~XKwNlchn2;q03mY7wpn=2}iC@k);y#5QqMAtD=RzWym9kqN%qcuPj=$Xu4Z^zUOBiiZ0j1Kf}fCd$XF&6@&ORx0h*Jz1@hM{ea4= znU_(ShWbM$|JneZS~%79kgqBQ+g$;%j@q~?)5e z(?$h@#Z94>-LKa0*Ph(0Nu^CA5%n(JEToTG^=~md$cygYTDH)k7p&}jp)(e zzI}7LejCBKx-$(?=PZ3g8p=nwp%Wt6g08HxHgp4#>Va5vE#+u_mn1t zseL`ZU!F?2xw%bDs3<8JVzY;e*~OzoctTzpt4yTpdpn!>HQUdV*hgmVMVbVEfP#oY ze38ecr&ir`Prq7GJ=GnVf>~E#%uF`Cy17RA!i7iG)e1>kh3ddSQ3&R7NTae?rv>Fs z$BrEn)P1+s(b?GqH|%Z6O=q||A;*L*f5g;iU?5#3HPdf3WT6}8<>r#+b94I5n>U77 zzUrCA0qib6kJ|NAKb8HQWzjBG2*hgKoR!oDn99;rkD$&;L(+CDl(Sm9c5TKAwsAIo z)k{`4k@3R#5*Qiz$nA|ByDazgHl#!%x`gu~FoZMO$q>~DU`-M#f-1W7jnEJRXT?Cn z8r4e4$xF2H;%>^9bqk3-j`1cIi9^u1761LbBs%I8v!>gt~)halcyr(9l4;;7vAQ>v*m?j0}N2><$GG41FCzRvkSD4f!XD43Y zQDMSw@xJDZ@tReuK9T7Fz48OaojeK}Ak<`*ZOS%3KJIh{tw1*|wlOe^m)swF@Grlh zpdb|2wlf3SCIGX>pPt-Aacf611Dlm12YxcmTh-;HrlJ}4sg_@l6^@i~d&kGTLt$u# z(jzu8@f?=PM`Zg)dDay%;l%YT9@iDd?jd{P&@pEV|401a8`6d ztm2rznnDu0CbiYn96x+~{EVNY)j(E;&ATq$K#t6 zF6x>UF{s=g)cr1Wv;(8TS?6w^C^?;0)QNyFA!WBGerw$Ka|7)r$IB2I>l_QaB-_&s zd|x}ejS0nFjaG6U5~buUS=hVk8HPIZlU)@LSu@QCMv(QB(|N?i)WgN>=wl_kS_vvi z3T|#ifEIPBx~FJ9*>_Px%1wMaacaep%q8)d^cmke;{afO-(w=`j*hwIu0KKNz(OOl&xS?jG_5{1f)@iv(_$CLM z4mE@F+EF^;IY55Jz)^RCfzmW%(v#IZ=Vi}CQ>IA_yQI_Uv{o(o_Mzs0*x0?2cqV#u zOG`_KQ@_2p&g)x^r|Ql<6duUw{RN<)eCZMo&8aKI;8I)s7@_b^`JW3H%WM-fnLgzW zfIXGd)Fqoaqj!8}1~FUR5S}8#lqVuRC*xe~jw7 zmYlhK`Ep@%0$YTj@Bzv2ZPq>(hPhEG%PNy;LPNO;Ce_1Xk(4h=yC{o2(obU$H*DU_ zRVv(YUbl-kPV(EMs}9a)LfMveUKHzVCaUM(+t`MLema_2SCpiKox+sI`S!qC)Rj3e zXKd5zW5>!(H^&Z|cM(ilbOln$G>cb}PlV4HIF%uaWsA3S2bSs`M8UI+Lp9_N0`L>3( z$HRvY6Id+@QY%feTdeO%>P@G|wsQ6Lm4h*y(Eakm%|)PB1cm55Er+kmoIQ##&KNLzeB~x!-9uiX5Z(r=j*ftw6)RJ3Mk+j?+4?Ou7onGLF$O&)bhE*&s)4*3SLfNHqoc#d#ANp{Gc%JydT#z*+g**Qu33k)O7D`; zH-%Rnv7)bBxguL8Ja1~;HcC*3KdpVy3$}%KAAPZe`Lo4A$Ntn3pQ(xzDg$Xa)!k3j zY0U8CsZ-+qqVuz(1KBYj!o?fr1e2P+%Azu^1>jOvQRz*ul1iU{ImAOD#VLc?JM?GI zxk-?br|H<8tPKB5zuctIRT@bu2}mJ_V){03*kA~5g&vJ)6?tK$g0tWm7nfP9I(hN; zji^x$7ONF^25L}g2=P4#WE z7V<2`eu?8Ro>icap>8#9$+4P<`3|15PTyUG#crrglSZS_qvshz?tJAHVWNt3-b
    >CKl0>o1G5w-JK64DOzlDGoY6iq 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 " >&2 + exit 2 +fi + +VARIANT="$1" +RUN_NAME="$2" +REPO_DIR="$3" +RUN_DIR="$4" + +if [[ "${VARIANT}" != "baseline" && "${VARIANT}" != "candidate" ]]; then + echo "VARIANT must be baseline or candidate, got ${VARIANT}" >&2 + exit 2 +fi + +mkdir -p "${RUN_DIR}" + +export CUDA_HOME="${CUDA_HOME:-/usr/local/lib/python3.11/dist-packages/nvidia/cu13}" +export PATH="${CUDA_HOME}/bin:${PATH}" +export LD_LIBRARY_PATH="${CUDA_HOME}/lib:/usr/local/lib/python3.11/dist-packages/nvidia/cudnn/lib:/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}" +export CPATH="/usr/local/lib/python3.11/dist-packages/nvidia/cudnn/include:${CPATH:-}" +export LIBRARY_PATH="/usr/local/lib/python3.11/dist-packages/nvidia/cudnn/lib:${CUDA_HOME}/lib:/usr/lib/x86_64-linux-gnu:${LIBRARY_PATH:-}" + +export CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES:-0,1,2,3,4,5,6,7}" +export NUM_GPUS="${NUM_GPUS:-8}" +export MEGATRON_TP="${MEGATRON_TP:-8}" +export MEGATRON_EP="${MEGATRON_EP:-8}" +export MEGATRON_CP="${MEGATRON_CP:-1}" +export ROLLOUT_NUM_GPUS_PER_ENGINE="${ROLLOUT_NUM_GPUS_PER_ENGINE:-8}" +export ROLLOUT_BATCH_SIZE="${ROLLOUT_BATCH_SIZE:-32}" +export N_SAMPLES_PER_PROMPT="${N_SAMPLES_PER_PROMPT:-8}" +export GLOBAL_BATCH_SIZE="${GLOBAL_BATCH_SIZE:-$((ROLLOUT_BATCH_SIZE * N_SAMPLES_PER_PROMPT))}" +export MAX_TOKENS_PER_GPU="${MAX_TOKENS_PER_GPU:-20480}" +export VLLM_GPU_MEMORY_UTILIZATION="${VLLM_GPU_MEMORY_UTILIZATION:-0.7}" +export VIME_VLLM_ENFORCE_EAGER="${VIME_VLLM_ENFORCE_EAGER:-1}" +export VIME_NO_GRAD_ACCUM_FUSION="${VIME_NO_GRAD_ACCUM_FUSION:-1}" +export ROLLOUT_MAX_RESPONSE_LEN="${ROLLOUT_MAX_RESPONSE_LEN:-8192}" +export NUM_ROLLOUT="${NUM_ROLLOUT:-12}" + +export VIME_TENSORBOARD=1 +export TENSORBOARD_DIR="${RUN_DIR}/tensorboard" +export TB_PROJECT_NAME="${TB_PROJECT_NAME:-vime-rlk-linear-logp}" +export TB_EXPERIMENT_NAME="${RUN_NAME}" +export VIME_CKPT_DIR="${RUN_DIR}/ckpt" +export VIME_DISABLE_SAVE="${VIME_DISABLE_SAVE:-1}" +export VIME_SAVE_INTERVAL="${VIME_SAVE_INTERVAL:-20}" +export VIME_SKIP_EVAL_BEFORE_TRAIN="${VIME_SKIP_EVAL_BEFORE_TRAIN:-1}" + +if [[ "${VARIANT}" == "candidate" ]]; then + export VIME_RL_KERNEL=1 + export VIME_RL_KERNEL_OPS=linear_logp + export VIME_RL_KERNEL_STRICT=1 +else + unset VIME_RL_KERNEL VIME_RL_KERNEL_OPS VIME_RL_KERNEL_STRICT +fi + +{ + echo "variant=${VARIANT}" + echo "run_name=${RUN_NAME}" + echo "repo_dir=${REPO_DIR}" + echo "run_dir=${RUN_DIR}" + echo "cuda_home=${CUDA_HOME}" + echo "cuda_visible_devices=${CUDA_VISIBLE_DEVICES}" + echo "num_gpus=${NUM_GPUS}" + echo "megatron_tp=${MEGATRON_TP}" + echo "megatron_ep=${MEGATRON_EP}" + echo "megatron_cp=${MEGATRON_CP}" + echo "rollout_num_gpus_per_engine=${ROLLOUT_NUM_GPUS_PER_ENGINE}" + echo "rollout_batch_size=${ROLLOUT_BATCH_SIZE}" + echo "n_samples_per_prompt=${N_SAMPLES_PER_PROMPT}" + echo "global_batch_size=${GLOBAL_BATCH_SIZE}" + echo "max_tokens_per_gpu=${MAX_TOKENS_PER_GPU}" + echo "vllm_gpu_memory_utilization=${VLLM_GPU_MEMORY_UTILIZATION}" + echo "vime_vllm_enforce_eager=${VIME_VLLM_ENFORCE_EAGER}" + echo "vime_no_grad_accum_fusion=${VIME_NO_GRAD_ACCUM_FUSION}" + echo "rollout_max_response_len=${ROLLOUT_MAX_RESPONSE_LEN}" + echo "num_rollout=${NUM_ROLLOUT}" + echo "vime_save_interval=${VIME_SAVE_INTERVAL}" + echo "vime_disable_save=${VIME_DISABLE_SAVE}" + echo "vime_skip_eval_before_train=${VIME_SKIP_EVAL_BEFORE_TRAIN}" + echo "vime_rl_kernel=${VIME_RL_KERNEL:-0}" + echo "vime_rl_kernel_ops=${VIME_RL_KERNEL_OPS:-}" + echo "vime_rl_kernel_strict=${VIME_RL_KERNEL_STRICT:-0}" + nvidia-smi --query-gpu=index,name,memory.total --format=csv,noheader,nounits +} > "${RUN_DIR}/run_config.txt" + +( + while true; do + date +%s + nvidia-smi --query-gpu=index,memory.used --format=csv,noheader,nounits + sleep "${VRAM_POLL_INTERVAL:-2}" + done +) > "${RUN_DIR}/vram.csv" & +MONITOR_PID=$! + +cleanup() { + kill "${MONITOR_PID}" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +cd "${REPO_DIR}" +bash scripts/run-qwen3-30B-A3B.sh 2>&1 | tee "${RUN_DIR}/train.log" diff --git a/scripts/run-qwen3-30B-A3B.sh b/scripts/run-qwen3-30B-A3B.sh index 726a929b..0b370666 100644 --- a/scripts/run-qwen3-30B-A3B.sh +++ b/scripts/run-qwen3-30B-A3B.sh @@ -46,7 +46,7 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" VIME_ROOT="$(cd -- "${SCRIPT_DIR}/.." &>/dev/null && pwd)" source "${SCRIPT_DIR}/models/qwen3-30B-A3B.sh" -MEGATRON_TP=${MEGATRON_TP:-4} +MEGATRON_TP=${MEGATRON_TP:-8} MEGATRON_EP=${MEGATRON_EP:-${NUM_GPUS}} MEGATRON_CP=${MEGATRON_CP:-1} MAX_TOKENS_PER_GPU=${MAX_TOKENS_PER_GPU:-20480} @@ -57,15 +57,20 @@ ROLLOUT_MAX_RESPONSE_LEN=${ROLLOUT_MAX_RESPONSE_LEN:-8192} GLOBAL_BATCH_SIZE=${GLOBAL_BATCH_SIZE:-$((ROLLOUT_BATCH_SIZE * N_SAMPLES_PER_PROMPT))} ROLLOUT_NUM_GPUS_PER_ENGINE=${ROLLOUT_NUM_GPUS_PER_ENGINE:-${NUM_GPUS}} VLLM_GPU_MEMORY_UTILIZATION=${VLLM_GPU_MEMORY_UTILIZATION:-0.7} +VIME_CKPT_DIR=${VIME_CKPT_DIR:-/root/Qwen3-30B-A3B_vime} CKPT_ARGS=( --hf-checkpoint /root/Qwen3-30B-A3B #--hf-checkpoint /root/Qwen3-30B-A3B-FP8 --ref-load /root/Qwen3-30B-A3B_torch_dist - --load /root/Qwen3-30B-A3B_vime/ - --save /root/Qwen3-30B-A3B_vime/ - --save-interval 20 + --load "${VIME_CKPT_DIR}/" ) +if [[ "${VIME_DISABLE_SAVE:-0}" != "1" ]]; then + CKPT_ARGS+=( + --save "${VIME_CKPT_DIR}/" + --save-interval "${VIME_SAVE_INTERVAL:-20}" + ) +fi ROLLOUT_ARGS=( --prompt-data /root/dapo-math-17k/dapo-math-17k.jsonl @@ -91,6 +96,9 @@ EVAL_ARGS=( --eval-max-response-len 16384 --eval-top-p 1 ) +if [[ "${VIME_SKIP_EVAL_BEFORE_TRAIN:-0}" == "1" ]]; then + EVAL_ARGS+=(--skip-eval-before-train) +fi PERF_ARGS=( --tensor-model-parallel-size "${MEGATRON_TP}" @@ -108,6 +116,9 @@ PERF_ARGS=( --use-dynamic-batch-size --max-tokens-per-gpu "${MAX_TOKENS_PER_GPU}" ) +if [[ "${VIME_NO_GRAD_ACCUM_FUSION:-0}" == "1" ]]; then + PERF_ARGS+=(--no-gradient-accumulation-fusion) +fi GRPO_ARGS=( --advantage-estimator grpo @@ -139,12 +150,24 @@ WANDB_ARGS=( # --wandb-key ${WANDB_KEY} ) +TB_ARGS=() +if [[ "${VIME_TENSORBOARD:-0}" == "1" ]]; then + export TENSORBOARD_DIR="${TENSORBOARD_DIR:-${VIME_ROOT}/tensorboard_log/${TB_EXPERIMENT_NAME:-qwen3-30B-A3B}}" + TB_ARGS+=(--use-tensorboard) + TB_ARGS+=(--tb-project-name "${TB_PROJECT_NAME:-vime-rlk}") + TB_ARGS+=(--tb-experiment-name "${TB_EXPERIMENT_NAME:-qwen3-30B-A3B}") +fi + VLLM_ARGS=( --rollout-num-gpus-per-engine "${ROLLOUT_NUM_GPUS_PER_ENGINE}" --vllm-gpu-memory-utilization "${VLLM_GPU_MEMORY_UTILIZATION}" --vllm-enable-expert-parallel - --vllm-cudagraph-capture-sizes 1 2 4 8 $(seq 16 8 256) ) +if [[ "${VIME_VLLM_ENFORCE_EAGER:-0}" == "1" ]]; then + VLLM_ARGS+=(--vllm-enforce-eager) +else + VLLM_ARGS+=(--vllm-cudagraph-capture-sizes 1 2 4 8 $(seq 16 8 256)) +fi MISC_ARGS=( # default dropout in megatron is 0.1 @@ -173,8 +196,14 @@ ray start --head --node-ip-address ${MASTER_ADDR} --num-gpus ${NUM_GPUS} --disab RUNTIME_ENV_JSON="{ \"env_vars\": { \"PYTHONPATH\": \"${VIME_ROOT}:/root/Megatron-LM/\", + \"PATH\": \"${PATH}\", + \"CUDA_HOME\": \"${CUDA_HOME:-}\", + \"LD_LIBRARY_PATH\": \"${LD_LIBRARY_PATH:-}\", + \"CPATH\": \"${CPATH:-}\", + \"LIBRARY_PATH\": \"${LIBRARY_PATH:-}\", \"CUDA_DEVICE_MAX_CONNECTIONS\": \"1\", - \"NCCL_NVLS_ENABLE\": \"${HAS_NVLINK}\" + \"NCCL_NVLS_ENABLE\": \"${HAS_NVLINK}\", + \"TENSORBOARD_DIR\": \"${TENSORBOARD_DIR:-}\" } }" @@ -190,6 +219,7 @@ ray job submit --address="http://127.0.0.1:8265" \ ${OPTIMIZER_ARGS[@]} \ ${GRPO_ARGS[@]} \ ${WANDB_ARGS[@]} \ + ${TB_ARGS[@]} \ ${PERF_ARGS[@]} \ ${EVAL_ARGS[@]} \ ${VLLM_ARGS[@]} \ diff --git a/tests/test_rl_kernel_linear_logp_integration.py b/tests/test_rl_kernel_linear_logp_integration.py index 4c0d1448..1fe6b5e6 100644 --- a/tests/test_rl_kernel_linear_logp_integration.py +++ b/tests/test_rl_kernel_linear_logp_integration.py @@ -328,6 +328,51 @@ def test_linear_logp_context_from_model_uses_tp_vocab_offsets(): assert context.sequence_parallel is True +@pytest.mark.unit +def test_linear_logp_context_prefers_output_layer_weight_for_untied_pp1_model(): + mpu.get_tensor_model_parallel_world_size.return_value = 1 + mpu.get_tensor_model_parallel_rank.return_value = 0 + mpu.get_tensor_model_parallel_group.return_value = None + + output_weight = torch.empty(8, 4) + embedding_weight = torch.empty(8, 4) + output_layer = types.SimpleNamespace(weight=output_weight, bias=None) + model = types.SimpleNamespace( + output_layer=output_layer, + post_process=True, + pre_process=True, + shared_embedding_or_output_weight=lambda: embedding_weight, + ) + args = _make_args() + + context = rlk_mod.get_linear_logp_context_from_model(args, model) + + assert context is not None + assert context.lm_head_weight is output_weight + + +@pytest.mark.unit +def test_linear_logp_context_uses_shared_weight_when_output_layer_weight_is_missing(): + mpu.get_tensor_model_parallel_world_size.return_value = 1 + mpu.get_tensor_model_parallel_rank.return_value = 0 + mpu.get_tensor_model_parallel_group.return_value = None + + embedding_weight = torch.empty(8, 4) + output_layer = types.SimpleNamespace(weight=None, bias=None) + model = types.SimpleNamespace( + output_layer=output_layer, + post_process=True, + pre_process=True, + shared_embedding_or_output_weight=lambda: embedding_weight, + ) + args = _make_args() + + context = rlk_mod.get_linear_logp_context_from_model(args, model) + + assert context is not None + assert context.lm_head_weight is embedding_weight + + @pytest.mark.unit def test_linear_logp_context_uses_covered_padded_vocab_when_padded_vocab_size_missing(): mpu.get_tensor_model_parallel_world_size.return_value = 4 diff --git a/vime-RLK.md b/vime-RLK.md index 42cf234d..cbb4957f 100644 --- a/vime-RLK.md +++ b/vime-RLK.md @@ -53,7 +53,7 @@ scripts/run-qwen3-30B-A3B.sh ```text NUM_GPUS=8 -MEGATRON_TP=4 +MEGATRON_TP=8 MEGATRON_EP=8 MEGATRON_CP=1 ROLLOUT_NUM_GPUS_PER_ENGINE=8 @@ -187,7 +187,7 @@ PYTHONPATH=/root/Megatron-LM torchrun --nproc-per-node 8 \ ```bash export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 export NUM_GPUS=8 -export MEGATRON_TP=4 +export MEGATRON_TP=8 export MEGATRON_EP=8 export MEGATRON_CP=1 export ROLLOUT_NUM_GPUS_PER_ENGINE=8 diff --git a/vime/backends/megatron_utils/rl_kernel.py b/vime/backends/megatron_utils/rl_kernel.py index aca8706d..181afd40 100644 --- a/vime/backends/megatron_utils/rl_kernel.py +++ b/vime/backends/megatron_utils/rl_kernel.py @@ -110,6 +110,10 @@ def _is_pipeline_last_stage_for_model(model) -> bool: def _get_lm_head_weight(model, output_layer) -> torch.Tensor | None: + weight = getattr(output_layer, "weight", None) + if isinstance(weight, torch.Tensor): + return weight + shared_weight = getattr(model, "shared_embedding_or_output_weight", None) if callable(shared_weight): try: @@ -119,8 +123,7 @@ def _get_lm_head_weight(model, output_layer) -> torch.Tensor | None: except Exception: logger.debug("Unable to read shared embedding/output weight for RL-Kernel linear_logp.", exc_info=True) - weight = getattr(output_layer, "weight", None) - return weight if isinstance(weight, torch.Tensor) else None + return None def get_linear_logp_context_from_model(args: Namespace, model) -> LinearLogpContext | None: diff --git a/vime/ray/actor_group.py b/vime/ray/actor_group.py index db3500dc..0e601671 100644 --- a/vime/ray/actor_group.py +++ b/vime/ray/actor_group.py @@ -63,6 +63,7 @@ def _allocate_gpus_for_actor(self, pg, num_gpus_per_actor): import torch_memory_saver for path in [ + "torch_memory_saver_hook_mode_preload_cu13.abi3.so", "torch_memory_saver_hook_mode_preload_cu12.abi3.so", "torch_memory_saver_hook_mode_preload.abi3.so", ]: