Links
Usage
This repo contains the merged weights: a standalone transformers model. Load it directly with AutoModelForCausalLM(PEFT is not required).
System prompt
System prompt usage is optional, however, prefer the canonical prompt anyway. It carried the bulk of training, and it is the only variant the eval numbers on this card were measured under: the alternatives are trained but not separately scored.
System prompt was varied during training. Most examples used the canonical prompt below; the rest were split between paraphrases of it and no system prompt at all. The model is therefore robust to both: a paraphrase works and the user message alone is typically a strong enough cue to trigger the task.
You are a binary code generator. Given a description of a program, output the complete binary as a lowercase hex string with no spaces, newlines, or other formatting. Output only the hex string and nothing else.
Generating a binary
from transformers import AutoModelForCausalLM, AutoProcessor
model = AutoModelForCausalLM.from_pretrained("matthewhaynesonline/gemma-4-E2B-it-crap")
processor = AutoProcessor.from_pretrained("matthewhaynesonline/gemma-4-E2B-it-crap")
messages = [
{"role": "system", "content": "You are a binary code generator. Given a description of a program, output the complete binary as a lowercase hex string with no spaces, newlines, or other formatting. Output only the hex string and nothing else."},
{"role": "user", "content": "Generate a Linux x86_64 binary that prints 'hello world'."},
]
inputs = processor.apply_chat_template(
messages, add_generation_prompt=True, tokenize=True, return_tensors="pt"
).to(model.device)
out = model.generate(**inputs, max_new_tokens=2048, do_sample=False)
hex_str = processor.decode(out[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True)
print(hex_str)
Saving and running the output
The model emits the binary as a lowercase hex string. Decode it to raw bytes, write it to a file, and set the executable bit (stdlib only):
import re, subprocess
from pathlib import Path
binary = bytes.fromhex(re.sub(r"\s+", "", hex_str))
path = Path("crap.out")
path.write_bytes(binary)
path.chmod(path.stat().st_mode | 0o111)
result = subprocess.run([f"./{path}"], capture_output=True)
print("stdout: ", result.stdout.decode(errors="replace"))
print("exit code: ", result.returncode)
A Linux binary runs as soon as the executable bit is set.
Training details
Table with columns: Field, Value| Field | Value |
|---|
| Base model | google/gemma-4-E2B-it |
| LoRA rank | 16 |
| LoRA alpha | 32 |
| LoRA target modules | q/k/v/o/gate/up/down_proj (text decoder only) |
| Trainable parameters | 24,158,208 |
Training environment
Recorded at train time (not at push time) by training_metadata.json.
Table with columns: Field, Value| Field | Value |
|---|
| Hardware | NVIDIA GeForce RTX 5090 |
| Device | cuda |
| Precision | bf16 |
| Packing | False |
| Attention impl | sdpa |
| Trained at | 2026-06-14T20:10:15 |
Library versions
Table with columns: Library, Version| Library | Version |
|---|
| torch | 2.12.0 |
| transformers | 5.9.0 |
| trl | 1.5.1 |
| peft | 0.19.1 |
| accelerate | 1.13.0 |
Eval results
The evaluation is execution based with no partial credit. An example counts as passed only if the emitted hex decodes, the bytes parse as a structurally valid ELF and the binary runs with the exact expected stdout and exit code. (The stdout comparison strips a single trailing newline from both sides and nothing else, stray NUL bytes still fail).
The base model scores ~0/23 on the same harness.
Training progression
Scored on the split above every 50 steps, the unmerged adapter reaches its peak of 20/23 (87.0%) by step 300 and holds it across steps 450 - 550, while training loss falls smoothly throughout (token loss is not the eval metric). What you download is the merged model, and it scores 20/23 (87.0%).
Known limitations
- Output carries a trailing newline. Generated binaries print the requested
string followed by
\n (the training convention) - account for it when
comparing output against an expected string.
- Write-length miscounting is the main failure mode. For arbitrary strings
the
write-length field can be wrong in either direction: over-counting reads
past the payload and appends stray NUL bytes (visible when the output is
piped, not on a terminal), under-counting drops the trailing newline. Short
strings are reliable; the error grows with length and novelty.
- Adjacent duplicate bytes aggravate the byte errors above. When a target
string repeats a character - the doubled
d in daddy, the oo in
gobbledygook - the model is likelier to scramble the payload (dropping,
transposing or duplicating a byte) than on a comparable string without the
repeat. It is an aggravator of the write-length and synthesis errors, not a
separate failure; treat repeated characters and other rare byte patterns as
lower-confidence.
- Exit-code immediates can be off. A persistent behavioral failure in the
published checkpoint is decimal-to-hex immediate encoding - e.g.
exit(223)
produces exit code 222. This survives to the end of training rather than being
a mid-run artifact, and it is the failure mode the eval's exit half exists to
measure.