On‑Prem LLMs for Psychotherapy Notes: System Design and Hardware Sizing Guide
Updated on December 25, 2025 19 minutes read
Psychotherapy documentation is one of the most privacy‑sensitive text workloads you can build for. Notes can include deeply personal disclosures, nuanced clinical reasoning, and high‑impact risk assessments.
At the same time, documentation burden is a real operational problem in mental health care. Clinicians often spend significant time after sessions formatting, summarizing, and translating messy human conversation into structured notes.
On‑prem LLMs can help because you control the boundary where data flows, keys live, and logs are stored. But “on‑prem” is not a shortcut to safety; without good system design, sensitive text can still leak through logs, traces, retrieval stores, or permissions.
This guide is written for engineers and technical decision‑makers who want serious depth. We will connect infrastructure choices (GPUs, inference servers, monitoring) to the realities of psychotherapy workflows and privacy constraints.
We will cover system architecture, sizing math you can actually use, and a privacy‑aware training example. Along the way, the core interdisciplinary message stays explicit: modern LLM systems only help mental health care if they are engineered to respect clinical workflow, documentation integrity, and patient privacy.
Background and prerequisites
You’ll get the most value if you already know Python and can read a PyTorch training loop. You should also understand basic ML evaluation and why imbalanced datasets make accuracy misleading.
From a systems perspective, you should be comfortable with internal APIs, containers, and basic security. If concepts like p95 latency, RBAC, and network segmentation are unfamiliar, read this article slowly and treat it as a roadmap.
From a domain perspective, you don’t need clinical credentials to build the system. You do need clinician partners who can define what “helpful,” “unsafe,” and “unacceptable hallucination” look like in documentation.
A particularly important distinction in the U.S. is the HIPAA category of “psychotherapy notes.” HIPAA treats psychotherapy notes differently from other mental health documentation and also clarifies what psychotherapy notes exclude, such as medication monitoring, session times, and general treatment summaries.
Even when you are not operating under HIPAA, the engineering principle is portable. Treat the most sensitive text as “crown jewel data” and design for strict minimization, least privilege, and safe defaults.
In GDPR contexts, data concerning health is generally treated as a special category of personal data. The practical engineering implication is similar: purpose limitation, minimization, and access control must be designed into the pipeline, not bolted on later.
What “good” looks like for an on‑prem therapy note assistant
A therapy note assistant should behave like a drafting tool, not an autonomous clinician. It should generate a structured draft that the clinician reviews, edits, and signs, without auto‑committing text into the legal record.
This design choice is not only ethical; it is operationally stabilizing. It reduces the damage of hallucinations and makes evaluation easier because clinicians provide the final ground truth.
The assistant’s job is usually one of three things: summarization, structure, or normalization. Summarization converts session content into a note template, structure forces consistent sections, and normalization refines language while preserving clinician intent.
These tasks have different risk profiles and different hardware profiles. Long‑context summarization tends to be memory‑bound and higher‑risk, while structured extraction and template filling can often run on smaller models with tighter controls.
The interdisciplinary point is simple: mental health care depends on trust. Any system that hides uncertainty, invents details, or makes review harder will erode trust and fail, regardless of model quality.
Reference architecture for on‑prem psychotherapy note generation
A practical on‑prem system can be described as a sequence of boundaries. The goal is to keep raw sensitive text in as few places as possible, for as short a time as possible, with the smallest possible audience.
At the edge, you have an ingress component: a clinician UI, an EHR integration, or an internal workflow app. Ingress should authenticate the user and attach context needed for authorization, such as clinician identity, clinic site, and patient scope.
Immediately after ingress, you should place a policy router. This service classifies the request, enforces retention and logging rules, and decides which pipeline path is allowed.
Policy routing is where you enforce note‑type separation. If psychotherapy notes must be handled differently from progress notes, this is where you make it impossible to “accidentally treat everything the same.”
From there, many systems include a retrieval layer to inject templates and clinic policies. In mental health settings, retrieval should be conservative; pulling in raw historical psychotherapy text can create a secondary medical record in a vector store.
A safer retrieval approach is “safe by construction.” Retrieve templates, section checklists, and approved phrasing, and prefer structured “memory” fields over free‑text retrieval.
Next is the inference service, which should be a tightly controlled internal API.
It runs the model runtime on GPUs and should accept only the minimum text needed to do the job.
Post‑processing is not optional for clinical drafting. It should enforce structure, validate outputs, and apply safe failure modes when the model output is malformed or incomplete.
Finally, you need a review surface that makes clinician oversight easy. The UI should present the draft as a draft, highlight uncertainty, and make edits fast, because “hard to review” is a safety risk.
A crucial supporting layer is audit and observability. You must be able to answer who accessed what, when, and why, without storing raw sensitive payloads in logs.

