mjolnir-cli 0.2.0__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/.gitignore +14 -0
  2. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/NOTICE +1 -1
  3. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/PKG-INFO +40 -14
  4. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/README.md +32 -4
  5. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/__init__.py +1 -1
  6. mjolnir_cli-0.4.2/mjolnir_cli/__main__.py +10 -0
  7. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/accuracy.py +135 -19
  8. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/autoresearch.py +151 -45
  9. mjolnir_cli-0.4.2/mjolnir_cli/availability.py +349 -0
  10. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/benchy.py +25 -11
  11. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_FLASHINFER/README.md +69 -0
  12. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_FLASHINFER/config.yaml +77 -0
  13. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_FLASHINFER/platforms/orin.yaml +18 -0
  14. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR/README.md +36 -0
  15. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR/config.yaml +81 -0
  16. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR/platforms/orin.yaml +18 -0
  17. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3/.env +4 -0
  18. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3/README.md +36 -0
  19. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3/config.yaml +82 -0
  20. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3/platforms/orin.yaml +18 -0
  21. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3_VT64K/.env +6 -0
  22. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3_VT64K/README.md +43 -0
  23. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3_VT64K/config.yaml +90 -0
  24. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_E2M3_VT64K/platforms/orin.yaml +18 -0
  25. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_VT64K/.env +4 -0
  26. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_VT64K/README.md +84 -0
  27. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_VT64K/config.yaml +83 -0
  28. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/AWQ_MJOLNIR_VT64K/platforms/orin.yaml +18 -0
  29. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/README.md +114 -0
  30. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/platforms/orin.yaml +5 -0
  31. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/platforms/spark.yaml +5 -0
  32. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/platforms/thor.yaml +4 -0
  33. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DFLASH2/README.md +36 -0
  34. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DSPARK/README.md +36 -0
  35. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA2/README.md +36 -0
  36. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA4hd256/README.md +36 -0
  37. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR/README.md +82 -0
  38. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3/README.md +82 -0
  39. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DFLASH2/README.md +36 -0
  40. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DSPARK/README.md +36 -0
  41. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/README.md +102 -0
  42. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/README.md +87 -0
  43. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/kv_scales/draft_vocab-awq.json +1 -0
  44. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/kv_scales/e2m3-awq.json +35197 -0
  45. mjolnir_cli-0.2.0/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/kv_scales.json → mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/kv_scales/e2m3.json +18842 -18842
  46. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_DFLASH/.env +4 -0
  47. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_DFLASH/README.md +72 -0
  48. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_DFLASH/config.yaml +42 -0
  49. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR/README.md +65 -0
  50. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR/config.yaml +55 -0
  51. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3/.env +3 -0
  52. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3/README.md +102 -0
  53. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3/config.yaml +48 -0
  54. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/.env +3 -0
  55. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/README.md +82 -0
  56. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/config.yaml +41 -0
  57. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_VT64K/.env +2 -0
  58. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_VT64K/README.md +36 -0
  59. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/NVFP4_MJOLNIR_E2M3_VT64K/config.yaml +38 -0
  60. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/README.md +62 -0
  61. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/draft_vocab.json +1 -0
  62. mjolnir_cli-0.4.2/mjolnir_cli/bundled/configs/nvidia/Qwen3.6-35B-A3B/kv_scales/e2m3.json +11425 -0
  63. mjolnir_cli-0.2.0/mjolnir_cli/bundled/scripts/hw/setup-thor.sh → mjolnir_cli-0.4.2/mjolnir_cli/bundled/scripts/hw/setup-jetson-steps.sh +128 -421
  64. mjolnir_cli-0.4.2/mjolnir_cli/bundled/scripts/hw/setup-jetson.sh +413 -0
  65. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/cachectl.py +2 -3
  66. mjolnir_cli-0.4.2/mjolnir_cli/charts.py +633 -0
  67. mjolnir_cli-0.4.2/mjolnir_cli/charts_base.py +358 -0
  68. mjolnir_cli-0.4.2/mjolnir_cli/charts_e2e.py +529 -0
  69. mjolnir_cli-0.4.2/mjolnir_cli/charts_serving.py +587 -0
  70. mjolnir_cli-0.4.2/mjolnir_cli/cli.py +125 -0
  71. mjolnir_cli-0.4.2/mjolnir_cli/cli_shared.py +38 -0
  72. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/clidoc.py +14 -1
  73. mjolnir_cli-0.4.2/mjolnir_cli/commands/__init__.py +11 -0
  74. mjolnir_cli-0.4.2/mjolnir_cli/commands/_common.py +58 -0
  75. mjolnir_cli-0.4.2/mjolnir_cli/commands/bench.py +490 -0
  76. mjolnir_cli-0.4.2/mjolnir_cli/commands/bench_report.py +104 -0
  77. mjolnir_cli-0.4.2/mjolnir_cli/commands/calibrate.py +94 -0
  78. mjolnir_cli-0.4.2/mjolnir_cli/commands/hw.py +102 -0
  79. mjolnir_cli-0.4.2/mjolnir_cli/commands/image.py +162 -0
  80. mjolnir_cli-0.4.2/mjolnir_cli/commands/litellm.py +91 -0
  81. mjolnir_cli-0.4.2/mjolnir_cli/commands/model.py +163 -0
  82. mjolnir_cli-0.4.2/mjolnir_cli/commands/perses.py +72 -0
  83. mjolnir_cli-0.4.2/mjolnir_cli/commands/platform.py +46 -0
  84. mjolnir_cli-0.4.2/mjolnir_cli/commands/results.py +153 -0
  85. mjolnir_cli-0.4.2/mjolnir_cli/commands/retention.py +35 -0
  86. mjolnir_cli-0.4.2/mjolnir_cli/commands/serve.py +94 -0
  87. mjolnir_cli-0.4.2/mjolnir_cli/commands/version.py +12 -0
  88. mjolnir_cli-0.4.2/mjolnir_cli/commands/window.py +54 -0
  89. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/config.py +140 -24
  90. mjolnir_cli-0.4.2/mjolnir_cli/config_bench.py +254 -0
  91. mjolnir_cli-0.4.2/mjolnir_cli/config_profile.py +127 -0
  92. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/dockerctl.py +237 -45
  93. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/drivers/fa2_driver.py +16 -16
  94. mjolnir_cli-0.4.2/mjolnir_cli/drivers/fa4_driver.py +105 -0
  95. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/drivers/fi_driver.py +11 -1
  96. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/e2e.py +81 -48
  97. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/gate.py +21 -4
  98. mjolnir_cli-0.4.2/mjolnir_cli/history.py +276 -0
  99. mjolnir_cli-0.4.2/mjolnir_cli/interactive.py +25 -0
  100. mjolnir_cli-0.4.2/mjolnir_cli/legs.py +235 -0
  101. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/litellm.py +4 -4
  102. mjolnir_cli-0.4.2/mjolnir_cli/overlays.py +145 -0
  103. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/perses.py +6 -6
  104. mjolnir_cli-0.4.2/mjolnir_cli/platforms.py +542 -0
  105. mjolnir_cli-0.4.2/mjolnir_cli/py.typed +0 -0
  106. mjolnir_cli-0.4.2/mjolnir_cli/rawprune.py +111 -0
  107. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/readme.py +394 -506
  108. mjolnir_cli-0.4.2/mjolnir_cli/readme_tables.py +406 -0
  109. mjolnir_cli-0.4.2/mjolnir_cli/readme_targets.py +702 -0
  110. mjolnir_cli-0.4.2/mjolnir_cli/retention.py +167 -0
  111. mjolnir_cli-0.4.2/mjolnir_cli/site/__init__.py +9 -0
  112. mjolnir_cli-0.4.2/mjolnir_cli/site/app.css +136 -0
  113. mjolnir_cli-0.4.2/mjolnir_cli/site/app.js +662 -0
  114. mjolnir_cli-0.4.2/mjolnir_cli/site/data.py +161 -0
  115. mjolnir_cli-0.4.2/mjolnir_cli/site/page.py +123 -0
  116. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/stack.py +10 -8
  117. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/tasks.py +31 -13
  118. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/theme.py +52 -25
  119. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/thor.py +44 -5
  120. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/cli.py +136 -61
  121. mjolnir_cli-0.4.2/mjolnir_cli/verify/correctness.py +263 -0
  122. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/docker.py +134 -21
  123. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/kvcache.py +1 -1
  124. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/legs.py +77 -9
  125. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/ncu.py +45 -15
  126. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/profile.py +7 -5
  127. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/report.py +1 -2
  128. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/results.py +99 -11
  129. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/stages.py +197 -256
  130. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/pyproject.toml +11 -11
  131. mjolnir_cli-0.2.0/mjolnir_cli/__main__.py +0 -4
  132. mjolnir_cli-0.2.0/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/.env +0 -2
  133. mjolnir_cli-0.2.0/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8/config.yaml +0 -98
  134. mjolnir_cli-0.2.0/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/.env +0 -4
  135. mjolnir_cli-0.2.0/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT32K/config.yaml +0 -98
  136. mjolnir_cli-0.2.0/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/.env +0 -3
  137. mjolnir_cli-0.2.0/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_VT32K/config.yaml +0 -98
  138. mjolnir_cli-0.2.0/mjolnir_cli/charts.py +0 -1687
  139. mjolnir_cli-0.2.0/mjolnir_cli/cli.py +0 -1480
  140. mjolnir_cli-0.2.0/mjolnir_cli/history.py +0 -159
  141. mjolnir_cli-0.2.0/mjolnir_cli/interactive.py +0 -383
  142. mjolnir_cli-0.2.0/mjolnir_cli/legs.py +0 -109
  143. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/LICENSE +0 -0
  144. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/hatch_build.py +0 -0
  145. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4/config.yaml +0 -0
  146. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DFLASH2/config.yaml +0 -0
  147. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_DSPARK/config.yaml +0 -0
  148. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA2/config.yaml +0 -0
  149. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_FA4hd256/config.yaml +0 -0
  150. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR/config.yaml +0 -0
  151. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3/config.yaml +0 -0
  152. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DFLASH2/config.yaml +0 -0
  153. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_DSPARK/config.yaml +0 -0
  154. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/.env +0 -0
  155. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/NVFP4_MJOLNIR_E2M3_LMH8_VT64K/config.yaml +0 -0
  156. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8-27B/draft_vocab.json +0 -0
  157. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/configs/Qwen/Qwen3.8_chat_template.jinja +0 -0
  158. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/litellm/docker-compose.yaml +0 -0
  159. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/litellm/litellm.env.example +0 -0
  160. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/litellm/litellm_config.template.example.yaml +0 -0
  161. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/README.md +0 -0
  162. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/dashboards/global-datasource.yaml +0 -0
  163. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/dashboards/performance_statistics.yaml +0 -0
  164. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/dashboards/project.yaml +0 -0
  165. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/dashboards/query_statistics.yaml +0 -0
  166. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/docker-compose.yaml +0 -0
  167. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/perses.yml +0 -0
  168. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/bundled/docker/perses/prometheus.template.yaml +0 -0
  169. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/fonts/InterDisplay-Bold.ttf +0 -0
  170. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/fonts/InterDisplay-Medium.ttf +0 -0
  171. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/fonts/InterDisplay-Regular.ttf +0 -0
  172. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/fonts/InterDisplay-SemiBold.ttf +0 -0
  173. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/fonts/OFL.txt +0 -0
  174. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/picker.py +0 -0
  175. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/__init__.py +0 -0
  176. {mjolnir_cli-0.2.0 → mjolnir_cli-0.4.2}/mjolnir_cli/verify/spec.py +0 -0
