vankhieu Codex commited on
Commit
9cb7d65
·
1 Parent(s): 6b711ec

Tune generation speed and timeouts

Browse files

Co-authored-by: Codex <codex@openai.com>

Files changed (3) hide show
  1. app.py +36 -3
  2. executor.py +2 -2
  3. generator.py +27 -4
app.py CHANGED
@@ -385,7 +385,7 @@ def get_agent():
385
  return agent
386
 
387
 
388
- @spaces.GPU(duration=300)
389
  def run_visual_lab(prompt: str, retries: int) -> Tuple[str | None, str, str]:
390
  try:
391
  visual_agent = get_agent()
@@ -404,8 +404,41 @@ def run_visual_lab(prompt: str, retries: int) -> Tuple[str | None, str, str]:
404
  def load_example(example: str) -> str:
405
  examples = {
406
  "Fourier": "Visualize how a square wave emerges from the first seven Fourier sine components, with each partial sum drawn in neon colors.",
407
- "Orbit": "Show a satellite in elliptical orbit around Earth with velocity and gravitational force vectors changing over time.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  "Eigen": "Explain eigenvectors of a 2D linear transformation using a grid, two vectors, and the transformed plane.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  }
410
  return examples.get(example, "")
411
 
@@ -451,7 +484,7 @@ with gr.Blocks(css=custom_css, title="SciVisual-Agent") as demo:
451
  clear_btn = gr.Button("Clear", elem_id="clear-btn")
452
 
