nbxtensor 0.0.2__tar.gz → 0.0.4__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 (344) hide show
  1. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/CHANGELOG.md +28 -1
  2. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/PKG-INFO +20 -12
  3. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/README.md +16 -9
  4. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/pyproject.toml +7 -3
  5. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/__init__.py +1 -1
  6. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/__main__.py +15 -5
  7. nbxtensor-0.0.4/src/nbxtensor/backend/install_metal.py +107 -0
  8. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/metal_backend.py +25 -17
  9. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/runtime.py +14 -1
  10. nbxtensor-0.0.4/tests/test_install_metal_fetches_only_what_it_names.py +117 -0
  11. nbxtensor-0.0.4/tests/test_it_installs_on_a_mac.py +23 -0
  12. nbxtensor-0.0.4/tests/test_the_backend_is_the_card_on_the_machine.py +19 -0
  13. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/.github/PULL_REQUEST_TEMPLATE/certificate.md +0 -0
  14. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/CONTRIBUTING.md +0 -0
  15. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/LICENSE +0 -0
  16. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/THIRD_PARTY_NOTICES.md +0 -0
  17. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/assets/logo.png +0 -0
  18. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/__init__.py +0 -0
  19. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/capabilities.py +0 -0
  20. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/cpu/__init__.py +0 -0
  21. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/cpu/blas.py +0 -0
  22. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/cpu/pool.py +0 -0
  23. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/cpu_backend.py +0 -0
  24. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/device_facts.py +0 -0
  25. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/device_files.py +0 -0
  26. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/device_names.py +0 -0
  27. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/amd/cdna.yml +0 -0
  28. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/amd/cdna2.yml +0 -0
  29. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/amd/cdna3.yml +0 -0
  30. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/amd/cdna4.yml +0 -0
  31. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/amd/rdna3.yml +0 -0
  32. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/amd/rdna4.yml +0 -0
  33. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m1.yml +0 -0
  34. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m1_max.yml +0 -0
  35. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m1_pro.yml +0 -0
  36. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m1_ultra.yml +0 -0
  37. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m2.yml +0 -0
  38. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m2_max.yml +0 -0
  39. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m2_pro.yml +0 -0
  40. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m2_ultra.yml +0 -0
  41. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m3.yml +0 -0
  42. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m3_max.yml +0 -0
  43. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m3_pro.yml +0 -0
  44. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m3_ultra.yml +0 -0
  45. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m4.yml +0 -0
  46. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m4_max.yml +0 -0
  47. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m4_pro.yml +0 -0
  48. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m5.yml +0 -0
  49. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m5_max.yml +0 -0
  50. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m5_pro.yml +0 -0
  51. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m5_ultra.yml +0 -0
  52. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_m6.yml +0 -0
  53. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/apple/apple_silicon.yml +0 -0
  54. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/intel/bmg.yml +0 -0
  55. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/intel/dg2.yml +0 -0
  56. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/intel/lnl.yml +0 -0
  57. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/intel/pvc.yml +0 -0
  58. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/nvidia/ampere.yml +0 -0
  59. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/nvidia/hopper.yml +0 -0
  60. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/devices/nvidia/volta.yml +0 -0
  61. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/faults.py +0 -0
  62. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/host_memory.py +0 -0
  63. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/install_help.py +0 -0
  64. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/install_steps.yml +0 -0
  65. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/launcher.py +0 -0
  66. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/launcher_contract.py +0 -0
  67. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/level_zero.py +0 -0
  68. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/level_zero_driver.py +0 -0
  69. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/metal_device.py +0 -0
  70. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/process_memory.py +0 -0
  71. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/replay_graph.py +0 -0
  72. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/backend/triton_ext_driver.py +0 -0
  73. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/__init__.py +0 -0
  74. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/convert.py +0 -0
  75. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/dtypes.yml +0 -0
  76. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/itemsize.py +0 -0
  77. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/nbx_dtype.py +0 -0
  78. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/operands.py +0 -0
  79. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/outputs.py +0 -0
  80. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/policy.py +0 -0
  81. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/policy.yml +0 -0
  82. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/dtype/promotion.py +0 -0
  83. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/host.py +0 -0
  84. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/io/__init__.py +0 -0
  85. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/io/read_rate.py +0 -0
  86. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/io/safetensors.py +0 -0
  87. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/__init__.py +0 -0
  88. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/allocator.py +0 -0
  89. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/arena.py +0 -0
  90. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/device_memory.py +0 -0
  91. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/device_transfer.py +0 -0
  92. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/layout.py +0 -0
  93. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/memory_pool.py +0 -0
  94. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/metal.py +0 -0
  95. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/oom_advice.py +0 -0
  96. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/pointer_tables.py +0 -0
  97. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/pool.py +0 -0
  98. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/shadow.py +0 -0
  99. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/slab.py +0 -0
  100. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/trace_replay.py +0 -0
  101. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +0 -0
  102. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/memory/vmm.py +0 -0
  103. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/__init__.py +0 -0
  104. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/_autotune_policy.py +0 -0
  105. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/_common.py +0 -0
  106. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/_configs.py +0 -0
  107. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/_cost.py +0 -0
  108. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/abs.py +0 -0
  109. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/adaptive_avg_pool2d.py +0 -0
  110. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/add.py +0 -0
  111. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/addcdiv.py +0 -0
  112. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/addcmul.py +0 -0
  113. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/addmv_op.py +0 -0
  114. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/addr.py +0 -0
  115. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/all_reduce.py +0 -0
  116. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/amax.py +0 -0
  117. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/any_reduce.py +0 -0
  118. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/arange_op.py +0 -0
  119. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/argmax.py +0 -0
  120. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/argmin.py +0 -0
  121. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/atan2.py +0 -0
  122. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/attention_wrappers.py +0 -0
  123. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/autotune_bucket.py +0 -0
  124. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/autotune_cache.py +0 -0
  125. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/autotune_certified.py +0 -0
  126. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/autotune_refusals.py +0 -0
  127. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/autotune_settings.py +0 -0
  128. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/avg_pool2d.py +0 -0
  129. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/baddbmm_op.py +0 -0
  130. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/batch_norm.py +0 -0
  131. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/binary_strided.py +0 -0
  132. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/bitwise_and.py +0 -0
  133. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/bitwise_left_shift.py +0 -0
  134. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/bitwise_not.py +0 -0
  135. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/bitwise_or.py +0 -0
  136. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/bitwise_right_shift.py +0 -0
  137. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/bucketize.py +0 -0
  138. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/cat_op.py +0 -0
  139. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/celu.py +0 -0
  140. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/certified_index.py +0 -0
  141. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/clamp.py +0 -0
  142. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/clamp_min.py +0 -0
  143. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/conv2d.py +0 -0
  144. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/conv2d_m8n8k4.py +0 -0
  145. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/conv_depthwise2d.py +0 -0
  146. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/conv_transpose2d.py +0 -0
  147. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/conv_wrappers.py +0 -0
  148. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/copy.py +0 -0
  149. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/copy_op.py +0 -0
  150. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/cos.py +0 -0
  151. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/count_nonzero.py +0 -0
  152. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/cross_entropy.py +0 -0
  153. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/cummax.py +0 -0
  154. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/cummin.py +0 -0
  155. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/cumsum.py +0 -0
  156. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/decode_attn_vec.py +0 -0
  157. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/depthwise_conv2d.py +0 -0
  158. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/dequant_gemv.py +0 -0
  159. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/dft.py +0 -0
  160. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/dispatch.py +0 -0
  161. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/div.py +0 -0
  162. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/dot_op.py +0 -0
  163. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/dropout.py +0 -0
  164. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/dtype_convert.py +0 -0
  165. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/elementwise_launch.py +0 -0
  166. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/elementwise_wrappers.py +0 -0
  167. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/elu.py +0 -0
  168. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/embedding.py +0 -0
  169. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/eq.py +0 -0
  170. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/erf.py +0 -0
  171. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/exp.py +0 -0
  172. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/exp2.py +0 -0
  173. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/fft_op.py +0 -0
  174. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/fill_op.py +0 -0
  175. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/flash_attention.py +0 -0
  176. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/flash_attention_m8n8k4.py +0 -0
  177. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/flash_decode.py +0 -0
  178. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/flip_op.py +0 -0
  179. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/floor.py +0 -0
  180. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/full_op.py +0 -0
  181. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/fused_moe.py +0 -0
  182. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/fused_moe_m8n8k4.py +0 -0
  183. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/fused_upsample_conv.py +0 -0
  184. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/gather_op.py +0 -0
  185. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/ge.py +0 -0
  186. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/gelu.py +0 -0
  187. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/gemv_vec.py +0 -0
  188. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/glu.py +0 -0
  189. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/grid_sampler.py +0 -0
  190. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/group_norm_tiled.py +0 -0
  191. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/groupnorm.py +0 -0
  192. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/gt.py +0 -0
  193. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/hardsigmoid.py +0 -0
  194. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/hardswish.py +0 -0
  195. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/im2col.py +0 -0
  196. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/index_add.py +0 -0
  197. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/index_put_op.py +0 -0
  198. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/index_select.py +0 -0
  199. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/index_wrappers.py +0 -0
  200. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/isfinite.py +0 -0
  201. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/isinf.py +0 -0
  202. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/isnan.py +0 -0
  203. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/launch_keys.py +0 -0
  204. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/launch_observer.py +0 -0
  205. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/layernorm.py +0 -0
  206. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/le.py +0 -0
  207. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/leaky_relu.py +0 -0
  208. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/lerp.py +0 -0
  209. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/linalg_wrappers.py +0 -0
  210. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/linspace_op.py +0 -0
  211. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/log.py +0 -0
  212. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/log_sigmoid.py +0 -0
  213. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/logical_and.py +0 -0
  214. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/logical_not.py +0 -0
  215. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/logical_or.py +0 -0
  216. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/logical_xor.py +0 -0
  217. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/loss_wrappers.py +0 -0
  218. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/lt.py +0 -0
  219. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/masked_fill.py +0 -0
  220. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/matmul.py +0 -0
  221. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/matmul_m8n8k4.py +0 -0
  222. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/max_pool2d.py +0 -0
  223. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/max_pool3d.py +0 -0
  224. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/max_reduce.py +0 -0
  225. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/maximum.py +0 -0
  226. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/mean.py +0 -0
  227. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/min_reduce.py +0 -0
  228. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/minimum.py +0 -0
  229. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/mish.py +0 -0
  230. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/mma_m8n8k4.py +0 -0
  231. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/moe_align.py +0 -0
  232. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/moe_decode_vec.py +0 -0
  233. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/moe_device_residency.py +0 -0
  234. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/moe_wrappers.py +0 -0
  235. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/mse_loss.py +0 -0
  236. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/mul.py +0 -0
  237. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/nan_to_num.py +0 -0
  238. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/ne.py +0 -0
  239. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/neg.py +0 -0
  240. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/nllloss.py +0 -0
  241. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/nonzero.py +0 -0
  242. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/nonzero_op.py +0 -0
  243. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/norm_wrappers.py +0 -0
  244. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/ones_op.py +0 -0
  245. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/oracles/__init__.py +0 -0
  246. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/oracles/conv2d_fp64.py +0 -0
  247. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/pad_op.py +0 -0
  248. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/paged_decode_attn.py +0 -0
  249. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/pixel_shuffle.py +0 -0
  250. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/pixel_unshuffle.py +0 -0
  251. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/pow.py +0 -0
  252. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/prod.py +0 -0
  253. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/rand_op.py +0 -0
  254. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/random_wrappers.py +0 -0
  255. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/reciprocal.py +0 -0
  256. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/reduction_wrappers.py +0 -0
  257. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/relu.py +0 -0
  258. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/remainder.py +0 -0
  259. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/repeat_interleave.py +0 -0
  260. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/repeat_op.py +0 -0
  261. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/residual_chain.py +0 -0
  262. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/rmsnorm.py +0 -0
  263. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/rope.py +0 -0
  264. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/rsqrt.py +0 -0
  265. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/scaled_softmax.py +0 -0
  266. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/scatter_op.py +0 -0
  267. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/screen_oracle.py +0 -0
  268. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/selu.py +0 -0
  269. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/shape_wrappers.py +0 -0
  270. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/sigmoid.py +0 -0
  271. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/silu.py +0 -0
  272. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/sin.py +0 -0
  273. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/softmax.py +0 -0
  274. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/softplus.py +0 -0
  275. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/sort_op.py +0 -0
  276. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/spectral_wrappers.py +0 -0
  277. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/sqrt.py +0 -0
  278. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/stack_op.py +0 -0
  279. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/std.py +0 -0
  280. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/strided_copy.py +0 -0
  281. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/sub.py +0 -0
  282. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/sum.py +0 -0
  283. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/tan.py +0 -0
  284. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/tanh.py +0 -0
  285. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/threshold.py +0 -0
  286. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/tiling.yml +0 -0
  287. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/tiling_policy.py +0 -0
  288. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/tiling_sizes.py +0 -0
  289. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/topk.py +0 -0
  290. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/trace_op.py +0 -0
  291. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/tril.py +0 -0
  292. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/triu.py +0 -0
  293. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/unfold_op.py +0 -0
  294. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/unique_op.py +0 -0
  295. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/upsample_bilinear2d.py +0 -0
  296. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/upsample_nearest2d.py +0 -0
  297. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/upsample_nearest3d.py +0 -0
  298. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/var.py +0 -0
  299. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/vector_norm.py +0 -0
  300. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/weight_norm.py +0 -0
  301. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/where.py +0 -0
  302. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/wrappers.py +0 -0
  303. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/ops/zeros_op.py +0 -0
  304. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/quant/__init__.py +0 -0
  305. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/quant/quantized_tensor.py +0 -0
  306. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/random/__init__.py +0 -0
  307. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/random/mt_randn.py +0 -0
  308. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/random/rng_pin.py +0 -0
  309. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/random/rng_stream.py +0 -0
  310. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/random/seeded_draw.py +0 -0
  311. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/shape/__init__.py +0 -0
  312. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/src/nbxtensor/tensor.py +0 -0
  313. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_a_small_tensor_never_pins_a_large_block.py +0 -0
  314. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_a_sweep_refused_everywhere_says_so.py +0 -0
  315. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_a_triton_kernel_compiles_for_amd_and_launches_through_hip.py +0 -0
  316. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_allocator_interceptor.py +0 -0
  317. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_array_api_promotion.py +0 -0
  318. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_cpu_pool.py +0 -0
  319. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_device_facts.py +0 -0
  320. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_device_files.py +0 -0
  321. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_device_memory.py +0 -0
  322. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_dtype_convert.py +0 -0
  323. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_dtype_outputs.py +0 -0
  324. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_dtype_promotion.py +0 -0
  325. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_host.py +0 -0
  326. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_io_bf16_split.py +0 -0
  327. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_io_read_rate.py +0 -0
  328. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_nbx_dtype.py +0 -0
  329. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_no_environment_switch_changes_a_kernel.py +0 -0
  330. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_ops_cost.py +0 -0
  331. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_policy.py +0 -0
  332. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_readme.py +0 -0
  333. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_shape.py +0 -0
  334. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_the_compiler_key_is_prewarmed.py +0 -0
  335. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_the_hip_driver_keeps_the_launch_contract.py +0 -0
  336. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_the_launcher_refuses_a_backend_it_has_no_driver_for.py +0 -0
  337. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_the_level_zero_backend_keeps_its_contracts.py +0 -0
  338. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_the_malloc_trace_names_the_wrapper.py +0 -0
  339. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_the_metal_dispatch_facts_are_declared_or_refused.py +0 -0
  340. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_third_party_notices.py +0 -0
  341. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tests/test_version.py +0 -0
  342. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tools/device_key_census.py +0 -0
  343. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tools/device_key_trace.py +0 -0
  344. {nbxtensor-0.0.2 → nbxtensor-0.0.4}/tools/scatter_scan.py +0 -0
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.4] - 2026-11-07
11
+
12
+ ### Added
13
+ - **Triton for the Apple GPU in one command**: `python -m nbxtensor install-metal` installs the Metal wheels of the
14
+ project's fork NeuroBrix/triton-ext (Triton built with triton-ext, and imperatormk's AppleGPU backend as
15
+ `nbx-triton-apple`, triton-lang/triton-ext#126), each by the release URL and sha256 written in the library,
16
+ through pip with `--no-deps --require-hashes --no-index` — never an extra index nor a package name resolved on PyPI. The
17
+ wheels are for Python 3.12 and macOS 15 or later (release apple-v0.1.0+triton3.8.0); it refuses by name off an
18
+ Apple-silicon Mac, on another Python, or below the macOS the wheels are built for. `python -m nbxtensor doctor` on a Mac
19
+ without it says so and names the command; the Metal backend's refusal names it too. On a Mac, nbxtensor depends on
20
+ `pyobjc-framework-Metal`, through which it opens the Metal device; without it the run fell to a CPU profile and was
21
+ refused for triton-cpu, it is now refused by name with its install line.
22
+
23
+ ### Fixed
24
+ - **nbxtensor installs on a Mac**: it depended on `triton` on every platform, and PyPI publishes no Triton for macOS, so
25
+ `pip install nbxtensor` — and with it the NeuroBrix engine, even its PyTorch engine on MPS — could not resolve on a
26
+ clean macOS environment. Triton is now a dependency where PyPI has it; on a Mac it is built from source with its Metal
27
+ backend (the README's install), and the library says by name what to build when it is absent.
28
+
29
+ ## [0.0.3] - 2026-11-06
30
+
31
+ ### Fixed
32
+ - **The backend is the one whose card is on the machine**: nbxtensor depends on the CUDA runtime wheel on Linux, and a runtime library loads without its card — on an AMD or Intel machine 0.0.2 named the CUDA backend and never tried HIP or Level Zero. Detection now tries only the runtime libraries of the GPU vendors present on the PCI bus (all of them where the bus cannot be read). The CUDA runtime wheel stays a Linux dependency: without an NVIDIA card it is not used.
33
+ - **The package summary says where it runs**: wherever Triton runs — NVIDIA, AMD, Intel and Apple GPUs (it still named NVIDIA and Apple only).
34
+
10
35
  ## [0.0.2] - 2026-11-06
11
36
 
12
37
  ### Added
@@ -137,6 +162,8 @@ memory; the fix is in test).
137
162
  - **`flash_decode_split_kernel` and its route**: armed only by an environment switch (`NBX_FLASH_DECODE`, with `NBX_FD_BLOCK_N` and `NBX_FD_SEG`) and measured slower than the vector decode kernels; `flash_decode.py` keeps the fixed-order reduce they share.
