{
    "archive_path": "archive/1765045449.496355",
    "base_url": "huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language",
    "basename": "12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language",
    "bookmarked_date": "2025-12-06 18:24",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=huy.rocks",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "huy.rocks",
    "downloaded_at": "2025-12-06T18:24:16.687846+00:00",
    "downloaded_datestr": "2025-12-06 18:24",
    "extension": "",
    "hash": "1Y5K6NNBKMWJPB971KZ9",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-06T18:26:09.349633+00:00",
                "index_texts": null,
                "output": "TimeoutExpired: Command '['/usr/bin/curl', '--silent', '--location', '--compressed', '--proxy', 'socks5://tor-socks-proxy:9150', '--head', '--max-time', '60', '--user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)', 'https://web.archive.org/save/https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language']' timed out after 60 seconds",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:25:09.274460+00:00",
                "status": "failed"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--proxy-server=socks5://tor-socks-proxy:9150",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
                ],
                "cmd_version": "131.0.6778",
                "end_ts": "2025-12-06T18:24:37.413203+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:27.251434+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=huy.rocks"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-06T18:24:21.127896+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:17.157965+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-06T18:24:21.326682+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:21.161875+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2025-12-06T18:24:55.565632+00:00",
                "index_texts": [
                    "(/favicon.ico) (https://fonts.googleapis.com) (https://fonts.gstatic.com) Teaching an LLM a Niche Diagraming Language (https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language) (https://fonts.gstatic.com) (https://use.typekit.net) (/_next/static/css/6fa4639e26363dd4.css) (/_next/static/css/6fa4639e26363dd4.css) (/_next/static/css/a258a3523f7a910a.css) (/_next/static/css/a258a3523f7a910a.css)  (/_next/static/chunks/pages/index-957121b2bf0f1aa0.js) (/_next/static/chunks/pages/everyday-9fb95aa5c88f14b7.js)  (/) huy.rocks \u25ae   (/everyday) \u2190 Back to everyday  AI  Teaching an LLM a Niche Diagraming Language December 1, 2025  Text-to-diagram seems to be an area that has been solved perfectly by LLMs, but only with popular languages like Mermaid or PlantUML. There are many other less popular diagramming languages like D2, Structurizr, or Pintora. I want to see how far I can go teaching an LLM one of these languages, and (https://pintorajs.vercel.app/) Pintora \u25f9 seems like a nice option to pick because I know there will not be a lot of LLMs trained with this language. At least, not the small ones. Project goal  So, I set out the goal for this training project. It will be an LLM that has the ability to work with Pintora diagrams, for the following tasks: Generating diagrams from scratch Editing any existing diagram  Picking the base model  I also limit the scope of this project to models that are smaller than 30B, simply because I do not have enough resources to train anything bigger than that. Having experimented with 4B models and smaller, I found that these models are not really suitable for either a code-generating task or instruction following in a long conversation. A 7B-14B would be a good choice. I also want to base it off a coding model, hoping that it will be, well, code-smart. Gemma-3 only has 4B and 27B. The smallest Codestral model is 22B. Ministral 3 was not released until 3 days after the writing of this post. I don\u2019t want to deal with thinking mode, so Qwen3 is also out of the list. After a lot of consideration, I picked Qwen2.5-Coder-7B. In 2025, it\u2019s like centuries old in terms of LLM-years, but whatever. To confirm that Qwen2.5-Coder does not have any knowledge of Pintora, I tried to run it with the following prompt: create a pintora component diagram for a website like twitter    And yes, the model proceeded to generate a very well-made\u2026 PlantUML diagram: Below is a component diagram using Pintora syntax. You can copy and paste this into a Pintora editor to visualize it.  @startuml !define RECTANGLE class  title Twitter Component Diagram  RECTANGLE \"User Interface\" as UI { RECTANGLE \"Header\" as Header RECTANGLE \"Footer\" as Footer ...    The training plan  Since we\u2019re teaching the model a new language, we will need to train in two phases: Phase 1: Continued Pretraining (CPT) - We will feed the model a bunch of Pintora diagrams, so the model can learn the language\u2019s syntax, recognize grammar structure, etc. Phase 2: Instruction Finetune (IFT) - Then we will train the model on some diagram generating/editing instructions, so it can learn how to use the new language\u2019s knowledge on specific tasks.  I will use Unsloth\u2019s (https://docs.unsloth.ai/basics/continued-pretraining) training notebook \u25f9 , since it supports 4-bit quantized LoRA training, that helped training faster and using less memory. Data preparation  First thing\u2019s first, I need a dataset that I can use in both the CPT and IFT phases. I will go for the minimum amount of data needed (around 1000-1500 rows). Pintora supports different kinds of diagrams: Sequence, ER, Component, Activity, Mindmap, Gantt, Class,\u2026 I will need a diverse number of data for each case, so that\u2019s about 150-200 rows per diagram type. I want the model to have an ability to either generate a diagram from scratch or edit an existing diagram, so the dataset should also contain some examples that have an input diagram. The plan is, each row will contain three fields: instruction : the description of what type of diagram the user wants to create input : an optional input diagram code for editing output : the final output code that the model should generate  With that clear plan, I started typing out each row, and after about 5 rows, I gave up\u2026 This kind of labor is not productive at all! Why not just grab some code that people already created? I started searching on Github to see how much Pintora code there is. Not much, there were like 5 or 6 repositories that had some diagram code. Also, I don\u2019t like the idea of stealing someone\u2019s code without asking for their permission, not to mention, if I actually asked, I\u2019m not sure how many of them would respond. So, the last resort is to generate training data using AI! There\u2019s not much to talk about this step. The trick is to write an over-detailed prompt that includes all the syntax documentation, examples,\u2026 then some patience to beg the AI agent every 50 entries, threatening it that an alien Godzilla will destroy the Golden Gate Bridge if it\u2019s not completing its job. At the end of the begging process, I ended up with about 2000 data entries. The result was not great at all, both Gemini 3 Pro and Claude Sonnet 4.5 generated a lot of syntactically incorrect code and a lot of duplicated entries. To clean it up, I wrote a script to merge every row that has the same output column into one, and then, for each row, use the @pintora/cli tool to render the actual diagram, removing any rows where the output code cannot be used. In the end, I was left with 1000 rows for CPT and 500 rows for IFT. If you are interested, they are available on Hugging Face, links are at the bottom of the post. Training  I started the training process on Google Colab (using a single 16GB T4 GPU) and quickly ran into an OOM issue. The situation was not getting any better with Kaggle\u2019s 2xT4 GPUs. So I ended up renting a 48GB A40 on Runpod for $0.4/hr. It turned out that even for a 7B model with 4-bit QLoRA, my training script took about 19.33GB of VRAM to run, which was too much for 16GB of a T4 (the actual available VRAM was even less than that). But it was an unnecessary problem. Theoretically, since Pintora language still uses keywords that already exist in most English-based programming languages, the model did not need to learn any new tokens. I could save about 5GB-6GB of VRAM needed by removing the embed_tokens and lm_head from the target_modules . model = FastLanguageModel.get_peft_model( model, r = 64 ,  target_modules = [ \"q_proj\" , \"k_proj\" , \"v_proj\" , \"o_proj\" ,  \"gate_proj\" , \"up_proj\" , \"down_proj\" , \"embed_tokens\" , \"lm_head\" , # could have removed this  ], lora_alpha = 64 , lora_dropout = 0.05 , use_gradient_checkpointing = \"unsloth\" , ... )    Back to the training process. After the CPT phase, I ran a test to see how well the model learned the syntax. () ()  Since it started to pick up some syntax characteristic of Pintora, the diagram code is still syntactically incorrect. In the next step, I loaded the pintora-edit-instruct dataset, with each entry formatted with this edit_prompt , and started the IFT phase. edit_prompt = \"\"\"Pintora Diagram Edit Instruction  ### Instruction: {instruction} {input}  ### Response: {output} \"\"\"    After this step, the model already learned to generate more accurate and syntactically correct code, for both generating from scratch and editing tasks. Generate diagram from scratch  () ()  Editing existing diagram  () ()  So to this point, I have successfully taught Qwen2.5-Coder how to generate Pintora diagrams instead of spitting out random Mermaid/PlantUML diagrams. But how well has it learned? Evaluation for accuracy  To quickly evaluate the accuracy of the generated diagram (not the quality), I vibed created a script to use the model to generate with some randomized prompts: ...  entities = [ 'User' , 'Client' , 'WebApp' , 'Backend' , 'Server' , 'Database' , 'AuthService' , 'PaymentGateway' , 'Cache' , 'Redis' , 'Worker' , 'TaskQueue' , 'Frontend' , 'API Gateway' , 'OrderSystem' , 'Inventory' , 'NotificationSvc' , 'Logger' , 'MetricsSvc'  ] actions = [ 'requests login' , 'fetches data' , 'updates record' , 'processes payment' , 'validates token' , 'sends email' , 'renders view' , 'queries index' , 'health check' , 'ack signal' , 'authenticates user' , 'writes to log' , 'queries for user profile' , 'returns 200 OK' , 'returns 404 Not Found' , 'submits form' , 'enqueues job' , 'dequeues job' , 'generates report'  ] diagram_types = ['sequenceDiagram' , 'componentDiagram' , 'activityDiagram' ]  def create_from_scratch_task (): d_type = random.choice(diagram_types) num_interactions = random.randint(1 , 3 ) interactions = [] for _ in range (num_interactions): src, dst = random.sample(entities, 2 ) action = random.choice(actions) interactions.append(f\"{src} {action} to {dst} \" ) prompt_desc = \", and then \" .join(interactions) instruction = f\"Create a {d_type} that shows: {prompt_desc} .\"  output_code = model.generate(instruction) return [instruction, \"\" , output_code]  for i in range (1000 ): create_from_scratch_task() ...    Some example result: instruction input output   Create a activityDiagram that shows: PaymentGateway returns 404 Not Found to\u2026  activityDiagram start :Worker requests PaymentGateway; if (PaymentGateway returns 404)\u2026  Add a step where User health check to MetricsSvc. sequenceDiagram Cache->>User: enqueues job sequenceDiagram Cache->>User: enqueues job User->>MetricsSvc: health check  Add a step where PaymentGateway health check to Inventory. sequenceDiagram Worker->>PaymentGateway: enqueues job sequenceDiagram Worker->>PaymentGateway: enqueues job PaymentGateway->>Inventory: health check    Then, I use the same technique in the data preparation step, deduplicate the result, and parse each output code with the @pintora/cli command. In the end, out of 996 diagrams, we have 139 diagrams with syntax errors and 857 diagrams successfully rendered. That gives us 86% accuracy, not bad for such a really small amount of training data. Final thoughts  There were a lot of learnings for me during this experiment, and countless mistakes that I could have done better, but ultimately, I had a lot of fun. Maybe I\u2019ll try to tackle the accuracy next with RL, I heard many good and bad things about it and I must give it a try. I am also interested in this music programming language called (https://strudel.cc/) Strudel \u25f9 , and it would be fun to train an LLM for it. In the meantime, if you are interested, here\u2019s the model (with GGUF) and the datasets, as well as the eval result below: Model:  (https://huggingface.co/huytd189/pintora-coder-7b) https://huggingface.co/huytd189/pintora-coder-7b \u25f9  (https://huggingface.co/huytd189/pintora-coder-7b-gguf) https://huggingface.co/huytd189/pintora-coder-7b-gguf \u25f9 (GGUF - F16, Q8, Q4_K_M)  Dataset:  (https://huggingface.co/datasets/huytd189/pintora-instruct) https://huggingface.co/datasets/huytd189/pintora-instruct \u25f9  (https://huggingface.co/datasets/huytd189/pintora-edit-instruct) https://huggingface.co/datasets/huytd189/pintora-edit-instruct \u25f9   Eval result:  (_meta/pintora_eval.csv) pintora_eval.csv  (_meta/pintora_eval_bad.csv) pintora_eval_bad.csv  (_meta/pintora_eval_good.csv) pintora_eval_good.csv   Now it's time for an ad if you don't mind ;) If you want to try AI-assisted diagramming yourself, check out my project, (https://chatuml.com) ChatUML . It's designed to turn text into diagrams instantly. You can grab 60% off right now using the code PINTORA !    (/everyday) \u2190 All posts (/category/ai) More in AI\u2192    (https://masto.ai/@huy) \ud83d\udc18 @huy   (/rss.xml) \ud83d\udcee RSS           "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:55.531610+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2025-12-06T18:25:09.203546+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:57.370145+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2025-12-06T18:24:55.497007+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:51.603586+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmpg38ejz36",
                    "https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2025-12-06T18:24:43.558402+00:00",
                "index_texts": [
                    "Text-to-diagram seems to be an area that has been solved perfectly by LLMs, but only with popular languages like Mermaid or PlantUML. There are many other less popular diagramming languages like D2, Structurizr, or Pintora. I want to see how far I can go teaching an LLM one of these languages, and Pintora\u25f9 seems like a nice option to pick because I know there will not be a lot of LLMs trained with this language. At least, not the small ones.\nProject goalSo, I set out the goal for this training project. It will be an LLM that has the ability to work with Pintora diagrams, for the following tasks:\n\nGenerating diagrams from scratch\nEditing any existing diagram\n\nPicking the base modelI also limit the scope of this project to models that are smaller than 30B, simply because I do not have enough resources to train anything bigger than that.\nHaving experimented with 4B models and smaller, I found that these models are not really suitable for either a code-generating task or instruction following in a long conversation. A 7B-14B would be a good choice. I also want to base it off a coding model, hoping that it will be, well, code-smart.\nGemma-3 only has 4B and 27B. The smallest Codestral model is 22B. Ministral 3 was not released until 3 days after the writing of this post. I don\u2019t want to deal with thinking mode, so Qwen3 is also out of the list. After a lot of consideration, I picked Qwen2.5-Coder-7B. In 2025, it\u2019s like centuries old in terms of LLM-years, but whatever.\nTo confirm that Qwen2.5-Coder does not have any knowledge of Pintora, I tried to run it with the following prompt:\ncreate a pintora component diagram for a website like twitter\n\nAnd yes, the model proceeded to generate a very well-made\u2026 PlantUML diagram:\nBelow is a component diagram using Pintora syntax. You can copy and paste this into a Pintora editor to visualize it.\n \n@startuml\n!define RECTANGLE class\n \ntitle Twitter Component Diagram\n \nRECTANGLE \"User Interface\" as UI {\n  RECTANGLE \"Header\" as Header\n  RECTANGLE \"Footer\" as Footer\n  ...\n\nThe training planSince we\u2019re teaching the model a new language, we will need to train in two phases:\n\nPhase 1: Continued Pretraining (CPT) - We will feed the model a bunch of Pintora diagrams, so the model can learn the language\u2019s syntax, recognize grammar structure, etc.\nPhase 2: Instruction Finetune (IFT) - Then we will train the model on some diagram generating/editing instructions, so it can learn how to use the new language\u2019s knowledge on specific tasks.\n\nI will use Unsloth\u2019s training notebook\u25f9, since it supports 4-bit quantized LoRA training, that helped training faster and using less memory.\nData preparationFirst thing\u2019s first, I need a dataset that I can use in both the CPT and IFT phases. I will go for the minimum amount of data needed (around 1000-1500 rows).\nPintora supports different kinds of diagrams: Sequence, ER, Component, Activity, Mindmap, Gantt, Class,\u2026 I will need a diverse number of data for each case, so that\u2019s about 150-200 rows per diagram type.\nI want the model to have an ability to either generate a diagram from scratch or edit an existing diagram, so the dataset should also contain some examples that have an input diagram.\nThe plan is, each row will contain three fields:\n\ninstruction: the description of what type of diagram the user wants to create\ninput: an optional input diagram code for editing\noutput: the final output code that the model should generate\n\nWith that clear plan, I started typing out each row, and after about 5 rows, I gave up\u2026 This kind of labor is not productive at all!\nWhy not just grab some code that people already created? I started searching on Github to see how much Pintora code there is. Not much, there were like 5 or 6 repositories that had some diagram code. Also, I don\u2019t like the idea of stealing someone\u2019s code without asking for their permission, not to mention, if I actually asked, I\u2019m not sure how many of them would respond.\nSo, the last resort is to generate training data using AI! There\u2019s not much to talk about this step. The trick is to write an over-detailed prompt that includes all the syntax documentation, examples,\u2026 then some patience to beg the AI agent every 50 entries, threatening it that an alien Godzilla will destroy the Golden Gate Bridge if it\u2019s not completing its job.\nAt the end of the begging process, I ended up with about 2000 data entries. The result was not great at all, both Gemini 3 Pro and Claude Sonnet 4.5 generated a lot of syntactically incorrect code and a lot of duplicated entries.\nTo clean it up, I wrote a script to merge every row that has the same output column into one, and then, for each row, use the @pintora/cli tool to render the actual diagram, removing any rows where the output code cannot be used.\nIn the end, I was left with 1000 rows for CPT and 500 rows for IFT. If you are interested, they are available on Hugging Face, links are at the bottom of the post.\nTrainingI started the training process on Google Colab (using a single 16GB T4 GPU) and quickly ran into an OOM issue. The situation was not getting any better with Kaggle\u2019s 2xT4 GPUs. So I ended up renting a 48GB A40 on Runpod for $0.4/hr.\nIt turned out that even for a 7B model with 4-bit QLoRA, my training script took about 19.33GB of VRAM to run, which was too much for 16GB of a T4 (the actual available VRAM was even less than that). But it was an unnecessary problem.\nTheoretically, since Pintora language still uses keywords that already exist in most English-based programming languages, the model did not need to learn any new tokens. I could save about 5GB-6GB of VRAM needed by removing the embed_tokens and lm_head from the target_modules.\nmodel = FastLanguageModel.get_peft_model(\n    model,\n    r = 64, \n    target_modules = [\n        \"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \n        \"gate_proj\", \"up_proj\", \"down_proj\",\n        \"embed_tokens\", \"lm_head\", # could have removed this\n    ],\n    lora_alpha = 64,\n    lora_dropout = 0.05,\n    use_gradient_checkpointing = \"unsloth\",\n    ...\n)\n\nBack to the training process. After the CPT phase, I ran a test to see how well the model learned the syntax.\n\nSince it started to pick up some syntax characteristic of Pintora, the diagram code is still syntactically incorrect.\nIn the next step, I loaded the pintora-edit-instruct dataset, with each entry formatted with this edit_prompt, and started the IFT phase.\nedit_prompt = \"\"\"Pintora Diagram Edit Instruction\n \n### Instruction:\n{instruction}\n{input}\n \n### Response:\n{output}\n\"\"\"\n\nAfter this step, the model already learned to generate more accurate and syntactically correct code, for both generating from scratch and editing tasks.\nGenerate diagram from scratch\n\nEditing existing diagram\n\nSo to this point, I have successfully taught Qwen2.5-Coder how to generate Pintora diagrams instead of spitting out random Mermaid/PlantUML diagrams. But how well has it learned?\nEvaluation for accuracyTo quickly evaluate the accuracy of the generated diagram (not the quality), I vibed created a script to use the model to generate with some randomized prompts:\n...\n \nentities = [\n    'User', 'Client', 'WebApp', 'Backend', 'Server', 'Database', 'AuthService',\n    'PaymentGateway', 'Cache', 'Redis', 'Worker', 'TaskQueue', 'Frontend',\n    'API Gateway', 'OrderSystem', 'Inventory', 'NotificationSvc', 'Logger', 'MetricsSvc'\n]\nactions = [\n    'requests login', 'fetches data', 'updates record', 'processes payment',\n    'validates token', 'sends email', 'renders view', 'queries index',\n    'health check', 'ack signal', 'authenticates user', 'writes to log',\n    'queries for user profile', 'returns 200 OK', 'returns 404 Not Found',\n    'submits form', 'enqueues job', 'dequeues job', 'generates report'\n]\ndiagram_types = ['sequenceDiagram', 'componentDiagram', 'activityDiagram']\n \ndef create_from_scratch_task():\n    d_type = random.choice(diagram_types)\n    num_interactions = random.randint(1, 3)\n    interactions = []\n    for _ in range(num_interactions):\n        src, dst = random.sample(entities, 2)\n        action = random.choice(actions)\n        interactions.append(f\"{src} {action} to {dst}\")\n    prompt_desc = \", and then \".join(interactions)\n    instruction = f\"Create a {d_type} that shows: {prompt_desc}.\"\n    output_code = model.generate(instruction)\n    return [instruction, \"\", output_code]\n \nfor i in range(1000):\n    create_from_scratch_task()\n    ...\n\nSome example result:\n\n\n\ninstruction\ninput\noutput\n\n\n\nCreate a activityDiagram that shows: PaymentGateway returns 404 Not Found to\u2026\n\nactivityDiagram start :Worker requests PaymentGateway; if (PaymentGateway returns 404)\u2026\n\n\nAdd a step where User health check to MetricsSvc.\nsequenceDiagram Cache->>User: enqueues job\nsequenceDiagram Cache->>User: enqueues job User->>MetricsSvc: health check\n\n\nAdd a step where PaymentGateway health check to Inventory.\nsequenceDiagram Worker->>PaymentGateway: enqueues job\nsequenceDiagram Worker->>PaymentGateway: enqueues job PaymentGateway->>Inventory: health check\n\n\nThen, I use the same technique in the data preparation step, deduplicate the result, and parse each output code with the @pintora/cli command.\nIn the end, out of 996 diagrams, we have 139 diagrams with syntax errors and 857 diagrams successfully rendered. That gives us 86% accuracy, not bad for such a really small amount of training data.\nFinal thoughtsThere were a lot of learnings for me during this experiment, and countless mistakes that I could have done better, but ultimately, I had a lot of fun. Maybe I\u2019ll try to tackle the accuracy next with RL, I heard many good and bad things about it and I must give it a try.\nI am also interested in this music programming language called Strudel\u25f9, and it would be fun to train an LLM for it.\nIn the meantime, if you are interested, here\u2019s the model (with GGUF) and the datasets, as well as the eval result below:\nModel:\n\nhttps://huggingface.co/huytd189/pintora-coder-7b\u25f9 \nhttps://huggingface.co/huytd189/pintora-coder-7b-gguf\u25f9 (GGUF - F16, Q8, Q4_K_M)\n\nDataset:\n\nhttps://huggingface.co/datasets/huytd189/pintora-instruct\u25f9\nhttps://huggingface.co/datasets/huytd189/pintora-edit-instruct\u25f9\n\nEval result:\n\npintora_eval.csv\npintora_eval_bad.csv\npintora_eval_good.csv\n\n\nNow it's time for an ad if you don't mind ;)\nIf you want to try AI-assisted diagramming yourself, check out my project, ChatUML. It's designed to turn text into diagrams instantly. You can grab 60% off right now using the code PINTORA!"
                ],
                "output": "readability/",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:39.203852+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-06T18:24:37.492694+00:00",
                "index_texts": null,
                "output": "Teaching an LLM a Niche Diagraming Language",
                "pwd": "/data/archive/1765045449.496355",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-06T18:24:37.465989+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "TimeoutExpired: Command '['/usr/bin/curl', '--silent', '--location', '--compressed', '--proxy', 'socks5://tor-socks-proxy:9150', '--head', '--max-time', '60', '--user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)', 'https://web.archive.org/save/https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language']' timed out after 60 seconds",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Teaching an LLM a Niche Diagraming Language",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1765045449.496355",
    "newest_archive_date": "2025-12-06T18:25:09.274460+00:00",
    "num_failures": 1,
    "num_outputs": 8,
    "oldest_archive_date": "2025-12-06T18:24:17.157965+00:00",
    "path": "/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01KBTEGCTV3682FC1301G3JWSE",
    "snapshot_id": "306cd302-5f2c-411b-b950-0188e039732e",
    "sources": [
        "/data/sources/1765045448-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1765045449.496355",
    "title": "Teaching an LLM a Niche Diagraming Language",
    "url": "https://huy.rocks/everyday/12-01-2025-ai-teaching-an-llm-a-niche-diagraming-language"
}