# Ttir Model Op Analysis

> Given a `.mlir` file (or a directory of `.mlir` files) with TTIR ops, run the same TTIR normalization passes as `D2MFrontendPipeline` before D2M, then produce per-file outputs: `preprocessed.mlir`, `ttir-op-report.txt` (op counts from normalized IR), and `ops.mlir` (one func per unique op configuration, golden-style). Optional: per-pass IR dumps.

- Skill: `tenstorrent/ttir-model-op-analysis` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tenstorrent/ttir-model-op-analysis`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tenstorrent/ttir-model-op-analysis/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tenstorrent (https://skillmd.com/u/tenstorrent)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tenstorrent/ttir-model-op-analysis

---


# TTIR model op analysis (post-frontend-normalization)

Given a `.mlir` file (or a directory of `.mlir` files, e.g. a multi-graph model)
containing TTIR ops (from vLLM, torch-mlir, or tt-forge), normalize it the way the
compiler does before D2M, then inventory which TTIR ops (and shapes) appear. **Do not**
open `TTIRToD2M.cpp`, grep `populateTTIRToD2MPatterns`, or produce "D2M coverage"
unless the user explicitly asks for that comparison.

The input can be:
- **A single `.mlir` file** (e.g. `model.mlir`)
- **A directory** of `.mlir` files (e.g. `vllm_opt/` containing `graph1.mlir`,
  `graph2.mlir`). Each file is processed independently with per-graph reports.

## Step 1: Run the frontend TTIR normalization pipeline

These passes match `createD2MFrontendPipeline` in
`lib/Dialect/D2M/Pipelines/D2MPipelines.cpp`:

| # | Pass flag | Short name (for filenames) |
|---|-----------|---------------------------|
| 1 | `--canonicalize` | `canonicalize` |
| 2 | `--ttir-predicate-type-alignment` | `predicate-type-alignment` |
| 3 | `--ttir-element-type-normalization` | `element-type-normalization` |
| 4 | `--ttir-to-ttir-decomposition` | `decomposition` |
| 5 | `--ttir-explicate-tms` | `explicate-tms` |
| 6 | `--ttir-erase-inverse-ops` | `erase-inverse-ops` |
| 7 | `--ttir-move-reshape-to-constant` | `move-reshape-to-constant` |
| 8 | `--ttir-fold-constant-reshape-broadcast` | `fold-constant-reshape-broadcast` |
| 9 | `--ttir-implicit-broadcast-fold` | `implicit-broadcast-fold` |

### Single-file mode

Create `<basename>/` next to the input (basename = filename without `.mlir`):

```
<basename>/
  preprocessed.mlir              # IR after all passes above
  ttir-op-report.txt             # counts + sorted mnemonic list (see below)
  ops.mlir                       # one func per unique TTIR op configuration
```

```bash
source env/activate
INPUT="<input.mlir>"
BASENAME="$(basename "$INPUT" .mlir)"
mkdir -p "$BASENAME"

ttmlir-opt \
  --canonicalize \
  --ttir-predicate-type-alignment \
  --ttir-element-type-normalization \
  --ttir-to-ttir-decomposition \
  --ttir-explicate-tms \
  --ttir-erase-inverse-ops \
  --ttir-move-reshape-to-constant \
  --ttir-fold-constant-reshape-broadcast \
  --ttir-implicit-broadcast-fold \
  -o "$BASENAME/preprocessed.mlir" \
  "$INPUT"
```

### Directory mode (multi-graph models)

When the input is a directory like `vllm_opt/` containing `graph1.mlir`,
`graph2.mlir`, etc., loop over each file and create a subdirectory per graph.
Each subdirectory uses clean names -- the folder provides the graph context:

```
vllm_opt/
  graph1.mlir                        # original input
  graph2.mlir
  ttir-op-report.txt                 # combined report across all graphs
  graph1/                            # per-graph output folder
    preprocessed.mlir
    ttir-op-report.txt
    ops.mlir
  graph2/
    preprocessed.mlir
    ttir-op-report.txt
    ops.mlir