138
163
  - **`conv1d_forward_kernel`**: launched by no route (a 1-D convolution runs through `conv2d`).
139
164
 
140
- [Unreleased]: https://github.com/NeuroBrix/nbxtensor/compare/v0.0.2...HEAD
165
+ [Unreleased]: https://github.com/NeuroBrix/nbxtensor/compare/v0.0.4...HEAD
166
+ [0.0.4]: https://github.com/NeuroBrix/nbxtensor/compare/v0.0.3...v0.0.4
167
+ [0.0.3]: https://github.com/NeuroBrix/nbxtensor/compare/v0.0.2...v0.0.3
141
168
  [0.0.2]: https://github.com/NeuroBrix/nbxtensor/compare/v0.0.1...v0.0.2
142
169
  [0.0.1]: https://github.com/NeuroBrix/nbxtensor/releases/tag/v0.0.1
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nbxtensor
3
- Version: 0.0.2
4
- Summary: Run AI models without PyTorch: tensors, Triton kernels, planned memory and certified tuning, on NVIDIA and Apple GPUs.
3
+ Version: 0.0.4
4
+ Summary: Run AI models without PyTorch: tensors, Triton kernels, planned memory and certified tuning, wherever Triton runs — NVIDIA, AMD, Intel and Apple GPUs.
5
5
  Project-URL: Homepage, https://github.com/NeuroBrix/nbxtensor
