ishti.dev
Vancouver

Solo founder · Vancouver

Ishtiaque
Hossain

Maker of fine Porcine Software.

Four live products, a small open-weights model, and patches upstream to Apple's MLX. Every detail, from interface to infrastructure, handled by one person.

4 Products live
100% Bootstrapped
8 Upstream patches
4 Cities, 3 countries

Background

I'm building porkicoder, a coding agent that reads code, edits files, and runs commands on its own. The goal is a real engineering collaborator, not another autocomplete.

Before that I shipped resumehog, which helps job seekers tailor a resume to a specific role in minutes by matching what hiring teams actually screen for.

I work alone. Design, code, infra, support. Every part of the product runs through me. It keeps decisions fast and the product opinionated.

Born in Bangladesh, studied in Kuala Lumpur, a few years in Toronto, now in Vancouver.

What I work on

The stack I build with, the problems I keep coming back to, and the road that got me here.

I build with

  • Full-stack web
  • LLM agents & tooling
  • Model fine-tuning
  • Product design
  • Backend systems

Focus areas

  • Developer tools
  • Coding agents
  • On-device models
  • Career & hiring tech
  • Solo SaaS

Lived in

  • Vancouver, Canada
  • Toronto, Canada
  • Kuala Lumpur, Malaysia
  • Dhaka, Bangladesh

Projects

Four products, plus a local model with open weights. Each one designed, built, and shipped alone. Click a card for the story behind it.

Upstream

Patches sent to MLX, Apple's machine-learning framework for Apple silicon. Each card types out the actual hunk from the PR and links back to the upstream change.

GitHub · 7 merged, 1 closed · verified Aug 10, 2026

python/mlx/nn/layers/normalization.py merged

BatchNorm tracked biased running variance

Training correctly normalized with the biased batch variance, but that same estimate was also stored in running_var, making evaluation diverge from PyTorch. Running stats now use the unbiased estimate while training behavior stays unchanged, with coverage across 2D, 3D, and 4D inputs.

@@ BatchNorm.__call__ @@
         mean, var = self._calc_stats(x)
         if self.training and self.track_running_stats:
             mu = self.momentum
+            _, running_var = self._calc_stats(x, ddof=1)
             self.running_mean = (1 - mu) * self.running_mean + mu * mean
-            self.running_var = (1 - mu) * self.running_var + mu * var
+            self.running_var = (1 - mu) * self.running_var + mu * running_var
+113 −37 2 files Jul 8, 2026 PR #3817 ↗
python/mlx/nn/layers/upsample.py merged

Upsample divided by zero for singleton outputs

Upsample with align_corners=True computed its scale with M - 1, so an output spatial dimension of 1 raised a ZeroDivisionError. Clamping that denominator maps singleton outputs to the first input coordinate without changing larger outputs.

@@ _scaled_indices(N, scale, align_corners, dim, ndims) @@
     M = int(scale * N)
     if align_corners:
-        indices = mx.arange(M, dtype=mx.float32) * ((N - 1) / (M - 1))
+        indices = mx.arange(M, dtype=mx.float32) * ((N - 1) / max(M - 1, 1))
+32 −1 2 files Jun 26, 2026 PR #3769 ↗
python/mlx/nn/layers/linear.py merged

Bilinear docs described the wrong tensor order

The Bilinear formula and weight shape listed its two input dimensions in the opposite order from the implementation. The patch corrected both, along with the example output for margin_ranking_loss under its default reduction.

@@ class Bilinear(Module) @@
-        y_i = x_1^\top W_i x_2 + b_i
+        y_i = x_2^\top W_i x_1 + b_i
-    W has shape [output_dims, input1_dims, input2_dims]
+    W has shape [output_dims, input2_dims, input1_dims]
+3 −3 2 files Jun 24, 2026 PR #3758 ↗
python/mlx/nn/init.py closed

Sparse initializer zeroed the wrong axis

nn.init.sparse documented a fixed fraction of zeros in every column, but sampled and assigned them row by row. This proposed column-oriented mask matched the documented behavior and PyTorch; the PR was closed without merge.

@@ sparse(sparsity, mean=0.0, std=0.01) @@
-        rows, cols = a.shape
-        num_zeros = int(math.ceil(sparsity * cols))
+        rows = a.shape[0]
+        num_zeros = int(math.ceil(sparsity * rows))
-        order = mx.argsort(mx.random.uniform(shape=a.shape), axis=1)
+        order = mx.argsort(mx.random.uniform(shape=a.shape), axis=0)
+        mask = mx.argsort(order, axis=0) < num_zeros
+24 −9 2 files Jun 19, 2026 PR #3725 ↗
python/mlx/nn/layers/convolution_transpose.py merged

