A porting assessment that ends in a verdict — “port to C++”, “move to CUDA” — is worth very little three months later, when the roadmap shifts or a new stakeholder asks where the number came from. The deliverable that survives is an evidence chain: a profiled baseline of the workload as it runs today, per-target gain estimates with their measurement basis stated, a migration cost model built from your own code structure, and a recommendation that can be re-derived from the first three. This piece walks the document section by section — not to argue for assessments, but to show what a reader should be able to interrogate on each page.
What does a porting-assessment deliverable look like in practice?
Four sections, in a fixed order, because each one constrains the next.
| Section | What it contains | The question it answers |
|---|---|---|
| 1. Profiled baseline | Wall-clock time attributed across interpreter/framework overhead, model compute, host↔device transfer, serialisation, and I/O wait, on representative inputs | Where does the time actually go today? |
| 2. Ranked target runtimes | Candidate targets (CPU-vector, GPU, WASM/WebGL, mobile) each with a gain range, and a label for whether the range is measured, extrapolated, or bounded by Amdahl’s law | What is the ceiling on each option? |
| 3. Migration cost model | Engineer-weeks broken out by module, derived from the client’s own call graph, test coverage, and dependency surface | What does each option cost to build and maintain? |
| 4. Recommendation | Commit / defer / don’t port, with the payback window computed from §2 and §3 | Which option clears the bar, and by how much? |
The ordering matters more than the contents. A gain estimate produced before the baseline is an opinion with a number attached to it. We put the baseline first so that every downstream figure has a denominator a reader can check.
The baseline section, and how the hot path gets attributed
The baseline is the only section made entirely of measurements. It states the input set it ran against (batch shapes, sequence lengths, frame rates — whatever the workload’s real distribution looks like), the hardware and driver versions, the build configuration, and the profilers used: py-spy or cProfile at the interpreter level, PyTorch’s own profiler for op-level attribution, Nsight Systems for the CUDA timeline, perf for CPU-side stalls.
Attribution is presented as a time budget that sums to 100% of wall clock. That constraint is deliberate. It forces the unattributed remainder into the open rather than letting it hide inside a “framework overhead” bucket — and in our experience the remainder is where the interesting finding usually lives, because it is often synchronous waiting rather than computation.
A baseline measured against the wrong build makes every port look attractive. If the current C++ path was profiled at -O0, or the PyTorch path ran without torch.compile and with a default thread count, the headroom in section 2 is partly compiler headroom being misfiled as porting headroom. The deliverable states the build flags explicitly for exactly this reason. Sibling reading on compiler baselines and on the profiling method itself goes deeper than we can here; the ranked-target section below is what this document adds.
How gain ranges are presented
Each candidate target gets a row, and each row carries its evidence class in the row itself rather than in a footnote. That is the difference between a table a reader can act on and one they have to trust.
| Candidate target | Gain range on total wall clock | Evidence class |
|---|---|---|
Tuned CPU build (-O3 -march=native, threading fixed) |
Measured directly on the client’s code, small prototype | benchmark — reproducible from the supplied build script |
| C++ rewrite of the hot module | Bounded above by the module’s measured share of wall clock | derived from baseline — arithmetic ceiling, not a promise |
| GPU port (CUDA or OpenCL) | Kernel-level microbenchmark, extrapolated to the full path | benchmark for the kernel; observed pattern for the extrapolation, which has held within a factor on comparable engagements |
| WASM/WebGL target | Estimated from a representative kernel port | observed pattern — not a benchmarked rate for this workload |
Two properties are non-negotiable here. First, every range is capped by the share of runtime the port can actually touch — a rewrite of a module holding 22% of wall clock cannot deliver a 3× end-to-end improvement, and the deliverable says so in the row rather than leaving the reader to do Amdahl arithmetic. Second, ranges are ranges. A single number implies a precision the profiling data does not support, and it is the first thing a hostile reviewer will attack.
The cost model, built from your code rather than from averages
Engineer-week estimates in most assessments come from a rate card. Ours come from the call graph. The model decomposes the candidate port into the modules that would actually change, and prices each against four observable properties of the existing codebase: the module’s public interface surface, its test coverage, the number of third-party dependencies that have no equivalent in the target runtime, and whether the numerics can be validated against the current implementation output-for-output.
That last property is usually the largest single cost driver, and it is the one teams forget to budget. Porting an activation, a normalisation step, or a post-processing stage into a different runtime changes float behaviour; proving the decision boundary did not move requires a differential test harness that often costs more than the kernel work. The model prices the harness as a line item, not as overhead.
The cost side also carries maintenance. A CUDA path pins you to one vendor’s stack; a WASM target adds a build pipeline someone has to own. Those are recurring engineer-weeks, and they belong in the payback window rather than in a caveat. Our GPU performance and portability engineering work is where these estimates are calibrated from, and the assessment itself sits within our broader R&D engagements with outcome ownership.
What a defer recommendation looks like as a document
This is the section that tells you whether the assessment was honest. A commit recommendation is easy to write. A defer recommendation has to price the thing that did not happen.
It reads roughly like this: the profiled baseline attributes the dominant share of wall clock to memory bandwidth and to a synchronous upstream call, not to compute in the module the team proposed to rewrite; therefore the arithmetic ceiling on the proposed port is a single-digit percentage of end-to-end latency, against a cost model of N engineer-weeks plus an ongoing maintenance surface; therefore the recommendation is to defer the port and to address the upstream call and the allocation pattern first, with a named threshold — a latency percentile or cost-per-inference figure — at which the port should be re-evaluated.
The avoided migration cost is stated as a number. Both outcomes carry a figure a budget owner can act on; a defer that only says “we don’t think it’s worth it” has failed at the same job as a commit that only says “port it”.
How an engineering leader should interrogate it
Read the four sections backwards. Take the recommendation, find the payback window, and trace each of its two inputs to the table row it came from. Then check three things:
- Does every gain figure name its evidence class, and is the strongest claim in the document actually a measurement rather than an extrapolation?
- Does each gain range respect the share of wall clock its target can touch, per the baseline’s own attribution?
- Does the cost model reference specific modules in your repository, or generic phases? Generic phases mean it was not built from your code.
If a number cannot be traced back to a measurement or to an explicitly labelled estimate, treat it as unsupported and ask for the derivation. The deeper question of when a port is worth funding at all — thresholds, capital allocation, the decision rather than the document — is the subject of our wider work on porting and performance assessment engagements.
One uncertainty stays uncomfortable and we would rather name it than smooth it over: extrapolating a kernel microbenchmark to a full serving path is the weakest link in any ranked-target table. It is labelled as such in the deliverable, and it is the row we would prototype first if the recommendation is close to the threshold.
Frequently Asked Questions
What does a porting-assessment deliverable look like in practice — what is actually in the document? A porting assessment delivers a scored matrix of target platforms, risk flags, and estimated engineering hours—not a recommendation memo. Four ordered sections: a profiled baseline of the current workload, a ranked list of candidate target runtimes with gain ranges, a migration cost model in engineer-weeks derived from your code, and a commit/defer recommendation with a payback window. The order is load-bearing, because each section supplies the denominators for the next.
What does the profiling baseline section contain, and how is the hot path attributed? It names the input distribution, hardware, driver and build configuration, then attributes wall-clock time across interpreter and framework overhead, model compute, host-to-device transfer, serialisation and I/O wait. The budget is required to sum to 100% of wall clock so that unattributed time is visible rather than buried.
How are per-target gain ranges presented so a reader can see what is measured versus estimated? Each candidate target is a table row carrying its own evidence class — measured benchmark, arithmetic ceiling derived from the baseline, or extrapolated estimate — rather than a footnote covering the whole table. Every range is capped by the share of wall clock that target can actually touch.
How is the migration cost model built from the client’s existing code, and what drives the engineer-week estimate? It decomposes the port into the specific modules that would change and prices each against interface surface, test coverage, dependencies with no equivalent in the target runtime, and the cost of proving numerical equivalence. The differential test harness is usually the largest single driver, and recurring maintenance is priced as a line item too.
How does the defer-or-commit recommendation trace back to the baseline and the cost model? The payback window is computed from exactly two inputs — a gain range from the ranked-target table and a cost figure from the model — so a reader can walk the recommendation backwards to the measurement it rests on. If any figure cannot be traced that way, it should be treated as unsupported.
What does a defer recommendation look like as a deliverable, and what evidence justifies it? It states which non-compute stage dominates wall clock, the resulting arithmetic ceiling on the proposed port, the cost model it would have run against, and the avoided migration cost as a number. It also names the threshold — a latency percentile or cost-per-inference figure — at which the port should be reconsidered.
How should an engineering leader interrogate the deliverable before committing budget? Read it backwards from the recommendation, trace both payback inputs to their table rows, and confirm the strongest claim is a measurement rather than an extrapolation. Then check that the cost model cites real modules in your repository rather than generic project phases.
Anatomy of a port assessment report
Every deliverable includes dependency graphs, hotspot analysis, risk ratings, and estimated engineering hours—no executive summaries without data. Everything else is detail.