Threat model: where psychotherapy text leaks in real systems
Most data leaks in on‑prem systems happen through plumbing, not through the model. A single debug logger that prints request bodies can turn a log aggregation system into an unapproved psychotherapy archive.
Observability tooling is another common leak path. Some APM systems capture payloads for “request replay,” and those payloads can include transcripts or drafted notes.
Retrieval stores are often overlooked as regulated data stores. If you embed and store sensitive text, your vector database becomes a high‑value target that must be governed like any other clinical datastore.
Permissions are also a major source of exposure. If “any engineer can query the vector DB” or “any developer can read inference request logs,” your system is not privacy‑preserving, even if the GPU is on‑prem.
A practical rule is to assume all raw therapy text is highly sensitive everywhere it appears. That includes RAM, VRAM, swap, caches, queues, and disk, and it should influence retention and access policies end‑to‑end.
Hardware sizing: the mental model that prevents expensive surprises

LLM capacity planning works best when you think in tokens and concurrency. Every request has input tokens and output tokens, and every active request consumes KV cache memory.
Therapy note workloads are often long‑context. Transcripts, detailed clinician notes, or multi‑session context can easily reach thousands of tokens, and that pushes memory far more than short chat prompts do.
The two main GPU memory buckets are model weights and the KV cache. Weights are mostly fixed, while KV cache grows with context length and concurrency, which is why production behavior can differ from a single‑request demo.
Memory bucket 1: model weights
A first‑order approximation for weight memory is:
Here, is the number of parameters and is the number of bytes per parameter. This approximation is not perfect, but it is directionally useful for capacity planning.
A rough “bytes per parameter” guide looks like this:
| Weight format | Approx bytes/param | Practical note |
|---|---|---|
| FP16 / BF16 | 2.0 | High quality, higher VRAM |
| INT8 | 1.0 | Lower VRAM, sometimes faster |
| INT4 | 0.5 | Much lower VRAM, quality can vary |
Quantization reduces the weight dramatically. However, quantization does not remove KV cache growth, which often becomes the limiting factor in long‑context clinical drafting.
Memory bucket 2: KV cache
The KV cache stores attention key/value tensors so the model can attend to previous tokens during generation. KV memory grows with the number of active tokens across concurrent sequences.
A widely used approximation is:
In this expression, is the number of transformer layers and is the hidden size. The term is the total number of stored tokens across all active sequences, which is often close to .
The factor of exists because you store both keys and values. This is why doubling context length or doubling concurrency can double KV memory, and doing both multiplies the effect.
In psychotherapy workflows, concurrency spikes are common. Clinicians tend to request drafts around similar times, which makes peak sizing and headroom more important than averages.
Fragmentation, headroom, and why inference engines matter
The formulas above assume perfect packing of memory. Real GPU allocators fragment memory under dynamic loads, which can cause out‑of‑memory failures even when “the math says it should fit.”
Serving engines that manage KV cache efficiently can make a big difference. Approaches like paged KV allocation reduce wasted space and stabilize concurrency behavior under long sequences.
In practice, you should always plan headroom. A reasonable early rule is to reserve 15–25% VRAM headroom for runtime buffers, fragmentation, and peak variability, then adjust after benchmarking.
Latency model: prefill versus decode
Clinicians experience latency as “time until I can work again.” In LLM inference, latency is usually a combination of prefill (processing the prompt) and decode (generating tokens).
A useful approximation is:
Here, is the input tokens and is the output tokens. The rates and depend on GPU type, quantization, batch scheduling, and runtime implementation.
Batching improves throughput but can hurt tail latency. In clinical drafting, tail latency matters because clinicians remember slow experiences and adapt their behavior around them.
A sizing workflow you can use in practice
Start by writing down one workflow in plain language, then translate it into tokens. For example, “draft a DAP note from a 3,000‑token transcript and generate 400 tokens.”
Next, set a context policy before you pick hardware. If you allow arbitrary transcript lengths, the KV cache becomes unbounded, and you will end up sizing for worst‑case outliers.
A safer pattern is staged summarization. Chunk long transcripts, summarize chunks into structured intermediate summaries, then draft the final note from those summaries.
After you define context caps and concurrency expectations, do a first‑order VRAM estimate. Then benchmark with production‑shaped prompts, because microbenchmarks and chat prompts tend to underestimate clinical costs.
The final step is to size for p95 behavior, not for average behavior. Clinical usage is bursty, and the cost of OOM failures is high because it interrupts care workflows and erodes trust.
A simple VRAM estimator for early planning
The following Python snippet gives you a rough VRAM estimate using weights and KV cache. It is intended as a planning tool, not as a replacement for benchmarking.
def estimate_vram_gb(
params_b: float,
bytes_per_param: float,
layers: int,
hidden: int,
concurrent: int,
seq_len: int,
kv_bytes: float = 2.0,
headroom: float = 1.2,
) -> float:
"""
Rough VRAM estimate for on-prem LLM inference.
params_b: parameters in billions (e.g., 7 for a 7B model)
bytes_per_param: ~2 for FP16/BF16, ~1 for INT8, ~0.5 for INT4 (rough)
layers, hidden: model shape
concurrent: number of simultaneously active requests
seq_len: context length (include room for generated tokens)
kv_bytes: KV cache element size (often FP16 -> 2 bytes; some stacks compress KV)
headroom: runtime margin for fragmentation and buffers
"""
weight_bytes = params_b * 1e9 * bytes_per_param
total_tokens = concurrent * seq_len
kv_bytes_total = 2 * layers * total_tokens * hidden * kv_bytes
return headroom * (weight_bytes + kv_bytes_total) / (1024**3)
# Example: Llama-like 7B-ish shape (approximate)
print(estimate_vram_gb(params_b=7, bytes_per_param=2, layers=32, hidden=4096,
concurrent=8, seq_len=4096))
After you run this, interpret the result as “order of magnitude.” Exact VRAM depends on the runtime, attention kernels, KV format, and scheduler behavior.
If your estimate is near the GPU’s VRAM limit, treat it as a red flag. In production, “near the limit” often becomes “unstable under load.”
CPU, RAM, storage, and networking: the sizing you can’t ignore
On‑prem LLM projects often fail because GPU sizing gets attention while everything else is treated as leftover. In psychotherapy workflows, the supporting pipeline can be as important as inference itself.
CPU capacity matters for preprocessing, redaction, retrieval, and schema validation. It also matters for encryption, compression, and queue management when you handle bursty traffic.
RAM matters if you use retrieval indices, caching, or CPU offload strategies. It also stabilizes your system when a queue buildss,s and you need to buffer safely.
Storage planning matters because retention mistakes are easy to make. If you accidentally persist raw prompts, raw outputs, or raw transcripts in backups, you have created a compliance and privacy problem that is hard to unwind.
Networking matters because internal latency and reliability affect clinician experience. A slow or flaky link between the UI, policy router, retrieval, and inference can feel like “the model is slow” even when GPUs are idle.
Differential privacy for fine‑tuning on sensitive clinical text
On‑prem inference reduces exposure to third‑party cloud services. It does not automatically prevent a model from memorizing rare strings if you fine‑tune on sensitive notes.
Differential privacy (DP) is a way to formalize a limited influence guarantee. It aims to ensure that adding or removing one person’s data does not change the distribution of model outputs too much.
A common definition is ‑DP. An algorithm is ‑DP if for any neighboring datasets and that differ by one individual and any output set :
Smaller typically means stronger privacy but often lower utility. The parameter is a small failure probability that you choose based on your risk tolerance and context.
DP‑SGD is a practical training method for DP in deep learning. It clips per‑example gradients to a max norm and adds Gaussian noise before applying the optimizer step.
DP is not a complete security solution. It does not prevent prompt logging leaks, insecure retrieval systems, or unsafe UI design, so it must be one part of an overall governance strategy.
Hands‑on PyTorch example on synthetic “note‑like” text
Real therapy notes should not be casually copied into tutorials or tickets. Even inside a clinic, development should minimize access to raw text.
Synthetic note‑like text lets you practice the mechanics safely. It also mirrors a real engineering task: extracting structured workflow flags from unstructured narrative text.
Step 1: generate synthetic “note snippets” with an imbalanced label
import random
import re
from typing import List, Tuple
import torch
import torch.nn as nn
import torch.nn.functional as F
random.seed(7)
torch.manual_seed(7)
SYMPTOMS = ["anxiety", "panic", "low mood", "insomnia", "rumination", "irritability"]
INTERVENTIONS = ["CBT", "DBT skills", "grounding", "mindfulness", "behavioral activation"]
RISK_LOW = ["denies suicidal ideation", "no intent to harm self"]
RISK_HIGH = ["reports passive suicidal ideation", "history of self-harm", "endorses suicidal ideation with plan"]
def make_note() -> Tuple[str, int]:
symptom = random.choice(SYMPTOMS)
intervention = random.choice(INTERVENTIONS)
# Positive class is intentionally rarer, like many clinical workflow flags.
Highgh = random.random() < 0.18
risk = random.choice(RISK_HIGH if high else RISK_LOW)
label = 1 if high else 0
text = (
f"Client reports {symptom}. Intervention: {intervention}. "
f"Risk assessment: {risk}. Plan: review coping skills and schedule follow-up."
)
Return text, label
def make_dataset(n: int = 2000) -> Tuple[List[str], torch.Tensor]:
xs, ys = [], []
for _ in range(n):
t, y = make_note()
xs.append(t)
ys.append(y)
return xs, torch.tensor(ys, dtype=torch.long)
texts, y = make_dataset()
This toy label can represent “route to safety planning template,” not an actual clinical prediction. In real systems, you’d design labels with clinicians and measure error severity carefully.
Step 2: tokenize and build tensors
def tokenize(s: str) -> List[str]:
return re.findall(r"[a-zA-Z']+", s.lower())
vocab = {"<pad>": 0, "<unk>": 1}
for t in texts:
for tok in tokenize(t):
if tok not in vocab:
vocab[tok] = len(vocab)
def encode(s: str, max_len: int = 64) -> List[int]:
toks = tokenize(s)[:max_len]
ids = [vocab.get(tok, vocab["<unk>"]) for tok in toks]
ids += [vocab["<pad>"]] * (max_len - len(ids))
return ids
MAX_LEN = 64
X = torch.tensor([encode(t, MAX_LEN) for t in texts], dtype=torch.long)
perm = torch.randperm(X.size(0))
cut = int(0.8 * X.size(0))
tr, va = perm[:cut], perm[cut:]
X_tr, y_tr = X[tr], y[tr]
X_va, y_va = X[va], y[va]
This is a minimal pipeline to keep the DP concept readable. For real LLM fine‑tuning, tokenization and batching are more complex and memory‑heavy.
Step 3: define a lightweight model and a rare‑event metric
class MeanPoolClassifier(nn.Module):
def __init__(self, vocab_size: int, emb_dim: int = 64):
super().__init__()
self.emb = nn.Embedding(vocab_size, emb_dim, padding_idx=0)
self.fc = nn.Linear(emb_dim, 2)
def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
emb = self.emb(input_ids) # [B, T, D]
mask = (input_ids != 0).float().unsqueeze(-1)
pooled = (emb * mask).sum(1) / mask.sum(1).clamp(min=1.0)
return self.fc(pooled)
@torch.no_grad()
def f1_pos(y_true: torch.Tensor, y_pred: torch.Tensor) -> float:
tp = ((y_true == 1) & (y_pred == 1)).sum().item()
fp = ((y_true == 0) & (y_pred == 1)).sum().item()
fn = ((y_true == 1) & (y_pred == 0)).sum().item()
p = tp / (tp + fp + 1e-9)
r = tp / (tp + fn + 1e-9)
return 2 * p * r / (p + r + 1e-9)
@torch.no_grad()
def eval_model(m: nn.Module, Xv: torch.Tensor, yv: torch.Tensor) -> float:
m.eval()
pred = m(Xv).argmax(1)
return f1_pos(yv, pred)
In mental health workflows, “rare positives” are common and often high‑stakes. That makes recall and F1 more informative than accuracy.
Step 4: train a non‑private baseline
def train_baseline(model, Xtr, ytr, Xv, yv, epochs=6, bs=128, lr=1e-2):
opt = torch.optim.Adam(model.parameters(), lr=lr)
for e in range(1, epochs + 1):
model.train()
p = torch.randperm(Xtr.size(0))
for i in range(0, Xtr.size(0), bs):
idx = p[i:i+bs]
xb, yb = Xtr[idx], ytr[idx]
opt.zero_grad(set_to_none=True)
loss = F.cross_entropy(model(xb), yb)
loss.backward()
opt.step()
print(f"epoch={e} val_f1={eval_model(model, Xv, yv):.3f}")
m_base = MeanPoolClassifier(len(vocab))
train_baseline(m_base, X_tr, y_tr, X_va, y_va)
This baseline establishes a non‑private utility reference. Now you can quantify what privacy constraints cost, rather than guessing.
In real DP training, you typically use a library such as Opacus. Opacus makes it easier to apply DP‑SGD and track the privacy budget as grows during training.
Step 5: DP‑SGD using Opacus
Opacus wraps a standard PyTorch loop with DP‑SGD mechanics and privacy accounting. It exposes a PrivacyEngine that makes it easier to add clipping and noise while tracking privacy budget.
# Template code for DP-SGD with Opacus (requires: pip install opacus)
from torch. Utils. data import DataLoader, TensorDataset
from opacus import PrivacyEngine
dataset = TensorDataset(X_tr, y_tr)
loader = DataLoader(dataset, batch_size=128, shuffle=True)
m_dp = MeanPoolClassifier(len(vocab))
opt = torch.optim.Adam(m_dp.parameters(), lr=1e-2)
privacy_engine = PrivacyEngine()
m_dp, opt, loader = privacy_engine.make_private(
module=m_dp,
optimizer=opt,
data_loader=loader,
noise_multiplier=1.1, # higher -> stronger privacy, lower utility
max_grad_norm=1.0, # gradient clipping threshold
)
for e in range(1, 7):
m_dp.train()
For xb, yb in loader:
opt.zero_grad(set_to_none=True)
loss = F.cross_entropy(m_dp(xb), yb)
loss.backward()
opt.step()
eps = privacy_engine.get_epsilon(delta=1e-5)
f1 = eval_model(m_dp, X_va, y_va)
print(f"epoch={e} eps={eps:.2f} val_f1={f1:.3f}")
DP introduces a second dimension to tuning: privacy budget. You are no longer optimizing only for performance, but also for an target that reflects acceptable privacy loss.
For psychotherapy workflows, DP is most relevant when you fine‑tune on sensitive local text. If you can avoid fine‑tuning by using templates, retrieval, and careful prompting, you often reduce privacy and governance burden significantly.
Production operations: what makes the system reliable in clinics
Clinical systems are judged by reliability and predictability. A system that is occasionally brilliant but sometimes slow or broken will be abandoned, even if the model is “good.”
Many teams benefit from separating interactive and batch lanes. Interactive drafting prioritizes p95 latency and stability, while batch processing prioritizes throughput and cost per token.
Queueing and backpressure are essential. If GPUs are saturated, the system should degrade gracefully, for example, by producing a template scaffold immediately and queueing the full draft.
Observability should focus on metrics rather than raw content. Tracking token counts, time to first token, total latency, VRAM usage, and error rates usually gives you enough signal without storing therapy text in logs.
Monitoring should also include structural validity. Schema failures, missing required sections, and formatting drift often appear before offline quality metrics change.
Security should treat the inference service like a sensitive internal API. Authentication, authorization, object‑level access checks, and network segmentation reduce the chance that one misconfigured client can access another clinician’s scope.
Supply chain hygiene matters more than teams expect. If you containerize runtimes, pin versions, scan images, and restrict outbound network access, you reduce the chance that an “innocent update” changes behavior in a safety‑critical workflow.
Risk, ethics, safety, and governance

