GLM-Image Local Tests
Local-only parity and smoke tests for the glm_image port: FastVideo vs the
official HF transformers + diffusers GLM-Image implementation. These are
not run in CI — they need the full weights, CUDA, and (for the reference
side) the diffusers GLM-Image classes; each test skips with an actionable
message when a prerequisite is missing.
What you need
| Weights | zai-org/GLM-Image → official_weights/glm_image (~30 GB: AR encoder 4 shards, transformer 3 shards, vae + text_encoder) |
| Runtime deps | transformers>=5.0.0 (first release with the AR encoder GlmImageForConditionalGeneration) and diffusers>=0.38.0 — both committed in pyproject.toml |
| Test-only deps | the diffusers GlmImageTransformer2DModel / GlmImagePipeline reference classes need diffusers>=0.37.0.dev0 (used only by the parity tests) |
Download weights (from the repo root; cache via HF_HOME if / is small):
python ".agents/skills/add-model-01-prep/scripts/download_hf_weights.py" \
"zai-org/GLM-Image" "official_weights/glm_image"
Optionally install the diffusers reference classes to run the parity side:
uv pip install -U "git+https://github.com/huggingface/diffusers.git@ff3b86b4"
Parity was checked against transformers a65bf6c9 + diffusers ff3b86b4.
Parity results (GB200)
| Component | Test | Result |
|---|---|---|
transformer (DiT) |
test_transformer_parity.py |
fp32 cosine 1.000000 (100% within 5e-3); bf16 cosine 0.999969 (element-wise rounding over 30 layers vs a different SDPA kernel — not a bug; RoPE proven exactly equivalent). fp32 + bf16 + strict-load |
vae (AutoencoderKL) |
test_vae_parity.py |
decode + encode + config, 3/3 |
text_encoder (T5) + ByT5 tokenizer |
test_t5_parity.py |
tokenizer + glyph extract + embeds, 3/3 |
vision_language_encoder (GLM-4-9B AR) |
test_ar_parity.py |
surface + to/eval + greedy determinism, 3/3 |
pipeline (T2I) |
test_pipeline_parity.py |
injected-input determinism: latent cosine 0.999997, decoded-image MAE 0.159/255. validity + deterministic parity, 2/2 |
pipeline (I2I / edit) |
test_edit_pipeline_parity.py |
validity / wiring gate (condition image enters via a KV-cache write pass, not noise-the-latent). diffusers distribution parity deferred (stochastic AR) |
processor (GlmImageProcessor) and scheduler (FlowMatchEulerDiscreteScheduler,
shared flow-matching) are exercised via the AR and pipeline tests.
Run:
pytest tests/local_tests/glm_image/test_transformer_parity.py -v -s
pytest tests/local_tests/glm_image/test_vae_parity.py -v -s
pytest tests/local_tests/glm_image/test_t5_parity.py -v -s
pytest tests/local_tests/glm_image/test_ar_parity.py -v -s
pytest tests/local_tests/glm_image/test_pipeline_parity.py -v -s
pytest tests/local_tests/glm_image/test_edit_pipeline_parity.py -v -s
Design notes (load-bearing — don't regress)
- Pipeline parity is by injection. Full-pipeline pixel parity is ill-posed
(stochastic AR + independent latent RNG), so the pipeline tests inject matched
(prompt_embeds, prior_token_ids, latents)into both the diffusers pipeline and the realGlmImageDenoisingStage, then compare denoised latents + decoded image — not raw end-to-end pixels. - HF imports are confined to the lazy-wrapper loader
fastvideo/models/encoders/glm_image_ar_loader.py, never the production loaders (production-boundary rule). - Tokenizer is ByT5, matching the diffusers reference (not plain T5);
compute_glyph_embedsmirrors diffusers_get_glyph_embeds. - Model-specific stages live under
fastvideo/pipelines/basic/glm_image/stages/per theadd-modelFiles Map. - Param mapping is handled in-place at load via
param_names_mapping(fastvideo/configs/models/dits/glm_image.py) + the native VAE/encoder configs — no conversion script; the loader raises on any unmatched param andtest_transformer_strict_load.pyasserts completeness withstrict=True.