cutechicken commited on
Commit
bde5c1b
ยท
verified ยท
1 Parent(s): 47c8e15

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -480
app.py CHANGED
@@ -1,557 +1,226 @@
1
  import gradio as gr
2
- import uuid
3
- import os
4
  import json
5
- from datetime import datetime
6
- from typing import Dict, List
7
- import shutil
8
- import time
9
- import threading
10
-
11
- # Initialize data storage
12
- peers: Dict[str, Dict] = {}
13
- jobs: List[Dict] = []
14
- job_lock = threading.Lock()
15
- peer_lock = threading.Lock()
16
-
17
- # Create directories
18
- os.makedirs("results", exist_ok=True)
19
- os.makedirs("client", exist_ok=True)
20
-
21
- # Client code
22
- CLIENT_CODE = '''import requests
23
- import subprocess
24
- import time
25
  import os
26
- import sys
27
  from datetime import datetime
28
- import json
29
-
30
- # ์„ค์ •
31
- PEER_ID = f"peer-{os.getenv('COMPUTERNAME', 'unknown')}-{datetime.now().strftime('%Y%m%d%H%M%S')}"
32
- SERVER_URL = "https://your-username-your-space.hf.space" # ์‹ค์ œ Space URL๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”
33
-
34
- def check_gpu():
35
- """GPU ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ"""
36
- try:
37
- result = subprocess.run(['nvidia-smi', '--query-gpu=utilization.gpu', '--format=csv,noheader,nounits'],
38
- capture_output=True, text=True)
39
- if result.returncode == 0:
40
- gpu_usage = int(result.stdout.strip())
41
- return gpu_usage < 20 # GPU ์‚ฌ์šฉ๋ฅ  20% ๋ฏธ๋งŒ์ด๋ฉด idle
42
- except:
43
- print("GPU๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. CPU ๋ชจ๋“œ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.")
44
- return False
45
 
46
- def register_peer():
47
- """์„œ๋ฒ„์— Peer ๋“ฑ๋ก"""
48
- try:
49
- response = requests.post(
50
- f"{SERVER_URL}/run/register_peer",
51
- json={"data": [PEER_ID]},
52
- headers={"Content-Type": "application/json"}
53
- )
54
- if response.status_code == 200:
55
- result = response.json()
56
- if result.get("data") and result["data"][0] == "registered":
57
- print(f"โœ… Peer ๋“ฑ๋ก ์„ฑ๊ณต: {PEER_ID}")
58
- return True
59
- except Exception as e:
60
- print(f"โŒ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
61
- return False
62
 
63
- def send_heartbeat():
64
- """์„œ๋ฒ„์— heartbeat ์ „์†ก"""
65
- try:
66
- response = requests.post(
67
- f"{SERVER_URL}/run/heartbeat",
68
- json={"data": [PEER_ID]},
69
- headers={"Content-Type": "application/json"}
70
- )
71
- return response.status_code == 200
72
- except:
73
- return False
74
 
75
- def request_job():
76
- """์ž‘์—… ์š”์ฒญ"""
77
- try:
78
- response = requests.post(
79
- f"{SERVER_URL}/run/request_job",
80
- json={"data": [PEER_ID]},
81
- headers={"Content-Type": "application/json"}
82
- )
83
- if response.status_code == 200:
84
- result = response.json()
85
- if result.get("data") and result["data"][0]:
86
- return json.loads(result["data"][0])
87
- except Exception as e:
88
- print(f"์ž‘์—… ์š”์ฒญ ์‹คํŒจ: {e}")
89
- return None
90
-
91
- def generate_image_cpu(prompt, output_path):
92
- """CPU๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
93
- from PIL import Image, ImageDraw, ImageFont
94
-
95
- img = Image.new('RGB', (512, 512), color='white')
96
  draw = ImageDraw.Draw(img)
97
 
98
- # ํ…์ŠคํŠธ ๊ทธ๋ฆฌ๊ธฐ
99
- text = f"Prompt: {prompt[:50]}..."
100
- draw.text((10, 10), text, fill='black')
101
- draw.text((10, 40), f"Generated by: {PEER_ID}", fill='gray')
102
- draw.text((10, 70), f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", fill='gray')
103
 
104
- # ํ…Œ์ŠคํŠธ ํŒจํ„ด ๊ทธ๋ฆฌ๊ธฐ
105
- for i in range(0, 512, 32):
106
- draw.line([(i, 0), (i, 512)], fill='lightgray')
107
- draw.line([(0, i), (512, i)], fill='lightgray')
108
-
109
- img.save(output_path)
110
- print(f"๐Ÿ“ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ: {output_path}")
111
-
112
- def submit_result(job_id, image_path):
113
- """์ž‘์—… ๊ฒฐ๊ณผ ์ œ์ถœ"""
114
- try:
115
- # ์ด๋ฏธ์ง€๋ฅผ base64๋กœ ์ธ์ฝ”๋”ฉ
116
- import base64
117
- with open(image_path, "rb") as f:
118
- image_data = base64.b64encode(f.read()).decode()
119
-
120
- response = requests.post(
121
- f"{SERVER_URL}/run/submit_result",
122
- json={"data": [job_id, image_data]},
123
- headers={"Content-Type": "application/json"}
124
- )
125
- return response.status_code == 200
126
- except Exception as e:
127
- print(f"๊ฒฐ๊ณผ ์ œ์ถœ ์‹คํŒจ: {e}")
128
- return False
129
-
130
- def main():
131
- print("๐Ÿš€ P2P GPU ํด๋ผ์ด์–ธํŠธ ์‹œ์ž‘...")
132
 
133
- if not register_peer():
134
- print("์„œ๋ฒ„ ๋“ฑ๋ก ์‹คํŒจ. ํ”„๋กœ๊ทธ๋žจ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.")
135
- return
 
136
 
137
- while True:
138
- try:
139
- # Heartbeat
140
- if not send_heartbeat():
141
- print("์„œ๋ฒ„ ์—ฐ๊ฒฐ ๋Š๊น€. ์žฌ๋“ฑ๋ก ์‹œ๋„...")
142
- if not register_peer():
143
- time.sleep(30)
144
- continue
145
-
146
- # ์ž‘์—… ์š”์ฒญ
147
- job = request_job()
148
- if job:
149
- job_id = job["id"]
150
- prompt = job["prompt"]
151
-
152
- print(f"\\n๐Ÿ“‹ ์ƒˆ ์ž‘์—… ์ˆ˜์‹ : {prompt}")
153
-
154
- # ์ด๋ฏธ์ง€ ์ƒ์„ฑ
155
- output_path = f"{job_id}.png"
156
-
157
- if check_gpu():
158
- print("๐ŸŽฎ GPU ๋ชจ๋“œ๋กœ ์ƒ์„ฑ ์ค‘...")
159
- # ์‹ค์ œ GPU ์ƒ์„ฑ ์ฝ”๋“œ๊ฐ€ ์—ฌ๊ธฐ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค
160
- generate_image_cpu(prompt, output_path)
161
- else:
162
- print("๐Ÿ’ป CPU ๋ชจ๋“œ๋กœ ์ƒ์„ฑ ์ค‘...")
163
- generate_image_cpu(prompt, output_path)
164
-
165
- # ๊ฒฐ๊ณผ ์—…๋กœ๋“œ
166
- if submit_result(job_id, output_path):
167
- print("โœ… ๊ฒฐ๊ณผ ์—…๋กœ๋“œ ์„ฑ๊ณต")
168
- else:
169
- print("โŒ ๊ฒฐ๊ณผ ์—…๋กœ๋“œ ์‹คํŒจ")
170
-
171
- # ์ž„์‹œ ํŒŒ์ผ ์‚ญ์ œ
172
- os.remove(output_path)
173
-
174
- time.sleep(10) # 10์ดˆ๋งˆ๋‹ค ํ™•์ธ
175
-
176
- except KeyboardInterrupt:
177
- print("\\n๐Ÿ‘‹ ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ")
178
- break
179
- except Exception as e:
180
- print(f"โš ๏ธ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
181
- time.sleep(30)
182
-
183
- if __name__ == "__main__":
184
- # ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ํ™•์ธ
185
- try:
186
- import PIL
187
- except ImportError:
188
- print("ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค...")
189
- subprocess.run([sys.executable, "-m", "pip", "install", "pillow", "requests"])
190
 
191
- main()
192
- '''
193
-
194
- # Create client files
195
- def create_client_files():
196
- with open("client/peer_agent.py", "w", encoding="utf-8") as f:
197
- f.write(CLIENT_CODE)
198
-
199
- with open("client/requirements.txt", "w") as f:
200
- f.write("requests\npillow\n")
201
 
