Cheapest of Six Azure GPU VMs for gpt-oss-120b: $2,681 to $5,363 a Month
The short answer
Of the six Azure GPU VM sizes we priced, the cheapest that fits gpt-oss-120b is the NC24ads A100 v4: $2,681.29 a month in East US and three other regions, $5,362.58 in Brazil South (2.00x). Median across 27 regions: $3,486. The NC40ads H100 v5 (one H100 NVL, 94 GB) lists at $5,095.40 to $7,388.33. Linux pay-as-you-go, VM compute only, 730 hours.
What you will have at the end
A Python script, azure_gpu_cost_by_region.py, that reads the size of a 120-billion-parameter open model, decides which Azure GPU VM sizes can hold it, pulls their prices from Microsoft’s public price list, and turns them into a monthly figure for every Azure region where the API lists a price for them. It writes the result as azure_gpu_cost_by_region.csv (169 rows, one per size and region), and it checks its own work before it writes anything.
Prices change, so the page’s numbers come from one saved copy of the raw answer, azure_vm_prices_2026-10-18.csv, fetched on 2026-10-18. Run the script with --snapshot on that file and you get the same numbers. Run it with --snapshot-out and you get today’s.
The model is OpenAI’s gpt-oss-120b. The price is the list price of the VM’s compute, running 730 hours a month, on Linux, pay-as-you-go. “Cheapest” on this page means the cheapest of the six VM sizes we chose to price, listed in Step 1. We did not price every Azure GPU size. It is a lower bound on a self-hosting bill for those sizes, not the bill. The section on build or buy says what is missing.
Step 1. Work out how much GPU memory the model needs
The model card lists gpt-oss-120b under the Apache 2.0 license, with “117B parameters with 5.1B active parameters”, and says it can “run on a single 80GB GPU” because its mixture-of-experts weights are quantized to MXFP4 (Hugging Face model card (read 2026-10-18)). We did not take that sentence on trust. The script asks the Hugging Face API for the file list of one pinned commit, b5c939de…, and adds up the root-level model-*.safetensors files: 15 files, 65,248,893,184 bytes (65.2 GB). The repository also holds a second copy of the weights under original/ and a file under metal/. They are alternative formats of the same model, so the script does not count them.
A model needs room beyond its weights: the key-value cache that grows with every token in flight, and the serving software. How much depends on context length and traffic, which this page does not measure. The script sets one constant, HEADROOM_GB = 10, and calls a size a fit when its total GPU memory is at least the weights plus that headroom: 75.2 GB. The 10 is our assumption. Change it and the “fits” column changes with it. The weights are counted in decimal gigabytes (109 bytes) and compared with the GB figure Microsoft states; the page does not settle whether Microsoft’s GB are decimal or binary, and the 10 GB of headroom absorbs that difference.
We chose the six sizes by hand from Microsoft’s GPU size pages: the one- and two-GPU sizes of the NC A100 v4 and NCads H100 v5 series, the eight-GPU ND H100 v5, and a four-T4 size as a size that must not fit. We did not price every Azure GPU size. Not priced: the four-GPU NC96ads A100 v4, the ND A100 v4 sizes, and any H200, MI300X or other family. A size outside this list could cost less than the cheapest one here.
The GPU memory of each size comes from Microsoft’s own size pages (each linked in the table, all read 2026-10-18). The mapping is written out in the script as SKUS, so the reader can disagree with it:
| Size | GPUs | Memory per GPU | Microsoft size page |
|---|---|---|---|
Standard_NC64as_T4_v3 | 4 × Tesla T4 | 16 GB | NCasT4_v3 |
Standard_NC24ads_A100_v4 | 1 × A100 PCIe | 80 GB | NC_A100_v4 |
Standard_NC48ads_A100_v4 | 2 × A100 PCIe | 80 GB | |
Standard_NC40ads_H100_v5 | 1 × H100 NVL | 94 GB | NCads_H100_v5 |
Standard_NC80adis_H100_v5 | 2 × H100 NVL | 94 GB | |
Standard_ND96isr_H100_v5 | 8 × H100 | 80 GB | ND-H100-v5 |
The four-T4 size is in the list on purpose. Its 64 GB in total is below the 65.2 GB of weights, so it is the one size the script must mark as not fitting. A check that cannot fail would prove nothing, and this one fails if the weights are miscounted (run 2).
Step 2. Pull the prices and keep the raw answer
Microsoft publishes list prices through the Azure Retail Prices API, which needs no sign-in. The script uses the 2023-01-01-preview version because the documentation says savings-plan rates are only returned by that version, asks for USD, and filters to serviceName eq 'Virtual Machines', priceType eq 'Consumption' and the six sizes above (Microsoft Learn: Azure Retail Prices REST API overview (read 2026-10-18)). The API returns at most 1,000 records a page and a link to the next one, so the script follows NextPageLink until there is none (Microsoft Learn, same page (read 2026-10-18)), pauses between pages, and waits as long as a retry-after header says if the API asks it to slow down. On 2026-10-18 the answer was 950 rows on one page.
The script writes every row as it came, with the fetch time and each row’s effectiveStartDate, to the snapshot CSV. Everything after that is computed from the file, not from the live API.
Each size has more than one price row per region. The script sorts the 950 rows into four classes from the fields the API returns: spot (skuName contains “Spot”), low priority (contains “Low Priority”), Windows (productName ends in “Windows”), and everything else, which we treat as Linux pay-as-you-go. The “Windows” test is our reading of the API: its documented example row has the productName “Virtual Machines FS Series Windows”, and the rows for these sizes follow the same pattern (Microsoft Learn, same page (read 2026-10-18)). Only the last class is priced here: 169 rows. A row of any other type than Consumption would show up as its own “other type” class in the row count and would not be priced.
Step 3. Turn the hourly price into a month
The API prices a VM per hour. A month is 730 hours (8,760 divided by 12), the same for every region, so the monthly figure is the hourly price times 730. That assumes the VM runs all month. A VM you stop part of the day costs less, and the CSV has the hourly price so you can apply your own hours. The 1-year and 3-year figures are the savings-plan hourly rates the API lists on the same row, times 730, where it lists them.
SELECT l.sku, t.gpus, t.gb, (t.gpus * t.gb >= {need_gb}) AS fits, l.region, l.location, l.price,
round(l.price * 730, 2) AS monthly, round(l.sp1 * 730, 2) AS m1, round(l.sp3 * 730, 2) AS m3
FROM lin l JOIN sku_t t USING (sku)
ORDER BY fits DESC, t.gpus, l.sku, monthly, l.region
The full script has the fetch, the self-checks listed under Method, and the CSV writer. It needs Python 3 and the duckdb module (DuckDB documentation (read 2026-10-18)).
Run 1: the measurement
We ran the script on 2026-10-18 with DuckDB 1.5.6, asking the live API. The self-check passed: the 950 price rows loaded equal the 950 data lines of the snapshot, the weights equal the pinned byte count, every size the API returned is one the script maps, every size in the mapping matches a Linux row, each size has one Linux row per region, every price is above zero in USD per hour, and no savings-plan rate is above its price. One size, the four-T4 VM, does not fit.
Command: run 1python3 guides/azure-gpu-cost-by-region/azure_gpu_cost_by_region.py --out guides/azure-gpu-cost-by-region/azure_gpu_cost_by_region.csv --snapshot-out guides/azure-gpu-cost-by-region/azure_vm_prices_2026-10-18.csv
Output from our run 1, the facts
duckdb 1.5.6, python 3.14.8 source https://prices.azure.com/api/retail/prices?api-version=2023-01-01-preview fetched 2026-10-18T14:42:48Z: 950 rows in 1 page(s) model openai/gpt-oss-120b @ b5c939de8f754692c1647ca79fbf85e8c1e70f8a license apache-2.0: 15 weight files, 65248893184 bytes (65.2 GB) need 65.2 GB of weights + 10 GB headroom = 75.2 GB of GPU memory prices guides/azure-gpu-cost-by-region/azure_vm_prices_2026-10-18.csv: 950 data lines, fetched 2026-10-18T14:42:48Z rows by meter class: linux pay-as-you-go 169, low priority 290, spot 322, windows 169 Linux pay-as-you-go rows from a non-primary meter region: 0 (kept: they are the only Linux row for their region and size if the checks below pass) Linux rows whose 1-year and 3-year savings-plan rates both equal the pay-as-you-go price (no discount): 2Output from our run 1, the tables and the self-check
Linux pay-as-you-go, USD per month at 730 hours, by VM size
size gpus gpu_gb fits regions min median max max/min cheapest dearest
Standard_NC64as_T4_v3 4 64 no 33 2067 3812 5548 2.68 attdallas1 attatlanta1
Standard_NC24ads_A100_v4 1 80 yes 27 2681 3486 5363 2.00 eastus brazilsouth
Standard_NC40ads_H100_v5 1 94 yes 29 5095 6624 7388 1.45 eastus australiaeast
Standard_NC48ads_A100_v4 2 160 yes 27 5363 6972 10725 2.00 eastus brazilsouth
Standard_NC80adis_H100_v5 2 188 yes 29 10191 13248 14777 1.45 eastus australiaeast
Standard_ND96isr_H100_v5 8 640 yes 24 71774 93306 136224 1.90 eastus southafricawest
USD per month by region, one-GPU sizes that fit (730 h; payg = pay-as-you-go, 1y and 3y = savings-plan rate; '-' = no price or no rate listed)
NC24ads A100 v4 NC40ads H100 v5
region payg 1y 3y payg 1y 3y
australiaeast 3,887.98 3,236.35 2,398.49 7,388.33 6,146.35 4,557.86
brazilsouth 5,362.58 - - - - -
canadacentral 3,217.84 2,678.53 1,985.09 6,270.70 5,216.60 3,868.39
centralindia 3,753.66 3,124.55 2,315.63 7,133.56 5,934.41 4,400.69
centralus 3,029.50 2,521.76 1,868.90 6,267.05 5,213.56 3,866.14
eastus 2,681.29 2,231.91 1,654.09 5,095.40 4,238.86 3,143.35
eastus2 2,681.29 2,231.91 1,654.09 5,095.40 4,238.86 3,143.35
francecentral 3,351.43 2,789.73 2,067.50 - - -
germanywestcentral 3,485.75 2,901.54 2,150.36 6,624.02 5,510.52 4,086.36
indonesiacentral - - - 5,961.91 4,959.71 3,677.90
italynorth 3,485.75 2,901.54 2,150.36 - - -
japaneast 3,887.98 3,236.35 2,398.49 7,388.33 6,146.35 4,557.86
jioindiacentral 3,753.66 - - 7,133.56 - -
jioindiawest 3,753.66 3,753.66 3,753.66 7,133.56 - -
koreacentral 3,620.07 - - 6,878.79 5,722.47 4,243.53
koreasouth - - - 6,369.25 5,298.58 3,929.19
malaysiawest - - - 5,961.91 4,959.71 3,677.90
northcentralus 3,217.84 2,678.53 1,985.09 6,114.48 5,086.64 3,772.02
northeurope 3,217.84 2,678.53 1,985.09 6,114.48 5,086.64 3,772.02
polandcentral 3,485.75 2,901.54 2,150.36 - - -
southcentralus 3,217.84 2,678.53 1,985.09 6,117.40 5,089.07 3,773.82
southeastasia 3,485.75 2,901.54 2,150.36 6,624.02 5,510.52 4,086.36
spaincentral 3,485.75 2,901.54 2,150.36 6,624.02 5,510.52 4,086.36
swedencentral 3,485.75 - - 6,624.02 5,510.52 4,086.36
switzerlandnorth 3,833.96 - - 7,286.13 6,061.33 4,494.81
uaenorth - - - 7,285.40 6,060.72 4,494.36
uksouth 3,351.43 2,789.73 2,067.50 6,369.25 5,298.58 3,929.19
ukwest - - - 6,672.20 5,550.60 4,116.08
usgovarizona - - - 6,372.90 - -
westeurope 3,485.75 2,901.54 2,150.36 6,628.40 5,514.17 4,089.06
westus 3,485.75 2,901.54 2,150.36 6,624.02 5,510.52 4,086.36
westus2 2,681.29 2,231.91 1,654.09 5,095.40 4,238.86 3,143.35
westus3 2,681.29 2,231.91 1,654.09 5,095.40 4,238.86 3,143.35
rows for sizes that fit: 136; with a 1-year savings-plan rate 96; with a 3-year rate 96
SELF-CHECK PASSED: 950 price rows equal the 950 data lines of the file, the weights equal the pinned 65248893184 bytes, every size is mapped, every SKUS key matches a Linux row, one Linux row per size and region, every price is above zero in USD per hour, no savings-plan rate is above its price, 1 size(s) do not fit
wrote guides/azure-gpu-cost-by-region/azure_gpu_cost_by_region.csv
exit code 0
The result
| Size | GPUs | GPU memory (GB) | Fits? | Regions | Min | Median | Max | Max / min |
|---|---|---|---|---|---|---|---|---|
Standard_NC64as_T4_v3 | 4 | 64 | no | 33 | 2,067 | 3,812 | 5,548 | 2.68 |
Standard_NC24ads_A100_v4 | 1 | 80 | yes | 27 | 2,681 | 3,486 | 5,363 | 2.00 |
Standard_NC40ads_H100_v5 | 1 | 94 | yes | 29 | 5,095 | 6,624 | 7,388 | 1.45 |
Standard_NC48ads_A100_v4 | 2 | 160 | yes | 27 | 5,363 | 6,972 | 10,725 | 2.00 |
Standard_NC80adis_H100_v5 | 2 | 188 | yes | 29 | 10,191 | 13,248 | 14,777 | 1.45 |
Standard_ND96isr_H100_v5 | 8 | 640 | yes | 24 | 71,774 | 93,306 | 136,224 | 1.90 |
The summary above lists the six sizes we priced, including the one that does not fit and the ones far larger than the model needs. “Regions” counts the regions where the API lists a Linux price for that size; “GPU memory” is the total across GPUs. The next table is the answer to the question for the two one-GPU sizes that fit, with the savings-plan rates the API lists. “None listed” means the API lists no such rate for that row; “no price listed” means it lists no price for the size in that region.
| Region | A100 80 GB pay-as-you-go | 1-year savings plan | 3-year savings plan | H100 NVL 94 GB pay-as-you-go | 1-year savings plan | 3-year savings plan |
|---|---|---|---|---|---|---|
US East (eastus) | 2,681.29 | 2,231.91 | 1,654.09 | 5,095.40 | 4,238.86 | 3,143.35 |
US East 2 (eastus2) | 2,681.29 | 2,231.91 | 1,654.09 | 5,095.40 | 4,238.86 | 3,143.35 |
US West 2 (westus2) | 2,681.29 | 2,231.91 | 1,654.09 | 5,095.40 | 4,238.86 | 3,143.35 |
US West 3 (westus3) | 2,681.29 | 2,231.91 | 1,654.09 | 5,095.40 | 4,238.86 | 3,143.35 |
US Central (centralus) | 3,029.50 | 2,521.76 | 1,868.90 | 6,267.05 | 5,213.56 | 3,866.14 |
US North Central (northcentralus) | 3,217.84 | 2,678.53 | 1,985.09 | 6,114.48 | 5,086.64 | 3,772.02 |
EU North (northeurope) | 3,217.84 | 2,678.53 | 1,985.09 | 6,114.48 | 5,086.64 | 3,772.02 |
US South Central (southcentralus) | 3,217.84 | 2,678.53 | 1,985.09 | 6,117.40 | 5,089.07 | 3,773.82 |
CA Central (canadacentral) | 3,217.84 | 2,678.53 | 1,985.09 | 6,270.70 | 5,216.60 | 3,868.39 |
UK South (uksouth) | 3,351.43 | 2,789.73 | 2,067.50 | 6,369.25 | 5,298.58 | 3,929.19 |
FR Central (francecentral) | 3,351.43 | 2,789.73 | 2,067.50 | no price listed | ||
DE West Central (germanywestcentral) | 3,485.75 | 2,901.54 | 2,150.36 | 6,624.02 | 5,510.52 | 4,086.36 |
AP Southeast (southeastasia) | 3,485.75 | 2,901.54 | 2,150.36 | 6,624.02 | 5,510.52 | 4,086.36 |
ES Central (spaincentral) | 3,485.75 | 2,901.54 | 2,150.36 | 6,624.02 | 5,510.52 | 4,086.36 |
SE Central (swedencentral) | 3,485.75 | none listed | none listed | 6,624.02 | 5,510.52 | 4,086.36 |
US West (westus) | 3,485.75 | 2,901.54 | 2,150.36 | 6,624.02 | 5,510.52 | 4,086.36 |
EU West (westeurope) | 3,485.75 | 2,901.54 | 2,150.36 | 6,628.40 | 5,514.17 | 4,089.06 |
IT North (italynorth) | 3,485.75 | 2,901.54 | 2,150.36 | no price listed | ||
PL Central (polandcentral) | 3,485.75 | 2,901.54 | 2,150.36 | no price listed | ||
KR Central (koreacentral) | 3,620.07 | none listed | none listed | 6,878.79 | 5,722.47 | 4,243.53 |
IN Central (centralindia) | 3,753.66 | 3,124.55 | 2,315.63 | 7,133.56 | 5,934.41 | 4,400.69 |
IN Central Jio (jioindiacentral) | 3,753.66 | none listed | none listed | 7,133.56 | none listed | none listed |
IN West Jio (jioindiawest) | 3,753.66 | 3,753.66 | 3,753.66 | 7,133.56 | none listed | none listed |
CH North (switzerlandnorth) | 3,833.96 | none listed | none listed | 7,286.13 | 6,061.33 | 4,494.81 |
AU East (australiaeast) | 3,887.98 | 3,236.35 | 2,398.49 | 7,388.33 | 6,146.35 | 4,557.86 |
JA East (japaneast) | 3,887.98 | 3,236.35 | 2,398.49 | 7,388.33 | 6,146.35 | 4,557.86 |
BR South (brazilsouth) | 5,362.58 | none listed | none listed | no price listed | ||
ID Central (indonesiacentral) | no price listed | 5,961.91 | 4,959.71 | 3,677.90 | ||
MY West (malaysiawest) | no price listed | 5,961.91 | 4,959.71 | 3,677.90 | ||
KR South (koreasouth) | no price listed | 6,369.25 | 5,298.58 | 3,929.19 | ||
US Gov AZ (usgovarizona) | no price listed | 6,372.90 | none listed | none listed | ||
UK West (ukwest) | no price listed | 6,672.20 | 5,550.60 | 4,116.08 | ||
AE North (uaenorth) | no price listed | 7,285.40 | 6,060.72 | 4,494.36 |
Five things stand out.
- Of the sizes we priced, one A100 80 GB VM is the cheapest that holds the model, and region moves its price by a factor of two.
Standard_NC24ads_A100_v4costs $2,681.29 a month in East US, East US 2, West US 2 and West US 3, $3,486 in the median of its 27 regions and $5,362.58 in Brazil South. Same VM, same hours: $2,681.29 a month apart. - The H100 NVL VM costs 1.90 times as much as the A100 VM in the cheapest regions, and its price varies less across regions.
Standard_NC40ads_H100_v5runs $5,095.40 in the same four US regions and $7,388.33 in Australia East and Japan East, a ratio of 1.45 across 29 regions. The extra 14 GB of GPU memory, and anything it buys in speed, is outside this page. - The same four US regions are cheapest for both. No other region matches their price for either size.
- Not every region lists both. Four regions list a price for the A100 VM and not the H100 NVL VM (Brazil South, France Central, Italy North, Poland Central). Six list one for the H100 NVL VM and not the A100 (Indonesia Central, Korea South, Malaysia West, UAE North, UK West, US Gov Arizona). 33 regions list a price for at least one of the two.
- Savings-plan rates are patchy. Of the 136 rows for sizes that fit, 96 carry a 1-year and a 3-year rate. In East US the 3-year rate is $1,654.09 a month for the A100 VM and $3,143.35 for the H100 NVL VM. Brazil South lists none for the A100 VM, and in India West (Jio) the A100 row lists rates equal to its pay-as-you-go price. A commitment is a different purchase from list price, and these are only the rates the API lists.
Microsoft’s A100 v4 size page carries a note: “We are currently only deploying net new capacity for the Current NC series, the NCads_H100_v5-series” (Microsoft Learn (read 2026-10-18)). A price in a region does not mean you can get a quota or capacity there. This page does not measure that.
The eight-GPU Standard_ND96isr_H100_v5 fits too, with 640 GB of GPU memory. It costs $71,774 to $136,224 a month across its 24 regions, many times the one-GPU sizes, and the model card says one 80 GB GPU is enough. It is in the CSV, with the two-GPU sizes.
Run 2: plant seven faults and see whether the script notices
We ran the script from the saved snapshot with seven faults planted after loading and before the checks: one Linux price row loaded twice, one price set to 0, one A100 row renamed to a size that is not in the mapping, every row of one size deleted, one price quoted per month instead of per hour, one 3-year savings-plan rate set 50% above the price, and the weights size halved, as if only half the files had been summed. Each fault trips named checks:
- The duplicated row trips the row count against the file’s data lines and the check for one Linux row per size and region.
- The zero price trips the check that every price is above zero.
- The renamed size trips the check that every size the API returned is in the mapping.
- The deleted size trips the check that every key of the mapping matches at least one Linux row (
Standard_NC80adis_H100_v5matches none), and the row count. - The monthly price trips the check that every price is in USD per hour.
- The inflated savings-plan rate trips the check that no rate is above the pay-as-you-go price.
- The halved weights trip the check against the pinned byte count and the check that the four-T4 size does not fit. With 32.6 GB of weights it would fit, and the table of results would quietly list it as a candidate.
The script reports nine failures, prints no table, exits with code 1 and writes no CSV. One fault is counted twice in places: the duplicated row is also the first row the zero-price, per-month and savings-plan faults hit, so those checks report two rows, not one. The planted run counts 141 Linux rows where run 1 counts 169. The script holds its result tables back when a check fails, so a reader never sees numbers from a run that did not pass.
Command: run 2python3 guides/azure-gpu-cost-by-region/azure_gpu_cost_by_region.py --out loop-out/planted.csv --snapshot guides/azure-gpu-cost-by-region/azure_vm_prices_2026-10-18.csv --plant duplicate-row,zero-price,rename-sku,drop-sku,bad-unit,sp-above-price,weights-shrunk
Output from our run 2
duckdb 1.5.6, python 3.14.8 model openai/gpt-oss-120b @ b5c939de8f754692c1647ca79fbf85e8c1e70f8a license apache-2.0: 15 weight files, 65248893184 bytes (65.2 GB) PLANTED: the weights size halved, as if only half the files were summed need 32.6 GB of weights + 10 GB headroom = 42.6 GB of GPU memory PLANTED: one Linux price row loaded twice PLANTED: one Linux price set to 0 PLANTED: one NC24ads A100 v4 row renamed to a size that is not in SKUS PLANTED: every NC80adis H100 v5 row deleted PLANTED: one Linux price quoted per '1 Month' instead of '1 Hour' PLANTED: one 3-year savings-plan rate set 50% above the pay-as-you-go price prices guides/azure-gpu-cost-by-region/azure_vm_prices_2026-10-18.csv: 950 data lines, fetched 2026-10-18T14:42:48Z rows by meter class: linux pay-as-you-go 141, low priority 248, spot 268, windows 140 Linux pay-as-you-go rows from a non-primary meter region: 0 (kept: they are the only Linux row for their region and size if the checks below pass) Linux rows whose 1-year and 3-year savings-plan rates both equal the pay-as-you-go price (no discount): 2 SELF-CHECK FAILED: no CSV written - loaded 797 price rows but the file has 950 data lines (-153) - weights are 32624446592 bytes, the pinned commit has 65248893184 - rows with a size that is not in SKUS: 'Standard_NC24ads_A100_v4_x' x 1 - SKUS key 'Standard_NC80adis_H100_v5' matches no Linux pay-as-you-go row - 1 size-and-region pairs have more than one Linux pay-as-you-go row - 2 Linux rows have a price that is missing or not above zero - 2 Linux rows are not priced in USD per '1 Hour' - 3 Linux rows have a savings-plan rate above the pay-as-you-go price (or a 3-year rate above the 1-year rate) - Standard_NC64as_T4_v3 (4 x 16 GB) was expected not to fit the weights, but it does exit code 1
Run 3: the same numbers from the saved snapshot
A reader who wants to check the page does not need today’s prices. We ran the script again with --snapshot on the file run 1 saved. It reads the same 950 rows, passes the same checks and writes a CSV that is identical to run 1’s, which run 4 confirms by comparing the two files. Only the weights are fetched again, from the pinned commit. The two tables between the blocks below are left out here; they are the ones run 1 printed, from the same rows.
python3 guides/azure-gpu-cost-by-region/azure_gpu_cost_by_region.py --out loop-out/rerun.csv --snapshot guides/azure-gpu-cost-by-region/azure_vm_prices_2026-10-18.csv
Output from our run 3, the facts
duckdb 1.5.6, python 3.14.8 model openai/gpt-oss-120b @ b5c939de8f754692c1647ca79fbf85e8c1e70f8a license apache-2.0: 15 weight files, 65248893184 bytes (65.2 GB) need 65.2 GB of weights + 10 GB headroom = 75.2 GB of GPU memory prices guides/azure-gpu-cost-by-region/azure_vm_prices_2026-10-18.csv: 950 data lines, fetched 2026-10-18T14:42:48Z rows by meter class: linux pay-as-you-go 169, low priority 290, spot 322, windows 169 Linux pay-as-you-go rows from a non-primary meter region: 0 (kept: they are the only Linux row for their region and size if the checks below pass) Linux rows whose 1-year and 3-year savings-plan rates both equal the pay-as-you-go price (no discount): 2Output from our run 3, the self-check
SELF-CHECK PASSED: 950 price rows equal the 950 data lines of the file, the weights equal the pinned 65248893184 bytes, every size is mapped, every SKUS key matches a Linux row, one Linux row per size and region, every price is above zero in USD per hour, no savings-plan rate is above its price, 1 size(s) do not fit wrote loop-out/rerun.csv exit code 0Command: run 4, compare the two CSV files
python3 -c "print(open('loop-out/rerun.csv').read() == open('guides/azure-gpu-cost-by-region/azure_gpu_cost_by_region.csv').read())"
Output from our run 4
True exit code 0
What this number is, and is not, in a build-or-buy decision
The question behind most searches for this number is how much it costs to host a large model on Azure, and whether to build the platform or buy a deployed one. This page answers the first part of that and only the first part. It is the price of the machine. A running service also needs, at least:
- Storage and network. The weights are 65.2 GB and need a disk. Traffic, private networking and any egress have their own meters, which this page does not read.
- Serving software and its upkeep. Something has to load the model, batch requests, authenticate callers and log what it needs to. This page prices none of that.
- Capacity. A list price does not give you quota. Whether a one-GPU size can be had in your region, and for how long, is a question for Azure quota and your account team.
- People and support. Operating a model is staff time, on call or not.
- Idle time. At 730 hours the machine costs the same whether one person or a thousand use it. How many requests it serves a second, which decides how many people one VM can serve, was not measured here.
So the $2,681.29 to $5,362.58 range is a lower bound, among the sizes we priced, on a self-hosted bill for this model at list price, under the assumptions above. It is not an estimate of the whole of one, and it says nothing about how a deployed offering would compare.
Method
- Prices: Azure Retail Prices API,
https://prices.azure.com/api/retail/prices,api-version=2023-01-01-preview,currencyCode=USD, fetched 2026-10-18 (run 1 saved the exact fetch time in the snapshot). The API has no release to pin, so the raw rows are the pin: azure_vm_prices_2026-10-18.csv, 950 rows, each with itseffectiveStartDate. Prices are as of that date. - Model:
openai/gpt-oss-120b, Apache 2.0, commitb5c939de8f754692c1647ca79fbf85e8c1e70f8a, read from the Hugging Face API. Weights are the 15 root-levelmodel-*.safetensorsfiles, 65,248,893,184 bytes. - Rows kept:
typeConsumption, not Spot, not Low Priority,productNamenot ending in Windows: Linux pay-as-you-go, 169 rows, one per size and region. - Monthly price: hourly price × 730. Savings-plan figures are the hourly rates in the same row × 730.
- Fits: GPUs × memory per GPU ≥ weights + 10 GB (
HEADROOM_GB, our assumption). - Median: the median of the regions where the API lists a price for a size, each region counted once. It is not weighted by anything.
- No sampling, no random seed. Output is sorted by fit, GPU count, size, price and region.
- Self-checks, each with the fault that trips it: rows loaded equal the file’s data lines (a duplicated row, a deleted size); weights equal the pinned byte count (halved weights); every size is mapped (a renamed size); every mapping key matches a Linux row (a deleted size); one Linux row per size and region (a duplicated row); every price is above zero (a zero price); every price is in USD per hour (a monthly price); no savings-plan rate is above its price (an inflated rate); the four-T4 size does not fit (halved weights); the output has as many rows as the Linux price rows (a join that drops rows; no run plants this fault). The CSV is written only if all pass.
Limits
- This is not a client result. It is a list-price calculation from public data. It describes no company’s bill and does not say what anyone paid.
- List price is not what you pay. Enterprise agreements, reservations, spot prices and negotiated discounts are not in it. We did not price reservations or spot, and the savings-plan rates are only those the API lists.
- Compute only. No disks, network, egress, support, serving software or staff.
- It does not measure speed. We did not run the model. Which size serves how many requests a second, at what context length, is not on this page, so the page cannot say which size is the better buy.
- The 10 GB of headroom is an assumption. A long context or many simultaneous requests needs more. At 75.2 GB the one-A100 option is a tight fit on an 80 GB card, and a deployment that needs more headroom would need a larger size, such as the 94 GB H100 NVL size.
- One model. The weights of gpt-oss-120b set the memory test. Another 120B model in another format would need another number.
- A price is not availability. A size listed in a region may need quota you do not have. The API lists Azure Government and edge regions as it returns them; we did not filter them.
- Prices move. The numbers are as of the fetch date. The snapshot lets anyone reproduce them; it does not keep them current.
- Use of the data. The prices are Microsoft’s published retail prices, as the API returns them. The model card lists the Apache 2.0 license (gpt-oss-120b model card (read 2026-10-18)).
Tested on
Tested on
- Platform
- DuckDB 1.5.6, Python 3.12, run from Python with the
duckdbmodule - Edition
- Open-source DuckDB, in-memory
- Date
- 2026-10-18
- Dataset
- Azure Retail Prices API, Virtual Machines, six GPU sizes, 950 rows (Azure Retail Prices API documentation); openai/gpt-oss-120b at commit b5c939de, Apache 2.0 (model card)
- Code
- azure_gpu_cost_by_region.py, result azure_gpu_cost_by_region.csv, raw prices azure_vm_prices_2026-10-18.csv
When this is not enough
- You need your own price. List price is a starting point. Your Azure agreement, reservations or committed spend change it, and so does what you already run.
- You need to know the speed. Which size holds the model is one question and how many users it serves is another. That needs a load test of your model, your context length and your traffic.
- You need capacity. A price in a region is not a quota. Ask Azure.
- You need the whole bill. Add storage, network, serving software, security review and the people to run it before you compare with a deployed alternative.
Weighing build against buy?
The numbers on this page are Azure list prices for the VM alone. They leave out speed, capacity, disks, network, staff, support and serving software. They are not a total cost of running a model yourself.
If you would rather have a private AI setup deployed for you than build one, see Private AI in a Box. Its page explains what it is and what it includes.