LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 196 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnSystem Design CapstonesCode Completion System
🏗️HardSystem Design

Code Completion System

Design a real-time code completion path with context construction, measured serving latency, privacy controls, and stale-result suppression.

43 min read
Learning path
Step 168 of 196 in the full curriculum
Content Moderation SystemMulti-Tenant LLM Platform

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

Content moderation designed a low-latency safety pipeline that decides whether content can enter the product. Code completion reuses those same production muscles, but the failure is visible at the cursor. A developer types is_allowed =, continues with graph., and then receives a suggestion built for the older buffer. If that stale text replaces the newer line, the system has corrupted the edit. Even a correct suggestion feels broken when it arrives after the developer has moved on.

A code completion system predicts useful edits while a developer is still typing. GitHub Copilot's product family splits that job into dimmed ghost text at the cursor and next-edit suggestions that also predict where the next change should land.[1]Reference 1GitHub Copilot code suggestions in your IDEhttps://docs.github.com/en/copilot/concepts/completions/code-suggestions Remote suggestions can use fill-in-the-middle (FIM), which conditions the missing code on both the text before the cursor and the text after it.

Coding agents that plan multi-file patches sit a layer above and get a different latency budget. In the design scenario below, the inline path has a 200 ms p95 service-level objective (SLO). A real product has to set and validate its own objective from user research and telemetry.

Before tracing the path, predict what should happen to an exact member lookup after graph. and to an open-ended assignment after is_allowed =. One should stay local; the other may justify remote generation. The figure makes that split, and its freshness gate, visible:

Code completion request path where an editor snapshot first tries a fast local lane for exact symbols, then falls back to a remote LLM lane for open-ended code, and only fresh results render as ghost text.
Local semantic completion should win exact members after a dot. Remote fill-in-the-middle generation only runs when a longer span is worth the extra latency, and the UI paints ghost text only if the response ID is still the latest.

You've already seen how Transformers predict the next token and how the KV cache avoids recomputing shared work. This chapter puts those ideas on a keystroke path: the client may notice every edit, but it only sends qualified requests and only displays fresh results.

What makes code completion harder than a normal chat product?

Answer

The user keeps editing while inference is in flight. The product must choose when to request a suggestion, use code before and after the cursor for infill, meet a measured latency objective, and suppress stale responses.

One cursor, five lanes

The running file is validate_token_scope. The cursor sits in a half-typed authorization helper. Treat it as one live buffer, not a museum of IDE history. Before naming the lanes, ask which one should answer PolicyGraph, which one should answer graph., and which one can invent the body of is_allowed =:

LaneWhat it sees in this fileBest at
LexicalPrefix of is_all or PolicLocal identifiers and keywords already in the buffer
SemanticTyped receiver after graph.Members the language server can prove
Inline generationPrefix plus suffix around is_allowed =A line or block that has to match return is_allowed
Next-editThe accepted line plus nearby editsThe next nearby change after that insertion
Coding agentThe repo, tests, and a task promptA multi-file patch, not a ghost-text keystroke

Lexical and semantic first

A prefix index can finish PolicyGraph from the import line. After graph., the language server should win. It knows the receiver type and the legal members. That's parsing, a symbol table, and type inference, not a model call.

Inline generation only when the type system is stuck

The empty assignment is_allowed = is the generative case. No symbol table can invent the policy check that has to produce is_allowed for the return below. That's the fill-in-the-middle path the 200 ms budget is for.

Agents stay off the keystroke clock

A coding agent can research the repo, run tests, and edit several files.[2]Reference 2GitHub Copilot cloud agenthttps://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent That's a different product loop. Ghost text still has to gather a small prompt, generate a candidate, and stay under the 200 ms p95 clock.

Why should a design separate inline completion from coding agents?

Answer

Inline completion optimizes keystroke latency and local context. Coding agents optimize multi-step repository changes, tool use, tests, and review. They share model and context foundations, but their latency budgets and product loops differ.

The keystroke budget

An inline suggestion on is_allowed = competes with the next keystroke. If it arrives too late, the developer has already typed past it. If it's irrelevant, it becomes visual noise.

Those two failure modes set the constraints:

  • Latency: For this scenario, keep single-line inline completion under 200 ms at p95. Give multi-line suggestions a separately measured budget.
  • Context: Use open files, imports, function signatures, and nearby project structure, not the current file alone.
  • Quality: Track acceptance and accepted-and-retained characters. The exact target varies by language, editor UX, and how aggressively the client shows suggestions.
  • Scale: Large global developer fleets with diurnal spikes, so GPU utilization and cancel rate both matter.
  • Privacy: Define what code may leave the client, what is retained, whether training use is disabled by default, and how tenants are isolated.

This is more than a chat endpoint with a code prompt. The path from the edit listener to the inference engine needs a measured time-to-first-token (TTFT): the delay from request submission until the first response token arrives.

One practical way to reason about the budget is to split it across stages: editor + network overhead, context assembly, first-token inference, and UI rendering. The exact numbers vary by region and model size, but every stage is on the clock. If the prompt contains a long prefix, which row would you expect to grow first: the network or first-token inference? Keep that prediction in mind while reading the scenario allocation below.

StageTypical budgetNotes
Client event handling + local parse5-15msCapture the keystroke, cursor position, and lightweight syntax state.
Network round trip20-70msDepends heavily on region and whether the request stays close to the user.
Context assembly10-40msBuild the prompt, gather nearby symbols, and fetch a few related files.
First-token inference40-90msUsually the hardest budget to hit because prefill dominates.
UI render5-15msPaint ghost text and avoid jank in the editor.

For the scenario below, those stage budgets fit under a 200 ms p95 objective. They are allocation guardrails, not a measured fleet percentile. Multi-line suggestions can have a different objective, but they still need cancellation and freshness checks.

Use an executable budget check instead of treating a latency target as a promise. This small calculation fails the candidate path when any stage pushes total latency over the scenario objective:

latency-budget-check.py
1STAGE_BUDGET_MS = { 2 "client_parse": 12, 3 "network": 55, 4 "context": 28, 5 "time_to_first_token": 78, 6 "paint": 10, 7} 8INLINE_P95_OBJECTIVE_MS = 200 9 10total_ms = sum(STAGE_BUDGET_MS.values()) 11headroom_ms = INLINE_P95_OBJECTIVE_MS - total_ms 12 13assert total_ms <= INLINE_P95_OBJECTIVE_MS 14print("scenario_p95_budget_ms:", total_ms) 15print("headroom_ms:", headroom_ms)
Output
1scenario_p95_budget_ms: 183 2headroom_ms: 17

If a completion arrives after the user typed three more characters, what should the client do?

Answer

Treat it as stale. Abort the request if possible and gate UI rendering by request ID so old suggestions never overwrite the user's newer editor state.


Who owns the is_allowed = keystroke

Three pieces share that keystroke: the IDE (Integrated Development Environment) extension, a context engine, and an inference server.

The extension owns keystrokes, open tabs, and cursor position. The context engine, which can run on the client or the gateway, picks the snippets that actually fit the live prompt budget. The Language Server Protocol (LSP) is the usual interface between an editor and a language server, so the same semantic engine can power completion, go-to-definition, and diagnostics across editors.[3]Reference 3Language Server Protocolhttps://microsoft.github.io/language-server-protocol/