6
6
  Project-URL: Source, https://github.com/NeuroBrix/nbxtensor
7
7
  Project-URL: Issues, https://github.com/NeuroBrix/nbxtensor/issues
@@ -12,8 +12,9 @@ License-File: LICENSE
12
12
  Requires-Python: >=3.10
13
13
  Requires-Dist: numpy
14
14
  Requires-Dist: nvidia-cuda-runtime-cu12; platform_system == 'Linux'
15
+ Requires-Dist: pyobjc-framework-metal; platform_system == 'Darwin'
15
16
  Requires-Dist: pyyaml
16
- Requires-Dist: triton
17
+ Requires-Dist: triton; platform_system != 'Darwin'
17
18
  Provides-Extra: test
18
19
  Requires-Dist: pytest; extra == 'test'
19
20
  Description-Content-Type: text/markdown
@@ -137,7 +138,7 @@ What changed from one version to the next — added, changed, fixed, removed —
137
138
  nbxtensor never needs torch: it is not a dependency, not even an optional one, and the library and its kernels run in
138
139
  an environment where torch is not installed (proven on Metal, 2026-10-29: `tools/torch_free_proof.py` records every
139
140
  attempt to import torch, caught ones included, and recorded none). Python 3.10 or later; its dependencies are numpy,
140
- pyyaml and Triton.
141
+ pyyaml and Triton (on a Mac, `pyobjc-framework-Metal` and the Triton `install-metal` brings).
141
142
 