@@ -1,8 +1,18 @@
1
1
  __pycache__/
2
2
  *.py[cod]
3
3
  .venv/
4
+ .playwright-mcp/
4
5
  .pytest_cache/
5
6
  .ruff_cache/
7
+ .env
8
+ # the configs' env sidecars are plain mjolnir knobs (calibration paths, feature
9
+ # flags) and ride with the configs; real secrets stay out
10
+ !configs/**/.env
11
+ # coverage artifacts (the CI test stage writes these; they regenerate)
12
+ .coverage
13
+ .coverage.*
14
+ coverage.xml
15
+ .coverage_html/
6
16
  dist/
7
17
  build/
8
18
  *.egg-info/
@@ -18,6 +28,7 @@ benchmarks/raw/*
18
28
  !benchmarks/raw/accuracy/
19
29
  benchmarks/raw/accuracy/*/*/
20
30
  benchmarks/logs/
31
+ benchmarks/staging/
21
32
  task/
22
33
 
23
34
  # autoresearch: the live ledger is untracked (discards are `git reset --hard`);
@@ -26,3 +37,6 @@ research/ledger.jsonl
26
37
  research/ledger.tsv
27
38
  # …and the exported runs ARE committed (the generic runs/ rule above would hide them)
28
39
  !research/runs/
