Introduction

PoCSmith is a specialized language model and the tooling around it, built to generate proof-of-concept exploits and multi-platform shellcode from vulnerability descriptions and CVE data. At its center is a CodeLlama-7B model fine-tuned with QLoRA 4-bit quantization on 1,472 exploit samples, wrapped in a CLI that fetches CVE data from NVD, builds a structured prompt, runs inference, and formats the result. This case study documents how the model was built, why each training choice was made, what the measured results were, and how the surrounding generation pipeline turns the model into a usable research assistant. The distinctive constraint throughout is hardware: the entire fine-tune ran on an RTX 4050 laptop GPU with 6GB of VRAM, and nearly every decision follows from that limit.

I set out to answer a concrete question: can a capable code model be specialized for offensive-artifact generation on the kind of GPU a student or independent researcher actually owns, and produce something useful? The answer the project reaches is yes, with a specific set of memory and training-configuration choices that this study walks through in detail.

Problem

Drafting exploit code from a CVE is a recurring, pattern-heavy task. A researcher reads the vulnerability description, classifies it (buffer overflow, SQL injection, use-after-free, and so on), identifies the affected software and version, recalls the relevant exploitation approach, and writes code, frequently including architecture-specific shellcode with its own constraints like null-byte avoidance and correct syscall sequencing. Much of this is translation from a known vulnerability class to a known code pattern, which is exactly the kind of work a specialized model can accelerate.

General code models do not do this well out of the box. They produce syntactically fine but generically-shaped code, and they have not seen enough exploit-structured examples to reliably emit the idioms offensive artifacts need. The gap is not raw coding ability; CodeLlama already has that. The gap is task specialization: mapping a vulnerability description to an exploit-shaped output. Closing that gap is what fine-tuning is for, and it is the problem PoCSmith targets.

Background

The project fine-tunes codellama/CodeLlama-7b-hf and ships the result as a LoRA adapter (adapter_model.safetensors, 33MB) alongside a CLI. It is MIT licensed, published on PyPI as pocsmith and on Hugging Face as regaan/pocsmith (a PEFT adapter over the CodeLlama base). The training stack is the standard modern QLoRA toolchain: transformers, peft, bitsandbytes, and trl. Inference runs locally through transformers + peft with CUDA. The CLI is built on click and targets Python 3.11+.

The single most important background fact is the hardware. Training ran on an NVIDIA RTX 4050 Laptop GPU with 6GB of VRAM, and VRAM usage peaked at 5.9GB of 6.0GB, 96% utilization, with roughly 228MB of headroom. That constraint is the protagonist of this study: it dictated the base model size, the quantization scheme, the batch size, the optimizer, and several smaller choices besides.

Threat Model and Ethical Posture

PoCSmith generates offensive artifacts, so its risk profile is different from a defensive tool, and the documentation treats that seriously. The README and usage guide are explicit and repeated: the tool is for authorized penetration testing, security research, and education in controlled environments, and not for unauthorized access. The architecture doc lists ethical safeguards as design considerations: requiring confirmation for exploit generation, surfacing legal warnings, logging usage locally, and rate limiting.

There is also a privacy dimension that doubles as a security property: processing happens locally, with no data sent to external servers. For a tool handling vulnerability research, keeping generation on the analyst’s own machine avoids leaking what CVEs or targets they are working on to a third-party API. The one external call is to the NVD API to fetch public CVE data, which is public information by definition. The local-inference design is therefore both an operational choice (works offline, on your own GPU) and a confidentiality choice (your research subjects do not leave your machine).

Why CodeLlama-7B

The base-model choice was made under the 6GB ceiling, and the reasoning is documented. CodeLlama was chosen for three reasons: it is specialized for code generation, it is small enough to fine-tune on consumer hardware at 7B rather than 70B, and it understands code structure better than a general model of similar size. The alternatives were rejected on concrete grounds. Llama 3 8B was too large for 6GB VRAM. CodeLlama 13B needs 12GB or more. GPT-based models are closed source and therefore not fine-tunable this way at all.

The logic is that the base model should already be good at code so the fine-tune only has to teach the task, not the medium. Starting from a code-specialized model means the 1,472 exploit samples are spent teaching “map this vulnerability to this exploit shape,” not “here is how to write valid C.” That is an efficient use of a small dataset, and it is the reason a modest fine-tune can move the needle at all.