Now make the parser answer a smaller question: after the unfinished is_allowed =, which function and imports surround the cursor? It should return useful scope even when a batch compiler would reject the buffer. Incremental parsers such as Tree-sitter still return a concrete syntax tree with ERROR and MISSING nodes, which is enough to recover the enclosing function, locals, and imports on every keystroke. Language servers then supply typed members. The LLM supplies longer infill. The client merges or gates those lanes by cursor position.

That hybrid is the product, not an optimization. After a dot, semantic candidates from the language server are usually faster and more accurate than free-form generation. The model earns GPU time on longer spans, comment-to-code, and cases the type system doesn't capture.

The inference server hosts the model and uses continuous batching so many short, cancellable requests can share resident weights.

When should deterministic semantic completion beat the LLM?

Answer

After exact trigger characters like ., when the language server knows the receiver type and valid members. It's faster, more precise, works offline, and avoids spending GPU cycles on a deterministic lookup.

Local fallback lane

Not every keystroke should take the remote generation path. Keep a deterministic lane for the cheap, high-confidence cases:

  • Local symbols and imported APIs: Exact member completion from the parser or language server.
  • Prefix indexes or tries: Keyword and snippet lookup with predictable latency.
  • Fuzzy matching: Edit-distance recovery for small typos like pritn instead of print.

That lane does two jobs: sub-50 ms suggestions for exact matches, and a fallback when the network is slow, the model abstains, or the workspace is offline.

The smallest useful parse is "which function am I in?" You don't need Tree-sitter to see why a backward scan of the buffer beats sending the whole file. This helper walks up from the cursor line and returns the nearest def header:

enclosing-def.py
1def enclosing_def(source: str, cursor_line: int) -> str | None: 2 lines = source.splitlines() 3 start = min(cursor_line, max(len(lines) - 1, 0)) 4 for index in range(start, -1, -1): 5 stripped = lines[index].lstrip() 6 if stripped.startswith("def "): 7 return stripped 8 return None 9 10source = """from authz.policies import PolicyGraph 11 12def validate_token_scope(request): 13 is_allowed = 14 return is_allowed 15""" 16 17assert enclosing_def(source, 3) == "def validate_token_scope(request):" 18assert enclosing_def(source, 0) is None 19print("enclosing:", enclosing_def(source, 3))
Output
1enclosing: def validate_token_scope(request):

A production client replaces that scan with an incremental concrete syntax tree, then asks the language server for types. The idea is the same: extract local scope from a broken buffer before you spend tokens on remote infill.

Why is a local fallback lane a product requirement rather than an optimization?

Answer

It preserves flow when remote inference is slow, blocked by policy, unavailable offline, or unnecessary for exact symbols. A completion product that goes blank during every network hiccup feels unreliable.

The client keeps lightweight parsing and exact-member lookup on-device, so only qualified prompts reach GPUs. The architecture figure above is that fork: local lane, remote lane, then a freshness gate.


Context gathering and management

The model needs enough context to make useful suggestions, but the live prompt budget is deliberately kept small because long prefills destroy latency. Even if the base model advertises a much larger context window, you can't stuff the whole repository into the keystroke path. The design question is not "how much can fit?" but "which tokens can change the next edit?"

Context priority strategy

Rank context by how likely it is to change the next few tokens, then fill a small budget (8k tokens in this scenario) from the top of the table down until the window is full. Before looking at the ranking, predict what you would keep when only 8k tokens remain: the code around the cursor, a distant file with a matching filename, or an unrelated open tab. The order below turns that intuition into a policy:

PrioritySourceMethodRationale
1 (Highest)Code before cursorDirect prefixImmediate grammatical context.
2Code after cursor (suffix)FIM SuffixNeeded to close brackets, match types.
3Imports & DefinitionsStatic AnalysisTypes and functions used in the file.
4Recently Edited FilesTemporal localityCode you just touched is likely relevant.
5Neighboring FilesJaccard SimilarityFiles that share imports with current file.

Fill-in-the-middle (FIM)

Standard causal language models predict the next token based only on the past (left-to-right). In coding, you often insert code in the middle of a file. Pause before naming the format: if the suffix contains return is_allowed, what can a prefix-only model not see? It might generate valid code that conflicts with the closing braces or logic below.

FIM context: A next-token prompt exposes only code before the cursor. FIM also exposes code after the cursor before generation, so the completion can match a return type, closing delimiter, later variable use, or test expectation that appears below the edit.

FIM reorders the prompt so the model sees the suffix before generating the middle. Marker strings differ by tokenizer and model; the notation below names their roles rather than defining a universal API:

PFIM=<PRE>+xprefix+<SUF>+xsuffix+<MID>P_{\text{FIM}} = \texttt{<PRE>} + x_{\text{prefix}} + \texttt{<SUF>} + x_{\text{suffix}} + \texttt{<MID>}PFIM​=<PRE>+xprefix​+<SUF>+xsuffix​+<MID>

Fill-in-the-middle prompt construction where code before the cursor becomes the prefix, code after the cursor becomes the suffix, and the model generates the middle.
File order is prefix, hole, suffix. The PSM prompt sends prefix, then suffix, then asks the model to generate the middle so `return is_allowed` is visible before generation starts.

Building the FIM prompt

Build the input in FIM order: prefix marker + code before cursor + suffix marker + code after cursor + middle marker. Then the model generates the missing middle section.

Use a concrete example: you're editing an authorization helper and your cursor sits inside an empty function body.

Prefix (code before cursor)

prefix-code-before-cursor.py
1from authz.policies import PolicyGraph 2 3def validate_token_scope(request): 4 """Check whether the requested API scope is allowed.""" 5 is_allowed =

Suffix (code after cursor)

suffix-code-after-cursor.py
1 return is_allowed

FIM prompt sent to the model

text
1<PRE>from authz.policies import PolicyGraph 2 3def validate_token_scope(request): 4 """Check whether the requested API scope is allowed.""" 5 is_allowed = <SUF> 6 return is_allowed 7<MID>

The model now generates the middle section, using both the docstring above and the return is_allowed below to infer that it should write a policy-scope check, not an unrelated parser. Without the suffix, it could generate code that never produces is_allowed, leaving the following line broken.

Before wiring up a real tokenizer, test the transformation itself. A serving adapter would replace these readable markers with the exact sentinel tokens required by its selected FIM-capable model.

format-fim-request.py
1def format_fim(prefix: str, suffix: str) -> str: 2 return f"<PRE>{prefix}<SUF>{suffix}<MID>" 3 4prefix = "def validate_token_scope(request):\n is_allowed = " 5suffix = "\n return is_allowed\n" 6prompt = format_fim(prefix, suffix) 7 8assert prompt.endswith("<MID>") 9assert prompt.index("<SUF>") < prompt.index("return is_allowed") 10print(prompt.replace("\n", "\\n"))
Output
1<PRE>def validate_token_scope(request):\n is_allowed = <SUF>\n return is_allowed\n<MID>