ConvTranspose repr dropped kernel dimensions

ConvTranspose2d and ConvTranspose3d sliced too few axes when formatting kernel_size, so non-square and non-cubic layers printed incomplete representations. The corrected slices now include every spatial dimension.

@@ ConvTranspose2d._extra_repr @@
-            f"kernel_size={self.weight.shape[1:2]}, stride={self.stride}, "
+            f"kernel_size={self.weight.shape[1:3]}, stride={self.stride}, "
@@ ConvTranspose3d._extra_repr @@
-            f"kernel_size={self.weight.shape[1:3]}, stride={self.stride}, "
+            f"kernel_size={self.weight.shape[1:4]}, stride={self.stride}, "
+12 −2 2 files Jun 19, 2026 PR #3724 ↗
python/mlx/nn/losses.py merged

nll_loss ignored its axis argument

The gather ran along axis, but the expand and squeeze were hardcoded to the last dimension, so any non-default axis either crashed with a confusing squeeze error or silently returned wrong values when the trailing dim was 1. Applied the same fix cross_entropy got, pinned down with tests against NumPy.

@@ def nll_loss(inputs, targets, axis=-1, reduction="none") @@
-    loss = -mx.take_along_axis(inputs, targets[..., None], axis).squeeze(-1)
+    loss = -mx.take_along_axis(inputs, mx.expand_dims(targets, axis), axis).squeeze(
+        axis
+    )
+23 −1 2 files Jun 10, 2026 PR #3651 ↗
python/mlx/optimizers/optimizers.py merged

Adafactor crashed on conv-shaped parameters

The factored second-moment update recombined row and column factors with a strictly 2-D matmul, so any parameter with more than two dims (a conv weight, a stacked weight) blew up. Swapped in the broadcasting outer-product form the reference implementation uses, verified against Hugging Face's Adafactor to within 4e-9.

@@ def _approximate_exp_moving_avg(self, exp_avg_sq_row, exp_avg_sq_col) @@
         c_factor = mx.rsqrt(exp_avg_sq_col)
-        return mx.matmul(
-            mx.expand_dims(r_factor, axis=-1), mx.expand_dims(c_factor, axis=0)
-        )
+        return mx.expand_dims(r_factor, axis=-1) * mx.expand_dims(c_factor, axis=-2)
+22 −3 2 files Jun 10, 2026 PR #3652 ↗
python/mlx/nn/layers/normalization.py merged

Norm layers accepted garbage silently

InstanceNorm on an input with no spatial dims quietly returned all zeros. GroupNorm accepted a num_groups that doesn't divide dims, mixing channel and spatial positions into meaningless statistics. Both now raise a ValueError up front, matching PyTorch's behavior.

@@ InstanceNorm.__call__ @@
+        if x.ndim < 3:
+            raise ValueError(
+                f"InstanceNorm expects inputs with at least 3 dimensions"
+                f" (N, ..., C) but the input has {x.ndim} dimensions."
+            )
@@ GroupNorm.__init__ @@
+        if dims % num_groups != 0:
+            raise ValueError(
+                f"The number of features ({dims}) must be evenly divisible"
+                f" by the number of groups ({num_groups})."
+            )
+31 −0 2 files Jun 10, 2026 PR #3653 ↗

What I'm doing

Where the hours go this season, in order of how much of the week each one takes.

Shipping porkicoder
The active focus. Agent loop, parallel sub-agents, and the tab-namer model that ships with it, released as it lands.
porkicoder ↗
Sending patches to MLX
Eight upstream PRs so far across normalization, losses, and optimizers, each pinned down with tests against PyTorch and NumPy.
See the patches ↘
Running hogmatix
Queues posts, staggers them across X accounts, and surfaces what landed. Used daily on my own handles.
hogmatix ↗
Maintaining resumehog
Live, paid, and profitable. Used by job seekers every day and still improving each release.
resumehog ↗

Where I've lived

Vancouver, Canada
Home base. Where I build today.
Toronto, Canada
First years in Canada. Then I headed west.
Kuala Lumpur, Malaysia
Studied here. Stayed for a decade.
Dhaka, Bangladesh
Where it started.