[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.

        In RL training, we directly replace the current LoRA weights without saving/loading 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 LoRA config
        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()

        # 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, lora_state_dict, peft_config))

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

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

    async def _update_single_lora_adapter(self, base_url: str, lora_state_dict: dict, peft_config: dict) -> 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 with current weights
            load_url = f"{base_url}/load_lora_adapter"
            logger.info(f"Loading LoRA adapter to {load_url}")

            # Convert state dict to safetensors format in memory
            import io
            buffer = io.BytesIO()
            safetensors.torch.save_file(lora_state_dict, buffer)
            model_bytes = buffer.getvalue()

            # Send adapter config and model data
            payload = {
                "adapter_config": peft_config,
                "adapter_model": model_bytes.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