pcuenq HF staff commited on
Commit
0313d6a
1 Parent(s): 7216234

Replace with Docker frontend

Browse files
Files changed (4) hide show
  1. Dockerfile +30 -0
  2. app.py +0 -469
  3. requirements.txt +0 -5
  4. startup.sh +13 -0
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9.16
2
+ ENV DEBIAN_FRONTEND=noninteractive \
3
+ TZ=Europe/Paris
4
+
5
+ # BEGIN root part
6
+
7
+ # Setup tailscale
8
+ WORKDIR /bin
9
+ ENV TSFILE=tailscale_1.38.2_amd64.tgz
10
+ RUN wget https://pkgs.tailscale.com/stable/${TSFILE} && \
11
+ tar xzf ${TSFILE} --strip-components=1
12
+ RUN mkdir -p /var/run && ln -s /tmp/tailscale /var/run/tailscale && \
13
+ mkdir -p /var/cache && ln -s /tmp/tailscale /var/cache/tailscale && \
14
+ mkdir -p /var/lib && ln -s /tmp/tailscale /var/lib/tailscale && \
15
+ mkdir -p /var/task && ln -s /tmp/tailscale /var/task/tailscale
16
+
17
+ # Install socat
18
+ RUN apt-get update && apt-get -y install socat
19
+
20
+ # User
21
+ RUN useradd -m -u 1000 user
22
+ USER user
23
+ ENV HOME=/home/user \
24
+ PATH=/home/user/.local/bin:$PATH
25
+ WORKDIR /home/user/app
26
+
27
+ COPY --link --chown=1000 ./ $HOME/app
28
+
29
+ ENTRYPOINT $HOME/app/startup.sh
30
+
app.py DELETED
@@ -1,469 +0,0 @@
1
- import gradio as gr
2
- import json
3
- import shutil
4
- import subprocess
5
- import urllib.parse
6
- from pathlib import Path
7
-
8
- from huggingface_hub import hf_hub_download, HfApi, scan_cache_dir
9
- from coremltools import ComputeUnit
10
- from coremltools.models.utils import _is_macos, _macos_version
11
-
12
- from transformers.onnx.utils import get_preprocessor
13
-
14
- from exporters.coreml import export
15
- from exporters.coreml.features import FeaturesManager
16
- from exporters.coreml.validate import validate_model_outputs
17
-
18
- compute_units_mapping = {
19
- "All": ComputeUnit.ALL,
20
- "CPU": ComputeUnit.CPU_ONLY,
21
- "CPU + GPU": ComputeUnit.CPU_AND_GPU,
22
- "CPU + NE": ComputeUnit.CPU_AND_NE,
23
- }
24
- compute_units_labels = list(compute_units_mapping.keys())
25
-
26
- framework_mapping = {
27
- "PyTorch": "pt",
28
- "TensorFlow": "tf",
29
- }
30
- framework_labels = list(framework_mapping.keys())
31
-
32
- precision_mapping = {
33
- "Float32": "float32",
34
- "Float16 quantization": "float16",
35
- }
36
- precision_labels = list(precision_mapping.keys())
37
-
38
- tolerance_mapping = {
39
- "Model default": None,
40
- "1e-2": 1e-2,
41
- "1e-3": 1e-3,
42
- "1e-4": 1e-4,
43
- }
44
- tolerance_labels = list(tolerance_mapping.keys())
45
-
46
- push_mapping = {
47
- "Submit a PR to the original repo": "pr",
48
- "Create a new repo": "new",
49
- }
50
- push_labels = list(push_mapping.keys())
51
-
52
- tasks_mapping = {
53
- "default": "Feature Extraction",
54
- "causal-lm": "Text Generation",
55
- "ctc": "CTC (Connectionist Temporal Classification)",
56
- "image-classification": "Image Classification",
57
- "image-segmentation": "Image Segmentation",
58
- "masked-im": "Image Fill-Mask",
59
- "masked-lm": "Fill-Mask",
60
- "multiple-choice": "Multiple Choice",
61
- "next-sentence-prediction": "Next Sentence Prediction",
62
- "object-detection": "Object Detection",
63
- "question-answering": "Question Answering",
64
- "semantic-segmentation": "Semantic Segmentation",
65
- "seq2seq-lm": "Text to Text Generation",
66
- "sequence-classification": "Text Classification",
67
- "speech-seq2seq": "Audio to Audio",
68
- "token-classification": "Token Classification",
69
- }
70
- reverse_tasks_mapping = {v: k for k, v in tasks_mapping.items()}
71
- tasks_labels = list(tasks_mapping.keys())
72
-
73
- # Map pipeline_tag to internal exporters features/tasks
74
- tags_to_tasks_mapping = {
75
- "feature-extraction": "default",
76
- "text-generation": "causal-lm",
77
- "image-classification": "image-classification",
78
- "image-segmentation": "image-segmentation",
79
- "fill-mask": "masked-lm",
80
- "object-detection": "object-detection",
81
- "question-answering": "question-answering",
82
- "text2text-generation": "seq2seq-lm",
83
- "text-classification": "sequence-classification",
84
- "token-classification": "token-classification",
85
- }
86
-
87
- def error_str(error, title="Error", model=None, task=None, framework=None, compute_units=None, precision=None, tolerance=None, destination=None, open_discussion=True):
88
- if not error: return ""
89
-
90
- discussion_text = ""
91
- if open_discussion:
92
- issue_title = urllib.parse.quote(f"Error converting {model}")
93
- issue_description = urllib.parse.quote(f"""Conversion Settings:
94
-
95
- Model: {model}
96
- Task: {task}
97
- Framework: {framework}
98
- Compute Units: {compute_units}
99
- Precision: {precision}
100
- Tolerance: {tolerance}
101
- Push to: {destination}
102
-
103
- Error: {error}
104
- """)
105
- issue_url = f"https://huggingface.co/spaces/pcuenq/transformers-to-coreml/discussions/new?title={issue_title}&description={issue_description}"
106
- discussion_text = f"You can open a discussion on the [Hugging Face Hub]({issue_url}) to report this issue."
107
- return f"""
108
- #### {title}
109
- {error}
110
-
111
- {discussion_text}
112
- """
113
-
114
- def url_to_model_id(model_id_str):
115
- if not model_id_str.startswith("https://huggingface.co/"): return model_id_str
116
- return model_id_str.split("/")[-2] + "/" + model_id_str.split("/")[-1]
117
-
118
- def get_pr_url(api, repo_id, title):
119
- try:
120
- discussions = api.get_repo_discussions(repo_id=repo_id)
121
- except Exception:
122
- return None
123
- for discussion in discussions:
124
- if (
125
- discussion.status == "open"
126
- and discussion.is_pull_request
127
- and discussion.title == title
128
- ):
129
- return f"https://huggingface.co/{repo_id}/discussions/{discussion.num}"
130
-
131
- def retrieve_model_info(model_id):
132
- api = HfApi()
133
- model_info = api.model_info(model_id)
134
- tags = model_info.tags
135
- frameworks = [tag for tag in tags if tag in ["pytorch", "tf"]]
136
- return {
137
- "pipeline_tag": model_info.pipeline_tag,
138
- "frameworks": sorted(["PyTorch" if f == "pytorch" else "TensorFlow" for f in frameworks]),
139
- }
140
-
141
- def supported_frameworks(model_info):
142
- """
143
- Return a list of supported frameworks (`PyTorch` or `TensorFlow`) for a given model_id.
144
- Only PyTorch and Tensorflow are supported.
145
- """
146
- api = HfApi()
147
- model_info = api.model_info(model_id)
148
- tags = model_info.tags
149
- frameworks = [tag for tag in tags if tag in ["pytorch", "tf"]]
150
- return sorted(["PyTorch" if f == "pytorch" else "TensorFlow" for f in frameworks])
151
-
152
- def on_model_change(model):
153
- model = url_to_model_id(model)
154
- tasks = None
155
- error = None
156
- frameworks = []
157
- selected_framework = None
158
- selected_task = None
159
-
160
- try:
161
- config_file = hf_hub_download(model, filename="config.json")
162
- if config_file is None:
163
- raise Exception(f"Model {model} not found")
164
-
165
- with open(config_file, "r") as f:
166
- config_json = f.read()
167
-
168
- config = json.loads(config_json)
169
- model_type = config["model_type"]
170
-
171
- # Ignore `-with-past` for now
172
- features = FeaturesManager.get_supported_features_for_model_type(model_type)
173
- tasks = list(features.keys())
174
- tasks = [task for task in tasks if "-with-past" not in task]
175
-
176
- model_info = retrieve_model_info(model)
177
- frameworks = model_info["frameworks"]
178
- selected_framework = frameworks[0] if len(frameworks) > 0 else None
179
-
180
- pipeline_tag = model_info["pipeline_tag"]
181
- # print(pipeline_tag)
182
- # Select the task corresponding to the pipeline tag
183
- if tasks:
184
- if pipeline_tag in tags_to_tasks_mapping:
185
- selected_task = tags_to_tasks_mapping[pipeline_tag]
186
- else:
187
- selected_task = tasks[0]
188
-
189
- # Convert to UI labels
190
- tasks = [tasks_mapping[task] for task in tasks]
191
- selected_task = tasks_mapping[selected_task]
192
-
193
- except Exception as e:
194
- error = e
195
- model_type = None
196
-
197
- return (
198
- gr.update(visible=bool(model_type)), # Settings column
199
- gr.update(choices=tasks, value=selected_task), # Tasks
200
- gr.update(visible=len(frameworks)>1, choices=frameworks, value=selected_framework), # Frameworks
201
- gr.update(value=error_str(error, model=model)), # Error
202
- )
203
-
204
-
205
- def convert_model(preprocessor, model, model_coreml_config,
206
- compute_units, precision, tolerance, output,
207
- use_past=False, seq2seq=None,
208
- progress=None, progress_start=0.1, progress_end=0.8):
209
- coreml_config = model_coreml_config(model.config, use_past=use_past, seq2seq=seq2seq)
210
-
211
- model_label = "model" if seq2seq is None else seq2seq
212
- progress(progress_start, desc=f"Converting {model_label}")
213
- mlmodel = export(
214
- preprocessor,
215
- model,
216
- coreml_config,
217
- quantize=precision,
218
- compute_units=compute_units,
219
- )
220
-
221
- filename = output
222
- if seq2seq == "encoder":
223
- filename = filename.parent / ("encoder_" + filename.name)
224
- elif seq2seq == "decoder":
225
- filename = filename.parent / ("decoder_" + filename.name)
226
- filename = filename.as_posix()
227
-
228
- mlmodel.save(filename)
229
-
230
- if _is_macos() and _macos_version() >= (12, 0):
231
- progress(progress_end * 0.8, desc=f"Validating {model_label}")
232
- if tolerance is None:
233
- tolerance = coreml_config.atol_for_validation
234
- validate_model_outputs(coreml_config, preprocessor, model, mlmodel, tolerance)
235
- progress(progress_end, desc=f"Done converting {model_label}")
236
-
237
-
238
- def push_to_hub(destination, directory, task, precision, token=None):
239
- api = HfApi(token=token)
240
- api.create_repo(destination, token=token, exist_ok=True)
241
- commit_message="Add Core ML conversion"
242
- api.upload_folder(
243
- folder_path=directory,
244
- repo_id=destination,
245
- token=token,
246
- create_pr=True,
247
- commit_message=commit_message,
248
- commit_description=f"Core ML conversion, task={task}, precision={precision}",
249
- )
250
-
251
- subprocess.run(["rm", "-rf", directory])
252
- return get_pr_url(HfApi(token=token), destination, commit_message)
253
-
254
-
255
- def cleanup(model_id, exported):
256
- if exported:
257
- shutil.rmtree(exported)
258
-
259
- # We remove the model from the huggingface cache, so it will have to be downloaded again
260
- # if the user wants to convert it for a different task or precision.
261
- # Alternatively, we could remove models older than 1 day or so.
262
- cache_info = scan_cache_dir()
263
- try:
264
- repo = next(repo for repo in cache_info.repos if repo.repo_id==model_id)
265
- except StopIteration:
266
- # The model was not in the cache!
267
- return
268
-
269
- if repo is not None:
270
- for revision in repo.revisions:
271
- delete_strategy = cache_info.delete_revisions(revision.commit_hash)
272
- delete_strategy.execute()
273
-
274
-
275
- def convert(model_id, task,
276
- compute_units, precision, tolerance, framework,
277
- push_destination, destination_model, token,
278
- progress=gr.Progress()):
279
- model_id = url_to_model_id(model_id)
280
- task = reverse_tasks_mapping[task]
281
- compute_units = compute_units_mapping[compute_units]
282
- precision = precision_mapping[precision]
283
- tolerance = tolerance_mapping[tolerance]
284
- framework = framework_mapping[framework]
285
- push_destination = push_mapping[push_destination]
286
- if push_destination == "pr":
287
- destination_model = model_id
288
-
289
- if token is None or token == "":
290
- return error_str("Please provide a token to push to the Hub.", open_discussion=False)
291
-
292
- # TODO: support legacy format
293
- exported_base = Path("exported")/model_id
294
- output = exported_base/"coreml"/task
295
- output.mkdir(parents=True, exist_ok=True)
296
- output = output/f"{precision}_model.mlpackage"
297
-
298
- try:
299
- progress(0, desc="Downloading model")
300
-
301
- preprocessor = get_preprocessor(model_id)
302
- model = FeaturesManager.get_model_from_feature(task, model_id, framework=framework)
303
- _, model_coreml_config = FeaturesManager.check_supported_model_or_raise(model, feature=task)
304
-
305
- if task in ["seq2seq-lm", "speech-seq2seq"]:
306
- convert_model(
307
- preprocessor,
308
- model,
309
- model_coreml_config,
310
- compute_units,
311
- precision,
312
- tolerance,
313
- output,
314
- seq2seq="encoder",
315
- progress=progress,
316
- progress_start=0.1,
317
- progress_end=0.4,
318
- )
319
- progress(0.4, desc="Converting decoder")
320
- convert_model(
321
- preprocessor,
322
- model,
323
- model_coreml_config,
324
- compute_units,
325
- precision,
326
- tolerance,
327
- output,
328
- seq2seq="decoder",
329
- progress=progress,
330
- progress_start=0.4,
331
- progress_end=0.7,
332
- )
333
- else:
334
- convert_model(
335
- preprocessor,
336
- model,
337
- model_coreml_config,
338
- compute_units,
339
- precision,
340
- tolerance,
341
- output,
342
- progress=progress,
343
- progress_end=0.7,
344
- )
345
-
346
- progress(0.7, "Uploading model to Hub")
347
- pr_url = push_to_hub(destination_model, exported_base, task, precision, token=token)
348
- progress(1, "Done")
349
-
350
- cleanup(model_id, exported_base)
351
-
352
- did_validate = _is_macos() and _macos_version() >= (12, 0)
353
- result = f"""### Successfully converted!
354
- We opened a PR to add the Core ML weights to the model repo. Please, view and merge the PR [here]({pr_url}).
355
-
356
- {f"**Note**: model could not be automatically validated as this Space is not running on macOS." if not did_validate else ""}
357
- """
358
- return result
359
- except Exception as e:
360
- return error_str(e, model=model_id, task=task, framework=framework, compute_units=compute_units, precision=precision, tolerance=tolerance)
361
-
362
- DESCRIPTION = """
363
- ## Convert a `transformers` model to Core ML
364
-
365
- With this Space you can try to convert a transformers model to Core ML. It uses the 🤗 Hugging Face [Exporters repo](https://github.com/huggingface/exporters) under the hood.
366
-
367
- Note that not all models are supported. If you get an error on a model you'd like to convert, please open an issue in the discussions tab of this Space. You'll get a link to do it when an error occurs.
368
- """
369
-
370
- with gr.Blocks() as demo:
371
- gr.Markdown(DESCRIPTION)
372
- with gr.Row():
373
- with gr.Column(scale=2):
374
- gr.Markdown("## 1. Load model info")
375
- input_model = gr.Textbox(
376
- max_lines=1,
377
- label="Model name or URL, such as apple/mobilevit-small",
378
- placeholder="pcuenq/distilbert-base-uncased",
379
- value="pcuenq/distilbert-base-uncased",
380
- )
381
- btn_get_tasks = gr.Button("Load")
382
- with gr.Column(scale=3):
383
- with gr.Column(visible=False) as group_settings:
384
- gr.Markdown("## 2. Select Task")
385
- radio_tasks = gr.Radio(label="Choose the task for the converted model.")
386
- gr.Markdown("The `default` task is suitable for feature extraction.")
387
- radio_framework = gr.Radio(
388
- visible=False,
389
- label="Framework",
390
- choices=framework_labels,
391
- value=framework_labels[0],
392
- )
393
- radio_compute = gr.Radio(
394
- label="Compute Units",
395
- choices=compute_units_labels,
396
- value=compute_units_labels[0],
397
- )
398
- radio_precision = gr.Radio(
399
- label="Precision",
400
- choices=precision_labels,
401
- value=precision_labels[0],
402
- )
403
- radio_tolerance = gr.Radio(
404
- label="Absolute Tolerance for Validation",
405
- choices=tolerance_labels,
406
- value=tolerance_labels[0],
407
- )
408
-
409
- with gr.Group():
410
- text_token = gr.Textbox(label="Hugging Face Token", placeholder="hf_xxxx", value="")
411
- radio_push = gr.Radio(
412
- label="Destination Model",
413
- choices=push_labels,
414
- value=push_labels[0],
415
- )
416
- # TODO: public/private
417
- text_destination = gr.Textbox(visible=False, label="Destination model name", value="")
418
-
419
- btn_convert = gr.Button("Convert & Push")
420
- gr.Markdown("Conversion will take a few minutes.")
421
-
422
-
423
- error_output = gr.Markdown(label="Output")
424
-
425
- # # Clear output
426
- # btn_get_tasks.click(lambda _: gr.update(value=''), None, error_output)
427
- # input_model.submit(lambda _: gr.update(value=''), None, error_output)
428
- # btn_convert.click(lambda _: gr.update(value=''), None, error_output)
429
-
430
- input_model.submit(
431
- fn=on_model_change,
432
- inputs=input_model,
433
- outputs=[group_settings, radio_tasks, radio_framework, error_output],
434
- queue=False,
435
- scroll_to_output=True
436
- )
437
- btn_get_tasks.click(
438
- fn=on_model_change,
439
- inputs=input_model,
440
- outputs=[group_settings, radio_tasks, radio_framework, error_output],
441
- queue=False,
442
- scroll_to_output=True
443
- )
444
-
445
- btn_convert.click(
446
- fn=convert,
447
- inputs=[input_model, radio_tasks, radio_compute, radio_precision, radio_tolerance, radio_framework, radio_push, text_destination, text_token],
448
- outputs=error_output,
449
- scroll_to_output=True,
450
- # api_name="convert",
451
- )
452
-
453
- radio_push.change(
454
- lambda x: gr.update(visible=x == "Create a new repo"),
455
- inputs=radio_push,
456
- outputs=text_destination,
457
- queue=False,
458
- scroll_to_output=False
459
- )
460
-
461
- gr.HTML("""
462
- <div style="border-top: 0.5px solid #303030;">
463
- <br>
464
- <p style="color:gray;font-size:smaller;font-style:italic">Adapted from https://huggingface.co/spaces/diffusers/sd-to-diffusers/tree/main</p><br>
465
- </div>
466
- """)
467
-
468
- demo.queue(concurrency_count=1, max_size=10)
469
- demo.launch(debug=True, share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt DELETED
@@ -1,5 +0,0 @@
1
- huggingface_hub
2
- transformers
3
- coremltools
4
- git+https://github.com/huggingface/exporters.git
5
- torch~=1.13
 
 
 
 
 
 
startup.sh ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ # start tailscale
4
+ echo "Start tailscale"
5
+ mkdir -p /tmp/tailscale
6
+ /bin/tailscaled --tun=userspace-networking --outbound-http-proxy-listen=localhost:1055 --state=/var/lib/tailscale/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock &
7
+ HOSTNAME=${SPACE_HOST#"https://"}
8
+ /bin/tailscale up --authkey ${TS_AUTHKEY} --hostname=${HOSTNAME} --accept-routes --accept-dns
9
+ echo "Tailscale started"
10
+ echo
11
+
12
+ echo "redirect 7860 -> backend through tailscale"
13
+ socat TCP4-LISTEN:7860,reuseaddr,fork PROXY:localhost:10.254.0.11:7860,proxyport=1055