Spaces:
Running
Running
| #!/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 & 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() | |