srilathatata commited on
Commit
6e93b43
Β·
1 Parent(s): 03335dc

fix: remove pycache from tracking

Browse files
.gitignore CHANGED
@@ -8,3 +8,4 @@ __pycache__/
8
  EOF*.jpg
9
  *.jpeg
10
  *.png
 
 
8
  EOF*.jpg
9
  *.jpeg
10
  *.png
11
+ __pycache__/
__pycache__/modal_services.cpython-312.pyc DELETED
Binary file (14.1 kB)
 
__pycache__/test_modal.cpython-312.pyc DELETED
Binary file (1.3 kB)
 
__pycache__/utils.cpython-312.pyc DELETED
Binary file (7.68 kB)
 
app.py CHANGED
@@ -203,4 +203,4 @@ with gr.Blocks(title="ZeroWasteKitchen", theme=gr.themes.Soft()) as app:
203
  )
204
 
205
  if __name__ == "__main__":
206
- app.launch()
 
203
  )
204
 
205
  if __name__ == "__main__":
206
+ app.launch(server_name="0.0.0.0")
modal_services.py CHANGED
@@ -2,31 +2,56 @@ import modal
2
  from modal import Volume
3
  from pathlib import Path
4
 
5
- # App definition first
6
  app = modal.App("zerowastekitchen")
7
 
8
- # Volume
9
  model_volume = Volume.from_name("model-cache", create_if_missing=True)
10
  MODEL_DIR = Path("/models")
11
 
12
- # Images
13
  download_image = (
14
  modal.Image.debian_slim()
15
  .pip_install("huggingface_hub", "hf_transfer")
16
  .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
17
  )
18
 
 
19
  base_image = (
20
  modal.Image.debian_slim()
21
  .pip_install(
22
- "torch", "torchvision", "transformers>=5.7.0",
23
- "accelerate", "pillow", "einops", "sentencepiece",
24
- "timm", "open_clip_torch", "av", "numpy",
25
- "huggingface_hub", "hf_transfer"
 
 
 
 
 
 
 
 
 
26
  )
27
  .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
28
  )
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  @app.function(
31
  image=download_image,
32
  volumes={str(MODEL_DIR): model_volume},
@@ -37,32 +62,46 @@ def download_models():
37
  from huggingface_hub import snapshot_download
38
  import os
39
  token = os.environ["HF_TOKEN"]
40
-
 
 
 
 
 
 
 
 
41
  snapshot_download(
42
  "CohereLabs/tiny-aya-fire",
43
  local_dir=str(MODEL_DIR / "tiny-aya-fire"),
44
  token=token
45
  )
 
 
46
  snapshot_download(
47
  "nvidia/Nemotron-Mini-4B-Instruct",
48
  local_dir=str(MODEL_DIR / "nemotron-4b"),
49
  token=token
50
  )
 
 
51
  snapshot_download(
52
- "openbmb/MiniCPM-V-4.6",
53
- local_dir=str(MODEL_DIR / "minicpm-v"),
54
  token=token
55
  )
 
56
  model_volume.commit()
57
  print("All models downloaded.")
58
 
 
59
  # ── 1. RECEIPT OCR ──────────────────────────────────────────
60
  @app.function(
61
  gpu="T4",
62
  image=base_image,
63
  timeout=180,
64
  memory=12288,
65
- volumes={"/models": model_volume},
66
  secrets=[modal.Secret.from_name("huggingface-secret")]
67
  )
68
  def parse_receipt(image_bytes: bytes) -> dict:
@@ -72,11 +111,11 @@ def parse_receipt(image_bytes: bytes) -> dict:
72
  import io, json, re
73
 
74
  processor = AutoProcessor.from_pretrained(
75
- "/models/minicpm-v",
76
  trust_remote_code=True
77
  )
78
  model = AutoModelForImageTextToText.from_pretrained(
79
- "/models/minicpm-v",
80
  torch_dtype="auto",
81
  device_map="auto",
82
  trust_remote_code=True
@@ -99,14 +138,10 @@ Return as JSON only, no other text:
99
  ]}]
100
 
101
  text = processor.apply_chat_template(
102
- messages,
103
- tokenize=False,
104
- add_generation_prompt=True
105
  )
