DaisyChain-Train / daisychain /spikewhale_task.py
Quazim0t0's picture
Release: SpikeWhale slider panel (HF dataset picker, stop/back), DaisyChain-Web (P2P WebRTC training, DaisyAdam, checkpoints, room host approval, verified-units-only)
4fd620e verified
Raw
History Blame Contribute Delete
5.65 kB
"""DaisyChain Task that trains the REAL SpikeWhale model on streamed FineWeb-Edu.
No reimplementation: it imports your actual `model_v2.SpikeWhaleLM` + `config.
SpikeWhaleConfig` + `spike_tokenizer.SpikeTokenizer`, builds a size chosen by the
sliders (env vars), and streams FineWeb-Edu for data. DaisyChain then distributes
it across machines; matmuls can be routed through the verified/GUDA units.
Point DaisyChain at it:
DAISY_TASK=daisychain.spikewhale_task:SpikeWhaleTask daisychain-train
Sliders (env):
DAISY_SW_PATH folder holding model_v2.py/config.py/tokenizer.json
DAISY_SW_HIDDEN hidden_size (default 256)
DAISY_SW_LAYERS num_hidden_layers (default 4)
DAISY_SW_HEADS num_attention_heads (default 4)
DAISY_SW_EXPERTS n_routed_experts (default 4)
DAISY_SW_SEQLEN sequence length (default 128)
DAISY_SW_MTP MTP heads (0=off) (default 0)
DAISY_SW_DATASET HF dataset (default HuggingFaceFW/fineweb-edu)
DAISY_SW_SUBSET dataset config name (default sample-10BT)
"""
import os
import sys
import torch
_DEF_PATH = os.environ.get("DAISY_SW_PATH", r"C:\Users\quaz\Desktop\Spikewhale")
def _import_spikewhale():
if _DEF_PATH not in sys.path:
sys.path.insert(0, _DEF_PATH)
from config import SpikeWhaleConfig
from model_v2 import SpikeWhaleLM
from spike_tokenizer import SpikeTokenizer
return SpikeWhaleConfig, SpikeWhaleLM, SpikeTokenizer
def _envi(k, d):
return int(os.environ.get(k, d))
def build_config():
SpikeWhaleConfig, _, _ = _import_spikewhale()
hidden = _envi("DAISY_SW_HIDDEN", 256)
return SpikeWhaleConfig(
hidden_size=hidden,
num_hidden_layers=_envi("DAISY_SW_LAYERS", 4),
num_attention_heads=_envi("DAISY_SW_HEADS", 4),
head_dim=32, qk_rope_head_dim=16,
q_lora_rank=max(32, hidden // 4), o_lora_rank=max(32, hidden // 8),
num_key_value_heads=1,
max_position_embeddings=max(256, _envi("DAISY_SW_SEQLEN", 128)),
moe_intermediate_size=hidden,
n_routed_experts=_envi("DAISY_SW_EXPERTS", 4), n_shared_experts=1,
num_experts_per_tok=min(2, _envi("DAISY_SW_EXPERTS", 4)),
num_hash_layers=1, hc_mult=2,
num_nextn_predict_layers=_envi("DAISY_SW_MTP", 0),
engram_table_size=4096, engram_compress_dim=48, engram_num_heads=2,
)
class _FineWebStream:
"""Streams FineWeb-Edu, tokenizes, yields fixed-length token windows."""
def __init__(self, tokenizer, seqlen, rank=0, world=1):
self.tok, self.seqlen = tokenizer, seqlen
self.buf = []
ds_path = os.environ.get("DAISY_SW_DATASET", "HuggingFaceFW/fineweb-edu")
# empty string = "use the dataset's default config" (custom datasets
# usually have no named config; the sample-10BT default only fits fineweb-edu)
default_subset = "sample-10BT" if ds_path == "HuggingFaceFW/fineweb-edu" else ""
subset = os.environ.get("DAISY_SW_SUBSET", default_subset)
self.eos = getattr(tokenizer, "eos_token_id", 1) or 1
try:
from datasets import load_dataset
ds = load_dataset(ds_path, name=subset or None, split="train", streaming=True)
ds = ds.shard(num_shards=world, index=rank) if world > 1 else ds
self.it = iter(ds)
self.source = f"{ds_path}:{subset or 'default'}"
except Exception as e:
print(f"[spikewhale] FineWeb stream unavailable ({e}); using local fallback text", flush=True)
self.it = None
self.source = "local-fallback"
def _more_tokens(self):
if self.it is not None:
row = next(self.it)
text = row.get("text", "") or ""
else:
text = ("Education is the process of learning and acquiring knowledge. "
"Small models can still learn useful patterns from good data. ")
ids = self.tok.encode(text, add_special_tokens=False)
self.buf.extend(ids + [self.eos])
def next_window(self):
while len(self.buf) < self.seqlen + 1:
self._more_tokens()
w = self.buf[: self.seqlen + 1]
self.buf = self.buf[self.seqlen:]
return w
class SpikeWhaleTask:
def __init__(self):
SpikeWhaleConfig, SpikeWhaleLM, SpikeTokenizer = _import_spikewhale()
self.cfg = build_config()
tok_file = os.path.join(_DEF_PATH, "tokenizer.json")
self.tok = SpikeTokenizer(vocab_file=tok_file)
self.seqlen = _envi("DAISY_SW_SEQLEN", 128)
rank = _envi("RANK", 0); world = _envi("WORLD_SIZE", 1)
self.stream = _FineWebStream(self.tok, self.seqlen, rank, world)
self._SpikeWhaleLM = SpikeWhaleLM
n = None
print(f"[spikewhale] data source: {self.stream.source}", flush=True)
def build_model(self):
torch.manual_seed(0) # identical init on every node
m = self._SpikeWhaleLM(self.cfg)
n = sum(p.numel() for p in m.parameters())
print(f"[spikewhale] model built: {n:,} params "
f"(hidden={self.cfg.hidden_size}, layers={self.cfg.num_hidden_layers}, "
f"experts={self.cfg.n_routed_experts}, seqlen={self.seqlen})", flush=True)
return m
def sample(self, n):
rows = [self.stream.next_window()[:self.seqlen] for _ in range(n)]
t = torch.tensor(rows, dtype=torch.long) # (n, seqlen)
return t, t # labels == input_ids; model shifts internally
def loss(self, model, X, y):
return model(input_ids=X, labels=y).loss