FIM gives a decoder-only model suffix information without changing left-to-right decoding. The model only understands that prompt if it was trained for it.

During pre-training, a subset of examples is transformed:

  1. Split a sequence at two boundaries into (prefix, middle, suffix).
  2. Reorder to (prefix, suffix, middle) with sentinel tokens between segments.
  3. Train the model to predict middle given (prefix, suffix).

The two common inference formats are:

  • PSM (Prefix-Suffix-Middle): <PRE>prefix<SUF>suffix<MID>middle. This is the format used in the prompt above, and the one Qwen2.5-Coder documents for file-level completion.[4]Reference 4Qwen2.5-Coder Technical Reporthttps://arxiv.org/abs/2409.12186
  • SPM (Suffix-Prefix-Middle): <SUF>suffix<PRE>prefix<MID>middle. Prefix and generated middle form one contiguous span.

Bavarian et al. call the surprising result "FIM-for-free": jointly training both formats, with FIM rates up to 90% in their tests, added infilling without costing ordinary left-to-right generation on their benchmarks.[5]Reference 5Efficient Training of Language Models to Fill in the Middle.https://arxiv.org/abs/2207.14255 They recommend character-level splits so the cursor can land mid-token. For mid-token robustness they found PSM inference can beat the particular SPM format they tested; the training mix is the bigger lever. Teams still tune the mix, because too much infill-only data can shift plain continuation.

Why does fill-in-the-middle matter more for IDE completion than for chat?

Answer

Developers usually edit inside existing files. For an insertion that must fit existing code, the model needs suffix context such as closing braces, return statements, or following tests.

Repository-level context

Large repos need more than simple file buffering. Retrieval still has one job: find definitions that can change the next few tokens without turning context assembly into a second latency problem. Imagine scope_validator.py imports PolicyGraph and ScopeRule, while metrics.py contains only counters. Which file should enter first? Two lightweight retrieval methods run fast enough to stay inside the keystroke budget.

  1. Jaccard Similarity: Calculate the intersection of unique tokens (variable names, imports) between the current file and other open files. High overlap means high relevance. For example, if the active file scope_validator.py imports PolicyGraph, ScopeRule, and TokenClaims, and scope_rules.py shares two of those three names, its Jaccard score is 2/4 = 0.5 (high enough to pull in a few symbol definitions from it).
  2. BM25 / Sparse Retrieval: A lightweight keyword search over the local repo index to find defining files for classes used in the current buffer.[6]Reference 6The Probabilistic Relevance Framework: BM25 and Beyond.https://doi.org/10.1561/1500000019

Dense vector retrieval usually stays off the hottest keystroke path unless it's cached or precomputed. Lookup, reranking, and prompt assembly together are hard to keep inside a 10-40 ms context budget. Iterative retrieve-then-generate loops that use a first draft to fetch better snippets belong on slower multi-line or chat routes, not on every keystroke. Pre-build a small symbol graph at editor startup rather than scanning the repository on every edit.

This overlap check is small enough to run on every keystroke. It keeps scope_rules.py and policy_graph.py because they share PolicyGraph, ScopeRule, or TokenClaims. metrics.py stays out of the live prompt:

select-nearby-context.py
1active_symbols = {"PolicyGraph", "ScopeRule", "TokenClaims"} 2candidate_files = { 3 "scope_rules.py": {"ScopeRule", "TokenClaims", "AuditEvent"}, 4 "metrics.py": {"Counter", "Histogram"}, 5 "policy_graph.py": {"PolicyGraph", "Node"}, 6} 7 8def jaccard(left: set[str], right: set[str]) -> float: 9 return len(left & right) / len(left | right) 10 11ranked = sorted( 12 ((jaccard(active_symbols, symbols), path) for path, symbols in candidate_files.items()), 13 reverse=True, 14) 15selected = [path for score, path in ranked if score >= 0.25] 16 17assert selected == ["scope_rules.py", "policy_graph.py"] 18print("selected_context:", selected)
Output
1selected_context: ['scope_rules.py', 'policy_graph.py']

The score is a retrieval signal, not proof that a file belongs in the prompt. If a known definition repeatedly misses the threshold, inspect symbol extraction and index freshness before increasing the context window.

Why is "send the whole repository" the wrong context strategy?

Answer

Long prefills break latency and irrelevant files can distract the model. Rank context by cursor proximity, suffix, imports, recent edits, and lightweight symbol overlap before spending tokens.


Model serving for low latency

The budget fight is TTFT. Decode of a short suggestion is cheap compared with prefilling thousands of context tokens. Before choosing an optimization, predict what a draft model can safely save: if it proposes five tokens and the target rejects token five, should tokens one through four still count?

Speculative decoding

Speculative decoding uses a smaller, faster draft model to propose the next KKK tokens, then a larger target model verifies them in one parallel pass.[7]Reference 7Fast Inference from Transformers via Speculative Decoding.https://arxiv.org/abs/2211.17192 Serving stacks expose this as an operational feature with workload-specific caveats, so it still needs acceptance-rate measurement before rollout.[8]Reference 8Speculative Decodinghttps://docs.vllm.ai/en/latest/features/speculative_decoding/

If the target agrees, you emit several tokens after one target pass. If the draft misses, the target supplies the correction and you continue from there.

