{
  "source_file": "0930",
  "source_platform": "Hashnode",
  "author": "Aman Jain",
  "title_listed": "How Much Context Does a Small GPT Model Really Need?",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://curious-pm.hashnode.dev/how-much-context-does-a-small-gpt-model-really-need",
  "publish_date": "2026-07-20",
  "corpus_stage": "",
  "notes": "",
  "id": "917dd6cddce3",
  "collected_at": "2026-10-02T14:11:44",
  "title": "Context Length in GPT Models: Quality vs Cost",
  "body_markdown": "How Much Context Does a Small GPT Model Really Need?\nThree controlled experiments on model quality, training throughput, and when additional context stops paying off.\nUpdated\nAugust 19, 2026\n•\n12\nmin read\n•\nView as Markdown\nA\nAman Jain\nProduct manager learning to build AI from the inside out — from LLM architecture to product applications.\nPart of series\nInside GPT: Experiments and Trade-offs\nTL;DR\nI ran three context-length experiments using the same compact GPT model (\nGithub\n):\nExperiment 1 — Fixed training-token budget:\nWhen every model processed approximately 200,000 tokens, the 64-token context produced the lowest loss, although the 32-token result was close. Contexts of 128 and 256 tokens did not improve quality in this setup.\nExperiment 2 — Fixed training-time budget:\nWhen every model trained for five minutes, the 32-token model processed the most data and produced the best validation result. The 64-token model remained close, while longer contexts completed fewer updates and produced higher loss.\nExperiment 3 — Training context vs evaluation context:\nGiving a model more context during evaluation than it had seen during training did not help. The model trained with 128-token sequences performed slightly worse when evaluated with 256 tokens.\nAcross these experiments, the most promising quality-cost region for this model, dataset, and training budget appeared to be around\n32–64 tokens\n.\nThis is not a general conclusion about language models. It is simply what I observed in this setup.\nStarting with a simple question\nLarger context windows are usually presented as an advantage.\nMore context gives a model access to more preceding text. In principle, that should help it connect ideas across sentences, retain earlier information, and make predictions using a broader view of the passage.\nMy initial assumption was therefore simple:\nIf I increase context length, the model should have more useful information available and make better next-token predictions.\nBut longer sequences also create larger attention calculations, reduce training throughput, and may allow the model to process fewer examples within a fixed amount of time.\nMore importantly, the additional context may not always contain useful information.\nSo I reframed the question:\nIn my setup, how does context length affect model quality and computational cost, and where do the gains begin to flatten?\nThe intuition: useful relationships may often be local\nConsider this sentence:\nThe cat sat on the wooden table because it was tired.\nTo predict the word after\nwas\n, the model probably does not need information from several paragraphs earlier. Nearby words already provide most of the useful signal.\nMany next-token predictions may work this way. Grammar, punctuation, common phrases, and sentence-level meaning can often be inferred from relatively nearby tokens.\nThis led to my working hypothesis:\nIn this dataset, many useful token relationships may fall within approximately 32 or 64 positions. Increasing context beyond that may add longer-range information, but not enough to justify the additional cost.\nThis was only an intuition to test. Longer-range relationships clearly exist. The question was whether they appeared frequently enough—and whether my small model had enough capacity and training exposure—to benefit from them.\nThe context lengths I compared\nI trained the same model with four context windows:\nContext length\nRough interpretation\n32 tokens\nPart of a sentence or a short passage\n64 tokens\nOne or more sentences\n128 tokens\nA longer paragraph\n256 tokens\nMultiple paragraphs\nThe model’s maximum supported context remained fixed at 256 tokens. Only the sequence length supplied during training changed.\nThis kept the positional-embedding table and parameter count identical across runs.\nMAX_MODEL_CONTEXT = 256\nTRAIN_CONTEXTS = [32, 64, 128, 256]\n\nMODEL_CONFIG = {\n    \"vocab_size\": 50_257,\n    \"context_length\": MAX_MODEL_CONTEXT,\n    \"emb_dim\": 128,\n    \"n_heads\": 4,\n    \"n_layers\": 4,\n    \"drop_rate\": 0.1,\n    \"qkv_bias\": False,\n}\nExperiment 1: Same number of training tokens\nFor the first experiment, I wanted every model to receive approximately the same training exposure.\nI adjusted batch size so each optimizer step processed 512 tokens:\nBATCH_SIZE_BY_CONTEXT = {\n    32: 16,\n    64: 8,\n    128: 4,\n    256: 2,\n}\nEvery run completed 391 optimizer steps and processed 200,192 tokens.\nThe question was:\nIf every model processes the same number of tokens, does giving each sequence more context improve prediction quality?\nThe core training loop remained the same across configurations:\ndef train_one_context(train_context, device=DEVICE):\n    batch_size = BATCH_SIZE_BY_CONTEXT[train_context]\n\n    # Build loaders and restore the shared model initialization.\n    model = GPTModel(MODEL_CONFIG)\n    model.load_state_dict(SHARED_INITIAL_STATE)\n    model = model.to(device)\n\n    for step in range(NUM_TRAINING_STEPS):\n        input_batch, target_batch = next(train_iterator)\n\n        optimizer.zero_grad(set_to_none=True)\n        logits = model(input_batch)\n        loss = F.cross_entropy(\n            logits.flatten(0, 1),\n            target_batch.flatten(),\n        )\n        loss.backward()\n        optimizer.step()\n\n        # Runtime, loss, throughput, and memory tracking omitted.\n\n    return result, history, generation_rows\nEvaluating the same prediction with different context lengths\nOrdinary validation windows at different context lengths may contain different passages and prediction targets. That makes it difficult to isolate the effect of context alone.\nTo make the comparison more controlled, I kept the target token fixed and changed only the amount of preceding context.\nSuppose\ntired\nis the target:\nContext 4:   table because it was                   → tired\nContext 8:   sat on the wooden table because it was → tired\nContext 16:  ...additional preceding text...        → tired\nEach longer context contains the shorter one.\nThis allowed me to ask:\nDoes adding earlier information improve the prediction of the exact same next token?\nThe dataset implementation was relatively small:\nclass NestedNextTokenDataset(Dataset):\n    def __init__(self, token_ids, target_positions, eval_context_length):\n        self.token_ids = torch.tensor(token_ids)\n        self.target_positions = target_positions\n        self.eval_context_length = eval_context_length\n\n    def __getitem__(self, index):\n        target_position = self.target_positions[index]\n        start = target_position - self.eval_context_length\n\n        input_ids = self.token_ids[start:target_position]\n        target_id = self.token_ids[target_position]\n\n        return input_ids, target_id\nFixed-token results\nThe matched nested evaluation produced:\nTraining context\nTarget loss\nTop-1 accuracy\n32\n6.8550\n14.84%\n64\n6.8353\n13.28%\n128\n6.8783\n13.87%\n256\n6.9479\n14.65%\nThe 64-token model produced the lowest target loss, although the difference from 32 tokens was small.\nI reran the experiment without changing the code. The exact values changed, but the overall pattern remained similar:\n32 and 64 tokens stayed relatively close.\n64 tokens produced the lowest loss.\n128 and 256 tokens did not improve average prediction loss.\nI would not claim that 64 tokens is universally better than 32. The more defensible observation is that increasing context to 128 or 256 tokens did not help in this setup.\nWhy loss and accuracy did not agree\nTop-1 accuracy did not follow exactly the same ordering as loss because the metrics measure different things.\nSuppose two models both fail to rank the correct token first:\nModel\nProbability assigned to the correct token\nTop-1 result\nModel A\n5%\nIncorrect\nModel B\n20%\nIncorrect\nBoth receive zero top-1 accuracy, but Model B receives a much better cross-entropy loss.\nFor this reason, I treated cross-entropy loss as the primary quality metric and top-1 accuracy as a supporting signal.\nExperiment 2: Same amount of training time\nFor the second experiment, every context configuration trained for five minutes.\nI also used a source file containing approximately 500,000 tokens. After the 90/10 split, 450,000 tokens were available for training.\nThe batch-size mapping remained unchanged, so every optimizer step still processed 512 tokens. The difference was that the number of optimizer steps was no longer fixed.\nFaster configurations could complete more updates and process more training data.\nThe question became:\nGiven the same amount of training time, does additional context compensate for lower throughput?\nTRAINING_TIME_SECONDS = 300.0\n\nwhile runtime_seconds < TRAINING_TIME_SECONDS:\n    step_started = time.perf_counter()\n\n    input_batch, target_batch = next(train_iterator)\n\n    optimizer.zero_grad(set_to_none=True)\n    logits = model(input_batch)\n    loss = F.cross_entropy(\n        logits.flatten(0, 1),\n        target_batch.flatten(),\n    )\n    loss.backward()\n    optimizer.step()\n\n    runtime_seconds += time.perf_counter() - step_started\n    optimizer_steps += 1\n    tokens_seen += input_batch.numel()\n\n    # Periodic evaluation and logging omitted.\nI also added untimed warm-up steps and restored the original model weights afterward. The execution order was randomized to reduce first-run and last-run effects.\nFixed-time results\nHere is what each configuration completed within approximately 300 seconds:\nContext\nSteps\nTokens processed\nTraining passes\nTokens/sec\nFinal loss\nAccuracy\n32\n687\n351,744\n0.78×\n1,171\n6.8528\n15.08%\n64\n677\n346,624\n0.77×\n1,155\n6.8756\n14.60%\n128\n633\n324,096\n0.72×\n1,080\n7.0078\n13.77%\n256\n591\n302,592\n0.67×\n1,009\n7.0112\n12.55%\nIn this run, the 32-token model:\ncompleted the most optimizer steps,\nprocessed the most training tokens,\nachieved the highest throughput,\nand produced the lowest final validation loss.\nThe 64-token result remained close.\nThe difference was clearer at 128 and 256 tokens. Those models processed less training data during the same five-minute period, and the additional context did not compensate for that reduced exposure.\nA different winner depending on the constraint\nThe two experiments produced slightly different winners.\nEqual training tokens\nThe 64-token context produced the best observed loss, with 32 tokens close behind.\nEqual training time\nThe 32-token context produced the best observed loss and processed slightly more training data.\nOne possible interpretation is:\nAt equal training exposure, the additional context between 32 and 64 tokens may provide a small benefit. Under a fixed time budget, the higher throughput of the 32-token model may offset that benefit.\nI would still treat the difference between 32 and 64 cautiously. The stronger signal was that 128- and 256-token contexts did not outperform the shorter configurations in these experiments.\nExperiment 3: Training context versus evaluation context\nFor the third experiment, I separated:\nthe context length used during training,\nthe context length supplied during evaluation.\nBecause every model technically supported 256 tokens, I could train a model using 128-token sequences and evaluate it using 256 tokens.\nThe relevant results were:\nTraining context\nEvaluation context\nRelationship\nTarget loss\nPerplexity\nAccuracy\n128\n128\nMatched\n6.8783\n970.97\n13.87%\n128\n256\nBeyond training\n6.9137\n1005.92\n12.70%\n256\n256\nMatched\n6.9479\n1040.93\n14.65%\nWhen the 128-token model received 256 tokens during evaluation, its loss became slightly worse.\nOne possible reason is that positions beyond 128 had not been meaningfully trained. The architecture accepted those positions, but that did not mean the model had learned to use them.\nThe model trained directly at 256 tokens also did not produce a better loss. In this setup, the additional distant tokens may not have contained enough frequently useful information, or the model may not have had enough capacity or training exposure to use them effectively.\nExperiment images\nThe following four outputs can be presented together as a two-by-two visual summary.\nFigure: Four views of the context-length trade-off in this experiment—quality, training exposure, throughput, and evaluation-length interaction.\nHow I interpret the combined results\nAcross the three experiments, I observed:\nContexts of 32 and 64 tokens produced relatively similar quality.\nA 64-token context performed slightly better when training exposure was fixed.\nA 32-token context performed slightly better when training time was fixed.\nContexts of 128 and 256 tokens reduced throughput without improving average loss.\nProviding more context during evaluation than the model had seen during training did not help.\nFor this particular setup, the most promising region appeared to be around 32–64 tokens.\nThere are several possible explanations:\nMuch of the predictive signal in the dataset may have been local.\nLonger-context batches contained fewer independent sequences.\nThe model may have needed more training to learn longer-range relationships.\nAverage next-token loss may underrepresent the relatively small number of predictions that require distant information.\nThe experiment also helped me distinguish among three related concepts:\nSupported context:\nThe maximum sequence length the architecture can technically accept.\nTraining context:\nThe sequence lengths the model actually sees while learning.\nEffective context:\nThe portion of the available history the model has learned to use productively.\nAll four models technically supported 256 tokens. That did not mean they had all been trained to use 256 positions, or that all 256 tokens contributed useful information to every prediction.\nFinal takeaway\nI started with the intuition that giving the model more preceding text should improve next-token prediction.\nIn this setup, the relationship was less straightforward.\nWith the same number of training tokens, 64 tokens produced the lowest observed loss.\nWith the same amount of training time, 32 tokens processed more data and produced the lowest observed loss.\nThe longer contexts of 128 and 256 tokens reduced throughput but did not improve quality in these runs.\nThe conclusion I would draw is deliberately narrow:\nFor this compact GPT model, WikiText dataset, and training budget, the useful quality-cost region appeared to be around 32–64 tokens. Increasing available context beyond that point did not produce enough measurable benefit to offset the additional cost.\nA larger context window gave the model the opportunity to use more information.\nWhether that information was useful—and whether the model could learn to use it—was a separate question.\nReferences\nA Comprehensive Survey on Long Context Language Modeling\nNoLiMa: Long-Context Evaluation Beyond Literal Matching\nContext Length Alone Hurts LLM Performance Despite Perfect Retrieval\nLongCodeBench: Evaluating Coding LLMs at 1M Context Windows\nLongRoPE2: Near-Lossless LLM Context Window Scaling\n#\ndeep-learning\n#\ncontext-length\n#\nlarge-language-models\n#\nartificial-intelligence\n10\nviews",
  "metadata": {
    "title": "Context Length in GPT Models: Quality vs Cost",
    "description": "I test 32–256 token context windows on a compact GPT model to compare quality, throughput, and when additional context stops paying off.",
    "keywords": null,
    "author": null,
    "og:title": "Context Length in GPT Models: Quality vs Cost",
    "og:description": "I test 32–256 token context windows on a compact GPT model to compare quality, throughput, and when additional context stops paying off.",
    "og:url": "https://curious-pm.hashnode.dev/how-much-context-does-a-small-gpt-model-really-need",
    "og:site_name": "Curious PM - Product to Parameters",
    "og:image": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/6339c212-3d0e-4b5d-ba7d-52c54e2d4017.png",
    "og:image:width": "1200",
    "og:image:height": "630",
    "og:type": "article",
    "twitter:card": "summary_large_image",
    "twitter:title": "Context Length in GPT Models: Quality vs Cost",
    "twitter:description": "I test 32–256 token context windows on a compact GPT model to compare quality, throughput, and when additional context stops paying off.",
    "twitter:image": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/6339c212-3d0e-4b5d-ba7d-52c54e2d4017.png",
    "article:published_time": "2026-07-20T04:45:03.957Z",
    "article:modified_time": "2026-08-19T05:05:24.136Z",
    "article:author": "https://curious-pm.hashnode.dev/@aman454"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/917dd6cddce3/code/body-000.txt",
      "chars": 249,
      "language": "text"
    },
    {
      "file": "attachments/917dd6cddce3/code/body-001.txt",
      "chars": 75,
      "language": "text"
    },
    {
      "file": "attachments/917dd6cddce3/code/body-002.txt",
      "chars": 742,
      "language": "text"
    },
    {
      "file": "attachments/917dd6cddce3/code/body-003.txt",
      "chars": 180,
      "language": "text"
    },
    {
      "file": "attachments/917dd6cddce3/code/body-004.txt",
      "chars": 563,
      "language": "text"
    },
    {
      "file": "attachments/917dd6cddce3/code/body-005.txt",
      "chars": 554,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/6339c212-3d0e-4b5d-ba7d-52c54e2d4017.png",
      "bytes": 1278447,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/000.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/6339c212-3d0e-4b5d-ba7d-52c54e2d4017.png",
      "alt": "How Much Context Does a Small GPT Model Really Need?",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/8a5af81d-6ed6-4545-b258-24d930240c3c.png",
      "bytes": 19558,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/001.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/8a5af81d-6ed6-4545-b258-24d930240c3c.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/1457b764-db42-491c-b8be-9c4685a6b902.png",
      "bytes": 104417,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/002.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/1457b764-db42-491c-b8be-9c4685a6b902.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/01763596-dce2-4248-8ddf-5750bb4ec7bf.png",
      "bytes": 95273,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/003.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/01763596-dce2-4248-8ddf-5750bb4ec7bf.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/3f4d99ec-fb9b-40a7-90c1-8ab394954f17.png",
      "bytes": 66985,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/004.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/3f4d99ec-fb9b-40a7-90c1-8ab394954f17.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/5c01acba-e96d-4688-b7d7-a7012670189b.png",
      "bytes": 59326,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/005.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/5c01acba-e96d-4688-b7d7-a7012670189b.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/69a25892-d662-4054-9be3-66d2a28b1b2a.png",
      "bytes": 70409,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/006.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/69a25892-d662-4054-9be3-66d2a28b1b2a.png",
      "alt": "",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/38d869af-6ef5-4f4f-a2a2-0ab9b5b62078.png",
      "bytes": 72032,
      "reused": true,
      "local_path": "attachments/917dd6cddce3/images/007.png",
      "src": "https://cdn.hashnode.com/uploads/covers/6a4f47d631bb27227f782535/38d869af-6ef5-4f4f-a2a2-0ab9b5b62078.png",
      "alt": "",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://curious-pm.hashnode.dev/how-much-context-does-a-small-gpt-model-really-need.md",
      "label": "View as Markdown",
      "type": "a"
    },
    {
      "href": "https://hashnode.com/@aman454",
      "label": "A",
      "type": "a"
    },
    {
      "href": "https://www.linkedin.com/in/aman-jain-1049188/",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/series/inside-gpt-experiments-and-trade-offs",
      "label": "Part of series Inside GPT: Experiments and Trade-offs",
      "type": "a"
    },
    {
      "href": "https://github.com/amanjain454/llms-from-scratch-work/tree/main/notebooks",
      "label": "Github",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/2503.17407",
      "label": "A Comprehensive Survey on Long Context Language Modeling",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/2502.05167",
      "label": "NoLiMa: Long-Context Evaluation Beyond Literal Matching",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/2510.05381",
      "label": "Context Length Alone Hurts LLM Performance Despite Perfect Retrieval",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/2505.07897",
      "label": "LongCodeBench: Evaluating Coding LLMs at 1M Context Windows",
      "type": "a"
    },
    {
      "href": "https://arxiv.org/abs/2502.20082",
      "label": "LongRoPE2: Near-Lossless LLM Context Window Scaling",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/deep-learning",
      "label": "# deep-learning",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/context-length",
      "label": "# context-length",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/large-language-models",
      "label": "# large-language-models",
      "type": "a"
    },
    {
      "href": "https://curious-pm.hashnode.dev/tag/artificial-intelligence",
      "label": "# artificial-intelligence",
      "type": "a"
    }
  ],
  "retry_at": "2026-10-03T08:09:06",
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:50:54Z",
  "source_url": "https://curious-pm.hashnode.dev/how-much-context-does-a-small-gpt-model-really-need",
  "papers": [
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/2503.17407",
      "bytes": 7471883,
      "reused": false,
      "local_path": "papers/repair/77d5625d4a937723b298.pdf"
    },
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/2502.05167",
      "bytes": 755027,
      "reused": false,
      "local_path": "papers/repair/39bf8eb3e5259bb0a695.pdf"
    },
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/2510.05381",
      "bytes": 631792,
      "reused": false,
      "local_path": "papers/repair/901731511125a7dcd294.pdf"
    },
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/2505.07897",
      "bytes": 1159494,
      "reused": false,
      "local_path": "papers/repair/d53d3117af26de4a7d5b.pdf"
    },
    {
      "status": "ok",
      "url": "https://arxiv.org/pdf/2502.20082",
      "bytes": 1529472,
      "reused": false,
      "local_path": "papers/repair/9ce66247402883c919a4.pdf"
    }
  ],
  "actual_video_files": [],
  "body_chars": 14945,
  "expected_body_images": 8,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/917dd6cddce3/pages/source.html",
  "local_html": "attachments/917dd6cddce3/pages/article.html",
  "body_base_path": "attachments/917dd6cddce3/pages/",
  "body_markdown_path": "attachments/917dd6cddce3/pages/article.md"
}