Support getting checksums in weight checker (#24537)
This commit is contained in:
@@ -1295,11 +1295,13 @@ async def resume_memory_occupation(
|
||||
@app.post("/weights_checker")
|
||||
@auth_level(AuthLevel.ADMIN_OPTIONAL)
|
||||
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,
|
||||
success, message, ranks = await _global_state.tokenizer_manager.check_weights(
|
||||
obj, request
|
||||
)
|
||||
body = {"success": success, "message": message}
|
||||
if ranks is not None:
|
||||
body["ranks"] = ranks
|
||||
return ORJSONResponse(body, status_code=200 if success else HTTPStatus.BAD_REQUEST)
|
||||
|
||||
|
||||
@app.api_route("/slow_down", methods=["GET", "POST"])
|
||||
|
||||
@@ -1625,6 +1625,7 @@ class CheckWeightsReqInput(BaseReq):
|
||||
class CheckWeightsReqOutput(BaseReq):
|
||||
success: bool
|
||||
message: str
|
||||
payload: Optional[Dict] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -196,8 +196,10 @@ class SchedulerUpdateWeightsMixin:
|
||||
|
||||
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.")
|
||||
payload = self.tp_worker.model_runner.check_weights(action=recv_req.action)
|
||||
return CheckWeightsReqOutput(
|
||||
success=True, message="Success.", payload=payload
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"check_weights see error: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -764,10 +764,14 @@ class TokenizerControlMixin:
|
||||
self: TokenizerManager,
|
||||
obj: CheckWeightsReqInput,
|
||||
request: Optional[fastapi.Request] = None,
|
||||
) -> CheckWeightsReqOutput:
|
||||
) -> Tuple[bool, str, Optional[List[Dict]]]:
|
||||
self.auto_create_handle_loop()
|
||||
results = await self.check_weights_communicator(obj)
|
||||
return FanOutCommunicator.merge_results(results)
|
||||
success, message = FanOutCommunicator.merge_results(results)
|
||||
ranks: Optional[List[Dict]] = None
|
||||
if any(r.payload is not None for r in results):
|
||||
ranks = [r.payload for r in results]
|
||||
return success, message, ranks
|
||||
|
||||
async def slow_down(
|
||||
self: TokenizerManager,
|
||||
|
||||
@@ -3484,7 +3484,7 @@ class ModelRunner(ModelRunnerKVCacheMixin):
|
||||
ShardedStateLoader.save_model(self.model, path, pattern, max_size)
|
||||
|
||||
def check_weights(self, action: str):
|
||||
self._weight_checker.handle(action=action)
|
||||
return self._weight_checker.handle(action=action)
|
||||
|
||||
def update_weights_from_ipc(self, recv_req):
|
||||
"""Update weights from IPC for checkpoint-engine integration."""
|
||||
|
||||
@@ -1,16 +1,40 @@
|
||||
import logging
|
||||
from typing import Dict, Iterable, Tuple
|
||||
import time
|
||||
from typing import Dict, Iterable, Optional, Tuple
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from sglang.srt.layers.quantization.fp8_utils import (
|
||||
block_quant_dequant,
|
||||
inverse_transform_scale_ue8m0,
|
||||
)
|
||||
from sglang.srt.managers.mm_utils import tensor_hash
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _StrictBaseModel(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class ParallelismInfo(_StrictBaseModel):
|
||||
tp_rank: int
|
||||
tp_size: int
|
||||
dp_rank: int
|
||||
dp_size: int
|
||||
pp_rank: int
|
||||
pp_size: int
|
||||
rank: int
|
||||
size: int
|
||||
|
||||
|
||||
class ChecksumInfo(_StrictBaseModel):
|
||||
checksums: Dict[str, str]
|
||||
parallelism_info: ParallelismInfo
|
||||
|
||||
|
||||
_NON_PERSISTENT_BUFFER_PATTERNS = (
|
||||
"cos_sin_cache",
|
||||
"inv_freq",
|
||||
@@ -28,14 +52,16 @@ class WeightChecker:
|
||||
self._model_runner = model_runner
|
||||
self._snapshot_tensors = None
|
||||
|
||||
def handle(self, action: str):
|
||||
def handle(self, action: str) -> Optional[Dict]:
|
||||
logger.info(f"[WeightChecker] handle action={action}")
|
||||
if action == "snapshot":
|
||||
self._snapshot()
|
||||
return self._snapshot()
|
||||
elif action == "reset_tensors":
|
||||
self._reset_tensors()
|
||||
return self._reset_tensors()
|
||||
elif action == "compare":
|
||||
self._compare()
|
||||
return self._compare()
|
||||
elif action == "checksum":
|
||||
return self._compute_checksum()
|
||||
else:
|
||||
raise Exception(f"Unsupported {action=}")
|
||||
|
||||
@@ -62,12 +88,56 @@ class WeightChecker:
|
||||
actual_tensors=_postprocess_tensors(dict(self._model_state())),
|
||||
)
|
||||
|
||||
def _compute_checksum(self) -> Dict:
|
||||
torch.cuda.synchronize()
|
||||
start = time.perf_counter()
|
||||
|
||||
# Reuse the snapshot/compare postprocess pipeline so fp8 weights are
|
||||
# dequantized to bf16 before hashing — two (qweight, scale) pairs that
|
||||
# produce the same bf16 must produce the same checksum.
|
||||
checksums = {
|
||||
name: _hash_tensor(tensor.data)
|
||||
for name, should_compare, tensor in _postprocess_tensors(
|
||||
dict(self._model_state())
|
||||
)
|
||||
if should_compare
|
||||
}
|
||||
|
||||
torch.cuda.synchronize()
|
||||
elapsed = time.perf_counter() - start
|
||||
logger.info(
|
||||
f"[WeightChecker] checksum computed for {len(checksums)} tensors in {elapsed:.3f}s"
|
||||
)
|
||||
|
||||
info = ChecksumInfo(
|
||||
checksums=checksums,
|
||||
parallelism_info=self._parallelism_info(),
|
||||
)
|
||||
return info.model_dump()
|
||||
|
||||
def _parallelism_info(self) -> ParallelismInfo:
|
||||
mr = self._model_runner
|
||||
return ParallelismInfo(
|
||||
tp_rank=mr.tp_rank,
|
||||
tp_size=mr.tp_size,
|
||||
dp_rank=mr.dp_rank if mr.dp_rank is not None else 0,
|
||||
dp_size=mr.dp_size,
|
||||
pp_rank=mr.pp_rank,
|
||||
pp_size=mr.pp_size,
|
||||
rank=dist.get_rank() if dist.is_initialized() else 0,
|
||||
size=dist.get_world_size() if dist.is_initialized() else 1,
|
||||
)
|
||||
|
||||
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 _hash_tensor(t: torch.Tensor) -> str:
|
||||
return f"{tensor_hash(t):016x}"
|
||||
|
||||
|
||||
def _check_tensors(
|
||||
expect_tensors: Iterable[Tuple[str, bool, torch.Tensor]],
|
||||
actual_tensors: Iterable[Tuple[str, bool, torch.Tensor]],
|
||||
|
||||
Reference in New Issue
Block a user