feat(dspark): Inkling-era trainer v2 — new-target adaptation + config-level backbone - #4
feat(dspark): Inkling-era trainer v2 — new-target adaptation + config-level backbone#4Boreas618 wants to merge 26 commits into
Conversation
|
Cross-reference: upstream sgl-project#734 (Inkling managed capture, disaggregated). Both PRs solve the same
Also relevant: upstream #735 adds a constant LR schedule ( |
…ion) Port of TorchSpec PR sgl-project#129 to SpecForge. Adds: - specforge/modeling/draft/dspark.py: DSparkConfig, VanillaMarkov, AcceptRatePredictor, DSparkDraftModel (subclass of DFlashDraftModel) - specforge/core/dspark.py: OnlineDSparkModel (subclass of OnlineDFlashModel) with Markov-biased logits + CE + L1 distribution distillation + confidence BCE and a pooled global-mean loss - scripts/train_dspark.py: training driver (clone of train_dflash.py) - configs/qwen3-8b-dspark.json, examples/run_qwen3_8b_dspark_online.sh - last_hidden_states surfaced from the DFlash target backends (HF + sglang) - tests/test_utils/test_dspark.py: 11 CPU unit tests
… eval) Train a dense 5-layer DSpark drafter for zai-org/GLM-5.2-FP8 (glm_moe_dsa), matching the RedHat/UCloud community consensus; validated end-to-end on a single-node tp=4 smoke against the real 753B target (loss 3.90->3.59, checkpoint save+reload). - configs/glm-5.2-dspark.json: dense 5L draft (hidden 6144, head_dim 64, block_size 7, aux target layers [1,19,38,57,76], markov 256 + confidence). - data/template.py + parse.py + preprocessing.py: glm-5.2 chat template + GLMParser (forces enable_thinking=False; loss mask = assistant content). - modeling/target/dflash_target_model.py: set_dflash_layers_to_capture + append final post-norm hidden so DSpark L1/confidence get last_hidden_states. - modeling/draft/dflash.py + core/dflash.py: optional FA4 flex path (gated off). - scripts/train_dspark.py: 4-node hardening + guarded accept-length eval hook. - scripts/eval_dspark_deepspec.py: DeepSpec mean-accepted-length eval (9 tasks). - scripts/prepare_glm52_dspark_data.py + examples/run_glm5.2_dspark_4node.sh: mixed-corpus prep + 4-node WORLD=16 DP-attention launcher.
…cept-length eval - train_dspark.py: torch.compile(dspark_model) after FSDP (SPECFORGE_COMPILE_DRAFT, on by default); flex attention + dual-source mask construction excluded via @torch.compiler.disable (they fail inductor lowering nested in an outer dynamic compile). Validated 2-GPU FSDP: ~55s warm, ~0.1s/step, dynamic shapes, finite grads. - eval_dspark_deepspec.py: rewrite the accept-length core to DeepSpec's EXACT rejection-sampling verify (logits_to_probs / sample_from_probs / sample_residual / gather_token_probs ported verbatim; accept_prob=clamp(p_t/p_d,max=1) -> rand<ap -> cumprod; +1 bonus unless EOS; accept_rate@k; per-sample seed_all(seed+idx)). Draft proposal now returns per-position draft_probs. Bit-exact vs DeepSpec-greedy at temp 0, faithful at any temperature. Default task = gsm8k; standalone defaults to DeepSpec's gsm8k protocol (500 prompts, 2048 tokens, seed 980406). Fixes a BatchEncoding.shape crash in _encode_prompt (GLM tokenizer returns a dict). - train_dspark.py: --evals-per-epoch (10 evals/epoch); in-loop accept-length eval hook now LOGS failures with full traceback (no silent swallow). - run script: num_anchors 1024 (RedHat), evals-per-epoch 10, EVAL_DATASETS_DIR default + gsm8k.jsonl build in `prepare`, SPECFORGE_COMPILE_DRAFT=1. - core/dflash.py: @torch.compiler.disable on create_dflash_block_mask; draft/dflash.py: @torch.compiler.disable on the dual-source attention (keeps block-sparse flex outside the outer compile). FA4-flex path retained (gated, off) with the SM100 findings. - .gitignore: ignore *.local.sh (local launch cheatsheets w/ credentials).
…, no IB/DeepEP) The devbox container exposes no /dev/infiniband verbs (HCAs are ACTIVE in sysfs but the char devices aren't mapped in), so NVSHMEM/DeepEP cannot init any cross-node IB transport (IBGDA fails; IBRC would too) -> Approach 1 (DP-attention+DeepEP) can't run cross-node. Add APPROACH=2: one per-node tp=NUM_GPUS engine (intra-node NVLink target, no DeepEP) + cross-node data-parallelism via the draft FSDP over NCCL (TCP when NCCL_IB_DISABLE=1). DATA_STREAMS/ACC and MEM_FRAC adjust per approach; global batch 512 preserved (A2: dp=4, acc=128). Loss-correct (objective pooled-mean is valid for the 4-replicated tp-groups). Use when IB is unavailable; prefer A1 once /dev/infiniband is exposed.
…crashes with FSDP) torch.compile(dspark_model) crashes under real varying-length data: dynamo recompiles on a new shape after a backward has populated grads and hits "assign a gradient of size [s0] to a tensor of size [s19]" (FSDP SHARD_GRAD_OP sharded-grad vs full-param) inside Qwen3RMSNorm. The 2-GPU test passed only because its shapes never forced a post-grad recompile. Eager is validated + stable and the run is target-bound, so default compile off; re-enable (=1) once the FSDP-recompile interaction is fixed.
Two DSpark training fixes surfaced by the 4-node run that showed loss=inf:
1. Resume silently loaded a RANDOM draft. Checkpoints saved while
torch.compile was enabled carry an `_orig_mod.` key prefix that neither
save_checkpoint nor the resume path stripped, so from_pretrained matched
0 keys and re-initialized the whole draft. Now:
- save_checkpoint strips both `_orig_mod.` and `draft_model.`;
- resume loads weights straight from the checkpoint safetensors (stripping
both prefixes) and RAISES if it matches nothing, instead of training a
random draft. Verified the existing epoch_0_step_2 checkpoint is 100%
finite — the inf is a forward-pass issue, not NaN weights.
2. loss=inf diagnostics + opt-in sanitizer in OnlineDSparkModel.forward /
_dspark_objective:
- [NONFINITE-INPUT] logs when the captured target hidden (context or
final) is non-finite — the likely inf source (fc(inf) -> inf logits).
- [NONFINITE-LOSS] fires only when the loss is already broken (zero cost
on the healthy path) and reports which numerator (ce/l1/conf), the
denominators, and whether draft_hidden/base_logits/last_hidden are the
source.
- SPECFORGE_SANITIZE_NONFINITE=1 (default OFF) drops non-finite tokens
from supervision and zeros their hidden so one bad sample cannot
NaN-poison all ranks via the gradient all-reduce under genuine DP.
Diagnostics default ON (SPECFORGE_DEBUG_NONFINITE=1); sanitizer default OFF
so the first run shows the raw source before we mask anything.
The GLM-5.2 launcher now defaults to the validated topology + robustness so
the real run needs no extra flags:
- APPROACH=${APPROACH:-2} (per-node tp=4 + cross-node draft DP over NVLink;
the DeepEP/IB Approach-1 path is opt-in, pending RDMA in-container).
- SPECFORGE_SANITIZE_NONFINITE=1 (was opt-in) — drop non-finite tokens from
supervision so one rare bad FP8-target-capture sample can't NaN all ranks
over a 1.5M x 10-epoch run; [NONFINITE-*]/[SANITIZE] still log the rate.
- SPECFORGE_DEBUG_NONFINITE=1, SPECFORGE_COMPILE_DRAFT=0 (already 0) made
explicit in the launcher.
All remain overridable via env (e.g. APPROACH=1, SANITIZE=0 to crash-on-bad-
sample for diagnosis). Fixed a stale trainer comment (run script sets compile
=0, not =1). The 40-step 4-node smoke was clean, so this is standing insurance.
The 4-node run hung at step ~47: rank 0 blocked in the DataLoader while ranks 1-3 spun in the target's tp=4 collective waiting for it. py-spy showed a fork()ed DataLoader worker deadlocked — mid-batch tensorize triggered an import, and while holding the import lock a GC'd wandb public-API object's weakref finalizer fired and blocked forever on api_cleanup_request (the wandb service socket doesn't exist in the fork). That worker never yielded its batch. This is exactly why the REPORT_TO=none smoke ran clean but the REPORT_TO=wandb real run hung. Fix: force num_workers=0 (load in-process, no fork, no inherited wandb finalizer). spawn workers avoid the inherit but re-exec this heavy module per worker and are fragile here; the workload is target-prefill-bound and data is pre-tokenised (Arrow mmap), so in-process loading costs ~1-2%/step. Launcher now passes --dataloader-num-workers 0 to match (the trainer forces 0 regardless).
…~2.5x) Fixes the step-46 CUDA OOM and cuts per-epoch wall clock ~2.5-3x under the Approach-2 (no-RDMA) topology. Recipe untouched: num_anchors stays 1024, global batch stays 512, optimizer steps/epoch stay 1445, same LR schedule. 1. Chunked DSpark objective (SPECFORGE_OBJECTIVE_CHUNK_BLOCKS=128, 0=legacy). The objective materialized a [nb,7,154880] f32 logits/probs stack — 24.91 GiB measured at nb=1024 — inside the ~45 GB left beside the sglang static pool; backward pushed it over. Now the block dim is processed in slices under torch.utils.checkpoint (recompute-in-backward): peak 3.93 GiB, +46 ms on the worst-case sample. Validated exactly grad-equivalent to the legacy path on CPU (loss, all metrics, draft/markov/confidence grads) and loss-delta 2.8e-5 in bf16 at real dims. Includes a cuBLAS fix: vocab linears on sliced 4D tensors hit a degenerate batched-GEMM kernel (37.8 ms vs 1.1 ms flattened) — all vocab GEMMs now flatten to 2D first. 2. TP-batch scatter (SPECFORGE_TP_BATCH_SCATTER=1, tp-replicated target only). Without DP-attention every rank in the node's tp group holds the same batch and identical target hiddens; training the draft on all of it just computes tp_size identical gradient copies. Now the node batch (default 4 under APPROACH=2) is prefilled once by the tp=4 target, then each rank trains the draft on a distinct 1/tp slice trimmed to its true length -> 16 unique data streams, draft compute deduplicated, ACC 128->32. 3. generate_dflash_data strips right-padding per request (the 750B target no longer prefills pad tokens at batch>1) and re-pads the returned hiddens; pad positions are never read (loss_mask=0, blocks attend <= anchor). 4. Launcher: BATCH_SIZE defaults to 4 under APPROACH=2, plus PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (the OOM report showed 12.8 GB reserved-but-unallocated fragmentation; safe — the in-process engine runs disable_cuda_graph=True). Validated end-to-end: single-node 4-GPU smoke (batch 4, scatter, chunked) ran 40 steps clean — no OOM/non-finite, losses match the pre-change runs, ~1.1 s/iter for 4 samples vs ~0.7 s/iter for 1 before (~2.5x throughput); batched prefill confirmed pad-stripped (hs_shape 5748 tokens for 4 samples).
anchor sampler, tau_probabilistic Full training-flow audit against deepseek-ai/DeepSpec@005e03b. Already exact (verified line-by-line against their source): label convention (slot k -> anchor+k+1, all slots supervised), eval mask (cumprod prefix), noise embed (slot0 = anchor token), position ids, attention mask (strict kv < anchor, bidirectional intra-block), backbone (hidden_norm(fc(.)), k_norm over ctx+noise concat, RoPE cos[-q_len:]), VanillaMarkov bias on teacher-forced prev ids applied before CE AND L1, confidence BCE on [hidden, prev_emb] vs detached accept_rate, loss 0.1*CE + 0.9*L1 + 1.0*conf with exp(-k/4) decay and pooled global-mean reduction (their 3 all-reduced denominators are all loss_weight_mask.sum() == our shared denominator), frozen target embed+head, BF16Optimizer/AdamW wd=0/cosine+warmup 0.04/ lr 6e-4/bf16/epochs 10/global batch 512/max_len 4096/seed 42. Three mismatches found and fixed: 1. Grad clipping was LOCAL-SHARD norm (BF16Optimizer clips fp32 master shards) vs DeepSpec's global-norm FSDP.clip_grad_norm_ — under 16-way sharding that is a ~sqrt(world)x looser, non-uniform clip. Now clip the FSDP model by the global norm before optimizer.step() (DeepSpec order); the optimizer's internal local clip provably no-ops after that. 2. Anchor sampling deviated 3 ways from DeepSpec sample_anchor_positions: no first-target-valid rule (loss_mask[p+1]) -> anchors wasted on zero-supervision blocks at turn boundaries; candidates capped at seq_len - block_size (DeepSpec allows to seq_len-2 with eval-mask truncation); off-by-one in the keep cap (batch_max_valid - 1). OnlineDSparkModel now overrides the DFlash sampler with a DeepSpec-exact port — validated bit-identical anchors + keep masks vs DeepSpec's own function under shared RNG across 200 randomized trials. One disclosed deviation kept: anchor tensor width caps at the batch max keep count instead of always padding to num_anchors (identical supervision; dummy columns only burn draft-MLP compute). 3. Added DeepSpec's tau_probabilistic train metric (expected accepted drafts per block + 1, over valid blocks) to both objective paths; chunked-vs-legacy equivalence re-verified incl. the new metric. Intentional deviations (owner-locked / infra): num_anchors 1024 (RedHat) vs DeepSpec reference 512; online sglang FP8 teacher vs offline bf16 HF cache (same tensors: layer-output hooks + post-norm final hidden); shard_grad_op vs no_shard; no no_sync during accumulation (math-identical, ~4% comm); compile off (FSDP-recompile crash); chunked objective and TP-batch scatter (both proven equivalence-preserving).
…odel rescue) The reshard-safe resume resets Adam moments; the first optimizer steps are then sign-like kicks of ~lr per coordinate (m_hat/sqrt(v_hat) = +-1 at t=1). On a converged model at full LR this is destructive — observed live: rollback to a healthy checkpoint (tau 4.06) degraded to tau ~1.1 within ~5 optimizer steps of resuming at lr 5.76e-4. Fix: linearly re-warm the LR over the first N optimizer steps after a moment-reset resume (SPECFORGE_RESUME_LR_REWARM_OPT_STEPS, default 64). The second moments re-estimate on real gradients while the weights barely move; identical factor on every rank keeps sharded updates consistent. Implementation note: the damp must be applied inside BF16Optimizer.step() (new optional lr_scale arg) and the original lr restored BEFORE scheduler.step() — torch schedulers like CosineAnnealingLR are recurrences on param_group["lr"], so a damped value left in place permanently drags the remaining schedule down (validated: naive one-shot damp collapsed the schedule 1e-3 -> 9.4e-5; the fixed path keeps the schedule exact while the effective LR ramps 0.25x -> 1x).
edge-of-stability collapses The converged drafter (tau ~4+, gsm8k accept_len 4.76) collapsed twice at the cosine schedule's peak region (5.76e-4): ce 1.8 -> 40+ with onset within ~3 optimizer steps of running at full schedule LR, at two different data positions (micro steps ~75.9k and ~77.1k), and was stable through the entire damped post-resume re-warm both times. Classic sharp-minimum instability; the cosine stays above half-peak until ~55% of training, so waiting it out is not an option. SPECFORGE_LR_SCALE (default 1.0 = DeepSpec-parity recipe; GLM launcher sets 0.5) multiplies the effective LR while leaving the schedule shape exact — applied through optimizer.step(lr_scale=...), which restores the group lr before the recurrent scheduler advances. Composes with the post-resume re-warm ramp. A one-time cross-rank all-reduce check aborts startup if ranks disagree on the scale (a silent mismatch would diverge the sharded updates).
Distinguishes gradient explosion (grad_norm >> max_norm at collapse onset) from curvature/step-size instability (collapse with flat grad_norm — Adam's update magnitude is ~lr per coordinate regardless of gradient scale, which clipping cannot bound).
(DeepSpec-style incremental verify on the sglang bridge) The DeepSpec eval re-prefilled the whole growing sequence every verify step (the training bridge only exposed stateless prefill) -> O(n^2) target compute per sample, ~50-100x DeepSpec's KV-cached incremental verify (DynamicCache + crop). New: start_cache_session()/end_cache_session() give the bridge a persistent RadixCache; each generate_dflash_data call prefix-matches against it (init_next_round_input), computes only the unmatched suffix, and inserts the COMMITTED tokens back (cache_finished_req with cache_commit_len). Only committed tokens are ever cached and drafted-block KV is freed, so a future match can never overrun the positions the verifier reads. Hidden states are returned suffix-only with a prefix_len offset (DFlashTargetOutput.prefix_len); outputs split by extend_input_len (identical to the old full-length split when no session is active). Training path untouched. Two traps fixed along the way: - Paged-allocator double free: free() frees WHOLE pages (unique(idx // page)); cache_finished_req already frees the page holding the unaligned commit boundary, so the manual tail free must start at the next page boundary. - eval cmd: multi-node dp x tp sweep entry (cmd_eval) in the 4-node launcher. Eval loop: unified relative indexing (prefix_len=0 without a session), context maintained incrementally, session released in a finally wrapper, SPECFORGE_EVAL_KV_REUSE=0 forces the legacy path. Validated: 4-rank lockstep breadcrumbs across cached+control phases, accept-length parity with the legacy path (4.88 vs 5.08 on n=4, within greedy near-tie noise from different kernel shapes), and per-64-page prefix matching observed live. Rows now flush immediately and partial JSON is checkpointed after each task.
… eval default
Root cause of the ~50% training-data loss: the glm-5.2 chat template hardcoded
assistant_header="<|assistant|><think></think>" (the EMPTY think block). GLM
renders a thinking-ON assistant turn as "<|assistant|><think>{reasoning}</think>
{answer}", so the literal-empty-block header never matched -> those turns got a
zero loss mask and were dropped by the min_loss_tokens filter. That is exactly
the 1,498,000 -> 739,583 (49.4%) filter seen in training: 100% of the dropped
half were thinking-ON regens (the mgoin open-perfectblend regenerations). The
drafter therefore trained thinking-OFF-only and was evaluated thinking-OFF.
Owner decision: target GLM-5.2's default THINKING-ON mode; supervise reasoning
+ answer. Fixes:
- template.py: assistant_header -> "<|assistant|><think>" (the always-scaffolding
prefix) + assistant_pattern_type="glm".
- parse.py: new "glm" assistant_pattern
<\|assistant\|><think>(?:</think>)?([\s\S]*?(?:<\|user\|>|$))
-> supervises {reasoning}</think>{answer} for thinking-ON and just {answer}
for thinking-OFF (the empty-block </think> scaffold is skipped, unsupervised).
Validated on CPU: retention 49.4% -> 99.8%; thinking-ON span starts at the
reasoning and includes the generated </think>; thinking-OFF span excludes the
scaffold </think>.
- train_dspark.py: (1) hard guard — abort if <90% of samples survive the
loss-mask filter (SPECFORGE_MIN_RETENTION), so a template/mask mismatch can
never again silently halve the corpus; (2) cache key versioned
(maskv2-glm-thinkhybrid) — the processed-dataset cache keyed on template NAME
would otherwise reuse the stale 49.4%-masked tensors after this content change;
(3) in-loop --eval-max-new-tokens default 256 -> 1024 so thinking-ON reasoning
isn't fully truncated.
- eval: SPECFORGE_EVAL_ENABLE_THINKING default OFF -> ON (matches deployment +
training target); launcher eval max_new 1024 -> 2048 (room for the reasoning
chain); EVAL_TAG records the thinking mode.
Recipe implication (training deferred): retention ~2x -> ~1.5M samples/epoch,
so steps/epoch and the cosine schedule length ~double vs the prior run. Stale
processed_dataset cache cleared.
… fixed The training path accreted ~10 SPECFORGE_* env knobs, two import-time monkeypatches, a broken resume branch, and one-off debug prints during bring-up. The flow is now settled, so most knobs have a single correct value. Net: the training path exposes ONE env var (SPECFORGE_DATA_NUM_PROC) down from ~10, with zero change to training numerics (verified: chunked==legacy objective loss+grads bit-parity; retention still 100%; defaults equal the old env values). scripts/train_dspark.py - Delete the torch.compile path (SPECFORGE_COMPILE_DRAFT) + its per-rank inductor/triton cache monkeypatch — compile crashes with FSDP on the data-dependent num_anchors shape; eager is the validated path. - Delete SPECFORGE_OFFLOAD_MASTER (pass offload_master=False) and the SPECFORGE_RESUME_FULL_OPTIM branch (impossible multi-rank — only rank-0's optim shard is saved; reshard-safe is the sole correct path). - Hardcode FSDP shard_grad_op (SPECFORGE_FSDP_STRATEGY) and the retention guard (MIN_RETENTION=0.9 module constant). - Promote the two genuine recipe hyperparameters to real CLI args: --lr-scale (0.5) and --resume-lr-rewarm-steps (64), keeping the cross-rank consistency check. - De-magic the PG-timeout monkeypatch (PG_TIMEOUT_MINUTES=45, now setdefault so it no longer silently clobbers callers) and delete the dead --dist-timeout arg it had been overriding. - TP-batch scatter: drop the env gate; auto-enable from topology (tp-replicated + batch divisible by tp). - Resume-path raw print() -> print_on_rank0 (stop duplicating across ranks). specforge/core/dspark.py - Convert the three objective env knobs to OnlineDSparkModel constructor params: objective_chunk_blocks=128, sanitize_nonfinite=True, debug_nonfinite=True. Legacy _full_numerators stays reachable via objective_chunk_blocks=0 (kept as the equivalence reference). Drop the now-unused `import os`. specforge/modeling/target/dflash_target_model.py - Remove the [capture] and one-shot [DEBUG _extend] scaffolding prints. Keep the ServerArgs-drop info line, the [KV] debug (gated), and the sglang serial-load / moe-runner-backend escape hatches (shared by all entrypoints). examples/run_glm5.2_dspark_4node.sh - Drop the exports for the removed/hardcoded knobs + redundant unsets; pass --lr-scale / --resume-lr-rewarm-steps in the torchrun call; drop --dist-timeout. Deliberately untouched (blast radius): optimizer.py (shared lr_scale default used by 7 other trainers), train_dspark_v4/moe/dflash (own knob copies), the shared sglang-load knobs, the FA4 flex gate in shared core/dflash.py, and Approach-1.
…pproved) Force the DeepSpec recipe values where they can apply to the GLM-5.2 target, per owner decision (with the known trade-offs accepted): Training - num_anchors 1024 -> 512. DeepSpec canonical (all their dspark configs use 512). Halves supervision density/step; also the likely reason DeepSpec is stable at unscaled 6e-4 (our earlier collapse was at 1024). - Effective LR 0.5x -> 1.0x (unscaled 6e-4) = DeepSpec. --lr-scale default is now 1.0 (launcher + argparse); the 0.5 escape hatch remains (set LR_SCALE=0.5) if the run re-collapses at the schedule peak. grad-norm telemetry + sanitizer stay as the safety net. Evaluation (DeepSpec-exact protocol) - temperature 0.0 -> 1.0 (DeepSpec's stochastic rejection-sampling default). - per-task caps: EVAL_LIMIT 128 -> 0, which now maps bare --tasks names to the DeepSpec DEFAULT_TASKS caps (gsm8k 500, math500 500, aime25 30, humaneval 164, mbpp 256, livecodebench 500, mt-bench 80, alpaca 500, arena-hard-v2 500) via a fallback added to _normalize_tasks. max_new 2048 + seed 980406 already matched. Already identical (verified earlier, unchanged): objective/loss math, block_size 7, 5 layers, markov 256 + vanilla, confidence + with_markov, α 0.1/0.9/1.0, decay 4.0, base lr 6e-4, warmup 0.04, wd 0, bf16, global_batch 512, epochs 10, grad-clip 1.0, max_len 4096. NOT changed (target-dependent, cannot be identical to a Qwen3/Gemma config): target=GLM-5.2-FP8, target_layer_ids [1,19,38,57,76] (78 layers), mask_token 154856, chat_template glm-5.2, vocab/hidden. NOT forced (owner left unchecked; would reintroduce known failures): torch_compile (FSDP crash), dataloader num_workers=4 (wandb+fork deadlock). Kept for safety (non-numerics): FSDP shard_grad_op (vs no_shard), save-interval 500 (vs 3000) for rollback granularity.
…igned in-loop eval
Fresh-run reset of the GLM-5.2 DSpark pipeline:
- Data: corpus = mgoin/open-perfectblend-glm5.2-regen ONLY (codealpaca mix
removed everywhere); whole corpus trains, no eval carve (the dedicated
DeepSpec accept-length suite is the eval; --eval-data-path was dead code
in this flow and is no longer passed).
- Thinking-ON only: GLMParser renders enable_thinking=True (training prompt
= deployment prompt byte-for-byte, incl. the "Reasoning Effort: Max"
header, outside the loss mask); eval _encode_prompt has no thinking-OFF
path; SPECFORGE_EVAL_ENABLE_THINKING removed. Cache key bumped to
maskv3-glm-thinkon (train + launcher warm cache kept in sync).
Validated on 500 real regen rows: retention 100%, header excluded from
the mask, span = {reasoning}</think>{answer}.
- In-loop eval: gsm8k + mbpp + mt-bench @ 32 prompts each (--eval-tasks),
temp 1.0 / max_new 2048 / seed 980406 == the final-eval protocol; the
in-loop subsets are seed-matched prefixes of the final runs. prepare now
builds all three jsonls per node (byte-identical to the reference files;
a jsonl present on some replicas but not others deadlocks the eval's dp
all-reduce).
- Launcher: Approach 2 only (per-node tp engine + cross-node FSDP DP);
DP-attention/DeepEP path removed (no /dev/infiniband in this container).
MEM_FRAC default 0.78.
- Trainer defaults resolved to the DeepSpec/owner recipe: block 7, 5 draft
layers, 10 epochs, max_length 4096.
- Docs: REGEN_OPEN_PERFECTBLEND.md regen+verification guide; execution doc
updated to match all of the above.
…-level backbone Ports the DSpark trainer as evolved for a second target family (Inkling, 42-layer hybrid-SWA MoE, NVFP4, eos-less tokenizer): - target: frozen embed/head allocated at padded_vocab_size (201024), not vocab_size; embed/head key remapping per target arch - data: assistant-pattern + ThinkingParser support, nda_inkling_thinking chat template, seeded node-file sampler + content-hash dedup in prepare, tokenized-cache key includes train-data content hash (prepare and train derive identical keys) - eval (in-loop): stop-token resolution falls back to the draft config's eos_token_id list when the tokenizer defines no eos (raises if neither exists); task list auto-discovers <task>.jsonl in eval-datasets-dir - resume: world-consistent resume guard — all ranks agree on the newest checkpoint present on every node, falling back to the newest common one (fixes a deterministic post-checkpoint rendezvous deadlock) - backbone v2 is config-level: KV injection + block-local attention already in the modeling stack; uniform target taps, target-matched GQA geometry, and block-size-adaptive loss decay are all expressed in the draft config - capture-parity probe (probe_nda_inkling_capture.py): scale-aware P1 + stream-distinctness P2b checks before first smoke Deliberately NOT included (companion PRs): native-list tools fix (fix/tools-native-list, based on main), evals-off guard, engine-stream drains, dual-rope-schema save. Excluded by owner selection: --fixed-lr, slim checkpoints, dual-node saves, watchdog machinery.
1631bbe to
81dd85d
Compare
db3915e to
0f8701e
Compare
Item 14 (core port) — intentionally based on
dspark-trainer, NOTmain(owner decision).main's online mode is the disaggregated runtime; this stack IS the colocated in-process-engine topology, and main's dspark modeling files have evolved separately. Reconciliation with main happens when the branches merge.The DSpark trainer as evolved for a second target family (42-layer hybrid-SWA MoE, NVFP4, eos-less tokenizer, 1M context):
padded_vocab_size; per-arch embed/head key remappingeos_token_idwhen the tokenizer defines no eos; task auto-discovery fromeval-datasets-dirStacked: #5 (evals-off), #6 (drains), launcher+probe fix. On
maininstead: #3, #7, #9, #10, #12, #13.🤖 Generated with Claude Code