cactus-compute 2.2.0__tar.gz → 2.2.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 (209) hide show
  1. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/PKG-INFO +1 -1
  2. cactus_compute-2.2.2/cactus/_version.py +1 -0
  3. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/bindings/cactus.py +30 -0
  4. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_audio_buckets.py +15 -4
  5. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_parakeet_capture.py +4 -2
  6. cactus_compute-2.2.2/cactus/convert/tests/test_parakeet_local_attention.py +125 -0
  7. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/aten_ops.py +1 -0
  8. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/audio_preprocess.py +6 -1
  9. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/canonicalize/cleanup.py +1 -0
  10. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/importers.py +18 -0
  11. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/lower.py +20 -0
  12. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/ops.py +1 -0
  13. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/tdt_runtime.py +86 -42
  14. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Converter/input_processor.py +1 -0
  15. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/ModelProfiles/profiles.py +1 -0
  16. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus_compute.egg-info/PKG-INFO +1 -1
  17. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus_compute.egg-info/SOURCES.txt +1 -0
  18. cactus_compute-2.2.0/cactus/_version.py +0 -1
  19. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/README.md +0 -0
  20. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/__init__.py +0 -0
  21. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/__main__.py +0 -0
  22. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/assets/test.wav +0 -0
  23. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/assets/test_monkey.png +0 -0
  24. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/bindings/__init__.py +0 -0
  25. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/__init__.py +0 -0
  26. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/auth.py +0 -0
  27. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/clean.py +0 -0
  28. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/code.py +0 -0
  29. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/common.py +0 -0
  30. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/compile.py +0 -0
  31. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/config_utils.py +0 -0
  32. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/convert.py +0 -0
  33. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/download.py +0 -0
  34. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/list.py +0 -0
  35. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/model.py +0 -0
  36. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/run.py +0 -0
  37. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/runtime.py +0 -0
  38. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/serve.py +0 -0
  39. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/test.py +0 -0
  40. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/transcribe.py +0 -0
  41. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/transpile.py +0 -0
  42. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/transpiler.py +0 -0
  43. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/upload.py +0 -0
  44. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/cli/utils.py +0 -0
  45. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/__init__.py +0 -0
  46. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/assets/gemma-4-e2b-it/probe.pt +0 -0
  47. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/cactus_adapters/__init__.py +0 -0
  48. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/cactus_adapters/config_utils.py +0 -0
  49. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/cactus_adapters/tensor_io.py +0 -0
  50. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/cactus_adapters/tokenizer.py +0 -0
  51. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/cactus_adapters/weight_patterns.py +0 -0
  52. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/calibration/__init__.py +0 -0
  53. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/calibration/hessian.py +0 -0
  54. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/calibration/loaders.py +0 -0
  55. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/cli.py +0 -0
  56. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/compat.py +0 -0
  57. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/export/__init__.py +0 -0
  58. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/export/files.py +0 -0
  59. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/export/qdq.py +0 -0
  60. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/export/reports.py +0 -0
  61. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/export/validate.py +0 -0
  62. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/handoff_probe.py +0 -0
  63. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/interleave_orthogonal_cq4.py +0 -0
  64. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/model_adapters/__init__.py +0 -0
  65. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/model_adapters/adapters.py +0 -0
  66. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/model_adapters/detection.py +0 -0
  67. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/model_adapters/naming.py +0 -0
  68. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/model_adapters/nemo.py +0 -0
  69. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/model_adapters/policy.py +0 -0
  70. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/quantization/__init__.py +0 -0
  71. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/quantization/cq.py +0 -0
  72. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_cq.py +0 -0
  73. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_gemma4_adapter.py +0 -0
  74. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_hessian.py +0 -0
  75. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_lfm2_adapter.py +0 -0
  76. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_naming_qdq.py +0 -0
  77. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_policy.py +0 -0
  78. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_qwen_adapter.py +0 -0
  79. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/convert/tests/test_reports.py +0 -0
  80. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/models/__init__.py +0 -0
  81. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/models/needle/__init__.py +0 -0
  82. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/models/needle/configuration_needle.py +0 -0
  83. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/models/needle/modeling_needle.py +0 -0
  84. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/models/needle/tokenization_needle.py +0 -0
  85. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/py.typed +0 -0
  86. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/server.py +0 -0
  87. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/__init__.py +0 -0
  88. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/canonicalize/utils.py +0 -0
  89. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/capture_jax.py +0 -0
  90. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/capture_pytorch.py +0 -0
  91. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/component_partition.py +0 -0
  92. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/component_pipeline.py +0 -0
  93. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/component_plan.py +0 -0
  94. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/__init__.py +0 -0
  95. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/attention.py +0 -0
  96. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/common.py +0 -0
  97. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/conv.py +0 -0
  98. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/deltanet.py +0 -0
  99. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/linear.py +0 -0
  100. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/lstm.py +0 -0
  101. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/mlp.py +0 -0
  102. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/rel_pos_bias.py +0 -0
  103. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/rms_norm.py +0 -0
  104. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/fusion/rope.py +0 -0
  105. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/graph_ir.py +0 -0
  106. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/hf_model.py +0 -0
  107. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/import_ir.py +0 -0
  108. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/import_semantics.py +0 -0
  109. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/jax_semantic_rewrites.py +0 -0
  110. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/jax_user_graph_bundle.py +0 -0
  111. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/media_limits.py +0 -0
  112. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/model_adapters.py +0 -0
  113. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/model_patterns.py +0 -0
  114. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/model_profiles.py +0 -0
  115. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/multimodal_runtime.py +0 -0
  116. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/normalize.py +0 -0
  117. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/npu/__init__.py +0 -0
  118. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/npu/audio.py +0 -0
  119. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/npu/coremltools_patches.py +0 -0
  120. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/npu/pipeline.py +0 -0
  121. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/npu/source.py +0 -0
  122. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/npu/vision.py +0 -0
  123. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/optimize_graph.py +0 -0
  124. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/runtime_compat.py +0 -0
  125. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/runtime_support.py +0 -0
  126. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/weight_binding.py +0 -0
  127. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpile/weight_compat.py +0 -0
  128. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Converter/cache_utils.py +0 -0
  129. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Converter/constants.py +0 -0
  130. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Converter/convert.py +0 -0
  131. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Converter/models.py +0 -0
  132. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Converter/overrides.py +0 -0
  133. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Converter/version.py +0 -0
  134. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/edges.py +0 -0
  135. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_builders.py +0 -0
  136. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_cache_conv.py +0 -0
  137. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_defs/__init__.py +0 -0
  138. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_defs/fusion_attention.py +0 -0
  139. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_defs/fusion_direct.py +0 -0
  140. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_defs/fusion_moe.py +0 -0
  141. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_defs/fusion_neural.py +0 -0
  142. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusion_defs/fusion_special.py +0 -0
  143. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/fusions.py +0 -0
  144. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/models.py +0 -0
  145. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Fusions/nodes.py +0 -0
  146. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/__init__.py +0 -0
  147. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/component_splits/__init__.py +0 -0
  148. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/component_splits/component_split.py +0 -0
  149. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/component_splits/component_split_boundaries.py +0 -0
  150. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/component_splits/component_split_builder.py +0 -0
  151. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/component_splits/component_split_plans.py +0 -0
  152. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/component_splits/component_split_types.py +0 -0
  153. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/component_splits/component_split_utils.py +0 -0
  154. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/constants.py +0 -0
  155. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/errors.py +0 -0
  156. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/generate.py +0 -0
  157. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/__init__.py +0 -0
  158. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_basic_ops.py +0 -0
  159. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_cache.py +0 -0
  160. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_constant_ops.py +0 -0
  161. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_core.py +0 -0
  162. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_nn_ops.py +0 -0
  163. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_special_ops.py +0 -0
  164. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_utils.py +0 -0
  165. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/lowerings/lowering_weights.py +0 -0
  166. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/models.py +0 -0
  167. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/Generator/version.py +0 -0
  168. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/constants.py +0 -0
  169. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/extra_matchers/__init__.py +0 -0
  170. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/extra_matchers/extra_matcher_cache.py +0 -0
  171. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/extra_matchers/extra_matcher_common.py +0 -0
  172. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/extra_matchers/extra_matcher_generic.py +0 -0
  173. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/extra_matchers/extra_matcher_linear.py +0 -0
  174. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/extra_matchers/extra_matcher_moe.py +0 -0
  175. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/match.py +0 -0
  176. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/match_utils.py +0 -0
  177. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/models.py +0 -0
  178. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/simplify_ir.py +0 -0
  179. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/special_fusions.py +0 -0
  180. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/IR/version.py +0 -0
  181. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/ModelProfiles/combinations.py +0 -0
  182. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/ModelProfiles/components.py +0 -0
  183. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/ModelProfiles/models.py +0 -0
  184. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/ModelProfiles/routes.py +0 -0
  185. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus/transpiler/RuntimePlan/models.py +0 -0
  186. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus_compute.egg-info/dependency_links.txt +0 -0
  187. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus_compute.egg-info/entry_points.txt +0 -0
  188. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus_compute.egg-info/requires.txt +0 -0
  189. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/cactus_compute.egg-info/top_level.txt +0 -0
  190. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/pyproject.toml +0 -0
  191. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/setup.cfg +0 -0
  192. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_bindings.py +0 -0
  193. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_chat_template_golden.py +0 -0
  194. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_cli_run.py +0 -0
  195. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_cli_transpile_defaults.py +0 -0
  196. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_cloud_handoff_live.py +0 -0
  197. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_component_plan.py +0 -0
  198. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_download.py +0 -0
  199. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_generator_component_split.py +0 -0
  200. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_graph.py +0 -0
  201. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_model.py +0 -0
  202. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_runtime_plan.py +0 -0
  203. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_server.py +0 -0
  204. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_server_live.py +0 -0
  205. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_tool_constraint_state.py +0 -0
  206. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_transpile_aten_ops.py +0 -0
  207. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_transpile_importers.py +0 -0
  208. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_transpile_weight_compat.py +0 -0
  209. {cactus_compute-2.2.0 → cactus_compute-2.2.2}/tests/test_transpiler_reporting.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cactus-compute
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: On-device AI inference — LLM, vision, speech, embeddings, and RAG
5
5
  Author: Cactus Compute