202
- with open("client/README.md", "w", encoding="utf-8") as f:
203
- f.write("""# P2P GPU ํด๋ผ์ด์–ธํŠธ (Windows)
204
-
205
- ## ์„ค์น˜ ๋ฐฉ๋ฒ•
206
- 1. Python 3.8 ์ด์ƒ ์„ค์น˜
207
- 2. `pip install -r requirements.txt` ์‹คํ–‰
208
- 3. `peer_agent.py` ํŒŒ์ผ์˜ SERVER_URL์„ ์‹ค์ œ Hugging Face Space URL๋กœ ์ˆ˜์ •
209
- 4. `python peer_agent.py` ์‹คํ–‰
210
-
211
- ## GPU ์ง€์›
212
- - NVIDIA GPU๊ฐ€ ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ๊ฐ์ง€
213
- - GPU๊ฐ€ ์—†์œผ๋ฉด CPU ๋ชจ๋“œ๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
214
- """)
215
-
216
- create_client_files()
217
-
218
- # Cleanup old peers periodically
219
- def cleanup_old_peers():
220
- while True:
221
- time.sleep(60) # Check every minute
222
- with peer_lock:
223
- current_time = datetime.now()
224
- to_remove = []
225
- for peer_id, peer_info in peers.items():
226
- if (current_time - peer_info['last_seen']).seconds > 120: # 2 minutes timeout
227
- to_remove.append(peer_id)
228
- for peer_id in to_remove:
229
- del peers[peer_id]
230
-
231
- # Start cleanup thread
232
- cleanup_thread = threading.Thread(target=cleanup_old_peers, daemon=True)
233
- cleanup_thread.start()
234
-
235
- # Gradio API functions
236
- def register_peer(peer_id: str):
237
- """Peer ๋“ฑ๋ก"""
238
- with peer_lock:
239
- peers[peer_id] = {
240
- "status": "idle",
241
- "last_seen": datetime.now(),
242
- "jobs_completed": 0
243
- }
244
- return "registered"
245
-
246
- def heartbeat(peer_id: str):
247
- """Peer heartbeat"""
248
- with peer_lock:
249
- if peer_id in peers:
250
- peers[peer_id]["last_seen"] = datetime.now()
251
- return "alive"
252
- return "unregistered"
253
-
254
- def submit_job(prompt: str):
255
  """์ž‘์—… ์ œ์ถœ"""
