Home Python NumPy Shapes Not Aligned: Fix matmul Errors
Beginner 5 min · September 23, 2026

NumPy Shapes Not Aligned: Fix matmul Errors

Shapes not aligned means matmul dims clash.

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
Production
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
Before you start⏱ 13 min
  • Basic NumPy arrays and reading .shape tuples
  • Running Python and interpreting ValueError messages
  • Matrix multiplication idea: rows times columns produce scores
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • 'Shapes not aligned' means matmul found mismatched inner dims — (n,k)@(k,m) requires the middle sizes to match, and yours don't.
  • Fix it now: print A.shape, B.shape before the @, then transpose or reshape so the inner pair agrees.
  • You'll stop mixing broadcasting (elementwise stretching) with matmul (sum-over-inner-dim) — * broadcasts while @ contracts, and swapping them causes most reports.
  • Reshape with -1 to infer one axis (X.reshape(-1, k)) and assert A.shape[1] == B.shape[0] before big multiplies so mismatches fail fast with context.
✦ Definition~90s read
What is NumPy Shapes Not Aligned Fix?

ValueError: shapes not aligned means a matmul (@) received operands whose inner dimensions disagree. The rule is (n,k) @ (k,m) -> (n,m): the left's columns must equal the right's rows, and the message quotes both shapes plus the clashing pair (64 vs 32 with dim numbers). Outer dims are bystanders — the fix always reconciles the two inner values against the pipeline's width contract.

It's like connecting train cars with mismatched couplers.

The mismatch usually traces to pipeline drift rather than arithmetic typos. Embedding upgrades change feature width while weight files lag a partial rollout; loaders save transposed orientations; batch axes wander into the contraction slot of 3D tensors.

Each produces the same error shape with a different correct patch: complete the rollout for stale widths, add one .T in the loader for orientation, transpose wandering batch axes for 3D cases. Blind reshaping or swapping @ for * silences the message while corrupting values.

The sibling confusion is broadcasting: elementwise stretches trailing dims where @ contracts the inner one. Scaling features uses , projecting to outputs uses @, and mixing the verbs accounts for a large share of reports. Harden with stage-wise .shape logs, inner-dim asserts carrying both shapes, 100-row CPU probes before GPU warmup, deploy pins verified by W.shape rather than version strings, and downstream freshness gates — width evolution stays routine, and splits page in seconds with evidence attached.

Plain-English First

It's like connecting train cars with mismatched couplers. Matrix multiplication joins each row of the left operand to each column of the right one, and the inner dimensions must match exactly — 4-wide plugs into 4-deep. Broadcasting is a different machine that stretches small shapes for side-by-side work, not joining. The error means a 4-coupler met a 3-coupler. Print .shape, align the inner pair, and the join succeeds.

Your model scores 10,000 rows fine, then dies with ValueError: shapes (128,64) and (32,10) not aligned: 64 (dim 1) != 32 (dim 0). The weights loaded. The features look right. But the inner dimensions — 64 versus 32 — refuse to join, and every retry crashes on the same @ line.

Matmul contracts over one shared axis: (n,k) @ (k,m) yields (n,m), and the two k values must be equal. Feature pipelines break this contract constantly — a dropped embedding column here, a transposed weight file there, a batch axis wandering into the wrong position. The error message states the mismatch plainly, yet developers stare at the outer dims (128 vs 10) instead of the inner pair the message names.

The deeper trap is reaching for the wrong fix: adding broadcasting where matmul belongs, or reshaping blindly until the error moves. You'll learn the inner-dim rule cold, the .shape prints that prove each operand, the broadcast-versus-matmul boundary, and reshape patterns that preserve data order. By the end, every @ in your pipeline will carry a shape assert that fails with context instead of a riddle.

The (n,k)@(k,m) Rule: Inner Dims Must Match Exactly

Matrix multiplication joins along one shared axis: the columns of the left operand (k) must equal the rows of the right (k), producing (n,m). A (4096,64) feature batch times a (64,10) weight matrix yields (4096,10) scores — 4,096 rows each mapped to 10 outputs through 64 shared features. Change either 64 without changing the other and the couplers mismatch: (4096,64) @ (32,10) raises because 64 != 32, exactly as the message states with dim numbers attached.