Hallucinations are not minor errors in clinical documentation. If an LLM fabricates a symptom, intervention, or risk statement, the record can mislead future care decisions.
A practical mitigation isa “draft‑only” posture with explicit clinician sign‑off. Another mitigation is evidence‑linked drafting, where each generated section maps back to specific input snippets or intermediate structured summaries.
Bias can show up as systematic differences in tone or interpretation across populations. In mental health text, cultural context and language are critical, so evaluation should include subgroup sensitivity and error severity, not only aggregate F1.
Privacy risk is not limited to model training. It also includes prompts and outputs, retrieval stores, logs, and the human workflow around debugging and support.
Governance frameworks can help teams make risk work continuous rather than ad hoc. Even if you do not fully implement a formal framework, you should have clear roles, review gates, incident processes, and monitoring that reflect clinical impact.
The interdisciplinary point remains central here. Technical excellence matters, but it must be coupled with clinical safety, privacy principles, and workflows that protect patients and clinicians.
Case study scenario: an outpatient clinic deployment
Consider a clinic network that wants DAP note drafts from transcripts. Clinicians request drafts after sessions, and the system should return a draft quickly enough to keep the workflow moving.
The clinic sets a hard context cap per request and uses staged summarization for long transcripts. This reduces KV cache pressure and reduces hallucination risk by narrowing the model’s context at each step.
The policy router separates psychotherapy notes from general progress notes at the first boundary. This reduces the chance that psychotherapy content ends up in generic indexing, broad caches, or unintended storage.
During load testing, the team finds that concurrency drivethehe V cache to the limit before weights do. They adjust by limiting interactive concurrency per GPU, adding headroom, and moving long drafts to a batch queue.
They also implement safe failure behavior. If the inference service is saturated, the UI shows a structured template scaffold immediately, with a clear indicator that the full draft is queued.
Over time, they evaluate success using clinician time saved and the reduction in formatting errors. They also track safety signals such as clinician‑reported hallucinations, schema failures, and any security anomalies.
Skills mapping and learning path