453
  example_choice = gr.Radio(
454
- choices=["Fourier", "Orbit", "Eigen"],
455
  label="Quick Load",
456
  value=None,
457
  interactive=True,
 
385
  return agent
386
 
387
 
388
+ @spaces.GPU(duration=600)
389
  def run_visual_lab(prompt: str, retries: int) -> Tuple[str | None, str, str]:
390
  try:
391
  visual_agent = get_agent()
 
404
  def load_example(example: str) -> str:
405
  examples = {
406
  "Fourier": "Visualize how a square wave emerges from the first seven Fourier sine components, with each partial sum drawn in neon colors.",
407
+ "Orbit": """Create a beautiful, scientifically accurate 2D physics animation of a satellite in an elliptical orbit around Earth using ManimCE.
408
+
409
+ Requirements:
410
+ 1. Scaling & Geometry (CRITICAL to avoid overlapping):
411
+ - Represent Earth as a clear sphere or circle in the center, but keep its radius small (e.g., radius=0.6) so it doesn't swallow the orbit.
412
+ - Create an explicit, highly elongated elliptical orbit path (`Ellipse(width=6.0, height=3.5)`) shifted slightly so that Earth sits exactly at one of the focal points (Foci) of the ellipse, NOT at the geometric center (Kepler's First Law).
413
+
414
+ 2. Dynamic Vectors & Updaters:
415
+ - Represent the satellite as a distinct, colored Dot (e.g., Cyber Cyan) moving along the elliptical path using a custom tracker or `ValueTracker` for the orbital angle.
416
+ - Create two dynamic arrows (`Vector` or `Arrow`) attached to the satellite dot:
417
+ * Gravitational Force Vector (Color: Neon Red): Must always point directly from the satellite's current position to Earth's center. Its length must dynamically increase when close to Earth and decrease when far away (Inverse-square law).
418
+ * Velocity Vector (Color: Bright Green): Must always be perfectly tangent to the elliptical orbit path in the direction of motion. Its length must dynamically represent orbital speed (faster at perigee, slower at apogee).
419
+ - Use `add_updater` on both vectors so their positions, directions, and lengths update smoothly at every single frame based on the satellite's position.
420
+
421
+ 3. Labels & Overlay:
422
+ - Add text labels for "Velocity (v)" and "Gravity (Fg)" matching the vector colors.
423
+ - In the top-right corner, add a LaTeX mathematical text displaying Newton's Law of Universal Gravitation: "F_g = G * (M_1 * M_2) / r²".
424
+
425
+ 4. Animation flow: Run the animation for 2 complete orbital periods (about 10-12 seconds) so the viewer can clearly observe the dramatic speeding up at the close approach and slowing down at the far end. Wrap everything inside a single clean Scene class.""",
426
  "Eigen": "Explain eigenvectors of a 2D linear transformation using a grid, two vectors, and the transformed plane.",
427
+ "Simple Pendulum": """Create a high-quality Physics simulation of a Damped Simple Pendulum using ManimCE.
428
+
429
+ Requirements:
430
+ 1. Physics Setup: Define explicit variables for gravitational acceleration (g=9.81), rod length (L=3.0), initial angle (theta = pi/4), and a damping coefficient (b=0.15) to simulate real-world air resistance. Use a clear numerical integration method (like Euler-Cromer) inside an object updater (`add_updater`) to dynamically recalculate the angular velocity and displacement at every frame (`dt`).
431
+
432
+ 2. Visual Elements (Strict Coordinate Updates):
433
+ - A fixed ceiling line or pivot dot at the top center.
434
+ - A colored dot (e.g., Cyber Cyan or Neon Pink) representing the heavy bob. Make it big enough for viewers to see clearly.
435
+ - A clean line representing the pendulum rod that attaches the pivot to the bob. CRITICAL: You must attach an updater to this rod using `add_updater` so that its end position dynamically calls `rod.put_start_and_end_on(pivot.get_center(), bob.get_center())` at every single frame. The rod must always move dynamically with the bob.
436
+ - Add an elegant fading trace/trail (`TracedPath`) attached to the bob to visually map its decaying sinusoidal path across the screen over time.
437
+
438
+ 3. Mathematical Overlay: In the upper left corner, display the differential equation governing the motion: "d²θ/dt² + (b/m)dθ/dt + (g/L)sin(θ) = 0" rendered beautifully via LaTeX.
439
+
440
+ 4. Animation flow: Let the simulation run smoothly for 12 seconds to clearly demonstrate the kinetic energy converting to thermal energy as the oscillation slowly dampens to a complete halt. Ensure the code is self-contained and wrapped inside a single executable Scene class.
441
+ """
442
  }
443
  return examples.get(example, "")
444
 
 
484
  clear_btn = gr.Button("Clear", elem_id="clear-btn")
485
 
486
  example_choice = gr.Radio(
487
+ choices=["Simple Pendulum", "Orbit", "Eigen"],
488
  label="Quick Load",
489
  value=None,
490
  interactive=True,
executor.py CHANGED
@@ -47,7 +47,7 @@ def _manim_command_prefix() -> list[str]:
47
 
48
  def render_code(
49
  code: str,
50
- timeout_seconds: int = 300,
51
  output_dir: Path = OUTPUT_DIR,
52
  quality: str = "-ql",
53
  ) -> RenderResult:
@@ -137,7 +137,7 @@ def render_code(
137
  Path(temp_script_name).unlink(missing_ok=True)
138
 
139
 
140
- def render_manim_scene(code: str, timeout_seconds: int = 300) -> Tuple[bool, str]:
141
  """Compatibility wrapper returning the tuple shape used by the app."""
142
  result = render_code(code, timeout_seconds=timeout_seconds)
143
  if result.success and result.video_path:
 
47
 
48
  def render_code(
49
  code: str,
50
+ timeout_seconds: int = 600,
51
  output_dir: Path = OUTPUT_DIR,
52
  quality: str = "-ql",
53
  ) -> RenderResult:
 
137
  Path(temp_script_name).unlink(missing_ok=True)
138
 
139
 
140
+ def render_manim_scene(code: str, timeout_seconds: int = 600) -> Tuple[bool, str]:
141
  """Compatibility wrapper returning the tuple shape used by the app."""
142
  result = render_code(code, timeout_seconds=timeout_seconds)
143
  if result.success and result.video_path:
generator.py CHANGED
@@ -33,7 +33,7 @@ class LocalModelConfig:
33
  prompt_mode: str = "chat"
34
  device_map: str = "auto"
35
  load_in_4bit: bool = True
36
- max_new_tokens: int = 4096
37
  temperature: float = 0.0
38
  top_p: float = 0.9
39
  timeout_seconds: int = 300
@@ -210,6 +210,9 @@ class LocalManimModel:
210
  if not self.ready:
211
  raise RuntimeError(self.error or "Local model is not ready.")
212
 
 
 
 
213
  if self.no_system_role_support and len(messages) >= 2 and messages[0]["role"] == "system":
214
  messages = [{"role": "user", "content": messages[0]["content"] + "\n\n" + messages[1]["content"]}]
215
 
@@ -227,6 +230,9 @@ class LocalManimModel:
227
  "pad_token_id": self.tokenizer.pad_token_id,
228
  "eos_token_id": self.tokenizer.eos_token_id,
229
  "use_cache": True,
 
 
 
230
  }
231
  if self.config.temperature and self.config.temperature > 0:
232
  generate_kwargs.update(
@@ -239,7 +245,8 @@ class LocalManimModel:
239
  else:
240
  generate_kwargs["do_sample"] = False
241
 
242
- generated_ids = self.model.generate(**generate_kwargs)
 
243
  output_ids = generated_ids[0][len(model_inputs.input_ids[0]) :].tolist()
244
 
245
  try:
@@ -276,6 +283,22 @@ class LocalManimModel:
276
  return "\n\n".join(chunks)
277
 
278
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  class ManimVisualAgent:
280
  """Generate Manim code with local inference and repair failures."""
281
 
@@ -426,9 +449,9 @@ def build_default_agent() -> ManimVisualAgent:
426
  prompt_mode=os.environ.get("SCIVISUAL_PROMPT_MODE", "chat"),
427
  device_map=os.environ.get("SCIVISUAL_DEVICE_MAP", "auto"),
428
  load_in_4bit=os.environ.get("SCIVISUAL_LOAD_IN_4BIT", "1").lower() not in ("0", "false", "no"),
429
- max_new_tokens=int(os.environ.get("SCIVISUAL_MAX_NEW_TOKENS", "4096")),
430
  temperature=float(os.environ.get("SCIVISUAL_TEMPERATURE", "0")),
431
  top_p=float(os.environ.get("SCIVISUAL_TOP_P", "0.9")),
432
- timeout_seconds=int(os.environ.get("SCIVISUAL_RENDER_TIMEOUT", "300")),
433
  )
434
  return ManimVisualAgent(config=config)
 
33
  prompt_mode: str = "chat"
34
  device_map: str = "auto"
35
  load_in_4bit: bool = True
36
+ max_new_tokens: int = 8192
37
  temperature: float = 0.0
38
  top_p: float = 0.9
39
  timeout_seconds: int = 300
 
210
  if not self.ready:
211
  raise RuntimeError(self.error or "Local model is not ready.")
212
 
213
+ import torch
214
+ from transformers import StoppingCriteriaList
215
+
216
  if self.no_system_role_support and len(messages) >= 2 and messages[0]["role"] == "system":
217
  messages = [{"role": "user", "content": messages[0]["content"] + "\n\n" + messages[1]["content"]}]
218
 
 
230
  "pad_token_id": self.tokenizer.pad_token_id,
231
  "eos_token_id": self.tokenizer.eos_token_id,
232
  "use_cache": True,
233
+ "stopping_criteria": StoppingCriteriaList(
234
+ [_StopOnTokenSequence(self.tokenizer.encode("</CODE>", add_special_tokens=False))]
235
+ ),
236
  }
237
  if self.config.temperature and self.config.temperature > 0:
238
  generate_kwargs.update(
 
245
  else:
246
  generate_kwargs["do_sample"] = False
247
 
248
+ with torch.inference_mode():
249
+ generated_ids = self.model.generate(**generate_kwargs)
250
  output_ids = generated_ids[0][len(model_inputs.input_ids[0]) :].tolist()
251
 
252
  try:
 
283
  return "\n\n".join(chunks)
284
 
285
 
286
+ class _StopOnTokenSequence:
287
+ """Stop generation when a specific token suffix appears."""
288
+
289
+ def __init__(self, stop_token_ids: List[int]) -> None:
290
+ self.stop_token_ids = stop_token_ids
291
+
292
+ def __call__(self, input_ids, scores, **kwargs) -> bool:
293
+ del scores, kwargs
294
+ if not self.stop_token_ids:
295
+ return False
296
+ if input_ids.shape[-1] < len(self.stop_token_ids):
297
+ return False
298
+ tail = input_ids[0, -len(self.stop_token_ids) :].tolist()
299
+ return tail == self.stop_token_ids
300
+
301
+
302
  class ManimVisualAgent:
303
  """Generate Manim code with local inference and repair failures."""
304
 
 
449
  prompt_mode=os.environ.get("SCIVISUAL_PROMPT_MODE", "chat"),
450
  device_map=os.environ.get("SCIVISUAL_DEVICE_MAP", "auto"),
451
  load_in_4bit=os.environ.get("SCIVISUAL_LOAD_IN_4BIT", "1").lower() not in ("0", "false", "no"),
452
+ max_new_tokens=int(os.environ.get("SCIVISUAL_MAX_NEW_TOKENS", "8192")),
453
  temperature=float(os.environ.get("SCIVISUAL_TEMPERATURE", "0")),
454
  top_p=float(os.environ.get("SCIVISUAL_TOP_P", "0.9")),
455
+ timeout_seconds=int(os.environ.get("SCIVISUAL_RENDER_TIMEOUT", "600")),
456
  )
457
  return ManimVisualAgent(config=config)