[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."""
        urls = []

        # Query each rollout engine for its server URL
        for engine in self.rollout_engines:
            try:
                # Assume engines have a method to get their server URL
                # This may need to be implemented in the SGLang engine actor
                url_ref = engine.get_server_url.remote()
                url = ray.get(url_ref)
                urls.append(url)
            except Exception as e:
                logger.error(f"Failed to get server URL from engine: {e}")
                # Fallback: try to construct URL from known patterns
                # This is a temporary workaround until proper URL discovery is implemented
                urls.append("http://localhost:30000")

        logger.info(f"Found {len(urls)} SGLang server URLs: {urls}")
        return urls