Repository navigation
ATOM vLLM Nightly #12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: ATOM vLLM Nightly | |
| on: | |
| schedule: | |
| # Nightly at 00:00 Beijing time (16:00 UTC on the previous day) | |
| - cron: '0 16 * * *' | |
| workflow_dispatch: | |
| # Manual runs use GitHub Actions' built-in branch dropdown ("Use workflow | |
| # from") as the ATOM branch selector, so users do not need to type branch | |
| # names. | |
| inputs: | |
| model_slot_1: | |
| description: "Manual selection slot 1: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| model_slot_2: | |
| description: "Manual selection slot 2: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| model_slot_3: | |
| description: "Manual selection slot 3: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| model_slot_4: | |
| description: "Manual selection slot 4: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| model_slot_5: | |
| description: "Manual selection slot 5: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| model_slot_6: | |
| description: "Manual selection slot 6: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| model_slot_7: | |
| description: "Manual selection slot 7: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| model_slot_8: | |
| description: "Manual selection slot 8: choose an accuracy model case from .github/benchmark/oot_models_accuracy.json." | |
| type: choice | |
| default: none | |
| options: | |
| - none | |
| - run_p0 | |
| - run_p1 | |
| - run_p2 | |
| - run_all | |
| - Qwen3-235B-A22B-Instruct-2507-FP8 TP8+EP8 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP2 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8 TP4 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP1 | |
| - Qwen3-Next-80B-A3B-Instruct-FP8-MTP TP4 | |
| - Qwen3.5-397B-A17B-FP8 TP8 | |
| - Qwen3.5-397B-A17B-FP8 TP4 | |
| - Qwen3.5-397B-A17B TP8 | |
| - Qwen3.5-397B-A17B-MXFP4 TP4 | |
| - Meta-Llama-3.1-405B-Instruct-FP8 TP8 | |
| - Llama-3.1-8B-Instruct TP1 | |
| - Kimi-K2-Thinking-MXFP4 TP4 | |
| - Kimi-K2-Thinking-MXFP4 TP8 | |
| - Kimi-K2.5-MXFP4 TP4 | |
| - Kimi-K2.5-MXFP4 TP8 | |
| - Kimi-K3 TP8 | |
| - DeepSeek-R1-FP8 TP8 | |
| - DeepSeek-R1-FP8 DP8+EP8 | |
| - DeepSeek-R1-0528-MXFP4 TP8 | |
| - DeepSeek-V4-Pro TP8 | |
| - DeepSeek-V3.2-FP8 TP4 | |
| - DeepSeek-V3.2-FP8 TP8 | |
| - DeepSeek-V3.2-FP8 MTP TP4 | |
| - DeepSeek-V3.2-FP8 PTPC TP4 | |
| - gpt-oss-120b TP1 | |
| - gpt-oss-120b TP2 | |
| - gpt-oss-120b TP8 | |
| - MiniMax-M2.5 TP2 | |
| - MiniMax-M2.5 TP4 | |
| - GLM-4.7-FP8 TP4 | |
| - GLM-4.7-FP8 TP8 | |
| - GLM-4.7-FP8 MTP TP4 | |
| - GLM-4.7-FP8 MTP TP8 | |
| - GLM-5.1-FP8 TP8 | |
| - GLM-5.2-FP8 TP4 | |
| - GLM-5.2-FP8 MTP TP4 | |
| - GLM-5.2-MXFP4 TP4 | |
| - GLM-5.2-MXFP4 MTP TP4 | |
| oot_image: | |
| description: "Optional atom-vLLM image override. Leave empty to keep the existing nightly/manual build behavior." | |
| required: false | |
| type: string | |
| default: "" | |
| rebuild_atom_base_from_dockerfile: | |
| description: "Full build image before validation" | |
| required: false | |
| type: boolean | |
| default: false | |
| upload_accuracy_to_dashboard: | |
| description: "Optional: upload OOT accuracy results to dashboard after this manual run" | |
| required: false | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| ATOM_BASE_NIGHTLY_IMAGE: rocm/atom-dev:latest | |
| NIGHTLY_OOT_IMAGE_TAG: rocm/atom-dev:vllm-latest | |
| VALIDATION_IMAGE_REPO: rocm/atom-dev | |
| GPU_ARCH: "gfx942;gfx950" | |
| AITER_REPO: "https://github.com/ROCm/aiter.git" | |
| AITER_COMMIT: "HEAD" | |
| jobs: | |
| # ---------- OOT full validation path (schedule / workflow_dispatch) ---------- | |
| prepare-oot-image: | |
| name: Prepare validation image | |
| runs-on: build-only-atom | |
| permissions: | |
| actions: read | |
| contents: read | |
| outputs: | |
| oot_image_tag: ${{ steps.meta.outputs.oot_image_tag }} | |
| cleanup_temp_image: ${{ steps.meta.outputs.cleanup_temp_image }} | |
| model_matrix_slot_1: ${{ steps.meta.outputs.model_matrix_slot_1 }} | |
| model_matrix_slot_2: ${{ steps.meta.outputs.model_matrix_slot_2 }} | |
| model_matrix_slot_3: ${{ steps.meta.outputs.model_matrix_slot_3 }} | |
| slot_1_has_models: ${{ steps.meta.outputs.slot_1_has_models }} | |
| slot_2_has_models: ${{ steps.meta.outputs.slot_2_has_models }} | |
| slot_3_has_models: ${{ steps.meta.outputs.slot_3_has_models }} | |
| scheduled_beijing_date: ${{ steps.meta.outputs.scheduled_beijing_date }} | |
| steps: | |
| - name: Checkout selected ATOM branch | |
| uses: actions/checkout@v6 | |
| - name: Load vLLM version | |
| run: cat atom/plugin/vllm/vllm-version.env >> "$GITHUB_ENV" | |
| - name: Docker Login | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| run: | | |
| echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin | |
| - name: Resolve image source and model matrix | |
| id: meta | |
| env: | |
| MODEL_SLOT_1: ${{ inputs.model_slot_1 || 'none' }} | |
| MODEL_SLOT_2: ${{ inputs.model_slot_2 || 'none' }} | |
| MODEL_SLOT_3: ${{ inputs.model_slot_3 || 'none' }} | |
| MODEL_SLOT_4: ${{ inputs.model_slot_4 || 'none' }} | |
| MODEL_SLOT_5: ${{ inputs.model_slot_5 || 'none' }} | |
| MODEL_SLOT_6: ${{ inputs.model_slot_6 || 'none' }} | |
| MODEL_SLOT_7: ${{ inputs.model_slot_7 || 'none' }} | |
| MODEL_SLOT_8: ${{ inputs.model_slot_8 || 'none' }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | |
| DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | |
| INPUT_OOT_IMAGE: ${{ inputs.oot_image || '' }} | |
| REBUILD_ATOM_BASE_FROM_DOCKERFILE: ${{ inputs.rebuild_atom_base_from_dockerfile }} | |
| run: | | |
| set -euo pipefail | |
| if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then | |
| SCHEDULE_CREATED_AT="$( | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| from urllib.error import HTTPError, URLError | |
| from urllib.request import Request, urlopen | |
| api_url = os.environ.get("GITHUB_API_URL", "https://api.github.com") | |
| repository = os.environ["GITHUB_REPOSITORY"] | |
| run_id = os.environ["GITHUB_RUN_ID"] | |
| token = os.environ.get("GITHUB_TOKEN", "") | |
| request = Request( | |
| f"{api_url}/repos/{repository}/actions/runs/{run_id}", | |
| headers={ | |
| "Accept": "application/vnd.github+json", | |
| "Authorization": f"Bearer {token}", | |
| "User-Agent": "atom-vllm-accuracy-nightly-priority", | |
| }, | |
| ) | |
| try: | |
| with urlopen(request, timeout=30) as response: | |
| payload = json.load(response) | |
| except HTTPError as exc: | |
| detail = exc.read().decode("utf-8", errors="replace").strip() | |
| raise RuntimeError( | |
| f"Failed to fetch workflow run metadata: HTTP {exc.code} {detail}" | |
| ) from exc | |
| except URLError as exc: | |
| raise RuntimeError( | |
| f"Failed to reach GitHub API for workflow run metadata: {exc}" | |
| ) from exc | |
| created_at = payload.get("created_at") | |
| if not created_at: | |
| raise RuntimeError("Workflow run metadata did not include created_at") | |
| print(created_at) | |
| PY | |
| )" | |
| export SCHEDULE_CREATED_AT | |
| else | |
| export SCHEDULE_CREATED_AT="" | |
| fi | |
| # Keep .github/benchmark/oot_models_accuracy.json as the single source | |
| # of truth for OOT full-validation coverage and runtime configuration. | |
| python3 - <<'PY' >> "$GITHUB_OUTPUT" | |
| import json | |
| import os | |
| import sys | |
| from datetime import datetime, timedelta, timezone | |
| from pathlib import Path | |
| event = os.environ["GITHUB_EVENT_NAME"] | |
| models_path = Path(".github/benchmark/oot_models_accuracy.json") | |
| raw_models = json.loads(models_path.read_text(encoding="utf-8")) | |
| if not isinstance(raw_models, list): | |
| raise SystemExit("OOT accuracy model catalog must be a JSON list.") | |
| required_fields = { | |
| "model_name", | |
| "model_path", | |
| "extra_args", | |
| "env_vars", | |
| "runner", | |
| "priority", | |
| "accuracy_test_threshold", | |
| } | |
| def normalize_model(raw_model, index): | |
| if not isinstance(raw_model, dict): | |
| raise SystemExit(f"Model catalog entry #{index} must be an object.") | |
| model = dict(raw_model) | |
| # Keep a short compatibility bridge while older local branches still use | |
| # the former dashboard-only field names. New entries should use snake_case. | |
| if "extra_args" not in model and "extraArgs" in model: | |
| model["extra_args"] = model.pop("extraArgs") | |
| else: | |
| model.pop("extraArgs", None) | |
| if "accuracy_test_threshold" not in model and "accuracy_threshold" in model: | |
| model["accuracy_test_threshold"] = model["accuracy_threshold"] | |
| missing = sorted(required_fields - model.keys()) | |
| if missing: | |
| name = model.get("model_name", f"entry #{index}") | |
| raise SystemExit( | |
| f"OOT accuracy model {name!r} is missing required fields: " | |
| + ", ".join(missing) | |
| ) | |
| model["model_name"] = str(model["model_name"]) | |
| model["model_path"] = str(model["model_path"]) | |
| model["extra_args"] = str(model.get("extra_args", "")) | |
| model["env_vars"] = str(model.get("env_vars", "")) | |
| model["runner"] = str(model["runner"]) | |
| model["test_level"] = str(model.get("test_level", "nightly")) | |
| model["priority"] = str(model["priority"]) | |
| if model["priority"] not in {"P0", "P1", "P2"}: | |
| raise SystemExit( | |
| f"OOT accuracy model {model['model_name']!r} has invalid priority " | |
| f"{model['priority']!r}; expected P0, P1, or P2." | |
| ) | |
| if "lm_eval_num_fewshot" in model: | |
| model["lm_eval_num_fewshot"] = int(model["lm_eval_num_fewshot"]) | |
| try: | |
| model["accuracy_test_threshold"] = float(model["accuracy_test_threshold"]) | |
| except (TypeError, ValueError) as exc: | |
| raise SystemExit( | |
| f"OOT accuracy model {model['model_name']!r} has non-numeric " | |
| "accuracy_test_threshold." | |
| ) from exc | |
| return model | |
| models = [] | |
| seen_model_names = set() | |
| for index, raw_model in enumerate(raw_models, start=1): | |
| model = normalize_model(raw_model, index) | |
| if model["model_name"] in seen_model_names: | |
| raise SystemExit( | |
| f"Duplicate OOT accuracy model_name: {model['model_name']!r}" | |
| ) | |
| seen_model_names.add(model["model_name"]) | |
| models.append(model) | |
| def matrix_entry(model): | |
| return dict(model) | |
| selected = [] | |
| if event == "workflow_dispatch": | |
| by_name = {model["model_name"]: model for model in models} | |
| group_selections = { | |
| "run_p0": [model for model in models if model["priority"] == "P0"], | |
| "run_p1": [model for model in models if model["priority"] == "P1"], | |
| "run_p2": [model for model in models if model["priority"] == "P2"], | |
| "run_all": models, | |
| } | |
| seen = set() | |
| def add_model(model): | |
| name = model["model_name"] | |
| if name in seen: | |
| return | |
| selected.append(matrix_entry(model)) | |
| seen.add(name) | |
| for slot_idx in range(1, 9): | |
| label = os.environ.get(f"MODEL_SLOT_{slot_idx}", "").strip() | |
| if not label or label == "none": | |
| continue | |
| if label in group_selections: | |
| for model in group_selections[label]: | |
| add_model(model) | |
| continue | |
| if label not in by_name: | |
| print( | |
| f"Unknown model_slot_{slot_idx} selection: {label}", | |
| file=sys.stderr, | |
| ) | |
| print("Available model_slot values:", file=sys.stderr) | |
| for value in ["none", *group_selections, *by_name]: | |
| print(f" - {value}", file=sys.stderr) | |
| sys.exit(1) | |
| add_model(by_name[label]) | |
| else: | |
| selected = [ | |
| matrix_entry(model) | |
| for model in models | |
| if model.get("test_level") == "nightly" | |
| ] | |
| if event == "workflow_dispatch" and not selected: | |
| print( | |
| "No models selected for manual run. Choose at least one model_slot value.", | |
| file=sys.stderr, | |
| ) | |
| sys.exit(1) | |
| beijing_tz = timezone(timedelta(hours=8)) | |
| validation_beijing_now = os.environ.get("VALIDATION_BEIJING_NOW", "") | |
| if validation_beijing_now: | |
| beijing_now = datetime.fromisoformat(validation_beijing_now) | |
| if beijing_now.tzinfo is None: | |
| beijing_now = beijing_now.replace(tzinfo=beijing_tz) | |
| else: | |
| beijing_now = beijing_now.astimezone(beijing_tz) | |
| elif event == "schedule": | |
| created_at = os.environ.get("SCHEDULE_CREATED_AT", "") | |
| if not created_at: | |
| raise SystemExit( | |
| "Scheduled validation requires workflow run created_at metadata." | |
| ) | |
| beijing_now = datetime.fromisoformat( | |
| created_at.replace("Z", "+00:00") | |
| ).astimezone(beijing_tz) | |
| else: | |
| beijing_now = datetime.now(timezone.utc).astimezone(beijing_tz) | |
| if event == "schedule": | |
| run_date = beijing_now.date().isoformat() | |
| else: | |
| run_date = "" | |
| order = ("P1", "P0", "P2") | |
| if event != "schedule" or beijing_now.weekday() < 5: | |
| order = ("P0", "P1", "P2") | |
| priority_slots = {priority: [] for priority in ("P0", "P1", "P2")} | |
| for model in selected: | |
| priority_slots[model["priority"]].append(model) | |
| for slot_idx, priority in enumerate(order, start=1): | |
| matrix = {"include": priority_slots[priority]} | |
| print(f"model_matrix_slot_{slot_idx}={json.dumps(matrix, separators=(',', ':'))}") | |
| print(f"slot_{slot_idx}_has_models={'true' if priority_slots[priority] else 'false'}") | |
| print(f"slot_{slot_idx}_priority={priority}") | |
| print(f"scheduled_beijing_date={run_date}") | |
| PY | |
| REBUILD_FROM_DOCKERFILE="${REBUILD_ATOM_BASE_FROM_DOCKERFILE:-false}" | |
| if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then | |
| if [ "${REBUILD_FROM_DOCKERFILE}" = "true" ]; then | |
| echo "Manual run selected full ATOM base rebuild from docker/atom_release.dockerfile" | |
| else | |
| echo "Manual run selected fast overlay rebuild from ${ATOM_BASE_NIGHTLY_IMAGE}" | |
| fi | |
| fi | |
| if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then | |
| if RESOLUTION_JSON="$( | |
| python3 .github/scripts/resolve_atom_image.py \ | |
| --repository "${VALIDATION_IMAGE_REPO}" \ | |
| --reference-tag vllm-latest | |
| )"; then | |
| RESOLVED_OOT_IMAGE="$( | |
| RESOLUTION_JSON="${RESOLUTION_JSON}" python3 - <<'PY' | |
| import json | |
| import os | |
| resolution = json.loads(os.environ["RESOLUTION_JSON"]) | |
| print(resolution["resolved_image"]) | |
| PY | |
| )" | |
| echo "Scheduled run resolved published OOT image: ${RESOLVED_OOT_IMAGE}" | |
| else | |
| echo "::warning::Failed to resolve ${NIGHTLY_OOT_IMAGE_TAG} to an immutable reference; falling back to the floating tag." | |
| RESOLVED_OOT_IMAGE="${NIGHTLY_OOT_IMAGE_TAG}" | |
| fi | |
| echo "oot_image_tag=${RESOLVED_OOT_IMAGE}" >> "$GITHUB_OUTPUT" | |
| echo "cleanup_temp_image=false" >> "$GITHUB_OUTPUT" | |
| echo "manual_base_image_tag=" >> "$GITHUB_OUTPUT" | |
| echo "rebuilt_atom_base_image_tag=" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ -n "${INPUT_OOT_IMAGE}" ]; then | |
| echo "Manual run will use user-provided atom-vLLM image: ${INPUT_OOT_IMAGE}" | |
| echo "oot_image_tag=${INPUT_OOT_IMAGE}" >> "$GITHUB_OUTPUT" | |
| echo "cleanup_temp_image=false" >> "$GITHUB_OUTPUT" | |
| echo "manual_base_image_tag=" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| SHORT_SHA="$(git rev-parse --short=12 HEAD)" | |
| MANUAL_BASE_IMAGE_TAG="atom_oot_manual_base:${GITHUB_RUN_ID}-${SHORT_SHA}" | |
| REBUILT_ATOM_BASE_IMAGE_TAG="atom_oot_rebuilt_base:${GITHUB_RUN_ID}-${SHORT_SHA}" | |
| OOT_IMAGE_TAG="${VALIDATION_IMAGE_REPO}:vllm-full-manual-${GITHUB_RUN_ID}-${SHORT_SHA}" | |
| BUILD_CONTEXT="$(mktemp -d)" | |
| trap 'rm -rf "${BUILD_CONTEXT}"' EXIT | |
| echo "oot_image_tag=${OOT_IMAGE_TAG}" >> "$GITHUB_OUTPUT" | |
| echo "cleanup_temp_image=true" >> "$GITHUB_OUTPUT" | |
| if [ "${REBUILD_FROM_DOCKERFILE}" = "true" ]; then | |
| echo "manual_base_image_tag=" >> "$GITHUB_OUTPUT" | |
| echo "rebuilt_atom_base_image_tag=${REBUILT_ATOM_BASE_IMAGE_TAG}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "manual_base_image_tag=${MANUAL_BASE_IMAGE_TAG}" >> "$GITHUB_OUTPUT" | |
| echo "rebuilt_atom_base_image_tag=" >> "$GITHUB_OUTPUT" | |
| fi | |
| git archive --format=tar HEAD | tar -xf - -C "${BUILD_CONTEXT}" | |
| if [ "${REBUILD_FROM_DOCKERFILE}" = "true" ]; then | |
| echo "Manual run rebuilds ATOM base and OOT image from branch ${GITHUB_REF_NAME}" | |
| DOCKER_BUILDKIT=1 docker build --pull --network=host --no-cache \ | |
| -t "${REBUILT_ATOM_BASE_IMAGE_TAG}" \ | |
| --build-arg GPU_ARCH="${GPU_ARCH}" \ | |
| --build-arg AITER_REPO="${AITER_REPO}" \ | |
| --build-arg AITER_COMMIT="${AITER_COMMIT}" \ | |
| --build-arg MAX_JOBS=64 \ | |
| --build-arg ATOM_REPO="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" \ | |
| --build-arg ATOM_COMMIT="${GITHUB_SHA}" \ | |
| -f "${BUILD_CONTEXT}/docker/atom_release.dockerfile" \ | |
| "${BUILD_CONTEXT}" | |
| DOCKER_BUILDKIT=1 docker build --network=host --no-cache \ | |
| -t "${OOT_IMAGE_TAG}" \ | |
| --build-arg OOT_BASE_IMAGE="${REBUILT_ATOM_BASE_IMAGE_TAG}" \ | |
| --build-arg MAX_JOBS=64 \ | |
| --build-arg VLLM_COMMIT="${VLLM_COMMIT}" \ | |
| --build-arg INSTALL_LM_EVAL=1 \ | |
| --build-arg INSTALL_FASTSAFETENSORS=1 \ | |
| -f "${BUILD_CONTEXT}/docker/vllm_release.dockerfile" \ | |
| "${BUILD_CONTEXT}" | |
| .github/scripts/docker_push_retry.sh "${OOT_IMAGE_TAG}" | |
| exit 0 | |
| fi | |
| cat <<'EOF' > "${BUILD_CONTEXT}/Dockerfile.mod" | |
| ARG ATOM_BASE_NIGHTLY_IMAGE | |
| FROM ${ATOM_BASE_NIGHTLY_IMAGE} | |
| ARG GPU_ARCH | |
| ARG AITER_REPO | |
| ARG AITER_COMMIT | |
| RUN pip install -U lm-eval[api] | |
| RUN pip show lm-eval || true | |
| RUN pip install hf_transfer | |
| RUN pip show hf_transfer || true | |
| RUN echo "=== Aiter version BEFORE uninstall ===" && pip show amd-aiter || true | |
| RUN pip uninstall -y amd-aiter | |
| RUN pip install --upgrade "pybind11>=3.0.1" | |
| RUN pip show pybind11 | |
| RUN rm -rf /app/aiter-test | |
| RUN git clone --depth 1 "${AITER_REPO}" /app/aiter-test && \ | |
| cd /app/aiter-test && \ | |
| git checkout "${AITER_COMMIT}" && \ | |
| git submodule sync && git submodule update --init --recursive && \ | |
| MAX_JOBS=64 PREBUILD_KERNELS=0 GPU_ARCHS="${GPU_ARCH}" python3 setup.py develop | |
| RUN echo "=== Aiter version AFTER installation ===" && pip show amd-aiter || true | |
| RUN echo "=== ATOM version BEFORE uninstall ===" && pip show atom || true | |
| RUN pip uninstall -y atom | |
| RUN rm -rf /app/ATOM | |
| COPY . /app/ATOM | |
| RUN cd /app/ATOM && pip install -e . | |
| RUN echo "=== ATOM version AFTER installation ===" && pip show atom || true | |
| EOF | |
| echo "Manual run rebuilds OOT image from branch ${GITHUB_REF_NAME}" | |
| docker build --pull --network=host --no-cache \ | |
| -t "${MANUAL_BASE_IMAGE_TAG}" \ | |
| --build-arg ATOM_BASE_NIGHTLY_IMAGE="${ATOM_BASE_NIGHTLY_IMAGE}" \ | |
| --build-arg GPU_ARCH="${GPU_ARCH}" \ | |
| --build-arg AITER_REPO="${AITER_REPO}" \ | |
| --build-arg AITER_COMMIT="${AITER_COMMIT}" \ | |
| -f "${BUILD_CONTEXT}/Dockerfile.mod" \ | |
| "${BUILD_CONTEXT}" | |
| docker build --network=host --no-cache \ | |
| -t "${OOT_IMAGE_TAG}" \ | |
| --build-arg OOT_BASE_IMAGE="${MANUAL_BASE_IMAGE_TAG}" \ | |
| --build-arg MAX_JOBS=64 \ | |
| --build-arg VLLM_COMMIT="${VLLM_COMMIT}" \ | |
| --build-arg INSTALL_LM_EVAL=1 \ | |
| --build-arg INSTALL_FASTSAFETENSORS=1 \ | |
| -f "${BUILD_CONTEXT}/docker/vllm_release.dockerfile" \ | |
| "${BUILD_CONTEXT}" | |
| .github/scripts/docker_push_retry.sh "${OOT_IMAGE_TAG}" | |
| - name: Clean up builder images | |
| if: ${{ always() && steps.meta.outputs.cleanup_temp_image == 'true' }} | |
| run: | | |
| for image in \ | |
| "${{ steps.meta.outputs.oot_image_tag }}" \ | |
| "${{ steps.meta.outputs.manual_base_image_tag }}" \ | |
| "${{ steps.meta.outputs.rebuilt_atom_base_image_tag }}"; do | |
| if [ -n "${image}" ]; then | |
| docker rmi "${image}" || true | |
| fi | |
| done | |
| oot-model-accuracy-priority-1: | |
| name: Model Accuracy ${{ matrix.priority }} (${{ matrix.model_name }}) | |
| needs: [prepare-oot-image] | |
| if: ${{ needs.prepare-oot-image.outputs.slot_1_has_models == 'true' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.prepare-oot-image.outputs.model_matrix_slot_1) }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 240 | |
| permissions: | |
| actions: read | |
| contents: write | |
| env: | |
| CONTAINER_NAME: atom_vllm_oot_validation_${{ matrix.priority }}_${{ strategy.job-index }} | |
| OOT_IMAGE_TAG: ${{ needs.prepare-oot-image.outputs.oot_image_tag }} | |
| steps: &oot-model-accuracy-steps | |
| - name: Check validation date window | |
| id: validation-window | |
| env: | |
| SCHEDULED_BEIJING_DATE: ${{ needs.prepare-oot-image.outputs.scheduled_beijing_date }} | |
| MIN_REMAINING_MINUTES: "150" | |
| run: | | |
| if [ "${GITHUB_EVENT_NAME}" != "schedule" ]; then | |
| echo "should_run=true" >> "$GITHUB_OUTPUT" | |
| echo "reason=manual run" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| python3 - <<'PY' >> "$GITHUB_OUTPUT" | |
| import os | |
| from datetime import datetime, timedelta, timezone | |
| beijing_tz = timezone(timedelta(hours=8)) | |
| now = datetime.now(timezone.utc).astimezone(beijing_tz) | |
| scheduled_date = os.environ.get("SCHEDULED_BEIJING_DATE", "") | |
| min_remaining_minutes = int(os.environ.get("MIN_REMAINING_MINUTES", "150")) | |
| current_date = now.date().isoformat() | |
| end_of_day = now.replace(hour=23, minute=59, second=59, microsecond=0) | |
| remaining_minutes = int((end_of_day - now).total_seconds() // 60) | |
| should_run = ( | |
| current_date == scheduled_date | |
| and remaining_minutes >= min_remaining_minutes | |
| ) | |
| print(f"should_run={'true' if should_run else 'false'}") | |
| print(f"current_beijing_date={current_date}") | |
| print(f"remaining_minutes={remaining_minutes}") | |
| if current_date != scheduled_date: | |
| print( | |
| f"reason=scheduled date {scheduled_date} no longer matches current Beijing date {current_date}" | |
| ) | |
| elif remaining_minutes < min_remaining_minutes: | |
| print( | |
| f"reason=only {remaining_minutes} Beijing-day minutes remain, below required {min_remaining_minutes}" | |
| ) | |
| else: | |
| print("reason=same Beijing day with enough time remaining") | |
| PY | |
| echo "Validation window check:" | |
| echo "- should_run=$(sed -n 's/^should_run=//p' "$GITHUB_OUTPUT")" | |
| echo "- reason=$(sed -n 's/^reason=//p' "$GITHUB_OUTPUT")" | |
| - name: Detect container engine | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| if command -v podman > /dev/null 2>&1; then | |
| echo "CONTAINER_ENGINE=podman" >> "$GITHUB_ENV" | |
| echo "Container engine: podman" | |
| elif docker info > /dev/null 2>&1; then | |
| echo "CONTAINER_ENGINE=docker" >> "$GITHUB_ENV" | |
| echo "Container engine: docker" | |
| else | |
| echo "ERROR: Neither docker nor podman is available on this runner." | |
| exit 1 | |
| fi | |
| - name: Checkout ATOM repo | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| uses: actions/checkout@v6 | |
| - name: Docker Login | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| REG="${OOT_IMAGE_TAG%%/*}" | |
| if [[ "$REG" != *.* && "$REG" != localhost* && "$REG" != *:* ]]; then | |
| REG="docker.io" | |
| fi | |
| echo "Logging in to registry: ${REG}" | |
| echo "${{ secrets.DOCKER_PASSWORD }}" | $CONTAINER_ENGINE login "$REG" -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin | |
| - name: Set HF_TOKEN | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: echo "HF_TOKEN=${HF_TOKEN:-${{ secrets.AMD_HF_TOKEN }}}" >> "$GITHUB_ENV" | |
| - name: Pull OOT image | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| set -euo pipefail | |
| IMG="${OOT_IMAGE_TAG}" | |
| if [[ "${CONTAINER_ENGINE}" == "podman" ]]; then | |
| if [[ "${IMG}" != */* ]]; then | |
| IMG="docker.io/library/${IMG}" | |
| else | |
| reg="${IMG%%/*}" | |
| if [[ "${reg}" != *.* && "${reg}" != localhost* && "${reg}" != *:* ]]; then | |
| IMG="docker.io/${IMG}" | |
| fi | |
| fi | |
| fi | |
| echo "OOT_IMAGE_REF=${IMG}" >> "$GITHUB_ENV" | |
| echo "Pulling OOT image: ${IMG} (workflow tag: ${OOT_IMAGE_TAG})" | |
| $CONTAINER_ENGINE pull "${IMG}" | |
| - name: Prepare model cache mount | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| MODEL_CACHE_ROOT="" | |
| MODEL_CACHE_MOUNT="" | |
| MODEL_CACHE_DESC="container-local /models (no host cache mount)" | |
| MODEL_MOUNT_PATH="/models" | |
| if [ -d "/mnt/dcgpuval" ]; then | |
| MODEL_CACHE_ROOT="/mnt/dcgpuval" | |
| elif [ -d "/shared/data/WRH/models" ]; then | |
| MODEL_CACHE_ROOT="/shared/data/WRH/models" | |
| elif [ -d "/shared_nfs/models" ]; then | |
| MODEL_CACHE_ROOT="/shared_nfs/models" | |
| elif [ -d "/it-share/models" ]; then | |
| MODEL_CACHE_ROOT="/it-share/models" | |
| elif [ -d "/models" ]; then | |
| MODEL_CACHE_ROOT="/models" | |
| elif [ -d "/data/models" ]; then | |
| MODEL_CACHE_ROOT="/data/models" | |
| fi | |
| if [ -n "${MODEL_CACHE_ROOT}" ]; then | |
| MODEL_CACHE_MOUNT="-v ${MODEL_CACHE_ROOT}:${MODEL_MOUNT_PATH}" | |
| MODEL_CACHE_DESC="${MODEL_CACHE_ROOT} (shared host path)" | |
| else | |
| echo "No shared host model cache found; using container-local ${MODEL_MOUNT_PATH}." | |
| fi | |
| echo "Using model cache backend: ${MODEL_CACHE_DESC}" | |
| echo "MODEL_CACHE_ROOT=${MODEL_CACHE_ROOT}" >> "$GITHUB_ENV" | |
| echo "MODEL_CACHE_MOUNT=${MODEL_CACHE_MOUNT}" >> "$GITHUB_ENV" | |
| echo "MODEL_CACHE_DESC=${MODEL_CACHE_DESC}" >> "$GITHUB_ENV" | |
| echo "MODEL_MOUNT_PATH=${MODEL_MOUNT_PATH}" >> "$GITHUB_ENV" | |
| - name: Clean up old containers | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| containers=$($CONTAINER_ENGINE ps -q) | |
| if [ -n "$containers" ]; then | |
| $CONTAINER_ENGINE kill $containers || true | |
| fi | |
| $CONTAINER_ENGINE rm -f "$CONTAINER_NAME" 2>/dev/null || true | |
| - name: Start validation container | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| if [ -f "/etc/podinfo/gha-render-devices" ]; then | |
| DEVICE_FLAG=$(cat /etc/podinfo/gha-render-devices) | |
| else | |
| DEVICE_FLAG="--device /dev/dri" | |
| fi | |
| if [ "${CONTAINER_ENGINE}" = "podman" ]; then | |
| GROUP_ADD_FLAG="--group-add keep-groups" | |
| else | |
| GROUP_ADD_FLAG="--group-add video" | |
| fi | |
| MODEL_MOUNT="${MODEL_CACHE_MOUNT}" | |
| echo "Using model cache backend: ${MODEL_CACHE_DESC}" | |
| { | |
| printf '%s\n' "${{ matrix.env_vars }}" | |
| printf '%s\n' \ | |
| "AITER_LOG_LEVEL=ERROR" \ | |
| "AITER_TRITON_LOG_LEVEL=CRITICAL" \ | |
| "AITER_LOG_MORE=0" | |
| } | sed '/^[[:space:]]*$/d; s/^[[:space:]]*//' > /tmp/oot_env_file.txt | |
| $CONTAINER_ENGINE run -dt --device=/dev/kfd $DEVICE_FLAG \ | |
| -v "${GITHUB_WORKSPACE:-$PWD}":/workspace \ | |
| $MODEL_MOUNT \ | |
| -w /workspace \ | |
| --ipc=host $GROUP_ADD_FLAG \ | |
| --privileged \ | |
| --cap-add=SYS_PTRACE \ | |
| --security-opt seccomp=unconfined \ | |
| --ulimit memlock=-1 \ | |
| --ulimit stack=67108864 \ | |
| --env-file /tmp/oot_env_file.txt \ | |
| -e HF_TOKEN="${HF_TOKEN:-}" \ | |
| --name "$CONTAINER_NAME" \ | |
| "${OOT_IMAGE_REF:-${OOT_IMAGE_TAG}}" | |
| env: | |
| GITHUB_WORKSPACE: ${{ github.workspace }} | |
| - name: Collect GPU info (inside container) | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| id: gpu-info | |
| run: bash .github/scripts/collect_gpu_info.sh "$CONTAINER_NAME" "$CONTAINER_ENGINE" "${{ matrix.runner }}" | |
| - name: Resolve local model path | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${MODEL_CACHE_MOUNT}" ]; then | |
| echo "OOT_MODEL_AVAILABLE_LOCALLY=false" >> "$GITHUB_ENV" | |
| echo "No shared model cache mount; local model resolution skipped." | |
| exit 0 | |
| fi | |
| if $CONTAINER_ENGINE exec \ | |
| -e MODEL_PATH="${{ matrix.model_path }}" \ | |
| "$CONTAINER_NAME" \ | |
| bash -lc ' | |
| set -euo pipefail | |
| python3 - <<'"'"'PY'"'"' | |
| import os | |
| from pathlib import Path | |
| model_path = os.environ["MODEL_PATH"].strip().strip("/") | |
| normalized = model_path.rstrip("/") | |
| basename = Path(normalized).name | |
| alias_map = { | |
| "Qwen/Qwen3-235B-A22B-Instruct-2507-FP8": [ | |
| "/models/Qwen/Qwen3-235B-A22B-Instruct-2507-FP8", | |
| "/models/Qwen3-235B-A22B-Instruct-2507-FP8", | |
| ], | |
| "Qwen/Qwen3-Next-80B-A3B-Instruct-FP8": [ | |
| "/models/Qwen/Qwen3-Next-80B-A3B-Instruct-FP8", | |
| "/models/Qwen3-Next-80B-A3B-Instruct-FP8", | |
| ], | |
| "Qwen/Qwen3.5-397B-A17B-FP8": [ | |
| "/models/Qwen/Qwen3.5-397B-A17B-FP8", | |
| "/models/Qwen3.5-397B-A17B-FP8", | |
| ], | |
| "Qwen/Qwen3.5-397B-A17B": [ | |
| "/models/Qwen/Qwen3.5-397B-A17B", | |
| "/models/Qwen3.5-397B-A17B", | |
| ], | |
| "amd/Qwen3.5-397B-A17B-MXFP4": [ | |
| "/models/amd/Qwen3.5-397B-A17B-MXFP4", | |
| "/models/Qwen/Qwen3.5-397B-A17B-MXFP4", | |
| "/models/Qwen3.5-397B-A17B-MXFP4", | |
| ], | |
| "Meta-Llama-3.1-405B-Instruct-FP8": [ | |
| "/models/Meta-Llama-3.1-405B-Instruct-FP8", | |
| "/models/facebook/Meta-Llama-3.1-405B-Instruct-FP8", | |
| ], | |
| "meta-llama/Llama-3.1-8B-Instruct": [ | |
| "/models/meta-llama/Llama-3.1-8B-Instruct", | |
| "/models/Llama-3.1-8B-Instruct", | |
| ], | |
| "amd/Kimi-K2-Thinking-MXFP4-AttnFP8": [ | |
| "/models/amd/Kimi-K2-Thinking-MXFP4-AttnFP8", | |
| "/models/kimi/Kimi-K2-Thinking-MXFP4-AttnFP8", | |
| "/models/Kimi-K2-Thinking-MXFP4-AttnFP8", | |
| ], | |
| "amd/Kimi-K2.5-MXFP4-AttnFP8": [ | |
| "/models/amd/Kimi-K2.5-MXFP4-AttnFP8", | |
| "/models/kimi/Kimi-K2.5-MXFP4-AttnFP8", | |
| "/models/Kimi-K2.5-MXFP4-AttnFP8", | |
| ], | |
| "deepseek-ai/DeepSeek-R1-0528": [ | |
| "/models/deepseek-ai/DeepSeek-R1-0528", | |
| "/models/DeepSeek-R1-0528", | |
| ], | |
| "amd/DeepSeek-R1-0528-MXFP4-MTP-MoEFP4": [ | |
| "/models/amd/DeepSeek-R1-0528-MXFP4-MTP-MoEFP4", | |
| "/models/deepseek-ai/DeepSeek-R1-0528-MXFP4-MTP-MoEFP4", | |
| "/models/DeepSeek-R1-0528-MXFP4-MTP-MoEFP4", | |
| ], | |
| "deepseek-ai/DeepSeek-V4-Pro": [ | |
| "/models/deepseek-ai/DeepSeek-V4-Pro", | |
| "/models/DeepSeek-V4-Pro", | |
| ], | |
| "deepseek-ai/DeepSeek-V3.2": [ | |
| "/models/deepseek-ai/DeepSeek-V3.2", | |
| "/models/DeepSeek-V3.2", | |
| ], | |
| "openai/gpt-oss-120b": [ | |
| "/models/openai/gpt-oss-120b", | |
| "/models/gpt-oss-120b", | |
| ], | |
| "MiniMaxAI/MiniMax-M2.5": [ | |
| "/models/MiniMaxAI/MiniMax-M2.5", | |
| "/models/MiniMax-M2.5", | |
| ], | |
| "zai-org/GLM-4.7-FP8": [ | |
| "/models/zai-org/GLM-4.7-FP8", | |
| "/models/GLM/GLM-4.7-FP8", | |
| "/models/GLM-4.7-FP8", | |
| ], | |
| "zai-org/GLM-5.1-FP8": [ | |
| "/models/zai-org/GLM-5.1-FP8", | |
| "/models/GLM/GLM-5.1-FP8", | |
| "/models/GLM-5.1-FP8", | |
| ], | |
| "zai-org/GLM-5.2-FP8": [ | |
| "/models/zai-org/GLM-5.2-FP8", | |
| "/models/GLM/GLM-5.2-FP8", | |
| "/models/GLM-5.2-FP8", | |
| ], | |
| "amd/GLM-5.2-MXFP4": [ | |
| "/models/amd/GLM-5.2-MXFP4", | |
| "/models/GLM/GLM-5.2-MXFP4", | |
| "/models/GLM-5.2-MXFP4", | |
| ], | |
| } | |
| candidates = [] | |
| if normalized: | |
| candidates.append(f"/models/{normalized}") | |
| if basename: | |
| candidates.append(f"/models/{basename}") | |
| candidates.extend(alias_map.get(normalized, [])) | |
| seen = set() | |
| for candidate in candidates: | |
| if candidate in seen: | |
| continue | |
| seen.add(candidate) | |
| path = Path(candidate) | |
| if (path / "config.json").is_file(): | |
| print(f"FOUND_LOCAL_MODEL={candidate}") | |
| raise SystemExit(0) | |
| print("LOCAL_MODEL_NOT_FOUND") | |
| print("Checked candidates:") | |
| for candidate in seen: | |
| print(f" - {candidate}") | |
| raise SystemExit(1) | |
| PY | |
| ' > /tmp/local_model_resolution.txt; then | |
| resolved_model_path="$(sed -n 's/^FOUND_LOCAL_MODEL=//p' /tmp/local_model_resolution.txt | tail -n 1)" | |
| if [ -z "${resolved_model_path}" ]; then | |
| echo "ERROR: local model resolver succeeded but did not emit a model path." | |
| cat /tmp/local_model_resolution.txt | |
| exit 1 | |
| fi | |
| echo "Resolved local model path: ${resolved_model_path}" | |
| echo "OOT_MODEL_AVAILABLE_LOCALLY=true" >> "$GITHUB_ENV" | |
| echo "OOT_RESOLVED_MODEL_PATH=${resolved_model_path}" >> "$GITHUB_ENV" | |
| else | |
| echo "Local model path not found for ${{ matrix.model_path }}." | |
| cat /tmp/local_model_resolution.txt || true | |
| echo "OOT_MODEL_AVAILABLE_LOCALLY=false" >> "$GITHUB_ENV" | |
| fi | |
| - name: Download model if needed | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| set -euo pipefail | |
| model_dir="${MODEL_MOUNT_PATH}/${{ matrix.model_path }}" | |
| if [ "${OOT_MODEL_AVAILABLE_LOCALLY:-false}" = "true" ]; then | |
| echo "Using local model path ${OOT_RESOLVED_MODEL_PATH}; skip download." | |
| exit 0 | |
| fi | |
| if [ -n "${MODEL_CACHE_MOUNT}" ]; then | |
| lock_suffix="$(printf '%s' "${{ matrix.model_path }}" | tr '/:@' '___')" | |
| if ! $CONTAINER_ENGINE exec \ | |
| -e HF_TOKEN="${HF_TOKEN:-}" \ | |
| -e MODEL_PATH="${{ matrix.model_path }}" \ | |
| -e MODEL_DIR="$model_dir" \ | |
| -e RUNNER_NAME="${{ matrix.runner }}" \ | |
| -e LOCK_SUFFIX="$lock_suffix" \ | |
| "$CONTAINER_NAME" \ | |
| bash -lc ' | |
| set -euo pipefail | |
| completion_flag="$MODEL_DIR/.download-complete" | |
| use_model_lock="true" | |
| if [ "$RUNNER_NAME" = "atom-mi355-8gpu.predownload" ]; then | |
| use_model_lock="false" | |
| fi | |
| download_model() { | |
| if [ -f "$MODEL_DIR/config.json" ]; then | |
| echo "Model directory exists without a completion marker under ${MODEL_DIR}; validating download" | |
| else | |
| echo "Model not found under ${MODEL_DIR}; downloading model" | |
| fi | |
| rm -f "$completion_flag" | |
| hf download "$MODEL_PATH" --local-dir "$MODEL_DIR" | |
| touch "$completion_flag" | |
| } | |
| if [ -f "$completion_flag" ]; then | |
| echo "Model already exists under ${MODEL_DIR}; skip download" | |
| elif [ "$use_model_lock" != "true" ]; then | |
| echo "Baremetal runner ${RUNNER_NAME} detected; skipping shared model download lock" | |
| download_model | |
| else | |
| lock_dir="/models/.cache/atom-download-locks" | |
| mkdir -p "$lock_dir" | |
| lock_file="$lock_dir/${LOCK_SUFFIX}.lock" | |
| exec 9>"$lock_file" | |
| echo "Waiting for model download lock: $lock_file" | |
| if ! flock -w 7200 9; then | |
| echo "Timed out waiting for model download lock: $lock_file" | |
| exit 1 | |
| fi | |
| if [ -f "$completion_flag" ]; then | |
| echo "Model became available under ${MODEL_DIR} while waiting for the lock; skip download" | |
| else | |
| download_model | |
| fi | |
| fi | |
| '; then | |
| echo "Model download failed for '${{ matrix.model_path }}'. Aborting." | |
| exit 1 | |
| fi | |
| else | |
| echo "${MODEL_MOUNT_PATH} directory not mounted; skipping model download" | |
| fi | |
| - name: Resolve OOT model path | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' && success() }} | |
| run: | | |
| if [ "${OOT_MODEL_AVAILABLE_LOCALLY:-false}" = "true" ]; then | |
| echo "Using previously resolved local model path: ${OOT_RESOLVED_MODEL_PATH}" | |
| exit 0 | |
| fi | |
| if [ -n "${MODEL_CACHE_MOUNT}" ]; then | |
| echo "OOT_RESOLVED_MODEL_PATH=${MODEL_MOUNT_PATH}/${{ matrix.model_path }}" >> "$GITHUB_ENV" | |
| echo "Using mounted model path: ${MODEL_MOUNT_PATH}/${{ matrix.model_path }}" | |
| else | |
| echo "OOT_RESOLVED_MODEL_PATH=${{ matrix.model_path }}" >> "$GITHUB_ENV" | |
| echo "Using model id: ${{ matrix.model_path }}" | |
| fi | |
| - name: Start OOT accuracy server | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' }} | |
| timeout-minutes: 10 | |
| env: | |
| OOT_MODEL_NAME: ${{ matrix.model_name }} | |
| OOT_MODEL_PATH: ${{ matrix.model_path }} | |
| OOT_EXTRA_ARGS: ${{ matrix.extra_args }} | |
| run: | | |
| $CONTAINER_ENGINE exec -d \ | |
| -e OOT_MODEL_NAME="${OOT_MODEL_NAME}" \ | |
| -e OOT_MODEL_PATH="${OOT_RESOLVED_MODEL_PATH:-$OOT_MODEL_PATH}" \ | |
| -e OOT_EXTRA_ARGS="${OOT_EXTRA_ARGS}" \ | |
| "$CONTAINER_NAME" bash -lc " | |
| set -euo pipefail | |
| bash .github/scripts/atom_oot_test.sh start full | |
| " | |
| - name: Wait for OOT accuracy server | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' && success() }} | |
| timeout-minutes: 120 | |
| env: | |
| MAX_WAIT_RETRIES: "120" | |
| WAIT_INTERVAL_SEC: "30" | |
| STREAM_VLLM_LOGS: "1" | |
| run: | | |
| last_vllm_log_line=0 | |
| emit_new_vllm_logs() { | |
| if [ "${STREAM_VLLM_LOGS}" != "1" ]; then | |
| return 0 | |
| fi | |
| current_line_count=$($CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc 'if [ -f /tmp/vllm_oot.log ]; then wc -l < /tmp/vllm_oot.log; else echo 0; fi' 2>/dev/null || echo 0) | |
| current_line_count=${current_line_count//$'\r'/} | |
| if [ "${current_line_count:-0}" -le "${last_vllm_log_line}" ]; then | |
| return 0 | |
| fi | |
| echo "" | |
| echo "========== New vLLM log output ==========" | |
| $CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc "sed -n '$((last_vllm_log_line + 1)),${current_line_count}p' /tmp/vllm_oot.log" || true | |
| last_vllm_log_line=${current_line_count} | |
| } | |
| for ((i=1; i<=MAX_WAIT_RETRIES; i++)); do | |
| if $CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc 'curl -fsS http://127.0.0.1:8000/v1/models >/dev/null 2>&1'; then | |
| emit_new_vllm_logs | |
| echo "vLLM server is ready." | |
| exit 0 | |
| fi | |
| emit_new_vllm_logs | |
| server_status=$($CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc ' | |
| if [ -f /tmp/vllm_oot.pid ]; then | |
| pid=$(cat /tmp/vllm_oot.pid) | |
| if kill -0 "$pid" 2>/dev/null; then | |
| echo running | |
| else | |
| echo dead | |
| fi | |
| elif pgrep -f "vllm serve" >/dev/null 2>&1; then | |
| echo running | |
| else | |
| echo starting | |
| fi | |
| ' 2>/dev/null || echo unknown) | |
| if [ "${server_status}" = "dead" ]; then | |
| echo "vLLM server process exited before becoming ready." | |
| $CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc 'tail -n 200 /tmp/vllm_oot.log || true' || true | |
| exit 1 | |
| fi | |
| echo "Waiting for vLLM server... (${i}/${MAX_WAIT_RETRIES}; status=${server_status})" | |
| sleep "${WAIT_INTERVAL_SEC}" | |
| done | |
| echo "vLLM server did not become ready in time." | |
| emit_new_vllm_logs | |
| $CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc 'tail -n 200 /tmp/vllm_oot.log || true' || true | |
| exit 1 | |
| - name: Run OOT gsm8k accuracy client | |
| id: run_accuracy_client | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' && success() }} | |
| timeout-minutes: 120 | |
| continue-on-error: true | |
| env: | |
| OOT_MODEL_NAME: ${{ matrix.model_name }} | |
| OOT_MODEL_PATH: ${{ matrix.model_path }} | |
| OOT_EXTRA_ARGS: ${{ matrix.extra_args }} | |
| OOT_CLIENT_COMMAND: ${{ matrix.client_command || '' }} | |
| LM_EVAL_NUM_FEWSHOT: ${{ matrix.lm_eval_num_fewshot }} | |
| run: | | |
| set -o pipefail | |
| $CONTAINER_ENGINE exec \ | |
| -e OOT_MODEL_NAME="${OOT_MODEL_NAME}" \ | |
| -e OOT_MODEL_PATH="${OOT_RESOLVED_MODEL_PATH:-$OOT_MODEL_PATH}" \ | |
| -e OOT_EXTRA_ARGS="${OOT_EXTRA_ARGS}" \ | |
| -e OOT_CLIENT_COMMAND="${OOT_CLIENT_COMMAND}" \ | |
| -e OOT_DOCKER_IMAGE="${OOT_IMAGE_TAG}" \ | |
| -e GPU_NAME="${{ steps.gpu-info.outputs.gpu_name }}" \ | |
| -e GPU_VRAM_GB="${{ steps.gpu-info.outputs.gpu_vram_gb }}" \ | |
| -e ROCM_VERSION="${{ steps.gpu-info.outputs.rocm_version }}" \ | |
| -e LM_EVAL_NUM_FEWSHOT="${LM_EVAL_NUM_FEWSHOT}" \ | |
| "$CONTAINER_NAME" bash -lc " | |
| set -euo pipefail | |
| bash .github/scripts/atom_oot_test.sh client full | |
| " 2>&1 | tee ./oot_accuracy_client.log | |
| - name: Check OOT accuracy test results | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' && success() && steps.run_accuracy_client.outcome == 'success' }} | |
| run: | | |
| $CONTAINER_ENGINE cp "$CONTAINER_NAME":/tmp/oot_accuracy_results ./oot_accuracy_results || true | |
| result_file=$(ls -1t oot_accuracy_results/*.json 2>/dev/null | head -n 1) | |
| if [ -z "$result_file" ] || [ ! -f "$result_file" ]; then | |
| echo "ERROR: No results JSON file found in oot_accuracy_results/" | |
| exit 2 | |
| fi | |
| echo "RESULT_FILE: $result_file" | |
| flexible_extract_value=$(jq '.results.gsm8k["exact_match,flexible-extract"]' "$result_file") | |
| echo "Flexible extract value: $flexible_extract_value" | |
| echo "Accuracy test threshold: ${{ matrix.accuracy_test_threshold }}" | |
| result=$(awk -v val="$flexible_extract_value" -v threshold="${{ matrix.accuracy_test_threshold }}" 'BEGIN {print (val < threshold) ? 1 : 0}') | |
| if [ "$result" -eq 1 ]; then | |
| echo "Accuracy test failed: Flexible extract value $flexible_extract_value is less than threshold ${{ matrix.accuracy_test_threshold }}." | |
| exit 1 | |
| else | |
| echo "Accuracy test passed: Flexible extract value $flexible_extract_value is greater than or equal to threshold ${{ matrix.accuracy_test_threshold }}." | |
| exit 0 | |
| fi | |
| - name: Check OOT MTP acceptance rate | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' && success() && steps.run_accuracy_client.outcome == 'success' && matrix.mtp_accept_threshold != '' }} | |
| env: | |
| MTP_ACCEPT_THRESHOLD: ${{ matrix.mtp_accept_threshold }} | |
| MTP_PER_POS_THRESHOLD: ${{ matrix.mtp_per_pos_threshold }} | |
| run: | | |
| # MTP acceptance is recorded into the result JSON by atom_oot_test.sh | |
| # (scraped from the live vLLM /metrics endpoint during the gsm8k run). | |
| # gsm8k accuracy alone CANNOT guard MTP: speculative decoding is loss- | |
| # less w.r.t. the target model, so a broken draft head leaves accuracy | |
| # unchanged and only craters acceptance/throughput. This step is the | |
| # only gate that catches an MTP regression. Uses jq+awk (no python on | |
| # the host runner), mirroring the gsm8k accuracy check above. | |
| result_file=$(ls -1t oot_accuracy_results/*.json 2>/dev/null | head -n 1) | |
| if [ -z "$result_file" ] || [ ! -f "$result_file" ]; then | |
| echo "ERROR: No results JSON file found for MTP acceptance check." | |
| exit 2 | |
| fi | |
| echo "RESULT_FILE: $result_file" | |
| overall=$(jq -r '.atom_ci_metadata.mtp_acceptance_overall // empty' "$result_file") | |
| if [ -z "$overall" ]; then | |
| echo "ERROR: mtp_acceptance_overall missing — spec-decode /metrics were not captured during the run. Treating as a failure." | |
| exit 1 | |
| fi | |
| fail=0 | |
| echo "MTP overall acceptance: $overall (threshold ${MTP_ACCEPT_THRESHOLD})" | |
| if awk -v v="$overall" -v t="${MTP_ACCEPT_THRESHOLD}" 'BEGIN {exit !(v < t)}'; then | |
| echo "FAIL: overall acceptance $overall < threshold ${MTP_ACCEPT_THRESHOLD}" | |
| fail=1 | |
| fi | |
| if [ -n "${MTP_PER_POS_THRESHOLD}" ]; then | |
| IFS=',' read -ra pos_thresholds <<< "${MTP_PER_POS_THRESHOLD}" | |
| idx=0 | |
| for th in "${pos_thresholds[@]}"; do | |
| p=$(jq -r ".atom_ci_metadata.mtp_per_pos_acceptance[$idx] // empty" "$result_file") | |
| if [ -z "$p" ]; then | |
| echo "FAIL: position $idx acceptance missing (expected >= $th)" | |
| fail=1 | |
| else | |
| echo "MTP position $idx acceptance: $p (threshold $th)" | |
| if awk -v v="$p" -v t="$th" 'BEGIN {exit !(v < t)}'; then | |
| echo "FAIL: position $idx acceptance $p < threshold $th" | |
| fail=1 | |
| fi | |
| fi | |
| idx=$((idx + 1)) | |
| done | |
| fi | |
| if [ "$fail" -eq 1 ]; then | |
| echo "MTP acceptance gate FAILED." | |
| exit 1 | |
| fi | |
| echo "MTP acceptance gate PASSED." | |
| - name: Collect summary | |
| if: ${{ steps.validation-window.outputs.should_run == 'true' && success() }} | |
| run: | | |
| echo "OOT gsm8k summary for ${{ matrix.model_name }}:" >> $GITHUB_STEP_SUMMARY | |
| $CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc "awk '/\|Tasks\|Version\|/,/^$/ { if (NF > 0) print }' /tmp/oot_accuracy_output.txt" >> $GITHUB_STEP_SUMMARY || true | |
| - name: Collect artifacts | |
| if: ${{ always() && steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| $CONTAINER_ENGINE cp "$CONTAINER_NAME":/tmp/vllm_oot.log ./vllm_oot.log || true | |
| $CONTAINER_ENGINE cp "$CONTAINER_NAME":/tmp/oot_accuracy_output.txt ./oot_accuracy_output.txt || true | |
| $CONTAINER_ENGINE cp "$CONTAINER_NAME":/tmp/oot_accuracy_results ./oot_accuracy_results || true | |
| if [ ! -f ./oot_accuracy_client.log ]; then : > ./oot_accuracy_client.log; fi | |
| - name: Upload model artifacts | |
| if: ${{ always() && steps.validation-window.outputs.should_run == 'true' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: oot-validation-${{ matrix.model_name }}-${{ github.run_id }} | |
| path: | | |
| vllm_oot.log | |
| oot_accuracy_client.log | |
| oot_accuracy_output.txt | |
| oot_accuracy_results | |
| - name: Fail if OOT accuracy client failed | |
| if: ${{ always() && steps.validation-window.outputs.should_run == 'true' && steps.run_accuracy_client.outcome == 'failure' }} | |
| run: exit 1 | |
| - name: Upload accuracy results for dashboard | |
| if: ${{ always() && steps.validation-window.outputs.should_run == 'true' && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.upload_accuracy_to_dashboard)) }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: accuracy-${{ matrix.model_name }} | |
| path: oot_accuracy_results/*.json | |
| if-no-files-found: ignore | |
| - name: Cleanup | |
| if: ${{ always() && steps.validation-window.outputs.should_run == 'true' }} | |
| run: | | |
| $CONTAINER_ENGINE exec "$CONTAINER_NAME" bash -lc "if [ -f /tmp/vllm_oot.pid ]; then kill \$(cat /tmp/vllm_oot.pid) || true; fi" || true | |
| $CONTAINER_ENGINE stop "$CONTAINER_NAME" || true | |
| $CONTAINER_ENGINE rm "$CONTAINER_NAME" || true | |
| $CONTAINER_ENGINE rmi "${OOT_IMAGE_REF:-${OOT_IMAGE_TAG}}" || true | |
| oot-model-accuracy-priority-2: | |
| name: Model Accuracy ${{ matrix.priority }} (${{ matrix.model_name }}) | |
| needs: [prepare-oot-image, oot-model-accuracy-priority-1] | |
| if: ${{ always() && needs.prepare-oot-image.result == 'success' && needs.prepare-oot-image.outputs.slot_2_has_models == 'true' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.prepare-oot-image.outputs.model_matrix_slot_2) }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 240 | |
| permissions: | |
| actions: read | |
| contents: write | |
| env: | |
| CONTAINER_NAME: atom_vllm_oot_validation_${{ matrix.priority }}_${{ strategy.job-index }} | |
| OOT_IMAGE_TAG: ${{ needs.prepare-oot-image.outputs.oot_image_tag }} | |
| steps: *oot-model-accuracy-steps | |
| oot-model-accuracy-priority-3: | |
| name: Model Accuracy ${{ matrix.priority }} (${{ matrix.model_name }}) | |
| needs: [prepare-oot-image, oot-model-accuracy-priority-2] | |
| if: ${{ always() && needs.prepare-oot-image.result == 'success' && needs.prepare-oot-image.outputs.slot_3_has_models == 'true' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.prepare-oot-image.outputs.model_matrix_slot_3) }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 240 | |
| permissions: | |
| actions: read | |
| contents: write | |
| env: | |
| CONTAINER_NAME: atom_vllm_oot_validation_${{ matrix.priority }}_${{ strategy.job-index }} | |
| OOT_IMAGE_TAG: ${{ needs.prepare-oot-image.outputs.oot_image_tag }} | |
| steps: *oot-model-accuracy-steps | |
| # Build the dashboard payload from all uploaded accuracy artifacts after the | |
| # full matrix completes. This keeps dashboard writes off the GPU runners and | |
| # ensures each nightly/manual run publishes at most once. | |
| accuracy-dashboard: | |
| name: Update accuracy dashboard | |
| needs: | |
| - oot-model-accuracy-priority-1 | |
| - oot-model-accuracy-priority-2 | |
| - oot-model-accuracy-priority-3 | |
| if: ${{ always() && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.upload_accuracy_to_dashboard)) }} | |
| concurrency: | |
| group: gh-pages-deploy | |
| cancel-in-progress: false | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.12' | |
| - name: Prepare accuracy artifact directory | |
| run: mkdir -p /tmp/accuracy-results | |
| - name: Download accuracy artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: /tmp/accuracy-results | |
| pattern: accuracy-* | |
| continue-on-error: true | |
| - name: Count downloaded accuracy artifacts | |
| id: downloaded | |
| run: | | |
| json_count=$(find /tmp/accuracy-results -type f -name '*.json' | wc -l | tr -d ' ') | |
| echo "json_count=${json_count}" >> "$GITHUB_OUTPUT" | |
| echo "Downloaded ${json_count} OOT accuracy JSON files" | |
| - name: List downloaded accuracy artifacts | |
| if: steps.downloaded.outputs.json_count != '0' | |
| run: | | |
| echo "=== Downloaded OOT accuracy artifacts ===" | |
| find /tmp/accuracy-results -type f -name '*.json' | head -20 || echo "No JSON files found" | |
| - name: Transform OOT accuracy results for dashboard | |
| if: steps.downloaded.outputs.json_count != '0' | |
| run: | | |
| python3 .github/scripts/accuracy_to_dashboard.py \ | |
| /tmp/accuracy-results \ | |
| --output accuracy-benchmark-input.json \ | |
| --models .github/benchmark/oot_models_accuracy.json \ | |
| --backend "ATOM-vLLM" \ | |
| --run-url "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| echo "=== Generated entries ===" | |
| cat accuracy-benchmark-input.json | |
| - name: Prepare existing dashboard data | |
| if: steps.downloaded.outputs.json_count != '0' | |
| id: prepare_dashboard_data | |
| uses: ./.github/actions/prepare-dashboard-data | |
| with: | |
| data-path: benchmark-dashboard/data.js | |
| - name: Store OOT accuracy result to dashboard | |
| if: steps.downloaded.outputs.json_count != '0' | |
| uses: benchmark-action/github-action-benchmark@v1 | |
| with: | |
| tool: customBiggerIsBetter | |
| output-file-path: accuracy-benchmark-input.json | |
| gh-pages-branch: gh-pages | |
| benchmark-data-dir-path: benchmark-dashboard | |
| auto-push: false | |
| max-items-in-chart: 300 | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Validate dashboard data after write | |
| if: steps.downloaded.outputs.json_count != '0' | |
| uses: ./.github/actions/validate-dashboard-data | |
| with: | |
| data-path: benchmark-dashboard/data.js | |
| min-entries: ${{ steps.prepare_dashboard_data.outputs.entry_count }} | |
| - name: Push dashboard data | |
| if: steps.downloaded.outputs.json_count != '0' | |
| run: git push origin gh-pages |