6
6
  License: Cactus Compute License
@@ -0,0 +1 @@
1
+ __version__ = "2.2.2"
@@ -568,6 +568,11 @@ _lib.cactus_graph_rel_pos_bias.argtypes = [
568
568
  cactus_graph_t, cactus_node_t, cactus_node_t, ctypes.c_float, ctypes.POINTER(cactus_node_t)
569
569
  ]
570
570
  _lib.cactus_graph_rel_pos_bias.restype = ctypes.c_int
571
+ _lib.cactus_graph_rel_pos_attention.argtypes = [
572
+ cactus_graph_t, cactus_node_t, cactus_node_t, cactus_node_t, cactus_node_t, cactus_node_t,
573
+ ctypes.c_bool, cactus_node_t, ctypes.c_float, ctypes.c_size_t, ctypes.POINTER(cactus_node_t)
574
+ ]
575
+ _lib.cactus_graph_rel_pos_attention.restype = ctypes.c_int
571
576
  _lib.cactus_graph_attention_int8_hybrid.argtypes = [
572
577
  cactus_graph_t, cactus_node_t, cactus_node_t, cactus_node_t, ctypes.c_float, ctypes.c_size_t,
573
578
  ctypes.POINTER(ctypes.c_int8), ctypes.POINTER(ctypes.c_int8),
@@ -2570,6 +2575,31 @@ class Graph:
2570
2575
  raise RuntimeError(_err("graph_rel_pos_bias failed"))
2571
2576
  return self._apply_backend(self._tensor_from_node(out.value), backend)
2572
2577
 
2578
+ def rel_pos_attention(self, query, key, value, rel_query, relative_key, key_mask, scale, window_size=0, backend=None):
2579
+ query = self._ensure_tensor(query)
2580
+ key = self._ensure_tensor(key)
2581
+ value = self._ensure_tensor(value)
2582
+ rel_query = self._ensure_tensor(rel_query)
2583
+ relative_key = self._ensure_tensor(relative_key)
2584
+ key_mask = None if key_mask is None else self._ensure_tensor(key_mask)
2585
+ out = cactus_node_t()
2586
+ rc = _lib.cactus_graph_rel_pos_attention(
2587
+ self.h,
2588
+ cactus_node_t(query.id),
2589
+ cactus_node_t(key.id),
2590
+ cactus_node_t(value.id),
2591
+ cactus_node_t(rel_query.id),
2592
+ cactus_node_t(relative_key.id),
2593
+ ctypes.c_bool(key_mask is not None),
2594
+ cactus_node_t(0 if key_mask is None else key_mask.id),
2595
+ ctypes.c_float(float(scale)),
2596
+ ctypes.c_size_t(int(window_size)),
2597
+ ctypes.byref(out),
2598
+ )
2599
+ if rc != 0:
2600
+ raise RuntimeError(_err("graph_rel_pos_attention failed"))
2601
+ return self._apply_backend(self._tensor_from_node(out.value), backend)
2602
+
2573
2603
  def attention_int8_hybrid(self, query, key_new, value_new, scale, position_offset,
2574
2604
  cached_keys, cached_values, k_scales, v_scales,
2575
2605
  cache_len, num_kv_heads, head_dim, window_size=0, backend=None):
@@ -1,5 +1,6 @@
1
1
  from cactus.transpile.audio_preprocess import audio_bucket_frames
2
2
  from cactus.transpile.audio_preprocess import audio_bucket_seconds
3
+ from cactus.transpile.audio_preprocess import audio_capture_frames
3
4
 
4
5
 
5
6
  def test_fibonacci_ladder_capped_at_model_window():
@@ -26,8 +27,18 @@ def test_buckets_are_sorted_and_unique():
26
27
  assert len(buckets) == len(set(buckets))
27
28
 
28
29
 
29
- def test_default_ladder_is_fibonacci_seconds():
30
- assert audio_bucket_seconds() == (1, 2, 3, 5, 8, 13, 21)
30
+ def test_default_ladder_seconds():
31
+ assert audio_bucket_seconds() == (1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 300, 377)
32
+
33
+
34
+ def test_capture_window_is_420_seconds():
35
+ assert audio_capture_frames() == 42000
36
+
37
+
38
+ def test_env_override_keeps_capture_window(monkeypatch):
39
+ monkeypatch.setenv("CACTUS_TRANSPILER_AUDIO_BUCKETS", "30,60")
40
+ assert audio_capture_frames() == 42000
41
+ assert audio_bucket_frames(audio_capture_frames()) == [3000, 6000, 42000]
31
42
 
32
43
 
33
44
  def test_env_override_replaces_ladder(monkeypatch):
@@ -43,9 +54,9 @@ def test_env_override_is_sorted_and_deduplicated(monkeypatch):
43
54
 
44
55
  def test_malformed_env_override_falls_back_to_default(monkeypatch):
45
56
  monkeypatch.setenv("CACTUS_TRANSPILER_AUDIO_BUCKETS", "not-a-number")
46
- assert audio_bucket_seconds() == (1, 2, 3, 5, 8, 13, 21)
57
+ assert audio_bucket_seconds() == (1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 300, 377)
47
58
 
48
59
 
49
60
  def test_empty_env_override_falls_back_to_default(monkeypatch):
50
61
  monkeypatch.setenv("CACTUS_TRANSPILER_AUDIO_BUCKETS", "0,-3")
51
- assert audio_bucket_seconds() == (1, 2, 3, 5, 8, 13, 21)
62
+ assert audio_bucket_seconds() == (1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 300, 377)
@@ -19,9 +19,11 @@ def test_capture_window_is_independent_of_validation_audio():
19
19
  with patch.dict("os.environ", {"CACTUS_TRANSPILER_AUDIO_BUCKETS": ""}):
20
20
  specs = build_parakeet_tdt_component_specs(model, named_tensors={"input_features": features})
21
21
  encoders = specs[:-1]
22
- assert [int(spec.metadata["audio_frames"]) for spec in encoders] == [100, 200, 300, 500, 800, 1300, 2100, 3000]
22
+ assert [int(spec.metadata["audio_frames"]) for spec in encoders] == [
23
+ 100, 200, 300, 500, 800, 1300, 2100, 3400, 5500, 8900, 14400, 23300, 30000, 37700, 42000,
24
+ ]
23
25
  assert encoders[-1].component == "audio_encoder"
24
- assert tuple(encoders[-1].example_inputs[0].shape) == (1, 3000, 128)
26
+ assert tuple(encoders[-1].example_inputs[0].shape) == (1, 42000, 128)
25
27
  assert torch.all(features == 2.0)
26
28
  assert not torch.all(encoders[-1].example_inputs[0] == 2.0)
27
29
 
@@ -0,0 +1,125 @@
1
+ from types import SimpleNamespace
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+ from cactus.transpile.tdt_runtime import ParakeetTDTSelfAttention
7
+ from cactus.transpile.tdt_runtime import rel_pos_attention
8
+
9
+
10
+ def _random_inputs(seq_len, rel_len, *, heads=2, dim=16, padded=3, seed=0):
11
+ generator = torch.Generator().manual_seed(seed)
12
+ shape = (1, seq_len, heads, dim)
13
+ tensors = [torch.randn(shape, generator=generator) for _ in range(4)]
14
+ relative_key = torch.randn((1, rel_len, heads, dim), generator=generator)
15
+ key_mask = torch.zeros((1, seq_len))
16
+ key_mask[:, seq_len - padded:] = -10000.0
17
+ return (*tensors, relative_key, key_mask)
18
+
19
+
20
+ def _dense_reference(query, key, value, rel_query, relative_key, key_mask, scale, window):
21
+ seq_len = query.shape[1]
22
+ center = (relative_key.shape[1] - 1) // 2
23
+ out = torch.empty_like(query)
24
+ for t in range(seq_len):
25
+ for h in range(query.shape[2]):
26
+ scores = []
27
+ for j in range(seq_len):
28
+ if window > 0 and abs(t - j) > window:
29
+ scores.append(float("-inf"))
30
+ continue
31
+ content = torch.dot(query[0, t, h], key[0, j, h])
32
+ position = torch.dot(rel_query[0, t, h], relative_key[0, center - (t - j), h])
33
+ scores.append(float((content + position) * scale + key_mask[0, j]))
34
+ weights = torch.softmax(torch.tensor(scores), dim=0)
35
+ out[0, t, h] = weights @ value[0, :, h]
36
+ return out
37
+
38
+
39
+ def test_rel_pos_attention_matches_dense_reference():
40
+ for seq_len, window, rel_len in ((40, 6, 13), (40, 0, 79), (300, 20, 41)):
41
+ inputs = _random_inputs(seq_len, rel_len)
42
+ expected = _dense_reference(*inputs, 0.25, window)
43
+ actual = rel_pos_attention(*inputs, 0.25, window)
44
+ torch.testing.assert_close(actual, expected, atol=1e-5, rtol=1e-5)
45
+
46
+
47
+ def _attention_layer(hidden=32, heads=4):
48
+ torch.manual_seed(0)
49
+ state_dict = {f"layer.{name}.weight": torch.randn(hidden, hidden) * 0.2
50
+ for name in ("linear_q", "linear_k", "linear_v", "linear_out", "linear_pos")}
51
+ state_dict["layer.pos_bias_u"] = torch.randn(heads, hidden // heads)
52
+ state_dict["layer.pos_bias_v"] = torch.randn(heads, hidden // heads)
53
+ config = SimpleNamespace(
54
+ hidden_dim=hidden,
55
+ attention_heads=heads,
56
+ attention_head_dim=hidden // heads,
57
+ attention_scale=(hidden // heads) ** -0.5,
58
+ )
59
+ return ParakeetTDTSelfAttention(config, "layer", state_dict).eval()
60
+
61
+
62
+ def test_local_attention_exports_single_semantic_op():
63
+ from cactus.transpile.capture_pytorch import capture_model
64
+
65
+ class _Windowed(torch.nn.Module):
66
+ def __init__(self):
67
+ super().__init__()
68
+ self.attention = _attention_layer()
69
+
70
+ def forward(self, x, mask):
71
+ return self.attention(x, mask, 4)
72
+
73
+ captured = capture_model(_Windowed().eval(), (torch.randn(1, 12, 32), torch.zeros(1, 1, 1, 12)), strict=False)
74
+ nodes = [
75
+ captured.ir_graph.nodes[node_id]
76
+ for node_id in captured.ir_graph.order
77
+ if captured.ir_graph.nodes[node_id].op == "rel_pos_attention"
78
+ ]
79
+ assert len(nodes) == 1
80
+ assert nodes[0].attrs == {"scale": 8 ** -0.5, "window_size": 4}
81
+ assert len(nodes[0].inputs) == 6
82
+
83
+
84
+ def test_engine_op_matches_torch_reference():
85
+ from cactus.bindings.cactus import Graph
86
+
87
+ for seq_len, window, rel_len in ((150, 20, 41), (70, 0, 139)):
88
+ inputs = _random_inputs(seq_len, rel_len, dim=32, padded=5, seed=1)
89
+ inputs = tuple(tensor.half().float() for tensor in inputs)
90
+ expected = rel_pos_attention(*inputs, 32 ** -0.5, window)
91
+
92
+ graph = Graph()
93
+ nodes = [graph.input(tuple(tensor.shape)) for tensor in inputs]
94
+ out = graph.rel_pos_attention(*nodes, 32 ** -0.5, window)
95
+ for node, tensor in zip(nodes, inputs):
96
+ graph.set_input(node, tensor.numpy().astype(np.float16))
97
+ graph.execute()
98
+ np.testing.assert_allclose(out.numpy().astype(np.float32), expected.numpy(), atol=3e-3)
99
+
100
+
101
+ def test_chunked_subsampling_matches_single_pass():
102
+ from cactus.transpile.tdt_runtime import ParakeetTDTPreEncode
103
+
104
+ torch.manual_seed(0)
105
+ channels, mels, hidden = 4, 16, 8
106
+ state_dict = {
107
+ "encoder.pre_encode.conv.0.weight": torch.randn(channels, 1, 3, 3),
108
+ "encoder.pre_encode.conv.0.bias": torch.randn(channels),
109
+ "encoder.pre_encode.out.weight": torch.randn(hidden, channels * 2),
110
+ "encoder.pre_encode.out.bias": torch.randn(hidden),
111
+ }
112
+ for index in (2, 5):
113
+ state_dict[f"encoder.pre_encode.conv.{index}.weight"] = torch.randn(channels, 1, 3, 3)
114
+ state_dict[f"encoder.pre_encode.conv.{index}.bias"] = torch.randn(channels)
115
+ state_dict[f"encoder.pre_encode.conv.{index + 1}.weight"] = torch.randn(channels, channels, 1, 1)
116
+ state_dict[f"encoder.pre_encode.conv.{index + 1}.bias"] = torch.randn(channels)
117
+ state_dict = {name: tensor * 0.3 for name, tensor in state_dict.items()}
118
+ config = SimpleNamespace(subsampling_conv_channels=channels, num_mel_bins=mels, hidden_dim=hidden)
119
+ pre_encode = ParakeetTDTPreEncode(config, state_dict).eval()
120
+ features = torch.randn(1, 7003, mels)
121
+ with torch.no_grad():
122
+ chunked = pre_encode(features)
123
+ single = pre_encode._subsample(features)
124
+ assert chunked.shape == single.shape
125
+ torch.testing.assert_close(chunked, single, atol=1e-5, rtol=1e-5)
@@ -54,6 +54,7 @@ _EXACT_RULES: dict[str, str] = {
54
54
  "qwen2_moe_layer_gated.default": "qwen2_moe_layer_gated",
55
55
  "cactus_transpile.gemma4_moe_layer_gated.default": "gemma4_moe_layer_gated",
56
56
  "gemma4_moe_layer_gated.default": "gemma4_moe_layer_gated",
57
+ "rel_pos_attention.default": "rel_pos_attention",
57
58
  # Keep the legacy importer key until the importer itself is renamed.
58
59
  "aten.diff.default": "aten.diff.default",
59
60
  }
@@ -21,7 +21,8 @@ _PARAKEET_HOP_LENGTH = 160
21
21
  _PARAKEET_PREEMPHASIS = 0.97
22
22
  _PARAKEET_LOG_FLOOR = np.float32(2**-24)
23
23
  _DEFAULT_MAX_AUDIO_SECONDS = 30.0
24
- _DEFAULT_AUDIO_BUCKET_SECONDS = (1, 2, 3, 5, 8, 13, 21)
24
+ _DEFAULT_AUDIO_BUCKET_SECONDS = (1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 300, 377)
25
+ _AUDIO_CAPTURE_SECONDS = 420
25
26
 
26
27
 
27
28
  def audio_duration_limit_seconds() -> float:
@@ -47,6 +48,10 @@ def audio_bucket_seconds() -> tuple[int, ...]:
47
48
  return positive
48
49
 
49
50
 
51
+ def audio_capture_frames() -> int:
52
+ return _AUDIO_CAPTURE_SECONDS * (_PARAKEET_SAMPLE_RATE // _PARAKEET_HOP_LENGTH)
53
+
54
+
50
55
  def audio_bucket_frames(max_frames: int) -> list[int]:
51
56
  if max_frames <= 0:
52
57
  return []
@@ -132,6 +132,7 @@ FP16_ONLY_OUTPUT_OPS = {
132
132
  "lfm2_moe_layer_gated",
133
133
  "qwen2_moe_layer_gated",
134
134
  "gemma4_moe_layer_gated",
135
+ "rel_pos_attention",
135
136
  "rope",
136
137
  }
137
138
 
@@ -1860,6 +1860,23 @@ def import_gemma4_moe_layer_gated(ir, node, ctx, *, shape, dtype, torch_op):
1860
1860
  op="gemma4_moe_layer_gated", has_expert_bias=False, activation="gelu")
1861
1861
 
1862
1862
 
1863
+ def import_rel_pos_attention(ir, node, ctx, *, shape, dtype, torch_op):
1864
+ if len(node.args) != 8:
1865
+ ctx.fail(f"rel_pos_attention expected 8 args, got {len(node.args)}")
1866
+ ir_node = IRNode(
1867
+ id=node_id(node),
1868
+ op="rel_pos_attention",
1869
+ inputs=[value_id(node.args[i], ctx) for i in range(6)],
1870
+ outputs=[value_id(node, ctx)],
1871
+ attrs={
1872
+ "scale": float(extract_literals(node.args[6])),
1873
+ "window_size": int(extract_literals(node.args[7])),
1874
+ },
1875
+ meta=_base_meta(shape, dtype, torch_op, node),
1876
+ )
1877
+ register_node(ir, ir_node, shape=shape, dtype=dtype)
1878
+
1879
+
1863
1880
  OP_IMPORTERS = {
1864
1881
  "arange": import_arange,
1865
1882
  "identity": import_identity,
@@ -1957,4 +1974,5 @@ OP_IMPORTERS = {
1957
1974
  "lfm2_moe_layer_gated": import_lfm2_moe_layer_gated,
1958
1975
  "qwen2_moe_layer_gated": import_qwen2_moe_layer_gated,
1959
1976
  "gemma4_moe_layer_gated": import_gemma4_moe_layer_gated,
1977
+ "rel_pos_attention": import_rel_pos_attention,
1960
1978
  }
@@ -2218,6 +2218,26 @@ def _lower_ir_node(g: Graph, node: IRNode, env: dict[str, Any], ir: IRGraph) ->
2218
2218
  )
2219
2219
  return [g.rel_pos_bias(query, relative_key, float(node.attrs.get("scale", 1.0)))]
2220
2220
 
2221
+ if op == "rel_pos_attention":
2222
+ query, key, value, rel_query, relative_key, key_mask = (
2223
+ _ensure_fp16_tensor(g, _tensor(env, input_id)) for input_id in node.inputs
2224
+ )
2225
+ mask_shape = (int(query.shape[0]), int(query.shape[1]))
2226
+ if tuple(int(dim) for dim in key_mask.shape) != mask_shape:
2227
+ key_mask = g.reshape(key_mask, mask_shape)
2228
+ return [
2229
+ g.rel_pos_attention(
2230
+ query,
2231
+ key,
2232
+ value,
2233
+ rel_query,
2234
+ relative_key,
2235
+ key_mask,
2236
+ float(node.attrs["scale"]),
2237
+ int(node.attrs.get("window_size", 0)),
2238
+ )
2239
+ ]
2240
+
2221
2241
  if op in {"gated_deltanet_prefill", "gated_deltanet_decode"}:
2222
2242
  x = _tensor(env, node.inputs[0])
2223
2243
  qkv_weight = _tensor(env, node.inputs[1])
@@ -147,6 +147,7 @@ OPS: tuple[OpSchema, ...] = (
147
147
  OpSchema("rms_norm", num_inputs=2, attrs=("eps",), backend_op="rms_norm"),
148
148
  OpSchema("rope", num_inputs=1, attrs=("theta", "position_offset"), backend_op="rope"),
149
149
  OpSchema("rel_pos_bias", num_inputs=2, attrs=("scale",), backend_op="rel_pos_bias"),
150
+ OpSchema("rel_pos_attention", num_inputs=6, attrs=("scale", "window_size"), backend_op="rel_pos_attention"),
150
151
  OpSchema(
151
152
  "attention",
152
153
  num_inputs=3,
@@ -14,11 +14,63 @@ import torch.nn.functional as F
14
14
 
15
15
  from cactus.transpile.component_pipeline import ComponentModuleSpec
16
16
  from cactus.transpile.audio_preprocess import audio_bucket_frames
17
+ from cactus.transpile.audio_preprocess import audio_capture_frames
17
18
  from cactus.transpile.audio_preprocess import prepare_native_parakeet_audio_features
18
19
  from cactus.transpile.model_profiles import add_tensor_aliases
19
20
  from cactus.transpile.model_profiles import PARAKEET_TDT_PROFILE
20
21
 
21
22
 
23
+ _FULL_ATTENTION_MAX_ENCODER_FRAMES = 688
24
+ _LOCAL_ATTENTION_WINDOW = 256
25
+ _REL_POS_ATTENTION_QUERY_BLOCK = 256
26
+ _PRE_ENCODE_CHUNK_FRAMES = 3000
27
+ _PRE_ENCODE_HALO_FRAMES = 16
28
+
29
+
30
+ @torch.library.custom_op("cactus_transpile::rel_pos_attention", mutates_args=())
31
+ def rel_pos_attention(
32
+ query: torch.Tensor,
33
+ key: torch.Tensor,
34
+ value: torch.Tensor,
35
+ rel_query: torch.Tensor,
36
+ relative_key: torch.Tensor,
37
+ key_mask: torch.Tensor,
38
+ scale: float,
39
+ window: int,
40
+ ) -> torch.Tensor:
41
+ seq_len = int(query.shape[1])
42
+ rel_len = int(relative_key.shape[1])
43
+ center = (rel_len - 1) // 2
44
+ reach = seq_len - 1 if window <= 0 or window >= seq_len else window
45
+ q = query.permute(0, 2, 1, 3).float()
46
+ k = key.permute(0, 2, 1, 3).float()
47
+ v = value.permute(0, 2, 1, 3).float()
48
+ q_rel = rel_query.permute(0, 2, 1, 3).float()
49
+ r = relative_key.permute(0, 2, 1, 3).float()
50
+ mask = key_mask.float()
51
+ out = torch.empty_like(q)
52
+ for t0 in range(0, seq_len, _REL_POS_ATTENTION_QUERY_BLOCK):
53
+ t1 = min(seq_len, t0 + _REL_POS_ATTENTION_QUERY_BLOCK)
54
+ k0 = max(0, t0 - reach)
55
+ k1 = min(seq_len, t1 + reach)
56
+ offset = (
57
+ torch.arange(k0, k1, device=query.device).view(1, -1)
58
+ - torch.arange(t0, t1, device=query.device).view(-1, 1)
59
+ )
60
+ rel_index = (center + offset).clamp(0, rel_len - 1).expand(q.shape[0], q.shape[1], -1, -1)
61
+ content = q[:, :, t0:t1] @ k[:, :, k0:k1].transpose(-1, -2)
62
+ position = (q_rel[:, :, t0:t1] @ r.transpose(-1, -2)).gather(-1, rel_index)
63
+ scores = (content + position) * scale + mask[:, None, None, k0:k1]
64
+ scores = scores.masked_fill(offset.abs() > reach, float("-inf"))
65
+ out[:, :, t0:t1] = scores.softmax(dim=-1) @ v[:, :, k0:k1]
66
+ return out.permute(0, 2, 1, 3).to(query.dtype).contiguous()
67
+
68
+
69
+ @rel_pos_attention.register_fake
70
+ def _rel_pos_attention_fake(query, key, value, rel_query, relative_key, key_mask, scale, window):
71
+ return torch.empty_like(query)
72
+
73
+
22
74
  def _cfg_get(config: dict[str, Any], key: str, default: Any = None) -> Any:
23
75
  value = config.get(key, default)
24
76
  return default if value is None else value
@@ -403,19 +455,6 @@ def _relative_position_embeddings(*, seq_len: int, hidden_dim: int, device: torc
403
455
  return embeddings.to(dtype=dtype)
404
456
 
405
457
 
406
- def _relative_position_bias(query: torch.Tensor, relative_key: torch.Tensor, *, scale: float) -> torch.Tensor:
407
- batch, heads, seq_len, _ = query.shape
408
- scores = torch.matmul(query, relative_key.transpose(-1, -2))
409
- rel_index = (
410
- torch.arange(seq_len, device=query.device).view(seq_len, 1)
411
- - torch.arange(seq_len, device=query.device).view(1, seq_len)
412
- + (seq_len - 1)
413
- )
414
- rel_index = rel_index.view(1, 1, seq_len, seq_len).expand(batch, heads, seq_len, seq_len)
415
- gathered = scores.gather(-1, rel_index)
416
- return gathered * float(scale)
417
-
418
-
419
458
  class ParakeetTDTFeedForward(nn.Module):
420
459
  def __init__(self, config: ParakeetTDTConfig, prefix: str, state_dict: dict[str, torch.Tensor]):
421
460
  super().__init__()
@@ -457,7 +496,7 @@ class ParakeetTDTSelfAttention(nn.Module):
457
496
  self.pos_bias_v = nn.Parameter(state_dict[f"{prefix}.pos_bias_v"].clone())
458
497
  self.config = config
459
498
 
460
- def forward(self, x: torch.Tensor, attn_mask: torch.Tensor) -> torch.Tensor:
499
+ def forward(self, x: torch.Tensor, attn_mask: torch.Tensor, window: int = 0) -> torch.Tensor:
461
500
  batch, seq_len, _ = x.shape
462
501
  num_heads = self.config.attention_heads
463
502
  head_dim = self.config.attention_head_dim
@@ -466,39 +505,29 @@ class ParakeetTDTSelfAttention(nn.Module):
466
505
  k = self.linear_k(x).view(batch, seq_len, num_heads, head_dim)
467
506
  v = self.linear_v(x).view(batch, seq_len, num_heads, head_dim)
468
507
 
508
+ reach = window if window > 0 else seq_len - 1
469
509
  rel_pos = _relative_position_embeddings(
470
- seq_len=seq_len,
510
+ seq_len=reach + 1,
471
511
  hidden_dim=self.config.hidden_dim,
472
512
  device=x.device,
473
513
  dtype=x.dtype,
474
514
  )
475
- rel_k = self.linear_pos(rel_pos).view(1, 2 * seq_len - 1, num_heads, head_dim)
515
+ rel_k = self.linear_pos(rel_pos).view(1, 2 * reach + 1, num_heads, head_dim)
476
516
 
477
517
  q_u = q + self.pos_bias_u.view(1, 1, num_heads, head_dim).to(dtype=x.dtype, device=x.device)
478
518
  q_v = q + self.pos_bias_v.view(1, 1, num_heads, head_dim).to(dtype=x.dtype, device=x.device)
479
519
 
480
- q_u_heads = q_u.permute(0, 2, 1, 3)
481
- q_v_heads = q_v.permute(0, 2, 1, 3)
482
- k_heads = k.permute(0, 2, 1, 3)
483
- v_heads = v.permute(0, 2, 1, 3)
484
- rel_k_heads = rel_k.permute(0, 2, 1, 3)
485
-
486
- rel_bias = _relative_position_bias(
487
- q_v_heads,
488
- rel_k_heads,
489
- scale=self.config.attention_scale,
490
- )
491
- rel_bias = rel_bias + attn_mask
492
- attn = F.scaled_dot_product_attention(
493
- q_u_heads,
494
- k_heads,
495
- v_heads,
496
- attn_mask=rel_bias,
497
- dropout_p=0.0,
498
- is_causal=False,
520
+ attn = rel_pos_attention(
521
+ q_u,
522
+ k,
523
+ v,
524
+ q_v,
525
+ rel_k,
526
+ attn_mask.reshape(batch, seq_len),
527
+ self.config.attention_scale,
528
+ window,
499
529
  )
500
- attn = attn.permute(0, 2, 1, 3).reshape(batch, seq_len, self.config.hidden_dim)
501
- return self.linear_out(attn)
530
+ return self.linear_out(attn.reshape(batch, seq_len, self.config.hidden_dim))
502
531
 
503
532
 
504
533
  class ParakeetTDTConformerConv(nn.Module):
@@ -583,9 +612,9 @@ class ParakeetTDTEncoderLayer(nn.Module):
583
612
  module.bias.data.copy_(state_dict[f"{prefix}.{name}.bias"].to(dtype=module.bias.dtype))
584
613
  self.config = config
585
614
 
586
- def forward(self, x: torch.Tensor, conv_mask: torch.Tensor, attn_mask: torch.Tensor) -> torch.Tensor:
615
+ def forward(self, x: torch.Tensor, conv_mask: torch.Tensor, attn_mask: torch.Tensor, window: int = 0) -> torch.Tensor:
587
616
  x = x + 0.5 * self.feed_forward1(self.norm_feed_forward1(x), activation=self.config.encoder_hidden_act)
588
- x = x + self.self_attn(self.norm_self_att(x), attn_mask)
617
+ x = x + self.self_attn(self.norm_self_att(x), attn_mask, window)
589
618
  x = x + self.conv(self.norm_conv(x), conv_mask)
590
619
  x = x + 0.5 * self.feed_forward2(self.norm_feed_forward2(x), activation=self.config.encoder_hidden_act)
591
620
  return self.norm_out(x)
@@ -624,6 +653,19 @@ class ParakeetTDTPreEncode(nn.Module):
624
653
  def forward(self, input_features: torch.Tensor) -> torch.Tensor:
625
654
  if input_features.ndim != 3:
626
655
  raise ValueError(f"expected input_features [batch, frames, mels], got {tuple(input_features.shape)}")
656
+ frames = int(input_features.shape[1])
657
+ if frames <= _PRE_ENCODE_CHUNK_FRAMES:
658
+ return self._subsample(input_features)
659
+ chunks = []
660
+ for start in range(0, frames, _PRE_ENCODE_CHUNK_FRAMES):
661
+ end = min(frames, start + _PRE_ENCODE_CHUNK_FRAMES)
662
+ low = max(0, start - _PRE_ENCODE_HALO_FRAMES)
663
+ out = self._subsample(input_features[:, low : min(frames, end + _PRE_ENCODE_HALO_FRAMES)])
664
+ first = (start - low) // 8
665
+ chunks.append(out[:, first : first + (end - start + 7) // 8])
666
+ return torch.cat(chunks, dim=1)
667
+
668
+ def _subsample(self, input_features: torch.Tensor) -> torch.Tensor:
627
669
  x = input_features.unsqueeze(1)
628
670
  x = F.relu(self.conv[0](x))
629
671
  x = F.relu(self.conv[3](self.conv[2](x)))
@@ -646,8 +688,9 @@ class ParakeetTDTEncoder(nn.Module):
646
688
  pad_mask = input_features_mask[:, :: self.config.subsampling_factor]
647
689
  conv_mask = pad_mask.unsqueeze(1)
648
690
  attn_mask = ((pad_mask - 1.0) * 10000.0).unsqueeze(1).unsqueeze(1)
691
+ window = 0 if int(x.shape[1]) <= _FULL_ATTENTION_MAX_ENCODER_FRAMES else _LOCAL_ATTENTION_WINDOW
649
692
  for layer in self.layers:
650
- x = layer(x, conv_mask, attn_mask)
693
+ x = layer(x, conv_mask, attn_mask, window)
651
694
  return x
652
695
 
653
696
 
@@ -850,11 +893,12 @@ def build_parakeet_tdt_component_specs(
850
893
  ) -> list[ComponentModuleSpec]:
851
894
  features = named_tensors["input_features"]
852
895
  input_features = torch.randn(
853
- (int(features.shape[0]), 3000, int(features.shape[2])),
896
+ (int(features.shape[0]), audio_capture_frames(), int(features.shape[2])),
854
897
  device=features.device,
855
898
  dtype=features.dtype,
856
899
  )
857
- example_hidden = model.encoder(input_features, torch.ones_like(input_features[:, :, 0]))
900
+ example_features = input_features[:, : audio_bucket_frames(int(input_features.shape[1]))[0], :]
901
+ example_hidden = model.encoder(example_features, torch.ones_like(example_features[:, :, 0]))
858
902
  batch_size = int(example_hidden.shape[0])
859
903
  initial_states = model.initial_decoder_state(
860
904
  batch_size=batch_size,
@@ -95,6 +95,7 @@ PROCESSOR_MAP = {
95
95
  "google/gemma-4-E2B": gemma4_processor,
96
96
  "openai/whisper-tiny": default_processor,
97
97
  "nvidia/parakeet-tdt-0.6b-v3": parakeet_processor,
98
+ "nvidia/parakeet-tdt-0.6b-v2": parakeet_processor,
98
99
  "LiquidAI/LFM2-VL-3B": default_processor,
99
100
  "Qwen/Qwen2.5-0.5B": default_processor,
100
101
  "LiquidAI/LFM2.5-8B-A1B": text_tokenizer_processor,
@@ -586,6 +586,7 @@ MODEL_ID_MAP = {
586
586
  "openai/whisper-tiny": WHISPER_PROFILE,
587
587
  "openai/whisper-small": WHISPER_PROFILE,
588
588
  "nvidia/parakeet-tdt-0.6b-v3": PARAKEET_PROFILE,
589
+ "nvidia/parakeet-tdt-0.6b-v2": PARAKEET_PROFILE,
589
590
  "LiquidAI/LFM2-VL-450M": LFM_VLM_PROFILE,
590
591
  "LiquidAI/LFM2-VL-3B": LFM_VLM_PROFILE,
591
592
  "Qwen/Qwen2.5-0.5B": QWEN2_5_0_5B_PROFILE,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cactus-compute
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: On-device AI inference — LLM, vision, speech, embeddings, and RAG
5
5
  Author: Cactus Compute
6
6
  License: Cactus Compute License
@@ -63,6 +63,7 @@ cactus/convert/tests/test_hessian.py
63
63
  cactus/convert/tests/test_lfm2_adapter.py
64
64
  cactus/convert/tests/test_naming_qdq.py
65
65
  cactus/convert/tests/test_parakeet_capture.py
66
+ cactus/convert/tests/test_parakeet_local_attention.py
66
67
  cactus/convert/tests/test_policy.py
67
68
  cactus/convert/tests/test_qwen_adapter.py
68
69
  cactus/convert/tests/test_reports.py
@@ -1 +0,0 @@
1
- __version__ = "2.2.0"
File without changes