106
  inputs = processor(
107
- text=text,
108
- images=[img],
109
- return_tensors="pt"
110
  ).to(model.device)
111
 
112
  with torch.no_grad():
@@ -146,7 +181,7 @@ Return as JSON only, no other text:
146
  image=base_image,
147
  timeout=300,
148
  memory=8192,
149
- volumes={"/models": model_volume},
150
  secrets=[modal.Secret.from_name("huggingface-secret")]
151
  )
152
  def estimate_expiry_llm(item_names: list) -> dict:
@@ -154,9 +189,9 @@ def estimate_expiry_llm(item_names: list) -> dict:
154
  from transformers import AutoTokenizer, AutoModelForCausalLM
155
  from datetime import datetime, timedelta
156
 
157
- tokenizer = AutoTokenizer.from_pretrained("/models/nemotron-4b")
158
  model = AutoModelForCausalLM.from_pretrained(
159
- "/models/nemotron-4b",
160
  torch_dtype=torch.float16,
161
  device_map="auto"
162
  )
@@ -201,30 +236,30 @@ Reply with a single integer only. No explanation.
201
  image=base_image,
202
  timeout=180,
203
  memory=8192,
204
- volumes={"/models": model_volume},
205
  secrets=[modal.Secret.from_name("huggingface-secret")]
206
  )
207
  def generate_dialogue(character: str, expiring_items: list, cuisine: str) -> str:
208
  import torch
209
  from transformers import AutoTokenizer, AutoModelForCausalLM
210
 
211
- tokenizer = AutoTokenizer.from_pretrained("/models/tiny-aya-fire")
212
  model = AutoModelForCausalLM.from_pretrained(
213
- "/models/tiny-aya-fire",
214
  torch_dtype=torch.float16,
215
  device_map="auto"
216
  )
217
 
218
  character_prompts = {
219
- "Grandma (Ammamma)": """You are Ammamma, a warm but dramatic Telugu grandma.
220
- Mix English with Telugu words: ayyo, nanna, babu, choodandi, waste cheyakandi.
221
- Scold gently about food waste but show love. 2-3 sentences only.""",
222
- "Chef": "You are a sharp professional chef. Be direct and impatient but brilliant. 2-3 sentences.",
223
- "Fitness Coach": "You are an enthusiastic fitness coach obsessed with gains and clean eating. 2-3 sentences.",
224
- "Food Critic": "You are a pompous food critic who is secretly warm. Be dramatic. 2-3 sentences.",
225
  }
226
 
227
- system = character_prompts.get(character, character_prompts["Grandma (Ammamma)"])
228
  items_str = ", ".join(expiring_items[:5])
229
 
230
  prompt = f"""<|system|>
@@ -232,7 +267,7 @@ Scold gently about food waste but show love. 2-3 sentences only.""",
232
  <|user|>
233
  These grocery items are expiring soon: {items_str}
234
  We are cooking {cuisine} food today.
235
- Give your in-character reaction about the expiring food waste.
236
  <|assistant|>