256
  if not prompt:
257
- return "ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
258
 
259
- job_id = str(uuid.uuid4())
260
- with job_lock:
261
- job = {
262
- "id": job_id,
263
- "prompt": prompt,
264
- "status": "pending",
265
- "created_at": datetime.now().isoformat()
266
- }
267
- jobs.append(job)
268
 
269
- return f"โœ… ์ž‘์—…์ด ์ œ์ถœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!\n์ž‘์—… ID: {job_id[:8]}..."
270
-
271
- def request_job(peer_id: str):
272
- """์ž‘์—… ์š”์ฒญ"""
273
- with job_lock:
274
- for job in jobs:
275
- if job["status"] == "pending":
276
- job["status"] = "assigned"
277
- job["peer_id"] = peer_id
278
- job["assigned_at"] = datetime.now().isoformat()
279
- return json.dumps(job)
280
- return None
281
-
282
- def submit_result(job_id: str, image_base64: str):
283
- """์ž‘์—… ๊ฒฐ๊ณผ ์ œ์ถœ"""
284
- import base64
285
 
286
- # base64 ๋””์ฝ”๋”ฉํ•˜์—ฌ ํŒŒ์ผ๋กœ ์ €์žฅ
287
- try:
288
- image_data = base64.b64decode(image_base64)
289
- filename = f"{job_id}.png"
290
- file_path = f"results/{filename}"
291
-
292
- with open(file_path, "wb") as f:
293
- f.write(image_data)
294
-
295
- with job_lock:
296
- for job in jobs:
297
- if job["id"] == job_id:
298
- job["status"] = "completed"
299
- job["filename"] = filename
300
- job["completed_at"] = datetime.now().isoformat()
301
-
302
- # Peer ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
303
- if "peer_id" in job:
304
- with peer_lock:
305
- if job["peer_id"] in peers:
306
- peers[job["peer_id"]]["jobs_completed"] += 1
307
- break
308
-
309
- return "success"
310
- except Exception as e:
311
- print(f"Error saving result: {e}")
312
- return "error"
313
 
314
  def get_status():
315
- """์‹œ์Šคํ…œ ์ƒํƒœ ์กฐํšŒ"""
316
- with peer_lock:
317
- active_peers = sum(1 for p in peers.values()
318
- if (datetime.now() - p['last_seen']).seconds < 60)
319
-
320
- with job_lock:
321
- pending = sum(1 for j in jobs if j['status'] == 'pending')
322
- completed = sum(1 for j in jobs if j['status'] == 'completed')
323
 
