Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions python/sglang/srt/entrypoints/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
LoadLoRAAdapterReqInput,
MultimodalDataInputFormat,
OpenSessionReqInput,
PostProcessWeightsReqInput,
ReleaseMemoryOccupationReqInput,
ResumeMemoryOccupationReqInput,
RpcReqInput,
Expand Down Expand Up @@ -1025,6 +1026,33 @@ def update_weights_from_ipc(
self.tokenizer_manager.update_weights_from_ipc(obj, None)
)

def post_process_weights(
self,
restore_weights_before_load: bool = False,
post_process_quantization: bool = False,
post_load_weights: bool = False,
):
"""
Optional post-processing for updated weights (e.g., Marlin conversion).
Should be called after weight update is finished.

Args:
restore_weights_before_load: Restore weights to pre-quantization state.
post_process_quantization: Re-apply quantization post-processing.
post_load_weights: Call model.post_load_weights() for models that
need post-load decomposition (e.g., DeepSeek MLA kv_b_proj
decomposition into w_kc/w_vc tensors after RDMA weight transfer).
"""
obj = PostProcessWeightsReqInput(
restore_weights_before_load=restore_weights_before_load,
post_process_quantization=post_process_quantization,
post_load_weights=post_load_weights,
)

return self.loop.run_until_complete(
self.tokenizer_manager.post_process_weights(obj, None)
)

