diff --git a/PROGRESS.md b/PROGRESS.md index daaf27b..13ddec6 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -14,7 +14,7 @@ | 表示、位置与残差高速公路 | 完成首版 | 81% | 真实 hidden-state / norm traces、长上下文位置外推与深层稳定性消融 | | Scaling Laws | 完成首版 | 74% | 真实拟合复现、置信区间与更多模型族对照 | | 数据工程与预训练配方 | 完成首版 | 73% | FineWeb / DCLM 逐图精读、真实去重误伤与 mixture traces | -| DeepSeek 专题 | 三轮实证进行中 | 91% | SM90 FlashMLA kernel、完整 27 层、长度匹配对照、FP8/pipeline 与 R1-like RL 复现 | +| DeepSeek 专题 | 三轮实证进行中 | 92% | SM90 FlashMLA kernel、完整 27 层、tokenization 扰动、FP8/pipeline 与 R1-like RL 复现 | | 指令微调与人类偏好 | 完成首版 | 75% | 真实偏好分歧、RM 长度偏置与 PPO/DPO 小模型复现 | | 推理与测试时扩展 | 完成首版 | 76% | 真实模型采样曲线、PRM 案例与逐篇图表精读 | | 工具使用与长程 Agent | 完成首版 | 74% | 真实环境 traces、cross-harness 对照、Agent RL 曲线与安全案例 | @@ -184,11 +184,16 @@ - [x] 统计单位从 token 修正为 prompt:域内有放回重采样 2,000 次,固定 seed `20260729`,同时报告 token-weighted / prompt-balanced 的 CV、Gini、effective experts、top share、64 专家份额与两两 JSD 95% percentile 区间。 - [x] 多域路由正式运行与独立重跑 byte-exact:两份 1.6 MiB JSON SHA-256 均为 `4678a1d1…a09e4`;六联交互实验可切换层与聚合口径,并永久注明不是训练分布、线上流量、专家语义或显著性检验。 - [x] DeepSeek 多域路由版本以源提交 `5bcfd58`、不可变镜像 `20260729T073342Z-5bcfd58` 发布;OCI digest `sha256:dd8bcbce…33abd4`,NAS / VPS / NPM / DNS / TLS / gzip / 门户与十六套生产 Chrome 回归全通过;保留 `20260729T065822Z-9ba26da` 回滚。 +- [x] 长度敏感性实验固定同一批 128 条源 prompt:四域均要求至少 24 tokens,再用同一固定 salt 选出 32 条;16-token 输入严格是 24-token 输入前缀,避免换样本后把内容差异误记成长度效应。 +- [x] RTX 5090 新增 16 / 24-token 两个等长 cohort:共 5,120 个有效 token、184,320 次真实 top-6 路由;两档独立重跑分别以完整 JSON SHA-256 `f8d437d5…aebd` / `bed54835…436` byte-exact。 +- [x] 成对 prompt bootstrap 闭环:2,000 次重采样共用同一组 prompt indices;16→24 tokens 后 24 个 layer×domain 中 22 个 CV 点估计下降、20 个区间完全低于零,最大变化为 Layer 2 Python 代码 `0.969→0.718`、prompt-balanced Δ `-0.251 [-0.283,-0.215]`。 +- [x] 中文↔代码 JSD 在六层均下降但没有消失;Layer 2 `0.091→0.065`、Δ `-0.026 [-0.036,-0.018]`。结论限定为固定前缀长度敏感性,不推出因果内容效应、专家语义或训练/线上总体。 +- [x] 自然长度与等长 16 / 24 三 cohort 合计 13,580 个有效 token、488,880 次真实路由;机器可读比较结果第二次生成 SHA-256 均为 `00bdc7fe…61a1`,网站加入 cohort、层、聚合口径与 paired delta 联动。 ## 正在进行 - [ ] K3 三轮下一闸门:获得真实 token hidden states、expert load 与 cache traces,解释或修订 `A_log [128]` 工件冲突,再做 Figure 3/4/5 数值重绘和独立小模型复现。 -- [ ] DeepSeek 三轮下一闸门:在官方支持的 SM90 环境执行 FlashMLA 优化 kernel;扩到完整 27 层并做长度/tokenization 匹配对照,再推进 FP8 / pipeline traces 与 R1-like RL 小模型复现。 +- [ ] DeepSeek 三轮下一闸门:在官方支持的 SM90 环境执行 FlashMLA 优化 kernel;扩到完整 27 层并补 tokenizer / prompt-template 扰动对照,再推进 FP8 / pipeline traces 与 R1-like RL 小模型复现。 - [ ] 表示、位置与残差二轮:真实 hidden-state / norm traces、长上下文位置外推复现与 mHC / AttnRes 深层稳定性消融。 - [ ] 评测安全二轮:真实 cross-harness / pass@k 复跑、Judge 元评测、动态污染与过拒案例。 - [ ] 推理服务二轮:真实 GPU kernel / workload traces、功耗与成本、跨 vLLM / SGLang / TensorRT-LLM 复现。 @@ -315,6 +320,9 @@ | 2026-07-29 | 路由区间以 prompt 而非 token 为抽样单位 | 同 prompt token 有共同前缀与主题,不能伪装成独立样本;域内 2,000 次 bootstrap,同时保留 token 加权与 prompt 等权 | | 2026-07-29 | 路由分布差异与 expert semantics 永久分开 | Layer 4 中文、Layer 5/6 数学的集中度与域间 JSD 只描述 128-prompt 探针;不命名 expert,不冒充训练/线上总体或显著性检验 | | 2026-07-29 | DeepSeek 多域路由版本以 `20260729T073342Z-5bcfd58` 发布 | OCI digest `sha256:dd8bcbce…33abd4`;复用 `12010→8080`、NPM 31 / cert 41、门户 order 180;HTML gzip 与十六套生产 Chrome 回归通过,保留 `20260729T065822Z-9ba26da` 回滚 | +| 2026-07-29 | 取消 32-token 长度对照,改用同源 prompt 的 16→24-token 成对设计 | TNEWS 仅 105/10,000 样本达到 32 tokens,强行统一 32 会选中约 1% 极端长尾;24-token eligibility 仍有 1,609 条中文候选 | +| 2026-07-29 | 长度效应必须用 paired prompt bootstrap | 16-token 输入是 24-token 输入前缀,2,000 次重采样共用 prompt indices;区间描述固定 cohort 的敏感性,不升级为内容因果或总体显著性 | +| 2026-07-29 | 自然长度、matched-16 与 matched-24 永久分开呈现 | 自然 cohort 回答“本批原始样本如何路由”;matched cohort 回答“同一前缀多看 8 tokens 后如何变化”,不能互相替代 | | 2026-07-29 | K3 二轮按 32 张对象账与完整报告顺序重建 | total/active、2.5×、KDA state、深度来源、专家路由、视觉目标、轨迹、缓存与评测协议不再压成一页组件摘要 | | 2026-07-29 | K3 原生视觉事实回到 §2.4 / §3.3 核验 | 删除“先冻结语言模型再解冻”旧表述;明确 MoonViT-V2 从头训练,视觉/文本从开始共同 NTP | | 2026-07-29 | K3 Figure 1–16 / Table 1–5 全部建立课程视觉契约 | 每张图同时写支持范围与不可外推项;作者报告、论文、推导与 toy model 使用 R/P/D/T 标签 | diff --git a/README.md b/README.md index b74a4c1..863d33e 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,14 @@ MLA/HF eager cache shapes 与 `31/31` exact 独立复跑;进一步用真实 la naive/absorb 路径,实际写入 576 元素 latent cache,并以 FP32 将两种结合顺序的最大误差压到 `1.19e-7`;再以 WikiText-2、TNEWS、HumanEval、GSM8K 各 32 条固定样本执行 304,560 次 真实 top-6 路由,用 2,000 次 prompt-level 分层 bootstrap 同报点估计与 95% 区间,独立重跑 -整份 JSON byte-exact。FlashMLA 的 SM90/SM100 官方支持矩阵与本机 SM120 边界单独记账。详见 +整份 JSON byte-exact。随后又对同一批 128 条、源长度至少 24 tokens 的 prompt 执行 +16 / 24-token 嵌套前缀对照,新增 184,320 次真实路由;paired bootstrap 显示延长前缀通常 +降低 CV,尤其 Layer 2 代码为 `−0.251 [−0.283, −0.215]`,但六层中文↔代码 JSD 仍未消失。 +当前累计 488,880 次公开语料路由。FlashMLA 的 SM90/SM100 官方支持矩阵与本机 SM120 边界单独记账。详见 [DEEPSEEK_V2_LITE_TRACE.md](./research/DEEPSEEK_V2_LITE_TRACE.md) 与 [DEEPSEEK_MLA_ABSORB_AUDIT.md](./research/DEEPSEEK_MLA_ABSORB_AUDIT.md)、 -[DEEPSEEK_ROUTING_CORPUS_AUDIT.md](./research/DEEPSEEK_ROUTING_CORPUS_AUDIT.md)。 +[DEEPSEEK_ROUTING_CORPUS_AUDIT.md](./research/DEEPSEEK_ROUTING_CORPUS_AUDIT.md)、 +[DEEPSEEK_ROUTING_LENGTH_CONTROL_AUDIT.md](./research/DEEPSEEK_ROUTING_LENGTH_CONTROL_AUDIT.md)。 其余专题按进度账本持续扩建。 ## 本地开发 diff --git a/experiments/deepseek/README.md b/experiments/deepseek/README.md index cd040ac..0ceca53 100644 --- a/experiments/deepseek/README.md +++ b/experiments/deepseek/README.md @@ -100,3 +100,56 @@ The committed independent rerun is byte-exact. Both JSON files have SHA-256: See `research/DEEPSEEK_ROUTING_CORPUS_AUDIT.md` for corpus revisions and hashes, metric definitions, interval semantics, results, and claim boundaries. + +## Paired length-control cohort + +The corpus runner can also select one fixed cohort by untruncated source length +and execute nested prefixes. The committed 16-token and 24-token traces use the +same 128 source prompts, all selected from records with at least 24 DeepSeek +tokens: + +```bash +common_args=( + --per-domain 32 + --batch-size 16 + --bootstrap 2000 + --sample-salt llm-atlas-deepseek-routing-length-control-v1 + --eligibility-min-tokens 24 +) + +python -B experiments/deepseek/v2_lite_routing_corpus.py \ + ...source arguments... \ + "${common_args[@]}" \ + --max-tokens 16 \ + --output src/data/deepseek-v2-lite-routing-matched16.json + +python -B experiments/deepseek/v2_lite_routing_corpus.py \ + ...source arguments... \ + "${common_args[@]}" \ + --max-tokens 24 \ + --output src/data/deepseek-v2-lite-routing-matched24.json +``` + +The two real traces add 184,320 top-6 route selections. Their independent +reruns are byte-exact: + +```text +matched-16 f8d437d5379ffb41ac8dca5a8e97c0f44ba10ce7b63f95d7be0b7c88ac0baebd +matched-24 bed54835ad243ca2ab46bf9574e53137e6c2c0e19267f581719b5f2f65546436 +``` + +Use the paired comparison runner to resample identical prompt indices in the +short and long traces: + +```bash +python -B experiments/deepseek/compare_routing_length_control.py \ + --short src/data/deepseek-v2-lite-routing-matched16.json \ + --long src/data/deepseek-v2-lite-routing-matched24.json \ + --output src/data/deepseek-v2-lite-routing-length-sensitivity.json \ + --bootstrap 2000 \ + --seed 20260729 +``` + +See `research/DEEPSEEK_ROUTING_LENGTH_CONTROL_AUDIT.md` for the sampling bias +audit, paired CV/JSD deltas, total-variation accounting, and interpretation +boundaries. diff --git a/experiments/deepseek/compare_routing_length_control.py b/experiments/deepseek/compare_routing_length_control.py new file mode 100644 index 0000000..7f79049 --- /dev/null +++ b/experiments/deepseek/compare_routing_length_control.py @@ -0,0 +1,375 @@ +#!/usr/bin/env python3 +"""Compare two routing traces that use the same prompts at different lengths. + +The comparison is paired at prompt level: every bootstrap replicate draws the +same source-prompt indices for the short and long trace. This isolates prefix +length within the fixed matched cohort more cleanly than comparing independent +confidence intervals. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import math +from datetime import datetime, timezone +from itertools import combinations +from pathlib import Path +from typing import Any + +import numpy as np + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--short", type=Path, required=True) + parser.add_argument("--long", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--bootstrap", type=int, default=2000) + parser.add_argument("--seed", type=int, default=20260729) + parser.add_argument("--captured-at", default=None) + return parser.parse_args() + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for block in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def scoped_seed(seed: int, scope: str) -> int: + payload = f"{seed}:{scope}".encode() + return int.from_bytes(hashlib.sha256(payload).digest()[:8], "big") + + +def interval(values: np.ndarray) -> list[float]: + low, high = np.quantile(values, [0.025, 0.975], axis=0) + return [float(low), float(high)] + + +def distributions(loads: np.ndarray, mode: str, sampled: np.ndarray | None = None) -> np.ndarray: + values = loads if sampled is None else loads[sampled] + if values.ndim == 2: + values = values[None, ...] + if mode == "token_weighted": + result = values.sum(axis=1, dtype=np.float64) + elif mode == "prompt_balanced": + normalized = values / values.sum(axis=2, keepdims=True) + result = normalized.mean(axis=1, dtype=np.float64) + else: + raise ValueError(mode) + return result / result.sum(axis=1, keepdims=True) + + +def metric_vector(values: np.ndarray) -> dict[str, np.ndarray]: + values = np.atleast_2d(values).astype(np.float64, copy=False) + expert_count = values.shape[1] + mean = values.mean(axis=1) + ordered = np.sort(values, axis=1) + indices = np.arange(1, expert_count + 1, dtype=np.float64) + gini = ( + ((2 * indices - expert_count - 1) * ordered).sum(axis=1) + / (expert_count * ordered.sum(axis=1)) + ) + logs = np.zeros_like(values) + np.log(values, out=logs, where=values > 0) + entropy = -(values * logs).sum(axis=1) + return { + "cv": values.std(axis=1) / mean, + "gini": gini, + "effective_experts": np.exp(entropy), + "top_expert_share": values.max(axis=1), + "used_experts": (values > 0).sum(axis=1).astype(np.float64), + } + + +def js_divergence(left: np.ndarray, right: np.ndarray) -> np.ndarray: + left = np.atleast_2d(left).astype(np.float64, copy=False) + right = np.atleast_2d(right).astype(np.float64, copy=False) + midpoint = (left + right) / 2 + left_ratio = np.ones_like(left) + right_ratio = np.ones_like(right) + np.divide(left, midpoint, out=left_ratio, where=left > 0) + np.divide(right, midpoint, out=right_ratio, where=right > 0) + left_log = np.zeros_like(left) + right_log = np.zeros_like(right) + np.log(left_ratio, out=left_log, where=left > 0) + np.log(right_ratio, out=right_log, where=right > 0) + return 0.5 * ( + (left * left_log).sum(axis=1) + + (right * right_log).sum(axis=1) + ) + + +def prompt_identity(data: dict[str, Any]) -> list[dict[str, Any]]: + return [ + { + key: row[key] + for key in ( + "id", + "domain", + "within_domain_index", + "selection_rank", + "text_sha256", + "characters", + "source_tokens", + ) + } + for row in data["corpus_contract"]["selected"] + ] + + +def load_by_domain( + layer: dict[str, Any], + domains: list[str], +) -> tuple[dict[str, np.ndarray], dict[str, list[str]]]: + loads: dict[str, np.ndarray] = {} + ids: dict[str, list[str]] = {} + for domain in domains: + rows = sorted( + (row for row in layer["prompts"] if row["domain"] == domain), + key=lambda row: row["id"], + ) + ids[domain] = [row["id"] for row in rows] + loads[domain] = np.asarray([row["load"] for row in rows], dtype=np.int64) + return loads, ids + + +def compare_domain( + short: np.ndarray, + long: np.ndarray, + mode: str, + bootstrap: int, + seed: int, + scope: str, +) -> dict[str, Any]: + if short.shape != long.shape: + raise ValueError(f"paired loads differ in shape: {short.shape} vs {long.shape}") + prompt_count = short.shape[0] + rng = np.random.default_rng(scoped_seed(seed, scope)) + sampled = rng.integers( + 0, + prompt_count, + size=(bootstrap, prompt_count), + endpoint=False, + ) + short_point = distributions(short, mode)[0] + long_point = distributions(long, mode)[0] + short_boot = distributions(short, mode, sampled) + long_boot = distributions(long, mode, sampled) + short_metrics = metric_vector(short_point) + long_metrics = metric_vector(long_point) + short_boot_metrics = metric_vector(short_boot) + long_boot_metrics = metric_vector(long_boot) + metrics = {} + for name in short_metrics: + delta = long_boot_metrics[name] - short_boot_metrics[name] + metrics[name] = { + "short": float(short_metrics[name][0]), + "long": float(long_metrics[name][0]), + "delta_long_minus_short": float( + long_metrics[name][0] - short_metrics[name][0] + ), + "delta_ci95": interval(delta), + } + total_variation_boot = 0.5 * np.abs(long_boot - short_boot).sum(axis=1) + return { + "metrics": metrics, + "total_variation": { + "point": float(0.5 * np.abs(long_point - short_point).sum()), + "ci95": interval(total_variation_boot), + }, + } + + +def compare_pair( + short_left: np.ndarray, + short_right: np.ndarray, + long_left: np.ndarray, + long_right: np.ndarray, + mode: str, + bootstrap: int, + seed: int, + scope: str, +) -> dict[str, Any]: + rng = np.random.default_rng(scoped_seed(seed, scope)) + left_indices = rng.integers( + 0, + short_left.shape[0], + size=(bootstrap, short_left.shape[0]), + endpoint=False, + ) + right_indices = rng.integers( + 0, + short_right.shape[0], + size=(bootstrap, short_right.shape[0]), + endpoint=False, + ) + short_point = js_divergence( + distributions(short_left, mode), + distributions(short_right, mode), + )[0] + long_point = js_divergence( + distributions(long_left, mode), + distributions(long_right, mode), + )[0] + short_boot = js_divergence( + distributions(short_left, mode, left_indices), + distributions(short_right, mode, right_indices), + ) + long_boot = js_divergence( + distributions(long_left, mode, left_indices), + distributions(long_right, mode, right_indices), + ) + return { + "short": float(short_point), + "long": float(long_point), + "delta_long_minus_short": float(long_point - short_point), + "delta_ci95": interval(long_boot - short_boot), + "unit": "nats", + "upper_bound": math.log(2), + } + + +def main() -> None: + args = parse_args() + if args.bootstrap < 100: + raise ValueError("bootstrap replicates must be at least 100") + short = json.loads(args.short.read_text()) + long = json.loads(args.long.read_text()) + domains = short["corpus_contract"]["domains"] + if domains != long["corpus_contract"]["domains"]: + raise ValueError("domain order differs") + if prompt_identity(short) != prompt_identity(long): + raise ValueError("short and long traces do not use the same source prompts") + for key in ("model", "corpora"): + if short["provenance"][key] != long["provenance"][key]: + raise ValueError(f"provenance differs: {key}") + if short["configuration"] != long["configuration"]: + raise ValueError("model configuration differs") + if short["corpus_contract"]["sample_salt"] != long["corpus_contract"]["sample_salt"]: + raise ValueError("sample salt differs") + + short_tokens = short["corpus_contract"]["max_tokens"] + long_tokens = long["corpus_contract"]["max_tokens"] + if short_tokens >= long_tokens: + raise ValueError("short max tokens must be less than long max tokens") + expected_short = len(domains) * short["corpus_contract"]["per_domain"] * short_tokens + expected_long = len(domains) * long["corpus_contract"]["per_domain"] * long_tokens + if short["inference_contract"]["valid_tokens"] != expected_short: + raise ValueError("short trace is not exactly length-controlled") + if long["inference_contract"]["valid_tokens"] != expected_long: + raise ValueError("long trace is not exactly length-controlled") + + layers = [] + for short_layer, long_layer in zip( + short["layers"][1:], + long["layers"][1:], + strict=True, + ): + if short_layer["layer"] != long_layer["layer"]: + raise ValueError("layer order differs") + layer_index = short_layer["layer"] + short_loads, short_ids = load_by_domain(short_layer, domains) + long_loads, long_ids = load_by_domain(long_layer, domains) + if short_ids != long_ids: + raise ValueError(f"prompt IDs differ at layer {layer_index}") + modes = {} + for mode in ("token_weighted", "prompt_balanced"): + domain_results = { + domain: compare_domain( + short_loads[domain], + long_loads[domain], + mode, + args.bootstrap, + args.seed, + f"layer={layer_index}|mode={mode}|domain={domain}", + ) + for domain in domains + } + pairs = [] + for left, right in combinations(domains, 2): + pairs.append( + { + "left": left, + "right": right, + "js_divergence": compare_pair( + short_loads[left], + short_loads[right], + long_loads[left], + long_loads[right], + mode, + args.bootstrap, + args.seed, + f"layer={layer_index}|mode={mode}|pair={left}:{right}", + ), + } + ) + modes[mode] = {"domains": domain_results, "pairs": pairs} + layers.append({"layer": layer_index, "modes": modes}) + + captured_at = args.captured_at or datetime.now(timezone.utc).isoformat() + result = { + "schema_version": 1, + "captured_at": captured_at, + "evidence_identity": "S / paired prompt-level bootstrap over two real routing traces", + "boundary": { + "same_source_prompts": True, + "nested_prefixes": True, + "causal_claim_beyond_fixed_cohort": False, + "expert_semantics_inferred": False, + "hypothesis_test": False, + }, + "inputs": { + "short": { + "path": args.short.name, + "sha256": sha256(args.short), + "tokens_per_prompt": short_tokens, + "valid_tokens": short["inference_contract"]["valid_tokens"], + "routes": short["inference_contract"]["routes_per_moe_layer"] * 6, + }, + "long": { + "path": args.long.name, + "sha256": sha256(args.long), + "tokens_per_prompt": long_tokens, + "valid_tokens": long["inference_contract"]["valid_tokens"], + "routes": long["inference_contract"]["routes_per_moe_layer"] * 6, + }, + }, + "paired_contract": { + "domains": domains, + "prompts_per_domain": short["corpus_contract"]["per_domain"], + "sample_salt": short["corpus_contract"]["sample_salt"], + "eligibility_min_tokens": short["corpus_contract"][ + "uniform_eligibility_min_tokens" + ], + "resampling_unit": "source prompt", + "same_indices_for_short_and_long": True, + "replicates": args.bootstrap, + "seed": args.seed, + "interval": "95% percentile interval of paired long-minus-short deltas", + "modes": ["token_weighted", "prompt_balanced"], + }, + "layers": layers, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n") + print( + json.dumps( + { + "output": str(args.output), + "layers": len(layers), + "short_tokens": short_tokens, + "long_tokens": long_tokens, + "bootstrap": args.bootstrap, + }, + ensure_ascii=False, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/experiments/deepseek/v2_lite_routing_corpus.py b/experiments/deepseek/v2_lite_routing_corpus.py index b6a4a8c..ed48061 100644 --- a/experiments/deepseek/v2_lite_routing_corpus.py +++ b/experiments/deepseek/v2_lite_routing_corpus.py @@ -69,6 +69,8 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--layers", type=int, default=7) parser.add_argument("--bootstrap", type=int, default=2000) parser.add_argument("--seed", type=int, default=20260729) + parser.add_argument("--sample-salt", default=SAMPLE_SALT) + parser.add_argument("--eligibility-min-tokens", type=int, default=None) parser.add_argument("--device", default="cuda") parser.add_argument("--captured-at", default=None) return parser.parse_args() @@ -175,6 +177,8 @@ def select_corpus( candidates: dict[str, list[dict[str, str]]], per_domain: int, max_tokens: int, + sample_salt: str, + eligibility_min_tokens: int | None, ) -> tuple[list[dict[str, Any]], dict[str, dict[str, int]]]: selected: list[dict[str, Any]] = [] counts: dict[str, dict[str, int]] = {} @@ -183,21 +187,31 @@ def select_corpus( texts = [row["text"] for row in rows] encoded: list[list[int]] = [] for start in range(0, len(texts), 512): - result = tokenizer( - texts[start : start + 512], - add_special_tokens=True, - truncation=True, - max_length=max_tokens, - padding=False, - ) + tokenizer_kwargs: dict[str, Any] = { + "add_special_tokens": True, + "padding": False, + } + if eligibility_min_tokens is None: + tokenizer_kwargs.update( + { + "truncation": True, + "max_length": max_tokens, + } + ) + else: + tokenizer_kwargs["truncation"] = False + result = tokenizer(texts[start : start + 512], **tokenizer_kwargs) encoded.extend(result.input_ids) eligible = [] for row, token_ids in zip(rows, encoded, strict=True): - if len(token_ids) < MIN_TOKENS[domain]: + minimum = eligibility_min_tokens or MIN_TOKENS[domain] + if len(token_ids) < minimum: continue + source_tokens = len(token_ids) + token_ids = token_ids[:max_tokens] rank = hashlib.sha256( - f"{SAMPLE_SALT}|{domain}|{row['id']}".encode() + f"{sample_salt}|{domain}|{row['id']}".encode() ).hexdigest() eligible.append( { @@ -209,6 +223,11 @@ def select_corpus( "characters": len(row["text"]), "token_ids": token_ids, "tokens": len(token_ids), + **( + {"source_tokens": source_tokens} + if eligibility_min_tokens is not None + else {} + ), "selection_rank": rank, } ) @@ -509,6 +528,10 @@ def main() -> None: raise ValueError("per-domain sample must be at least two") if args.bootstrap < 100: raise ValueError("bootstrap replicates must be at least 100") + if args.eligibility_min_tokens is not None and args.eligibility_min_tokens < 2: + raise ValueError("eligibility minimum must be at least two tokens") + if not args.sample_salt.strip(): + raise ValueError("sample salt must not be empty") torch.manual_seed(args.seed) torch.cuda.manual_seed_all(args.seed) @@ -532,6 +555,8 @@ def main() -> None: candidates, args.per_domain, args.max_tokens, + args.sample_salt, + args.eligibility_min_tokens, ) batches = make_batches(samples, args.batch_size, tokenizer.pad_token_id) device = torch.device(args.device) @@ -658,6 +683,11 @@ def main() -> None: "text_sha256": sample["text_sha256"], "characters": sample["characters"], "tokens": sample["tokens"], + **( + {"source_tokens": sample["source_tokens"]} + if "source_tokens" in sample + else {} + ), } for sample in samples ] @@ -754,11 +784,23 @@ def main() -> None: "corpus_contract": { "domains": list(DOMAIN_ORDER), "domain_labels": DOMAIN_LABELS, - "sample_salt": SAMPLE_SALT, + "sample_salt": args.sample_salt, "selection": "ascending SHA256(salt|domain|source_id), then source_id", "per_domain": args.per_domain, "max_tokens": args.max_tokens, - "minimum_tokens": MIN_TOKENS, + "minimum_tokens": ( + {domain: args.eligibility_min_tokens for domain in DOMAIN_ORDER} + if args.eligibility_min_tokens is not None + else MIN_TOKENS + ), + **( + { + "uniform_eligibility_min_tokens": args.eligibility_min_tokens, + "matched_length_control": True, + } + if args.eligibility_min_tokens is not None + else {} + ), "special_tokens": True, "truncation": "right", "counts": corpus_counts, diff --git a/research/DEEPSEEK_ROUTING_LENGTH_CONTROL_AUDIT.md b/research/DEEPSEEK_ROUTING_LENGTH_CONTROL_AUDIT.md new file mode 100644 index 0000000..b23c287 --- /dev/null +++ b/research/DEEPSEEK_ROUTING_LENGTH_CONTROL_AUDIT.md @@ -0,0 +1,309 @@ +# DeepSeek-V2-Lite 路由长度控制审计 + +> 状态:第三轮真实权重执行 / 同 prompt 嵌套前缀 / paired bootstrap +> 捕获时间:2026-07-29T07:41:00+00:00 至 07:43:00+00:00 +> 证据身份:X(两组真实 forward)+ S(配对统计) +> 前置账本:`DEEPSEEK_ROUTING_CORPUS_AUDIT.md` + +## 1. 为什么必须补这轮 + +第一版四域公开语料探针有意保留各来源的自然长度: + +| 域 | 32 条 prompt 的有效 token | +| --- | ---: | +| 英文百科 | 2,882 | +| 中文新闻 | 573 | +| Python 代码 | 2,983 | +| 小学数学 | 2,022 | + +虽然默认的 `prompt_balanced` 口径会先归一每条 prompt,再让每条 prompt 等权, +它仍不能消除以下混杂: + +- 短 prompt 主要覆盖序列起点; +- 代码前缀常集中出现 `def`、函数名、类型和 docstring 开头; +- 长 prompt 让后续自然语言、变量与推理结构进入 router; +- position、共享前缀和内容范围会一起变化。 + +因此上一轮的“Layer 4 中文更集中”“Layer 5/6 数学更集中”只能先当描述事实,不能 +直接解释成 domain semantics。 + +## 2. 为什么没有选择 32-token 对照 + +对四域全部候选用官方 DeepSeek-V2-Lite tokenizer 计算未截断长度: + +| 域 | 候选 | ≥32 tokens | ≥48 tokens | +| --- | ---: | ---: | ---: | +| 英文百科 | 1,841 | 1,623 | 1,525 | +| 中文新闻 | 10,000 | **105** | **8** | +| Python 代码 | 164 | 164 | 162 | +| 小学数学 | 1,319 | 1,279 | 940 | + +如果强行要求 32 tokens,中文域只能从 TNEWS 最长约 1% 的尾部取样。长度虽然相同, +“中文新闻”本身却会变成异常长标题/句子的特殊子群。 + +本轮选择 24 tokens 作为 eligibility threshold。TNEWS 仍有 1,609 条合格候选, +可以固定选出 32 条,同时保留足够的共同上下文。 + +## 3. 对照合同 + +### 3.1 同一批源 prompt + +固定 salt: + +```text +llm-atlas-deepseek-routing-length-control-v1 +``` + +四域都先筛选 tokenizer 长度至少 24 的候选,再按: + +```text +SHA256(salt | domain | source_id), then source_id +``` + +取前 32 条。 + +两档实验的 source ID、selection rank、原文 SHA-256、字符数和未截断 token 数完全一致。 +唯一实验变化是模型实际看到的嵌套前缀: + +```text +short: tokens [0:16] +long: tokens [0:24] +``` + +所以 16-token 输入严格是同一条 24-token 输入的前缀,而不是另选一批样本。 + +### 3.2 样本池 + +| 域 | 文本过滤后候选 | ≥24 tokens | 选中 | 选中原始长度 min / mean / max | +| --- | ---: | ---: | ---: | ---: | +| 英文百科 | 1,841 | 1,655 | 32 | 26 / 152.0 / 423 | +| 中文新闻 | 10,000 | 1,609 | 32 | 24 / 26.7 / 48 | +| Python 代码 | 164 | 164 | 32 | 43 / 152.9 / 325 | +| 小学数学 | 1,319 | 1,319 | 32 | 35 / 62.0 / 139 | + +这仍不是四个来源的无偏随机样本。特别是中文只代表“至少 24 tokens 的 TNEWS 子群”。 +它是长度控制 cohort,不应取代自然长度 cohort。 + +## 4. 真实执行量 + +模型、revision、第一 checkpoint shard、BF16、layer 0–6、batch size 16 与上一轮相同。 + +| cohort | prompt | tokens / prompt | 有效 token | routes / MoE layer | 6 层 routes | +| --- | ---: | ---: | ---: | ---: | ---: | +| matched-16 | 128 | 16 | 2,048 | 12,288 | 73,728 | +| matched-24 | 128 | 24 | 3,072 | 18,432 | 110,592 | +| 合计 | 256 次输入 | — | 5,120 | — | **184,320** | + +加上自然长度 cohort 的 304,560 次,本专题目前累计保存 **488,880 次**真实 +top-6 routed-expert 选择。 + +每条 prompt、每层继续满足: + +```text +routes = valid_tokens × 6 +``` + +padding positions 没有进入统计。 + +## 5. 为什么要做 paired bootstrap + +分别看两档 95% 区间并不等于直接估计变化。因为两档使用相同 source prompts,最有效的 +比较方式是: + +1. 每个 domain 内生成一组 32 个有放回 prompt indices; +2. short 与 long 同时使用这组 indices; +3. 分别计算 metric; +4. 保存 `long - short`; +5. 重复 2,000 次,取 delta 的 2.5% 与 97.5% 分位点。 + +固定: + +```text +replicates = 2000 +seed = 20260729 +resampling unit = source prompt +same sampled indices for short and long = true +``` + +这样 prompt 本身的难度/格式差异会在配对差中部分抵消。它仍不是随机分配实验,不能 +外推到 cohort 之外。 + +## 6. 主要结果:延长前缀通常让路由更平 + +以下使用 `prompt_balanced`。表中是: + +```text +CV(16 tokens) → CV(24 tokens), Δ = long - short [paired 95% CI] +``` + +### Layer 1 + +| 域 | CV 变化 | +| --- | ---: | +| 英文百科 | 0.841 → 0.699, **−0.142** [−0.176, −0.113] | +| 中文新闻 | 0.882 → 0.801, **−0.081** [−0.114, −0.050] | +| Python 代码 | 1.048 → 0.934, **−0.114** [−0.138, −0.092] | +| 小学数学 | 0.929 → 0.800, **−0.129** [−0.160, −0.100] | + +### Layer 2 + +| 域 | CV 变化 | +| --- | ---: | +| 英文百科 | 0.610 → 0.527, **−0.084** [−0.116, −0.059] | +| 中文新闻 | 0.499 → 0.390, **−0.109** [−0.135, −0.081] | +| Python 代码 | 0.969 → 0.718, **−0.251** [−0.283, −0.215] | +| 小学数学 | 0.552 → 0.480, **−0.072** [−0.102, −0.051] | + +### Layer 3 + +| 域 | CV 变化 | +| --- | ---: | +| 英文百科 | 0.542 → 0.513, −0.030 [−0.064, −0.005] | +| 中文新闻 | 0.520 → 0.462, **−0.059** [−0.092, −0.029] | +| Python 代码 | 0.989 → 0.862, **−0.127** [−0.164, −0.089] | +| 小学数学 | 0.593 → 0.536, **−0.057** [−0.097, −0.025] | + +### Layer 4 + +| 域 | CV 变化 | +| --- | ---: | +| 英文百科 | 0.672 → 0.609, **−0.063** [−0.098, −0.033] | +| 中文新闻 | 0.995 → 0.898, **−0.097** [−0.124, −0.069] | +| Python 代码 | 1.135 → 0.979, **−0.156** [−0.201, −0.111] | +| 小学数学 | 0.663 → 0.594, **−0.070** [−0.108, −0.036] | + +### Layer 5 + +| 域 | CV 变化 | +| --- | ---: | +| 英文百科 | 0.597 → 0.547, −0.050 [−0.085, −0.023] | +| 中文新闻 | 0.613 → 0.510, **−0.103** [−0.137, −0.068] | +| Python 代码 | 0.934 → 0.778, **−0.156** [−0.194, −0.119] | +| 小学数学 | 0.651 → 0.686, +0.035 [−0.016, +0.072] | + +### Layer 6 + +| 域 | CV 变化 | +| --- | ---: | +| 英文百科 | 0.661 → 0.632, −0.028 [−0.059, −0.002] | +| 中文新闻 | 0.679 → 0.582, **−0.096** [−0.118, −0.072] | +| Python 代码 | 0.908 → 0.810, **−0.099** [−0.127, −0.071] | +| 小学数学 | 0.676 → 0.687, +0.011 [−0.038, +0.052] | + +24 个 layer×domain 比较中: + +- 22 个点估计为负; +- 20 个 paired 95% interval 完全低于 0; +- 只有 Layer 5/6 数学的点估计略为正,区间都跨 0。 + +最强变化是 Layer 2 代码:增加 8 个后续 tokens 后,CV 下降 0.251。一个合理但尚未 +被因果证明的解释是,16-token 代码前缀共享更多 Python 函数签名/样板结构;继续读到 +docstring 和任务内容后,路由分散到更多 experts。 + +正确语气是“固定 cohort 的短代码前缀更集中”,不是“代码越长一定越均衡”。 + +## 7. 中文↔代码距离没有消失,但随长度下降 + +在两档 matched cohort 中,六层最大的 JSD pair 都是中文新闻↔代码。 + +| layer | JSD@16 | JSD@24 | paired Δ | 95% CI | +| ---: | ---: | ---: | ---: | ---: | +| 1 | 0.0946 | 0.0787 | **−0.0158** | [−0.0259, −0.0088] | +| 2 | 0.0906 | 0.0650 | **−0.0256** | [−0.0359, −0.0182] | +| 3 | 0.1287 | 0.1106 | **−0.0182** | [−0.0289, −0.0093] | +| 4 | 0.1862 | 0.1584 | **−0.0278** | [−0.0390, −0.0195] | +| 5 | 0.1290 | 0.1118 | **−0.0172** | [−0.0279, −0.0090] | +| 6 | 0.1487 | 0.1363 | **−0.0124** | [−0.0240, −0.0040] | + +这给出两个同时成立的事实: + +1. 长度/前缀范围确实影响域间距离;从 16 延长到 24 后,六层距离都下降; +2. 长度控制没有抹掉中文↔代码差异;24-token 下六层仍为 0.065–0.158。 + +所以不能把上一轮差异全部归因于长度,也不能把剩余差异全部归因于语义。tokenizer、 +字符集、代码格式、共享样板和内容都仍在一起变化。 + +## 8. 自然长度 cohort 应怎样重新解读 + +### Layer 4 中文集中仍然存在 + +自然长度中文 Layer 4 CV 为 0.910;matched-24 为 0.898。两个 cohort 的点估计接近, +说明“Layer 4 中文较集中”不是只在极短标题上才出现。 + +但两组 source prompts 不同,因此这个接近不能写成配对因果结论。 + +### Layer 5/6 数学集中对 cohort 很敏感 + +| layer | 自然长度数学 CV | matched-24 数学 CV | +| ---: | ---: | ---: | +| 5 | 0.848 | 0.686 | +| 6 | 0.770 | 0.687 | + +自然长度与 matched cohort 同时改变了样本身份和截断位置,所以不能说“差值全部由长度 +造成”。它足以发出一个敏感性警报: + +> Layer 5/6 数学集中现象不应在没有更多长度分层和同样本长上下文实验前被当成稳定 +> domain signature。 + +## 9. Total variation:新增 8 tokens 真的改变了分布 + +每域同层的 16/24 aggregate distributions 还计算: + +```text +TV(P16, P24) = 1/2 Σ_e |P16(e) - P24(e)| +``` + +prompt-balanced 点估计范围约 0.049–0.116。代码域通常最大: + +- Layer 2 code TV = 0.116; +- Layer 4 code TV = 0.114; +- Layer 6 code TV = 0.114。 + +这说明变化不只是 CV 公式的小幅抖动;约 10% 以上的 aggregate probability mass +需要在 expert IDs 之间重新分配,才能把短前缀分布变成长前缀分布。 + +TV 仍然不命名 expert,也不告诉我们输出质量。 + +## 10. 可复现性 + +真实 trace: + +| 输出 | 完整 JSON SHA-256 | 独立重跑 | +| --- | --- | --- | +| matched-16 | `f8d437d5379ffb41ac8dca5a8e97c0f44ba10ce7b63f95d7be0b7c88ac0baebd` | byte-exact | +| matched-24 | `bed54835ad243ca2ab46bf9574e53137e6c2c0e19267f581719b5f2f65546436` | byte-exact | + +配对比较: + +```text +00bdc7fe3bffba564516d4cc76567b1144e0fcaa3b304eb80e7395be3dcf61a1 +``` + +比较脚本独立执行两次,结果 byte-exact。它验证 short/long 的 source identity、 +模型/语料 provenance、layer 顺序和 exact token contract 后才生成 delta。 + +入口: + +- `experiments/deepseek/v2_lite_routing_corpus.py` +- `experiments/deepseek/compare_routing_length_control.py` +- `src/data/deepseek-v2-lite-routing-matched16.json` +- `src/data/deepseek-v2-lite-routing-matched24.json` +- `src/data/deepseek-v2-lite-routing-length-sensitivity.json` + +## 11. 仍然不能说什么 + +- 不能把至少 24 tokens 的 TNEWS cohort 写成所有中文新闻; +- 不能把 matched-16/24 写成完整上下文行为; +- 不能把前缀长度的影响写成 position 的单独因果效应; +- 不能把中文↔代码 JSD 写成 expert specialization 的直接证明; +- 不能用 paired interval 冒充随机实验或线上总体区间; +- 不能把 CV 下降自动解释成模型“更好”; +- 不能把早期六个 MoE 层外推到完整 27 层。 + +## 12. 下一步 + +1. 下载并执行完整 layer 0–26,观察长度敏感性是否在中后层改变; +2. 在同一批长源文本上增加 32/48/64 token 嵌套前缀,但中文需要换成更适合长文本的公开语料; +3. 加入 tokenizer fragmentation、字符数和 unique-token ratio 协变量; +4. 对代码拆开 signature、docstring、body,不把格式位置与内容混为一谈; +5. 若要做总体推断,预注册 sample frame、主要指标和 permutation/null procedure。 diff --git a/scripts/check-deepseek-browser.mjs b/scripts/check-deepseek-browser.mjs index f95a2f2..cd8754d 100644 --- a/scripts/check-deepseek-browser.mjs +++ b/scripts/check-deepseek-browser.mjs @@ -305,21 +305,31 @@ const artifactCorpus = await evaluate(`(() => { const read = () => ({ panel: root.querySelector("[data-artifact-panel]:not([hidden])").dataset.artifactPanel, rows: root.querySelectorAll("[data-corpus-domain-rows] > div").length, + tokens: [...root.querySelectorAll("[data-corpus-domain-rows] > div > span:first-child > small")].map((node) => node.textContent.trim()), heatRows: root.querySelectorAll("[data-corpus-heatmap] > div").length, heatCells: root.querySelectorAll("[data-corpus-heatmap] > div > span").length, jsdCells: root.querySelectorAll("[data-corpus-jsd] > *").length, highest: root.querySelector("[data-corpus-highest-cv]").textContent.trim(), largest: root.querySelector("[data-corpus-largest-jsd]").textContent.trim(), heatTitle: root.querySelector("[data-corpus-heat-title]").textContent.trim(), + cohortTitle: root.querySelector("[data-corpus-cohort-title]").textContent.trim(), exact: root.querySelector(".corpus-ledger .exact b").textContent.trim(), modeNote: root.querySelector("[data-corpus-mode-note]").textContent.trim(), + deltaCards: root.querySelectorAll("[data-length-delta-grid] > article").length, + lengthJsd: root.querySelector("[data-length-jsd]").textContent.trim(), + lengthLargest: root.querySelector("[data-length-largest]").textContent.trim(), }); const layer1 = read(); root.querySelector('[data-corpus-layer="4"]').click(); const layer4 = read(); + root.querySelector('[data-corpus-cohort="matched16"]').click(); + root.querySelector('[data-corpus-layer="2"]').click(); + const matched16 = read(); + root.querySelector('[data-corpus-cohort="matched24"]').click(); + const matched24 = read(); root.querySelector('[data-corpus-mode="token_weighted"]').click(); const tokenWeighted = read(); - return { layer1, layer4, tokenWeighted }; + return { layer1, layer4, matched16, matched24, tokenWeighted }; })()`); await evaluate(`(() => { document.querySelector("[data-dsv2-lab]").scrollIntoView({ block: "start", behavior: "instant" }); @@ -327,6 +337,12 @@ await evaluate(`(() => { })()`); await pause(180); await screenshot("/tmp/llm-atlas-deepseek-corpus-desktop.png"); +await evaluate(`(() => { + document.querySelector(".length-sensitivity").scrollIntoView({ block: "start", behavior: "instant" }); + window.scrollBy(0, -82); +})()`); +await pause(120); +await screenshot("/tmp/llm-atlas-deepseek-length-sensitivity-desktop.png"); const artifactEvidence = await evaluate(`(() => { const root = document.querySelector("[data-dsv2-lab]"); @@ -399,6 +415,8 @@ const mobile = await evaluate(`(() => { tabs: root.querySelectorAll("[data-ds-tab]").length, artifactTabs: artifact.querySelectorAll("[data-artifact-tab]").length, artifactHeatCells: artifact.querySelectorAll("[data-route-heatmap] > span").length, + corpusCohorts: artifact.querySelectorAll("[data-corpus-cohort]").length, + lengthDeltaCards: artifact.querySelectorAll("[data-length-delta-grid] > article").length, offenders: [...document.querySelectorAll("body *")] .filter((node) => !node.closest(".paper-chain, .advantage-table, .precision-table, .mapping-table, [data-deepseek-lab], [data-dsv2-lab]")) .filter((node) => node.getBoundingClientRect().right > document.documentElement.clientWidth + 1) @@ -420,6 +438,12 @@ await evaluate(`(() => { })()`); await pause(180); await screenshot("/tmp/llm-atlas-deepseek-corpus-mobile.png"); +await evaluate(`(() => { + document.querySelector(".length-sensitivity").scrollIntoView({ block: "start", behavior: "instant" }); + window.scrollBy(0, -70); +})()`); +await pause(120); +await screenshot("/tmp/llm-atlas-deepseek-length-sensitivity-mobile.png"); const report = { overview, capacity, cache, codesign, rl, artifactRoute, artifactLoad, artifactCache, artifactAbsorb, artifactCorpus, artifactEvidence, home, papers, mobile, exceptions }; console.log(JSON.stringify(report, null, 2)); @@ -459,15 +483,18 @@ if (artifactAbsorb.panel !== "absorb" || artifactAbsorb.algebra !== 2 || artifac if (artifactAbsorb.metrics[0] !== "8.8889×" || artifactAbsorb.metrics[1] !== "0.00390625" || artifactAbsorb.metrics[2] !== "1.19e-7" || artifactAbsorb.metrics[3] !== "BYTE-EXACT") failures.push("absorb 数值正确性或复跑闸门异常"); if (artifactAbsorb.precisionRows !== 3 || artifactAbsorb.matrixRows !== 5 || artifactAbsorb.localUnsupported !== 4 || artifactAbsorb.executionCards !== 3 || !artifactAbsorb.boundary.includes("不是 FlashMLA 性能")) failures.push("FlashMLA SM120 边界结构异常"); if (artifactCorpus.layer1.panel !== "corpus" || artifactCorpus.layer1.rows !== 4 || artifactCorpus.layer1.heatRows !== 4 || artifactCorpus.layer1.heatCells !== 256 || artifactCorpus.layer1.jsdCells !== 25) failures.push("128 样本路由区间结构异常"); -if (!artifactCorpus.layer1.highest.includes("中文新闻 · 0.754") || !artifactCorpus.layer1.largest.includes("中文新闻 ↔ Python 代码 · 0.059") || artifactCorpus.layer1.exact !== "BYTE-EXACT") failures.push("Layer 1 多域统计或复跑闸门异常"); +if (!artifactCorpus.layer1.highest.includes("中文新闻 · 0.754") || !artifactCorpus.layer1.largest.includes("中文新闻 ↔ Python 代码 · 0.059") || artifactCorpus.layer1.exact !== "3 / 3 EXACT") failures.push("Layer 1 多域统计或三 cohort 复跑闸门异常"); if (!artifactCorpus.layer4.highest.includes("中文新闻 · 0.910") || !artifactCorpus.layer4.largest.includes("中文新闻 ↔ Python 代码 · 0.150") || !artifactCorpus.layer4.heatTitle.includes("layer 4")) failures.push("Layer 4 多域路由切换异常"); -if (!artifactCorpus.tokenWeighted.heatTitle.includes("按 token 加权") || !artifactCorpus.tokenWeighted.modeNote.includes("长 prompt 权重更高")) failures.push("公开语料聚合口径切换异常"); +if (!artifactCorpus.matched16.highest.includes("Python 代码 · 0.969") || artifactCorpus.matched16.cohortTitle !== "同样本 · 16 tokens" || !artifactCorpus.matched16.tokens.every((value) => value.includes("512 tokens"))) failures.push("16-token 同源 cohort 切换异常"); +if (!artifactCorpus.matched24.highest.includes("Python 代码 · 0.718") || artifactCorpus.matched24.cohortTitle !== "同样本 · 24 tokens" || !artifactCorpus.matched24.tokens.every((value) => value.includes("768 tokens"))) failures.push("24-token 同源 cohort 切换异常"); +if (artifactCorpus.matched24.deltaCards !== 4 || artifactCorpus.matched24.lengthLargest !== "Python 代码 · Δ -0.251" || artifactCorpus.matched24.lengthJsd !== "0.091 → 0.065 · Δ -0.026") failures.push("16→24 token 成对敏感性结论异常"); +if (!artifactCorpus.tokenWeighted.heatTitle.includes("token 加权") || !artifactCorpus.tokenWeighted.modeNote.includes("理论上重合")) failures.push("等长 cohort 聚合口径切换异常"); if (artifactEvidence.panel !== "evidence" || artifactEvidence.layers !== 27 || artifactEvidence.executed !== 7 || artifactEvidence.split !== 1 || artifactEvidence.unloaded !== 19 || artifactEvidence.exact !== "31 / 31") failures.push("真实工件执行边界或复跑闸门异常"); if (!artifactEvidence.dependency.includes("Transformers 5.5") || !artifactEvidence.dependency.includes("4.41.2") || !artifactEvidence.boundary.includes("完整 27 层生成")) failures.push("依赖版本或未覆盖边界异常"); if (artifactEvidence.keyboardSelected !== "load" || artifactEvidence.keyboardVisible !== "load") failures.push("真实工件实验键盘 tab 导航异常"); if (home.releaseCards !== 17 || !home.firstRelease.includes("47 页不再压成摘要") || home.firstHref !== "/k3/" || home.paperCount !== "486") failures.push("首页 DeepSeek 首发入口或论文数异常"); if (papers.total !== 486 || !papers.hasFilter || papers.visible < 20 || !papers.hasCoder || !papers.hasEngram) failures.push("论文库 DeepSeek 聚光异常"); -if (!mobile.menuVisible || mobile.menuOpen !== "true" || mobile.tabs !== 4 || mobile.artifactTabs !== 6 || mobile.artifactHeatCells !== 64) failures.push("移动端导航或实验异常"); +if (!mobile.menuVisible || mobile.menuOpen !== "true" || mobile.tabs !== 4 || mobile.artifactTabs !== 6 || mobile.artifactHeatCells !== 64 || mobile.corpusCohorts !== 3 || mobile.lengthDeltaCards !== 4) failures.push("移动端导航或实验异常"); if (mobile.offenders.length) failures.push(`移动端越界元素:${JSON.stringify(mobile.offenders)}`); if (exceptions.length) failures.push(`浏览器异常:${exceptions.join(" | ")}`); diff --git a/src/components/DeepSeekArtifactLab.astro b/src/components/DeepSeekArtifactLab.astro index f00eaae..d6ed95d 100644 --- a/src/components/DeepSeekArtifactLab.astro +++ b/src/components/DeepSeekArtifactLab.astro @@ -5,6 +5,11 @@ import rawAbsorb from "@/data/deepseek-v2-lite-absorb.json"; import rawAbsorbRepro from "@/data/deepseek-v2-lite-absorb-repro.json"; import rawCorpus from "@/data/deepseek-v2-lite-routing-corpus.json"; import rawCorpusRepro from "@/data/deepseek-v2-lite-routing-corpus-repro.json"; +import rawMatched16 from "@/data/deepseek-v2-lite-routing-matched16.json"; +import rawMatched16Repro from "@/data/deepseek-v2-lite-routing-matched16-repro.json"; +import rawMatched24 from "@/data/deepseek-v2-lite-routing-matched24.json"; +import rawMatched24Repro from "@/data/deepseek-v2-lite-routing-matched24-repro.json"; +import rawLengthSensitivity from "@/data/deepseek-v2-lite-routing-length-sensitivity.json"; const trace = rawTrace as any; const repro = rawRepro as any; @@ -12,8 +17,15 @@ const absorb = rawAbsorb as any; const absorbRepro = rawAbsorbRepro as any; const corpus = rawCorpus as any; const corpusRepro = rawCorpusRepro as any; +const matched16 = rawMatched16 as any; +const matched16Repro = rawMatched16Repro as any; +const matched24 = rawMatched24 as any; +const matched24Repro = rawMatched24Repro as any; +const lengthSensitivity = rawLengthSensitivity as any; const absorbExact = JSON.stringify(absorb) === JSON.stringify(absorbRepro); const corpusExact = JSON.stringify(corpus) === JSON.stringify(corpusRepro); +const matched16Exact = JSON.stringify(matched16) === JSON.stringify(matched16Repro); +const matched24Exact = JSON.stringify(matched24) === JSON.stringify(matched24Repro); const bytes = (value: number) => value >= 1024 ? `${(value / 1024).toFixed(2)} KiB` : `${value.toLocaleString()} B`; @@ -49,17 +61,25 @@ const compact = { }, }; const compactJson = JSON.stringify(compact).replaceAll("<", "\\u003c"); -const corpusCompact = { - domains: corpus.corpus_contract.domains, - labels: corpus.corpus_contract.domain_labels, - counts: corpus.corpus_contract.counts, - inference: corpus.inference_contract, - statistics: corpus.statistical_contract, - layers: corpus.layers.slice(1).map((layer: any) => ({ +const compactCorpus = (input: any) => ({ + domains: input.corpus_contract.domains, + labels: input.corpus_contract.domain_labels, + counts: input.corpus_contract.counts, + inference: input.inference_contract, + statistics: input.statistical_contract, + layers: input.layers.slice(1).map((layer: any) => ({ layer: layer.layer, routes: layer.routes, modes: layer.statistics, })), +}); +const corpusCompact = { + cohorts: { + natural: compactCorpus(corpus), + matched16: compactCorpus(matched16), + matched24: compactCorpus(matched24), + }, + lengthSensitivity, }; const corpusCompactJson = JSON.stringify(corpusCompact).replaceAll("<", "\\u003c"); const shardFraction = trace.provenance.shard_1_bytes / trace.provenance.checkpoint_tensor_bytes; @@ -73,7 +93,7 @@ const shardFraction = trace.provenance.shard_1_bytes / trace.provenance.checkpoi

