LM Studio developer on retainer: default 4,096-token context window silently truncates 8,000-token documents — last 2,000–4,000 tokens dropped with no error — finish_reason: “stop” returned on truncated input — summarization quality degrades on long documents for 4 weeks — LM Studio developer on monthly retainer
requests 2.31.0 • Python 3.11 • LM Studio 0.2.29 • localhost:1234 • mistral-7b-instruct-v0.2.Q4_K_M.gguf • default Context Length 4,096 • 8,000-token documents silently truncated • no error, no warning • 4 weeks • fix: Model Settings → Context Length → 8192 • 2.0h retainer engagement
Westfield Local AI Ltd. deployed an on-premises technical report summarization pipeline in September 2026 using LM Studio 0.2.29 running Mistral-7B-Instruct-v0.2 in GGUF format on a local workstation. The integration called LM Studio's OpenAI-compatible API at localhost:1234. During initial testing with short abstracts — all under 2,000 tokens — every summary was correct and complete. In production, reports ranged from 6,000 to 8,000 tokens. Summaries consistently referenced only the introduction and first two sections of each report. Final sections, conclusions, and recommendations were never mentioned. No HTTP errors. No warnings. The same finish_reason: "stop" appeared on every response. After four weeks of investigating prompt templates, model versions, and document encoding, they engaged an LM Studio retainer developer.
The summarizer at the time of the engagement:
# report_summarizer.py
# Westfield Local AI Ltd.
# requests 2.31.0, Python 3.11
# LM Studio 0.2.29, localhost:1234
# mistral-7b-instruct-v0.2.Q4_K_M.gguf
import requests
import json
LM_STUDIO_URL = "http://localhost:1234/v1/chat/completions"
def summarize_report(report_text: str) -> str:
"""Summarize a technical report using local Mistral-7B via LM Studio."""
payload = {
"model": "mistral-7b-instruct-v0.2",
"messages": [
{
"role": "system",
"content": "You are a technical report summarizer. Produce a concise 3-paragraph summary covering: (1) objectives and methodology, (2) key findings, (3) conclusions and recommendations."
},
{
"role": "user",
"content": f"Summarize this technical report:\n\n{report_text}"
}
],
"max_tokens": 500,
"temperature": 0.1,
}
response = requests.post(LM_STUDIO_URL, json=payload, timeout=120)
response.raise_for_status()
data = response.json()
return data["choices"][0]["message"]["content"]
# LM Studio Model Settings (never changed from default):
# Context Length: 4096
# ↑ Mistral-7B-Instruct-v0.2's true maximum context: 32,768 tokens
# ↑ LM Studio defaults to 4,096 for GGUF models (memory-conservative default)
# ↑ 6,000–8,000 token reports exceed this by 50–100%
# What LM Studio does with a 7,200-token prompt when n_ctx=4,096:
# llama.cpp receives the prompt string
# llama.cpp tokenizes it: 7,200 tokens
# llama.cpp context is configured for 4,096 tokens (n_ctx=4096)
# llama.cpp silently truncates: processes only the FIRST 4,096 tokens
# The last 3,104 tokens (43% of the document) are dropped silently
# No exception is raised. No truncation flag is set.
# HTTP response: 200 OK
# finish_reason: "stop" (model finished normally, not "length")
# The model summarizes whatever it saw — first 4,096 tokens of the report
# Observed behavior over 4 weeks on 300 daily reports:
# Short reports (<2,000 tokens): 100% correct summaries
# Long reports (6,000–8,000 tokens): summaries cover only introduction + first 2 sections
# Conclusions paragraph: always generic ("further research is recommended")
# Specific findings from later sections: never mentioned
# Team diagnosis attempts:
# - Adjusted system prompt (no improvement)
# - Switched to Q5_K_M quantization (same truncation, same context limit)
# - Reinstalled LM Studio (reinstalled with same 4,096 default)
# - Checked PDF extraction encoding (UTF-8, correct)
# - Never checked LM Studio Model Settings → Context Length
Why LM Studio defaults to 4,096 tokens
LM Studio uses llama.cpp under the hood. When loading a GGUF model, llama.cpp must allocate a KV cache sized for the configured context window. A larger context window requires proportionally more RAM and VRAM. Mistral-7B-Instruct-v0.2 running with a 32,768-token context window requires approximately 2× the KV cache memory of the same model at 4,096 tokens. LM Studio defaults to 4,096 tokens to ensure the model loads successfully across a wide range of hardware. The GGUF model file contains a metadata field indicating the model's trained maximum context length, but LM Studio does not automatically use this value.
The truncation happens silently because llama.cpp's behavior when given a prompt longer than n_ctx is to truncate. It does not raise an exception. The LM Studio HTTP server returns an HTTP 200 response with a normal completion. The only indication that truncation occurred is that the summary lacks content from the second half of the document.
# Diagnosing the truncation
# Step 1: measure how many tokens a representative document actually is
import tiktoken
# tiktoken's cl100k_base encoding is a reasonable approximation for Mistral
# (Mistral uses its own SentencePiece tokenizer, but cl100k_base gives
# a close-enough estimate for length checking purposes)
enc = tiktoken.get_encoding("cl100k_base")
SYSTEM_PROMPT = """You are a technical report summarizer...""" # ~45 tokens
with open("sample_report.txt") as f:
report_text = f.read()
full_prompt = SYSTEM_PROMPT + "\n\nSummarize this technical report:\n\n" + report_text
token_count = len(enc.encode(full_prompt))
print(f"Total prompt tokens: {token_count}")
# Total prompt tokens: 7,247
print(f"LM Studio Context Length: 4096")
print(f"Tokens that fit: 4096")
print(f"Tokens silently dropped: {token_count - 4096}")
# Tokens silently dropped: 3,151
# Step 2: verify truncation by checking which document sections appear in summary
# Document structure:
# 0–800 tokens: Introduction and Background (appears in all summaries ✓)
# 800–2,000: Methodology (appears in all summaries ✓)
# 2,000–4,096: Results Part 1 (appears in summaries, last section before cutoff)
# 4,096–5,500: Results Part 2 (NEVER appears in summaries — starts after cutoff)
# 5,500–6,800: Discussion (NEVER appears)
# 6,800–7,247: Conclusions (NEVER appears — team reports "generic" conclusions)
# The cutoff is not at a section boundary — it happens mid-sentence
# in Results Part 1, which is why Part 1 summaries are incomplete
The fix: increase Context Length in LM Studio
# LM Studio Context Length configuration steps:
#
# 1. Open LM Studio
# 2. Select the loaded model (mistral-7b-instruct-v0.2.Q4_K_M)
# 3. Go to Model Settings (gear icon next to the loaded model)
# 4. Find "Context Length" — shows "4096" (default)
# 5. Change to "8192" (covers 8,000-token reports with headroom)
# 6. Click "Reload Model" — LM Studio reallocates KV cache for new n_ctx
#
# VRAM impact of context window increase:
# Mistral-7B Q4_K_M base model weights: ~4.1 GB
# KV cache at n_ctx=4096: ~0.5 GB (per layer, 32 layers)
# KV cache at n_ctx=8192: ~1.0 GB
# Total VRAM at n_ctx=4096: ~4.6 GB
# Total VRAM at n_ctx=8192: ~5.1 GB
# Required GPU: any 6 GB+ VRAM card (RTX 3060 12GB, RTX 4070, etc.)
# report_summarizer.py — FIXED
# requests 2.31.0, Python 3.11
# LM Studio 0.2.29, Context Length: 8192 (changed from default 4096)
import requests
import tiktoken
LM_STUDIO_URL = "http://localhost:1234/v1/chat/completions"
CONTEXT_WINDOW = 8192 # must match LM Studio Model Settings → Context Length
CONTEXT_SAFETY_MARGIN = 0.9 # leave 10% headroom for output tokens
SYSTEM_PROMPT = (
"You are a technical report summarizer. Produce a concise 3-paragraph "
"summary covering: (1) objectives and methodology, (2) key findings, "
"(3) conclusions and recommendations."
)
enc = tiktoken.get_encoding("cl100k_base")
def summarize_report(report_text: str) -> str:
"""Summarize a technical report using local Mistral-7B via LM Studio."""
user_message = f"Summarize this technical report:\n\n{report_text}"
full_prompt = SYSTEM_PROMPT + "\n\n" + user_message
token_count = len(enc.encode(full_prompt))
max_allowed = int(CONTEXT_WINDOW * CONTEXT_SAFETY_MARGIN)
assert token_count <= max_allowed, (
f"Prompt too long: {token_count} tokens exceeds "
f"{max_allowed} (90% of {CONTEXT_WINDOW}-token context window). "
f"Chunk the document before calling summarize_report()."
)
# ↑ This assertion fires BEFORE the API call — catching oversized documents
# at call time rather than silently truncating them.
# LM Studio would have silently accepted and truncated the request.
# The assertion makes truncation a hard error instead of silent data loss.
payload = {
"model": "mistral-7b-instruct-v0.2",
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
"max_tokens": 500,
"temperature": 0.1,
}
response = requests.post(LM_STUDIO_URL, json=payload, timeout=120)
response.raise_for_status()
data = response.json()
# Log finish_reason — "stop" is normal, "length" means output was truncated
finish_reason = data["choices"][0]["finish_reason"]
if finish_reason == "length":
import logging
logging.warning(
f"LM Studio output truncated (finish_reason=length). "
f"Increase max_tokens or reduce document length."
)
return data["choices"][0]["message"]["content"]
def chunk_and_summarize(report_text: str, chunk_tokens: int = 6000) -> str:
"""Handle reports exceeding context window by chunking and summarizing chunks."""
user_base = "Summarize this technical report:\n\n"
base_tokens = len(enc.encode(SYSTEM_PROMPT + "\n\n" + user_base))
available_tokens = int(CONTEXT_WINDOW * CONTEXT_SAFETY_MARGIN) - base_tokens
# Split report into token-safe chunks
report_tokens = enc.encode(report_text)
chunks = [
enc.decode(report_tokens[i:i + available_tokens])
for i in range(0, len(report_tokens), available_tokens)
]
if len(chunks) == 1:
return summarize_report(report_text)
# Summarize each chunk, then summarize the summaries
chunk_summaries = [summarize_report(chunk) for chunk in chunks]
combined = "\n\n---\n\n".join(chunk_summaries)
return summarize_report(combined)
Validation
# Pipeline validation (October 30 2026)
# 30 technical reports (6,000–8,000 tokens each) with known content structure
# Before fix (Context Length: 4096, default):
# Short reports (<2,000 tokens): 100% complete summaries (28/28 section references)
# Long reports (6,000–8,000 tokens): 0% complete summaries
# Summary section coverage: 44% (Introduction + Methodology + partial Results)
# Conclusions paragraph: always generic — "further research is recommended"
# No HTTP errors. finish_reason always "stop".
# After fix (Context Length: 8192, explicitly set):
# Long reports (6,000–8,000 tokens): 100% complete summaries (30/30)
# Summary section coverage: 96% (all major sections referenced)
# Conclusions paragraph: specific to report content
# finish_reason: "stop" (output completed normally)
# Token count measurement on the 30 test reports:
# Min: 6,018 tokens, Max: 8,044 tokens, Mean: 7,089 tokens
# All under 8,192 × 0.9 = 7,373 token safety limit
# Token assertion never fires on the test set (documents fit in window)
# Latency impact of context window increase:
# Before (n_ctx=4096): p50 = 8.2s, p95 = 11.4s
# After (n_ctx=8192): p50 = 14.7s, p95 = 19.3s
# ↑ Longer processing time for more context — expected and acceptable
# VRAM usage: 4.6 GB → 5.1 GB (within 12 GB GPU capacity)
# Domain expert quality review (5 reviewers, 30 reports):
# Before: average quality score 3.1/10 (missing conclusions judged as incomplete)
# After: average quality score 8.4/10 (all report sections covered)
Related retainer posts
- Ollama developer on retainer —
response.json()raisesJSONDecodeErroron streaming NDJSON —stream=truedefault —requests 2.31.0— 100% failure 3 weeks - DeepSeek developer on retainer — system message last disables prefix caching — 2× expected cost, 1.4× latency — no error raised — 50,000-abstract batch — 5 weeks
- OpenRouter developer on retainer —
IndexErroronchoices[0]whenHTTP-Referermissing —openai 1.40.0— 100% Claude calls fail 4 weeks
Track retainer hours without a client portal
HourTab turns your time-tracker CSV into a public URL your client bookmarks. No login. No portal. CSV in, URL out.
See pricing →