237
  """
238
 
@@ -240,7 +275,7 @@ Give your in-character reaction about the expiring food waste.
240
  with torch.no_grad():
241
  outputs = model.generate(
242
  **inputs,
243
- max_new_tokens=150,
244
  temperature=0.8,
245
  do_sample=True,
246
  pad_token_id=tokenizer.eos_token_id
@@ -251,7 +286,6 @@ Give your in-character reaction about the expiring food waste.
251
  skip_special_tokens=True
252
  ).strip()
253
 
254
- # Clean up any leaked instructions
255
  response = response.split("##")[0].strip()
256
  response = response.split("Instruction")[0].strip()
257
  return response
@@ -263,7 +297,7 @@ Give your in-character reaction about the expiring food waste.
263
  image=base_image,
264
  timeout=240,
265
  memory=8192,
266
- volumes={"/models": model_volume},
267
  secrets=[modal.Secret.from_name("huggingface-secret")]
268
  )
269
  def generate_recipe_llm(
@@ -274,9 +308,9 @@ def generate_recipe_llm(
274
  import torch
275
  from transformers import AutoTokenizer, AutoModelForCausalLM
276
 
277
- tokenizer = AutoTokenizer.from_pretrained("/models/nemotron-4b")
278
  model = AutoModelForCausalLM.from_pretrained(
279
- "/models/nemotron-4b",
280
  torch_dtype=torch.float16,
281
  device_map="auto"
282
  )
@@ -328,11 +362,54 @@ IMPORTANT: Maximum 5 steps. Stop after step 5.
328
  return response
329
 
330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  @app.local_entrypoint()
332
  def main():
333
- print("Downloading models...")
334
  download_models.remote()
335
- print("Done. Running tests...")
336
  # Test expiry estimation
337
  test_items = ["INDIA WALA PANEER", "CURRIS LEAVES PACKET", "BASMATI RICE"]
338
  print("Testing expiry estimation...")
@@ -340,9 +417,9 @@ def main():
340
  print(expiry_map)
341
 
342
  # Test dialogue
343
- print("\nTesting Ammamma dialogue...")
344
  dialogue = generate_dialogue.remote(
345
- "Grandma (Ammamma)",
346
  ["INDIA WALA PANEER", "CURRIS LEAVES PACKET"],
347
  "South Indian"
348
  )
@@ -353,4 +430,11 @@ def main():
353
  all_items = [{"name": i} for i in test_items]
354
  expiring = [{"name": i} for i in test_items[:2]]
355
  recipe = generate_recipe_llm.remote(all_items, expiring, "South Indian")
356
- print(recipe)
 
 
 
 
 
 
 
 
2
  from modal import Volume
3
  from pathlib import Path
4
 
5
+ # App definition
6
  app = modal.App("zerowastekitchen")
7
 
8
+ # Volume for model storage
9
  model_volume = Volume.from_name("model-cache", create_if_missing=True)
10
  MODEL_DIR = Path("/models")
11
 
12
+ # Download image - lightweight, just needs huggingface_hub
13
  download_image = (
14
  modal.Image.debian_slim()
15
  .pip_install("huggingface_hub", "hf_transfer")
16
  .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
17
  )
18
 
19
+ # Base image for OCR, expiry, dialogue, recipe
20
  base_image = (
21
  modal.Image.debian_slim()
22
  .pip_install(
23
+ "torch",
24
+ "torchvision",
25
+ "transformers>=5.7.0",
26
+ "accelerate",
27
+ "pillow",
28
+ "einops",
29
+ "sentencepiece",
30
+ "timm",
31
+ "open_clip_torch",
32
+ "av",
33
+ "numpy",
34
+ "huggingface_hub",
35
+ "hf_transfer"
36
  )
37
  .env({"HF_HUB_ENABLE_HF_TRANSFER": "1"})
38
  )
39
 
40
+ tts_image = (
41
+ modal.Image.debian_slim()
42
+ .apt_install("libsndfile1", "ffmpeg")
43
+ .pip_install(
44
+ "torch==2.5.0",
45
+ "torchaudio==2.5.0",
46
+ "numpy",
47
+ "soundfile",
48
+ "transformers==4.40.0",
49
+ "click",
50
+ "coqui-tts"
51
+ )
52
+ )
53
+
54
+ # ── 0. DOWNLOAD MODELS ───────────────────────────────────────
55
  @app.function(
56
  image=download_image,
57
  volumes={str(MODEL_DIR): model_volume},
 
62
  from huggingface_hub import snapshot_download
63
  import os
64
  token = os.environ["HF_TOKEN"]
65
+
66
+ print("Downloading MiniCPM-V 4.6...")
67
+ snapshot_download(
68
+ "openbmb/MiniCPM-V-4.6",
69
+ local_dir=str(MODEL_DIR / "minicpm-v"),
70
+ token=token
71
+ )
72
+
73
+ print("Downloading tiny-aya-fire...")
74
  snapshot_download(
75
  "CohereLabs/tiny-aya-fire",
76
  local_dir=str(MODEL_DIR / "tiny-aya-fire"),
77
  token=token
78
  )
79
+
80
+ print("Downloading Nemotron 4B...")
81
  snapshot_download(
82
  "nvidia/Nemotron-Mini-4B-Instruct",
83
  local_dir=str(MODEL_DIR / "nemotron-4b"),
84
  token=token
85
  )
86
+
87
+ print("Downloading Magpie TTS...")
88
  snapshot_download(
89
+ "nvidia/magpie_tts_multilingual_357m",
90
+ local_dir=str(MODEL_DIR / "magpie-tts"),
91
  token=token
92
  )
93
+
94
  model_volume.commit()
95
  print("All models downloaded.")
96
 
97
+
98
  # ── 1. RECEIPT OCR ──────────────────────────────────────────
99
  @app.function(
100
  gpu="T4",
101
  image=base_image,
102
  timeout=180,
103
  memory=12288,
104
+ volumes={str(MODEL_DIR): model_volume},
105
  secrets=[modal.Secret.from_name("huggingface-secret")]
106
  )
107
  def parse_receipt(image_bytes: bytes) -> dict:
 
111
  import io, json, re
112
 
113
  processor = AutoProcessor.from_pretrained(
114
+ str(MODEL_DIR / "minicpm-v"),
115
  trust_remote_code=True
116
  )
117
  model = AutoModelForImageTextToText.from_pretrained(
118
+ str(MODEL_DIR / "minicpm-v"),
119
  torch_dtype="auto",
120
  device_map="auto",
121
  trust_remote_code=True
 
138
  ]}]
139
 
140
  text = processor.apply_chat_template(
141
+ messages, tokenize=False, add_generation_prompt=True
 
 
142
  )
143
  inputs = processor(
144
+ text=text, images=[img], return_tensors="pt"
 
 
145
  ).to(model.device)
146
 
147
  with torch.no_grad():
 
181
  image=base_image,
182
  timeout=300,
183
  memory=8192,
184
+ volumes={str(MODEL_DIR): model_volume},
185
  secrets=[modal.Secret.from_name("huggingface-secret")]
186
  )
187
  def estimate_expiry_llm(item_names: list) -> dict:
 
189
  from transformers import AutoTokenizer, AutoModelForCausalLM
190
  from datetime import datetime, timedelta
191
 
192
+ tokenizer = AutoTokenizer.from_pretrained(str(MODEL_DIR / "nemotron-4b"))
193
  model = AutoModelForCausalLM.from_pretrained(
194
+ str(MODEL_DIR / "nemotron-4b"),
195
  torch_dtype=torch.float16,
196
  device_map="auto"
197
  )
 
236
  image=base_image,
237
  timeout=180,
238
  memory=8192,
239
+ volumes={str(MODEL_DIR): model_volume},
240
  secrets=[modal.Secret.from_name("huggingface-secret")]
241
  )
242
  def generate_dialogue(character: str, expiring_items: list, cuisine: str) -> str:
243
  import torch
244
  from transformers import AutoTokenizer, AutoModelForCausalLM
245
 
246
+ tokenizer = AutoTokenizer.from_pretrained(str(MODEL_DIR / "tiny-aya-fire"))
247
  model = AutoModelForCausalLM.from_pretrained(
248
+ str(MODEL_DIR / "tiny-aya-fire"),
249
  torch_dtype=torch.float16,
250
  device_map="auto"
251
  )
252
 
253
  character_prompts = {
254
+ "Grandma (Nani)": """You are Nani, a warm but dramatic Hindi-speaking grandma.
255
+ Mix English with Hindi words: arre, beta, dekho, barbaad mat karo, achha khana, waste mat karo.
256
+ Scold gently about food waste but show love. 2-3 sentences only. Be concise.""",
257
+ "Chef": "You are a sharp professional chef. Be direct and impatient but brilliant. 2-3 sentences only.",
258
+ "Fitness Coach": "You are an enthusiastic fitness coach obsessed with gains and clean eating. 2-3 sentences only.",
259
+ "Food Critic": "You are a pompous food critic who is secretly warm. Be dramatic. 2-3 sentences only.",
260
  }
261
 
262
+ system = character_prompts.get(character, character_prompts["Grandma (Nani)"])
263
  items_str = ", ".join(expiring_items[:5])
264
 
265
  prompt = f"""<|system|>
 
