- Sarvam-30B β Compression & Distillation Method (Notes + Working Pipeline)
- 1. The winning model β W4A16
- 2. The distillation attempts (and why they failed)
- 3. Pipeline (scripts included)
- 4. Cluster gotchas (the hard-won list β A100-80GB SLURM, transformers 4.57, vLLM 0.19.1)
- 4b. Response-economy chat-template trick β DOES NOT transfer to Sarvam (measured)
- 5. Honest conclusion
- 1. The winning model β W4A16
Sarvam-30B β Compression & Distillation Method (Notes + Working Pipeline)
A complete, honest engineering write-up of compressing sarvamai/sarvam-30b (SarvamMoE, 19 layers,
128 routed + 1 shared expert, top-6) for the Resilient AI Challenge β Track 1 (lowest inference
energy on A100-80GB via stock vllm serve, β₯80% quality recovery per category). This repo documents
what worked, what didn't, and every cluster gotcha, plus the reproducible scripts.
TL;DR: W4A16 quantization wins the energy game; brevity distillation does NOT work for this model. Energy = (tokens generated) Γ (bytes read per token). We maxed the bytes axis (4-bit). The tokens axis (shorten reasoning via distillation) is not reducible here with lightweight LoRA β proven across 3 runs.
1. The winning model β W4A16
The submitted model uses a quality-first, serving-safe recipe (merge_quant.py):
- GPTQ (calibrated 4-bit) on attention (
query_key_value,dense) + shared experts β the always-active, quality-sensitive paths. Calibration:sarvamai/indivibe(math/STEM/chat/code) + a multilingual builtin fallback, 16 samples Γ 128 seq (fast; passes all floors). - RTN (data-free 4-bit) on the 128 routed experts β the sparse bank tolerates it.
- Kept bf16:
lm_head(vLLM rejects a quantized lm_head), router gates (routing precision), layer 0. - Result: ~19 GB, passes every category floor with margin; ~β25% energy vs a standard-W4 cluster.
Why this is the energy optimum: at batch-1 decode the GPU is memory-bandwidth-bound β joules/token β bytes read from HBM. W4 is the practical floor for stock vLLM (sub-4-bit doesn't serve; quantized lm_head fails). 8-bit / FP8 entries read ~2Γ the bytes β more energy. GGUF entries don't serve on vLLM at all.
2. The distillation attempts (and why they failed)
Goal: cut reasoning tokens (the other energy lever β the model is a "thinking" model that burns
hundreds of tokens in <think>β¦</think>). Three honest attempts, all gated on quality + token count:
| Run | Data | SFT | Inference tokens (vs 49,587 shipped) | Floors | Winner |
|---|---|---|---|---|---|
| Brevity v1 | shortest-of-6, temp 0.9, cap 1536 | LoRA attn, gentle | ~103k (2.1Γ) | all pass | β |
| Brevity v2 | terse, cap 320, temp 0.6 (avg 203 tok) | LoRA attn, LR 2e-5 | ~99k (2.0Γ) | all pass | β |
| No-think | <think></think> prefill, cap 96 (avg 67 tok) |
LoRA attn r=32 | ~101k (2.0Γ) | all pass | β |
The finding (consistent across all 3): LoRA-SFT teaches the model to predict terse targets given a terse context (training loss drops cleanly, e.g. 0.34 β 0.26, or no-think 1.7 β 0.65 β it learns), but it cannot override the model's habit of reasoning long when freely generating at temp 1.0. The attention- only adapter (5β10M params) is too weak to change the generation-length distribution; if anything it perturbs the model toward more rambling. Quality stayed perfect every time β only brevity failed.
What would be needed (untested, higher-risk): heavier fine-tuning (higher-rank LoRA on MLP/experts, or full FT) and/or on-policy / RL-style training to actually shift free-generation behavior β which also risks MoE routing stability and the "no finetune after compression" rule (so it must be done before quant).
Legality note (challenge rules, verbatim): distillation is allowed only if the base model is the student; no finetuning after compression. So the legal order is distill β merge β quantize LAST.
3. Pipeline (scripts included)
gen_data.py β sft.py β merge_quant.py β bench_offline.py, orchestrated by pipeline.sbatch.
guarantee2.sbatch quantizes the base directly (no adapter) and pushes a serving-ready repo.
- Data-gen (
gen_data.py): vLLM offline, best-of-N, keep shortest correct (math/MCQ verified vs known answers; open-ended shortest-coherent).NOTHINK=1prefills<think></think>to elicit no-think. - SFT (
sft.py): LoRA onquery_key_value,denseonly (does NOT touch MoE routing/experts). Customcompute_losswith explicitignore_index=-100. Stable config: LR 1e-5β2e-5, grad-clip 0.3. - Merge + Quant (
merge_quant.py): merge adapter on CPU, then GPTQ(attn+shared)+RTN(experts). - Bench (
bench_offline.py): vLLM offline, energy = β« GPU power dt; per-category accuracy + tokens.
4. Cluster gotchas (the hard-won list β A100-80GB SLURM, transformers 4.57, vLLM 0.19.1)
These cost the most time; documented so you don't repeat them:
download.pytorch.orgfirewalled β install torch from PyPI (default cu128 works on driver 575).- HF repos are Xet-backed β install
hf_xet(needs β₯24 GB RAM or it core-dumps);hf_transferdoesn't help Xet repos. trlversion conflict β drop it; usetransformers.Trainer+peftdirectly.- transformers 4.57 needs
tokenizer_classin tokenizer_config (repo lacks it + no AutoTokenizer auto_map) β patchtokenizer_class: PreTrainedTokenizerFast. - NCCL multi-GPU "unhandled cuda error" in the cgroup (P2P/SHM/cuMem disable doesn't fix) β
vLLM TP=1 for gen/bench. SFT/quant use transformers
device_mapnaive-PP (no NCCL). - A ~72 GB per-process GPU memory cap + ~129 GB virtual-address cap (RLIMIT_AS) on the cgroup. A 64 GB model can't load both within them on multi-GPU. Quant: load on CPU, stream layers to GPU.
caching_allocator_warmup(transformers 4.57) OOMs on a big contiguous block despite free mem β no-op it for quant (CPU-load); but KEEP it for SFT (device_map GPU load relies on it).- RTN's
DataFreePipeline.dispatch_modelonloads the whole model β OOM on the cap. No-op patchllmcompressor.pipelines.data_free.pipeline.dispatch_modelso RTN runs on CPU (it's pure weight math). - GPTQ accumulation β pass
sequential_targets=["SarvamMoEDecoderLayer"]tooneshotso it onloads/offloads one decoder layer at a time (GPU stays ~5%). - A foreign non-SLURM process can pin a GPU SLURM still allocates β pick free GPUs at runtime via
torch.cuda.mem_get_info; avoid the busy one. - vLLM HTTP server 500s (starlette 1.x + prometheus_fastapi_instrumentator incompat) β bench
offline (
LLMclass), with anif __name__=='__main__'guard (vLLM uses spawn). - NFS mmap / RSS limits when loading 64 GB β keep base on home disk (reclaimable page cache), not tmpfs (non-reclaimable, hard-charges the cgroup).
4b. Response-economy chat-template trick β DOES NOT transfer to Sarvam (measured)
A config-only "answer concisely / omit nothing / no filler" instruction in the chat template gives
non-reasoning models (e.g. Gemma-4-E4B-it) a large token cut (~β44%) with no quality loss. Measured on
Sarvam-30B (test_template.py, eval prompts, temp 1.0), the same instruction backfires:
| Total tokens | Math | Questions | |
|---|---|---|---|
| Baseline | 48,416 | 1.00 | 1.00 |
| + economy instruction | 99,823 (2.06Γ) | 0.78 (fails 80% floor) | 0.87 |
Sarvam is a thinking model β the brevity instruction makes it reason more (about how to be
economical) inside <think>, doubling tokens, and the meta-reasoning disrupts math. The lever that helps
direct-answer models actively hurts reasoning models. Token-axis is closed here via both distillation
and template instruction.
5. Honest conclusion
For Sarvam-30B on this challenge, W4A16 (attention+shared GPTQ, experts RTN) is the energy optimum achievable with serving-safe, legal tools. The brevity/token lever β which would compound on top β is not reachable via lightweight LoRA distillation for this model. Most "distilled" competitor entries are 8-bit (higher energy) or quality-recovery rather than token-reduction; the byte axis (4-bit) is where the real energy win lives.
Scripts in this repo are provided as-is for reference. Base model Β© Sarvam AI.