Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
08875c2
Upload lm_eval results for qwen 3 8B (mmlu + wikitext
danielkorzekwa Oct 12, 2026
62b3159
fix formatting
danielkorzekwa Oct 12, 2026
b7e93cc
improving text clarity
danielkorzekwa Oct 13, 2026
f598396
Improve eval results formatting
danielkorzekwa Oct 13, 2026
8e22e30
Remove raw lm_eval results
danielkorzekwa Oct 13, 2026
5616ccd
Simplify nel configs (tokenizer_path variable), not using hf_cache
danielkorzekwa Oct 13, 2026
ec7d244
Fix teacher checkpoint symlinks and align evaluation tokenizer paths
danielkorzekwa Oct 13, 2026
c693835
Use the same oc.env:MODEL_CHECKPOINT across different evaluated models
danielkorzekwa Oct 13, 2026
20905a6
move evaluation section to shared mmlu yaml file
danielkorzekwa Oct 13, 2026
f8b4b17
Remove reference to unused /hf_cache - replaced with /checkpoint
danielkorzekwa Oct 13, 2026
eccbd99
Create a dedicated deployment nel file
danielkorzekwa Oct 13, 2026
bd9bfbd
Created a shared slurm execution nel config file
danielkorzekwa Oct 13, 2026
3799ef6
improve nyl config clarify
danielkorzekwa Oct 13, 2026
f9a15b5
Add per subject mmlu results
danielkorzekwa Oct 13, 2026
3514389
Add explictly arguments for wikitext evaluation task into wikitext.yaml
danielkorzekwa Oct 14, 2026
c55cc26
Clean up nel config files for wikitext
danielkorzekwa Oct 14, 2026
68f124c
Simplify nel config files
danielkorzekwa Oct 14, 2026
64303ae
improve clarity of eval readme
danielkorzekwa Oct 14, 2026
a396147
clarify eval running configuration
danielkorzekwa Oct 14, 2026
91b5687
Merge branch 'main' into dkorzekws/hf_qad_nel_results
danielkorzekwa Oct 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/llm_qat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ The cached dataset is stored under `.dataset_cache/tokenized/` by default (confi

## Results

\[Coming Soon\]
See [Qwen3-8B evaluation results](evaluation/README.md) for the teacher, PTQ, and PTQ → QAD comparison.

## Native Fake-Quantized Evaluation

Expand Down
116 changes: 116 additions & 0 deletions examples/llm_qat/evaluation/README.md
Comment thread
danielkorzekwa marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Evaluation

## Results

Qwen3-8B was evaluated using NEL with vLLM on one H100 per evaluation. QAD starts from the PTQ checkpoint and uses the BF16 model as teacher.

| Checkpoint | Deployed precision | MMLU accuracy ↑ (14,042 questions) | WikiText-2 perplexity ↓ (241,335 words) |
|---|---|---:|---:|
| Teacher (BF16) | BF16 | 74.94% | 13.485 |
| PTQ (NVFP4) | NVFP4 weights / BF16 activations | 73.59% | 13.883 |
| PTQ → QAD (NVFP4) | NVFP4 weights / BF16 activations | 73.76% | 13.684 |

QAD improves both metrics over PTQ but remains below the BF16 teacher. The quantized checkpoints use Marlin's W4A16 fallback on H100; these results do not measure native NVFP4 W4A4 execution on Blackwell. KV cache is BF16 for all checkpoints.
Comment on lines +7 to +13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION] The headline table reports single-run point estimates and then draws a conclusion the margins do not clearly support.

The MMLU gap between PTQ (73.59%) and PTQ → QAD (73.76%) is 0.17 pp, i.e. about 24 questions out of 14,042. The binomial standard error at n=14,042 and p≈0.74 is ~0.37 pp, so the standard error on the difference is larger than the difference itself. Yet the text states flatly:

QAD improves both metrics over PTQ but remains below the BF16 teacher.

The per-subject breakdown makes this visible: QAD is worse than PTQ on Formal logic (60.32% vs 66.67%), College computer science (68.00% vs 72.00%), and High school chemistry (71.92% vs 72.91%), while better elsewhere — the pattern of noise, not a uniform shift. The WikiText perplexity improvement (13.883 → 13.684) is the stronger of the two signals and recovers ~50% of the teacher gap, which is a more defensible claim.

Suggested rewording, keeping the result but matching the evidence:

QAD recovers about half of the PTQ perplexity gap to the teacher (13.883 → 13.684 vs 13.485). The MMLU difference (+0.17 pp) is within single-run noise at this sample size; the per-subject breakdown below shows gains and losses in both directions. Both quantized checkpoints remain below the BF16 teacher.

The sibling tutorial reports repeat counts and ± figures per benchmark (examples/megatron_bridge/tutorials/Qwen3.6-35B-A3B/README.md); noting here that these are 1-run numbers would align the two and set reader expectations. The existing --seed 42 gives reproducibility of a single sample, not a variance estimate.


## Run evaluations

From `examples/llm_qat/evaluation/`, configure access to your Slurm cluster:

```bash
pip install "nemo-evaluator-launcher[all]==0.2.6"

export NEL_SLURM_HOST="<cluster-login-host>"
export SLURM_USER="<cluster-username>"
export NEL_SLURM_ACCOUNT="<slurm-account>"
export NEL_SLURM_PARTITION="<slurm-partition>"
export NEL_OUTPUT_DIR="/shared/path/to/evaluation-results"
export MODEL_CHECKPOINT="/shared/path/to/checkpoint"

# See execution.mounts.evaluation in nel_configs/h100_slurm_execution.yaml
# for why the checkpoint mount is passed via -o.
# Run MMLU and WikiText.
nemo-evaluator-launcher run --config nel_config.yaml \
-o deployment.served_model_name=qwen3-8b-ptq \
-o "++execution.mounts.evaluation={$MODEL_CHECKPOINT: /checkpoint}"
Comment on lines +26 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMPORTANT Compatibility] The committed configs are not the ones that produced the results table above, and the PR body lists the cross-check as pending ("rerun both benchmarks on cw-dfw-cs-001 ... and compare with the published results").

That matters because this README presents the table as reproducible by running the command in this block. Two concrete gaps between this block and the commands that actually generated the numbers (per the PR description):

  • The PR commands pass -t lm-evaluation-harness.mmlu / .wikitext separately, each with its own -o deployment.port=.... This block runs both tasks together on the shared default port — see my note on nel_config.yaml.
  • The PR commands do not use ++execution.mounts.evaluation=...; the evaluator-mount workaround is new in this PR and therefore untested against the published run.

Please either land this after the rerun confirms the numbers, or add a one-line note here stating which NEL version and config revision produced the table so a reader who gets different numbers knows where to look.

Also on the -o override itself:

-o "++execution.mounts.evaluation={$MODEL_CHECKPOINT: /checkpoint}"

Hydra's override grammar does not accept an arbitrary filesystem path as a bare dict key — a key like /lustre/fs1/.../ckpt is not a valid unquoted ID. Quoting it is the safe form and costs nothing:

-o "++execution.mounts.evaluation={'$MODEL_CHECKPOINT': /checkpoint}"

Worth confirming during the rerun, since this is the documented primary path and a parse error here blocks every evaluation.

```

Use the BF16 teacher or an exported Hugging Face PTQ/QAD checkpoint. The checkpoint directory must be accessible on the cluster and contain the model files, without symlinks to unmounted locations.

For the teacher, download a self-contained directory on the cluster; no additional `/blobs` mounts are needed:

```bash
hf download Qwen/Qwen3-8B --local-dir /shared/path/to/qwen3-8b-teacher
export MODEL_CHECKPOINT="/shared/path/to/qwen3-8b-teacher"
```

Add `--dry-run` to preview, or `-t lm-evaluation-harness.mmlu` / `-t lm-evaluation-harness.wikitext` to select one benchmark. NEL 0.2.6 launches one Slurm job per benchmark; results are saved under `NEL_OUTPUT_DIR`.

<details>
<summary>MMLU accuracy by subject</summary>

| Subject | Questions | Teacher (BF16) | PTQ (NVFP4) | PTQ → QAD (NVFP4) |
|---|---:|---:|---:|---:|
| Abstract algebra | 100 | 55.00% | 56.00% | 56.00% |
| Anatomy | 135 | 70.37% | 68.89% | 68.89% |
| Astronomy | 152 | 90.13% | 88.82% | 90.13% |
| Business ethics | 100 | 78.00% | 77.00% | 79.00% |
| Clinical knowledge | 265 | 78.49% | 80.38% | 79.62% |
| College biology | 144 | 87.50% | 86.81% | 87.50% |
| College chemistry | 100 | 56.00% | 59.00% | 56.00% |
| College computer science | 100 | 73.00% | 72.00% | 68.00% |
| College mathematics | 100 | 58.00% | 59.00% | 58.00% |
| College medicine | 173 | 79.19% | 78.03% | 78.03% |
| College physics | 102 | 64.71% | 59.80% | 63.73% |
| Computer security | 100 | 81.00% | 81.00% | 80.00% |
| Conceptual physics | 235 | 84.68% | 85.11% | 84.68% |
| Econometrics | 114 | 65.79% | 64.91% | 68.42% |
| Electrical engineering | 145 | 80.69% | 80.69% | 82.76% |
| Elementary mathematics | 378 | 78.84% | 75.40% | 76.19% |
| Formal logic | 126 | 67.46% | 66.67% | 60.32% |
| Global facts | 100 | 52.00% | 44.00% | 43.00% |
| High school biology | 310 | 90.97% | 91.29% | 91.61% |
| High school chemistry | 203 | 75.86% | 72.91% | 71.92% |
| High school computer science | 100 | 86.00% | 86.00% | 86.00% |
| High school European history | 165 | 85.45% | 85.45% | 87.27% |
| High school geography | 198 | 86.87% | 86.36% | 86.87% |
| High school government and politics | 193 | 93.26% | 92.23% | 93.26% |
| High school macroeconomics | 390 | 81.03% | 79.49% | 80.00% |
| High school mathematics | 270 | 53.70% | 50.00% | 51.85% |
| High school microeconomics | 238 | 93.70% | 91.60% | 92.02% |
| High school physics | 151 | 72.85% | 69.54% | 70.20% |
| High school psychology | 545 | 91.19% | 90.46% | 90.83% |
| High school statistics | 216 | 78.24% | 75.93% | 75.46% |
| High school US history | 204 | 86.27% | 85.29% | 83.82% |
| High school world history | 237 | 86.50% | 85.65% | 85.65% |
| Human aging | 223 | 74.44% | 75.34% | 73.99% |
| Human sexuality | 131 | 83.21% | 88.55% | 84.73% |
| International law | 121 | 73.55% | 73.55% | 73.55% |
| Jurisprudence | 108 | 81.48% | 83.33% | 79.63% |
| Logical fallacies | 163 | 82.21% | 80.37% | 82.21% |
| Machine learning | 112 | 64.29% | 64.29% | 65.18% |
| Management | 103 | 87.38% | 89.32% | 87.38% |
| Marketing | 234 | 93.59% | 92.74% | 91.88% |
| Medical genetics | 100 | 83.00% | 82.00% | 82.00% |
| Miscellaneous | 783 | 85.82% | 83.78% | 83.40% |
| Moral disputes | 346 | 76.01% | 72.25% | 73.99% |
| Moral scenarios | 895 | 55.42% | 53.85% | 53.85% |
| Nutrition | 306 | 81.05% | 81.70% | 80.07% |
| Philosophy | 311 | 77.81% | 74.28% | 76.85% |
| Prehistory | 324 | 83.95% | 82.72% | 83.02% |
| Professional accounting | 282 | 59.22% | 58.51% | 57.09% |
| Professional law | 1,534 | 51.17% | 49.02% | 50.26% |
| Professional medicine | 272 | 81.99% | 80.51% | 80.88% |
| Professional psychology | 612 | 78.59% | 76.31% | 76.14% |
| Public relations | 110 | 70.00% | 68.18% | 70.00% |
| Security studies | 245 | 79.59% | 80.00% | 80.41% |
| Sociology | 201 | 86.57% | 84.58% | 84.08% |
| US foreign policy | 100 | 84.00% | 83.00% | 85.00% |
| Virology | 166 | 56.02% | 52.41% | 53.61% |
| World religions | 171 | 87.13% | 85.38% | 84.21% |

</details>

## File structure

- `nel_config.yaml`: entry point containing the MMLU and WikiText recipes.
- `nel_configs/`: shared Slurm execution and vLLM deployment settings.
61 changes: 61 additions & 0 deletions examples/llm_qat/evaluation/nel_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# MMLU and WikiText recipes for nemo-evaluator-launcher==0.2.6.
defaults:
- execution: slurm/default
- nel_configs/h100_slurm_execution@_global_
- deployment: vllm
- nel_configs/qwen3_8b_vllm_deployment@_global_
- _self_
deployment:
# Override when launching to identify the checkpoint, e.g., -o deployment.served_model_name=qwen3-8b-ptq
served_model_name: qwen3-8b
port: 18100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMPORTANT Compatibility] Both tasks share a single deployment.port, but the README's primary command launches both of them at once.