142
143
  ```
143
144
  pip install nbxtensor
@@ -155,14 +156,21 @@ install commands when one is not.
155
156
 
156
157
  **From the NeuroBrix repository** (contributors): `pip install -e nbxtensor`.
157
158
 
158
- **macOS, Apple silicon (Metal).** PyPI publishes no Triton for macOS, so Triton and its Metal backend are built from
159
- source, then nbxtensor is installed into that environment:
159
+ **macOS, Apple silicon (Metal).** PyPI publishes no Triton for macOS. `install-metal` installs it from the releases of
160
+ the project's fork of triton-ext, [NeuroBrix/triton-ext](https://github.com/NeuroBrix/triton-ext): Triton built with
161
+ `TRITON_EXT_ENABLED=1`, and the AppleGPU backend (the `nbx-triton-apple` wheel) written by imperatormk
162
+ ([triton-lang/triton-ext#126](https://github.com/triton-lang/triton-ext/pull/126)); each wheel is checked against the
163
+ sha256 written in the library (Python 3.12, macOS 15 or later):
160
164
 
161
- 1. Triton (github.com/triton-lang/triton) built with `TRITON_EXT_ENABLED=1`, which lets out-of-tree backends plug in;
162
- 2. the AppleGPU backend of triton-ext (github.com/triton-lang/triton-ext, `backend/AppleGPU`), installed into the same
163
- environment;
164
- 3. `pyobjc-framework-Metal` (the library opens the Metal device through it);
165
- 4. Xcode's Metal toolchain (`xcrun metal` must find the real compiler, not Xcode's placeholder: `TOOLCHAINS=Metal`).
165
+ ```
166
+ pip install nbxtensor
167
+ python -m nbxtensor install-metal
168
+ ```
169
+
170
+ Xcode's Metal toolchain is the other requirement (`xcrun metal` must find the real compiler, not Xcode's placeholder:
171
+ `TOOLCHAINS=Metal`); `python -m nbxtensor doctor` says what is missing. To build Triton and its Metal backend yourself
172
+ instead: Triton (github.com/triton-lang/triton) built with `TRITON_EXT_ENABLED=1`, then triton-ext's `backend/AppleGPU`
173
+ installed into the same environment.
166
174
 
167
175
  `nbxtensor.dtype` and `nbxtensor.shape` need neither a device nor Triton.
168
176
 
@@ -492,7 +500,7 @@ with the tensor class (API.md §4).
492
500
 
493
501
  ## Versions
494
502
 
495
- The first publication was **0.0.1**; this is **0.0.2**. Each fix published after it increments the number (0.0.3,
503
+ The first publication was **0.0.1**; this is **0.0.4**. Each fix published after it increments the number (0.0.5,
496
504
  …), with no alpha or beta label. The rewrite of the core in Rust will be **2.0.0**. `nbxtensor.__version__` is the
497
505
  installed version.
498
506
 
@@ -117,7 +117,7 @@ What changed from one version to the next — added, changed, fixed, removed —
117
117
  nbxtensor never needs torch: it is not a dependency, not even an optional one, and the library and its kernels run in
118
118
  an environment where torch is not installed (proven on Metal, 2026-10-29: `tools/torch_free_proof.py` records every
119
119
  attempt to import torch, caught ones included, and recorded none). Python 3.10 or later; its dependencies are numpy,
120
- pyyaml and Triton.
120
+ pyyaml and Triton (on a Mac, `pyobjc-framework-Metal` and the Triton `install-metal` brings).
121
121
 
122
122
  ```
123
123
  pip install nbxtensor
@@ -135,14 +135,21 @@ install commands when one is not.
135
135
 
136
136
  **From the NeuroBrix repository** (contributors): `pip install -e nbxtensor`.
137
137
 
138
- **macOS, Apple silicon (Metal).** PyPI publishes no Triton for macOS, so Triton and its Metal backend are built from
139
- source, then nbxtensor is installed into that environment:
138
+ **macOS, Apple silicon (Metal).** PyPI publishes no Triton for macOS. `install-metal` installs it from the releases of
139
+ the project's fork of triton-ext, [NeuroBrix/triton-ext](https://github.com/NeuroBrix/triton-ext): Triton built with
140
+ `TRITON_EXT_ENABLED=1`, and the AppleGPU backend (the `nbx-triton-apple` wheel) written by imperatormk
141
+ ([triton-lang/triton-ext#126](https://github.com/triton-lang/triton-ext/pull/126)); each wheel is checked against the
142
+ sha256 written in the library (Python 3.12, macOS 15 or later):
140
143
 
141
- 1. Triton (github.com/triton-lang/triton) built with `TRITON_EXT_ENABLED=1`, which lets out-of-tree backends plug in;
142
- 2. the AppleGPU backend of triton-ext (github.com/triton-lang/triton-ext, `backend/AppleGPU`), installed into the same
143
- environment;
144
- 3. `pyobjc-framework-Metal` (the library opens the Metal device through it);
145
- 4. Xcode's Metal toolchain (`xcrun metal` must find the real compiler, not Xcode's placeholder: `TOOLCHAINS=Metal`).
144
+ ```
145
+ pip install nbxtensor
146
+ python -m nbxtensor install-metal
147
+ ```
148
+
149
+ Xcode's Metal toolchain is the other requirement (`xcrun metal` must find the real compiler, not Xcode's placeholder:
150
+ `TOOLCHAINS=Metal`); `python -m nbxtensor doctor` says what is missing. To build Triton and its Metal backend yourself
151
+ instead: Triton (github.com/triton-lang/triton) built with `TRITON_EXT_ENABLED=1`, then triton-ext's `backend/AppleGPU`
152
+ installed into the same environment.
146
153
 
147
154
  `nbxtensor.dtype` and `nbxtensor.shape` need neither a device nor Triton.
148
155
 
@@ -472,7 +479,7 @@ with the tensor class (API.md §4).
472
479
 
