Implement PPL evaluator for qwen 3 0.6B - #1209
Conversation
DingmaomaoBJTU
left a comment
There was a problem hiding this comment.
Please see the inline comments.
- Fix mypy type errors across genai_causal_lm, evaluate, and text_generation_evaluator - Remove unused module-level logger (CodeQL) - Gate directory-as-genai-bundle on genai_config.json so local HF checkpoints still route by model_id - Validate bundle ONNX files recursively (rglob) to allow bundle-relative subpaths - Force streaming corpus load with in-order early-stop; honor num_tokens as the token budget - Reject --perf-iterations for text-generation baselines instead of hitting the latency path - Stream per-position logits from forward() and accumulate NLL per step to avoid materializing full (seqlen, vocab) tensors
DingmaomaoBJTU
left a comment
There was a problem hiding this comment.
A few additional issues found while reviewing the updated changes.
Enable genai bundle compilation in the text-generation loader (matching winml perf's safety path) and mirror the base evaluator's local-directory branch so save_to_disk datasets load via load_from_disk. Drop the not-yet-runnable Qwen3 text-generation E2E registry entry and its baseline cache line until E2E build support lands.
Address E1-E3 review comments: force EP override for explicit --device on a genai bundle, preserve user --column values when injecting a default dataset, and neutralize bundle search options so teacher forcing is not masked.
DingmaomaoBJTU
left a comment
There was a problem hiding this comment.
LGTM. Well-structured text-generation perplexity evaluator. Key observations (non-blocking):
-
Teacher forcing via set_logits is clever — the one-token-at-a-time replay with forced logits correctly scores all positions while reusing the genai generator's KV cache. The neutralization of bundle search constraints (min_length=0,
o_repeat_ngram_size=0, etc.) is essential and properly tested. -
_step_nll is numerically sound — the log-sum-exp with max subtraction avoids overflow, and the f64 upcast avoids precision issues on large vocabs.
-
Default dataset column merge fix is a nice UX improvement — preserving user --column values while filling in defaults means --column num_tokens=1024 --column seqlen=512 works without needing to repeat --dataset.
-
Minor: _load_corpus_tokens uses model.encode(text) + 2 per row as an approximation, which could over-shoot significantly for short rows (many BPE boundary tokens). Fine for the first iteration, but a comment noting this is conservative (overcounts) would help.
-
Minor: HFCausalLM.forward computes the full sequence in one shot (model(input_ids=...)) which works for short seqlens but could OOM on very long sequences on GPU. Since the default seqlen is 2048 and this is a baseline path, acceptable — just noting for awareness.
Add teacher-forced perplexity evaluation for genai causal LMs
Summary
Adds a
text-generationeval task towinml evalthat computes teacher-forced, disjoint fixed-length perplexity (PPL) for autoregressive causal LMs. The same evaluator scores both onnxruntime-genai bundles (ONNX, on-device) and PyTorch fp32 baselines through a single model-agnostic contract, so ONNX-vs-baseline accuracy regression can be tracked in the existing e2e harness.Validated on Qwen/Qwen3-0.6B against wikitext-2-raw-v1 (test split):
What's included
Evaluator (
eval/text_generation_evaluator.py)WinMLTextGenerationEvaluator: model-agnostic, zero-branch. Builds a token corpus, splits it into non-overlappingseqlenblocks, and computes PPL via a numerically stable log-sum-exp NLL accumulator.encode(text) -> list[int]andforward(ids) -> outwhereout.logitsis(1, len(ids)-1, vocab)with row i predicting token i+1. No per-model conditionals.Model layer (
models/winml/genai_causal_lm.py)WinMLGenaiCausalLM: causal-LM inference over an onnxruntime-genai bundle directory (loaded as-is,compile=False— building/compiling iswinml build's job).HFCausalLM: PyTorch fp32 adapter implementing the same contract (lazy torch/transformers import,add_special_tokens=Falseto reproduce POC tokenization). Lives insrcso the baseline script and the evaluator share one code path.CausalLMOutputshared output dataclass.Wiring
eval/evaluate.py: registers thetext-generationevaluator, a default wikitext-2-raw-v1 dataset, and_load_genai_causal_lm(validates the bundle dir hasgenai_config.json+.onnxfiles before loading).commands/eval.py:_resolve_model_pathnow routes an existing local directory in-mtomodel_path(so a genai bundle dir is read from disk instead of being treated as a Hub id).utils/eval_utils.py:_TEXT_GENERATION_SCHEMAwithinput_column(defaulttext),num_tokens(default 8192),seqlen(default 2048).Baseline / accuracy harness
run_pytorch_baseline.py: text-generation branch uses the sharedHFCausalLMadapter.accuracy.py: adds aperplexitycompare strategy (delta_relative, warn 0.05 / fail 0.10, lower-is-better).testsets/models_with_acc.json+cache/baseline_cache.json: Qwen/Qwen3-0.6B entry with cached fp32 baseline (PPL 23.645425).Tests
test_text_generation_evaluator.py(14): block-chunking protocol, corpus loading, uniform-logits → PPL = vocab-size sanity, NLL cross-entropy reference.test_genai_causal_lm.py(9):CausalLMOutput, encode/forward contract, float32 conversion, last-row trimming.