固定官方 revision、tokenizer、模型代码和 BF16 第一分片;RTX 5090 连续执行 layer 0–6, - 从 3,240 次 token 显微轨迹扩到 304,560 次公开语料路由,并让 layer-1 权重继续走入官方吸收式 cache。 + 从 3,240 次 token 显微轨迹扩到 488,880 次公开语料路由,并让 layer-1 权重继续走入官方吸收式 cache。 所有结论都带证据身份与停止线。

@@ -390,22 +410,35 @@ const shardFraction = trace.provenance.shard_1_bytes / trace.provenance.checkpoi diff --git a/src/pages/progress/index.astro b/src/pages/progress/index.astro index 29a6c5b..7423705 100644 --- a/src/pages/progress/index.astro +++ b/src/pages/progress/index.astro @@ -15,7 +15,7 @@ const workstreams = [ { label: "表示、位置与残差高速公路", value: 81, next: "加入真实 hidden-state / norm traces、长上下文位置外推复现与更多深层稳定性消融" }, { label: "Scaling Laws", value: 74, next: "加入真实拟合复现、置信区间与更多模型族对照" }, { label: "数据工程与预训练配方", value: 73, next: "逐图精读 FineWeb / DCLM,加入真实去重与 mixture traces" }, - { label: "DeepSeek 专题", value: 91, next: "SM90 FlashMLA kernel、完整 27 层、长度匹配对照、FP8/pipeline 与 R1-like RL 复现" }, + { label: "DeepSeek 专题", value: 92, next: "SM90 FlashMLA kernel、完整 27 层、tokenization 扰动、FP8/pipeline 与 R1-like RL 复现" }, { label: "指令微调与人类偏好", value: 75, next: "加入真实偏好分歧样本、RM 长度偏置与 PPO/DPO 小模型复现" }, { label: "推理与测试时扩展", value: 76, next: "真实模型采样曲线、PRM 案例与逐篇图表精读" }, { label: "工具使用与长程 Agent", value: 74, next: "补真实环境 traces、cross-harness 对照、Agent RL 训练曲线与安全案例" }, @@ -50,7 +50,7 @@ const workstreams = [
OVERALL
专题平均 {average}%
READABLE
{published} 个首版可读专题
ACTIVE
{researching} 个研究/写作中
-
UPDATED
2026-07-29 15:25 CST
+
UPDATED
2026-07-29 16:03 CST
MODE
持续迭代,不锁死版本
@@ -102,7 +102,7 @@ const workstreams = [
✓

语言模型前史深度专题

八张独立问题账、33 个正式节点、20 段长文与概率—向量—记忆—对齐四联实验。

✓

Transformer 深度专题

十张独立问题账、40 个正式节点、21 段正文与 QKV—Mask—多头位置—Block 成本四联实验。

✓

表示、位置与残差高速公路深度专题

二十张问题账、66 个一手节点、DeepSeek/Kimi 双谱系,以及 Token—位置—Norm—Residual/FFN 四联实验。

-
✓

DeepSeek 三轮真实权重里程碑

在二十四张问题账、十次转向与四联公式实验上,新增 V2-Lite 7/27 层连续 forward、官方 V3 absorb 的 576 元素真实缓存,以及四域 128 条固定公开 prompt、304,560 次真实路由、2,000 次 prompt bootstrap 与 byte-exact 独立重跑的六联实验。

+
✓

DeepSeek 三轮真实权重里程碑

在二十四张问题账、十次转向与四联公式实验上,新增 V2-Lite 7/27 层连续 forward、官方 V3 absorb 的 576 元素真实缓存,以及自然长度 / 同源 16 / 同源 24 三个 cohort、488,880 次真实路由、2,000 次成对 prompt bootstrap 与 3/3 byte-exact 独立重跑的六联实验。

✓

Kimi K3 技术报告二轮深读

三十二张问题账、Figure 1–16 / Table 1–5 审计、100 节点阅读链,以及 Delta—Decay—AttnRes—LatentMoE—SiTU—QB—MOPD—Cache 八联实验。

✓

Kimi K3 三轮开放工件里程碑

固定官方 revisions,审计 96 个 shards、497,220 个 tensor entries 与真实 KDA / MLA / MoE / MoonViT shapes;四联实验分开显示层型、tensor anatomy、参数范围和复现边界。

✓

FlashKDA RTX 5090 执行闸门

隔离 CUDA 13.0 / glibc 2.39 编译 sm_120a wheel;6/6 官方参考逐元素相等,并完成 fixed / varlen、三种 state mode 的 1,800 个 CUDA Event samples。

@@ -134,7 +134,7 @@ const workstreams = [
优先级专题本轮交付完成闸门
P0K3 三轮

开放权重 traces → FlashKDA / AttnRes / MoE 真实行为 → Figure 1–16 数值重绘与独立复现

运行证据 + 逐图复现
-
P0DeepSeek 三轮

SM90 FlashMLA kernel / 完整 27 层 / 长度匹配对照 → FP8 / pipeline traces → R1-like RL 小模型复现

运行证据 + 独立复现
+
P0DeepSeek 三轮

SM90 FlashMLA kernel / 完整 27 层 / tokenizer 与 prompt-template 扰动 → FP8 / pipeline traces → R1-like RL 小模型复现

运行证据 + 独立复现
P0Transformer 二轮

多头电路逐图 → Pre/Post-LN 真实 traces → Flash/KV 配置与 kernel 对照

逐图笔记 + 实测边界
P0表示、位置与残差二轮

真实 hidden-state / norm traces → 长上下文位置外推 → mHC / AttnRes 深层稳定性消融

可复现实验 + 逐图笔记
P0语言模型前史二轮

Kneser–Ney / LSTM / Bahdanau 逐图 → 真实小语料复现 → tokenizer 公平性

可复现实验 + 逐图笔记
@@ -215,6 +215,9 @@ const workstreams = [
A_log 形状冲突保持未决

checkpoint 的 [128] 与 config / remote code / FlashKDA API 期待的 [96] 并列展示;不宣布权重损坏,也不把 channel-wise 假设写成真实 forward。

FlashKDA 编译与执行永久分两道闸门

容器产出 sm_120a wheel 只证明可编译;RTX 5090 的 6/6 official-reference exact suite 通过后,才把证据升级为本机执行 X。

作者表与 RTX 5090 表永久分账

H20 / GB200 保持 O;本站只报告独立环境、协议、300 samples/mode 和延迟分布,未跑本机 FLA 就不写本机 speedup。

+
32-token 对照改为同源 16→24

TNEWS 只有 105/10,000 条达到 32 tokens,强行统一会落入约 1% 极端长尾;24-token eligibility 仍保留 1,609 条中文候选。

+
长度敏感性必须成对重采样

16-token 输入严格是 24-token 输入前缀,2,000 次 bootstrap 共用 prompt indices;结果只描述固定 cohort 的长度敏感性。

+
三类 cohort 永久分身份

自然长度回答本批样本如何路由;matched-16 / 24 回答同一 prompt 多看 8 tokens 后如何变化,不把二者混成内容因果。