Nacrith-CPU / app.py
robtacconelli's picture
Update app.py
b644961 verified
#!/usr/bin/env python3
"""Nacrith CPU -- Neural Arithmetic Compression -- Hugging Face Space Demo (CPU).
Supports both text (TC01) and binary (NC05) compression.
"""
import gzip
import time
import base64
import gradio as gr
from compressor import NeuralCompressor, MAGIC, MAGIC_BIN, HEADER_SIZE
from utils import format_size
MAX_TEXT_TOKENS = 1500
MAX_BINARY_UPLOAD = 1 * 1024 * 1024 # 1 MB
MAX_NC_UPLOAD = 1 * 1024 * 1024 # 1 MB
compressor = None
def get_compressor():
global compressor
if compressor is None:
compressor = NeuralCompressor(verbose=False)
return compressor
# ---------------------------------------------------------------------------
# Tab 1: Compress Text (TC01)
# ---------------------------------------------------------------------------
def compress_text(text):
if not text or not text.strip():
return "Please enter some text to compress.", "", ""
text = text.strip()
comp = get_compressor()
original_bytes = len(text.encode("utf-8"))
token_ids = comp.model.tokenizer.encode(text)
num_tokens = len(token_ids)
if num_tokens > MAX_TEXT_TOKENS:
return (
f"**Input too long.** Please use up to {MAX_TEXT_TOKENS} tokens (~4000 characters) "
"for the demo. Larger files work locally -- see "
"[GitHub](https://github.com/st4ck/Nacrith-CPU).",
"", "",
)
t0 = time.time()
compressed = comp.compress(text)
elapsed = time.time() - t0
compressed_size = len(compressed)
ratio = compressed_size / original_bytes * 100
tokens_per_sec = num_tokens / elapsed if elapsed > 0 else 0
decompressed = comp.decompress(compressed)
lossless = decompressed == text
gzip_data = gzip.compress(text.encode("utf-8"), compresslevel=9)
gzip_size = len(gzip_data)
gzip_ratio = gzip_size / original_bytes * 100
improvement = gzip_size / compressed_size if compressed_size > 0 else 0
b64 = base64.b64encode(compressed).decode("ascii")
verify = "Yes" if lossless else "FAILED"
md = f"""## Compression Results
| | Size | Ratio |
|---|---|---|
| **Original** | {format_size(original_bytes)} | 100% |
| **gzip -9** | {format_size(gzip_size)} | {gzip_ratio:.1f}% |
| **Nacrith CPU** | {format_size(compressed_size)} | {ratio:.1f}% |
**Nacrith CPU is {improvement:.1f}x smaller than gzip**
Tokens: {num_tokens} | Time: {elapsed:.1f}s ({tokens_per_sec:.0f} tok/s) | Lossless: {verify} | Space saved: {100 - ratio:.1f}%
"""
download_html = (
f'<a download="compressed.nc" '
f'href="data:application/octet-stream;base64,{b64}" '
f'style="display:inline-block;padding:10px 24px;background:#ef4444;color:white;'
f'border-radius:8px;text-decoration:none;font-weight:bold;font-size:14px;'
f'margin-top:8px;cursor:pointer;">'
f'Download compressed.nc ({format_size(compressed_size)})</a>'
)
return md, download_html, b64
# ---------------------------------------------------------------------------
# Tab 2: Upload Text/Binary — auto-detects format
# ---------------------------------------------------------------------------
def _is_text_file(data: bytes) -> bool:
"""Check if data looks like a valid UTF-8 text file."""
try:
data.decode("utf-8")
return True
except UnicodeDecodeError:
return False
def compress_file_b64(b64_data, filename):
if not b64_data or not b64_data.strip():
return "Please upload a file using the button above.", ""
try:
data = base64.b64decode(b64_data.strip())
except Exception:
return "**Failed to read uploaded file.**", ""
if len(data) > MAX_BINARY_UPLOAD:
return (
f"**File too large** ({format_size(len(data))}). "
f"Max upload size is {format_size(MAX_BINARY_UPLOAD)}.",
"",
)
if len(data) == 0:
return "**Empty file.** Please upload a file with content.", ""
comp = get_compressor()
original_size = len(data)
is_text = _is_text_file(data)
gzip_data = gzip.compress(data, compresslevel=9)
gzip_size = len(gzip_data)
gzip_ratio = gzip_size / original_size * 100
t0 = time.time()
if is_text:
text = data.decode("utf-8")
compressed = comp.compress(text)
else:
compressed = comp.compress_bytes(data)
elapsed = time.time() - t0
compressed_size = len(compressed)
ratio = compressed_size / original_size * 100
improvement = gzip_size / compressed_size if compressed_size > 0 else 0
# Verify lossless round-trip
decompressed = comp.decompress(compressed)
if is_text:
lossless = decompressed == text
else:
lossless = decompressed == data
verify = "Yes" if lossless else "FAILED"
b64_out = base64.b64encode(compressed).decode("ascii")
# Derive output filename
out_name = (filename.strip() + ".nc") if filename and filename.strip() else "compressed.nc"
mode_label = "Text (TC01)" if is_text else "Binary (NC05)"
md = f"""## Compression Results
| | Size | Ratio |
|---|---|---|
| **Original** | {format_size(original_size)} | 100% |
| **gzip -9** | {format_size(gzip_size)} | {gzip_ratio:.1f}% |
| **Nacrith CPU** | {format_size(compressed_size)} | {ratio:.1f}% |
**Nacrith CPU is {improvement:.1f}x smaller than gzip**
Mode: {mode_label} | Time: {elapsed:.1f}s | Lossless: {verify} | Space saved: {100 - ratio:.1f}%
"""
download_html = (
f'<a download="{out_name}" '
f'href="data:application/octet-stream;base64,{b64_out}" '
f'style="display:inline-block;padding:10px 24px;background:#ef4444;color:white;'
f'border-radius:8px;text-decoration:none;font-weight:bold;font-size:14px;'
f'margin-top:8px;cursor:pointer;">'
f'Download {out_name} ({format_size(compressed_size)})</a>'
)
return md, download_html
# ---------------------------------------------------------------------------
# Tab 3: Decompress (TC01 text or NC05 binary)
# ---------------------------------------------------------------------------
def _decompress_data(data):
"""Shared decompression logic. Returns (md, download_html, text)."""
if len(data) < HEADER_SIZE:
return "**Invalid data.** Too short to be a Nacrith CPU compressed file.", "", ""
magic = data[:4]
if magic not in (MAGIC, MAGIC_BIN):
return "**Invalid data.** Not a Nacrith CPU compressed file (wrong magic bytes).", "", ""
comp = get_compressor()
is_binary = magic == MAGIC_BIN
try:
t0 = time.time()
result = comp.decompress(data)
elapsed = time.time() - t0
except Exception as e:
return f"**Decompression failed:** {e}", "", ""
if is_binary:
original_size = len(result)
b64_out = base64.b64encode(result).decode("ascii")
md = f"""## Decompression Results (Binary)
- Compressed: {format_size(len(data))}
- Decompressed: {format_size(original_size)}
- Time: {elapsed:.1f}s
- Format: NC05 (hybrid binary)
- **Lossless reconstruction successful**
"""
download_html = (
f'<a download="decompressed.bin" '
f'href="data:application/octet-stream;base64,{b64_out}" '
f'style="display:inline-block;padding:10px 24px;background:#22c55e;color:white;'
f'border-radius:8px;text-decoration:none;font-weight:bold;font-size:14px;'
f'margin-top:8px;cursor:pointer;">'
f'Download decompressed.bin ({format_size(original_size)})</a>'
)
return md, download_html, ""
else:
original_bytes = len(result.encode("utf-8"))
md = f"""## Decompression Results (Text)
- Compressed: {format_size(len(data))}
- Decompressed: {format_size(original_bytes)}
- Time: {elapsed:.1f}s
- Format: TC01 (text)
- **Lossless reconstruction successful**
"""
return md, "", result
def decompress_file_b64(b64_data):
"""Decompress from file uploaded via JS (passed as base64)."""
if not b64_data or not b64_data.strip():
return "**No file.** Please upload a .nc file.", "", ""
try:
data = base64.b64decode(b64_data.strip())
except Exception:
return "**Failed to read uploaded file.**", "", ""
if len(data) > MAX_NC_UPLOAD:
return (
f"**File too large** ({format_size(len(data))}). "
f"Max size is {format_size(MAX_NC_UPLOAD)}.",
"", "",
)
return _decompress_data(data)
def decompress_b64(b64_text):
"""Decompress from pasted base64 data."""
if not b64_text or not b64_text.strip():
return "**No data.** Paste base64 data from the Compress tab, or upload a .nc file above.", "", ""
try:
data = base64.b64decode(b64_text.strip())
except Exception:
return "**Invalid base64 data.** Please paste the exact output from the Compress tab.", "", ""
if len(data) > MAX_NC_UPLOAD:
return (
f"**Data too large** ({format_size(len(data))}). "
f"Max size is {format_size(MAX_NC_UPLOAD)}.",
"", "",
)
return _decompress_data(data)
# ---------------------------------------------------------------------------
# UI
# ---------------------------------------------------------------------------
HEADER_HTML = """
<div style="text-align: center; margin-bottom: 0.5em;">
<img src="https://raw.githubusercontent.com/st4ck/Nacrith-CPU/main/assets/banner_cpu.png" alt="Nacrith" style="max-width:420px;width:100%;margin:0 auto;">
<p style="font-size: 1.1em; color: #aaa; margin-top: 8px;">Neural Arithmetic Compression -- State-of-the-Art Lossless Compression</p>
<p style="font-size: 0.9em;">
<a href="https://nacrith.com">Website</a> |
<a href="https://github.com/st4ck/Nacrith-CPU">GitHub</a> |
Trigram Tables + Arithmetic Coding | CPU-only | Supports text &amp; binary files
</p>
<p style="font-size: 1.1em; color: #aaa; margin-top: 8px;"><i>Information is Already There</i></p>
</div>
"""
# JS to read binary file upload as base64 into the hidden textbox
BINARY_UPLOAD_JS = """
function setupBinaryUpload() {
const input = document.getElementById('binary-file-input');
if (!input) return;
input.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
// Update file name display
const nameSpan = document.getElementById('binary-file-name');
if (nameSpan) nameSpan.textContent = file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)';
// Store filename
const fnBox = document.querySelectorAll('#binary-filename textarea');
if (fnBox.length > 0) {
fnBox[0].value = file.name;
fnBox[0].dispatchEvent(new Event('input', {bubbles: true}));
}
// Read as base64
const reader = new FileReader();
reader.onload = function(ev) {
const b64 = ev.target.result.split(',')[1];
const textareas = document.querySelectorAll('#binary-b64-data textarea');
if (textareas.length > 0) {
textareas[0].value = b64;
textareas[0].dispatchEvent(new Event('input', {bubbles: true}));
}
};
reader.readAsDataURL(file);
});
}
setTimeout(setupBinaryUpload, 1000);
"""
# JS to read .nc file upload as base64 into the hidden textbox
NC_UPLOAD_JS = """
function setupNcUpload() {
const input = document.getElementById('nc-file-input');
if (!input) return;
input.addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const nameSpan = document.getElementById('nc-file-name');
if (nameSpan) nameSpan.textContent = file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)';
const reader = new FileReader();
reader.onload = function(ev) {
const b64 = ev.target.result.split(',')[1];
const textareas = document.querySelectorAll('#nc-b64-data textarea');
if (textareas.length > 0) {
textareas[0].value = b64;
textareas[0].dispatchEvent(new Event('input', {bubbles: true}));
}
};
reader.readAsDataURL(file);
});
}
setTimeout(setupNcUpload, 1200);
"""
with gr.Blocks(title="Nacrith CPU") as demo:
gr.HTML(HEADER_HTML)
# ---- Tab 1: Compress Text ----
with gr.Tab("Compress Text"):
gr.Markdown("Compress text using trigram arithmetic coding (TC01 format).")
text_input = gr.Textbox(
label="Input Text",
placeholder="Paste or type text here (up to ~4000 characters)...",
lines=8,
)
compress_text_btn = gr.Button("Compress Text", variant="primary", size="lg")
compress_text_results = gr.Markdown()
compress_text_download = gr.HTML()
compress_text_b64 = gr.Textbox(
label="Compressed data (base64) -- copy to Decompress tab to verify",
lines=3,
show_copy_button=True,
)
compress_text_btn.click(
fn=compress_text,
inputs=[text_input],
outputs=[compress_text_results, compress_text_download, compress_text_b64],
)
gr.Examples(
examples=[
["The quick brown fox jumps over the lazy dog. This is a simple test of the neural compression system."],
["In the beginning, the universe was created. This has made a lot of people very angry and has been widely regarded as a bad move. The story so far: In the beginning the Universe was created. This has made a lot of people very angry and been widely regarded as a bad move."],
["Machine learning is a subset of artificial intelligence that focuses on building systems that learn from data. Unlike traditional programming where rules are explicitly coded, machine learning algorithms identify patterns in data and make decisions with minimal human intervention. Deep learning, a further subset, uses neural networks with many layers to model complex patterns in large amounts of data."],
],
inputs=[text_input],
label="Try these examples",
)
# ---- Tab 2: Upload Text/Binary ----
with gr.Tab("Upload Text/Binary"):
gr.Markdown(
"Upload any file (up to 1 MB) to compress.\n\n"
"The format is auto-detected: text files use trigram compression (TC01), "
"binary files use hybrid compression (NC05) where text-like regions are "
"trigram-compressed and binary regions are gzip/lzma-compressed."
)
gr.HTML(
'<div style="margin:8px 0;">'
'<label style="display:inline-block;padding:10px 24px;background:#6366f1;color:white;'
'border-radius:8px;cursor:pointer;font-weight:bold;font-size:14px;">'
'Choose file to compress'
'<input id="binary-file-input" type="file" style="display:none;">'
'</label>'
'<span id="binary-file-name" style="margin-left:10px;color:#aaa;"></span>'
'</div>'
)
# Hidden textboxes to receive data from JS
binary_b64_data = gr.Textbox(visible=False, elem_id="binary-b64-data")
binary_filename = gr.Textbox(visible=False, elem_id="binary-filename")
compress_file_btn = gr.Button("Compress", variant="primary", size="lg")
compress_file_results = gr.Markdown()
compress_file_download = gr.HTML()
compress_file_btn.click(
fn=compress_file_b64,
inputs=[binary_b64_data, binary_filename],
outputs=[compress_file_results, compress_file_download],
)
# ---- Tab 3: Decompress ----
with gr.Tab("Decompress"):
gr.Markdown(
"Upload a `.nc` file to decompress, or paste base64 data from the Compress Text tab.\n\n"
"Supports both TC01 (text) and NC05 (binary) formats."
)
gr.HTML(
'<div style="margin:8px 0;">'
'<label style="display:inline-block;padding:10px 24px;background:#6366f1;color:white;'
'border-radius:8px;cursor:pointer;font-weight:bold;font-size:14px;">'
'Upload .nc file'
'<input id="nc-file-input" type="file" accept=".nc" style="display:none;">'
'</label>'
'<span id="nc-file-name" style="margin-left:10px;color:#aaa;"></span>'
'</div>'
)
nc_b64_data = gr.Textbox(visible=False, elem_id="nc-b64-data")
decompress_file_btn = gr.Button("Decompress File", variant="primary", size="lg")
gr.Markdown("**Or** paste base64 data:")
decompress_b64_input = gr.Textbox(
label="Compressed data (base64)",
placeholder="Paste base64 data here...",
lines=3,
)
decompress_b64_btn = gr.Button("Decompress Base64", variant="primary", size="lg")
decompress_results = gr.Markdown()
decompress_download = gr.HTML()
decompress_text_output = gr.Textbox(
label="Decompressed Text",
lines=10,
interactive=False,
)
decompress_file_btn.click(
fn=decompress_file_b64,
inputs=[nc_b64_data],
outputs=[decompress_results, decompress_download, decompress_text_output],
)
decompress_b64_btn.click(
fn=decompress_b64,
inputs=[decompress_b64_input],
outputs=[decompress_results, decompress_download, decompress_text_output],
)
gr.Markdown("""
---
**How it works:** Trigram tables precomputed from large text corpora predict the next token at each step.
Those predictions feed an arithmetic coder -- high-confidence predictions cost nearly zero bits.
The same tables run on both sides, guaranteeing perfect lossless reconstruction. No GPU required.
**Text (TC01):** Text is tokenized and trigram-compressed directly. Achieves ~15% ratio on English text (2.5x better than gzip).
**Binary (NC05):** Files are segmented into text-like and binary regions. Text regions are trigram-compressed;
binary regions are compressed with gzip or lzma. The hybrid approach beats gzip on files with significant text content.
<br>
<img src="https://raw.githubusercontent.com/st4ck/Nacrith-CPU/main/assets/compression_ratio.png" alt="Compression Ratio Bar Charts">
<br>
Apache 2.0 | Made by [Roberto Tacconelli](https://github.com/st4ck) | [tacconelli.rob@gmail.com](tacconelli.rob@gmail.com) | [roberto@elizetaplus.com](roberto@elizetaplus.com)
""")
# Inject upload JS
gr.HTML(f"<script>{BINARY_UPLOAD_JS}</script>")
gr.HTML(f"<script>{NC_UPLOAD_JS}</script>")
demo.queue()
demo.launch()