```

```bash
source env/activate
INPUT_DIR="<dir>"
for f in "$INPUT_DIR"/*.mlir; do
  STEM="$(basename "$f" .mlir)"
  mkdir -p "$INPUT_DIR/$STEM"
  ttmlir-opt \
    --canonicalize \
    --ttir-predicate-type-alignment \
    --ttir-element-type-normalization \
    --ttir-to-ttir-decomposition \
    --ttir-explicate-tms \
    --ttir-erase-inverse-ops \
    --ttir-move-reshape-to-constant \
    --ttir-fold-constant-reshape-broadcast \
    --ttir-implicit-broadcast-fold \
    -o "$INPUT_DIR/$STEM/preprocessed.mlir" \
    "$f" || { echo "FAILED on $f"; continue; }
done
```

#### `ttir-op-report.txt` and `ops.mlir`

**Do not hand-roll parsing.** From the repo root (any Python 3.12+; no venv import deps).
The script accepts multiple paths, so pass all preprocessed files in one command:

```bash
python tools/scripts/model_breakdown/ttir_model_op_inventory.py "$INPUT_DIR"/*/preprocessed.mlir
```

This writes `ttir-op-report.txt` and `ops.mlir` next to each `preprocessed.mlir`,
plus a combined `ttir-op-report.txt` at the common parent directory with per-file
summary and merged mnemonic counts.

Optional flags: `--report PATH`, `--ops PATH` (single-file only), `-v`.

The script reports: total `"ttir.*"` instances, per-mnemonic counts (with optional percentages),
distinct mnemonic count, and distinct op configurations (SSA-normalized op lines). **`ops.mlir`**
follows the same shape as `test/python/golden/mlir_snippets/models/qwen3_4b/ops.mlir`: one
`func.func` per unique configuration (`<mnemonic>_<index>`), args `%arg0`, ..., single op + `return`.
For multi-result ops such as `ttir.sort` and `ttir.topk`, the generated op must use MLIR
multi-result binding syntax (`%0:2 = ...`) and return projected results (`return %0#0, %0#1 :
...`). If you see `operation defines 2 results but was provided 1 to bind`, regenerate with
`tools/scripts/model_breakdown/ttir_model_op_inventory.py` instead of hand-editing every snippet.

`ttir.full` and `ttir.constant` are **excluded** from `ops.mlir` unless their result is directly
returned by the module -- they almost always just produce values consumed by other ops, and are
already inlined as const producers inside those ops' test functions. The report still counts them.

It assumes **one generic TTIR op per line** (normal `ttmlir-opt` output). If needed, sanity-check
parse with `ttmlir-opt` on `ops.mlir`.

### Optional: dump IR after each pass

Only if the user asks. Same pass loop as above; write
`<basename>/<basename>.<short-name>.mlir` for each stage. Final stage is the source for
`ttir-op-report.txt` and `ops.mlir` (same as `*.implicit-broadcast-fold.mlir` when you ran
all nine).

```bash
source env/activate
INPUT="<input.mlir>"
BASENAME="$(basename "$INPUT" .mlir)"
mkdir -p "$BASENAME"
PREV="$INPUT"
declare -a PASSES=(
  "--canonicalize|canonicalize"
  "--ttir-predicate-type-alignment|predicate-type-alignment"
  "--ttir-element-type-normalization|element-type-normalization"
  "--ttir-to-ttir-decomposition|decomposition"
  "--ttir-explicate-tms|explicate-tms"
  "--ttir-erase-inverse-ops|erase-inverse-ops"
  "--ttir-move-reshape-to-constant|move-reshape-to-constant"
  "--ttir-fold-constant-reshape-broadcast|fold-constant-reshape-broadcast"
  "--ttir-implicit-broadcast-fold|implicit-broadcast-fold"
)
for entry in "${PASSES[@]}"; do
  IFS='|' read -r FLAG SHORTNAME <<< "$entry"
  OUT="$BASENAME/$BASENAME.$SHORTNAME.mlir"
  ttmlir-opt $FLAG -o "$OUT" "$PREV" || { echo "FAILED at: $FLAG"; break; }
  PREV="$OUT"
done
```

**NOTE:** If a pass fails, check `include/ttmlir/Dialect/TTIR/Transforms/Passes.td` for the
registered flag. Some setups need
`--ttcore-register-device="system-desc-path=..."` before other passes.

## Step 2: Fill the report and `ops.mlir`

Run **`tools/scripts/model_breakdown/ttir_model_op_inventory.py`** on each `preprocessed.mlir` file
(single-file mode). See above.

## Next: compile/run the snippets

To compile or execute the generated `ops.mlir` on device, see the
**run-ops-mlir-snippets** skill.