267
  <|user|>
268
  These grocery items are expiring soon: {items_str}
269
  We are cooking {cuisine} food today.
270
+ Give your in-character reaction about the expiring food. 2-3 sentences maximum.
271
  <|assistant|>
272
  """
273
 
 
275
  with torch.no_grad():
276
  outputs = model.generate(
277
  **inputs,
278
+ max_new_tokens=100,
279
  temperature=0.8,
280
  do_sample=True,
281
  pad_token_id=tokenizer.eos_token_id
 
286
  skip_special_tokens=True
287
  ).strip()
288
 
 
289
  response = response.split("##")[0].strip()
290
  response = response.split("Instruction")[0].strip()
291
  return response
 
297
  image=base_image,
298
  timeout=240,
299
  memory=8192,
300
+ volumes={str(MODEL_DIR): model_volume},
301
  secrets=[modal.Secret.from_name("huggingface-secret")]
302
  )
303
  def generate_recipe_llm(
 
308
  import torch
309
  from transformers import AutoTokenizer, AutoModelForCausalLM
310
 
311
+ tokenizer = AutoTokenizer.from_pretrained(str(MODEL_DIR / "nemotron-4b"))
312
  model = AutoModelForCausalLM.from_pretrained(
313
+ str(MODEL_DIR / "nemotron-4b"),
314
  torch_dtype=torch.float16,
315
  device_map="auto"
316
  )
 
362
  return response
363
 
364
 
365
+ # ── 5. TEXT TO SPEECH ────────────────────────────────────────
366
+ @app.function(
367
+ gpu="T4",
368
+ image=tts_image,
369
+ timeout=600,
370
+ memory=8192,
371
+ volumes={str(MODEL_DIR): model_volume},
372
+ secrets=[modal.Secret.from_name("huggingface-secret")]
373
+ )
374
+ def text_to_speech(text: str, character: str) -> bytes:
375
+ import torch
376
+ import io
377
+ import soundfile as sf
378
+ import os
379
+
380
+ print("Downloading XTTS-v2...")
381
+ os.environ["COQUI_TOS_AGREED"] = "1"
382
+ from TTS.api import TTS
383
+ tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2")
384
+
385
+ speaker_map = {
386
+ "Grandma (Ammamma)": "Claribel Dervla",
387
+ "Chef": "Damien Black",
388
+ "Fitness Coach": "Abrahan Mack",
389
+ "Food Critic": "Annmarie Nele",
390
+ }
391
+ speaker = speaker_map.get(character, "Claribel Dervla")
392
+
393
+ # Keep short
394
+ sentences = text.replace("!", ".").replace("?", ".").split(".")
395
+ short_text = ". ".join([s.strip() for s in sentences[:2] if s.strip()]) + "."
396
+
397
+ wav = tts.tts(
398
+ text=short_text,
399
+ speaker=speaker,
400
+ language="hi"
401
+ )
402
+
403
+ buf = io.BytesIO()
404
+ sf.write(buf, wav, samplerate=24000, format="WAV")
405
+ return buf.getvalue()
406
+
407
+
408
  @app.local_entrypoint()
409
  def main():
410
+ print("Downloading all models to volume...")
411
  download_models.remote()
412
+ print("Done.")
413
  # Test expiry estimation
414
  test_items = ["INDIA WALA PANEER", "CURRIS LEAVES PACKET", "BASMATI RICE"]
415
  print("Testing expiry estimation...")
 
417
  print(expiry_map)
418
 
419
  # Test dialogue
420
+ print("\nTesting Nani dialogue...")
421
  dialogue = generate_dialogue.remote(
422
+ "Grandma (Nani)",
423
  ["INDIA WALA PANEER", "CURRIS LEAVES PACKET"],
424
  "South Indian"
425
  )
 
430
  all_items = [{"name": i} for i in test_items]
431
  expiring = [{"name": i} for i in test_items[:2]]
432
  recipe = generate_recipe_llm.remote(all_items, expiring, "South Indian")
433
+ print(recipe)
434
+
435
+ # Test TTS
436
+ print("\nTesting TTS...")
437
+ audio_bytes = text_to_speech.remote(dialogue, "Grandma (Nani)")
438
+ with open("test_audio.wav", "wb") as f:
439
+ f.write(audio_bytes)
440
+ print("Audio saved to test_audio.wav")
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ modal
3
+ pillow
4
+ soundfile
5
+ numpy