Read the message inside-out: 'shapes (128,64) and (32,10) not aligned: 64 (dim 1) != 32 (dim 0)' names both shapes, then pinpoints dim 1 of the left (64) against dim 0 of the right (32). Those two numbers are the entire diagnosis — the outer dims (128, 10) are fine and irrelevant. Developers who audit the batch size (128) or output count (10) chase ghosts; the fix always touches one of the two inner values.

Build the reflex: see the error, read the inner pair, ask which side violates the data contract. Features at 64 with weights at 32 means weights are stale (finish the rollout). Weights at 64 with features at 32 means the embedder lags (upgrade the stage). The message can't tell you which is stale — your pipeline's contract can, and the shape assert you'll add encodes it.

numpy_inner_rule.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

X = np.zeros((128, 64))
W = np.zeros((64, 10))
print("aligned:", (X @ W).shape)  # (128, 10)

stale = np.zeros((32, 10))
try:
    X @ stale
    print("unexpectedly joined")
except ValueError as exc:
    print("misaligned:", exc)
assert X.shape[1] == W.shape[0], (X.shape, W.shape)
print("gate: inner dims", X.shape[1], "==", W.shape[0])
📊 Production Insight
The 1:12 a.m. crash message named 64 vs 32 plainly, but two retries burned GPU warmup before anyone read the inner pair. A pre-multiply assert with both shapes would have failed in seconds on the 100-row probe instead of 12 minutes into batch one.
🎯 Key Takeaway
Matmul needs equal inner dims: (n,k)@(k,m). Read the message's dim pair first — it names the exact two numbers to reconcile against your data contract.

Every shape bug starts with the same two prints: the operands' .shape values and the contract they should satisfy. Log A.shape, B.shape, and the boolean A.shape[1] == B.shape[0] before the @ — in the function, not in a notebook afterward. The log line turns 'shapes not aligned' from a riddle into a record: (4096,64) vs (32,10) with the contract (64,10) stated beside it names the stale weights file without further digging.

Trace shapes through the pipeline, not just at the crash. Embedding output width, normalization's preserved width, batching's added leading axis, weight file's stored shape — print each stage's output shape on the probe batch and the drift's location appears. The incident's drift sat between the embedder (64) and the weight file (32); stage-wise prints would have shown the embedder agreement ending exactly at the loader.

Make the prints permanent, not debugging graffiti. A shape-logging line per pipeline stage costs microseconds on a 100-row probe and pays back on every future width change. When the next embedder upgrade lands, the probe log shows the new width propagating stage by stage — or stopping dead at the stale file, with its shape quoted.

Store the probe shapes alongside the run record so post-incident review compares expected versus actual widths without rerunning anything.

numpy_print_shape.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np

def scored(X, W):
    print(f"shapes: X{X.shape} W{W.shape} inner-ok={X.shape[1] == W.shape[0]}")
    assert X.shape[1] == W.shape[0], f"inner mismatch: {X.shape} vs {W.shape}"
    return X @ W

X = np.zeros((256, 64))
W = np.load("/tmp/W64.npy") if __import__("os").path.exists("/tmp/W64.npy") else np.zeros((64, 10))
print("scores:", scored(X, W).shape)
try:
    scored(X, np.zeros((32, 10)))
except AssertionError as exc:
    print("fast fail with context:", exc)
📊 Production Insight
No stage logged shapes, so the 64-vs-32 split hid until batch one crashed after GPU warmup. Stage-wise shape prints on the 100-row probe now show embedder width versus weight width in the first seconds of every run.
🎯 Key Takeaway
Log both .shape values plus the inner-dim boolean before every production @. Stage-wise prints locate drift; asserts with both shapes fail fast with context.

Broadcasting vs Matmul: Stretching Is Not Joining

Broadcasting (*) aligns trailing dimensions elementwise, stretching size-1 axes to match — a (4,3) frame plus a (3,) row vector adds the vector to every row. Matmul (@) contracts the shared inner axis with sums — (4,3) @ (3,2) yields (4,2) through 3-wide dot products. Same operands, different verbs: one scales and shifts, the other projects and mixes. Swapping them produces either 'shapes not aligned' (matmul given broadcast shapes) or silently wrong values (broadcast given matmul intent).

