Support sanity checking weight consistency especially for RL (#13854)
This commit is contained in:
@@ -76,6 +76,7 @@ from sglang.srt.environ import envs
|
|||||||
from sglang.srt.function_call.function_call_parser import FunctionCallParser
|
from sglang.srt.function_call.function_call_parser import FunctionCallParser
|
||||||
from sglang.srt.managers.io_struct import (
|
from sglang.srt.managers.io_struct import (
|
||||||
AbortReq,
|
AbortReq,
|
||||||
|
CheckWeightsReqInput,
|
||||||
CloseSessionReqInput,
|
CloseSessionReqInput,
|
||||||
ConfigureLoggingReq,
|
ConfigureLoggingReq,
|
||||||
ContinueGenerationReqInput,
|
ContinueGenerationReqInput,
|
||||||
@@ -956,6 +957,15 @@ async def resume_memory_occupation(
|
|||||||
return _create_error_response(e)
|
return _create_error_response(e)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/weights_checker")
|
||||||
|
async def check_weights(obj: CheckWeightsReqInput, request: Request):
|
||||||
|
success, message = await _global_state.tokenizer_manager.check_weights(obj, request)
|
||||||
|
return ORJSONResponse(
|
||||||
|
{"success": success, "message": message},
|
||||||
|
status_code=200 if success else HTTPStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.api_route("/slow_down", methods=["GET", "POST"])
|
@app.api_route("/slow_down", methods=["GET", "POST"])
|
||||||
async def slow_down(obj: SlowDownReqInput, request: Request):
|
async def slow_down(obj: SlowDownReqInput, request: Request):
|
||||||
"""Slow down the system deliberately. Only for testing. Example scenario:
|
"""Slow down the system deliberately. Only for testing. Example scenario:
|
||||||
|
|||||||
@@ -1311,6 +1311,17 @@ class ResumeMemoryOccupationReqOutput(BaseReq):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CheckWeightsReqInput(BaseReq):
|
||||||
|
action: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CheckWeightsReqOutput(BaseReq):
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SlowDownReqInput(BaseReq):
|
class SlowDownReqInput(BaseReq):
|
||||||
forward_sleep_time: Optional[float]
|
forward_sleep_time: Optional[float]
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ from sglang.srt.managers.io_struct import (
|
|||||||
BaseReq,
|
BaseReq,
|
||||||
BatchTokenizedEmbeddingReqInput,
|
BatchTokenizedEmbeddingReqInput,
|
||||||
BatchTokenizedGenerateReqInput,
|
BatchTokenizedGenerateReqInput,
|
||||||
|
CheckWeightsReqInput,
|
||||||
ClearHiCacheReqInput,
|
ClearHiCacheReqInput,
|
||||||
ClearHiCacheReqOutput,
|
ClearHiCacheReqOutput,
|
||||||
CloseSessionReqInput,
|
CloseSessionReqInput,
|
||||||
@@ -568,6 +569,7 @@ class Scheduler(
|
|||||||
(GetWeightsByNameReqInput, self.get_weights_by_name),
|
(GetWeightsByNameReqInput, self.get_weights_by_name),
|
||||||
(ReleaseMemoryOccupationReqInput, self.release_memory_occupation),
|
(ReleaseMemoryOccupationReqInput, self.release_memory_occupation),
|
||||||
(ResumeMemoryOccupationReqInput, self.resume_memory_occupation),
|
(ResumeMemoryOccupationReqInput, self.resume_memory_occupation),
|
||||||
|
(CheckWeightsReqInput, self.check_weights),
|
||||||
(SlowDownReqInput, self.slow_down),
|
(SlowDownReqInput, self.slow_down),
|
||||||
(ProfileReq, self.profile),
|
(ProfileReq, self.profile),
|
||||||
(FreezeGCReq, self.handle_freeze_gc),
|
(FreezeGCReq, self.handle_freeze_gc),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
from typing import TYPE_CHECKING, Tuple
|
from typing import TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
@@ -12,6 +13,8 @@ from sglang.srt.constants import (
|
|||||||
GPU_MEMORY_TYPE_WEIGHTS,
|
GPU_MEMORY_TYPE_WEIGHTS,
|
||||||
)
|
)
|
||||||
from sglang.srt.managers.io_struct import (
|
from sglang.srt.managers.io_struct import (
|
||||||
|
CheckWeightsReqInput,
|
||||||
|
CheckWeightsReqOutput,
|
||||||
DestroyWeightsUpdateGroupReqInput,
|
DestroyWeightsUpdateGroupReqInput,
|
||||||
DestroyWeightsUpdateGroupReqOutput,
|
DestroyWeightsUpdateGroupReqOutput,
|
||||||
GetWeightsByNameReqInput,
|
GetWeightsByNameReqInput,
|
||||||
@@ -166,6 +169,15 @@ class SchedulerUpdateWeightsMixin:
|
|||||||
|
|
||||||
return ResumeMemoryOccupationReqOutput()
|
return ResumeMemoryOccupationReqOutput()
|
||||||
|
|
||||||
|
def check_weights(self: Scheduler, recv_req: CheckWeightsReqInput):
|
||||||
|
try:
|
||||||
|
self.tp_worker.model_runner.check_weights(action=recv_req.action)
|
||||||
|
return CheckWeightsReqOutput(success=True, message="Success.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"check_weights see error: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
return CheckWeightsReqOutput(success=False, message=f"{e}")
|
||||||
|
|
||||||
def save_remote_model(self: Scheduler, params):
|
def save_remote_model(self: Scheduler, params):
|
||||||
url = params["url"]
|
url = params["url"]
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import fastapi
|
|||||||
import zmq
|
import zmq
|
||||||
|
|
||||||
from sglang.srt.managers.io_struct import (
|
from sglang.srt.managers.io_struct import (
|
||||||
|
CheckWeightsReqInput,
|
||||||
|
CheckWeightsReqOutput,
|
||||||
ClearHiCacheReqInput,
|
ClearHiCacheReqInput,
|
||||||
ClearHiCacheReqOutput,
|
ClearHiCacheReqOutput,
|
||||||
CloseSessionReqInput,
|
CloseSessionReqInput,
|
||||||
@@ -183,6 +185,9 @@ class TokenizerCommunicatorMixin:
|
|||||||
self.resume_memory_occupation_communicator = _Communicator(
|
self.resume_memory_occupation_communicator = _Communicator(
|
||||||
self.send_to_scheduler, server_args.dp_size
|
self.send_to_scheduler, server_args.dp_size
|
||||||
)
|
)
|
||||||
|
self.check_weights_communicator = _Communicator(
|
||||||
|
self.send_to_scheduler, server_args.dp_size
|
||||||
|
)
|
||||||
self.slow_down_communicator = _Communicator(
|
self.slow_down_communicator = _Communicator(
|
||||||
self.send_to_scheduler, server_args.dp_size
|
self.send_to_scheduler, server_args.dp_size
|
||||||
)
|
)
|
||||||
@@ -256,6 +261,10 @@ class TokenizerCommunicatorMixin:
|
|||||||
ResumeMemoryOccupationReqOutput,
|
ResumeMemoryOccupationReqOutput,
|
||||||
self.resume_memory_occupation_communicator.handle_recv,
|
self.resume_memory_occupation_communicator.handle_recv,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
CheckWeightsReqOutput,
|
||||||
|
self.check_weights_communicator.handle_recv,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
SlowDownReqOutput,
|
SlowDownReqOutput,
|
||||||
self.slow_down_communicator.handle_recv,
|
self.slow_down_communicator.handle_recv,
|
||||||
@@ -670,6 +679,15 @@ class TokenizerCommunicatorMixin:
|
|||||||
self.auto_create_handle_loop()
|
self.auto_create_handle_loop()
|
||||||
await self.resume_memory_occupation_communicator(obj)
|
await self.resume_memory_occupation_communicator(obj)
|
||||||
|
|
||||||
|
async def check_weights(
|
||||||
|
self: TokenizerManager,
|
||||||
|
obj: CheckWeightsReqInput,
|
||||||
|
request: Optional[fastapi.Request] = None,
|
||||||
|
) -> CheckWeightsReqOutput:
|
||||||
|
self.auto_create_handle_loop()
|
||||||
|
results = await self.check_weights_communicator(obj)
|
||||||
|
return _Communicator.merge_results(results)
|
||||||
|
|
||||||
async def slow_down(
|
async def slow_down(
|
||||||
self: TokenizerManager,
|
self: TokenizerManager,
|
||||||
obj: SlowDownReqInput,
|
obj: SlowDownReqInput,
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ from sglang.srt.utils.offloader import (
|
|||||||
)
|
)
|
||||||
from sglang.srt.utils.patch_torch import monkey_patch_torch_reductions
|
from sglang.srt.utils.patch_torch import monkey_patch_torch_reductions
|
||||||
from sglang.srt.utils.torch_memory_saver_adapter import TorchMemorySaverAdapter
|
from sglang.srt.utils.torch_memory_saver_adapter import TorchMemorySaverAdapter
|
||||||
|
from sglang.srt.utils.weight_checker import WeightChecker
|
||||||
from sglang.srt.weight_sync.tensor_bucket import (
|
from sglang.srt.weight_sync.tensor_bucket import (
|
||||||
FlattenedTensorBucket,
|
FlattenedTensorBucket,
|
||||||
FlattenedTensorMetadata,
|
FlattenedTensorMetadata,
|
||||||
@@ -328,6 +329,8 @@ class ModelRunner:
|
|||||||
# CPU offload
|
# CPU offload
|
||||||
set_offloader(create_offloader_from_server_args(server_args, dp_rank=dp_rank))
|
set_offloader(create_offloader_from_server_args(server_args, dp_rank=dp_rank))
|
||||||
|
|
||||||
|
self._weight_checker = WeightChecker(model_runner=self)
|
||||||
|
|
||||||
if get_bool_env_var("SGLANG_DETECT_SLOW_RANK"):
|
if get_bool_env_var("SGLANG_DETECT_SLOW_RANK"):
|
||||||
slow_rank_detector.execute()
|
slow_rank_detector.execute()
|
||||||
# Init mindspore running environment when model impl is "mindspore"
|
# Init mindspore running environment when model impl is "mindspore"
|
||||||
@@ -2508,6 +2511,9 @@ class ModelRunner:
|
|||||||
)
|
)
|
||||||
ShardedStateLoader.save_model(self.model, path, pattern, max_size)
|
ShardedStateLoader.save_model(self.model, path, pattern, max_size)
|
||||||
|
|
||||||
|
def check_weights(self, action: str):
|
||||||
|
self._weight_checker.handle(action=action)
|
||||||
|
|
||||||
def update_weights_from_ipc(self, recv_req):
|
def update_weights_from_ipc(self, recv_req):
|
||||||
"""Update weights from IPC for checkpoint-engine integration."""
|
"""Update weights from IPC for checkpoint-engine integration."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WeightChecker:
|
||||||
|
def __init__(self, model_runner):
|
||||||
|
self._model_runner = model_runner
|
||||||
|
self._snapshot_tensors = None
|
||||||
|
|
||||||
|
def handle(self, action: str):
|
||||||
|
logger.info(f"[WeightChecker] handle action={action}")
|
||||||
|
if action == "snapshot":
|
||||||
|
self._snapshot()
|
||||||
|
elif action == "reset_tensors":
|
||||||
|
self._reset_tensors()
|
||||||
|
elif action == "compare":
|
||||||
|
self._compare()
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unsupported {action=}")
|
||||||
|
|
||||||
|
def _snapshot(self):
|
||||||
|
named_tensors = [
|
||||||
|
(name, param.data.detach().cpu()) for name, param in self._model_state()
|
||||||
|
]
|
||||||
|
self._snapshot_tensors = dict(named_tensors)
|
||||||
|
assert len(self._snapshot_tensors) == len(
|
||||||
|
named_tensors
|
||||||
|
), f"should not have duplicated tensor name"
|
||||||
|
|
||||||
|
def _reset_tensors(self):
|
||||||
|
for name, param in self._model_state():
|
||||||
|
param.copy_(_random_like(param))
|
||||||
|
|
||||||
|
def _compare(self):
|
||||||
|
assert self._snapshot_tensors is not None
|
||||||
|
|
||||||
|
_check_tensors(
|
||||||
|
expect_tensors=self._snapshot_tensors,
|
||||||
|
actual_tensors=dict(self._model_state()),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _model_state(self):
|
||||||
|
# TODO: support EAGLE etc (e.g. yield from both main model and draft model)
|
||||||
|
yield from self._model_runner.model.named_parameters()
|
||||||
|
yield from self._model_runner.model.named_buffers()
|
||||||
|
|
||||||
|
|
||||||
|
def _check_tensors(
|
||||||
|
expect_tensors: Dict[str, torch.Tensor], actual_tensors: Dict[str, torch.Tensor]
|
||||||
|
):
|
||||||
|
from sglang.srt.debug_utils.dumper import get_tensor_info
|
||||||
|
|
||||||
|
assert len(expect_tensors) == len(actual_tensors)
|
||||||
|
|
||||||
|
good_names = []
|
||||||
|
error_messages = []
|
||||||
|
|
||||||
|
for name in expect_tensors:
|
||||||
|
expect = expect_tensors[name].cuda()
|
||||||
|
actual = actual_tensors[name].cuda()
|
||||||
|
|
||||||
|
if torch.all(expect == actual):
|
||||||
|
good_names.append(name)
|
||||||
|
else:
|
||||||
|
abs_diff = (actual.float() - expect.float()).abs()
|
||||||
|
error_messages.append(
|
||||||
|
f"name={name} "
|
||||||
|
f"max_abs_err={abs_diff.max()} "
|
||||||
|
f"mean_abs_err={abs_diff.mean()} "
|
||||||
|
f"{get_tensor_info(expect)=} "
|
||||||
|
f"{get_tensor_info(actual)=} "
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"[check_tensors] passed: {good_names}")
|
||||||
|
if len(error_messages) > 0:
|
||||||
|
raise Exception(f"check tensor equality failed:\n" + "\n".join(error_messages))
|
||||||
|
|
||||||
|
|
||||||
|
def _random_like(t: torch.Tensor):
|
||||||
|
device = t.device
|
||||||
|
shape = t.shape
|
||||||
|
dtype = t.dtype
|
||||||
|
|
||||||
|
if dtype.is_floating_point:
|
||||||
|
return torch.rand(shape, device=device, dtype=torch.float32).to(dtype)
|
||||||
|
|
||||||
|
if dtype == torch.bool:
|
||||||
|
return torch.rand(shape, device=device) > 0.5
|
||||||
|
|
||||||
|
info = torch.iinfo(dtype)
|
||||||
|
return torch.randint(
|
||||||
|
low=int(info.min), high=int(info.max), size=shape, device=device, dtype=dtype
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user