Why QLoRA

Full fine-tuning of a 7B model is out of the question on 6GB; even loading the model in 16-bit would not fit with optimizer states and activations. QLoRA is the technique that makes it possible, and the project uses it in its standard, aggressive form. The model is loaded in 4-bit with NF4 quantization, double quantization, and bfloat16 compute dtype. On top of the frozen 4-bit base, LoRA adapters are trained: rank 16, alpha 32, targeting the q_proj and v_proj attention projections, with 0.05 dropout and no bias, as a causal-LM task.

The payoff is stark. Only 8,388,608 parameters are trainable, which is 0.12% of the model. The 4-bit quantization reduced the model’s memory footprint from roughly 13GB to about 4GB, which is what buys enough room to train at all on a 6GB card. Targeting only the query and value projections (rather than all linear layers) is a deliberate frugality: it is the classic LoRA sweet spot that captures most of the adaptation benefit for a fraction of the trainable parameters and VRAM. Everything about the QLoRA configuration is chosen to fit the task into the memory envelope while keeping enough capacity to learn the specialization.

Training Configuration

Every training hyperparameter reads as a memory-versus-progress tradeoff resolved in favor of fitting the hardware.

Batch size is 1, the minimum, with gradient accumulation of 4 to simulate an effective batch of 4 without the memory cost of a real batch of 4. Gradient checkpointing is enabled, trading recomputation during the backward pass for lower activation memory. The optimizer is paged_adamw_8bit, an 8-bit paged AdamW that offloads optimizer states to CPU RAM, which is essential because full AdamW states for even a small trainable set would compete for the scarce VRAM. Precision is bfloat16, which is native on the RTX 40-series and avoids the overhead of mixed-precision loss scaling. Max sequence length is 1024, long enough for the exploit samples but capped to bound activation memory.

The learning rate is 2e-4, a standard LoRA learning rate, over 3 epochs. That combination is conventional for a small-adapter fine-tune: LoRA can tolerate a higher learning rate than full fine-tuning because so few parameters move, and 3 epochs over 1,472 samples is enough to specialize without overfitting a dataset this size into memorization.

Dataset

The dataset is 1,472 samples, split 1,177 training (80%), 147 validation (10%), and 148 test (10%). Its composition is two-part: 407 CVE-to-exploit pairs and 1,065 shellcode examples. Each sample is an instruction-tuning record with an instruction, an input (the vulnerability description or context), an output (the exploit or shellcode), and a combined text field in the ### Instruction: / ### Response: format that the trainer consumes.

The composition tells you what the model is actually good at. With 1,065 of 1,472 samples being shellcode, the dataset is shellcode-heavy, which aligns with the CLI’s strong multi-platform shellcode support (five platforms, four payload types). The 407 CVE-to-exploit pairs are the smaller half, teaching the CVE-description-to-exploit mapping. The instruction-tuning format is the right choice for a task that is fundamentally “given this instruction and this context, produce this artifact,” and it matches how the model is later prompted at inference time.

Assembling this dataset is itself the hard, unglamorous part of the project. The acknowledgments name the provenance of the raw material: Exploit-DB, the CVE database, and the Metasploit framework, curated into the instruction/response format. A dataset of 1,472 clean, correctly-formatted CVE-exploit and shellcode samples is a meaningful curation effort, and it is the foundation everything else sits on.

Training Results

The fine-tune produced measured, documented improvements. Training loss fell from 1.20 to 0.84, a 30% reduction. Token accuracy rose from 72.6% to 78.4%, a 5.8-point gain. Final evaluation loss settled at 0.926 and was reported as stable. The learning curve is smooth and monotonic: loss 1.20 and accuracy 72.6% at the start, 1.03/75.4% by epoch 0.34, 0.89/78.0% at epoch 1, 0.85/78.2% at epoch 2, and 0.84/78.4% at epoch 3.

