| """dcode Gradio Space - Text to Gcode inference.""" |
|
|
| import re |
| import gradio as gr |
| import torch |
| from transformers import AutoModelForSeq2SeqLM, AutoModelForCausalLM, AutoTokenizer |
|
|
| |
| MODELS = { |
| "flan-t5-base (best)": "twarner/dcode-flan-t5-base", |
| } |
|
|
| |
| BOUNDS = {"left": -420.5, "right": 420.5, "top": 594.5, "bottom": -594.5} |
|
|
| |
| _model_cache = {} |
|
|
|
|
| def get_model(model_name: str): |
| """Load and cache model.""" |
| if model_name not in _model_cache: |
| model_id = MODELS[model_name] |
| device = "cuda" if torch.cuda.is_available() else "cpu" |
| dtype = torch.float16 if device == "cuda" else torch.float32 |
| |
| tokenizer = AutoTokenizer.from_pretrained(model_id) |
| |
| if "gpt2" in model_id or "codegen" in model_id: |
| model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=dtype).to(device) |
| else: |
| model = AutoModelForSeq2SeqLM.from_pretrained(model_id, torch_dtype=dtype).to(device) |
| |
| model.eval() |
| _model_cache[model_name] = (model, tokenizer, device) |
| |
| return _model_cache[model_name] |
|
|
|
|
| def validate_gcode(gcode: str) -> str: |
| """Clamp coordinates to machine bounds.""" |
| lines = [] |
| for line in gcode.split("\n"): |
| corrected = line |
| |
| x_match = re.search(r"X([-\d.]+)", line, re.IGNORECASE) |
| if x_match: |
| x = float(x_match.group(1)) |
| x = max(BOUNDS["left"], min(BOUNDS["right"], x)) |
| corrected = re.sub(r"X[-\d.]+", f"X{x:.2f}", corrected, flags=re.IGNORECASE) |
|
|
| y_match = re.search(r"Y([-\d.]+)", line, re.IGNORECASE) |
| if y_match: |
| y = float(y_match.group(1)) |
| y = max(BOUNDS["bottom"], min(BOUNDS["top"], y)) |
| corrected = re.sub(r"Y[-\d.]+", f"Y{y:.2f}", corrected, flags=re.IGNORECASE) |
|
|
| lines.append(corrected) |
|
|
| return "\n".join(lines) |
|
|
|
|
| def generate(prompt: str, model_name: str, temperature: float, max_tokens: int) -> str: |
| """Generate gcode from prompt.""" |
| if not prompt or not prompt.strip(): |
| return "Enter a prompt to generate gcode" |
|
|
| try: |
| model, tokenizer, device = get_model(model_name) |
| model_id = MODELS[model_name] |
| |
| inputs = tokenizer(prompt, return_tensors="pt", max_length=128, truncation=True) |
| inputs = {k: v.to(device) for k, v in inputs.items()} |
|
|
| with torch.no_grad(): |
| outputs = model.generate( |
| **inputs, |
| max_new_tokens=max_tokens, |
| do_sample=True, |
| temperature=temperature, |
| top_p=0.9, |
| pad_token_id=tokenizer.eos_token_id, |
| ) |
|
|
| |
| if "gpt2" in model_id or "codegen" in model_id: |
| gcode = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) |
| else: |
| gcode = tokenizer.decode(outputs[0], skip_special_tokens=True) |
| |
| gcode = validate_gcode(gcode) |
| line_count = len(gcode.split("\n")) |
| return f"; dcode output - {line_count} lines\n; Model: {model_name}\n; Machine validated\n\n{gcode}" |
| except Exception as e: |
| return f"; Error: {e}" |
|
|
|
|
| demo = gr.Interface( |
| fn=generate, |
| inputs=[ |
| gr.Textbox(label="Prompt", placeholder="drawing of a cat...", lines=2), |
| gr.Dropdown(choices=list(MODELS.keys()), value="flan-t5-base (best)", label="Model"), |
| gr.Slider(0.1, 1.5, value=0.8, label="Temperature", info="Higher = more creative"), |
| gr.Slider(256, 2048, value=1024, step=256, label="Max Tokens"), |
| ], |
| outputs=gr.Code(label="Gcode", language=None, lines=25), |
| title="dcode", |
| description="**Text → Polargraph Gcode** | Generate machine-compatible gcode from natural language. [GitHub](https://github.com/Twarner491/dcode) | [Model](https://huggingface.co/twarner/dcode-flan-t5-base) | [Dataset](https://huggingface.co/datasets/twarner/dcode-polargraph-gcode)", |
| examples=[ |
| ["drawing of a cat", "flan-t5-base (best)", 0.8, 1024], |
| ["abstract spiral pattern", "flan-t5-base (best)", 0.9, 1024], |
| ["simple house with chimney", "flan-t5-base (best)", 0.7, 512], |
| ["portrait of a woman", "flan-t5-base (best)", 0.8, 1024], |
| ], |
| theme=gr.themes.Soft(primary_hue="emerald"), |
| article=""" |
| ## About |
| |
| dcode finetunes text-to-text models to directly output polargraph-compatible gcode from natural language descriptions. |
| |
| **Training**: Flan-T5-base trained on 175,952 art-caption-gcode triplets for 20 epochs on H100. |
| |
| **Machine Bounds**: X: ±420.5mm, Y: ±594.5mm | Pen servo: 40° (down) / 90° (up) |
| |
| **License**: MIT |
| """, |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|