Speculative decoding path where a small draft model proposes five tokens graph . allows ( user, the large model accepts the first four in one pass, rejects user, and samples request as the correction.
The draft proposed `graph.allows(user`. The target kept `graph.allows(` and corrected the last token to `request`. Four accepted tokens from one target pass is a win; a first-token miss is not.

Take the running helper. After is_allowed =, a draft might emit graph . allows ( user. The target accepts the first four tokens and samples request from the residual. That's four useful tokens for one target verification pass, plus cheap draft work.

Suppose the draft is about 5x faster than the target and proposes 5 tokens:

  • 4 of 5 accepted: 5 draft steps cost about one target step, plus one verify pass, so about 4 tokens for 2 target-step equivalents, near a 2x speedup. Exact gain depends on draft cost and accepted length.
  • 1 of 5 accepted: you keep that token and throw the rest away. Net speedup is small or slightly negative once draft overhead is counted.
  • First token rejected: you fall back to ordinary single-token generation for that step.

Code with repetitive imports or boilerplate can raise acceptance. Treat that as a rollout hypothesis, not a property of all languages: measure accepted draft length and end-to-end latency by language and suggestion type before enabling it broadly.[7]Reference 7Fast Inference from Transformers via Speculative Decoding.https://arxiv.org/abs/2211.17192

The sketch treats target verification as roughly one target step for the whole draft. Under continuous batching, verifying many draft tokens can steal decode slots from neighbors, raise their TTFT, and erase the headline speedup. Re-measure with production batch occupancy; see speculative decoding for that interaction.

The rollout gate can be expressed as a small policy. Here, Python boilerplate improves latency enough to enable speculation, while a low-acceptance configuration remains on ordinary decoding:

gate-speculative-decoding.py
1measurements = { 2 "python_imports": {"mean_accepted_tokens": 3.8, "baseline_p95_ms": 178, "spec_p95_ms": 136}, 3 "sql_queries": {"mean_accepted_tokens": 1.1, "baseline_p95_ms": 169, "spec_p95_ms": 176}, 4} 5 6def enable_speculation(sample: dict[str, float]) -> bool: 7 latency_gain_ms = sample["baseline_p95_ms"] - sample["spec_p95_ms"] 8 return sample["mean_accepted_tokens"] >= 2.0 and latency_gain_ms >= 10 9 10enabled = [name for name, sample in measurements.items() if enable_speculation(sample)] 11assert enabled == ["python_imports"] 12print("speculative_decode_enabled_for:", enabled)
Output
1speculative_decode_enabled_for: ['python_imports']

Your draft model proposes 5 tokens and the target accepts 3. Did speculative decoding fail?

Answer

No. You accepted three tokens with roughly one target-model verification step plus cheap draft work. It only fails to help when acceptance is consistently low enough that draft overhead outweighs saved target steps.

KV cache reuse (prefix caching)

Developers often type, pause, and type again in the same file. The file's header (imports, class definitions, and previously written functions) remains constant across these rapid sequential interactions. Ask what a cache hit can remove before reading the timing example: prefill work, decode work, or both?

Instead of prefilling the same stable file header on every request, the server can cache Key-Value (KV) blocks for a shared prefix. When the next prompt begins with the same cacheable blocks under the same tenant and model policy, it reuses those blocks and prefills only the uncached delta. Automatic prefix caching does not make generation of new output tokens cheaper; it removes duplicate prefill work for reused input context.[9]Reference 9Automatic Prefix Cachinghttps://docs.vllm.ai/en/stable/design/prefix_caching/

In this scenario a 1,200-token file header is stable and the cursor-adjacent delta is 80 tokens. A miss still spends 72 ms on prefill plus 24 ms of decode. A hit on the same tenant, tokenizer, and shard drops prefill to 11 ms and leaves decode at 24 ms.

Next requestReusable prefix workRemaining work
Same stable header, a few characters appendedSkip prefill for matching cached blocksPrefill new input delta, then decode suggestion tokens
Edit near the top of the fileOnly blocks before the changed point can matchPrefill from first changed block onward, then decode
Different tenant, model, tokenizer, or cache policyNo permitted reuseFull prompt prefill, then decode

Those savings depend on stable prefixes, block matching, routing affinity, and isolation. A long header with a small cursor-adjacent delta is a good candidate. An early edit or a cross-tenant request is a miss by design.

Prefix cache reuse for code completion where one stable file header is prefetched once, then a nearby edit either lands on the same shard and reuses that prefix or lands elsewhere and pays full prefill again. Decode time stays 24 ms in both cases.
Both bars keep a 24 ms decode. The miss spends 72 ms prefilling the 1,200-token header; the hit prefills an 80-token delta in 11 ms. Routing to the wrong shard throws that saving away.

Use a cache key that enforces isolation as well as affinity. The example reuses a stable header for the same tenant and model, but never treats another tenant's identical text as a hit:

prefix-cache-contract.py
1def cache_key(tenant: str, model: str, tokenizer: str, prefix: str) -> tuple[str, str, str, str]: 2 return tenant, model, tokenizer, prefix 3 4stable_prefix = "from authz.policies import PolicyGraph\n" 5cached = { 6 cache_key("acme-devtools", "code-fim-v3", "tok-v3", stable_prefix): "kv-block-91", 7} 8 9same_scope = cache_key("acme-devtools", "code-fim-v3", "tok-v3", stable_prefix) 10other_tenant = cache_key("contoso-tools", "code-fim-v3", "tok-v3", stable_prefix) 11 12assert cached.get(same_scope) == "kv-block-91" 13assert cached.get(other_tenant) is None 14print("same_scope_hit:", same_scope in cached) 15print("cross_tenant_hit:", other_tenant in cached)
Output
1same_scope_hit: True 2cross_tenant_hit: False

This can eliminate repeated prefill work on sequential edits when the prefix stays stable and the reuse boundary is valid.

Why is prefix caching especially useful for a developer typing in one file?

Answer

Most consecutive requests share imports, class definitions, and earlier functions. Reusing cached KV blocks avoids recomputing the same prefix on every keystroke.

Quantization

Code models may tolerate post-training quantization, but you still have to re-check acceptance and retained-edit metrics after compression. Relative to FP16 weights, INT8 and INT4 weight storage can cut model-memory traffic by about 2x and 4x before format overhead.[10]Reference 10GPTQ: Accurate Post-Training Quantization for Generative Pre-Trained Transformershttps://arxiv.org/abs/2210.17323[11]Reference 11GPTQhttps://huggingface.co/docs/transformers/main/en/quantization/gptq

Token-by-token decode is often memory-bandwidth bound, so weight compression can raise throughput and sometimes help latency. TTFT can still be dominated by queueing, context length, and prefill. Profile those phases separately before you claim a win. Compression can be the difference between serving a mid-sized coder model on one 24 GB to 48 GB GPU and needing model parallelism, depending on the KV-cache budget.

GPTQ (a post-training method for generative pretrained transformers) compresses off-the-shelf weights without a full fine-tune. That's useful when you need a FIM model to fit a specific GPU, not when you want a new capability.

What metric should you re-check after quantizing a code model?

Answer

Don't check throughput alone. Re-check acceptance rate, accepted-and-retained characters, syntax/error regressions, and latency percentiles by language and suggestion type.


Debounce r and o; fire on .

Sending remote work for every edit of is_allowed floods the GPU with requests that will be stale before they return. The client has to decide when to request, when to wait, and when to cancel. Imagine requests for r, o, and . crossing in flight. Predict which response the editor may paint before following the request IDs through the diagram:

Diagram showing Keystroke, Trigger char?, 0 ms trigger / 150 ms else, and Send, bump ID, abort older.
Keystroke, Trigger char?, 0 ms trigger / 150 ms else, and Send, bump ID, abort older.

Debouncing and cancellation

Use a dynamic debounce:

  • 0 ms delay on trigger characters such as ., (, and \n.
  • 150 ms delay on ordinary typing.

The RequestManager below is that policy in code. Trigger characters fire immediately. Ordinary characters cancel any pending request and reschedule with a 150 ms delay. Each request gets a monotonically increasing ID so a late response can't paint over newer editor state:

debouncing-and-cancellation.py
1import asyncio 2 3class RequestManager: 4 """Manages debounce, cancellation, and stale-response suppression.""" 5 6 def __init__(self): 7 self.pending_task: asyncio.Task[None] | None = None 8 self.latest_request_id = 0 9 self.trigger_chars = {'.', '(', '\n'} 10 self.sent_requests: list[int] = [] 11 self.shown_suggestions: list[str] = [] 12 13 async def on_type(self, char: str) -> None: 14 if self.pending_task and not self.pending_task.done(): 15 self.pending_task.cancel() 16 17 delay_sec = 0.0 if char in self.trigger_chars else 0.15 18 self.latest_request_id += 1 19 request_id = self.latest_request_id 20 self.pending_task = asyncio.create_task( 21 self.debounce_fetch(delay_sec, request_id) 22 ) 23 24 async def debounce_fetch(self, delay_sec: float, request_id: int) -> None: 25 try: 26 await asyncio.sleep(delay_sec) 27 suggestion = await self.fetch_completion(request_id) 28 self.handle_response(request_id, suggestion) 29 except asyncio.CancelledError: 30 pass 31 32 async def fetch_completion(self, request_id: int) -> str: 33 # Real clients also attach request_id to an abortable HTTP request. 34 self.sent_requests.append(request_id) 35 return f"completion-{request_id}" 36 37 def handle_response(self, request_id: int, suggestion: str) -> None: 38 if request_id == self.latest_request_id: 39 self.show_ghost_text(suggestion) 40 41 def show_ghost_text(self, suggestion: str) -> None: 42 self.shown_suggestions.append(suggestion) 43 44async def demo() -> None: 45 manager = RequestManager() 46 await manager.on_type('r') 47 await asyncio.sleep(0.05) 48 await manager.on_type('o') 49 await asyncio.sleep(0.05) 50 await manager.on_type('.') 51 52 if manager.pending_task: 53 await manager.pending_task 54 55 manager.handle_response(2, "late-completion-2") 56 print("sent_requests:", manager.sent_requests) 57 print("shown_suggestions:", manager.shown_suggestions) 58 59asyncio.run(demo())
Output
1sent_requests: [3] 2shown_suggestions: ['completion-3']

In a production editor, you usually combine both layers: abort the HTTP request when possible, and still guard UI updates with request IDs in case the server races or ignores the cancellation.

Why do you need both HTTP cancellation and request-ID gating?

Answer

Cancellation saves server and network work when it succeeds. Request-ID gating protects the editor even when the server races, ignores cancellation, or returns an old response after a newer keystroke.

Ghost text, not a second product

The inline surface is ghost text: a gray suggestion at the cursor. If the next character matches the suggestion prefix, the client can keep the remainder visible. Tab inserts what's left.[12]Reference 12Programmatic Language Features: Show Inline Completionshttps://code.visualstudio.com/api/language-extensions/programmatic-language-features A comment such as // validate token scopes against the policy graph is still an inline completion, just with a natural-language prefix.

Repo-wide questions and test generation belong on the slower agent path, not on is_allowed =. Mixing them into the keystroke SLO is how teams accidentally build a chat product with ghost-text cosmetics.


Did is_allowed = actually help?

Unlike chat, completion gives a click: the developer accepts the suggestion or they don't. That click isn't enough. A four-character accept that gets deleted still counts as a click. Suppose a new model raises acceptance while developers delete more generated code. Which release improved the product? Hold that question while the figure separates inserted text from retained text.

A useful scoreboard measures how often suggestions appear, how often they're inserted, how much of that inserted text survives, and whether the path still meets its latency objective. Split those metrics by language, suggestion class, and traffic period or a model regression hides inside an aggregate.

Completion quality bars for four shown suggestions: accepted characters 18, 4, 42, and 0 versus retained characters 0, 4, 35, and 0. Three accepts look like 75 percent success until retention drops to 39 of 64 characters.
Suggestions A through D were shown. Three were accepted, which looks like 75% success, but A is fully undone and D was never inserted. Only 39 of 64 accepted characters remain.
MetricDefinitionWhy it matters
Acceptance Rate% of shown suggestions inserted by the user.GitHub's published study reported a 27% acceptance rate in its sample and found acceptance rate best predicted perceived productivity among its usage measurements. It's still gameable with tiny safe suggestions, so pair it with value metrics.[13]Reference 13Measuring GitHub Copilot's Impact on Productivityhttps://cacm.acm.org/research/measuring-github-copilots-impact-on-productivity/
Accepted-and-retained charactersCharacters from accepted suggestions that still remain after a chosen observation window.Captures value that survives editing, not the click alone. The GitHub study measured unchanged and mostly unchanged completion persistence at several time windows, reinforcing why retention complements acceptance.[13]Reference 13Measuring GitHub Copilot's Impact on Productivityhttps://cacm.acm.org/research/measuring-github-copilots-impact-on-productivity/
Completion Shown Rate% of eligible requests that surface a suggestion.A model that abstains too often won't feel helpful even if the few suggestions it shows are accurate.
Latency P9999th percentile (P99) response time.Slow suggestions break flow. The tail matters as much as the median.

Offline evaluation on benchmarks like HumanEval[14]Reference 14Evaluating Large Language Models Trained on Code (HumanEval).https://arxiv.org/abs/2107.03374 is useful for catching model regressions, but it doesn't measure editor timing, shown-rate policy, or accepted edits that users later undo. An online experiment is needed to determine whether a model or context heuristic improves the product experience.

Optimizing solely for acceptance rate can lead to a model that only suggests short, obvious tokens like closing parens because they are safe. You need value metrics such as retained characters, not click-through alone.

The metric computation should retain that distinction. In this example, three accepted suggestions become only one meaningfully retained edit:

measure-retained-completions.py
1suggestions = [ 2 {"shown": True, "accepted_chars": 18, "retained_chars": 0}, 3 {"shown": True, "accepted_chars": 4, "retained_chars": 4}, 4 {"shown": True, "accepted_chars": 42, "retained_chars": 35}, 5 {"shown": True, "accepted_chars": 0, "retained_chars": 0}, 6] 7 8shown = len(suggestions) 9accepted = sum(item["accepted_chars"] > 0 for item in suggestions) 10accepted_chars = sum(item["accepted_chars"] for item in suggestions) 11retained_chars = sum(item["retained_chars"] for item in suggestions) 12 13acceptance_rate = accepted / shown 14retained_char_rate = retained_chars / accepted_chars 15assert acceptance_rate == 0.75 16assert round(retained_char_rate, 3) == 0.609 17print("acceptance_rate:", f"{acceptance_rate:.0%}") 18print("retained_char_rate:", f"{retained_char_rate:.1%}")
Output
1acceptance_rate: 75% 2retained_char_rate: 60.9%

Why is acceptance rate easy to game?

Answer

A model can suggest tiny safe completions that users accept often but that save little work. Accepted-and-retained characters and shown rate better capture useful assistance.

Metrics matter operationally when they are tied to a release slice. If p95 rises while retention is flat, inspect queueing, context length, and cache hits, then roll back the serving or routing change. If p95 holds while retention or syntax checks fall, roll back the model or context change. Keep the last known-good route per language and suggestion class so one bad slice doesn't force a fleet-wide guess.


Routing by suggestion size

A more capable model can write a better function body and still lose if it arrives after the developer has moved on. A fast model that emits the wrong API also fails.

Keep the keystroke path on a smaller FIM-tuned model. Reserve slower, more capable models for multi-line bodies, next-edit suggestions, and chat. Qwen2.5-Coder is a useful existence proof: code-focused pre-training plus an explicit FIM objective, including repo-level markers, lets smaller coder models compete on completion benchmarks without putting an agent-scale model on every keystroke.[4]Reference 4Qwen2.5-Coder Technical Reporthttps://arxiv.org/abs/2409.12186 Distillation is the other common way to move that capability into a cheaper draft or inline model.

Don't assume parameter count predicts editor usefulness. Measure TTFT, acceptance, and retained characters on the actual trigger mix.

How much to generate is a separate routing decision. Before assigning a model, predict what the developer is asking for after . versus after a function signature. A single extra token after . wants exactness in 60 ms. An empty function body can wait 500 ms for a structured block:

TriggerSuggestion TypeScenario p95 objective
Mid-expression (after ., ()Type-informed local suggestion first60 ms
End of lineSingle line completion200 ms
After function signatureMulti-line body500 ms
Empty line in a functionMulti-line block500 ms

A lightweight client classifier can pick the request class before sending remote work. Multi-line completions can use a more capable model; exact member completion stays local.

route-completion-request.py
1def route(trigger: str, after_signature: bool) -> tuple[str, int]: 2 if trigger in {".", "("}: 3 return "LOCAL_SEMANTIC", 60 4 if after_signature: 5 return "REMOTE_MULTILINE", 500 6 return "REMOTE_INLINE", 200 7 8cases = [ 9 route(".", False), 10 route("\n", True), 11 route("r", False), 12] 13assert cases == [ 14 ("LOCAL_SEMANTIC", 60), 15 ("REMOTE_MULTILINE", 500), 16 ("REMOTE_INLINE", 200), 17] 18print("routes:", cases)
Output
1routes: [('LOCAL_SEMANTIC', 60), ('REMOTE_MULTILINE', 500), ('REMOTE_INLINE', 200)]

Why route single-token and multi-line completions differently?

Answer

They have different budgets and quality needs. Single-token completions need ultra-low latency and exactness; multi-line blocks can spend more time for structure, tests, and style consistency.


The fleet behind one keystroke

Code completion is a high-churn GPU workload: many requests die while the developer is still typing is_allowed. Provisioning has to protect the 200 ms objective during spikes, not just average tokens per second.

GPU fleet management and load balancing

Route for latency, cache affinity, and isolation at the same time:

  • Prefix-aware routing: Requests that share a long prompt prefix should hit the same GPU instance. If someone is editing src/auth/handler.py, the next keystroke in that file should see the warm KV blocks.
  • Geographic routing: Prefer an approved nearby region when residency and capacity allow. A 50 ms network round trip already consumes one quarter of the 200 ms objective.
  • Model tiering: Smaller models for single-line completions, larger models for multi-line bodies.
Diagram showing Next edit, Same tenant, model, shard?, Reuse cached KV prefix, and Full prefill.
Next edit, Same tenant, model, shard?, Reuse cached KV prefix, and Full prefill.

Fleet sizing sketch

Count tokens, not "editors per GPU." Before calculating, estimate the surviving prefill load for 5,000 active editors. The cancellation rate and prefix-hit rate both change the answer, so one labeled peak-hour scenario is:

  1. 5,000 editors actively typing in a region, not 50,000 idle sessions.
  2. After debounce, about 0.4 remote requests per typing editor per second → 2,000 raw req/s.
  3. 60% cancelled before GPU admission → 800 requests that still need serving work. A cancel after admission still burns partial GPU time and belongs in utilization.
  4. Mean prompt 2,048 tokens. 50% prefix-cache hits that skip 75% of prefill.

Effective prefill rate is then 800 × 2048 × (0.50 × 0.25 + 0.50 × 1.0) = 1,024,000 tokens/s. If one GPU sustains about 20,000 prefill tokens/s at this mix, you need ceil(1,024,000 / 20,000) = 52 busy GPUs before headroom, plus shards for residency and prefix affinity. This is an arithmetic envelope, not a hardware promise. Measure tok/s, cancel rate, hit rate, queueing, and p95 on your traffic before sizing a fleet.

size-completion-fleet.py
1import math 2 3TYPING_EDITORS = 5_000 4REQ_PER_EDITOR_PER_S = 0.4 5CANCEL_BEFORE_GPU = 0.60 6MEAN_PREFILL_TOKENS = 2_048 7PREFIX_HIT_RATE = 0.50 8PREFIX_SKIP_FRACTION = 0.75 9GPU_PREFILL_TOK_PER_S = 20_000 10 11raw_qps = TYPING_EDITORS * REQ_PER_EDITOR_PER_S 12gpu_qps = raw_qps * (1 - CANCEL_BEFORE_GPU) 13miss_or_partial = (1 - PREFIX_HIT_RATE) + PREFIX_HIT_RATE * (1 - PREFIX_SKIP_FRACTION) 14prefill_tok_s = gpu_qps * MEAN_PREFILL_TOKENS * miss_or_partial 15gpus = math.ceil(prefill_tok_s / GPU_PREFILL_TOK_PER_S) 16 17assert raw_qps == 2_000 18assert gpu_qps == 800 19assert prefill_tok_s == 1_024_000 20assert gpus == 52 21print("gpu_qps:", gpu_qps) 22print("prefill_tok_s:", int(prefill_tok_s)) 23print("busy_gpus:", gpus)
Output
1gpu_qps: 800.0 2prefill_tok_s: 1024000 3busy_gpus: 52

Continuous batching

Waiting to fill a fixed batch can add queue delay before first-token work begins. Continuous batching allows requests to join and leave an active serving schedule. vLLM[15]Reference 15Efficient Memory Management for Large Language Model Serving with PagedAttention.https://arxiv.org/abs/2309.06180 supports optimized scheduling and uses PagedAttention to manage KV-cache storage in blocks. TGI also documents continuous batching, although Hugging Face now labels TGI maintenance mode and recommends newer engines such as vLLM or SGLang for future work.[16]Reference 16Text Generation Inference.https://huggingface.co/docs/text-generation-inference/index More batching isn't automatically faster. Ask whether its saved GPU work outweighs added queueing at the inline p95. Tune batching against TTFT and throughput because higher utilization can still harm inline latency when queues grow.

Cost economics

At scale, code completion is expensive because the IDE emits a steady stream of short-lived requests while the user is typing. The key design question isn't just cost per token. It's cost per useful suggestion.

  • High churn: Many requests are cancelled before the user ever sees the output.
  • Prefill-heavy workload: Much of the cost sits in reading context, not in generating long responses.
  • Useful north-star metric: Track cost per accepted-and-retained suggestion, not cost per request alone.

Prefix caching, request cancellation, and better abstention policies improve both cost and user experience at the same time.

What is a better cost metric than cost per request?

Answer

Cost per accepted-and-retained suggestion. Completion systems generate many cancelled or ignored requests, so request volume alone overstates useful value.


What of this buffer may leave the laptop

validate_token_scope may sit next to proprietary source, configuration, and credentials accidentally present in the buffer. The product has to say what may be uploaded, logged, or retained before the first remote call. Before trusting TLS or a cache setting, ask whether the policy still holds in prompt logs, GPU batches, and recovery paths.

A useful design begins with explicit contracts: which buffers may be uploaded, whether prompts can be logged or used for training, how long operational data is retained, and which controls prevent cross-tenant reuse.

Data isolation

For an enterprise configuration that forbids code reuse across organizations, enforce tenant isolation across request ingestion, telemetry, and caches:

  • No training on user code by default. Model weights are frozen at deployment. User code is processed transiently for inference only.
  • Prompt data retention: Define explicit, minimal retention policies. Enterprises often require no raw-code retention or short, auditable windows for operational logs.
  • Tenant isolation: Prevent one organization's code context from leaking through another organization's cache, logs, or outputs. Shared GPU batches can be acceptable only when the runtime preserves per-request state boundaries and observability keeps tenant scopes intact. Partition batches where policy or implementation can't prove that boundary.

The isolation contract should be executable. A logging policy can retain safe metadata for latency debugging while dropping raw source by default:

apply-prompt-retention-policy.py
1def audit_record(request: dict[str, str], retain_raw_code: bool) -> dict[str, str]: 2 record = { 3 "tenant": request["tenant"], 4 "model": request["model"], 5 "latency_bucket": request["latency_bucket"], 6 } 7 if retain_raw_code: 8 record["prompt"] = request["prompt"] 9 return record 10 11request = { 12 "tenant": "acme-devtools", 13 "model": "code-fim-v3", 14 "latency_bucket": "p95_under_200ms", 15 "prompt": "API_TOKEN='secret-value'", 16} 17record = audit_record(request, retain_raw_code=False) 18 19assert "prompt" not in record 20print("audit_fields:", sorted(record))
Output
1audit_fields: ['latency_bucket', 'model', 'tenant']

PII redaction

For enterprise usage, client-side redaction can reduce the chance that recognized secrets leave the developer's machine. Regular-expression and entropy-based scanners can catch some API keys, tokens, and passwords, while policy-based filters can mask selected identifiers before prompt construction. Scanners are incomplete, so redaction complements upload controls, restricted logging, access control, and incident response.

If a policy promises that matching secret patterns won't be uploaded, replacement must run before upload. The system replaces matched strings with placeholders such as <API_KEY> before constructing the remote prompt.

When the server returns a generated completion, the client should only reinsert masked values if the placeholder maps to a known local value. Otherwise it should keep the placeholder visible and require an explicit user edit. That prevents the model from inventing a secret-looking string and having the client silently treat it as real.

mask-known-secrets-before-upload.py
1import re 2 3TOKEN = re.compile(r"demo_api_token_[A-Za-z0-9]+") 4 5def redact(text: str) -> tuple[str, dict[str, str]]: 6 mapping: dict[str, str] = {} 7 def replace(match: re.Match[str]) -> str: 8 placeholder = f"<API_KEY_{len(mapping) + 1}>" 9 mapping[placeholder] = match.group(0) 10 return placeholder 11 return TOKEN.sub(replace, text), mapping 12 13prompt, local_mapping = redact("client = API('demo_api_token_abc123')") 14assert "demo_api_token_" not in prompt 15assert local_mapping["<API_KEY_1>"] == "demo_api_token_abc123" 16print("upload_prompt:", prompt)
Output
1upload_prompt: client = API('<API_KEY_1>')

Why must secret redaction happen before prompt upload?

Answer

When policy forbids uploading known secret patterns, scanning before upload prevents matched values from entering prompt logs, caches, batches, or model context. It's one control, not proof that every secret is caught.

On-premises deployment

Some enterprise policies prohibit sending source code to a shared external service. A product serving those customers may need private Virtual Private Cloud (VPC), self-hosted, or offline deployment options.

  • Self-hosted models: Deploy suitable code models inside customer-controlled infrastructure. This changes trust boundaries, but still requires identity, network, logging, and supply-chain controls.
  • Air-gapped environments: Support fully offline operation for classified environments. The context engine, model, and inference server all run locally.

Public-code matching and the mastery gap

A completion can be locally useful and still be the wrong artifact to insert. Copilot-style products can check suggestions against publicly available code and then discard a match or attach a reference, depending on policy.[1]Reference 1GitHub Copilot code suggestions in your IDEhttps://docs.github.com/en/copilot/concepts/completions/code-suggestions That's one control for training-data regurgitation, not a license to skip review.

The other failure is quieter. If developers accept ghost text they don't understand, debugging and design instincts weaken. The product still helps with boilerplate, unfamiliar APIs, and lowering the syntax tax. It also hallucinates libraries, emits deprecated calls, and can suggest insecure snippets. Reward retained useful edits, not blind accepts.

What ethical failure can happen even when completions are technically correct?

Answer

Developers may accept code they don't understand, miss security problems, or stop building debugging instincts. Product telemetry should reward retained useful edits, not blind acceptance.


Common pitfalls

Treat these as diagnoses, not a release checklist. Start with the first user-visible symptom, isolate the stage or state that can cause it, change one lever, and compare the next measured slice. That sequence keeps a model-quality complaint from turning into blind serving work.

  • Symptom: Suggestions appear after the user already typed past them. Cause: No real cancellation path, or UI trusts arrival order instead of request IDs. Fix: Abort in-flight work when possible and gate rendering on newest request ID.

  • Symptom: Completions are often exact but still feel unhelpful. Cause: System optimizes for raw acceptance with tiny safe suggestions. Fix: Track accepted-and-retained characters and shown rate, not acceptance alone.

  • Symptom: Member completion is slower and less accurate after . than IDE autocomplete used to be. Cause: Every keystroke is routed to the LLM instead of keeping semantic lane for deterministic cases. Fix: Let parser or language server own exact symbol completion and reserve GPU work for open-ended spans.

  • Symptom: Prefix caching hit rate stays low even though users edit the same file repeatedly. Cause: Routing breaks shard affinity, so matching prefixes miss the cached KV blocks. Fix: Add prefix-aware routing keyed by tenant, model, and stable prompt prefix.

  • Symptom: Inserted code fights the code below the cursor. Cause: System ignores suffix context or uses left-to-right continuation where infill is required. Fix: Use FIM prompt formatting and FIM-trained models for in-file edits.

  • Symptom: Local scope is empty while the developer is mid-token, so every request ships the whole file. Cause: The client waits for a valid AST and gives up on incomplete buffers. Fix: Use an incremental concrete syntax tree (or a cheaper backward scan) that still returns the enclosing function on broken input.

  • Symptom: Model quality looks strong in offline code benchmarks but users still dislike the product. Cause: Benchmarks miss stale-response behavior, latency tails, abstention policy, and IDE interaction friction. Fix: Pair offline evals with online product metrics and real editor A/B tests.


What to ship with

Code completion moves part of the work from typing to reviewing suggestions. A useful system gathers bounded context, reuses permitted computation, drops stale output, and measures whether retained edits justify the latency and the data you touched.

CheckPass when
LatencyContext, network, TTFT, and paint fit the 200 ms p95 scenario budget
ContextPrefix, suffix, imports, then nearby files by overlap; no whole-repo dump
InfillFIM-trained model sees (prefix, suffix) before generating the middle
ServingSpeculative decoding and prefix reuse are gated on measured gain, not hope
FreshnessDebounce, HTTP abort, and request-ID gating all exist
PrivacyUpload, retention, training-use, cache isolation, and deployment match the contract

Those same isolation and accounting checks become tenant-scoped in the next capstone, where many products share one GPU platform.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.An inline completion trace budgets 12 ms for routing, 55 ms for retrieval, 28 ms for prompt assembly, 78 ms for inference, and 10 ms for validation. The p95 target is 200 ms. What is the sound design conclusion?

Correct answer: The trace totals 183 ms, leaving 17 ms headroom; keep the path and measure each stage's p95 before changing architecture.

Adding the stage budgets gives 183 ms, so the trace has 17 ms against the 200 ms target. That is a budget check, not proof of a fleet p95: stage-level tails, cancellations, queueing, and model variance still need measurement.

2.A repository context builder has an 8,000-token prompt budget. For a completion inside graph., which context policy best preserves useful signal without flooding the FIM request?

Correct answer: Prioritize prefix and suffix, imports and definitions, recent edits, and nearby files ranked by lexical or semantic relevance.

The context budget should preserve the edit boundary and high-value dependencies first. Lexical or semantic ranking such as BM25 or Jaccard can select relevant neighboring code, while whole-repository prompts and arbitrary recency consume tokens without improving the local completion contract.

3.Two inline requests overlap after a user types quickly. Request 41 returns after newer request 42 and contains an older prefix. Which response-control invariant prevents stale text from replacing the latest completion?

Correct answer: Abort obsolete work and accept a response only when its request identity still matches the editor's latest input state.

Cancellation saves work, but cancellation alone cannot prevent a response already in flight from arriving late. A monotonically tracked request identity or equivalent latest-input gate ensures an older prefix cannot overwrite newer editor state.

4.A model receives a prefix before the cursor and a suffix after it. Which request format and model capability are required for a fill-in-the-middle completion?

Correct answer: Use the FIM order <PRE>prefix<SUF>suffix<MID> with a model trained on fill-in-the-middle examples and tokens.

FIM is a trained interface, not a prompt spelling trick. The prefix and suffix occupy explicit roles, and the model needs training exposure to that ordering so it can generate the missing middle rather than continue only from the prefix.

5.A speculative decoder is evaluated on two slices. Python imports average 3.8 accepted draft tokens and improve p95 from 178 ms to 136 ms. SQL averages 1.1 accepted tokens and worsens p95 from 169 ms to 176 ms. The gate requires mean acceptance of at least 2 tokens and a latency gain of at least 10 ms. What should ship?

Correct answer: Enable speculation for Python imports only; it passes both gates, while SQL fails acceptance and makes p95 latency worse.

Python passes both gates: 3.8 accepted draft tokens on average and a 42 ms p95 improvement. SQL averages fewer than 2 accepted tokens and regresses by 7 ms, so it should remain on ordinary decoding rather than inherit one fleet-wide setting.

6.A prefix-cache entry contains a 1,200-token stable header followed by an 80-token edit. A miss costs 72 ms prefill plus 24 ms decode; a hit costs 11 ms prefill plus 24 ms decode. What cache contract and latency result are correct?

Correct answer: Require matching tenant, model, tokenizer, and stable prefix; the hit is 35 ms versus 96 ms on a miss and still pays decode.

A matching prefix can skip reusable prefill only within a compatible trust and serving contract. The miss is 72 + 24 = 96 ms, while the hit is 11 + 24 = 35 ms. Decode remains request-specific and cache entries cannot cross tenant or tokenizer boundaries.

7.A user types graph. in a known language-server file, then asks for a new is_allowed = line with repository context. Which route assignment best matches the article's latency and quality boundaries?

Correct answer: Use local semantic or LSP completion for graph., and remote FIM for the novel line when broader context can improve the suggestion.

The local semantic lane handles predictable member completion with low latency. A remote FIM model can use prefix, suffix, and retrieved code for a less local line. Coding-agent planning is a separate interaction and should not sit on the inline hot path.

8.A service has 5,000 editors at 0.4 raw requests per second. Sixty percent cancel before useful work. Each surviving request averages 2,048 prompt tokens; half are prefix hits that skip 75% of prefill. At 20,000 effective tokens per GPU-second, what rough prefill capacity is needed before headroom?

Correct answer: About 1,024,000 tokens per second, or roughly 52 busy GPUs before headroom, after cancellations, a 50% hit rate, and 75% prefill skipped on each hit.

Raw load is 5,000 × 0.4 = 2,000 requests per second, and 40% survive cancellation, or 800. The workload retains 62.5% of full prefill: all work on misses plus 25% on hits. That yields 1,024,000 tokens per second, or 51.2 GPUs rounded up to 52 before headroom.

8 questions remaining.

Next Step
Continue to Multi-Tenant LLM Platform

You now have one product path that reuses a prefix cache only inside a tenant, model, and tokenizer key, and that drops stale work before it hits the editor. Next you'll put many of those specialized models on shared GPUs and keep isolation, quotas, cost attribution, and canary rollouts honest when the fleet is no longer dedicated.

PreviousContent Moderation System
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

GitHub Copilot code suggestions in your IDE

GitHub · 2026

https://docs.github.com/en/copilot/concepts/completions/code-suggestions

GitHub Copilot cloud agent

GitHub · 2026

https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent

Language Server Protocol

Microsoft · 2026

https://microsoft.github.io/language-server-protocol/

Qwen2.5-Coder Technical Report

Qwen Team, Alibaba Group · 2024 · arXiv preprint

https://arxiv.org/abs/2409.12186

Efficient Training of Language Models to Fill in the Middle.

Bavarian, M., et al. · 2022 · arXiv preprint

https://arxiv.org/abs/2207.14255

The Probabilistic Relevance Framework: BM25 and Beyond.

Robertson, S., & Zaragoza, H. · 2009 · Foundations and Trends in Information Retrieval

https://doi.org/10.1561/1500000019

Fast Inference from Transformers via Speculative Decoding.

Leviathan, Y., Kalman, M., & Matias, Y. · 2023 · ICML 2023

https://arxiv.org/abs/2211.17192

Speculative Decoding

vLLM Team · 2026 · vLLM Documentation

https://docs.vllm.ai/en/latest/features/speculative_decoding/

Automatic Prefix Caching

vLLM · 2026

https://docs.vllm.ai/en/stable/design/prefix_caching/

GPTQ: Accurate Post-Training Quantization for Generative Pre-Trained Transformers

Frantar, E., et al. · 2023 · ICLR 2023

https://arxiv.org/abs/2210.17323

GPTQ

Hugging Face · 2026

https://huggingface.co/docs/transformers/main/en/quantization/gptq

Programmatic Language Features: Show Inline Completions

Visual Studio Code · 2026

https://code.visualstudio.com/api/language-extensions/programmatic-language-features

Measuring GitHub Copilot's Impact on Productivity

Ziegler, A., et al. · 2024

https://cacm.acm.org/research/measuring-github-copilots-impact-on-productivity/

Evaluating Large Language Models Trained on Code (HumanEval).

Chen, M., et al. · 2021 · arXiv preprint

https://arxiv.org/abs/2107.03374

Efficient Memory Management for Large Language Model Serving with PagedAttention.

Kwon, W., et al. · 2023 · SOSP 2023

https://arxiv.org/abs/2309.06180

Text Generation Inference.

Hugging Face · 2026

https://huggingface.co/docs/text-generation-inference/index

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.