6 Commits
Author SHA1 Message Date
fam 8c7210abeb doc: W1 audio smoke summary
smoke / nanochat-smoke (push) Canceled after 0s
Walkthrough of the three W1 commits, the 4090 result (50 steps in ~1s,
loss 5.55 → 0.17), and the limitations to keep in mind before reading
into the loss-down (LM is also random + tiny vocab, so the drop is
mostly memorisation, not Whisper-Projector alignment — W2 freezes the
LM specifically to test that). Includes the W2 hand-off checklist.
2026-05-05 22:40:57 +01:00
fam 3c1cc3302f omni: W1 audio align smoke — synthetic dataset + 50-step script
End-to-end smoke proving the audio path:
  wav -> WhisperEncoder (frozen) -> Projector -> prepend to text embeddings
      -> tiny d6 GPT (random init) -> CE loss on text only

Pass criterion is a plain "loss drops by at least 0.5". On a 4090 the run
finishes in ~1 s and goes 5.55 -> 0.17 over 50 steps, so the threshold has
plenty of headroom against false positives.

Two design calls worth keeping in mind:

1. Synthetic sine clips, not LibriSpeech. W1 is forward-path proof, not
   alignment quality, and a deterministic offline dataset means no network
   on the smoke path. data/audio_smoke/manifest.jsonl is the only thing
   committed; wavs are regenerated by audio_smoke_data.py and gitignored.
   W2 swaps in real LibriSpeech.

2. Standalone byte-level tokenizer (UTF-8 bytes + a single BOS, vocab=257).
   Avoids depending on a trained nanochat BPE — the d6 GPT is random
   anyway, so vocab choice doesn't matter for "does the gradient flow"
   smoke. W2 onwards uses the real BPE on a real base.

Caveat documented in doc/todo.md: because the LM is also random and being
trained, the loss-down here mostly reflects the LM memorising 5 short
strings, not Whisper-Projector alignment. That's fine for proving
plumbing; W2 freezes the LM so projector-only gradient is the only path
to lower loss.
2026-05-05 22:39:20 +01:00
fam 7cc94cf584 omni: nanochat/audio.py — frozen Whisper encoder + Projector
The audio modality module that pairs with the gpt.forward audio_features
hook. Two things live here:

WhisperEncoder: thin wrapper around transformers' WhisperModel.encoder.
- Weight loading prefers ModelScope when WHISPER_MS_ID is set (matches the
  CN-mirror policy in doc/todo.md — modelscope is first-class for model
  weights, hf-mirror is fallback). Otherwise falls back to plain HF, with
  WHISPER_HF_ID as the override and `openai/whisper-base` as the default
  (the smallest variant that still produces useful features for smoke).
- Encoder params have requires_grad=False from __init__ so they never
  appear in the optimizer's param list. Caller does not need to remember
  to freeze it.
- preprocess() runs the feature extractor; forward() takes (B, n_mels,
  T_mel) and returns last_hidden_state (B, T_enc, d_model). Whisper pads
  every clip to 30 s, so T_enc is a constant 1500 regardless of input
  duration — handy for batching, wasteful for short clips. We accept the
  waste at W1; W2 can switch to streaming-style chunking.
- Note for W3+/W5+: last_hidden_state is the most text-semantic layer.
  When we start caring about timbre / prosody / emotion ("质感感知"), we
  should expose middle layers or a learnable weighted sum across layers.

Projector: 2-layer MLP (in_dim → out_dim → out_dim) with GELU and the
nanochat Linear class so master weights stay fp32 while forward runs in
the activation dtype (bf16). fc2 is zero-initialized so the model starts
ignoring audio entirely, which gives a clean baseline before any training
signal flows through (audio path is opt-out by default, opt-in by
training).
2026-05-05 22:39:05 +01:00
fam d760915daa omni: gpt.forward — optional audio_features for soft-token prepend
W1 needs the GPT to consume Whisper-projector outputs as a prefix of "soft
tokens" sitting in front of the text token embeddings (LLaVA-style). The
change is intentionally minimal:

- forward() takes a new keyword-only arg `audio_features` of shape
  (B, T_a, n_embd). They must already be projected to n_embd by the caller
  (Projector lives in nanochat.audio, kept out of GPT itself).
- The audio rows are normed (matches the post-wte norm convention) and
  concatenated *after* smear so smear stays a strictly text-side op (its
  prev-token semantics aren't defined for soft tokens, and revisiting that
  belongs to a later phase).
- Rotary embeddings are re-sliced for T_a + T_text. Audio gets positions
  0..T_a-1, text 0-shifts to T_a..T_full-1. The 10× over-allocated rotary
  cache in __init__ already covers this.
- value_embeds lookup uses an idx padded with 0 for audio positions. They
  feed the v residual but the gate (`ve_gate`) is input-dependent and will
  learn to suppress the dummy rows; for W1 smoke this is fine.
- targets are auto-padded with -1 (ignore_index) over audio positions so
  the LM is only graded on text predictions.

Not yet supported: audio_features with kv_cache. The KV-cache path is a
prefill+decode protocol that would need its own audio-aware semantics; W1
runs train-style forwards only, so we just assert.
2026-05-05 22:38:49 +01:00
fam b585e07dc2 omni: CI smoke + docs + README preamble
- .gitea/workflows/smoke.yml: gitea CI on ailab gpu runner (manual git
  clone since actions/checkout@v4 mis-resolves subpath gitea); injects
  WANDB_API_KEY + CI_RUN_TAG=smoke-$run_number
- scripts/smoke.sh: in-place smoke (uv sync + 1 shard + tokenizer +
  d=6 50-step base_train); idempotent cache at /data/nanochat-smoke/
- doc/research_feasibility.md: voice-first multimodal feasibility study (mochi)
- doc/todo.md: phase-by-phase roadmap (W1 Whisper smoke → W4 MVP)
- README.md: omni preamble pointing at upstream nanochat README
- .gitignore: exclude .claude/ runtime files
2026-05-05 22:21:31 +01:00
fam 7939990181 patch: CN mirrors for pytorch-wheels and HF datasets
- pyproject.toml + uv.lock: pytorch-cu128/cpu indexes → mirror.sjtu.edu.cn
  (aliyun lacks 2.9.1, sjtu has it)
- nanochat/dataset.py: climbmix BASE_URL → hf-mirror.com

For ailab (CN, RTX 5090) where direct pytorch.org and huggingface.co
are unreachable. Override at uv-sync time with UV_DEFAULT_INDEX env.
2026-05-05 22:21:21 +01:00