324
  status_text = f"""## ๐Ÿ“Š ์‹œ์Šคํ…œ ์ƒํƒœ
325
 
326
  ### ํ˜„์žฌ ์ƒํ™ฉ
327
- - **ํ™œ์„ฑ Peer**: {active_peers}๊ฐœ
328
- - **๋Œ€๊ธฐ์ค‘ ์ž‘์—…**: {pending}๊ฐœ
329
  - **์™„๋ฃŒ๋œ ์ž‘์—…**: {completed}๊ฐœ
 
330
 
331
- ### ์ตœ๊ทผ ์ž‘์—… ๋ชฉ๋ก
332
  """
333
 
334
- with job_lock:
335
- recent_jobs = jobs[-5:][::-1] # Last 5 jobs, reversed
336
- if recent_jobs:
337
- for job in recent_jobs:
338
- status_emoji = "โœ…" if job['status'] == 'completed' else "โณ" if job['status'] == 'pending' else "๐Ÿ”„"
339
- status_text += f"\n{status_emoji} **{job['id'][:8]}**: {job['prompt'][:50]}... ({job['status']})"
340
- else:
341
- status_text += "\n์•„์ง ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค."
342
 
343
  return status_text
344
 
345
  def get_gallery():
346
- """๊ฐค๋Ÿฌ๋ฆฌ์šฉ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ"""
347
- image_files = []
348
- with job_lock:
349
- for job in jobs[-20:][::-1]: # Last 20 jobs, newest first
350
- if job['status'] == 'completed' and 'filename' in job:
351
- file_path = f"results/{job['filename']}"
352
- if os.path.exists(file_path):
353
- image_files.append((file_path, job['prompt']))
354
 
355
- return image_files
 
 
 
 
 
 
356
 
357
- def download_client_file(filename):
358
- """ํด๋ผ์ด์–ธํŠธ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ"""
359
- file_path = f"client/{filename}"
360
- if os.path.exists(file_path):
361
- return file_path
362
- return None
363
 
364
- # Custom CSS
365
- custom_css = """
366
- .container {
367
- max-width: 1200px;
368
- margin: 0 auto;
369
- }
370
- #gallery {
371
- min-height: 400px;
372
- }
373
- .gradio-container {
374
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
375
- }
376
  """
377
 
378
- # Create Gradio interface
379
- with gr.Blocks(title="P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ", css=custom_css, theme=gr.themes.Soft()) as demo:
380
- gr.Markdown("# ๐Ÿค– P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ")
381
- gr.Markdown("์œ ํœด GPU๋ฅผ ํ™œ์šฉํ•œ ๋ถ„์‚ฐ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์Šคํ…œ")
 
 
 
 
 
 
 
 
 
 
 
382
 
383
  with gr.Tabs():
 
384
  with gr.Tab("๐ŸŽจ ์ž‘์—… ์ œ์ถœ"):
385
  with gr.Row():
386
  with gr.Column(scale=2):
387
  prompt_input = gr.Textbox(
388
- label="์ด๋ฏธ์ง€ ํ”„๋กฌํ”„ํŠธ",
389
- placeholder="์ƒ์„ฑํ•˜๊ณ  ์‹ถ์€ ์ด๋ฏธ์ง€๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”...\n์˜ˆ: ํ‘ธ๋ฅธ ํ•˜๋Š˜ ์•„๋ž˜ ๋ฒš๊ฝƒ์ด ๋งŒ๊ฐœํ•œ ๊ณต์›",
390
- lines=4
391
  )
392
- with gr.Row():
393
- submit_btn = gr.Button("์ž‘์—… ์ œ์ถœ", variant="primary", scale=2)
394
- clear_btn = gr.Button("์ง€์šฐ๊ธฐ", scale=1)
395
 
396
- result_text = gr.Textbox(
397
- label="์ œ์ถœ ๊ฒฐ๊ณผ",
398
- interactive=False,
399
- lines=2
400
- )
401
 
402
  with gr.Column(scale=1):
403
  gr.Markdown("""
404
  ### ๐Ÿ’ก ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
405
- 1. ์›ํ•˜๋Š” ์ด๋ฏธ์ง€๋ฅผ ์„ค๋ช…ํ•˜์„ธ์š”
406
- 2. '์ž‘์—… ์ œ์ถœ' ๋ฒ„ํŠผ์„ ํด๋ฆญ
407
- 3. Peer๊ฐ€ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
408
- 4. ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ๊ฒฐ๊ณผ ํ™•์ธ
409
 
410
- ### ๐ŸŽฏ ์ข‹์€ ํ”„๋กฌํ”„ํŠธ ์˜ˆ์‹œ
411
- - ๊ตฌ์ฒด์ ์ธ ์„ค๋ช… ํฌํ•จ
412
- - ์Šคํƒ€์ผ์ด๋‚˜ ๋ถ„์œ„๊ธฐ ๋ช…์‹œ
413
- - ์ƒ‰์ƒ, ์กฐ๋ช… ๋“ฑ ์„ธ๋ถ€์‚ฌํ•ญ
414
  """)
