Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Running Models Locally

Authors
Affiliations
Johns Hopkins Bloomberg School of Public Health
Johns Hopkins Bloomberg School of Public Health

1Local LLMs

There are two reasons a statistician ends up running a language model on their own hardware, and only one of them is cost. The first is data governance: if the text you want to process is protected health information, student records, or unpublished data from a collaborator, sending it to a third-party API is often simply not permitted, and no amount of convenience changes that. The second is reproducibility, which this readership cares about more than the field does --- a local model with pinned weights and a fixed seed is a fixed object that will give the same answer next year, while a hosted endpoint is a moving target that may be silently updated or retired between your submission and the referee report. The cost of taking this route is that you must think about memory, numerical precision, and throughput, three subjects a statistician can normally ignore. This section is a practical guide to that arithmetic: how large a model fits in the hardware you have, what quantization does to the weights and therefore to the predictions, and how to decide honestly whether the smaller local model is good enough for your task.

Running a model locally turns statistical questions into numerical and resource questions; this dictionary maps the engineering vocabulary back onto ideas the reader already has.

ML / AI termStatistical analogueWhat is the sameWhat is different / the catch
Open-weight modelA published fitted model with coefficients releasedEstimates are available for reuse and auditThe training data, and hence the target population, is usually not disclosed
QuantizationRounding coefficients to a coarse grid; fixed-point storageDeliberately trading precision for storageThe rounding error is not iid; a few outlier weights carry most of the loss
4-bit / 8-bit weightsCoarsened measurement of a parameterFewer bits, same estimandError propagates nonlinearly through many layers, so per-weight RMSE does not predict output degradation
Perplexity check after quantizingComparing fitted log-likelihoods before and after an approximationA likelihood-based goodness checkNearly unchanged perplexity can still hide large degradation on a narrow task
KV cacheStoring sufficient statistics of the past to avoid recomputationCaching a state so each new observation is O(1)O(1)Its memory grows linearly in context length and often exceeds the weights
DistillationFitting a small model to a large model’s predictive distributionSurrogate / emulator modelingThe teacher’s errors are inherited as if they were data
PruningVariable selection / setting coefficients to zeroRemoving parameters that contribute littleStructured (whole-head) pruning helps throughput; unstructured sparsity often does not
LoRA adapterA low-rank perturbation of a fitted coefficient matrixConstraining an update to a low-dimensional subspaceModifies behaviour with 1%\ll 1\% of the parameters; it is a reparameterization, not a new model
Context lengthSize of the conditioning setMore conditioning informationMemory and time cost grow quadratically in attention, linearly in cache
Tokens per secondThroughput of the samplerIterations per second in any iterative algorithmGeneration is memory-bandwidth bound, not FLOP bound; buying compute does not always help
Greedy decoding with fixed seedA deterministic point predictionRemoves Monte Carlo variabilityStill not bit-reproducible across GPU types or batch sizes
Model cardA data/method documentation sheetReporting provenance and intended useVoluntary and uneven; absence of a limitation is not evidence of absence
GGUF / safetensors fileA serialized model object (like an .rds)A portable fitted objectFormat determines which runtime can load it; no universal standard

1.1The Memory Arithmetic

The single calculation that determines whether a model runs on your machine is its weight footprint. A model with NN parameters stored at bb bits each requires

Mweights  =  Nb8 bytes    Nb8×230 GiB,M_{\text{weights}} \;=\; \frac{N b}{8} \text{ bytes} \;\approx\; \frac{N b}{8 \times 2^{30}} \text{ GiB},

so a 7-billion-parameter model needs about 26 GiB in fp32, 13 GiB in fp16, and roughly 3.3 GiB at 4 bits. Inference needs more than the weights: activations, and above all the key--value cache, which for a transformer with LL layers, HH key/value heads of dimension dhd_h, context length TT and bcb_c bits per cached value costs

MKV  =  2LHdhTbc8 bytes,M_{\text{KV}} \;=\; \frac{2\, L\, H\, d_h\, T\, b_c}{8} \text{ bytes},

linear in context length. At long context the cache can rival or exceed the weights, which is why a model that loads happily may still fail on a long document. (Serving runtimes attack this directly: vLLM’s paged allocator stores the cache in fixed-size blocks so that concurrent requests do not each reserve their worst-case contiguous slab Kwon et al., 2023.)

Precision is the lever that decides what runs on your hardware. Left: weight memory from the equation against model size, with three common VRAM limits marked; dropping from fp16 to 4 bits moves the largest feasible model up by roughly a factor of four. Right: a simulation of round-trip quantization error for Gaussian weights under uniform grids with three clipping rules. Relative error falls by about a factor of two per additional bit, and the choice of clipping threshold --- that is, how the rare large weights are handled --- sets the level of the whole curve.
:width: 90%

