ml-explore / ml-explore/mlx-examples
Poor Speculative Decoding Performance on M2 Ultra
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 9k
- Forks
- 1.2k
- PR merge metrics
- No merged PRs in 30d
Description
Speculative decoding does not seem to improve generation speed as expected on M2 Ultra Mac Studio, 128GB.
Main model: https://huggingface.co/lmstudio-community/Qwen2.5-Coder-32B-Instruct-MLX-4bit
Draft model: https://huggingface.co/lmstudio-community/Qwen2.5-Coder-0.5B-Instruct-MLX-4bit or https://huggingface.co/mlx-community/Qwen2.5-0.5B-Instruct-4bit
Prompt: "Write a quicksort algorithm"
Without spec decoding: 29.803 tokens-per-sec
With spec decoding: 29.051 tokens-per-sec
Qwen2.5-Coder-0.5B-Instruct-MLX-4Bit alone: 284.647 tokens-per-sec
In the same situation on an M3 Pro, 32GB of ram, we see tremendous speedup (~7tok/sec -> ~16tok/sec)
Full logs:
Click to expand
(venv) ➜ test mlx_lm.generate --model lmstudio-community/Qwen2.5-Coder-32B-Instruct-MLX-4bit --prompt "Write a quicksort algorithm" --draft-model mlx-community/Qwen2.5-0.5B-Instruct-4bit -m 1000 --temp 0
==========
Certainly! Quicksort is a popular and efficient sorting algorithm that uses a divide-and-conquer approach to sort elements. Below is a simple implementation of the Quicksort algorithm in Python:
def quicksort(arr):
if len(arr) <= 1:
return arr
else:
pivot = arr[len(arr) // 2] # Choose the middle element as the pivot
left = [x for x in arr if x < pivot] # Elements less than the pivot
middle = [x for x in arr if x == pivot] # Elements equal to the pivot
right = [x for x in arr if x > pivot] # Elements greater than the pivot
return quicksort(left) + middle + quicksort(right)
# Example usage:
arr = [3, 6, 8, 10, 1, 2, 1]
sorted_arr = quicksort(arr)
print("Sorted array:", sorted_arr)
Explanation:
- Base Case: If the array has 0 or 1 element, it is already sorted, so we return it as is.
- Pivot Selection: We choose the middle element of the array as the pivot.
- Partitioning: We create three lists:
leftfor elements less than the pivot.middlefor elements equal to the pivot.rightfor elements greater than the pivot.
- Recursive Sorting: We recursively apply the
quicksortfunction to theleftandrightlists and concatenate the results with themiddlelist.
This implementation is simple and easy to understand, but it may not be the most efficient in terms of space complexity due to the use of additional lists. For an in-place version, you can modify the algorithm to swap elements within the original array. Here's an in-place version:
def quicksort_inplace(arr, low, high):
if low < high:
pi = partition(arr, low, high) # Partitioning index
quicksort_inplace(arr, low, pi - 1) # Sort left part
quicksort_inplace(arr, pi + 1, high) # Sort right part
def partition(arr, low, high):
pivot = arr[high] # Choose the last element as the pivot
i = low - 1 # Index of smaller element
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i] # Swap
arr[i + 1], arr[high] = arr[high], arr[i + 1] # Swap pivot element
return i + 1
# Example usage:
arr = [3, 6, 8, 10, 1, 2, 1]
quicksort_inplace(arr, 0, len(arr) - 1)
print("Sorted array:", arr)
In this in-place version, the partition function rearranges the elements in the array such that elements less than the pivot are on the left, elements greater than the pivot are on the right, and the pivot is in its correct position. The quicksort_inplace function then recursively sorts the subarrays.
==========
Prompt: 34 tokens, 71.386 tokens-per-sec
Generation: 709 tokens, 29.051 tokens-per-sec
Peak memory: 18.932 GB
(venv) ➜ test mlx_lm.generate --model lmstudio-community/Qwen2.5-Coder-32B-Instruct-MLX-4bit --prompt "Write a quicksort algorithm" -m 1000 --temp 0
==========
Certainly! Quicksort is a popular and efficient sorting algorithm that uses a divide-and-conquer approach to sort elements. Below is a simple implementation of the Quicksort algorithm in Python:
def quicksort(arr):
if len(arr) <= 1:
return arr
else:
pivot = arr[len(arr) // 2] # Choose the middle element as the pivot
left = [x for x in arr if x < pivot] # Elements less than the pivot
middle = [x for x in arr if x == pivot] # Elements equal to the pivot
right = [x for x in arr if x > pivot] # Elements greater than the pivot
return quicksort(left) + middle + quicksort(right)
# Example usage:
arr = [3, 6, 8, 10, 1, 2, 1]
sorted_arr = quicksort(arr)
print("Sorted array:", sorted_arr)
Explanation:
- Base Case: If the array has 0 or 1 element, it is already sorted, so we return it as is.
- Pivot Selection: We choose the middle element of the array as the pivot.
- Partitioning: We create three lists:
leftfor elements less than the pivot.middlefor elements equal to the pivot.rightfor elements greater than the pivot.
- Recursive Sorting: We recursively apply the
quicksortfunction to theleftandrightlists and concatenate the results with themiddlelist.
This implementation is simple and easy to understand, but it may not be the most efficient in terms of space complexity due to the use of additional lists. For an in-place version, you can modify the algorithm to swap elements within the original array. Here's an in-place version:
def quicksort_inplace(arr, low, high):
if low < high:
pi = partition(arr, low, high) # Partitioning index
quicksort_inplace(arr, low, pi - 1) # Sort left part
quicksort_inplace(arr, pi + 1, high) # Sort right part
def partition(arr, low, high):
pivot = arr[high] # Choose the last element as the pivot
i = low - 1 # Index of smaller element
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i] # Swap
arr[i + 1], arr[high] = arr[high], arr[i + 1] # Swap pivot element
return i + 1
# Example usage:
arr = [3, 6, 8, 10, 1, 2, 1]
quicksort_inplace(arr, 0, len(arr) - 1)
print("Sorted array:", arr)
In this in-place version, the partition function rearranges the elements in the array such that elements less than the pivot are on the left, elements greater than the pivot are on the right, and the pivot is in its correct position. The quicksort_inplace function then recursively sorts the subarrays.
==========
Prompt: 34 tokens, 75.790 tokens-per-sec
Generation: 709 tokens, 29.803 tokens-per-sec
Peak memory: 18.643 GB
(venv) ➜ test mlx_lm.generate --model lmstudio-community/Qwen2.5-Coder-0.5B-Instruct-MLX-4bit --prompt "Write a quicksort algorithm" -m 1000 --temp 0
==========
Sure, here's a simple implementation of the quicksort algorithm in Python:
def quicksort(arr):
# Base case: if the array is empty or has one element, it's already sorted
if len(arr) <= 1:
return arr
# Choose a pivot element
pivot = arr[len(arr) // 2]
# Partition the array into two sub-arrays: elements less than or equal to the pivot and elements greater than or equal to the pivot
less_than_pivot = [x for x in arr if x <= pivot]
greater_than_pivot = [x for x in arr if x > pivot]
# Recursively sort the two sub-arrays
quicksort(less_than_pivot)
quicksort(greater_than_pivot)
# Merge the sorted sub-arrays
return less_than_pivot + [pivot] + greater_than_pivot
This function takes an array as input and returns a new array sorted in ascending order. It uses a simple partitioning strategy: it selects a pivot element and partitions the array into two sub-arrays: all elements less than or equal to the pivot and all elements greater than or equal to the pivot. The function then recursively sorts the two sub-arrays and merges them to form the sorted array.
==========
Prompt: 34 tokens, 683.032 tokens-per-sec
Generation: 276 tokens, 284.647 tokens-per-sec
Peak memory: 0.299 GB
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the mlx_lm.generate commands and the supplied M2 Ultra logs, reproducing both speculative-decoding and baseline runs with the listed models. Compare the results against the M3 Pro behavior; done means the hardware-specific performance discrepancy is explained and an appropriate correction or documented limitation is established.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- machine-learning, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100