That curve is worth reading closely. Most of the gain arrives in the first epoch: accuracy goes from 72.6% to 78.0% by the end of epoch 1, then only inches to 78.4% across epochs 2 and 3. This is the expected shape for a small-adapter fine-tune on a focused dataset: the model quickly picks up the task’s surface structure, then plateaus. The near-flat second and third epochs suggest the fine-tune was close to the useful ceiling for this dataset size and adapter capacity, and that more epochs would mostly risk overfitting rather than add accuracy. The stable 0.926 eval loss against a 0.84 train loss shows a modest train/eval gap, consistent with light rather than severe overfitting.

Training Process and Performance

The run took 3 hours 17 minutes over 885 total steps (295 per epoch), at roughly 12 seconds per step, with evaluation every 100 steps and no out-of-memory crashes. VRAM sat at 5.9GB of 6.0GB (96%) with the GPU stable around 73°C. The headroom was about 228MB, which is thin but held.

These numbers are the proof of the central claim: a genuinely useful specialization of a 7B model was completed in under three and a half hours on a laptop GPU, without a single OOM crash, at 96% memory utilization. The 12-seconds-per-step rate at batch size 1 with gradient accumulation 4 is what you would expect for a checkpointed, paged-optimizer, 4-bit setup: not fast, but steady and, crucially, stable within the memory envelope. Running at 96% VRAM for over three hours without crashing is itself an engineering result; it means the memory budget was calculated correctly rather than approached by trial and error.

Optimizations Applied

The memory optimizations are worth separating from the speed ones, because they served different masters. For memory: 4-bit quantization shrank the model from 13GB to about 4GB; gradient checkpointing cut activation memory during backprop; the paged optimizer pushed optimizer states out to RAM; and bf16 precision, native on the RTX 40-series, avoided extra scaling overhead. Together these are what created the ~228MB of headroom on a 6GB card.

For speed and stability within that budget: batch size 1 maximized per-step memory efficiency, gradient accumulation simulated a larger effective batch without the memory cost, and packing was deliberately disabled to avoid the overhead of concatenating sequences. Turning off packing is a small but telling choice: packing would have improved throughput, but at 1024 max length and batch size 1 the memory predictability was worth more than the throughput, so it was left off.

Refactoring and Debugging Lessons

The fine-tuning documentation records three concrete problems and their fixes, and each carries a transferable lesson.

The first was TRL API compatibility: the SFTTrainer API had changed between library versions, and the fix was migrating from TrainingArguments to SFTConfig. This is the standard tax of building on a fast-moving ML toolchain; the lesson is that pinning and tracking the exact trl API surface matters, because the training config is where breaking changes bite first.

The second was a model cache issue: model files downloaded to the wrong directory, fixed by manually copying them to the correct commit-hash folder. This is a Hugging Face cache-layout gotcha, and the lesson is that the local cache structure (keyed by commit hash) is load-bearing and worth understanding before a run rather than during one.

The third was VRAM spikes during evaluation: evaluation caused temporary VRAM spikes, and rather than reduce eval batching or frequency, the risk was accepted because the 228MB headroom was sufficient. That is a calculated decision, not an oversight; it says the memory budget had been characterized well enough to know the spikes would fit. The broader lesson from all three is that constrained-hardware fine-tuning is as much about toolchain and memory bookkeeping as about the model itself.

The Generation Pipeline

The model is only half the product; the CLI is what makes it usable. The documented data flow for CVE-to-PoC generation is: user supplies a CVE ID, the CVE parser fetches and extracts the vulnerability details (type, affected software, severity) from NVD, the context is built into a structured prompt, the fine-tuned model generates the PoC, and the formatter validates and formats the output into working exploit code. The CLI surfaces this as python src/cli/main.py cve CVE-2024-1234, with --output to save and --no-cache to force a fresh NVD fetch.

There are three generation entry points. cve goes from a CVE ID through NVD. generate goes from a free-text vulnerability description with --vuln, --target, and --details, for cases where there is no CVE or the researcher wants to describe the bug directly. shellcode goes from a platform and payload type to architecture-specific shellcode. Supporting commands (list-platforms, list-payloads, disclaimer) round out the surface. Generated files land in output/ by default. The NVD integration respects the API’s rate limit (5 requests per 30 seconds), which the troubleshooting guide documents alongside the CUDA-out-of-memory and model-path failure modes.

Multi-Platform Shellcode