Figure 1:Precision is the lever that decides what runs on your hardware. Left: weight memory from the equation against model size, with three common VRAM limits marked; dropping from fp16 to 4 bits moves the largest feasible model up by roughly a factor of four. Right: a simulation of round-trip quantization error for Gaussian weights under uniform grids with three clipping rules. Relative error falls by about a factor of two per additional bit, and the choice of clipping threshold --- that is, how the rare large weights are handled --- sets the level of the whole curve. :width: 90%

1.2Quantization as Deliberate Measurement Error

Quantizing a weight matrix replaces each entry with the nearest point on a coarse grid. For a symmetric uniform scheme with bb bits and scale ss,

w^  =  sclip ⁣(ws,2b1,2b11),s=maxiwi2b11,\hat w \;=\; s \cdot \mathrm{clip}\!\left( \left\lfloor \frac{w}{s} \right\rceil, \, -2^{b-1},\, 2^{b-1}-1 \right), \qquad s = \frac{\max_i |w_i|}{2^{b-1}-1},

and the induced error ε=w^w\varepsilon = \hat w - w behaves, to first order, like measurement error on the coefficients. The naive analysis --- treat ε\varepsilon as uniform on [s/2,s/2][-s/2, s/2], note the variance is s2/12s^2/12, conclude that error falls by a factor of two per bit --- is a useful first pass and is what the right panel of Figure the figure shows. It also explains why practice departs from it: ss is set by the largest weight in the group, so a handful of outlier weights inflates the step size for everything else. Every serious quantization method is a response to that fact, whether by grouping weights into small blocks with their own scales, keeping outlier channels in higher precision, or choosing the grid to be optimal for a normal prior, as in the NF4 format of QLoRA Dettmers et al., 2023. (The two post-training schemes you will meet by name when downloading weights, GPTQ Frantar et al., 2022 and AWQ Lin et al., 2023, are both of this kind: each uses a small calibration sample to decide which weights may be coarsened and which must be protected, which makes the calibration set a design choice worth recording.)

1.3Adapting a Local Model Without Retraining It

Full fine-tuning of even a 7B model requires optimizer state several times the size of the weights and is out of reach for most local hardware. Low-rank adaptation sidesteps this by freezing W0W_0 and learning a low-rank update Hu et al., 2021,

W  =  W0  +  αrBA,BRd×r,  ARr×k,  rmin(d,k),W \;=\; W_0 \;+\; \frac{\alpha}{r} B A, \qquad B \in \mathbb{R}^{d \times r},\; A \in \mathbb{R}^{r \times k},\; r \ll \min(d,k),

which trains a fraction of a percent of the parameters and yields an adapter file of a few tens of megabytes. Combining a 4-bit frozen base with a low-rank update in higher precision --- QLoRA --- brings fine-tuning of a mid-sized model onto a single consumer GPU Dettmers et al., 2023. The statistical reading is that rr is a rank constraint acting as a regularizer, and the effective number of parameters, not the nominal one, governs how much data you need.

1.4A Reproducible Local Workflow

The reason to prefer a local model in a paper is that it can be pinned. Record the model repository and revision hash, the quantization format, the runtime and its version, the decoding parameters, and the seed --- all five, because any one of them can change your outputs. Even then, exact bitwise reproducibility across different GPUs or batch sizes is not guaranteed, so the honest claim is distributional reproducibility, and the honest report includes several seeds.

name, rev = “microsoft/Phi-4-mini-instruct”, “main” # pin a commit hash here tok = AutoTokenizer.from_pretrained(name, revision=rev) model = AutoModelForCausalLM.from_pretrained( name, revision=rev, torch_dtype=torch.bfloat16, device_map=“auto”)

msgs = [{“role”: “user”, “content”: “State Bayes’ theorem in one sentence.”}] ids = tok.apply_chat_template(msgs, add_generation_prompt=True, return_tensors=“pt”) torch.manual_seed(0) out = model.generate(ids.to(model.device), max_new_tokens=128, do_sample=True, temperature=0.7, top_p=0.95) print(tok.decode(out[0, ids.shape[-1]:], skip_special_tokens=True)) print(torch.cuda.max_memory_allocated() / 2**30, “GiB peak”)

1.5Tools in Practice

The categories below are what stands between a file of weights and a number in your paper. They are worth learning as categories, because the products inside each one turn over quickly while the division of labour between them has been stable: something loads weights, something quantizes them, something serves them at throughput, and something records what you ran.

Before downloading anything, do the arithmetic. The calculation below combines the equation and the equation with a bandwidth-bound throughput ceiling; substitute your own device’s memory and bandwidth, which the runtime or the data sheet will tell you, and read off which configurations are feasible.

import numpy as np
GiB = 2**30
vram = 24.0                                  # your device, in GiB; read it from the runtime
bw = 400.0                                   # its memory bandwidth in GB/s, from the data sheet
N, L, Hkv, dh = 13e9, 40, 8, 128             # 13B-class, 8 key/value heads (grouped-query)
print(" w-bits  kv-bits  context   weights   KV cache   total+20%   fits   tok/s ceiling")
for bw_, kvb, T in [(16, 16, 8192), (8, 16, 8192), (4, 16, 8192),
                    (4, 16, 65536), (4, 8, 65536), (4, 8, 262144)]:
    w = N * bw_ / 8 / GiB
    kv = 2 * L * Hkv * dh * T * kvb / 8 / GiB
    tot = 1.20 * (w + kv)
    print(f"{bw_:7d}  {kvb:7d}  {T:7d}   {w:7.2f}   {kv:8.2f}   {tot:9.2f}   "
          f"{str(tot <= vram):>5s}   {bw / (w + kv):13.1f}")
Tstar = N * 16 / 8 / (2 * L * Hkv * dh * 16 / 8)
print(f"context at which the cache equals the fp16 weights: {Tstar:.0f} tokens")
print(f"same for 4-bit weights and fp16 cache:              {Tstar / 4:.0f} tokens")
 w-bits  kv-bits  context   weights   KV cache   total+20%   fits   tok/s ceiling
     16       16     8192     24.21       1.25       30.56   False            15.7
      8       16     8192     12.11       1.25       16.03    True            29.9
      4       16     8192      6.05       1.25        8.76    True            54.8
      4       16    65536      6.05      10.00       19.26    True            24.9
      4        8    65536      6.05       5.00       13.26    True            36.2
      4        8   262144      6.05      20.00       31.26   False            15.4
context at which the cache equals the fp16 weights: 158691 tokens
same for 4-bit weights and fp16 cache:              39673 tokens

The first row does not fit and the second does, which is the whole practical content of quantization. Two further readings are worth extracting. The throughput ceiling in the last column is bandwidth divided by bytes touched per token, and it falls as the context grows even though the weights have not changed: generation is memory-bound, so a longer conversation is slower for a reason that has nothing to do with arithmetic. And the crossover context --- where cache equals weights --- moves down by exactly the compression factor, so the more aggressively you quantize the weights, the sooner the cache becomes the binding constraint. That is why cache quantization and paged allocation Kwon et al., 2023 appear immediately after weight quantization in every serving stack: they are the next term in the same budget.

Substitute your own numbers before trusting any of this. The point of the calculation is that it is a calculation, not a benchmark: it takes two device constants and four model constants, all of which are published, and returns a feasibility answer that does not depend on anyone’s marketing.

1.6Exercises

  1. Using the equation and the equation, compute the total memory needed to serve a 13B-parameter model (L=40L=40, H=40H=40, dh=128d_h=128) at fp16 weights and fp16 cache with a 16{,}384-token context. At what context length does the cache exceed the weights?

  2. Show that for weights uniform on [s/2,s/2][-s/2, s/2] rounding error the mean squared error is s2/12s^2/12, and hence that relative RMSE falls by a factor of two per additional bit. Then explain, in one paragraph, why the right panel of Figure the figure shows the max-clipping rule beating the 3σ3\sigma rule at 8 bits but losing to it at 2 bits.

  3. Count the trainable parameters in the equation for a 4096×40964096 \times 4096 weight matrix at r=8r=8, and express the result as a fraction of the full matrix. What rank would be needed to reach 10%10\%?

  4. Computational. Simulate the weight distribution of one layer as a heavy-tailed draw (say t3t_3 scaled to sd 0.02), quantize it with the equation at b=8,4,3b = 8, 4, 3, and plot the relative RMSE for per-tensor scaling versus per-block scaling with blocks of 64. Quantify how much of the improvement from blocking is attributable to the tail.

  5. Computational. Run a small instruct model locally at fp16 and again at 4 bits, on the same 50 prompts with the same seed. Report the mean per-token log-likelihood under each and the fraction of prompts where the two answers differ materially. Comment on whether the perplexity change would have predicted the task-level change.

References
  1. Kwon, W., Li, Z., Zhuang, S., Sheng, Y., Zheng, L., Yu, C. H., Gonzalez, J., Zhang, H., & Stoica, I. (2023). Efficient Memory Management for Large Language Model Serving with PagedAttention. Proceedings of the 29th Symposium on Operating Systems Principles, 611–626. 10.1145/3600006.3613165
  2. Dettmers, T., Pagnoni, A., Holtzman, A., & Zettlemoyer, L. (2023). QLoRA: Efficient Finetuning of Quantized LLMs.
  3. Frantar, E., Ashkboos, S., Hoefler, T., & Alistarh, D. (2022). GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers. arXiv Preprint arXiv:2210.17323.
  4. Lin, J., Tang, J., Tang, H., Yang, S., Chen, W.-M., Wang, W.-C., Xiao, G., Dang, X., Gan, C., & Han, S. (2023). AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. arXiv Preprint arXiv:2306.00978.
  5. Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L., & Chen, W. (2021). LoRA: Low-Rank Adaptation of Large Language Models.
  6. Micikevicius, P., Narang, S., Alben, J., & others. (2018). Mixed Precision Training.
  7. Rajbhandari, S., Rasley, J., Ruwase, O., & He, Y. (2020). ZeRO: Memory Optimizations Toward Training Trillion Parameter Models.