The confusion peaks on scaling ops: multiplying features by per-column weights is X w with w shaped (k,) — broadcast, no contraction. Projecting to outputs is X @ W with W shaped (k,m) — contraction to m columns. Code that writes X W for a (128,64)/(64,10) pair broadcasts nothing meaningful and errors; code that writes X @ w for scaling contracts where it should stretch. Name the intent first (scale or project), then pick the operator.

Audit by operator: grep @ lines and confirm each pair satisfies the inner rule; grep * lines on 2D operands and confirm the trailing dims broadcast legally. The two checks take minutes and separate the genuinely misaligned (fix the width) from the mis-verbed (fix the operator) — different bugs with different patches.

numpy_broadcast_vs_matmul.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
import numpy as np

X = np.arange(12, dtype=float).reshape(4, 3)
w = np.array([10.0, 100.0, 1000.0])
print("broadcast scale row:", (X * w)[0].tolist())  # stretch (3,) over rows
W = np.ones((3, 2))
print("matmul project:", (X @ W).shape)  # contract k=3 -> (4, 2)
try:
    X * np.ones((3, 2))  # trailing dims 4,3 vs 3,2: no broadcast
    print("unexpected broadcast")
except ValueError as exc:
    print("broadcast rejected:", str(exc)[:70])
📊 Production Insight
A sibling notebook 'fixed' a similar mismatch by switching @ to * — the error vanished and the scores became elementwise garbage for a week. Operator swaps that silence the message without satisfying a contract deserve a value-assert, not a commit.
🎯 Key Takeaway
Use to stretch (scale/shift) and @ to contract (project/mix). Confirm @ pairs by the inner rule and pairs by trailing-dim broadcast legality.

reshape, transpose, squeeze: Change Layout Without Scrambling Data

Reshape changes the shape tuple while preserving C-order data sequence by default: arange(12).reshape(3,4) then .reshape(2,6) keeps values 0-11 marching row by row. The -1 placeholder infers one axis (X.reshape(-1, 64) keeps 64-wide rows however many batches arrived), which is ideal for variable batch sizes. Transpose swaps axes (B.T turns (32,10) into (10,32)) for orientation fixes, and squeeze/expand_dims add or drop size-1 axes that batching introduces.

