ishti.dev
Vancouver

Solo founder · Vancouver

Ishtiaque
Hossain

Maker of fine Porcine Software.

Four live products (the newest, Shlexball, runs its own model on your Mac), 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
9 Upstream patches
4 Cities, 3 countries

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.

00

Shlexball

macOS app · local LLM file search

Describe a file in plain language and Shlexball finds it. A fine-tuned 1.5B model runs on the Mac itself and searches file names, dates, and the text inside PDFs, documents and spreadsheets. Read-only: it never moves, edits or deletes anything.

macOS On-device LLM Fine-tuning

01

tab namer 77M

Local title model · open weights

Names PorkiCoder terminal tabs in one to three words. Fine-tuned from Google FLAN-T5-small after tiny 12.7M and 35M models failed to keep English coherent. Average 7.28 versus Google 3.12 on 500 held-out tasks.

Hugging Face On-device Seq2seq

02

porkicoder

Agentic coding assistant

A coding agent that does the work. Reads code, edits files, runs commands, spawns sub-agents in parallel. For developers who want a collaborator, not another autocomplete.

Artificial Intelligence Developer Tools Software Engineering

03

resumehog

Resume optimization platform

Tailors a resume to a specific job in minutes by matching what hiring teams actually screen for, instead of stuffing keywords and hoping.

Career Tech Web Application Product Design

04

Hogmatix

Social scheduling · back in active development

Schedule posts for X, TikTok and YouTube from one place. Connect your accounts, add your content, and choose when it goes live. Works in the browser and through PorkiCoder.

Social Media Scheduling Agent Tools

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

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 open, 1 closed · verified Sep 26, 2026

python/mlx/optimizers/optimizers.py open

MultiOptimizer crashed on empty parameter groups

MultiOptimizer rebuilt each optimizer's parameter subset with tree_unflatten, which returns a list for an empty subset where a dict is expected downstream, so an optimizer whose filters matched no parameters raised an IndexError. Empty subsets now produce an empty dict, covered by a test that steps an optimizer holding no parameters in both the first and fallback slots.

@@ MultiOptimizer._split_dictionary @@
-        return [tree_unflatten(p) for p in parts]
+        # tree_unflatten([]) returns a list so use a dict for empty parts
+        return [tree_unflatten(p) if p else {} for p in parts]
+29 −1 2 files Sep 26, 2026 PR #4570 ↗
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
Nine upstream PRs so far across normalization, losses, and optimizers, each pinned down with tests against PyTorch and NumPy.
See the patches ↘
Maintaining resumehog
Live, paid, and profitable. Used by job seekers every day and still improving each release.
resumehog ↗
Building Hogmatix again
Back in active development. Social scheduling for X, TikTok and YouTube, from the browser and through PorkiCoder.
Hogmatix ↗

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.