If you can build this system, you are practicing modern interdisciplinary engineering. You are connecting ML, systems, and a regulated healthcare domain in a way that employers and clinics both value.
On the ML side, you learn how to evaluate rare-event text tasks and how privacy constraints change training. You also learn why structured outputs and schema validation are production tools, not “nice extras.”
If you want to develop these skills with guided projects, mentor feedback, and career support, explore Code Labs Academy’s programs.
Start here: Code Labs Academy Bootcamps.
On the systems side, you learn to reason about VRAM using weights and KV cache and to size for concurrency. You also practice designing APIs, queues, observability, and failure modes that match real clinical usage.
If you’re aiming for roles like ML Engineer, Platform Engineer, or MLOps Engineer, it helps to build these foundations with production-oriented tooling. See how our curriculum supports that path: Learn about Code Labs Academy.
On the domain side, you learn to translate clinical constraints into design rules.
That includes note type separation, retention discipline, and UX patterns that keep clinicians in control.
If you’re coming from a healthcare background and want to build technical depth without losing the domain context, our bootcamps are designed for career-switchers and upskillers alike.
A strong next step is to build a minimal pilot. Start with strict context limits and template-driven drafting, then add load testing and monitoring before you expand the scope.
If you want help choosing the right learning plan and timeline, you can book a quick call with our team.
Book a call.
Conclusion
On-prem LLMs for psychotherapy notes are feasible, but success depends on system design, not slogans. The critical constraints are privacy boundaries, KV cache behavior under concurrency, and workflow-safe output handling.
A sizing method that separates weights and KV cache prevents expensive surprises. Once you treat tokens and concurrency as first-class sizing inputs, hardware planning becomes transparent engineering.
If you fine-tune on sensitive text, DP-SGD can reduce memorization risk under a formal privacy definition. But privacy still depends on the whole system: logging, access control, retrieval governance, and clinician review.
If you want a system clinicians trust, optimize for stability, reviewability, and conservative failure modes. In mental health care, reliability and respect for privacy are not “constraints”; they are the product.
If you want to build the skills to ship systems like this, secure, measurable, and production-ready,y Code Labs Academy can help you get there with structured learning and hands-on projects.
Explore the full program overview: Code Labs Academy, and browse the bootcamps here: View all courses.