Each tool answers a different diagnosis. Wrong count of elements (can't reshape 4,096x64 into 32-wide) means the data itself is stale — no layout trick fixes a width split, so finish the rollout. Right elements, wrong orientation (weights saved as (10,64)) means one .T in the loader. Extra leading batch axis on one side means squeeze or indexed selection before the @. Applying transpose to a width split just moves the error; applying reshape to an orientation issue scrambles order silently.

Verify every layout change on a tiny labeled array first: reshape arange(12) and check the first row's values survived in order, transpose a marked matrix and confirm the mark moved correctly. The 30-second micro-test on toy data prevents the order-scrambling 'fix' that passes shapes and corrupts scores.

numpy_reshape.pyPYTHON
1
2
3
4
5
6
7
8
9
import numpy as np

x = np.arange(12).reshape(3, 4)
print("reshape -1:", x.reshape(-1, 2).shape)  # (6, 2), order kept
print("first row kept:", x.reshape(2, -1)[0][:4].tolist())
W = np.zeros((10, 64))  # saved transposed
print("loader fix:", (np.zeros((256, 64)) @ W.T).shape)  # (256, 10)
batch = np.zeros((1, 64))
print("squeeze batch:", batch.squeeze(axis=0).shape)  # (64,)
⚠ Reshape Can't Fix Stale Widths
If feature width (64) disagrees with weight rows (32), no transpose or reshape repairs it — the data contract split across a partial rollout. Finish the rollout or gate the probe; layout tools fix orientation, never staleness.
📊 Production Insight
The incident needed no reshape at all — the 64-wide features were correct and the 32-wide weights stale. A transpose 'fix' would have produced (4096,64) @ (10,32) and a second riddle; completing the rollout to (64,10) was the only valid patch.
🎯 Key Takeaway
Reshape with -1 for batch flexibility, transpose for orientation, squeeze for stray batch axes. Verify order on toy arrays; never layout-trick a genuine width split.

Batched @ on 3D Tensors: Leading Axes Broadcast, Last Two Contract

For stacked batches, X @ W follows one rule: leading axes broadcast, the last two axes matmul. A (32,128,64) batch times (64,10) weights yields (32,128,10) — 32 sequences each projected independently. A (32,128,64) times (32,64,10) per-batch weights also works via leading-axis broadcast. But a (32,64,128) activation times (64,10) fails: the last two axes (64,128) vs (64,10) misalign because the sequence axis wandered into the contraction slot.

Fix orientation with transpose or moveaxis before the @, and state which axes contract in a comment: scores = X @ W # (B,T,k)@(k,m)->(B,T,m). The comment is load-bearing — the next reader sees the intended contraction without reverse-engineering axis order, and the reviewer checks the transpose against the comment instead of guessing.

Probe 3D shapes on CPU with zeros before GPU time: print the 3-tuple pair and the result tuple on a (2,4,64)/(64,10) miniature. The miniature fails in milliseconds with the same message the 1.2M-row run would produce after warmup — the cheapest possible reproduction of every batched variant of this error.

Keep one miniature per 3D call site in the test suite so axis regressions fail at merge time instead of after GPU scheduling.

numpy_batched.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

X = np.zeros((32, 128, 64))
W = np.zeros((64, 10))
print("batched:", (X @ W).shape)  # (32, 128, 10)
wandering = np.zeros((32, 64, 128))
try:
    wandering @ W
    print("unexpected join")
except ValueError as exc:
    print("batch in slot:", str(exc)[:80])
fixed = wandering.transpose(0, 2, 1)  # (32, 128, 64)
print("transposed:", (fixed @ W).shape)
📊 Production Insight
Sequence pipelines hit this variant when a (B,k,T) activation meets (k,m) weights — batch fine, contraction polluted by the time axis. The axis comment plus transpose in the loader now documents which two axes contract for every 3D @ in scoring.
🎯 Key Takeaway
Batched @ broadcasts leading axes and contracts the last two. Transpose wandering axes out of the contraction slot and comment the intended (B,T,k)@(k,m) contract.

Harden the Pipeline: Probes, Pins, and Freshness Gates

Close the class with three durable controls. First, a 100-row probe before GPU warmup: run one miniature batch through embedder-to-scores, asserting each stage's width and the final inner pair, paging on any gap. The probe fails in seconds where the full run failed after 12 minutes of warmup — same message, 1% of the cost. Second, pin the embedder and weight versions as a tested pair in the deploy manifest, verified by a script asserting W.shape against the embedder's declared width rather than comparing version strings.

Third, gate downstream on freshness: ranking tables assert row counts and max-score-date after every run, paging on emptiness instead of serving fallbacks silently for 5 hours. The homepage review that caught the incident becomes a monitor that catches it at 1:13 a.m.

Log shapes with the run record — embedder width, weight shape, batch count, rows scored — so the next mismatch arrives with its evidence attached. Width changes are normal evolution; unprobed width changes are incidents. The three controls convert every future split into a seconds-fast page with both shapes quoted.

Review the width log after each model release even when green; slow drift across releases is visible there weeks before it becomes a mismatch.

numpy_probe_gate.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

def probe(embed_width, W, n=100):
    X = np.zeros((n, embed_width))
    ok = X.shape[1] == W.shape[0]
    print(f"probe: X{X.shape} W{W.shape} inner-ok={ok}")
    if not ok:
        raise SystemExit(f"width split: embed {embed_width} vs weights {W.shape}")
    return (X @ W).shape

print("paired:", probe(64, np.zeros((64, 10))))
try:
    probe(64, np.zeros((32, 10)))
except SystemExit as exc:
    print("gate paged:", exc)
📊 Production Insight
The probe gate added after the outage (100-row width check before warmup) has blocked 1 mismatched rollout since — a seconds-fast page with both shapes quoted, versus 5 hours of fallback ordering and an empty ranking table.
🎯 Key Takeaway
Probe 100 rows before warmup, pin embedder-plus-weights as a shape-verified pair, and gate ranking freshness after every run. Width evolution stays routine; unprobed splits become fast pages.
● Production incidentPOST-MORTEMseverity: high

Embedding Width Change Misaligned Scoring for 5 Hours

Symptom
The nightly scoring job failed at 1:12 a.m. on its first batch with ValueError: shapes (4096,64) and (32,10) not aligned: 64 (dim 1) != 32 (dim 0). All 1.2M rows went unscored, the 7 a.m. ranking tables rendered empty, and the homepage served fallback ordering for 5 hours until the rerun finished at 8:04 a.m. Retries at 2:00 and 3:30 a.m. crashed identically — the mismatch was deterministic, not transient, so every retry burned 12 minutes of GPU warmup for nothing.
Assumption
The team assumed the embedding upgrade (32 to 64 dims, deployed Tuesday) was decoupled from scoring because the model registry showed version 7 for both stages and CI passed. CI used a 64-wide fixture for features but loaded its weights from a stale 64-wide mock — while production weights for model 7 were still the 32-wide file from a partial rollout. Review compared version numbers, not .shape values, and no gate asserted feature width against weight width before the 1.2M-row run.
Root cause
Features arrived (4096,64) from the upgraded embedder while the production weight file stayed (32,10) from the incomplete rollout — inner dims 64 vs 32. Line 73 of scoring/batch.py ran scores = X @ W with no shape assert, so the first 4,096-row batch raised immediately. The remaining 293 batches never ran, and the ranking tables' freshness check (which would have paged) didn't exist — emptiness was discovered by the morning homepage review, not by monitoring.
Fix
The fix touched 3 files and rescored in 52 minutes. The immediate repair completed the rollout: production weights moved to the 64-wide (64,10) file matching model 7, verified by assert W.shape == (64, 10) in the deploy script. Line 73 gained assert X.shape[1] == W.shape[0] with both shapes in the message, failing fast before GPU warmup. A pre-run gate in scoring/validate.py now compares embedder output width against weight width on a 100-row probe and pages past any gap. The 8:04 a.m. rerun scored all 1.2M rows; the probe gate has since blocked 1 mismatched rollout before it reached batch one.
Key lesson
  • Assert inner dims before GPU warmup; a 100-row probe comparing 64 vs 32 fails in seconds where 1.2M rows failed over 5 hours.
  • Gate rollouts on .shape values, not version numbers; model 7 named two different widths and CI's mock hid the split.
  • Probe gates beat freshness luck; ranking emptiness was found by homepage review because no monitor watched table freshness.
Production debug guideFive patterns that print the clashing dims — with commands that show the fix shape.5 entries
Symptom · 01
ValueError names two shapes and dims but you can't see which operand is wrong
Fix
Print both shapes plus the rule: python -c "import numpy as np; A=np.zeros((128,64)); B=np.zeros((32,10)); print(A.shape, B.shape, 'need A.shape[1]==B.shape[0]:', A.shape[1]==B.shape[0])". The message's dim pair (64 vs 32) is the inner contract — fix whichever side disagrees with the data contract, not both.
Symptom · 02
You suspect a stray transpose or a weights file saved in the wrong orientation
Fix
Test the transpose hypothesis numerically: python -c "import numpy as np; B=np.zeros((32,10)); print('B.T:', B.T.shape)" and check the file's stored shape with python -c "import numpy as np; W=np.load('/tmp/W.npy'); print(W.shape, W.dtype)". If B.T matches the contract, the file was saved transposed — fix the loader once, not every call site.
Symptom · 03
Broadcasting (*) used where matmul (@) belongs, or vice versa
Fix
Contrast the two on small shapes: python -c "import numpy as np; A=np.zeros((4,3)); B=np.zeros((3,2)); print('matmul:', (A@B).shape); C=np.zeros((4,3)); print('broadcast:', (C+np.zeros(3)).shape)". Matmul contracts inner dims to (4,2); broadcasting stretches trailing dims elementwise — pick @ for joins, * for scaling.
Symptom · 04
Batch axis wandering into the matmul dims of a 3D tensor
Fix
Isolate the contracting axes: python -c "import numpy as np; X=np.zeros((32,128,64)); W=np.zeros((64,10)); print((X@W).shape)" — batched matmul broadcasts the leading axis and contracts the last two, giving (32,128,10). If your error shows a batch size where k belongs, transpose with X.transpose(0,2,1) or moveaxis before the @.
Symptom · 05
Reshape needed but you're unsure which axis holds the data order
Fix
Prove order preservation on a tiny array: python -c "import numpy as np; x=np.arange(12).reshape(3,4); print(x.reshape(-1,2).shape); print(x.reshape(2,-1).tolist()[0][:4])". Reshape keeps C-order by default — use -1 to infer one axis, verify the first row's values survived, then apply to the (4096,64) batch.
Shape Misalignment Causes at a Glance
Root CauseHow to ConfirmFixPrevention
Stale width after upgradeInner pair 64 vs 32; version sameComplete rollout to paired widthsProbe gate on 100 rows pre-run
Transposed weight fileB.T satisfies the contractOne .T in loaderDeploy assert on W.shape
Broadcast/matmul swap@ vs * intent mismatchPick operator by intentGrep audits per operator
Batch axis in contract slot3D last-two misaligntranspose/moveaxis + commentCPU miniature probe
Blind reshape scrambleShapes pass, values wrongToy-array order check firstValue asserts after layout
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
numpy_inner_rule.pyX = np.zeros((128, 64))The (n,k)@(k,m) Rule
numpy_print_shape.pydef scored(X, W):Print .shape First
numpy_broadcast_vs_matmul.pyX = np.arange(12, dtype=float).reshape(4, 3)Broadcasting vs Matmul
numpy_reshape.pyx = np.arange(12).reshape(3, 4)reshape, transpose, squeeze
numpy_batched.pyX = np.zeros((32, 128, 64))Batched @ on 3D Tensors
numpy_probe_gate.pydef probe(embed_width, W, n=100):Harden the Pipeline

Key takeaways

1
Matmul contracts inner dims
read the message's dim pair and reconcile those two numbers.
2
Print .shape values plus the inner boolean before every production @; assert with both shapes.
3
Use * for stretching and @ for contracting
audit each operator against its own rule.
4
Reshape with -1, transpose orientation, squeeze batch axes; verify order on toy arrays first.
5
Batched @ broadcasts leading axes
transpose wanderers out and comment the axis contract.
6
Probe 100 rows pre-warmup, pin shape-verified pairs, and gate ranking freshness after runs.

Common mistakes to avoid

5 patterns
×

Auditing outer dims instead of the message's inner pair

Symptom
Hours checking batch 128 and outputs 10 while the named 64-vs-32 split sits in the message unexamined.
Fix
Read dim 1 vs dim 0 first; reconcile exactly those two numbers with the data contract.
×

Transposing or reshaping a genuine width split

Symptom
Second riddle (or silently scrambled scores) because layout tools can't repair a partial rollout's stale file.
Fix
Finish the rollout to paired widths; reserve layout tools for orientation and batch axes.
×

Swapping @ to * to silence the error

Symptom
Error gone, scores garbage — elementwise values where projections belong, wrong for a week.
Fix
Pick the operator by intent (scale vs project) and assert values, not just shapes.
×

Retrying a deterministic mismatch

Symptom
Two retries burn GPU warmup for identical crashes — 24 minutes of nothing.
Fix
Probe 100 rows first; deterministic shape errors never heal on retry.
×

Comparing version numbers instead of shapes

Symptom
Model 7 names both 32-wide and 64-wide artifacts; CI's mock hides the split until 1.2M rows fail.
Fix
Assert W.shape against embedder width in deploy and pre-run probe.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does 'shapes (128,64) and (32,10) not aligned: 64 != 32' mean?
Q02JUNIOR
How do broadcasting (*) and matmul (@) differ?
Q03SENIOR
When is transpose the right fix versus a rollout completion?
Q04SENIOR
How does batched @ on 3D tensors contract axes?
Q05SENIOR
Design shape-hardening for a 1.2M-row scoring pipeline.
Q01 of 05JUNIOR

What does 'shapes (128,64) and (32,10) not aligned: 64 != 32' mean?

ANSWER
Matmul needs equal inner dims: (n,k)@(k,m). Dim 1 of the left (64) must equal dim 0 of the right (32) — here they don't. Outer dims are fine; reconcile the inner pair against the data contract.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What does 'not aligned' actually complain about?
02
Should I transpose or reshape to fix it?
03
Why does * work where @ fails?
04
How do I handle variable batch sizes?
05
What about 3D batches in scoring?
06
How do I stop rollouts from splitting widths?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
Verified
production tested
September 23, 2026
last updated
1,905
articles · all by Naren
🔥

That's Libraries. Mark it forged?

5 min read · try the examples if you haven't

Previous
pip Could Not Build Wheels Fix
3 / 3 · Libraries