473
480
  ## Versions
474
481
 
475
- The first publication was **0.0.1**; this is **0.0.2**. Each fix published after it increments the number (0.0.3,
482
+ The first publication was **0.0.1**; this is **0.0.4**. Each fix published after it increments the number (0.0.5,
476
483
  …), with no alpha or beta label. The rewrite of the core in Rust will be **2.0.0**. `nbxtensor.__version__` is the
477
484
  installed version.
478
485
 
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nbxtensor"
7
- version = "0.0.2"
8
- description = "Run AI models without PyTorch: tensors, Triton kernels, planned memory and certified tuning, on NVIDIA and Apple GPUs."
7
+ version = "0.0.4"
8
+ description = "Run AI models without PyTorch: tensors, Triton kernels, planned memory and certified tuning, wherever Triton runs — NVIDIA, AMD, Intel and Apple GPUs."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
11
11
  requires-python = ">=3.10"
@@ -16,7 +16,11 @@ authors = [{ name = "NeuroBrix" }]
16
16
  # On Linux the CUDA runtime library its CUDA backend opens (libcudart.so.12), which the NVIDIA driver does not ship: a clean
17
17
  # machine with only the driver found no GPU runtime and the engine read the card as CPU-only (the stable's install test,
18
18
  # 2026-11-05). On a machine without an NVIDIA card it is a library nothing opens.
19
- dependencies = ["numpy", "triton", "pyyaml", "nvidia-cuda-runtime-cu12; platform_system == 'Linux'"]
19
+ # Triton on every platform PyPI publishes it for; macOS has none (the Metal Triton is built from source, with triton-ext's
20
+ # AppleGPU backend — the README's install), so a plain "triton" made the library uninstallable on a Mac (2026-11-06).
21
+ # On a Mac the Metal device is opened through pyobjc's Metal framework; Triton for Metal comes from `install-metal`.
22
+ dependencies = ["numpy", "triton; platform_system != 'Darwin'", "pyyaml", "nvidia-cuda-runtime-cu12; platform_system == 'Linux'",
23
+ "pyobjc-framework-Metal; platform_system == 'Darwin'"]
20
24
 
21
25
  [project.urls]
22
26
  Homepage = "https://github.com/NeuroBrix/nbxtensor"
@@ -13,5 +13,5 @@ first publication is 0.0.1, each fix after it increments, the Rust rewrite is
13
13
  """
14
14
 
15
15
  #: the one version number; `pyproject.toml` states the same (tests/test_version.py)
16
- __version__ = "0.0.2"
16
+ __version__ = "0.0.4"
17
17
  __all__: list = []
@@ -1,11 +1,17 @@
1
1
  """`python -m nbxtensor doctor`: which GPU backend this machine runs, and for every NVIDIA, AMD or Intel GPU on its PCI
2
2
  bus whether the runtime library and the Triton backend it needs are installed — with the vendor's install steps when
3
- one is missing (`backend.install_help`)."""
3
+ one is missing (`backend.install_help`); on an Apple-silicon Mac, whether Triton for Metal is installed.
4
+ `python -m nbxtensor install-metal [--dry-run]`: install Triton for the Apple GPU from this version's release
5
+ (`backend.install_metal`)."""
4
6
  import sys
5
7
 
6
8
 
7
9
  def doctor() -> int:
8
10
  from nbxtensor.backend import install_help, runtime
11
+ from nbxtensor.backend.metal_backend import is_apple_silicon, triton_metal_available
12
+ if is_apple_silicon() and not triton_metal_available():
13
+ print("Apple GPU found; Triton for Metal not installed — run: python -m nbxtensor install-metal")
14
+ return 0
9
15
  try:
10
16
  backend = runtime.gpu_backend()
11
17
  print(f"backend in force: {backend}" + (" (the Apple GPU, through Metal)" if backend == "metal" else ""))
@@ -19,10 +25,14 @@ def doctor() -> int:
19
25
 
20
26
  def main(argv=None) -> int:
21
27
  argv = sys.argv[1:] if argv is None else argv
22
- if argv[:1] != ["doctor"]:
23
- print("usage: python -m nbxtensor doctor", file=sys.stderr)
24
- return 2
25
- return doctor()
28
+ if argv[:1] == ["doctor"]:
29
+ return doctor()
30
+ if argv[:1] == ["install-metal"] and set(argv[1:]) <= {"--dry-run"}:
31
+ import nbxtensor
32
+ from nbxtensor.backend import install_metal
33
+ return install_metal.main(nbxtensor.__version__, dry_run="--dry-run" in argv[1:])
34
+ print("usage: python -m nbxtensor doctor | install-metal [--dry-run]", file=sys.stderr)
35
+ return 2
26
36
 
27
37
 
28
38
  if __name__ == "__main__":
@@ -0,0 +1,107 @@
1
+ """`python -m nbxtensor install-metal`: install Triton for the Apple GPU — the project's own wheels (Triton built with
2
+ TRITON_EXT_ENABLED=1, and triton-ext's AppleGPU backend as `nbx-triton-apple`), downloaded from the release of the
3
+ project's fork, github.com/NeuroBrix/triton-ext. The AppleGPU backend is imperatormk's (triton-lang/triton-ext#126).
4
+
5
+ PyPI publishes no Triton for macOS, so a Mac has no Triton after `pip install nbxtensor`. The wheels are fetched from
6
+ the URLs written below, each checked against the sha256 written beside it (`pip --require-hashes`), with `--no-deps`:
7
+ never through an extra index and never by a package name pip would look up on PyPI — anyone can publish a package
8
+ named "triton-apple-backend" there (dependency confusion). A machine other than an Apple-silicon Mac, or a Python the
9
+ table does not cover, is refused by name.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import platform
15
+ import re
16
+ import subprocess
17
+ import sys
18
+ import tempfile
19
+ from typing import List, Tuple
20
+
21
+ #: the only place the wheels are fetched from: the releases of the project's fork of triton-ext
22
+ RELEASES = "https://github.com/NeuroBrix/triton-ext/releases/download/"
23
+
24
+ #: the Metal wheels per CPython tag: (URL of the release asset, sha256 of the file), Triton first; a tag absent here is
25
+ #: refused by name. The fork's release apple-v0.1.0+triton3.8.0 (2026-11-07): Triton 3.8.0 (git 4a15f415) built with
26
+ #: TRITON_EXT_ENABLED=1 and nbx-triton-apple 0.1.0, both macOS 15.0 arm64 (the prebuilt LLVM Triton links is minos 15.0);
27
+ #: sha256 read on the downloaded assets.
28
+ METAL_WHEELS: dict = {
29
+ "cp312": (
30
+ (RELEASES + "apple-v0.1.0%2Btriton3.8.0/triton-3.8.0%2Bgit4a15f415.nbx1-cp312-cp312-macosx_15_0_arm64.whl",
31
+ "b9084dc93ae7f1468062a03e713d49c9add9a1c9a609fae753a7b32a13b7a223"),
32
+ (RELEASES + "apple-v0.1.0%2Btriton3.8.0/nbx_triton_apple-0.1.0-cp312-cp312-macosx_15_0_arm64.whl",
33
+ "b9ed95c983b220b1fbf1300481b1e2f52a0435c10ee618ef7ccf60a132fe263c"),
34
+ ),
35
+ }
36
+
37
+
38
+ class InstallMetalRefused(RuntimeError):
39
+ """This machine or this Python has no Metal wheels in this version's table."""
40
+
41
+
42
+ def _python_tag() -> str:
43
+ return f"cp{sys.version_info.major}{sys.version_info.minor}"
44
+
45
+
46
+ def wheels(version: str) -> List[Tuple[str, str]]:
47
+ """[(url, sha256)] of the wheels this machine installs, or a named refusal."""
48
+ if not (platform.system() == "Darwin" and platform.machine() == "arm64"):
49
+ raise InstallMetalRefused(
50
+ f"install-metal installs Triton for the Apple GPU and runs on an Apple-silicon Mac only (this machine: "
51
+ f"{platform.system()} {platform.machine()}). Elsewhere Triton comes from PyPI with nbxtensor.")
52
+ tag = _python_tag()
53
+ if tag not in METAL_WHEELS:
54
+ covered = ", ".join(sorted(METAL_WHEELS)) or "none yet"
55
+ raise InstallMetalRefused(
56
+ f"nbxtensor {version} carries no Metal wheels for Python {sys.version_info.major}.{sys.version_info.minor} "
57
+ f"({tag}); covered: {covered}.")
58
+ entries = list(METAL_WHEELS[tag])
59
+ need = max(_min_macos(url) for url, _sha in entries)
60
+ have = _macos_version()
61
+ if have < need:
62
+ raise InstallMetalRefused(
63
+ f"macOS {'.'.join(map(str, need))} required for now: the Metal wheels of nbxtensor {version} are built for "
64
+ f"macOS {'.'.join(map(str, need))} and later (this Mac: macOS {'.'.join(map(str, have))}).")
65
+ return entries
66
+
67
+
68
+ def _min_macos(url: str) -> Tuple[int, int]:
69
+ """The lowest macOS a wheel installs on, read from its platform tag (`macosx_15_0_arm64` -> (15, 0))."""
70
+ m = re.search(r"-macosx_(\d+)_(\d+)_arm64\.whl$", url)
71
+ if m is None:
72
+ raise InstallMetalRefused(f"not a macOS arm64 wheel: {url}")
73
+ return int(m.group(1)), int(m.group(2))
74
+
75
+
76
+ def _macos_version() -> Tuple[int, int]:
77
+ parts = [int(p) for p in (platform.mac_ver()[0] or "0").split(".")[:2]]
78
+ return (parts + [0])[0], (parts + [0])[1]
79
+
80
+
81
+ def pip_command(requirements: str) -> List[str]:
82
+ """pip on this interpreter, from a requirements file of URLs and hashes only: no index is read, nothing else."""
83
+ return [sys.executable, "-m", "pip", "install", "--no-deps", "--require-hashes", "--no-index", "-r", requirements]
84
+
85
+
86
+ def requirements_text(entries: List[Tuple[str, str]]) -> str:
87
+ return "".join(f"{url} --hash=sha256:{sha}\n" for url, sha in entries)
88
+
89
+
90
+ def main(version: str, dry_run: bool = False) -> int:
91
+ try:
92
+ entries = wheels(version)
93
+ except InstallMetalRefused as exc:
94
+ print(f"install-metal: {exc}", file=sys.stderr)
95
+ return 1
96
+ text = requirements_text(entries)
97
+ if dry_run:
98
+ print(text, end="")
99
+ print(" ".join(pip_command("<these lines>")))
100
+ return 0
101
+ fd, path = tempfile.mkstemp(prefix="nbxtensor-metal-", suffix=".txt")
102
+ try:
103
+ with os.fdopen(fd, "w") as f:
104
+ f.write(text)
105
+ return subprocess.call(pip_command(path))
106
+ finally:
107
+ os.unlink(path)
@@ -139,6 +139,16 @@ class TritonMetalShaderCompilerMissingError(TritonMetalNotInstalledError):
139
139
  """
140
140
 
141
141
 
142
+ class TritonMetalBindingMissingError(TritonMetalNotInstalledError):
143
+ """Raised when Triton for Metal is installed but pyobjc's Metal binding, through which the device is opened, is
144
+ not: the device cannot be opened, and the run would otherwise fall to a CPU profile."""
145
+
146
+
147
+ def metal_binding_available() -> bool:
148
+ """pyobjc's Metal binding, through which the device is opened, is installed (asked without importing it)."""
149
+ return importlib.util.find_spec("Metal") is not None
150
+
151
+
142
152
  def is_apple_silicon() -> bool:
143
153
  """True on an Apple-Silicon Mac."""
144
154
  return platform.system() == "Darwin" and platform.machine() == "arm64"
@@ -573,27 +583,25 @@ def ensure_triton_metal_or_raise() -> None:
573
583
 
574
584
  if not triton_metal_available():
575
585
  raise TritonMetalNotInstalledError(
576
- "The --triton engine needs a Triton backend that targets Metal, and "
577
- "none is installed.\n"
578
- "\n"
579
- "Upstream Triton has no Apple GPU target. An out-of-tree backend "
580
- "exists:\n"
581
- " pip install triton-msl (MIT; needs Triton 3.7+ built "
582
- "with TRITON_EXT_ENABLED=1)\n"
586
+ "The --triton engine needs Triton for the Apple GPU (Triton with "
587
+ "triton-ext's AppleGPU backend), and none is installed. PyPI publishes "
588
+ "no Triton for macOS; nbxtensor's release carries it:\n"
589
+ " python -m nbxtensor install-metal\n"
583
590
  "\n"
584
- "NeuroBrix does not install it for you: fetching a wheel at runtime "
585
- "is a\n"
586
- "supply-chain surface you did not consent to, and it fails "
587
- "confusingly on\n"
588
- "air-gapped machines.\n"
591
+ "A run does not install it for you: a download at run time is a "
592
+ "supply-chain surface you did not consent to.\n"
589
593
  "\n"
590
- "Working alternative today: drop --triton. The default compiled "
591
- "engine runs\n"
592
- "on Apple GPUs through PyTorch MPS with no extra install.\n"
593
- "\n"
594
- "Status and known gaps: docs/internal/metal_adoption_plan_2026_09_03.md"
594
+ "Alternative: the PyTorch engine (--compiled) runs on the Apple GPU "
595
+ "through MPS (pip install torch \"neurobrix[pytorch]\")."
595
596
  )
596
597
 
598
+ if not metal_binding_available():
599
+ # the device is opened through pyobjc's Metal binding: without it the run found no device and fell to a CPU
600
+ # profile, refused there for triton-cpu with not a word about Metal (the Mac's clean venv, 2026-11-07 00:51)
601
+ raise TritonMetalBindingMissingError(
602
+ "The --triton engine opens the Apple GPU through pyobjc's Metal binding, and it is not installed:\n"
603
+ " pip install pyobjc-framework-Metal")
604
+
597
605
  if not metal_shader_compiler_available():
598
606
  raise TritonMetalShaderCompilerMissingError(
599
607
  "A Triton Metal backend is installed, but Apple's offline shader "
@@ -141,6 +141,19 @@ _GPU_BACKENDS = {
141
141
  }
142
142
 
143
143
 
144
+ #: the GPU vendor whose card each runtime-library backend drives (PCI vendor names, `backend.install_help`)
145
+ _BACKEND_VENDOR = {"cuda": "nvidia", "hip": "amd"}
146
+
147
+
148
+ def _on_this_machine(backends):
149
+ """The runtime-library backends whose vendor has a card on this machine's PCI bus, in table order — all of them
150
+ where the bus cannot be read (no sysfs). A runtime library loads without its card: the CUDA runtime wheel nbxtensor
151
+ depends on on Linux loaded on an AMD or Intel machine, named "cuda", and HIP / Level Zero were never tried."""
152
+ from nbxtensor.backend.install_help import pci_gpus
153
+ vendors = {v for v, _vid, _dev in pci_gpus()}
154
+ return [(n, b) for n, b in backends if not vendors or _BACKEND_VENDOR[n] in vendors]
155
+
156
+
144
157
  #: the backends found by asking for a DEVICE rather than by loading a runtime library, in order: (name, the probe's
145
158
  #: dotted path). Their runtime tables are not rows of `_GPU_BACKENDS` (another API than one C ABI): `_NATIVE_TABLES`.
146
159
  _DEVICE_PROBES = (("metal", "nbxtensor.backend.metal_device.metal_device_available"),
@@ -314,7 +327,7 @@ def gpu_backend() -> str:
314
327
  return _pin_triton_backend(forced)
315
328
  # The backend TABLE decides: the first vendor runtime the process can
316
329
  # load names the backend — adding a backend is adding a table entry.
317
- for name, backend in _GPU_BACKENDS.items():
330
+ for name, backend in _on_this_machine(_GPU_BACKENDS.items()):
318
331
  for lib in backend["rt_libs"]:
319
332
  try:
320
333
  ctypes.cdll.LoadLibrary(lib)
@@ -0,0 +1,117 @@
1
+ """`python -m nbxtensor install-metal` installs Triton for the Apple GPU from the release of the project's fork
2
+ (github.com/NeuroBrix/triton-ext), each wheel by the URL and sha256 written in the code, through pip with --no-deps --require-hashes --no-index — never an extra index,
3
+ never a package name pip would resolve on PyPI (anyone can publish "triton-apple-backend" there: dependency
4
+ confusion). Off an Apple-silicon Mac, or on a Python the table does not cover, it refuses by name. `python -m nbxtensor
5
+ doctor` on a Mac without Triton names the command (the owner via the supervisor, 2026-11-07 00:45).
6
+
7
+ What this test would do if the code were wrong: a run on Linux that tries to install (red), a covered Python missing
8
+ from the table that installs anyway (red), a pip command reading an index or resolving dependencies (red), an entry
9
+ whose URL leaves the release or whose hash is not a sha256 (red), the table changed without this file (red)."""
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ import sys
14
+
15
+ import pytest
16
+
17
+ from nbxtensor.backend import install_metal as M
18
+
19
+ #: the table as published — this file changes with it, by hand, from the files the release carries
20
+ _R = "https://github.com/NeuroBrix/triton-ext/releases/download/apple-v0.1.0%2Btriton3.8.0/"
21
+ EXPECTED_METAL_WHEELS: dict = {
22
+ "cp312": (
23
+ (_R + "triton-3.8.0%2Bgit4a15f415.nbx1-cp312-cp312-macosx_15_0_arm64.whl",
24
+ "b9084dc93ae7f1468062a03e713d49c9add9a1c9a609fae753a7b32a13b7a223"),
25
+ (_R + "nbx_triton_apple-0.1.0-cp312-cp312-macosx_15_0_arm64.whl",
26
+ "b9ed95c983b220b1fbf1300481b1e2f52a0435c10ee618ef7ccf60a132fe263c"),
27
+ ),
28
+ }
29
+
30
+
31
+ def _mac(monkeypatch, macos="15.4"):
32
+ monkeypatch.setattr(M.platform, "system", lambda: "Darwin")
33
+ monkeypatch.setattr(M.platform, "machine", lambda: "arm64")
34
+ monkeypatch.setattr(M.platform, "mac_ver", lambda: (macos, ("", "", ""), "arm64"))
35
+
36
+
37
+ def test_the_table_is_the_published_one():
38
+ assert M.METAL_WHEELS == EXPECTED_METAL_WHEELS
39
+
40
+
41
+ def test_every_entry_is_a_release_url_of_the_fork_with_a_sha256():
42
+ for tag, entries in M.METAL_WHEELS.items():
43
+ assert re.fullmatch(r"cp3\d+", tag), tag
44
+ names = [url.rsplit("/", 1)[1] for url, _ in entries]
45
+ assert [n.split("-")[0] for n in names] == ["triton", "nbx_triton_apple"], names # Triton first, then the backend
46
+ for (url, sha), name in zip(entries, names):
47
+ assert url.startswith(M.RELEASES) and url.count("/") == M.RELEASES.count("/") + 1, url
48
+ assert re.fullmatch(r"[A-Za-z0-9_.]+-[^-]+-%s-%s-macosx_\d+_\d+_arm64\.whl" % (tag, tag), name), name
49
+ assert re.fullmatch(r"[0-9a-f]{64}", sha), (name, sha)
50
+
51
+
52
+ @pytest.mark.parametrize("system, machine", [("Linux", "x86_64"), ("Darwin", "x86_64"), ("Windows", "AMD64")])
53
+ def test_off_an_apple_silicon_mac_it_refuses_by_name(monkeypatch, system, machine):
54
+ monkeypatch.setattr(M.platform, "system", lambda: system)
55
+ monkeypatch.setattr(M.platform, "machine", lambda: machine)
56
+ with pytest.raises(M.InstallMetalRefused, match="Apple-silicon Mac only"):
57
+ M.wheels("0.0.4")
58
+ calls = []
59
+ monkeypatch.setattr(M.subprocess, "call", lambda *a, **k: calls.append(a) or 0)
60
+ assert M.main("0.0.4") == 1 and not calls
61
+
62
+
63
+ def test_a_python_the_table_does_not_cover_is_refused_by_name(monkeypatch):
64
+ _mac(monkeypatch)
65
+ monkeypatch.setattr(M, "METAL_WHEELS", {"cp399": (("triton-3.8.0-cp399-cp399-macosx_15_0_arm64.whl", "0" * 64),)})
66
+ with pytest.raises(M.InstallMetalRefused, match=r"no Metal wheels for Python .*covered: cp399"):
67
+ M.wheels("0.0.4")
68
+
69
+
70
+ def test_a_covered_python_installs_by_url_and_hash_only(monkeypatch):
71
+ _mac(monkeypatch)
72
+ tag = f"cp{sys.version_info.major}{sys.version_info.minor}"
73
+ base = M.RELEASES + "apple-v0.1.0+triton3.8.0/"
74
+ t = f"{base}triton-3.8.0-{tag}-{tag}-macosx_15_0_arm64.whl"
75
+ b = f"{base}nbx_triton_apple-0.1.0-{tag}-{tag}-macosx_15_0_arm64.whl"
76
+ monkeypatch.setattr(M, "METAL_WHEELS", {tag: ((t, "a" * 64), (b, "b" * 64))})
77
+ seen = {}
78
+
79
+ def call(cmd):
80
+ seen["cmd"] = cmd
81
+ seen["req"] = open(cmd[-1]).read()
82
+ return 0
83
+ monkeypatch.setattr(M.subprocess, "call", call)
84
+ assert M.main("0.0.4") == 0
85
+ assert seen["req"] == f"{t} --hash=sha256:{'a' * 64}\n{b} --hash=sha256:{'b' * 64}\n"
86
+ cmd = seen["cmd"]
87
+ assert cmd[:4] == [sys.executable, "-m", "pip", "install"]
88
+ assert {"--no-deps", "--require-hashes", "--no-index"} <= set(cmd), cmd
89
+ assert not any(a.startswith(("--index-url", "--extra-index-url", "-i", "--find-links", "-f")) for a in cmd), cmd
90
+
91
+
92
+ def test_doctor_on_a_mac_without_triton_names_the_command(monkeypatch, capsys):
93
+ import nbxtensor.__main__ as main_mod
94
+ from nbxtensor.backend import metal_backend
95
+ monkeypatch.setattr(metal_backend, "is_apple_silicon", lambda: True)
96
+ monkeypatch.setattr(metal_backend, "triton_metal_available", lambda: False)
97
+ assert main_mod.main(["doctor"]) == 0
98
+ assert capsys.readouterr().out.strip() == (
99
+ "Apple GPU found; Triton for Metal not installed — run: python -m nbxtensor install-metal")
100
+
101
+
102
+ def test_the_command_line_names_install_metal(capsys):
103
+ import nbxtensor.__main__ as main_mod
104
+ assert main_mod.main(["nothing"]) == 2
105
+ assert "install-metal" in capsys.readouterr().err
106
+
107
+
108
+ def test_a_mac_older_than_the_wheels_tag_is_refused_by_name(monkeypatch):
109
+ # the fork's first wheels were built for macOS 27 (macosx_27_0): pip refuses them on macOS 14-26 with no word of why
110
+ _mac(monkeypatch, macos="15.4")
111
+ tag = f"cp{sys.version_info.major}{sys.version_info.minor}"
112
+ url = f"{M.RELEASES}apple-v0.1.0+triton3.8.0/triton-3.8.0-{tag}-{tag}-macosx_27_0_arm64.whl"
113
+ monkeypatch.setattr(M, "METAL_WHEELS", {tag: ((url, "a" * 64),)})
114
+ with pytest.raises(M.InstallMetalRefused, match=r"macOS 27\.0 required for now.*this Mac: macOS 15\.4"):
115
+ M.wheels("0.0.4")
116
+ _mac(monkeypatch, macos="27.1")
117
+ assert M.wheels("0.0.4") == [(url, "a" * 64)]
@@ -0,0 +1,23 @@
1
+ """nbxtensor installs on a Mac. PyPI publishes no Triton for macOS (triton 3.8.0: two manylinux wheels, no sdist): the
2
+ Metal Triton is built from source (Triton with TRITON_EXT_ENABLED=1 and triton-ext's AppleGPU backend), so a plain
3
+ "triton" dependency made `pip install nbxtensor` — and `pip install "neurobrix[pytorch]"`, the PyTorch engine on MPS —
4
+ ResolutionImpossible on a clean macOS venv (the Mac, 2026-11-06 23:03). The engine's own pyproject already carries the
5
+ marker. Where Triton is absent the library refuses by name with what to build (backend/metal_backend).
6
+
7
+ What this test would do if the packaging were wrong: a triton dependency that macOS must resolve from PyPI (red)."""
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ try:
13
+ import tomllib
14
+ except ModuleNotFoundError: # python 3.10
15
+ import tomli as tomllib
16
+
17
+ PYPROJECT = Path(__file__).resolve().parents[1] / "pyproject.toml"
18
+
19
+
20
+ def test_triton_is_not_resolved_from_pypi_on_macos():
21
+ deps = tomllib.loads(PYPROJECT.read_text())["project"]["dependencies"]
22
+ triton = [d.replace(" ", "").replace('"', "'") for d in deps if d.split(";")[0].strip() == "triton"]
23
+ assert triton == ["triton;platform_system!='Darwin'"], triton
@@ -0,0 +1,19 @@
1
+ """The runtime-library backend is the one whose card is on the machine's PCI bus (`runtime._on_this_machine`).
2
+
3
+ nbxtensor 0.0.2 depends on the CUDA runtime wheel on Linux, and a runtime library loads without its card: on an AMD or
4
+ Intel machine `gpu_backend()` named "cuda" and HIP / Level Zero were never tried (the supervisor's reading of the 0.0.2
5
+ metadata, 2026-11-06 18:25). What this test does if the code were wrong: "cuda" kept on a bus with only an AMD card, or
6
+ a backend dropped where the bus cannot be read — red."""
7
+ from nbxtensor.backend import install_help, runtime
8
+
9
+
10
+ def test_only_the_backends_of_the_cards_present_are_tried(monkeypatch):
11
+ table = list(runtime._GPU_BACKENDS.items())
12
+ monkeypatch.setattr(install_help, "pci_gpus", lambda: [("amd", 0x1002, 0x744C)])
13
+ assert [n for n, _b in runtime._on_this_machine(table)] == ["hip"]
14
+ monkeypatch.setattr(install_help, "pci_gpus", lambda: [("intel", 0x8086, 0xE20B)])
15
+ assert runtime._on_this_machine(table) == [] # Level Zero is probed for its device after
16
+ monkeypatch.setattr(install_help, "pci_gpus", lambda: [("nvidia", 0x10DE, 0x1DB1)])
17
+ assert [n for n, _b in runtime._on_this_machine(table)] == ["cuda"]
18
+ monkeypatch.setattr(install_help, "pci_gpus", lambda: []) # no readable bus (macOS, a sandbox): as before
19
+ assert [n for n, _b in runtime._on_this_machine(table)] == ["cuda", "hip"]
File without changes
File without changes
File without changes