Skip to content
View erwinzhang7's full-sized avatar

Block or report erwinzhang7

Block user

Prevent this user from interacting with your repositories and sending you notifications. Learn more about blocking users.

You must be logged in to block users.

Content in all repositories owned by your account will be closed.
Maximum 250 characters. Please don’t include any personal information such as legal names or email addresses. Markdown is supported. This note will only be visible to you.
Report abuse

Contact GitHub support about this user’s behavior. Learn more about reporting abuse.

Report abuse
erwinzhang7/README.md

Erwin Zhang

ML systems & performance engineering · CS @ University of Toronto, 2024-2027

Interested in ML systems, inference runtimes, heterogeneous compute, performance engineering, and Apple Silicon.

Selected work

MLX multi-node distributed. Eight merged fixes across both backends. In ring, a hang when a peer disconnects: a closed socket returns 0 from recv() without touching errno, so the failure was tested against a stale value and skipped. And a single transfer of 2 GiB or more failing outright, because send(2) rejects a length above INT_MAX while ring handed the syscall the whole remaining task size; the measured boundary is exactly INT_MAX.

In JACCL, an out-of-bounds read that reached ibv_reg_mr as a protection domain, an uninitialized GID handed to the queue pair whose failure surfaced much later and somewhere else, and an errno dropped at the one point it was known. Also a configured JACCL never reached because Open MPI returns a size-1 world outside mpirun and backend="any" accepts it, a hostfile whose rank order leaves every rank alone in a group of size one while passing every check that existed, and a version string compared numerically in the JACCL build gate, where an SDK reporting 26.10 reads as 26.1 and silently disables the backend.

A common thread: most of these fail without saying anything. A hang, a fallback, a group of size one that reports success.

Not all of it landed. sum_scatter and split were both declined on ring, and both gaps are still in the tree: RingGroup::sum_scatter throws, so nn.fully_shard cannot run on ring at all, and mlx.launch uses ring locally. CONTRIBUTIONS.md has everything, including what was declined and why.

mx.searchsorted — a new public MLX operation, added as a native primitive with hand-written CPU, Metal and CUDA kernels. The binary search runs through the same comparator sort uses, so ordering, including where NaNs land, stays consistent across all three backends.

mlx-lm server — a non-streaming chat completion came back with no content key at all when the model stopped while still inside a reasoning block, so a client reading message["content"] got a KeyError rather than an empty answer.

DuckDB hash join — a regression where dictionary emission changed a chain field from a pointer to an index, breaking the existing dead-end sentinel.

coreml-compute-placement — measuring where Core ML actually places operations on Apple Silicon, and what each choice costs. The Neural Engine is faster than the GPU on M4 Pro and 4.7x slower on M5 Max; the default ComputeUnit.ALL is sometimes the slowest option available. A second axis measures how much of the memory bus each chip's CPU can reach: 91% of peak on M4 Pro against 49% on M5 Max, so the same offload strategy is worth very different amounts on the two chips. Includes a cost-weighted ANE residency verifier with a CI gate.

Full list, including work in flight and issue investigations: CONTRIBUTIONS.md

Test hardware

Available for reproduction and benchmarking:

  • M5 Max — 128 GB unified memory
  • 2x M4 Pro Mac mini — 64 GB
  • 2x M4 Pro Mac mini — 48 GB
  • Multi-node Apple Silicon over Thunderbolt 5 with RDMA

Built up as chain, then ring, then full mesh, measuring at each stage: 4.23 -> 16.70 -> 21.48 GB/s all-reduce bus bandwidth, 5.1x end to end. Most of the distributed work above came out of running that rig rather than out of reading the source, which is why the bugs are mostly hangs, silent fallbacks and failures that surface somewhere other than where they happen.

I am particularly interested in issues that need recent Apple Silicon, large unified-memory configurations, or multiple Macs to reproduce. Several of the JACCL investigations in CONTRIBUTIONS.md had been open for months without a reproduction, for exactly that reason.

Current interests

Apple Silicon ML runtimes · Metal / GPU compute · Core ML and ANE execution · quantized inference · multi-node inference · compiler and runtime behavior · performance and correctness bugs that only appear at scale

Elsewhere

Earlier academic / course projects: @Erwinnnnnnn

Pinned Loading

  1. coreml-compute-placement coreml-compute-placement Public

    Where Core ML actually places your ops on Apple silicon, and what each choice costs. The ANE beats the GPU on M4 Pro and loses 4.7x on M5 Max; the default is sometimes the slowest option.

    Python

  2. mlx mlx Public

    Forked from ml-explore/mlx

    MLX: An array framework for Apple silicon

    C++