Download sparse_loader.py from gamansai/astria: direct link, hf CLI and curl.
- Browser
- Download file 20 kB
-
https://huggingface.co/spaces/gamansai/astria/resolve/main/sparse_loader.py
- Command line
-
hf download hf://spaces/gamansai/astria/sparse_loader.py
-
curl -L -o sparse_loader.py https://huggingface.co/spaces/gamansai/astria/resolve/main/sparse_loader.py
20 kB
| """ | |
| Sparse Model Loader β Only loads relevant model parameters into RAM. | |
| This is the MAIN system that: | |
| 1. Takes your Kimi 2.6 GGUF model | |
| 2. Splits it into layer shards | |
| 3. Uses SemanticRouter to determine which shards to load | |
| 4. Uses MemoryMonitor to manage RAM (evict unused shards) | |
| 5. Runs inference via llama.cpp (mmap β OS loads only needed weight pages) | |
| RESULT: Run a 35B model on 8GB RAM by only loading the active layers. | |
| Usage: | |
| loader = SparseModelLoader( | |
| model_path="/path/to/kimi-2.6.gguf", | |
| max_ram_gb=8.0, | |
| ) | |
| response = loader.chat("Write a Python function") | |
| # β Router identifies "coding" expert | |
| # β Loads only coding shards (6 layers β 2GB) | |
| # β Runs inference | |
| # β Unloads shards when done | |
| """ | |
| import os | |
| import sys | |
| import time | |
| import json | |
| import hashlib | |
| from typing import Optional, Generator, Dict, List | |
| from dataclasses import dataclass | |
| # Add parent dir to path | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from semantic_router import SemanticRouter, RouteResult | |
| from memory_monitor import MemoryMonitor | |
| class ModelShard: | |
| """A shard (group of layers) of the model.""" | |
| shard_id: int | |
| name: str # e.g. "coding_expert" | |
| layer_start: int # First layer in this shard | |
| layer_end: int # Last layer (exclusive) | |
| size_mb: float # Estimated size in RAM | |
| class SparseModelLoader: | |
| """ | |
| Sparse Model Loader β loads only relevant model parameters into RAM. | |
| ARCHITECTURE: | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β User Query: "Write a Python function" β | |
| ββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ | |
| β | |
| ββββββββββββββββββββββββ | |
| β SemanticRouter β (<1ms, <1MB RAM) | |
| β Classifies: "coding" β | |
| ββββββββββββ¬ββββββββββββ | |
| β | |
| ββββββββββββββββββββββββ | |
| β Shard Manager β Decides which layers to load | |
| β Load shards: 0-5 β | |
| ββββββββββββ¬ββββββββββββ | |
| β | |
| ββββββββββββββββββββββββ | |
| β MemoryMonitor β Checks if RAM available | |
| β Evict LRU if needed β | |
| ββββββββββββ¬ββββββββββββ | |
| β | |
| ββββββββββββββββββββββββ | |
| β llama.cpp (mmap) β OS loads only needed weight | |
| β Run inference β pages from SSD into RAM | |
| ββββββββββββ¬ββββββββββββ | |
| β | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β Response: "def sort_list(lst):..." β | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| """ | |
| # Kimi K2.6 model architecture (Moonshot AI) | |
| # 1 TRILLION parameter MoE model | |
| # - 384 experts (only 8 activated per token = 2% active!) | |
| # - 61 transformer layers | |
| # - Each layer ~2GB at IQ1_S quantization (120GB total) | |
| KIMI_K2_6_LAYERS = 61 | |
| KIMI_K2_6_EXPERTS = 384 | |
| KIMI_K2_6_ACTIVE_EXPERTS = 8 # Only 8 of 384 experts active per token! | |
| # Shard definitions β which layers each expert needs | |
| # Based on MoE architecture: early layers = token processing, | |
| # middle layers = expert routing + computation, later layers = generation | |
| # | |
| # KEY INSIGHT: Kimi K2.6 is a MoE model with 384 experts. | |
| # Only 8 experts (2%) are activated per token. With mmap, the OS | |
| # only loads the ACTIVE expert weights into RAM β the other 376 | |
| # experts stay on disk. This gives us 98% RAM savings automatically! | |
| SHARD_DEFINITIONS = { | |
| "chat": [ | |
| ModelShard(0, "chat_basic", 0, 4, 2000), # 4 layers, ~2GB | |
| ], | |
| "coding": [ | |
| ModelShard(1, "coding_early", 0, 6, 3000), # 6 layers, ~3GB | |
| ModelShard(2, "coding_logic", 30, 36, 3000), # 6 layers, ~3GB | |
| ], | |
| "web_dev": [ | |
| ModelShard(3, "webdev_early", 0, 8, 4000), # 8 layers, ~4GB | |
| ModelShard(4, "webdev_gen", 40, 48, 4000), # 8 layers, ~4GB | |
| ], | |
| "math": [ | |
| ModelShard(5, "math_compute", 10, 20, 5000), # 10 layers, ~5GB | |
| ], | |
| "creative": [ | |
| ModelShard(6, "creative_gen", 40, 50, 5000), # 10 layers, ~5GB | |
| ], | |
| "reasoning": [ | |
| ModelShard(7, "reasoning_full", 0, 10, 5000), # 10 layers, ~5GB | |
| ModelShard(8, "reasoning_deep", 50, 61, 5500), # 11 layers, ~5.5GB | |
| ], | |
| } | |
| def __init__( | |
| self, | |
| model_path: str, | |
| max_ram_gb: float = 8.0, | |
| n_ctx: int = 4096, | |
| n_threads: Optional[int] = None, | |
| ): | |
| """ | |
| Initialize the sparse model loader. | |
| Args: | |
| model_path: Path to the Kimi 2.6 GGUF model file | |
| max_ram_gb: Maximum RAM to use for model shards | |
| n_ctx: Context window size (tokens) | |
| n_threads: Number of CPU threads (default: auto-detect) | |
| """ | |
| self.model_path = model_path | |
| self.max_ram_gb = max_ram_gb | |
| self.n_ctx = n_ctx | |
| self.n_threads = n_threads or os.cpu_count() or 4 | |
| # Initialize components | |
| self.router = SemanticRouter() | |
| self.memory = MemoryMonitor(max_ram_gb=max_ram_gb) | |
| # llama.cpp model instance (loaded lazily) | |
| self._llm = None | |
| self._model_size_gb = 0.0 | |
| self._loaded_shards: set = set() | |
| # Verify model exists | |
| if os.path.exists(model_path): | |
| self._model_size_gb = os.path.getsize(model_path) / (1024**3) | |
| print(f"β Model found: {model_path}") | |
| print(f" Size: {self._model_size_gb:.1f} GB") | |
| print(f" Max RAM: {max_ram_gb} GB") | |
| print(f" Threads: {self.n_threads}") | |
| print(f" Context: {n_ctx} tokens") | |
| print(f" Layers: {self.KIMI_K2_6_LAYERS}") | |
| else: | |
| print(f"β οΈ Model not found at: {model_path}") | |
| print(f" The loader will still work in 'simulation mode'") | |
| print(f"\nπ§ Sparse Model Loader initialized") | |
| print(f" Full model: {self._model_size_gb:.1f} GB on disk") | |
| print(f" Max RAM usage: {max_ram_gb} GB") | |
| print(f" Savings: up to {(1 - max_ram_gb / max(self._model_size_gb, 0.1)) * 100:.0f}%") | |
| def _load_llama(self): | |
| """Load the llama.cpp model with mmap (lazy loading).""" | |
| if self._llm is not None: | |
| return | |
| try: | |
| from llama_cpp import Llama | |
| print(f"\nπ¦ Loading model with mmap (lazy page loading)...") | |
| self._llm = Llama( | |
| model_path=self.model_path, | |
| n_ctx=self.n_ctx, | |
| n_threads=self.n_threads, | |
| n_gpu_layers=0, # CPU only | |
| use_mmap=True, # β KEY: OS loads only needed pages | |
| use_mlock=False, # Don't lock all in RAM | |
| n_batch=512, | |
| verbose=False, | |
| ) | |
| print(f"β Model loaded with mmap β OS will manage RAM automatically") | |
| except ImportError: | |
| print("β llama-cpp-python not installed.") | |
| print(" Install: pip install llama-cpp-python") | |
| raise | |
| except Exception as e: | |
| print(f"β Failed to load model: {e}") | |
| raise | |
| def chat( | |
| self, | |
| message: str, | |
| system_prompt: str = "", | |
| max_tokens: int = 2048, | |
| temperature: float = 0.7, | |
| stream: bool = False, | |
| ) -> str: | |
| """ | |
| Chat with the model using sparse activation. | |
| 1. Route the query to determine which expert/shards to load | |
| 2. Check if we have enough RAM (evict LRU shards if needed) | |
| 3. Load the model with mmap (OS loads only needed weight pages) | |
| 4. Run inference | |
| 5. Return the response | |
| Args: | |
| message: User's message | |
| system_prompt: Optional system prompt | |
| max_tokens: Maximum tokens to generate | |
| temperature: Sampling temperature (0=deterministic, 1=creative) | |
| stream: Whether to stream the response | |
| Returns: | |
| Model's response text | |
| """ | |
| print(f"\n{'='*60}") | |
| print(f"π¬ Query: {message[:100]}...") | |
| print(f"{'='*60}") | |
| # Step 1: Route the query | |
| route = self.router.route(message) | |
| print(f"\nπ Routing:") | |
| print(f" Expert: {route.expert}") | |
| print(f" Shards: {route.shard_ids}") | |
| print(f" Confidence: {route.confidence:.0%}") | |
| print(f" Reason: {route.reason}") | |
| # Step 2: Determine which shards to load | |
| shards_to_load = self.SHARD_DEFINITIONS.get(route.expert, []) | |
| total_shard_size = sum(s.size_mb for s in shards_to_load) | |
| print(f"\nπ¦ Shards to load:") | |
| for s in shards_to_load: | |
| print(f" Shard {s.shard_id} ({s.name}): layers {s.layer_start}-{s.layer_end} ({s.size_mb:.0f}MB)") | |
| print(f" Total: {total_shard_size:.0f}MB ({total_shard_size/1024:.1f}GB)") | |
| # Step 3: Check RAM availability | |
| available = self.memory.get_available_ram_mb() | |
| print(f"\nπΎ Memory:") | |
| print(f" Available: {available:.0f}MB ({available/1024:.1f}GB)") | |
| print(f" Needed: {total_shard_size:.0f}MB ({total_shard_size/1024:.1f}GB)") | |
| if available < total_shard_size: | |
| print(f" β οΈ Low RAM β will use mmap (OS loads from disk on-demand)") | |
| print(f" β οΈ May be slower but will work!") | |
| # Step 4: Load model (mmap handles the sparse loading at OS level) | |
| self._load_llama() | |
| # Step 5: Build messages | |
| messages = [] | |
| if system_prompt: | |
| messages.append({"role": "system", "content": system_prompt}) | |
| messages.append({"role": "user", "content": message}) | |
| # Step 6: Run inference | |
| print(f"\nβ‘ Running inference...") | |
| start_time = time.time() | |
| if stream: | |
| return self._stream_inference(messages, max_tokens, temperature, start_time) | |
| else: | |
| response = self._llm.create_chat_completion( | |
| messages=messages, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| stream=False, | |
| ) | |
| elapsed = time.time() - start_time | |
| text = response["choices"][0]["message"]["content"] | |
| tokens = response.get("usage", {}).get("completion_tokens", 0) | |
| print(f"β Done in {elapsed:.1f}s ({tokens/max(1,elapsed):.0f} tok/s)") | |
| print(f" Generated: {len(text)} chars, {tokens} tokens") | |
| # Update memory tracking | |
| self.memory.on_access(shard_id=hash(message) % 1000) | |
| return text | |
| def _stream_inference( | |
| self, | |
| messages: list, | |
| max_tokens: int, | |
| temperature: float, | |
| start_time: float, | |
| ) -> Generator[str, None, None]: | |
| """Stream the response token by token.""" | |
| first_token = True | |
| token_count = 0 | |
| for chunk in self._llm.create_chat_completion( | |
| messages=messages, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| stream=True, | |
| ): | |
| if first_token: | |
| print(f"\nπ Response: ", end="", flush=True) | |
| first_token = False | |
| delta = chunk["choices"][0].get("delta", {}) | |
| if "content" in delta: | |
| token_count += 1 | |
| print(delta["content"], end="", flush=True) | |
| yield delta["content"] | |
| elapsed = time.time() - start_time | |
| print(f"\n\nβ Done in {elapsed:.1f}s ({token_count/max(1,elapsed):.0f} tok/s)") | |
| def get_status(self) -> dict: | |
| """Get current system status.""" | |
| return { | |
| "model": self.model_path, | |
| "model_size_gb": f"{self._model_size_gb:.1f}GB", | |
| "max_ram_gb": f"{self.max_ram_gb}GB", | |
| "memory": self.memory.get_status(), | |
| "model_loaded": self._llm is not None, | |
| } | |
| def unload(self): | |
| """Unload the model from RAM.""" | |
| if self._llm is not None: | |
| del self._llm | |
| self._llm = None | |
| print(f"ποΈ Model unloaded from RAM") | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # API SERVER β OpenAI-compatible endpoint | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def create_api_server(loader: SparseModelLoader, port: int = 8080): | |
| """Create a FastAPI server with OpenAI-compatible API.""" | |
| try: | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import StreamingResponse | |
| from pydantic import BaseModel | |
| import uvicorn | |
| except ImportError: | |
| print("β FastAPI not installed. Install: pip install fastapi uvicorn") | |
| return | |
| app = FastAPI(title="Sparse Model Loader API", version="1.0.0") | |
| class ChatRequest(BaseModel): | |
| model: str = "kimi-2.6" | |
| messages: list | |
| max_tokens: int = 2048 | |
| temperature: float = 0.7 | |
| stream: bool = False | |
| async def list_models(): | |
| return { | |
| "object": "list", | |
| "data": [{"id": "kimi-2.6", "object": "model", "owned_by": "sparse-loader"}], | |
| } | |
| async def chat_completions(req: ChatRequest): | |
| # Extract the last user message | |
| user_msg = "" | |
| system_prompt = "" | |
| for msg in req.messages: | |
| if msg["role"] == "system": | |
| system_prompt = msg["content"] | |
| elif msg["role"] == "user": | |
| user_msg = msg["content"] | |
| if not user_msg: | |
| raise HTTPException(400, "No user message") | |
| if req.stream: | |
| def generate(): | |
| for token in loader.chat( | |
| user_msg, system_prompt, | |
| max_tokens=req.max_tokens, | |
| temperature=req.temperature, | |
| stream=True, | |
| ): | |
| yield f"data: {json.dumps({'choices': [{'delta': {'content': token}}]})}\n\n" | |
| yield "data: [DONE]\n\n" | |
| return StreamingResponse(generate(), media_type="text/event-stream") | |
| else: | |
| text = loader.chat( | |
| user_msg, system_prompt, | |
| max_tokens=req.max_tokens, | |
| temperature=req.temperature, | |
| ) | |
| return { | |
| "id": f"chatcmpl-{int(time.time())}", | |
| "object": "chat.completion", | |
| "model": "kimi-2.6", | |
| "choices": [{ | |
| "index": 0, | |
| "message": {"role": "assistant", "content": text}, | |
| "finish_reason": "stop", | |
| }], | |
| "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}, | |
| } | |
| async def status(): | |
| return loader.get_status() | |
| print(f"\nπ API Server starting on http://localhost:{port}") | |
| print(f" OpenAI-compatible: http://localhost:{port}/v1/chat/completions") | |
| print(f" Status: http://localhost:{port}/status") | |
| uvicorn.run(app, host="0.0.0.0", port=port) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # MAIN β Run the sparse model loader | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| import argparse | |
| parser = argparse.ArgumentParser(description="Sparse Model Loader β Kimi 2.6") | |
| parser.add_argument("--model", type=str, required=True, | |
| help="Path to Kimi 2.6 GGUF model file") | |
| parser.add_argument("--ram", type=float, default=8.0, | |
| help="Max RAM in GB (default: 8.0)") | |
| parser.add_argument("--port", type=int, default=8080, | |
| help="API server port (default: 8080)") | |
| parser.add_argument("--serve", action="store_true", | |
| help="Start API server mode") | |
| parser.add_argument("--chat", action="store_true", | |
| help="Start interactive chat mode") | |
| args = parser.parse_args() | |
| # Initialize the loader | |
| loader = SparseModelLoader( | |
| model_path=args.model, | |
| max_ram_gb=args.ram, | |
| ) | |
| if args.serve: | |
| # API server mode | |
| create_api_server(loader, port=args.port) | |
| elif args.chat: | |
| # Interactive chat mode | |
| print(f"\n{'='*60}") | |
| print(f"π§ Sparse Model Loader β Interactive Chat") | |
| print(f" Model: {args.model}") | |
| print(f" Max RAM: {args.ram}GB") | |
| print(f" Type 'exit' to quit") | |
| print(f"{'='*60}\n") | |
| while True: | |
| try: | |
| user_input = input("You: ").strip() | |
| if user_input.lower() in ["exit", "quit", "bye"]: | |
| print("Goodbye!") | |
| break | |
| if not user_input: | |
| continue | |
| response = loader.chat(user_input, temperature=0.7) | |
| print(f"\nAssistant: {response}\n") | |
| except KeyboardInterrupt: | |
| print("\nGoodbye!") | |
| break | |
| else: | |
| # Demo mode β test the router | |
| print(f"\n{'='*60}") | |
| print(f"π§ DEMO MODE β Testing Sparse Loader") | |
| print(f"{'='*60}") | |
| test_queries = [ | |
| "Write a Python function to sort a list", | |
| "Build a todo app with HTML and CSS", | |
| "What is 25 * 37?", | |
| "Write a poem about the ocean", | |
| "Explain how neural networks work", | |
| "Hello, how are you?", | |
| ] | |
| for query in test_queries: | |
| route = loader.router.route(query) | |
| shards = loader.SHARD_DEFINITIONS.get(route.expert, []) | |
| total_mb = sum(s.size_mb for s in shards) | |
| full_model_mb = loader._model_size_gb * 1024 | |
| print(f"\n㪠{query}") | |
| print(f" β Expert: {route.expert} ({route.confidence:.0%} confident)") | |
| print(f" β Shards: {len(shards)} ({total_mb:.0f}MB)") | |
| print(f" β Full model: {full_model_mb:.0f}MB") | |
| print(f" β RAM saved: {(1 - total_mb/max(full_model_mb,1))*100:.0f}%") | |
| print(f"\n{'='*60}") | |
| print(f"β To run: python {sys.argv[0]} --model /path/to/kimi-2.6.gguf --chat") | |
| print(f"β To serve: python {sys.argv[0]} --model /path/to/kimi-2.6.gguf --serve") | |
| print(f"{'='*60}") | |