Shellcode generation is the model’s strongest area, which follows directly from the dataset being 72% shellcode examples. The CLI supports five platforms (Linux x86, Linux x64, Windows x86, Windows x64, and ARM) and four payload types (reverse shell, bind shell, exec, and download-and-exec). The documented example generates a Linux/x86 reverse shell that calls socket() → connect() → dup2() → execve(), the canonical reverse-shell syscall sequence, parameterized by the attacker’s --lhost and --lport. The architecture doc lists pwntools as the shellcode tooling and names encoder/obfuscation and null-byte avoidance as concerns, which are the standard requirements for shellcode that has to survive delivery through a vulnerable input.

Design Decisions

The decisions all trace back to the hardware constraint and the task. Choosing a 7B code model over a larger general model was about fitting VRAM while starting from code competence. Choosing QLoRA with only q/v projections trained was about fitting the fine-tune into 4GB and keeping the adapter tiny (33MB). Choosing a shellcode-heavy dataset shaped a tool that is strongest at shellcode. Choosing local inference was about both offline usability and keeping research subjects confidential. Choosing an instruction-tuning format aligned training with how the model is prompted at inference. Each is a coherent response to “specialize a capable code model for offensive artifacts, cheaply, on hardware I actually have.”

Tradeoffs

The 7B base trades ceiling for accessibility: a 13B or larger model might generate better artifacts, but it would not train on the target hardware. QLoRA trades a little quality (4-bit quantization is lossy, and adapters are lower-capacity than full fine-tuning) for the ability to train at all on 6GB. Training only q/v projections trades some adaptation capacity for memory and a tiny adapter. The shellcode-heavy dataset trades breadth of CVE-exploit coverage for depth on shellcode. Local inference trades the convenience of a hosted API for privacy and offline operation. The 3-epoch schedule trades a possible sliver of extra accuracy for avoiding overfitting on a small dataset, which the near-flat late learning curve justifies.

Limitations

The documented results are training metrics, and token accuracy (78.4%) measures next-token prediction on held-out data, not the functional correctness of generated exploits; a high token accuracy does not guarantee a working PoC, which is exactly why the usage guide insists on reviewing and testing every output. The dataset is small (1,472 samples) and shellcode-weighted, so CVE-to-exploit generation rests on only 407 pairs. The model is a 4-bit-quantized 7B adapter, so it is a first-draft assistant, not an autonomous exploit writer. CVE generation depends on the NVD API and its rate limit. These are the honest edges of a consumer-hardware, small-dataset fine-tune, and the project frames outputs as drafts accordingly.

Future Work

The repository does not publish a roadmap. Grounded, near-term directions implied by the current tree include broadening the architecture layer, expanding the fuzzing corpus categories beyond loader and trace, quantifying decompiler and recovery quality, and publishing the benchmark set. Any specific committed roadmap is Additional validation required.

Lessons Learned

The clearest lesson is that useful model specialization is now within reach on consumer hardware: a 7B code model was specialized for a real task in 3h17min on a 6GB laptop GPU, and the memory budget was tight but calculable. A second lesson is that dataset composition is destiny; a 72%-shellcode dataset produced a shellcode-strong tool, and the CVE-exploit capability is exactly as broad as its 407 pairs allow. A third is that on constrained hardware the hard problems are as much toolchain and memory bookkeeping (the TRL API migration, the cache-directory fix, the characterized eval spikes) as they are modeling. A fourth is that the learning curve is a design signal: the near-flat epochs 2 and 3 said the fine-tune had reached its useful ceiling for this dataset and adapter, and that more epochs would buy overfitting rather than accuracy.

Conclusion

PoCSmith is a demonstration that a capable code model can be cheaply specialized into a useful offensive-artifact assistant on hardware an independent researcher actually owns. The measured results are modest and honestly reported: a 30% training-loss reduction, 78.4% token accuracy, a 33MB adapter, all from a 3h17min run at 96% VRAM on a 6GB laptop GPU. The generation pipeline turns that model into a working CLI that goes from a CVE ID or a description to exploit code or multi-platform shellcode, entirely locally. The project’s strongest area (shellcode) follows directly from its dataset, its limits (small dataset, quantized small model, token-accuracy-not-correctness) are stated plainly, and its ethical framing (drafts to review, local processing, authorized use only) is consistent throughout. It is a well-scoped, well-documented specialization project that does exactly what its constraints allow.