The README says "NEL 0.2.6 launches one Slurm job per benchmark", and h100_slurm_execution.yaml sets sbatch_extra_flags.exclusive: false, so the two jobs can be co-scheduled on one node. Both then start a vLLM server on 18100 and both eval clients connect to http://127.0.0.1:18100/... — one server fails to bind, or an eval client talks to the other benchmark's server.

This is exactly the hazard the PR description works around manually ("Each evaluation uses a different port to avoid collisions between these runs if jobs share a node"), but the committed README does not carry that workaround: its one-liner runs both tasks with the shared default port and no -o deployment.port=....

Three ways to close it, any one is enough:

  1. Add mode: sequential to execution in nel_configs/h100_slurm_execution.yaml — this is what every other eval config in the repo does (examples/megatron_bridge/tutorials/*/eval_configs/*.yaml, examples/pruning/minitron/.../nemo_evaluator.yaml).
  2. Set exclusive: true so jobs never share a node.
  3. Keep the parallel launch but document per-task ports in the README, i.e. replace the combined command with the two -t ... -o deployment.port=... invocations from the PR description.

Option 1 is the smallest change and matches the existing convention.

evaluation:
nemo_evaluator_config:
target:
api_endpoint:
api_key_name:
adapter_config:
use_reasoning: false
use_request_logging: true
use_response_logging: true
max_logged_requests: 2
max_logged_responses: 2
tasks:
- name: lm-evaluation-harness.mmlu
endpoint_type: completions
container: nvcr.io/nvidia/eval-factory/lm-evaluation-harness:26.03
nemo_evaluator_config:
config:
params:
parallelism: 1
request_timeout: 300
max_retries: 2
temperature:
Comment on lines +29 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMPORTANT Performance] parallelism: 1 for MMLU is very likely to blow the 4 h walltime in h100_slurm_execution.yaml, and it leaves the GPU almost idle.

lm-eval's mmlu is loglikelihood-scored: 14,042 questions × 4 choices ≈ 56k requests. At parallelism: 1 those are issued strictly one at a time, and --max-num-seqs 2 on the server means no batching to compensate. Even at a generous 0.25 s per round-trip that is ~3.9 h — and these are NVFP4 checkpoints running through vLLM's W4A16 Marlin fallback on H100, i.e. the slow path the README itself calls out. A walltime overrun kills the job after hours of compute with no results.

WikiText uses parallelism: 2 with no stated reason for the asymmetry, which suggests 1 here may be incidental rather than deliberate.

Suggested fix — raise MMLU concurrency and the server's batch ceiling together, since num_concurrent is interpolated from parallelism and is capped in practice by --max-num-seqs:

# nel_config.yaml, mmlu task
            parallelism: 16
# nel_configs/qwen3_8b_vllm_deployment.yaml
    --max-num-seqs 16

Loglikelihood scoring is order-independent, so concurrency does not change the MMLU numbers. If parallelism: 1 was deliberate (e.g. to pin determinism for the published table), please say so in a comment here and raise walltime to match the measured runtime.

top_p:
max_new_tokens:
task: mmlu
extra:
num_fewshot: 5
tokenizer: ${deployment.tokenizer_path}
tokenizer_backend: huggingface
tokenized_requests: true
args: --seed 42 --model_args "base_url=http://127.0.0.1:${deployment.port}/v1/completions,model=${deployment.served_model_name},tokenized_requests=True,tokenizer=${deployment.tokenizer_path},tokenizer_backend=huggingface,num_concurrent=${..parallelism},timeout=${..request_timeout},max_retries=${..max_retries},stream=False,max_length=32768"
- name: lm-evaluation-harness.wikitext
endpoint_type: completions
container: nvcr.io/nvidia/eval-factory/lm-evaluation-harness:26.03
nemo_evaluator_config:
config:
params:
parallelism: 2
request_timeout: 300
max_retries: 2
temperature:
top_p:
max_new_tokens:
task: wikitext
extra:
num_fewshot: 0
tokenizer: ${deployment.tokenizer_path}
tokenizer_backend: huggingface
tokenized_requests: true
args: --seed 42 --model_args "base_url=http://127.0.0.1:${deployment.port}/v1/completions,model=${deployment.served_model_name},tokenized_requests=True,tokenizer=${deployment.tokenizer_path},tokenizer_backend=huggingface,num_concurrent=${..parallelism},timeout=${..request_timeout},max_retries=${..max_retries},stream=False,max_length=2048"
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Shared single-GPU Slurm execution settings for H100 evaluation.
# NEL mounts the server checkpoint automatically; pass the evaluator mount with -o.
execution:
hostname: ${oc.env:NEL_SLURM_HOST}
username: ${oc.env:SLURM_USER}
account: ${oc.env:NEL_SLURM_ACCOUNT}
partition: ${oc.env:NEL_SLURM_PARTITION}
output_dir: ${oc.env:NEL_OUTPUT_DIR}
num_nodes: 1
ntasks_per_node: 1
gres: gpu:1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION] gres: gpu:1 is set but gpus_per_node is not. Every other Slurm eval config in the repo sets both (gpus_per_node: 4 + gres: "gpu:4" in examples/megatron_bridge/tutorials/Qwen3.6-35B-A3B/eval_configs/*.yaml; 8/"gpu:8" in the Nemotron ones). On clusters where NEL/Slurm derives the task's visible device count from --gpus-per-node rather than --gres, omitting it can land the job with no GPU visible to the container. Adding gpus_per_node: 1 here costs nothing and makes the single-GPU intent explicit.

Same file: execution.type: slurm is also set explicitly in all the sibling configs; relying solely on the execution: slurm/default defaults entry is a behavioral difference worth confirming against NEL 0.2.6.

walltime: 04:00:00
sbatch_extra_flags:
exclusive: false
mem: 64G
mounts:
mount_home: false
deployment: {}
# NEL 0.2.6 validates this field as a dictionary before resolving oc.create,
# so the expression deriving the mount from MODEL_CHECKPOINT was rejected.
# The README therefore supplies a concrete mapping via the CLI instead:
# -o "++execution.mounts.evaluation={$MODEL_CHECKPOINT: /checkpoint}"
evaluation: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Shared vLLM deployment for Qwen3-8B teacher, PTQ, and PTQ → QAD.
deployment:
image: vllm/vllm-openai:v0.28.0-cu129
checkpoint_path: ${oc.env:MODEL_CHECKPOINT}
tensor_parallel_size: 1
data_parallel_size: 1
command: >-
vllm serve /checkpoint
--served-model-name ${deployment.served_model_name}
--host 0.0.0.0
--port ${deployment.port}
--dtype bfloat16
--tensor-parallel-size ${deployment.tensor_parallel_size}
--data-parallel-size ${deployment.data_parallel_size}
--max-model-len 32768
--max-num-seqs 2
--gpu-memory-utilization 0.85
--seed 42
--kv-cache-dtype auto

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[IMPORTANT Compatibility] --kv-cache-dtype auto does not guarantee the README's claim that "KV cache is BF16 for all checkpoints."

With auto, vLLM reads the KV-cache setting from the checkpoint's own quantization_config — the sibling config in this repo documents exactly this behavior for ModelOpt exports:

"Serves BF16 and NVFP4 unchanged: vLLM reads the FP8 KV-cache setting from the checkpoint's own quantization_config."
— examples/megatron_bridge/tutorials/Qwen3.6-35B-A3B/eval_configs/gpqa.yaml

So if the exported PTQ/QAD checkpoints carry kv_cache_quant_algo: FP8, these runs used an FP8 KV cache while the BF16 teacher used BF16 — the three rows of the results table would not be KV-cache-comparable, and the stated PTQ-vs-teacher delta would partly reflect KV quantization rather than weight quantization.

Two options:

  • If BF16 KV cache is the intent, pin it rather than inferring it. Replace --kv-cache-dtype auto with an explicit dtype so the config enforces what the README asserts.
  • If the checkpoints do specify FP8 KV cache, drop or amend the README's "KV cache is BF16 for all checkpoints" sentence and state the actual per-checkpoint setting.

Please check quantization_config.kv_cache_quant_algo in the two NVFP4 exports to determine which applies.

Separately on line 14, --dtype bfloat16: for a quantized checkpoint this sets the activation/compute dtype only and leaves the quantized weights alone, so it is consistent with the "BF16 activations" claim — but worth a short inline comment saying so, since a reader may otherwise read it as dequantizing the checkpoint.

tokenizer_path: /checkpoint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION] tokenizer_path: /checkpoint is an in-container path that is only valid if the user remembers the ++execution.mounts.evaluation={...: /checkpoint} override from the README.

It is interpolated into both tasks' extra.tokenizer and into the --model_args tokenizer=... string in nel_config.yaml, so forgetting the -o fails inside the eval container with a confusing "tokenizer path not found" rather than a clear "you forgot a mount." Given that h100_slurm_execution.yaml deliberately leaves evaluation: {} empty and defers the mount to the CLI, the coupling is easy to miss.

A pointer comment here would make the dependency discoverable at the point of use:

  # Valid only when the checkpoint is mounted at /checkpoint in the evaluation
  # container; see the -o ++execution.mounts.evaluation override in ../README.md.
  tokenizer_path: /checkpoint

Loading