def get_weights_by_name(self, name: str, truncate_size: int = 100):
"""Get weights by parameter name."""
obj = GetWeightsByNameReqInput(name=name, truncate_size=truncate_size)
Expand Down
18 changes: 18 additions & 0 deletions python/sglang/srt/entrypoints/http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
OpenSessionReqInput,
ParseFunctionCallReq,
PauseGenerationReqInput,
PostProcessWeightsReqInput,
ProfileReqInput,
ReleaseMemoryOccupationReqInput,
ResumeMemoryOccupationReqInput,
Expand Down Expand Up @@ -1223,6 +1224,23 @@ async def update_weights_from_ipc(obj: UpdateWeightsFromIPCReqInput, request: Re
return ORJSONResponse(content, status_code=HTTPStatus.BAD_REQUEST)


@app.post("/post_process_weights")
@auth_level(AuthLevel.ADMIN_OPTIONAL)
async def post_process_weights(req: PostProcessWeightsReqInput, request: Request):
"""
Optional post-processing for updated weights (e.g., Marlin conversion).
This should be called selectively after `update_weights_from_distributed/update_weights_from_tensor`.
"""
success, message = await _global_state.tokenizer_manager.post_process_weights(
req, request
)

content = {"success": success, "message": message}
return ORJSONResponse(
content, status_code=200 if success else HTTPStatus.BAD_REQUEST
)


@app.post("/update_weight_version")
@auth_level(AuthLevel.ADMIN_OPTIONAL)
async def update_weight_version(obj: UpdateWeightVersionReqInput, request: Request):
Expand Down
17 changes: 17 additions & 0 deletions python/sglang/srt/managers/io_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,23 @@ class InitWeightsSendGroupForRemoteInstanceReqOutput(BaseReq):
message: str


@dataclass
class PostProcessWeightsReqInput(BaseReq):
# Whether to restore weights before loading new weights
restore_weights_before_load: bool = False
# Whether to enable quantization post-processing
post_process_quantization: bool = False
# Whether to call model.post_load_weights() after weight update
# (e.g., DeepSeek MLA kv_b_proj decomposition into w_kc/w_vc tensors)
post_load_weights: bool = False


@dataclass
class PostProcessWeightsReqOutput(BaseReq):
success: bool
message: str


@dataclass
class SendWeightsToRemoteInstanceReqInput(BaseReq):
# The master address
Expand Down
2 changes: 2 additions & 0 deletions python/sglang/srt/managers/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
LoadLoRAAdapterReqOutput,
OpenSessionReqInput,
PauseGenerationReqInput,
PostProcessWeightsReqInput,
ProfileReq,
ReleaseMemoryOccupationReqInput,
RemoveExternalCorpusReqInput,
Expand Down Expand Up @@ -1441,6 +1442,7 @@ def init_request_dispatcher(self):
),
(UpdateWeightsFromTensorReqInput, self.update_weights_from_tensor),
(UpdateWeightsFromIPCReqInput, self.update_weights_from_ipc),
(PostProcessWeightsReqInput, self.post_process_weights),
(GetWeightsByNameReqInput, self.get_weights_by_name),
(ReleaseMemoryOccupationReqInput, self.release_memory_occupation),
(ResumeMemoryOccupationReqInput, self.resume_memory_occupation),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
GetWeightsByNameReqOutput,
InitWeightsUpdateGroupReqInput,
InitWeightsUpdateGroupReqOutput,
PostProcessWeightsReqInput,
PostProcessWeightsReqOutput,
ReleaseMemoryOccupationReqInput,
ReleaseMemoryOccupationReqOutput,
ResumeMemoryOccupationReqInput,
Expand Down Expand Up @@ -129,6 +131,13 @@ def update_weights_from_ipc(
torch.distributed.barrier(group=self.tp_cpu_group)
return UpdateWeightsFromIPCReqOutput(success, message)

def post_process_weights(self, recv_req: PostProcessWeightsReqInput):
"""Optional post-processing for updated weights (e.g., Marlin conversion)."""
success, message = self.tp_worker.post_process_weights(recv_req)
if self.tp_cpu_group is not None:
torch.distributed.barrier(group=self.tp_cpu_group)
return PostProcessWeightsReqOutput(success, message)

def get_weights_by_name(self: Scheduler, recv_req: GetWeightsByNameReqInput):
parameter = self.tp_worker.get_weights_by_name(recv_req)
return GetWeightsByNameReqOutput(parameter)
Expand Down
22 changes: 22 additions & 0 deletions python/sglang/srt/managers/tokenizer_control_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
LoadLoRAAdapterReqOutput,
LoRAUpdateOutput,
OpenSessionReqInput,
PostProcessWeightsReqInput,
PostProcessWeightsReqOutput,
ProfileReq,
ProfileReqOutput,
ProfileReqType,
Expand Down Expand Up @@ -102,6 +104,7 @@
("send_weights_to_remote_instance", SendWeightsToRemoteInstanceReqOutput),
("update_weights_from_tensor", UpdateWeightsFromTensorReqOutput),
("update_weights_from_ipc", UpdateWeightsFromIPCReqOutput),
("post_process_weights", PostProcessWeightsReqOutput),
("get_weights_by_name", GetWeightsByNameReqOutput),
("release_memory_occupation", ReleaseMemoryOccupationReqOutput),
("resume_memory_occupation", ResumeMemoryOccupationReqOutput),
Expand Down Expand Up @@ -531,6 +534,25 @@ async def update_weights_from_ipc(

return success, message

async def post_process_weights(
self: TokenizerManager,
obj: PostProcessWeightsReqInput,
request: Optional[fastapi.Request] = None,
) -> Tuple[bool, str]:
"""Trigger post-processing hooks for weights after loading (e.g., Marlin conversion)."""
self.auto_create_handle_loop()

async with self.is_pause_cond:
is_paused = self.is_pause
if is_paused:
results = await self.post_process_weights_communicator(obj)

if not is_paused:
async with self.model_update_lock.writer_lock:
results = await self.post_process_weights_communicator(obj)
Comment on lines +545 to +552

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is this a bit improveable

@xiuhu17 xiuhu17 May 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. Do you think we should combine the logic of post_process_weights into update_weights_from_xxx?


return FanOutCommunicator.merge_results(results)

async def _unload_lora_adapter_locked(
self: TokenizerManager,
obj: UnloadLoRAAdapterReqInput,
Expand Down
6 changes: 6 additions & 0 deletions python/sglang/srt/managers/tp_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
InitWeightsUpdateGroupReqInput,
LoadLoRAAdapterFromTensorsReqInput,
LoadLoRAAdapterReqInput,
PostProcessWeightsReqInput,
SendWeightsToRemoteInstanceReqInput,
UnloadLoRAAdapterReqInput,
UpdateWeightFromDiskReqInput,
Expand Down Expand Up @@ -171,6 +172,11 @@ def update_weights_from_ipc(self, recv_req: UpdateWeightsFromIPCReqInput):
success, message = self.model_runner.update_weights_from_ipc(recv_req)
return success, message

def post_process_weights(self, recv_req: PostProcessWeightsReqInput):
"""Perform optional post-processing on the updated model weights (e.g., Marlin conversion)."""
success, message = self.model_runner.post_process_weights(recv_req)
return success, message

def get_weights_by_name(self, recv_req: GetWeightsByNameReqInput):
parameter = self.model_runner.get_weights_by_name(
recv_req.name, recv_req.truncate_size
Expand Down
44 changes: 44 additions & 0 deletions python/sglang/srt/model_executor/model_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3518,6 +3518,50 @@ def update_weights_from_ipc(self, recv_req):
logger.error(f"IPC weight update failed: {e}")
return False, str(e)

def post_process_weights(self, recv_req):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: not sure whether this is the best impl

@xiuhu17 xiuhu17 May 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

post_process_weights only calls once after all update_weights_from_xxx finished and once before. I am not sure whether there is a better way to improve the design. Do you have any suggestion?

"""
Execute post-processing logic for model weights, such as Marlin quantization format conversion
and model-specific post_load_weights hooks (e.g., DeepSeek MLA kv_b_proj decomposition).
"""
from sglang.srt.model_loader.loader import device_loading_context

target_device = torch.device("cuda", torch.cuda.current_device())

if recv_req.post_load_weights:
# Call model.post_load_weights() if available (e.g., for DeepSeek MLA
# models that need to decompose kv_b_proj.weight into w_kc/w_vc tensors
# after RDMA weight transfer)
if hasattr(self.model, "post_load_weights"):
self.model.post_load_weights()

if recv_req.restore_weights_before_load:
for _, module in self.model.named_modules():
quant_method = getattr(module, "quant_method", None)

# Check if the module supports restoring weights
if quant_method is not None and hasattr(
quant_method, "restore_weights_before_loading"
):

with device_loading_context(module, target_device):
quant_method.restore_weights_before_loading(module)

if recv_req.post_process_quantization:
# Iterate through all modules to apply specific post-loading processing
for _, module in self.model.named_modules():
quant_method = getattr(module, "quant_method", None)

# Check if the module supports quantization post-processing
if quant_method is not None and hasattr(
quant_method, "process_weights_after_loading"
):

# Apply the post-processing (e.g., repacking weights for Marlin kernel)
with device_loading_context(module, target_device):
quant_method.process_weights_after_loading(module)

return True, "Success"

def prealloc_symmetric_memory_pool(self):
# PyTorch mempools never de-fragment memory in OOM scenarios, so we need to pre-allocate a large chunk of memory to limit fragmentation.
if (
Expand Down
Loading