We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
自定义模型Launch后无法在控制台删除，命令行也无法删除
### Describe the bug
自定义模型Launch后无法在控制台删除，命令行也无法删除且报错

### To Reproduce
To help us to reproduce this bug, please provide information below:

1. Python 3.10.12
2. xinference, version 0.7.5
3.命令行执行xinference list命令信息如下：
root@ai-gpu:/opt/devlop# xinference list
UID              Type    Name             Format      Size (in billions)  Quantization
---------------  ------  ---------------  --------  --------------------  --------------
baichuan-2-chat  LLM     baichuan-2-chat  pytorch                     13  8-bit
UID                                             Type       Name                                         Dimensions
----------------------------------------------  ---------  -----------------------------------------  ------------
text2vec-large-chinese                          embedding  text2vec-large-chinese                             1024
bge-base-zh-v1.5                                embedding  bge-base-zh-v1.5                                    768
damo/nlp_corom_sentence-embedding_chinese-base  embedding  nlp_corom_sentence-embedding_chinese-base           768

4.命令行执行停止命令报错如下：
root@ai-gpu:/opt/devlop# xinference terminate --model-uid "damo/nlp_corom_sentence-embedding_chinese-base"
Traceback (most recent call last):
  File "/usr/local/bin/xinference", line 8, in <module>
    sys.exit(cli())
  File "/usr/lib/python3/dist-packages/click/core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/click/core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "/usr/lib/python3/dist-packages/click/core.py", line 1659, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/usr/lib/python3/dist-packages/click/core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/lib/python3/dist-packages/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/usr/local/lib/python3.10/dist-packages/xinference/deploy/cmdline.py", line 631, in model_terminate
    client.terminate_model(model_uid=model_uid)
  File "/usr/local/lib/python3.10/dist-packages/xinference/client/restful/restful_client.py", line 771, in terminate_model
    raise RuntimeError(
RuntimeError: Failed to terminate model, detail: Not Found 


### Additional context
服务器日志没有任何异常


### 原因猜测
可能是我的uid中有/引起的，我的uid是`damo/nlp_corom_sentence-embedding_chinese-base  embedding`


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `xinference/model/utils.py`
Content:
```
1 # Copyright 2022-2023 XProbe Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 import json
15 import logging
16 import os
17 from json import JSONDecodeError
18 from pathlib import Path
19 from typing import Any, Callable, Dict, Optional, Tuple
20 
21 from fsspec import AbstractFileSystem
22 
23 from ..constants import XINFERENCE_CACHE_DIR, XINFERENCE_ENV_MODEL_SRC
24 
25 logger = logging.getLogger(__name__)
26 MAX_ATTEMPTS = 3
27 
28 
29 def is_locale_chinese_simplified() -> bool:
30     import locale
31 
32     try:
33         lang, _ = locale.getdefaultlocale()
34         return lang == "zh_CN"
35     except:
36         return False
37 
38 
39 def download_from_modelscope() -> bool:
40     if os.environ.get(XINFERENCE_ENV_MODEL_SRC) == "modelscope":
41         return True
42     elif is_locale_chinese_simplified():
43         return True
44     else:
45         return False
46 
47 
48 def symlink_local_file(path: str, local_dir: str, relpath: str) -> str:
49     from huggingface_hub.file_download import _create_symlink
50 
51     # cross-platform transcription of filename, to be used as a local file path.
52     relative_filename = os.path.join(*relpath.split("/"))
53     if os.name == "nt":
54         if relative_filename.startswith("..\\") or "\\..\\" in relative_filename:
55             raise ValueError(
56                 f"Invalid filename: cannot handle filename '{relative_filename}' on Windows. Please ask the repository"
57                 " owner to rename this file."
58             )
59     # Using `os.path.abspath` instead of `Path.resolve()` to avoid resolving symlinks
60     local_dir_filepath = os.path.join(local_dir, relative_filename)
61     if (
62         Path(os.path.abspath(local_dir))
63         not in Path(os.path.abspath(local_dir_filepath)).parents
64     ):
65         raise ValueError(
66             f"Cannot copy file '{relative_filename}' to local dir '{local_dir}': file would not be in the local"
67             " directory."
68         )
69 
70     os.makedirs(os.path.dirname(local_dir_filepath), exist_ok=True)
71     real_blob_path = os.path.realpath(path)
72     _create_symlink(real_blob_path, local_dir_filepath, new_blob=False)
73     return local_dir_filepath
74 
75 
76 def retry_download(
77     download_func: Callable,
78     model_name: str,
79     model_info: Optional[Dict],
80     *args,
81     **kwargs,
82 ):
83     last_ex = None
84     for current_attempt in range(1, MAX_ATTEMPTS + 1):
85         try:
86             return download_func(*args, **kwargs)
87         except Exception as e:
88             remaining_attempts = MAX_ATTEMPTS - current_attempt
89             last_ex = e
90             logger.debug(
91                 "Download failed: %s, download func: %s, download args: %s, kwargs: %s",
92                 e,
93                 download_func,
94                 args,
95                 kwargs,
96             )
97             logger.warning(
98                 f"Attempt {current_attempt} failed. Remaining attempts: {remaining_attempts}"
99             )
100 
101     else:
102         model_size = (
103             model_info.pop("model_size", None) if model_info is not None else None
104         )
105         model_format = (
106             model_info.pop("model_format", None) if model_info is not None else None
107         )
108         if model_size is not None or model_format is not None:  # LLM models
109             raise RuntimeError(
110                 f"Failed to download model '{model_name}' "
111                 f"(size: {model_size}, format: {model_format}) "
112                 f"after multiple retries"
113             ) from last_ex
114         else:  # Embedding models
115             raise RuntimeError(
116                 f"Failed to download model '{model_name}' after multiple retries"
117             ) from last_ex
118 
119 
120 def valid_model_revision(
121     meta_path: str, expected_model_revision: Optional[str]
122 ) -> bool:
123     if not os.path.exists(meta_path):
124         return False
125     with open(meta_path, "r") as f:
126         try:
127             meta_data = json.load(f)
128         except JSONDecodeError:  # legacy meta file for embedding models
129             logger.debug("Legacy meta file detected.")
130             return True
131 
132         if "model_revision" in meta_data:  # embedding, image
133             real_revision = meta_data["model_revision"]
134         elif "revision" in meta_data:  # llm
135             real_revision = meta_data["revision"]
136         else:
137             logger.warning(
138                 f"No `revision` information in the `__valid_download` file. "
139             )
140             return False
141         return real_revision == expected_model_revision
142 
143 
144 def is_model_cached(model_spec: Any, name_to_revisions_mapping: Dict):
145     cache_dir = os.path.realpath(
146         os.path.join(XINFERENCE_CACHE_DIR, model_spec.model_name)
147     )
148     meta_path = os.path.join(cache_dir, "__valid_download")
149     revisions = name_to_revisions_mapping[model_spec.model_name]
150     if model_spec.model_revision not in revisions:  # Usually for UT
151         revisions.append(model_spec.model_revision)
152     return any([valid_model_revision(meta_path, revision) for revision in revisions])
153 
154 
155 def is_valid_model_name(model_name: str) -> bool:
156     model_name = model_name.strip()
157     return 0 < len(model_name) <= 100
158 
159 
160 def parse_uri(uri: str) -> Tuple[str, str]:
161     import glob
162     from urllib.parse import urlparse
163 
164     if os.path.exists(uri) or glob.glob(uri):
165         return "file", uri
166     else:
167         parsed = urlparse(uri)
168         scheme = parsed.scheme
169         path = parsed.netloc + parsed.path
170         if parsed.scheme == "" or len(parsed.scheme) == 1:  # len == 1 for windows
171             scheme = "file"
172         return scheme, path
173 
174 
175 def is_valid_model_uri(model_uri: Optional[str]) -> bool:
176     if not model_uri:
177         return False
178 
179     src_scheme, src_root = parse_uri(model_uri)
180 
181     if src_scheme == "file":
182         if not os.path.isabs(src_root):
183             raise ValueError(f"Model URI cannot be a relative path: {model_uri}")
184         return os.path.exists(src_root)
185     else:
186         # TODO: handle other schemes.
187         return True
188 
189 
190 def copy_from_src_to_dst(
191     _src_fs: "AbstractFileSystem",
192     _src_path: str,
193     dst_fs: "AbstractFileSystem",
194     dst_path: str,
195     max_attempt: int = 3,
196 ):
197     from tqdm import tqdm
198 
199     for attempt in range(max_attempt):
200         logger.info(f"Copy from {_src_path} to {dst_path}, attempt: {attempt}")
201         try:
202             with _src_fs.open(_src_path, "rb") as src_file:
203                 file_size = _src_fs.info(_src_path)["size"]
204 
205                 dst_fs.makedirs(os.path.dirname(dst_path), exist_ok=True)
206                 with dst_fs.open(dst_path, "wb") as dst_file:
207                     chunk_size = 1024 * 1024  # 1 MB
208 
209                     with tqdm(
210                         total=file_size,
211                         unit="B",
212                         unit_scale=True,
213                         unit_divisor=1024,
214                         desc=_src_path,
215                     ) as pbar:
216                         while True:
217                             chunk = src_file.read(chunk_size)
218                             if not chunk:
219                                 break
220                             dst_file.write(chunk)
221                             pbar.update(len(chunk))
222             logger.info(
223                 f"Copy from {_src_path} to {dst_path} finished, attempt: {attempt}"
224             )
225             break
226         except:
227             logger.error(
228                 f"Failed to copy from {_src_path} to {dst_path} on attempt {attempt + 1}",
229                 exc_info=True,
230             )
231             if attempt + 1 == max_attempt:
232                 raise
233 
234 
235 def patch_trust_remote_code():
236     """sentence-transformers calls transformers without the trust_remote_code=True, some embedding
237     models will fail to load, e.g. jina-embeddings-v2-base-en
238 
239     :return:
240     """
241     try:
242         from transformers.dynamic_module_utils import resolve_trust_remote_code
243     except ImportError:
244         logger.error("Patch transformers trust_remote_code failed.")
245     else:
246 
247         def _patched_resolve_trust_remote_code(*args, **kwargs):
248             logger.info("Patched resolve_trust_remote_code: %s %s", args, kwargs)
249             return True
250 
251         if (
252             resolve_trust_remote_code.__code__
253             != _patched_resolve_trust_remote_code.__code__
254         ):
255             resolve_trust_remote_code.__code__ = (
256                 _patched_resolve_trust_remote_code.__code__
257             )
258 
259 
260 def select_device(device):
261     try:
262         import torch
263     except ImportError:
264         raise ImportError(
265             f"Failed to import module 'torch'. Please make sure 'torch' is installed.\n\n"
266         )
267 
268     if device == "auto":
269         # When env CUDA_VISIBLE_DEVICES=-1, torch.cuda.is_available() return False
270         if torch.cuda.is_available():
271             return "cuda"
272         elif torch.backends.mps.is_available():
273             return "mps"
274         return "cpu"
275     elif device == "cuda":
276         if not torch.cuda.is_available():
277             raise ValueError("cuda is unavailable in your environment")
278     elif device == "mps":
279         if not torch.backends.mps.is_available():
280             raise ValueError("mps is unavailable in your environment")
281     elif device == "cpu":
282         pass
283     else:
284         raise ValueError(f"Device {device} is not supported in temporary")
285     return device
286
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/xinference/model/utils.py b/xinference/model/utils.py
--- a/xinference/model/utils.py
+++ b/xinference/model/utils.py
@@ -153,8 +153,13 @@
 
 
 def is_valid_model_name(model_name: str) -> bool:
-    model_name = model_name.strip()
-    return 0 < len(model_name) <= 100
+    import re
+
+    if len(model_name) == 0:
+        return False
+
+    # check if contains +/?%#&=\s
+    return re.match(r"^[^+\/?%#&=\s]*$", model_name) is not None
 
 
 def parse_uri(uri: str) -> Tuple[str, str]:

{"golden_diff": "diff --git a/xinference/model/utils.py b/xinference/model/utils.py\n--- a/xinference/model/utils.py\n+++ b/xinference/model/utils.py\n@@ -153,8 +153,13 @@\n \n \n def is_valid_model_name(model_name: str) -> bool:\n-    model_name = model_name.strip()\n-    return 0 < len(model_name) <= 100\n+    import re\n+\n+    if len(model_name) == 0:\n+        return False\n+\n+    # check if contains +/?%#&=\\s\n+    return re.match(r\"^[^+\\/?%#&=\\s]*$\", model_name) is not None\n \n \n def parse_uri(uri: str) -> Tuple[str, str]:\n", "issue": "\u81ea\u5b9a\u4e49\u6a21\u578bLaunch\u540e\u65e0\u6cd5\u5728\u63a7\u5236\u53f0\u5220\u9664\uff0c\u547d\u4ee4\u884c\u4e5f\u65e0\u6cd5\u5220\u9664\n### Describe the bug\r\n\u81ea\u5b9a\u4e49\u6a21\u578bLaunch\u540e\u65e0\u6cd5\u5728\u63a7\u5236\u53f0\u5220\u9664\uff0c\u547d\u4ee4\u884c\u4e5f\u65e0\u6cd5\u5220\u9664\u4e14\u62a5\u9519\r\n\r\n### To Reproduce\r\nTo help us to reproduce this bug, please provide information below:\r\n\r\n1. Python 3.10.12\r\n2. xinference, version 0.7.5\r\n3.\u547d\u4ee4\u884c\u6267\u884cxinference list\u547d\u4ee4\u4fe1\u606f\u5982\u4e0b\uff1a\r\nroot@ai-gpu:/opt/devlop# xinference list\r\nUID              Type    Name             Format      Size (in billions)  Quantization\r\n---------------  ------  ---------------  --------  --------------------  --------------\r\nbaichuan-2-chat  LLM     baichuan-2-chat  pytorch                     13  8-bit\r\nUID                                             Type       Name                                         Dimensions\r\n----------------------------------------------  ---------  -----------------------------------------  ------------\r\ntext2vec-large-chinese                          embedding  text2vec-large-chinese                             1024\r\nbge-base-zh-v1.5                                embedding  bge-base-zh-v1.5                                    768\r\ndamo/nlp_corom_sentence-embedding_chinese-base  embedding  nlp_corom_sentence-embedding_chinese-base           768\r\n\r\n4.\u547d\u4ee4\u884c\u6267\u884c\u505c\u6b62\u547d\u4ee4\u62a5\u9519\u5982\u4e0b\uff1a\r\nroot@ai-gpu:/opt/devlop# xinference terminate --model-uid \"damo/nlp_corom_sentence-embedding_chinese-base\"\r\nTraceback (most recent call last):\r\n  File \"/usr/local/bin/xinference\", line 8, in <module>\r\n    sys.exit(cli())\r\n  File \"/usr/lib/python3/dist-packages/click/core.py\", line 1128, in __call__\r\n    return self.main(*args, **kwargs)\r\n  File \"/usr/lib/python3/dist-packages/click/core.py\", line 1053, in main\r\n    rv = self.invoke(ctx)\r\n  File \"/usr/lib/python3/dist-packages/click/core.py\", line 1659, in invoke\r\n    return _process_result(sub_ctx.command.invoke(sub_ctx))\r\n  File \"/usr/lib/python3/dist-packages/click/core.py\", line 1395, in invoke\r\n    return ctx.invoke(self.callback, **ctx.params)\r\n  File \"/usr/lib/python3/dist-packages/click/core.py\", line 754, in invoke\r\n    return __callback(*args, **kwargs)\r\n  File \"/usr/local/lib/python3.10/dist-packages/xinference/deploy/cmdline.py\", line 631, in model_terminate\r\n    client.terminate_model(model_uid=model_uid)\r\n  File \"/usr/local/lib/python3.10/dist-packages/xinference/client/restful/restful_client.py\", line 771, in terminate_model\r\n    raise RuntimeError(\r\nRuntimeError: Failed to terminate model, detail: Not Found \r\n\r\n\r\n### Additional context\r\n\u670d\u52a1\u5668\u65e5\u5fd7\u6ca1\u6709\u4efb\u4f55\u5f02\u5e38\r\n\r\n\r\n### \u539f\u56e0\u731c\u6d4b\r\n\u53ef\u80fd\u662f\u6211\u7684uid\u4e2d\u6709/\u5f15\u8d77\u7684\uff0c\u6211\u7684uid\u662f`damo/nlp_corom_sentence-embedding_chinese-base  embedding`\r\n\n", "before_files": [{"content": "# Copyright 2022-2023 XProbe Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport json\nimport logging\nimport os\nfrom json import JSONDecodeError\nfrom pathlib import Path\nfrom typing import Any, Callable, Dict, Optional, Tuple\n\nfrom fsspec import AbstractFileSystem\n\nfrom ..constants import XINFERENCE_CACHE_DIR, XINFERENCE_ENV_MODEL_SRC\n\nlogger = logging.getLogger(__name__)\nMAX_ATTEMPTS = 3\n\n\ndef is_locale_chinese_simplified() -> bool:\n    import locale\n\n    try:\n        lang, _ = locale.getdefaultlocale()\n        return lang == \"zh_CN\"\n    except:\n        return False\n\n\ndef download_from_modelscope() -> bool:\n    if os.environ.get(XINFERENCE_ENV_MODEL_SRC) == \"modelscope\":\n        return True\n    elif is_locale_chinese_simplified():\n        return True\n    else:\n        return False\n\n\ndef symlink_local_file(path: str, local_dir: str, relpath: str) -> str:\n    from huggingface_hub.file_download import _create_symlink\n\n    # cross-platform transcription of filename, to be used as a local file path.\n    relative_filename = os.path.join(*relpath.split(\"/\"))\n    if os.name == \"nt\":\n        if relative_filename.startswith(\"..\\\\\") or \"\\\\..\\\\\" in relative_filename:\n            raise ValueError(\n                f\"Invalid filename: cannot handle filename '{relative_filename}' on Windows. Please ask the repository\"\n                \" owner to rename this file.\"\n            )\n    # Using `os.path.abspath` instead of `Path.resolve()` to avoid resolving symlinks\n    local_dir_filepath = os.path.join(local_dir, relative_filename)\n    if (\n        Path(os.path.abspath(local_dir))\n        not in Path(os.path.abspath(local_dir_filepath)).parents\n    ):\n        raise ValueError(\n            f\"Cannot copy file '{relative_filename}' to local dir '{local_dir}': file would not be in the local\"\n            \" directory.\"\n        )\n\n    os.makedirs(os.path.dirname(local_dir_filepath), exist_ok=True)\n    real_blob_path = os.path.realpath(path)\n    _create_symlink(real_blob_path, local_dir_filepath, new_blob=False)\n    return local_dir_filepath\n\n\ndef retry_download(\n    download_func: Callable,\n    model_name: str,\n    model_info: Optional[Dict],\n    *args,\n    **kwargs,\n):\n    last_ex = None\n    for current_attempt in range(1, MAX_ATTEMPTS + 1):\n        try:\n            return download_func(*args, **kwargs)\n        except Exception as e:\n            remaining_attempts = MAX_ATTEMPTS - current_attempt\n            last_ex = e\n            logger.debug(\n                \"Download failed: %s, download func: %s, download args: %s, kwargs: %s\",\n                e,\n                download_func,\n                args,\n                kwargs,\n            )\n            logger.warning(\n                f\"Attempt {current_attempt} failed. Remaining attempts: {remaining_attempts}\"\n            )\n\n    else:\n        model_size = (\n            model_info.pop(\"model_size\", None) if model_info is not None else None\n        )\n        model_format = (\n            model_info.pop(\"model_format\", None) if model_info is not None else None\n        )\n        if model_size is not None or model_format is not None:  # LLM models\n            raise RuntimeError(\n                f\"Failed to download model '{model_name}' \"\n                f\"(size: {model_size}, format: {model_format}) \"\n                f\"after multiple retries\"\n            ) from last_ex\n        else:  # Embedding models\n            raise RuntimeError(\n                f\"Failed to download model '{model_name}' after multiple retries\"\n            ) from last_ex\n\n\ndef valid_model_revision(\n    meta_path: str, expected_model_revision: Optional[str]\n) -> bool:\n    if not os.path.exists(meta_path):\n        return False\n    with open(meta_path, \"r\") as f:\n        try:\n            meta_data = json.load(f)\n        except JSONDecodeError:  # legacy meta file for embedding models\n            logger.debug(\"Legacy meta file detected.\")\n            return True\n\n        if \"model_revision\" in meta_data:  # embedding, image\n            real_revision = meta_data[\"model_revision\"]\n        elif \"revision\" in meta_data:  # llm\n            real_revision = meta_data[\"revision\"]\n        else:\n            logger.warning(\n                f\"No `revision` information in the `__valid_download` file. \"\n            )\n            return False\n        return real_revision == expected_model_revision\n\n\ndef is_model_cached(model_spec: Any, name_to_revisions_mapping: Dict):\n    cache_dir = os.path.realpath(\n        os.path.join(XINFERENCE_CACHE_DIR, model_spec.model_name)\n    )\n    meta_path = os.path.join(cache_dir, \"__valid_download\")\n    revisions = name_to_revisions_mapping[model_spec.model_name]\n    if model_spec.model_revision not in revisions:  # Usually for UT\n        revisions.append(model_spec.model_revision)\n    return any([valid_model_revision(meta_path, revision) for revision in revisions])\n\n\ndef is_valid_model_name(model_name: str) -> bool:\n    model_name = model_name.strip()\n    return 0 < len(model_name) <= 100\n\n\ndef parse_uri(uri: str) -> Tuple[str, str]:\n    import glob\n    from urllib.parse import urlparse\n\n    if os.path.exists(uri) or glob.glob(uri):\n        return \"file\", uri\n    else:\n        parsed = urlparse(uri)\n        scheme = parsed.scheme\n        path = parsed.netloc + parsed.path\n        if parsed.scheme == \"\" or len(parsed.scheme) == 1:  # len == 1 for windows\n            scheme = \"file\"\n        return scheme, path\n\n\ndef is_valid_model_uri(model_uri: Optional[str]) -> bool:\n    if not model_uri:\n        return False\n\n    src_scheme, src_root = parse_uri(model_uri)\n\n    if src_scheme == \"file\":\n        if not os.path.isabs(src_root):\n            raise ValueError(f\"Model URI cannot be a relative path: {model_uri}\")\n        return os.path.exists(src_root)\n    else:\n        # TODO: handle other schemes.\n        return True\n\n\ndef copy_from_src_to_dst(\n    _src_fs: \"AbstractFileSystem\",\n    _src_path: str,\n    dst_fs: \"AbstractFileSystem\",\n    dst_path: str,\n    max_attempt: int = 3,\n):\n    from tqdm import tqdm\n\n    for attempt in range(max_attempt):\n        logger.info(f\"Copy from {_src_path} to {dst_path}, attempt: {attempt}\")\n        try:\n            with _src_fs.open(_src_path, \"rb\") as src_file:\n                file_size = _src_fs.info(_src_path)[\"size\"]\n\n                dst_fs.makedirs(os.path.dirname(dst_path), exist_ok=True)\n                with dst_fs.open(dst_path, \"wb\") as dst_file:\n                    chunk_size = 1024 * 1024  # 1 MB\n\n                    with tqdm(\n                        total=file_size,\n                        unit=\"B\",\n                        unit_scale=True,\n                        unit_divisor=1024,\n                        desc=_src_path,\n                    ) as pbar:\n                        while True:\n                            chunk = src_file.read(chunk_size)\n                            if not chunk:\n                                break\n                            dst_file.write(chunk)\n                            pbar.update(len(chunk))\n            logger.info(\n                f\"Copy from {_src_path} to {dst_path} finished, attempt: {attempt}\"\n            )\n            break\n        except:\n            logger.error(\n                f\"Failed to copy from {_src_path} to {dst_path} on attempt {attempt + 1}\",\n                exc_info=True,\n            )\n            if attempt + 1 == max_attempt:\n                raise\n\n\ndef patch_trust_remote_code():\n    \"\"\"sentence-transformers calls transformers without the trust_remote_code=True, some embedding\n    models will fail to load, e.g. jina-embeddings-v2-base-en\n\n    :return:\n    \"\"\"\n    try:\n        from transformers.dynamic_module_utils import resolve_trust_remote_code\n    except ImportError:\n        logger.error(\"Patch transformers trust_remote_code failed.\")\n    else:\n\n        def _patched_resolve_trust_remote_code(*args, **kwargs):\n            logger.info(\"Patched resolve_trust_remote_code: %s %s\", args, kwargs)\n            return True\n\n        if (\n            resolve_trust_remote_code.__code__\n            != _patched_resolve_trust_remote_code.__code__\n        ):\n            resolve_trust_remote_code.__code__ = (\n                _patched_resolve_trust_remote_code.__code__\n            )\n\n\ndef select_device(device):\n    try:\n        import torch\n    except ImportError:\n        raise ImportError(\n            f\"Failed to import module 'torch'. Please make sure 'torch' is installed.\\n\\n\"\n        )\n\n    if device == \"auto\":\n        # When env CUDA_VISIBLE_DEVICES=-1, torch.cuda.is_available() return False\n        if torch.cuda.is_available():\n            return \"cuda\"\n        elif torch.backends.mps.is_available():\n            return \"mps\"\n        return \"cpu\"\n    elif device == \"cuda\":\n        if not torch.cuda.is_available():\n            raise ValueError(\"cuda is unavailable in your environment\")\n    elif device == \"mps\":\n        if not torch.backends.mps.is_available():\n            raise ValueError(\"mps is unavailable in your environment\")\n    elif device == \"cpu\":\n        pass\n    else:\n        raise ValueError(f\"Device {device} is not supported in temporary\")\n    return device\n", "path": "xinference/model/utils.py"}], "after_files": [{"content": "# Copyright 2022-2023 XProbe Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport json\nimport logging\nimport os\nfrom json import JSONDecodeError\nfrom pathlib import Path\nfrom typing import Any, Callable, Dict, Optional, Tuple\n\nfrom fsspec import AbstractFileSystem\n\nfrom ..constants import XINFERENCE_CACHE_DIR, XINFERENCE_ENV_MODEL_SRC\n\nlogger = logging.getLogger(__name__)\nMAX_ATTEMPTS = 3\n\n\ndef is_locale_chinese_simplified() -> bool:\n    import locale\n\n    try:\n        lang, _ = locale.getdefaultlocale()\n        return lang == \"zh_CN\"\n    except:\n        return False\n\n\ndef download_from_modelscope() -> bool:\n    if os.environ.get(XINFERENCE_ENV_MODEL_SRC) == \"modelscope\":\n        return True\n    elif is_locale_chinese_simplified():\n        return True\n    else:\n        return False\n\n\ndef symlink_local_file(path: str, local_dir: str, relpath: str) -> str:\n    from huggingface_hub.file_download import _create_symlink\n\n    # cross-platform transcription of filename, to be used as a local file path.\n    relative_filename = os.path.join(*relpath.split(\"/\"))\n    if os.name == \"nt\":\n        if relative_filename.startswith(\"..\\\\\") or \"\\\\..\\\\\" in relative_filename:\n            raise ValueError(\n                f\"Invalid filename: cannot handle filename '{relative_filename}' on Windows. Please ask the repository\"\n                \" owner to rename this file.\"\n            )\n    # Using `os.path.abspath` instead of `Path.resolve()` to avoid resolving symlinks\n    local_dir_filepath = os.path.join(local_dir, relative_filename)\n    if (\n        Path(os.path.abspath(local_dir))\n        not in Path(os.path.abspath(local_dir_filepath)).parents\n    ):\n        raise ValueError(\n            f\"Cannot copy file '{relative_filename}' to local dir '{local_dir}': file would not be in the local\"\n            \" directory.\"\n        )\n\n    os.makedirs(os.path.dirname(local_dir_filepath), exist_ok=True)\n    real_blob_path = os.path.realpath(path)\n    _create_symlink(real_blob_path, local_dir_filepath, new_blob=False)\n    return local_dir_filepath\n\n\ndef retry_download(\n    download_func: Callable,\n    model_name: str,\n    model_info: Optional[Dict],\n    *args,\n    **kwargs,\n):\n    last_ex = None\n    for current_attempt in range(1, MAX_ATTEMPTS + 1):\n        try:\n            return download_func(*args, **kwargs)\n        except Exception as e:\n            remaining_attempts = MAX_ATTEMPTS - current_attempt\n            last_ex = e\n            logger.debug(\n                \"Download failed: %s, download func: %s, download args: %s, kwargs: %s\",\n                e,\n                download_func,\n                args,\n                kwargs,\n            )\n            logger.warning(\n                f\"Attempt {current_attempt} failed. Remaining attempts: {remaining_attempts}\"\n            )\n\n    else:\n        model_size = (\n            model_info.pop(\"model_size\", None) if model_info is not None else None\n        )\n        model_format = (\n            model_info.pop(\"model_format\", None) if model_info is not None else None\n        )\n        if model_size is not None or model_format is not None:  # LLM models\n            raise RuntimeError(\n                f\"Failed to download model '{model_name}' \"\n                f\"(size: {model_size}, format: {model_format}) \"\n                f\"after multiple retries\"\n            ) from last_ex\n        else:  # Embedding models\n            raise RuntimeError(\n                f\"Failed to download model '{model_name}' after multiple retries\"\n            ) from last_ex\n\n\ndef valid_model_revision(\n    meta_path: str, expected_model_revision: Optional[str]\n) -> bool:\n    if not os.path.exists(meta_path):\n        return False\n    with open(meta_path, \"r\") as f:\n        try:\n            meta_data = json.load(f)\n        except JSONDecodeError:  # legacy meta file for embedding models\n            logger.debug(\"Legacy meta file detected.\")\n            return True\n\n        if \"model_revision\" in meta_data:  # embedding, image\n            real_revision = meta_data[\"model_revision\"]\n        elif \"revision\" in meta_data:  # llm\n            real_revision = meta_data[\"revision\"]\n        else:\n            logger.warning(\n                f\"No `revision` information in the `__valid_download` file. \"\n            )\n            return False\n        return real_revision == expected_model_revision\n\n\ndef is_model_cached(model_spec: Any, name_to_revisions_mapping: Dict):\n    cache_dir = os.path.realpath(\n        os.path.join(XINFERENCE_CACHE_DIR, model_spec.model_name)\n    )\n    meta_path = os.path.join(cache_dir, \"__valid_download\")\n    revisions = name_to_revisions_mapping[model_spec.model_name]\n    if model_spec.model_revision not in revisions:  # Usually for UT\n        revisions.append(model_spec.model_revision)\n    return any([valid_model_revision(meta_path, revision) for revision in revisions])\n\n\ndef is_valid_model_name(model_name: str) -> bool:\n    import re\n\n    if len(model_name) == 0:\n        return False\n\n    # check if contains +/?%#&=\\s\n    return re.match(r\"^[^+\\/?%#&=\\s]*$\", model_name) is not None\n\n\ndef parse_uri(uri: str) -> Tuple[str, str]:\n    import glob\n    from urllib.parse import urlparse\n\n    if os.path.exists(uri) or glob.glob(uri):\n        return \"file\", uri\n    else:\n        parsed = urlparse(uri)\n        scheme = parsed.scheme\n        path = parsed.netloc + parsed.path\n        if parsed.scheme == \"\" or len(parsed.scheme) == 1:  # len == 1 for windows\n            scheme = \"file\"\n        return scheme, path\n\n\ndef is_valid_model_uri(model_uri: Optional[str]) -> bool:\n    if not model_uri:\n        return False\n\n    src_scheme, src_root = parse_uri(model_uri)\n\n    if src_scheme == \"file\":\n        if not os.path.isabs(src_root):\n            raise ValueError(f\"Model URI cannot be a relative path: {model_uri}\")\n        return os.path.exists(src_root)\n    else:\n        # TODO: handle other schemes.\n        return True\n\n\ndef copy_from_src_to_dst(\n    _src_fs: \"AbstractFileSystem\",\n    _src_path: str,\n    dst_fs: \"AbstractFileSystem\",\n    dst_path: str,\n    max_attempt: int = 3,\n):\n    from tqdm import tqdm\n\n    for attempt in range(max_attempt):\n        logger.info(f\"Copy from {_src_path} to {dst_path}, attempt: {attempt}\")\n        try:\n            with _src_fs.open(_src_path, \"rb\") as src_file:\n                file_size = _src_fs.info(_src_path)[\"size\"]\n\n                dst_fs.makedirs(os.path.dirname(dst_path), exist_ok=True)\n                with dst_fs.open(dst_path, \"wb\") as dst_file:\n                    chunk_size = 1024 * 1024  # 1 MB\n\n                    with tqdm(\n                        total=file_size,\n                        unit=\"B\",\n                        unit_scale=True,\n                        unit_divisor=1024,\n                        desc=_src_path,\n                    ) as pbar:\n                        while True:\n                            chunk = src_file.read(chunk_size)\n                            if not chunk:\n                                break\n                            dst_file.write(chunk)\n                            pbar.update(len(chunk))\n            logger.info(\n                f\"Copy from {_src_path} to {dst_path} finished, attempt: {attempt}\"\n            )\n            break\n        except:\n            logger.error(\n                f\"Failed to copy from {_src_path} to {dst_path} on attempt {attempt + 1}\",\n                exc_info=True,\n            )\n            if attempt + 1 == max_attempt:\n                raise\n\n\ndef patch_trust_remote_code():\n    \"\"\"sentence-transformers calls transformers without the trust_remote_code=True, some embedding\n    models will fail to load, e.g. jina-embeddings-v2-base-en\n\n    :return:\n    \"\"\"\n    try:\n        from transformers.dynamic_module_utils import resolve_trust_remote_code\n    except ImportError:\n        logger.error(\"Patch transformers trust_remote_code failed.\")\n    else:\n\n        def _patched_resolve_trust_remote_code(*args, **kwargs):\n            logger.info(\"Patched resolve_trust_remote_code: %s %s\", args, kwargs)\n            return True\n\n        if (\n            resolve_trust_remote_code.__code__\n            != _patched_resolve_trust_remote_code.__code__\n        ):\n            resolve_trust_remote_code.__code__ = (\n                _patched_resolve_trust_remote_code.__code__\n            )\n\n\ndef select_device(device):\n    try:\n        import torch\n    except ImportError:\n        raise ImportError(\n            f\"Failed to import module 'torch'. Please make sure 'torch' is installed.\\n\\n\"\n        )\n\n    if device == \"auto\":\n        # When env CUDA_VISIBLE_DEVICES=-1, torch.cuda.is_available() return False\n        if torch.cuda.is_available():\n            return \"cuda\"\n        elif torch.backends.mps.is_available():\n            return \"mps\"\n        return \"cpu\"\n    elif device == \"cuda\":\n        if not torch.cuda.is_available():\n            raise ValueError(\"cuda is unavailable in your environment\")\n    elif device == \"mps\":\n        if not torch.backends.mps.is_available():\n            raise ValueError(\"mps is unavailable in your environment\")\n    elif device == \"cpu\":\n        pass\n    else:\n        raise ValueError(f\"Device {device} is not supported in temporary\")\n    return device\n", "path": "xinference/model/utils.py"}]}