40
+ /cutlass/
41
+ /fa2/
42
+ /llamacpp/
@@ -8,7 +8,7 @@ works listed below; each keeps its own license and copyright notice.
8
8
  -------------------------------------------------------------------------------
9
9
  1. NVIDIA CUTLASS — CuTe DSL examples BSD-3-Clause
10
10
  -------------------------------------------------------------------------------
11
- src/mjolnir/mjolnir/t5_decode.py (the Mjolnir decode-attention kernel) is
11
+ src/mjolnir/mjolnir/arch/thor/decode.py (the Mjolnir decode-attention kernel) is
12
12
  derived from
13
13
 
14
14
  examples/python/CuTeDSL/cute/blackwell/kernel/attention/mixed_input_fmha/
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mjolnir-cli
3
- Version: 0.2.0
3
+ Version: 0.4.2
4
4
  Summary: Serve, benchmark and document LLMs on NVIDIA Jetson AGX Thor: YAML model configs, the Mjolnir vLLM image, KV-cache calibration, kernel / serving / accuracy benchmarks and auto-rendered reports.
5
- Project-URL: Homepage, https://github.com/thor-arena/mjolnir
6
- Project-URL: Documentation, https://github.com/thor-arena/mjolnir/tree/main/docs
7
- Project-URL: Repository, https://github.com/thor-arena/mjolnir.git
8
- Project-URL: Issues, https://github.com/thor-arena/mjolnir/issues
9
- Project-URL: Changelog, https://github.com/thor-arena/mjolnir/releases
10
- Project-URL: CLI reference, https://github.com/thor-arena/mjolnir/blob/main/docs/cli.md
5
+ Project-URL: Homepage, https://github.com/GoBrrr-ai/mjolnir
6
+ Project-URL: Documentation, https://github.com/GoBrrr-ai/mjolnir/tree/main/docs
7
+ Project-URL: Repository, https://github.com/GoBrrr-ai/mjolnir.git
8
+ Project-URL: Issues, https://github.com/GoBrrr-ai/mjolnir/issues
9
+ Project-URL: Changelog, https://github.com/GoBrrr-ai/mjolnir/releases
10
+ Project-URL: CLI reference, https://github.com/GoBrrr-ai/mjolnir/blob/main/docs/cli.md
11
11
  Author-email: Mieszko Syty <mieszko@ms1design.pl>
