[content] miles/backends/fsdp_utils/update_weight_utils.py (python)
    def update_lora_adapter(self) -> None:
        """Update LoRA adapter using SGLang's unload_lora_adapter and load_lora_adapter endpoints.

        Since update_weights_from_tensor doesn't support LoRA weights, we need to:
        1. Save current LoRA weights to temporary adapter files
        2. Unload current LoRA adapter from SGLang
        3. Load new LoRA adapter from temporary files
        """
        from miles.utils.async_utils import run

        logger.info(f"[Rank {dist.get_rank()}] Updating LoRA adapter via HTTP endpoints")

        # Extract LoRA weights from model state dict
        lora_state_dict = {k: v for k, v in self.model.state_dict().items() if "lora_" in k}

        # Create temporary directory for adapter files
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Create adapter_config.json
            lora_config = LoraConfig(
                lora_rank=self.args.lora_rank,
                lora_alpha=self.args.lora_alpha,
                lora_dropout=self.args.lora_dropout,
                target_modules=self.args.lora_target_modules,
            )
            peft_config = lora_config.to_hf_peft_config()
            config_path = temp_path / "adapter_config.json"
            with open(config_path, "w") as f:
                json.dump(peft_config, f, indent=2)

            # Save adapter_model.safetensors
            model_path = temp_path / "adapter_model.safetensors"
            safetensors.torch.save_file(lora_state_dict, model_path)

            # Get server URLs for each rollout engine
            server_urls = self._get_sglang_server_urls()

            # Run async LoRA update
            run(self._update_lora_adapter_async(server_urls, temp_path))

    async def _update_lora_adapter_async(self, server_urls: list[str], adapter_path: Path) -> None:
        """Asynchronously update LoRA adapters on all servers."""
        tasks = []
        for url in server_urls:
            task = self._update_single_lora_adapter(url, adapter_path)
            tasks.append(task)

        # Wait for all updates to complete
        await asyncio.gather(*tasks)

    async def _update_single_lora_adapter(self, base_url: str, adapter_path: Path) -> None:
        """Update LoRA adapter on a single SGLang server."""
        try:
            # Unload current adapter
            unload_url = f"{base_url}/unload_lora_adapter"
            logger.info(f"Unloading LoRA adapter from {unload_url}")
            await post(unload_url, {})

            # Load new adapter
            load_url = f"{base_url}/load_lora_adapter"
            logger.info(f"Loading LoRA adapter from {load_url}")

            # Read adapter files
            config_path = adapter_path / "adapter_config.json"
            model_path = adapter_path / "adapter_model.safetensors"

            with open(config_path, "r") as f:
                config_data = json.load(f)

            with open(model_path, "rb") as f:
                model_data = f.read()

            # TODO: Determine correct payload format for load_lora_adapter
            # This may need to be file upload or different format
            payload = {
                "adapter_config": config_data,
                "adapter_model": model_data.hex(),  # Convert bytes to hex string for JSON
            }

            await post(load_url, payload)

        except Exception as e:
            logger.error(f"Failed to update LoRA adapter on {base_url}: {e}")
            raise

    def _get_sglang_server_urls(self) -> list[str]:
        """Get the HTTP URLs for all SGLang rollout servers.

        TODO: Implement proper URL discovery from rollout engines.
        For now, this is a placeholder.
        """
        # Placeholder implementation - need to get actual server URLs
        # This should query the rollout engines for their HTTP endpoints
        logger.warning("Server URL discovery not implemented - using placeholder")
        return ["http://localhost:30000"]  # Placeholder