415
 
416
  submit_btn.click(
417
- fn=submit_job,
418
- inputs=prompt_input,
419
- outputs=result_text
420
- )
421
- clear_btn.click(
422
- fn=lambda: ("", ""),
423
- outputs=[prompt_input, result_text]
424
  )
425
 
 
426
  with gr.Tab("๐Ÿ“Š ์‹œ์Šคํ…œ ์ƒํƒœ"):
427
  status_display = gr.Markdown()
428
- with gr.Row():
429
- refresh_btn = gr.Button("๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
430
 
431
- refresh_btn.click(
432
- fn=get_status,
433
- outputs=status_display
434
- )
435
 
436
- # Auto-refresh status on load
437
- demo.load(fn=get_status, outputs=status_display)
438
 
 
439
  with gr.Tab("๐Ÿ–ผ๏ธ ๊ฐค๋Ÿฌ๋ฆฌ"):
440
- gr.Markdown("### ์ตœ๊ทผ ์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋“ค")
441
  gallery = gr.Gallery(
442
- label="์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€",
443
- show_label=False,
444
- elem_id="gallery",
445
- columns=4,
446
- rows=3,
447
- height="auto",
448
- object_fit="contain",
449
- preview=True
450
- )
451
- refresh_gallery_btn = gr.Button("๐Ÿ”„ ๊ฐค๋Ÿฌ๋ฆฌ ์ƒˆ๋กœ๊ณ ์นจ", variant="secondary")
452
-
453
- refresh_gallery_btn.click(
454
- fn=get_gallery,
455
- outputs=gallery
456
  )
 
457
 
458
- # Auto-load gallery
459
  demo.load(fn=get_gallery, outputs=gallery)
460
 
461
- with gr.Tab("๐Ÿ’ป ํด๋ผ์ด์–ธํŠธ ๋‹ค์šด๋กœ๋“œ"):
462
- gr.Markdown("""
463
- ## Windows ํด๋ผ์ด์–ธํŠธ ์„ค์ • ๊ฐ€์ด๋“œ
464
-
465
- ### ๐Ÿ“ฅ 1๋‹จ๊ณ„: ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
466
- ํด๋ผ์ด์–ธํŠธ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜๋ ค๋ฉด ์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”:
467
- """)
468
-
469
- with gr.Row():
470
- download_peer_btn = gr.Button("๐Ÿ“ฅ peer_agent.py ๋‹ค์šด๋กœ๋“œ")
471
- download_req_btn = gr.Button("๐Ÿ“ฅ requirements.txt ๋‹ค์šด๋กœ๋“œ")
472
- download_readme_btn = gr.Button("๐Ÿ“ฅ README.md ๋‹ค์šด๋กœ๋“œ")
473
-
474
- download_output = gr.File(label="๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ", visible=False)
475
-
476
- download_peer_btn.click(
477
- fn=lambda: download_client_file("peer_agent.py"),
478
- outputs=download_output
479
- )
480
- download_req_btn.click(
481
- fn=lambda: download_client_file("requirements.txt"),
482
- outputs=download_output
483
- )
484
- download_readme_btn.click(
485
- fn=lambda: download_client_file("README.md"),
486
- outputs=download_output
487
- )
488
-
489
  gr.Markdown("""
490
- ### ๐Ÿ 2๋‹จ๊ณ„: Python ์„ค์น˜
491
- - Python 3.8 ์ด์ƒ ๋ฒ„์ „ ํ•„์š”
492
- - [python.org](https://python.org)์—์„œ ๋‹ค์šด๋กœ๋“œ
493
 
494
- ### ๐Ÿ“ฆ 3๋‹จ๊ณ„: ํŒจํ‚ค์ง€ ์„ค์น˜
495
- ```bash
496
- pip install -r requirements.txt
497
- ```
498
 
499
- ### โš™๏ธ 4๋‹จ๊ณ„: ์„ค์ •
500
- `peer_agent.py` ํŒŒ์ผ์„ ์—ด๊ณ  SERVER_URL์„ ์ด Space์˜ URL๋กœ ๋ณ€๊ฒฝ:
501
  ```python
502
- SERVER_URL = "https://๋‹น์‹ ์˜-space-url.hf.space"
503
- ```
504
-
505
- ### ๐Ÿš€ 5๋‹จ๊ณ„: ์‹คํ–‰
506
- ```bash
507
- python peer_agent.py
508
  ```
509
 
510
- ### โœจ ํŠน์ง•
511
- - GPU ์ž๋™ ๊ฐ์ง€
512
- - CPU ํด๋ฐฑ ์ง€์›
513
- - ์ž๋™ ์ž‘์—… ์ฒ˜๋ฆฌ
514
- - 10์ดˆ๋งˆ๋‹ค ์ƒˆ ์ž‘์—… ํ™•์ธ
515
-
516
- ### ๐ŸŽฎ GPU ์ง€์›
517
- - NVIDIA GPU๊ฐ€ ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ํ™œ์šฉ
518
- - GPU๊ฐ€ ์—†์–ด๋„ CPU๋กœ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
519
- - GPU ์‚ฌ์šฉ๋ฅ  20% ๋ฏธ๋งŒ์ผ ๋•Œ๋งŒ ์ž‘์—… ์ˆ˜ํ–‰
520
  """)
521
-
522
- # Hidden API endpoints for client communication
523
- gr.Interface(
524
- fn=register_peer,
525
- inputs=gr.Textbox(visible=False),
526
- outputs=gr.Textbox(visible=False),
527
- api_name="register_peer",
528
- visible=False
529
- )
530
-
531
- gr.Interface(
532
- fn=heartbeat,
533
- inputs=gr.Textbox(visible=False),
534
- outputs=gr.Textbox(visible=False),
535
- api_name="heartbeat",
536
- visible=False
537
- )
538
-
539
- gr.Interface(
540
- fn=request_job,
541
- inputs=gr.Textbox(visible=False),
542
- outputs=gr.Textbox(visible=False),
543
- api_name="request_job",
544
- visible=False
545
- )
546
-
547
- gr.Interface(
548
- fn=submit_result,
549
- inputs=[gr.Textbox(visible=False), gr.Textbox(visible=False)],
550
- outputs=gr.Textbox(visible=False),
551
- api_name="submit_result",
552
- visible=False
553
- )
554
 
555
- # Launch the app
556
  if __name__ == "__main__":
557
  demo.launch()
 
1
  import gradio as gr
 
 
2
  import json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import os
 
4
  from datetime import datetime
5
+ import base64
6
+ from PIL import Image, ImageDraw, ImageFont
7
+ import io
8
+ import random
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # ๊ฐ„๋‹จํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์†Œ
11
+ jobs = []
12
+ results = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ # ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
15
+ os.makedirs("temp", exist_ok=True)
 
 
 
 
 
 
 
 
 
16
 
17
+ def create_test_image(prompt):
18
+ """ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ"""
19
+ img = Image.new('RGB', (512, 512), color=(255, 255, 255))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  draw = ImageDraw.Draw(img)
21
 
22
+ # ๋ฐฐ๊ฒฝ ๊ทธ๋ผ๋ฐ์ด์…˜
23
+ for i in range(512):
24
+ color = int(255 * (1 - i/512))
25
+ draw.rectangle([(0, i), (512, i+1)], fill=(color, color, 255))
 
26
 
27
+ # ํ…์ŠคํŠธ ์ถ”๊ฐ€
28
+ text_lines = [
29
+ f"Prompt: {prompt[:40]}...",
30
+ f"Generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
31
+ f"Job ID: {random.randint(1000, 9999)}"
32
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
+ y_offset = 20
35
+ for line in text_lines:
36
+ draw.text((20, y_offset), line, fill='white')
37
+ y_offset += 30
38
 
39
+ # ๊ฒฉ์ž ํŒจํ„ด
40
+ for i in range(0, 512, 32):
41
+ draw.line([(i, 100), (i, 512)], fill=(200, 200, 200), width=1)
42
+ draw.line([(0, i+100), (512, i+100)], fill=(200, 200, 200), width=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ return img
 
 
 
 
 
 
 
 
 
45
 
46
+ def submit_job(prompt):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  """์ž‘์—… ์ œ์ถœ"""
48
  if not prompt:
49
+ return "โŒ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", None
50
 
51
+ job_id = f"job_{len(jobs)}_{datetime.now().strftime('%H%M%S')}"
52
+ job = {
53
+ "id": job_id,
54
+ "prompt": prompt,
55
+ "status": "processing",
56
+ "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
57
+ }
58
+ jobs.append(job)
 
59
 
60
+ # ์‹œ๋ฎฌ๋ ˆ์ด์…˜: ๋ฐ”๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
61
+ img = create_test_image(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ # ์ด๋ฏธ์ง€๋ฅผ ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅ
64
+ img_path = f"temp/{job_id}.png"
65
+ img.save(img_path)
66
+ results[job_id] = img_path
67
+ job["status"] = "completed"
68
+
69
+ return f"โœ… ์ž‘์—…์ด ์ œ์ถœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!\n์ž‘์—… ID: {job_id}", img
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  def get_status():
72
+ """์‹œ์Šคํ…œ ์ƒํƒœ"""
73
+ total_jobs = len(jobs)
74
+ completed = sum(1 for j in jobs if j["status"] == "completed")
 
 
 
 
 
75
 
76
  status_text = f"""## ๐Ÿ“Š ์‹œ์Šคํ…œ ์ƒํƒœ
77
 
78
  ### ํ˜„์žฌ ์ƒํ™ฉ
79
+ - **์ „์ฒด ์ž‘์—…**: {total_jobs}๊ฐœ
 
80
  - **์™„๋ฃŒ๋œ ์ž‘์—…**: {completed}๊ฐœ
81
+ - **๋Œ€๊ธฐ์ค‘ ์ž‘์—…**: {total_jobs - completed}๊ฐœ
82
 
83
+ ### ์ตœ๊ทผ ์ž‘์—… (์ตœ๋Œ€ 5๊ฐœ)
84
  """
85
 
86
+ recent_jobs = jobs[-5:][::-1] if jobs else []
87
+ for job in recent_jobs:
88
+ emoji = "โœ…" if job["status"] == "completed" else "โณ"
89
+ status_text += f"\n{emoji} **{job['id']}**: {job['prompt'][:30]}... ({job['created_at']})"
90
+
91
+ if not jobs:
92
+ status_text += "\n\n*์•„์ง ์ž‘์—…์ด ์—†์Šต๋‹ˆ๋‹ค.*"
 
93
 
94
  return status_text
95
 
96
  def get_gallery():
97
+ """๊ฐค๋Ÿฌ๋ฆฌ ์ด๋ฏธ์ง€ ๋ชฉ๋ก"""
98
+ gallery_images = []
99
+
100
+ for job in jobs[-12:][::-1]: # ์ตœ๊ทผ 12๊ฐœ
101
+ if job["status"] == "completed" and job["id"] in results:
102
+ img_path = results[job["id"]]
103
+ if os.path.exists(img_path):
104
+ gallery_images.append((img_path, f"{job['prompt'][:50]}..."))
105
 
106
+ return gallery_images
107
+
108
+ # ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ
109
+ CLIENT_CODE = """# P2P GPU Client (Demo Version)
110
+ import time
111
+ import random
112
+ from datetime import datetime
113
 
114
+ print("๐Ÿš€ P2P GPU ํด๋ผ์ด์–ธํŠธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜")
115
+ print("์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” Gradio API๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.")
 
 
 
 
116
 
117
+ while True:
118
+ print(f"\\n[{datetime.now().strftime('%H:%M:%S')}] ์ž‘์—… ํ™•์ธ ์ค‘...")
119
+
120
+ if random.random() > 0.7: # 30% ํ™•๋ฅ ๋กœ ์ž‘์—… ์ˆ˜์‹ 
121
+ print("๐Ÿ“‹ ์ƒˆ ์ž‘์—… ๋ฐœ๊ฒฌ!")
122
+ print("๐ŸŽจ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๏ฟฝ๏ฟฝ๏ฟฝ...")
123
+ time.sleep(2)
124
+ print("โœ… ์™„๋ฃŒ! ์„œ๋ฒ„๋กœ ์ „์†ก")
125
+ else:
126
+ print("๋Œ€๊ธฐ ์ค‘...")
127
+
128
+ time.sleep(5)
129
  """
130
 
131
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค
132
+ with gr.Blocks(
133
+ title="P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ",
134
+ theme=gr.themes.Soft(),
135
+ css="""
136
+ .container { max-width: 1200px; margin: 0 auto; }
137
+ #gallery { min-height: 400px; }
138
+ """
139
+ ) as demo:
140
+
141
+ gr.Markdown("""
142
+ # ๐Ÿค– P2P GPU ์ด๋ฏธ์ง€ ์ƒ์„ฑ ํ—ˆ๋ธŒ
143
+
144
+ ์œ ํœด GPU๋ฅผ ํ™œ์šฉํ•œ ๋ถ„์‚ฐ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹œ์Šคํ…œ (๋ฐ๋ชจ ๋ฒ„์ „)
145
+ """)
146
 
147
  with gr.Tabs():
148
+ # ์ž‘์—… ์ œ์ถœ ํƒญ
149
  with gr.Tab("๐ŸŽจ ์ž‘์—… ์ œ์ถœ"):
150
  with gr.Row():
151
  with gr.Column(scale=2):
152
  prompt_input = gr.Textbox(
153
+ label="์ด๋ฏธ์ง€ ํ”„๋กฌํ”„ํŠธ",
154
+ placeholder="์ƒ์„ฑํ•˜๊ณ  ์‹ถ์€ ์ด๋ฏธ์ง€๋ฅผ ์„ค๋ช…ํ•˜์„ธ์š”...",
155
+ lines=3
156
  )
157
+ submit_btn = gr.Button("๐Ÿš€ ์ž‘์—… ์ œ์ถœ", variant="primary")
 
 
158
 
159
+ result_msg = gr.Textbox(label="์ œ์ถœ ๊ฒฐ๊ณผ", interactive=False)
160
+ result_img = gr.Image(label="์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€", type="pil")
 
 
 
161
 
162
  with gr.Column(scale=1):
163
  gr.Markdown("""
164
  ### ๐Ÿ’ก ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
165
+ 1. ํ”„๋กฌํ”„ํŠธ ์ž…๋ ฅ
166
+ 2. ์ž‘์—… ์ œ์ถœ ํด๋ฆญ
167
+ 3. ๊ฒฐ๊ณผ ํ™•์ธ
 
168
 
169
+ ### ๐Ÿ“ ์˜ˆ์‹œ
170
+ - ํ‘ธ๋ฅธ ํ•˜๋Š˜๊ณผ ๊ตฌ๋ฆ„
171
+ - ์ผ๋ชฐ์˜ ํ•ด๋ณ€ ํ’๊ฒฝ
172
+ - ๋ฏธ๋ž˜์ ์ธ ๋„์‹œ ์ „๊ฒฝ
173
  """)
174
 
175
  submit_btn.click(
176
+ fn=submit_job,
177
+ inputs=prompt_input,
178
+ outputs=[result_msg, result_img]
 
 
 
 
179
  )
180
 
181
+ # ์‹œ์Šคํ…œ ์ƒํƒœ ํƒญ
182
  with gr.Tab("๐Ÿ“Š ์‹œ์Šคํ…œ ์ƒํƒœ"):
183
  status_display = gr.Markdown()
184
+ refresh_btn = gr.Button("๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ")
 
185
 
186
+ def refresh_status():
187
+ return get_status()
 
 
188
 
189
+ refresh_btn.click(fn=refresh_status, outputs=status_display)
190
+ demo.load(fn=refresh_status, outputs=status_display)
191
 
192
+ # ๊ฐค๋Ÿฌ๋ฆฌ ํƒญ
193
  with gr.Tab("๐Ÿ–ผ๏ธ ๊ฐค๋Ÿฌ๋ฆฌ"):
 
194
  gallery = gr.Gallery(
195
+ label="์ƒ์„ฑ๋œ ์ด๋ฏธ์ง€๋“ค",
196
+ columns=3,
197
+ rows=2,
198
+ height="auto"
 
 
 
 
 
 
 
 
 
 
199
  )
200
+ gallery_refresh_btn = gr.Button("๐Ÿ”„ ๊ฐค๋Ÿฌ๋ฆฌ ์ƒˆ๋กœ๊ณ ์นจ")
201
 
202
+ gallery_refresh_btn.click(fn=get_gallery, outputs=gallery)
203
  demo.load(fn=get_gallery, outputs=gallery)
204
 
205
+ # ๋‹ค์šด๋กœ๋“œ ํƒญ
206
+ with gr.Tab("๐Ÿ’ป ํด๋ผ์ด์–ธํŠธ"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  gr.Markdown("""
208
+ ## ํด๋ผ์ด์–ธํŠธ ํ”„๋กœ๊ทธ๋žจ
 
 
209
 
210
+ ### ๋ฐ๋ชจ ์ฝ”๋“œ
211
+ ์•„๋ž˜๋Š” P2P ํด๋ผ์ด์–ธํŠธ์˜ ๊ฐ„๋‹จํ•œ ๋ฐ๋ชจ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค:
 
 
212
 
 
 
213
  ```python
214
+ """ + CLIENT_CODE + """
 
 
 
 
 
215
  ```
216
 
217
+ ### ์‹ค์ œ ๊ตฌํ˜„ ์‹œ
218
+ - Gradio API๋ฅผ ํ†ตํ•œ ์„œ๋ฒ„ ํ†ต์‹ 
219
+ - GPU ์ž๋™ ๊ฐ์ง€ ๋ฐ ํ™œ์šฉ
220
+ - ์‹ค์ œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๋ชจ๋ธ ์—ฐ๋™
221
+ - ์ž๋™ ์ž‘์—… ์ฒ˜๋ฆฌ ๋ฐ ๊ฒฐ๊ณผ ์ „์†ก
 
 
 
 
 
222
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
+ # ์•ฑ ์‹คํ–‰
225
  if __name__ == "__main__":
226
  demo.launch()