12
12
  License-Expression: Apache-2.0
13
13
  License-File: LICENSE
@@ -18,13 +18,11 @@ Classifier: Environment :: Console
18
18
  Classifier: Intended Audience :: Developers
19
19
  Classifier: Operating System :: POSIX :: Linux
20
20
  Classifier: Programming Language :: Python :: 3
21
- Classifier: Programming Language :: Python :: 3.10
22
- Classifier: Programming Language :: Python :: 3.11
23
21
  Classifier: Programming Language :: Python :: 3.12
24
22
  Classifier: Programming Language :: Python :: 3.13
25
23
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
26
24
  Classifier: Topic :: System :: Benchmark
27
- Requires-Python: >=3.10
25
+ Requires-Python: >=3.12
28
26
  Requires-Dist: jinja2>=3.1.6
29
27
  Requires-Dist: pyyaml>=6.0.3
30
28
  Requires-Dist: rich>=15.0.0
@@ -36,7 +34,7 @@ Description-Content-Type: text/markdown
36
34
  # mjolnir-cli
37
35
 
38
36
  **Serve, benchmark and document LLMs on the NVIDIA Jetson AGX Thor** with
39
- [Mjolnir](https://github.com/thor-arena/mjolnir) — a decode-attention kernel
37
+ [Mjolnir](https://github.com/GoBrrr-ai/mjolnir) — a decode-attention kernel
40
38
  built for Thor (`sm_110`), a 6-bit KV-cache format for vLLM and a patched vLLM
41
39
  image that runs them. This package is the `mjolnir` command that drives all of
42
40
  it; the kernel itself ships separately as
@@ -71,17 +69,45 @@ mjolnir serve up # serve the default model on the Mjolnir image,
71
69
  tests and logs kernel experiments.
72
70
 
73
71
  Every command, with options and defaults:
74
- **[the CLI reference](https://github.com/thor-arena/mjolnir/blob/main/docs/cli.md)**
72
+ **[the CLI reference](https://github.com/GoBrrr-ai/mjolnir/blob/main/docs/cli.md)**
75
73
  (generated from the commands; `mjolnir <command> --help` prints the same).
76
74
 
75
+ ## Library use
76
+
77
+ Two modules are declared a public, stable API — reuse them instead of
78
+ re-implementing the clean-window gate or the bench-history substrate:
79
+
80
+ - **`mjolnir_cli.gate`** — `wait_for_idle`, `server_load`, `server_counters`,
81
+ `spec_delta`, the `CleanWindow` context manager, `PreflightError` and the
82
+ `DEFAULT_*` tuning constants.
83
+ - **`mjolnir_cli.history`** — `append_record`, `load_records` and
84
+ `SCHEMA_VERSION`; the history-row schema is documented in the module
85
+ docstring. Rows carry `schema_version` — absence means the legacy (pre-0.3)
86
+ format — and `load_records` returns rows raw, never normalized.
87
+ `append_record` files one record per serving key (platform, model, image,
88
+ config): a re-run merges its cells into the stored record instead of
89
+ appending a line; records without a serving key are appended verbatim.
90
+
91
+ Anything else can change without notice; the module docstrings are the API
92
+ reference: [`gate.py`](https://github.com/GoBrrr-ai/mjolnir/blob/main/src/mjolnir-cli/mjolnir_cli/gate.py),
93
+ [`history.py`](https://github.com/GoBrrr-ai/mjolnir/blob/main/src/mjolnir-cli/mjolnir_cli/history.py).
94
+
95
+ `uv tool install mjolnir-cli` is an isolated CLI install — *not* importable
96
+ from another project. Library use means adding `mjolnir-cli` as a dependency
97
+ of your project (`uv add mjolnir-cli` / `pip install mjolnir-cli`). The
98
+ gate/history import chain is stdlib-only: the CLI dependency stack (typer,
99
+ rich, pyyaml, jinja2) is paid at install time, not import time. Python ≥ 3.12
100
+ is required (pip refuses older interpreters — relevant on JetPack 6-era
101
+ images), and type annotations ship (`py.typed`).
102
+
77
103
  ## Requirements
78
104
 
79
105
  A Jetson AGX Thor (JetPack 7.x) with Docker and the NVIDIA Container Toolkit
80
- for anything that touches the GPU; Python ≥ 3.10. Charts need the `plot` extra
106
+ for anything that touches the GPU; Python ≥ 3.12. Charts need the `plot` extra
81
107
  (`uv tool install 'mjolnir-cli[plot]'`).
82
108
 
83
109
  ## Versions
84
110
 
85
111
  `mjolnir-cli` and `mjolnir-kernel` version independently; the Docker image is
86
112
  tagged after the upstream vLLM it is built on (`v0.31.0`). Release notes:
87
- [GitHub Releases](https://github.com/thor-arena/mjolnir/releases).
113
+ [GitHub Releases](https://github.com/GoBrrr-ai/mjolnir/releases).
@@ -1,7 +1,7 @@
1
1
  # mjolnir-cli
2
2
 
3
3
  **Serve, benchmark and document LLMs on the NVIDIA Jetson AGX Thor** with
4
- [Mjolnir](https://github.com/thor-arena/mjolnir) — a decode-attention kernel
4
+ [Mjolnir](https://github.com/GoBrrr-ai/mjolnir) — a decode-attention kernel
5
5
  built for Thor (`sm_110`), a 6-bit KV-cache format for vLLM and a patched vLLM
6
6
  image that runs them. This package is the `mjolnir` command that drives all of
7
7
  it; the kernel itself ships separately as
@@ -36,17 +36,45 @@ mjolnir serve up # serve the default model on the Mjolnir image,
36
36
  tests and logs kernel experiments.
37
37
 
38
38
  Every command, with options and defaults:
39
- **[the CLI reference](https://github.com/thor-arena/mjolnir/blob/main/docs/cli.md)**
39
+ **[the CLI reference](https://github.com/GoBrrr-ai/mjolnir/blob/main/docs/cli.md)**
40
40
  (generated from the commands; `mjolnir <command> --help` prints the same).
41
41
 
42
+ ## Library use
43
+
44
+ Two modules are declared a public, stable API — reuse them instead of
45
+ re-implementing the clean-window gate or the bench-history substrate:
46
+
47
+ - **`mjolnir_cli.gate`** — `wait_for_idle`, `server_load`, `server_counters`,
48
+ `spec_delta`, the `CleanWindow` context manager, `PreflightError` and the
49
+ `DEFAULT_*` tuning constants.
50
+ - **`mjolnir_cli.history`** — `append_record`, `load_records` and
51
+ `SCHEMA_VERSION`; the history-row schema is documented in the module
52
+ docstring. Rows carry `schema_version` — absence means the legacy (pre-0.3)
53
+ format — and `load_records` returns rows raw, never normalized.
54
+ `append_record` files one record per serving key (platform, model, image,
55
+ config): a re-run merges its cells into the stored record instead of
56
+ appending a line; records without a serving key are appended verbatim.
57
+
58
+ Anything else can change without notice; the module docstrings are the API
59
+ reference: [`gate.py`](https://github.com/GoBrrr-ai/mjolnir/blob/main/src/mjolnir-cli/mjolnir_cli/gate.py),
60
+ [`history.py`](https://github.com/GoBrrr-ai/mjolnir/blob/main/src/mjolnir-cli/mjolnir_cli/history.py).
61
+
62
+ `uv tool install mjolnir-cli` is an isolated CLI install — *not* importable
63
+ from another project. Library use means adding `mjolnir-cli` as a dependency
64
+ of your project (`uv add mjolnir-cli` / `pip install mjolnir-cli`). The
65
+ gate/history import chain is stdlib-only: the CLI dependency stack (typer,
66
+ rich, pyyaml, jinja2) is paid at install time, not import time. Python ≥ 3.12
67
+ is required (pip refuses older interpreters — relevant on JetPack 6-era
68
+ images), and type annotations ship (`py.typed`).
69
+
42
70
  ## Requirements
43
71
 
44
72
  A Jetson AGX Thor (JetPack 7.x) with Docker and the NVIDIA Container Toolkit
45
- for anything that touches the GPU; Python ≥ 3.10. Charts need the `plot` extra
73
+ for anything that touches the GPU; Python ≥ 3.12. Charts need the `plot` extra
46
74
  (`uv tool install 'mjolnir-cli[plot]'`).
47
75
 
48
76
  ## Versions
49
77
 
50
78
  `mjolnir-cli` and `mjolnir-kernel` version independently; the Docker image is
51
79
  tagged after the upstream vLLM it is built on (`v0.31.0`). Release notes:
52
- [GitHub Releases](https://github.com/thor-arena/mjolnir/releases).
80
+ [GitHub Releases](https://github.com/GoBrrr-ai/mjolnir/releases).
@@ -5,4 +5,4 @@ kernel against FlashInfer and FlashAttention 2, and render the
5
5
  data-driven README charts — one command surface.
6
6
  """
7
7
 
8
- __version__ = "0.2.0"
8
+ __version__ = "0.4.2"
@@ -0,0 +1,10 @@
1
+ """Run the ``mjolnir`` CLI via ``python -m mjolnir_cli`` — the same entry
2
+ point as the console script (``mjolnir_cli.cli:main``), UnknownPlatform
3
+ handler included."""
4
+
5
+ import sys
6
+
7
+ from mjolnir_cli.cli import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())
@@ -14,8 +14,9 @@ tool-eval-bench keeps its results in ``./data/benchmarks.sqlite`` and
14
14
  under ``benchmarks/raw/accuracy/<run>/<leg>/``, so the database holds exactly
15
15
  that leg's runs. :func:`summarize_db` reads it back into the committed
16
16
  ``accuracy.json`` (``mjolnir.accuracy/1``): per suite the score, per
17
- tool-call category the share of points, per scenario the points — what the
18
- docs table and charts are rendered from.
17
+ tool-call category the share of points, per scenario the points, and the ids
18
+ tool-eval-bench excluded from scoring (``excluded``) — what the docs table
19
+ and charts are rendered from.
19
20
  """
20
21
 
21
22
  from __future__ import annotations
@@ -24,6 +25,7 @@ import json
24
25
  import os
25
26
  import shlex
26
27
  import shutil
28
+ import platform as host_platform # the record's host field names the machine (platform.node)
27
29
  import sqlite3
28
30
  import subprocess
29
31
  import sys
@@ -31,6 +33,9 @@ import time
31
33
  from pathlib import Path
32
34
  from statistics import mean
33
35
 
36
+ from mjolnir_cli.config import by_model, model_of, model_short
37
+ from mjolnir_cli.platforms import platform_of
38
+
34
39
  FORMAT = "mjolnir.accuracy/1"
35
40
  TOOL_REPO = "https://github.com/SeraphimSerapis/tool-eval-bench"
36
41
  # pinned: a score is only comparable to one produced by the same scenario set
@@ -94,7 +99,9 @@ def _pct(earned: float, mx: float) -> float | None:
94
99
  def summarize_db(db: Path) -> dict:
95
100
  """tool-eval-bench's SQLite → ``{"suites": {suite: {...}}, "categories":
96
101
  {...}, "scenarios": {...}}``. Several completed runs of a kind (``--trials``)
97
- are averaged; each suite keeps its per-run scores."""
102
+ are averaged; each suite keeps its per-run scores. A scenario the tool
103
+ excluded from scoring (an infrastructure failure: the probe failed, a
104
+ timeout) comes back as ``excluded``."""
98
105
  if not db.exists():
99
106
  return {"suites": {}, "categories": {}, "scenarios": {}, "runs": []}
100
107
  con = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
@@ -108,13 +115,15 @@ def summarize_db(db: Path) -> dict:
108
115
  per_suite: dict[str, list[dict]] = {}
109
116
  cats: dict[str, dict] = {}
110
117
  scen: dict[str, list[float]] = {}
118
+ excluded: set[str] = set()
111
119
  runs = []
112
- for run_id, created, status, run_type, scores_json, config_json in rows:
120
+ for run_id, created, status, run_type, scores_json, _config_json in rows:
113
121
  runs.append({"run_id": run_id, "created_at": created, "status": status, "run_type": run_type})
114
122
  if status != "completed":
115
123
  continue
116
124
  sc = _loads(scores_json)
117
125
  if run_type == "tool_eval":
126
+ excluded |= set(sc.get("excluded_scenarios") or ()) # upstream lists them only when non-empty
118
127
  cs = sc.get("category_scores") or []
119
128
  std = [c for c in cs if c.get("category") != HARD_CATEGORY]
120
129
  hard = [c for c in cs if c.get("category") == HARD_CATEGORY]
@@ -160,8 +169,13 @@ def summarize_db(db: Path) -> dict:
160
169
  continue
161
170
  last = dict(items[-1])
162
171
  last.pop("score", None)
172
+ if all("max" in i for i in items): # every trial's points: deltas rescale each trial exactly
173
+ last["earneds"], last["maxes"] = [i["earned"] for i in items], [i["max"] for i in items]
174
+ comps = [i["completion_rate"] for i in items if i.get("completion_rate") is not None]
175
+ if comps:
176
+ last["completion_rate"] = min(comps) # the worst trial: an exclusion in any trial shows
163
177
  suites[name] = {"score": round(mean(scores), 2), "scores": scores, "n": len(scores), **last}
164
- return {
178
+ out = {
165
179
  "suites": suites,
166
180
  "categories": {
167
181
  k: {"label": v["label"], "percent": round(mean(v["percent"]), 2)}
@@ -171,6 +185,9 @@ def summarize_db(db: Path) -> dict:
171
185
  "scenarios": {k: round(mean(v), 3) for k, v in sorted(scen.items())},
172
186
  "runs": runs,
173
187
  }
188
+ if excluded: # e.g. TC-45: the tool_choice=required probe failed — scored as zero on the full max
189
+ out["excluded"] = sorted(excluded)
190
+ return out
174
191
 
175
192
 
176
193
  # ── the run ─────────────────────────────────────────────────────────────────
@@ -204,9 +221,7 @@ def leg_label(rec: dict) -> str:
204
221
  if "MJOLNIR" not in cfg:
205
222
  return f"Mjolnir image · {BACKEND_LABELS.get(cfg, cfg)} · {kv}"
206
223
  extra = "".join(
207
- f" + {name}"
208
- for tag, name in (("LMH8", "fp8 lm_head"), ("VT32K", "32K draft vocab"), ("VT64K", "64K draft vocab"))
209
- if tag in cfg.split("_")
224
+ f" + {name}" for tag, name in (("LMH8", "fp8 lm_head"), ("VT64K", "64K draft vocab")) if tag in cfg.split("_")
210
225
  )
211
226
  return f"Mjolnir · {kv}{extra}"
212
227
 
@@ -223,6 +238,7 @@ def run_accuracy(
223
238
  tool-eval-bench against it, and write ``accuracy.json``. Returns its path."""
224
239
  from mjolnir_cli import dockerctl
225
240
  from mjolnir_cli.config import config_field
241
+ from mjolnir_cli.platforms import detect, gpu_name
226
242
 
227
243
  tool = find_tool()
228
244
  run_id = time.strftime("%Y%m%d-%H%M%S")
@@ -232,6 +248,10 @@ def run_accuracy(
232
248
  "format": FORMAT,
233
249
  "run_id": run_id,
234
250
  "created": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
251
+ "host": host_platform.node() or "unknown",
252
+ "gpu": gpu_name(),
253
+ # the detection-first rule: the record names the *detected* platform
254
+ "platform": detect().platform,
235
255
  "tool": {"repo": TOOL_REPO, "commit": TOOL_COMMIT, "command": tool},
236
256
  "args": list(passthrough) or list(DEFAULT_ARGS),
237
257
  "legs": [],
@@ -292,36 +312,132 @@ def load_runs(layout) -> list[dict]:
292
312
 
293
313
 
294
314
  def latest_legs(docs: list[dict]) -> list[dict]:
295
- """One row per (model, image, config): every suite from the newest run
296
- that measured it (an A/B run and a later GSM8K run merge into one row).
297
- Order: first appearance."""
315
+ """One row per (platform, model, image side, config): every suite from the
316
+ newest run that measured it (an A/B run and a later GSM8K run merge into
317
+ one row). Order: first appearance. The platform comes from the run doc
318
+ (platform-less records are the repo's Thor history); a leg measured on
319
+ two platforms is two rows — its charts are per (platform, model). The
320
+ side (official image vs Mjolnir) keys the row, not the image tag, so a
321
+ re-measure on a newer image of the same side supersedes the older scores
322
+ in place (the row keeps its position and the newest image) instead of
323
+ duplicating a row the labels cannot tell apart."""
298
324
  rows: dict[tuple, dict] = {}
299
325
  for d in docs:
326
+ plat = platform_of(d)
300
327
  for leg in d.get("legs", []):
301
- key = (leg.get("model"), leg.get("image"), leg.get("config"))
328
+ key = (plat, model_of(leg), _is_mjolnir(str(leg.get("image", ""))), leg.get("config"))
302
329
  row = rows.setdefault(
303
330
  key,
304
331
  {k: leg.get(k) for k in ("id", "model", "image", "config", "label", "kv_cache_dtype", "env")}
305
- | {"suites": {}, "categories": {}, "scenarios": {}, "runs": {}},
332
+ | {"platform": plat, "suites": {}, "categories": {}, "scenarios": {}, "runs": {}},
306
333
  )
334
+ row["image"] = leg.get("image") or row.get("image")
307
335
  row["kv_cache_dtype"] = leg.get("kv_cache_dtype") or row.get("kv_cache_dtype")
308
336
  row["label"] = leg_label(row) # recomputed: labels improve after a run is stored
309
337
  for name, suite in (leg.get("suites") or {}).items():
310
338
  row["suites"][name] = suite
311
339
  row["runs"][name] = {"run_id": d.get("run_id"), "args": d.get("args")}
340
+ if leg.get("excluded"): # an exclusion in any run of the leg shows
341
+ row["excluded"] = sorted({*(row.get("excluded") or ()), *leg["excluded"]})
312
342
  if (leg.get("suites") or {}).keys() & {"tools", "hardmode"}:
313
343
  row["categories"] = leg.get("categories") or row["categories"]
314
344
  row["scenarios"] = leg.get("scenarios") or row["scenarios"]
315
- return [r for r in rows.values() if r["suites"]]
345
+ groups = by_model(r for r in rows.values() if r["suites"])
346
+ if len(groups) > 1: # rows of several models: each model's rows together, labels say which
347
+ for r in (r for g in groups.values() for r in g):
348
+ r["label"] = f"{model_short(r)} · {r['label']}"
349
+ return [r for g in groups.values() for r in g]
316
350
 
317
351
 
318
352
  def baseline(rows: list[dict]) -> dict | None:
319
- """The reference row for deltas: native FlashInfer on the official image
320
- when measured, else the first row."""
321
- for r in rows:
322
- if not _is_mjolnir(str(r.get("image", ""))) and r.get("config") == "NVFP4":
353
+ """The reference row for deltas among ONE model's rows: native FlashInfer
354
+ on the official image when measured, else the first official-image row,
355
+ else the first row. For mixed rows use :func:`baselines`."""
356
+ official = [r for r in rows if not _is_mjolnir(str(r.get("image", "")))]
357
+ for r in official:
358
+ if r.get("config") == "NVFP4":
323
359
  return r
324
- return rows[0] if rows else None
360
+ return (official or rows or [None])[0]
361
+
362
+
363
+ def baselines(rows: list[dict]) -> list[dict | None]:
364
+ """Each row's baseline, in row order: its own model's :func:`baseline`.
365
+ Rows of another model are never a baseline — their scores are not comparable."""
366
+ per_model = {m: baseline(group) for m, group in by_model(rows).items()}
367
+ return [per_model[model_of(r)] for r in rows]
368
+
369
+
370
+ def _maxes(s: dict) -> list:
371
+ """Every trial's max for a suite (older records keep only the last trial's)."""
372
+ return list(s.get("maxes") or ([s["max"]] if s.get("max") else []))
373
+
374
+
375
+ def _on_max(s: dict, m: float) -> float:
376
+ """The suite's score with every trial's points over ``m``: exact from the
377
+ per-trial points when recorded, else from the one run's points, else from
378
+ the mean % (an older averaged record, which keeps only the last max)."""
379
+ if s.get("earneds"):
380
+ return mean(e / m * 100 for e in s["earneds"])
381
+ if s.get("n", 1) == 1 and "earned" in s:
382
+ return s["earned"] / m * 100
383
+ return s["score"] * s["max"] / m
384
+
385
+
386
+ def delta(r: dict, base: dict | None, name: str) -> tuple[float, bool] | None:
387
+ """Points ``r`` scored over ``base`` on suite ``name``, and whether an
388
+ excluded scenario was scored as zero (None when ``r`` is the baseline,
389
+ either lacks the suite, or the two measured different scenario sets).
390
+
391
+ tool-eval-bench drops a scenario it judged the endpoint unable to serve
392
+ (TC-45 when its ``tool_choice=required`` probe fails, a timeout, an error),
393
+ which shrinks that trial's max and lowers its completion rate, so its %
394
+ divides by less. Then every trial of both legs is scored on the largest
395
+ max, the excluded points counting as zero: an exclusion never raises a
396
+ delta. Different maxes at full completion mean different scenario sets
397
+ (another run's arguments), which no rescaling makes comparable."""
398
+ if base is None or r is base:
399
+ return None
400
+ v, b = r.get("suites", {}).get(name) or {}, base.get("suites", {}).get(name) or {}
401
+ if v.get("score") is None or b.get("score") is None:
402
+ return None
403
+ maxes = _maxes(v) + _maxes(b)
404
+ if len(set(maxes)) > 1:
405
+ if all(x.get("completion_rate", 100.0) >= 100.0 for x in (v, b)):
406
+ return None
407
+ m = max(maxes)
408
+ return _on_max(v, m) - _on_max(b, m), True
409
+ return v["score"] - b["score"], False
410
+
411
+
412
+ def fmt_delta(d: tuple[float, bool] | None) -> str:
413
+ """' (+1.2)', ' (-1.4‡)' when an excluded scenario was scored as zero, '' for no delta."""
414
+ return f" ({d[0]:+.1f}{'‡' if d[1] else ''})" if d else ""
415
+
416
+
417
+ def leg_excluded(r: dict, base: dict | None, names: list[str]) -> bool:
418
+ """Whether any of ``r``'s suite deltas scored an excluded scenario as zero."""
419
+ return any((d := delta(r, base, n)) and d[1] for n in names)
420
+
421
+
422
+ def any_excluded(rows: list[dict], bases: list[dict | None], names: list[str]) -> bool:
423
+ return any(leg_excluded(r, b, names) for r, b in zip(rows, bases, strict=True))
424
+
425
+
426
+ EXCLUDED_NOTE = (
427
+ "‡ = tool-eval-bench excluded a scenario on one of the two legs; the delta scores both on the full max, "
428
+ "the excluded points counting as zero"
429
+ )
430
+ EXCLUDED_NOTE_SHORT = "‡ = a scenario was excluded on one side, scored as zero"
431
+ CATEGORY_EXCLUDED_NOTE = (
432
+ "‡ = this leg had a scenario excluded: its category % divides by the smaller max, so one category reads high"
433
+ )
434
+
435
+
436
+ def baseline_caption(bases: list[dict | None], item_sep: str = " · ") -> str:
437
+ """The distinct baseline labels, first-seen order, ' / '-joined ('' if none);
438
+ ``item_sep`` replaces the label's own ' · ' separator."""
439
+ labels = dict.fromkeys(b["label"].replace(" · ", item_sep) for b in bases if b is not None)
440
+ return " / ".join(labels)
325
441
 
326
442
 
327
443
  def suites_in(rows: list[dict]) -> list[str]: