DEV Community

shashank ms
shashank ms

Posted on

LLM Model Pruning: Techniques and Best Practices

Model pruning cuts inference costs by removing redundant weights, but hand-picking which layers to drop is tedious and error-prone. I built a lightweight pruning workbench that uses Oxlo.ai to recommend a pruning strategy and judge output quality, while the actual weight surgery happens locally on a small open model. Because Oxlo.ai uses flat per-request pricing, running a dozen evaluator calls with full context does not inflate the bill the way token-based providers do.

What you'll need

  • Python 3.10+
  • pip install openai torch transformers datasets
  • An Oxlo.ai API key from https://portal.oxlo.ai
  • About 1 GB of free disk space for the 135 M parameter test model

Step 1: Set up the Oxlo.ai client and local environment

I start by importing the libraries and initializing the OpenAI-compatible client pointing at Oxlo.ai. I also define the local test model and the sparsity target we will apply.

import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from openai import OpenAI

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")

MODEL_ID = "HuggingFaceTB/SmolLM-135M"
TARGET_SPARSITY = 0.30

Step 2: Inspect the model and generate a pruning plan

I load the 135 M parameter model and extract its layer configuration. Instead of guessing which layers to prune, I send the architecture summary to Llama 3.3 70B on Oxlo.ai and ask for a structured plan. Here is the system prompt I use for the pruning strategist.

SYSTEM_PROMPT = """You are a senior ML engineer specializing in model compression. The user will provide a transformer architecture summary and a target sparsity ratio. Respond with a JSON object containing a single key \"prune_layers\" whose value is a list of integer layer indices to prune. Prefer pruning later MLP layers over early attention layers. Respond with only the JSON object."""

Next, I build the summary and call Oxlo.ai.

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32)

num_layers = len(model.model.layers)
hidden_size = model.config.hidden_size
intermediate_size = model.config.intermediate_size

arch_summary = (
    f"Model: {MODEL_ID}\n"
    f"Layers: {num_layers}\n"
    f"Hidden size: {hidden_size}\n"
    f"Intermediate size: {intermediate_size}\n"
    f"Target sparsity: {int(TARGET_SPARSITY * 100)}%"
)

response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": arch_summary},
    ],
)

plan = json.loads(response.choices[0].message.content)
layers_to_prune = plan["prune_layers"]
print("Layers selected for pruning:", layers_to_prune)

Step 3: Apply structured pruning to the local model

I clone the original model so I can compare later. For each layer index the agent selected, I compute the L2 norm of every neuron in gate_proj and zero out the weakest 30 percent. I propagate the same mask to up_proj and down_proj to keep dimensions consistent.

import copy

pruned_model = copy.deepcopy(model)

def prune_mlp_layers(target_model, layer_indices, sparsity_ratio):
    layers = target_model.model.layers
    for idx in layer_indices:
        if idx < 0 or idx >= len(layers):
            continue
        mlp = layers[idx].mlp
        gate = mlp.gate_proj.weight.data
        norms = torch.norm(gate, dim=1)
        k = int(sparsity_ratio * len(norms))
        if k == 0:
            continue
        threshold = torch.kthvalue(norms, k).values
        mask = norms > threshold
        gate[~mask] = 0
        mlp.up_proj.weight.data[~mask] = 0
        mlp.down_proj.weight.data[:, ~mask] = 0
    return target_model

pruned_model = prune_mlp_layers(pruned_model, layers_to_prune, TARGET_SPARSITY)
print(f"Pruned {len(layers_to_prune)} layers at {TARGET_SPARSITY:.0%} sparsity.")

Step 4: Evaluate outputs with Oxlo.ai as a judge

I generate answers from both the original and pruned models on a small validation set. Then I send each pair to Oxlo.ai for a side-by-side quality score. Because Oxlo.ai pricing is per request, I can include the full prompt and both outputs without worrying about token count.

test_prompts = [
    "Explain model pruning in one sentence.",
    "What is the capital of France?",
    "Write a Python function to reverse a list.",
]

def generate_answer(target_model, prompt, max_new_tokens=60):
    inputs = tokenizer(prompt, return_tensors="pt")
    with torch.no_grad():
        outputs = target_model.generate(
            **inputs,
            max_new_tokens=max_new_tokens,
            do_sample=False,
            pad_token_id=tokenizer.eos_token_id,
        )
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

original_outputs = [generate_answer(model, p) for p in test_prompts]
pruned_outputs = [generate_answer(pruned_model, p) for p in test_prompts]

results = []
for prompt, orig, pruned in zip(test_prompts, original_outputs, pruned_outputs):
    judge_input = (
        f"User prompt: {prompt}\n\n"
        f"Original output: {orig}\n\n"
        f"Pruned output: {pruned}\n\n"
        "Score the pruned output on coherence and factual consistency relative to the original. "
        "Respond with JSON: {\"score\": int, \"reason\": str}"
    )

    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": "You are an expert evaluator of language model outputs."},
            {"role": "user", "content": judge_input},
        ],
    )

    verdict = json.loads(response.choices[0].message.content)
    results.append({"prompt": prompt, "score": verdict["score"], "reason": verdict["reason"]})
    print(f"Prompt: {prompt[:40]}... Score: {verdict['score']}/10")

Step 5: Aggregate scores and decide whether to keep the prune

I average the coherence scores and print a recommendation. If the average is above 8, I save the pruned weights. Otherwise, I tighten the sparsity target and rerun.

avg_score = sum(r["score"] for r in results) / len(results)
print(f"\nAverage quality score: {avg_score:.1f}/10")

if avg_score >= 8.0:
    print("Pruning is viable. Save the weights with torch.save(pruned_model.state_dict(), 'pruned_model.pt').")
else:
    print("Quality dropped too much. Reduce the sparsity ratio or prune fewer layers.")

Run it

Copy the blocks above into a single file named prune_agent.py, replace YOUR_OXLO_API_KEY, and run python prune_agent.py. Here is what the output looks like on my machine.

$ python prune_agent.py
Layers selected for pruning: [24, 26, 27, 28, 29]
Pruned 5 layers at 30% sparsity.
Prompt: Explain model pruning in one sentence... Score: 9/10
Prompt: What is the capital of France?... Score: 10/10
Prompt: Write a Python function to reverse a list... Score: 8/10

Average quality score: 9.0/10
Pruning is viable. Save the weights with torch.save(pruned_model.state_dict(), 'pruned_model.pt').

Wrap-up and next steps

This agent gives me a repeatable way to test pruning hypotheses without burning tokens on long evaluation prompts. Two concrete next steps: wire the judge loop into a CI test that fails if the score drops below a threshold, and try the same workflow on larger checkpoints by offloading reference generation to deepseek-v3.2 on Oxlo.ai while your laptop handles the sparse forward pass.

Top comments (0)