mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-07-26 02:39:50 -05:00
Migrate compound scale suffixes
This commit is contained in:
@@ -1848,7 +1848,7 @@ The economic implications are substantial. In production settings, labeling cost
|
||||
# │ Imports: mlsysim.book (fmt)
|
||||
# │ Exports: n_unlabeled_str, cost_saving_str, speedup_str, etc.
|
||||
# └─────────────────────────────────────────────────────────────────────────────
|
||||
from mlsysim.fmt import fmt_percent, fmt, check, fmt_time
|
||||
from mlsysim.fmt import fmt_percent, fmt, check, fmt_time, fmt_count, fmt_usd
|
||||
|
||||
class ActiveLearningRoi:
|
||||
"""Medical imaging active learning: 20× speedup, $4.75M savings vs. naive labeling."""
|
||||
@@ -1876,7 +1876,7 @@ class ActiveLearningRoi:
|
||||
check(speedup_value > 1.0, "Active learning must require fewer labels than naive labeling.")
|
||||
|
||||
# ┌── 4. OUTPUT (Formatting) ─────────────────────────────────────────────
|
||||
n_unlabeled_str = fmt(n_unlabeled_value/MILLION, precision=0, suffix=" Million")
|
||||
n_unlabeled_str = fmt_count(n_unlabeled_value, scale="Million", precision=0)
|
||||
cost_per_label_str = fmt(cost_per_label_value, precision=0, commas=False)
|
||||
budget_str = fmt(budget_value, precision=0, commas=True)
|
||||
cost_all_str = fmt(cost_all_value, precision=0, commas=True)
|
||||
@@ -1886,7 +1886,7 @@ class ActiveLearningRoi:
|
||||
cost_active_str = fmt(cost_active_value, precision=0, commas=True)
|
||||
cost_active_pct_str = fmt_percent(cost_active_pct_value/100, precision=0, commas=False, style='prose')
|
||||
speedup_str = fmt(speedup_value, precision=0, commas=False)
|
||||
cost_saving_str = fmt(cost_saving_value/MILLION, precision=2, suffix=" Million")
|
||||
cost_saving_str = fmt_usd(cost_saving_value, precision=2, commas=False, scale="Million")
|
||||
over_budget_ratio_str = fmt(over_budget_ratio_value, precision=0, commas=False)
|
||||
deadline_months_str = fmt_time(deadline_months_value, 'month', precision=0, commas=False, style='word')
|
||||
```
|
||||
@@ -1908,7 +1908,7 @@ class ActiveLearningRoi:
|
||||
3. **Training speed**: With `{python} ActiveLearningRoi.speedup_str`$\times$ less data, each training epoch is `{python} ActiveLearningRoi.speedup_str`$\times$ faster.
|
||||
4. **Active learning outcome**: Empirical studies suggest that these `{python} ActiveLearningRoi.n_active_str` "high-information" samples often achieve higher accuracy than `{python} ActiveLearningRoi.n_random_str` random samples.
|
||||
|
||||
**Systems insight**: Data selection functions as a `{python} ActiveLearningRoi.speedup_str`$\times$ compute accelerator and a \$`{python} ActiveLearningRoi.cost_saving_str` cost-saving measure, delivering gains that compound with every training iteration.
|
||||
**Systems insight**: Data selection functions as a `{python} ActiveLearningRoi.speedup_str`$\times$ compute accelerator and a `{python} ActiveLearningRoi.cost_saving_str` cost-saving measure, delivering gains that compound with every training iteration.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
@@ -793,7 +793,7 @@ plt.close()
|
||||
# │ .kws_size_kb_str
|
||||
# └─────────────────────────────────────────────────────────────────────────────
|
||||
from mlsysim import Models
|
||||
from mlsysim.fmt import fmt_int, fmt, fmt_qty, check, MarkdownStr
|
||||
from mlsysim.fmt import fmt_int, fmt, fmt_qty, check, MarkdownStr, fmt_count
|
||||
|
||||
# ┌── LEGO ───────────────────────────────────────────────
|
||||
class LighthouseModels:
|
||||
@@ -822,14 +822,14 @@ class LighthouseModels:
|
||||
|
||||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||||
resnet_gflops_str = fmt_qty(m_resnet.inference_flops, GFLOPs, precision=1)
|
||||
resnet_params_m_str = fmt(resnet_params_m, precision=1, suffix=" million parameters")
|
||||
resnet_params_m_str = fmt_count(m_resnet.parameters.m_as(param), scale="million", precision=1, label="parameter")
|
||||
resnet_fp32_mb_str = fmt(resnet_fp32_mb, precision=1, suffix=" MB")
|
||||
gpt2_params_b_str = fmt(gpt2_params_b, precision=1, suffix=" billion parameters")
|
||||
gpt2_params_b_str = fmt_count(m_gpt2.parameters.m_as(param), scale="billion", precision=1, label="parameter")
|
||||
llama_range_str = MarkdownStr("7 to 70 billion parameters")
|
||||
dlrm_embedding_str = fmt(dlrm_embedding_gb, precision=0, suffix=" GB")
|
||||
mobilenet_flops_reduction_str = fmt_multiple(mobilenet_flops_reduction, precision=1)
|
||||
mobile_tdp_range_str = MarkdownStr("2 to 5 W")
|
||||
kws_params_str = fmt(kws_params, precision=0, commas=False, suffix=" K parameters")
|
||||
kws_params_str = fmt_count(m_kws.parameters.m_as(param), scale="K", precision=0, commas=False, label="parameter")
|
||||
kws_flops_m_str = fmt_qty(m_kws.inference_flops, MFLOPs, precision=0)
|
||||
kws_size_kb_str = fmt(kws_size_kb, precision=0, suffix=" KB")
|
||||
```
|
||||
@@ -1000,7 +1000,7 @@ A quantitative comparison applies this analysis to ResNet-50 inference on a data
|
||||
# │ ResnetSetup.resnet_int8_mb_str
|
||||
# └─────────────────────────────────────────────────────────────────────────────
|
||||
from mlsysim import Models
|
||||
from mlsysim.fmt import fmt, fmt_qty
|
||||
from mlsysim.fmt import fmt, fmt_qty, fmt_count
|
||||
|
||||
# ┌── LEGO ───────────────────────────────────────────────
|
||||
class ResnetSetup:
|
||||
@@ -1017,7 +1017,7 @@ class ResnetSetup:
|
||||
|
||||
# ┌── 4. OUTPUT (Formatting) ─────────────────────────────────────────────
|
||||
resnet_gflops_str = fmt_qty(m.inference_flops, GFLOPs, precision=1, commas=False)
|
||||
resnet_params_m_str = fmt(resnet_params_m_value, precision=1, commas=False, suffix=" million parameters")
|
||||
resnet_params_m_str = fmt_count(m.parameters.m_as(param), scale="million", precision=1, commas=False, label="parameter")
|
||||
resnet_fp32_mb_str = fmt_qty(resnet_fp32_bytes_value, MB, precision=1, commas=False)
|
||||
resnet_fp16_mb_str = fmt_qty(resnet_fp16_bytes_value, MB, precision=1, commas=False)
|
||||
resnet_int8_mb_str = fmt_qty(resnet_int8_bytes_value, MB, precision=1, commas=False)
|
||||
@@ -3064,7 +3064,7 @@ TinyML's milliwatt-scale power consumption represents a six-order-of-magnitude r
|
||||
# │ EnergyInference.batt_cap_mah_str, EnergyInference.batt_volt_str
|
||||
# └─────────────────────────────────────────────────────────────────────────────
|
||||
from mlsysim import Models, Hardware, Engine
|
||||
from mlsysim.fmt import fmt_int, fmt, check, MarkdownStr
|
||||
from mlsysim.fmt import fmt_int, fmt, check, MarkdownStr, fmt_count
|
||||
|
||||
# ┌── LEGO ───────────────────────────────────────────────
|
||||
class EnergyInference:
|
||||
@@ -3112,7 +3112,7 @@ class EnergyInference:
|
||||
q_resnet_cloud_str = fmt_count(round(batt_energy_j.m_as("J") / (e_resnet_cloud_mj * 1e-3)), commas=True, label="query")
|
||||
q_resnet_edge_str = fmt_count(round(batt_energy_j.m_as("J") / (e_resnet_edge_mj * 1e-3)), commas=True, label="query")
|
||||
q_mobilenet_str = fmt_count(round(batt_energy_j.m_as("J") / (e_mobilenet_mj * 1e-3)), commas=True, label="query")
|
||||
q_kws_str = fmt(q_kws.m_as('') / MILLION, precision=0, commas=False, suffix=" million queries")
|
||||
q_kws_str = fmt_count(round(q_kws.m_as('') / MILLION) * MILLION, scale="million", precision=0, commas=False, label="query")
|
||||
|
||||
batt_cap_mah_str = fmt(Scenarios.PhoneBattery.CapacityMah.m_as('mAh'), precision=0, commas=False, suffix=" mAh")
|
||||
batt_volt_str = fmt(Scenarios.PhoneBattery.VoltageV.m_as('V'), precision=1, commas=False, suffix=" V")
|
||||
|
||||
@@ -4844,7 +4844,7 @@ class LlmServingCalc:
|
||||
total_decode_s_str = fmt_qty(total_decode_s, second, precision=2, commas=False)
|
||||
kv_cache_gb_str = fmt_qty(kv_cache_reserved, GB, precision=0, commas=False)
|
||||
kv_per_token_mb_str = fmt_qty(kv_per_token, MB, precision=3, commas=False)
|
||||
kv_capacity_tokens_str = fmt(kv_capacity_tokens_value/MILLION, precision=1, commas=False, suffix=" million tokens")
|
||||
kv_capacity_tokens_str = fmt_count(round(kv_capacity_tokens_value), scale="million", precision=1, commas=False, label="token")
|
||||
tokens_per_req_str = fmt_count(total_tokens, label="token")
|
||||
concurrent_batch_str = fmt_count(concurrent_batch_value, commas=False, label="request")
|
||||
req_time_s_str = fmt_qty(req_time_s, second, precision=2, commas=False)
|
||||
@@ -4854,7 +4854,7 @@ class LlmServingCalc:
|
||||
prefill_req_per_s_str = fmt_rate(prefill_req_per_s_value, 'req/s', precision=0, commas=False)
|
||||
decode_req_capacity_str = fmt_rate(round(decode_req_capacity_value), 'req/s', precision=0, commas=False)
|
||||
hourly_cost_str = fmt_usd(hourly_cost_value, precision=0, commas=False)
|
||||
tokens_per_hour_m_str = fmt(tokens_per_hour_value/MILLION, precision=1, commas=False, suffix=" million tokens/hour")
|
||||
tokens_per_hour_m_str = fmt_rate(tokens_per_hour_value, "tokens/hour", scale="million", precision=1, commas=False)
|
||||
cost_per_m_tokens_str = fmt_usd(cost_per_m_tokens_value, precision=3, commas=False)
|
||||
remaining_vram_gb_str = fmt_int(remaining_vram_gb_value, commas=False, suffix=" GB")
|
||||
```
|
||||
|
||||
@@ -1731,7 +1731,7 @@ else:
|
||||
# │ Exports: HistoricalScale.gpt3_params_b_str,
|
||||
# │ HistoricalScale.gpt4_params_t_str
|
||||
# └─────────────────────────────────────────────────────────────────────────────
|
||||
from mlsysim.fmt import fmt_int, fmt, check
|
||||
from mlsysim.fmt import fmt_int, fmt, fmt_count, check
|
||||
|
||||
# ┌── LEGO ───────────────────────────────────────────────
|
||||
class HistoricalScale:
|
||||
@@ -4481,7 +4481,7 @@ class AdamMemoryOverhead:
|
||||
check(state_gb == 56, f"Expected 56 GB Adam state, got {state_gb}")
|
||||
|
||||
# ┌── 4. OUTPUT (Formatting) ─────────────────────────────────────────────
|
||||
params_b_str = fmt(params_b, precision=0, commas=False, suffix="-billion")
|
||||
params_b_str = fmt_count(params_b * BILLION, scale="billion", precision=0, commas=False, attributive=True)
|
||||
state_gb_str = fmt(state_gb, precision=0, commas=False, suffix=" GB")
|
||||
```
|
||||
|
||||
|
||||
@@ -490,10 +490,9 @@ class GPT2Compute:
|
||||
attn_pair_billion_str = fmt_int(flops_attn_pair/BILLION, commas=False)
|
||||
out_proj_billion_str = fmt_int(flops_out_proj/BILLION, commas=False)
|
||||
|
||||
total_per_layer_b = flops_layer_fwd/BILLION
|
||||
total_layer_str = fmt(total_per_layer_b, precision=1, commas=False, suffix=" billion FLOPs")
|
||||
total_layer_str = fmt_qty(flops_layer_fwd * flop, GFLOPs, precision=1, commas=False, unit_label="billion FLOPs")
|
||||
|
||||
per_step_t_str = fmt(flops_step_total/TRILLION, precision=1, commas=False, suffix=" trillion FLOPs")
|
||||
per_step_t_str = fmt_qty(flops_step_total * flop, TFLOPs, precision=1, commas=False, unit_label="trillion FLOPs")
|
||||
total_peta_str = fmt_qty(flops_training_total * flop, PFLOPs, precision=1, commas=False)
|
||||
v100_time_str = fmt_time(v100_time_s, 'second', precision=1, commas=False)
|
||||
|
||||
@@ -2465,7 +2464,7 @@ Applying this throughput analysis to our GPT-2 Lighthouse Model reveals where th
|
||||
# │ pcie_gen3_str, transfer_ms_str, n_cpu_workers_str,
|
||||
# │ parallel_tokenization_ms_str
|
||||
# └─────────────────────────────────────────────────────────────────────────────
|
||||
from mlsysim.fmt import fmt_int, fmt, check, fmt_time
|
||||
from mlsysim.fmt import fmt_int, fmt, check, fmt_time, fmt_count, fmt_rate
|
||||
|
||||
# ┌── LEGO ───────────────────────────────────────────────
|
||||
class GPT2DataPipeline:
|
||||
@@ -2502,8 +2501,8 @@ class GPT2DataPipeline:
|
||||
parallel_token_ms = tokenization_ms/workers
|
||||
|
||||
# ┌── 4. OUTPUT (Formatting) ──────────────────────────────────────────────
|
||||
tokens_per_batch_str = fmt_int(tokens_per_batch // THOUSAND, commas=False, suffix="K tokens")
|
||||
token_rate_str = fmt_int(token_rate // THOUSAND, commas=False, suffix="K tokens/s")
|
||||
tokens_per_batch_str = fmt_count(tokens_per_batch, scale="K", precision=1, commas=False, label="token")
|
||||
token_rate_str = fmt_rate(token_rate, "tokens/s", scale="K", precision=0, commas=False)
|
||||
tokenization_ms_str = fmt_time(tokenization_ms, 'millisecond', precision=1, commas=False)
|
||||
batch_kb_str = fmt(batch_kb, precision=1, commas=False, suffix=' KB')
|
||||
pcie_gen3_str = fmt(pcie_bw, precision=2, commas=False, suffix=' GB/s')
|
||||
|
||||
@@ -50,8 +50,41 @@ Status:
|
||||
- `git diff --check` PASS
|
||||
- `PYTHONPATH=mlsysim python3 -m pytest mlsysim/tests/test_fmt.py book/tests/test_codemod_fmt.py book/tests/test_fmt_prose_contract.py book/tests/test_audit_prose_semantics.py book/tests/test_visible_text.py -q -o addopts=''` PASS, 157 tests
|
||||
- `PYTHONPATH=mlsysim python3 book/tools/audit/fmt/fmt_prose_contract.py --root book/quarto/contents` PASS, 0 violations
|
||||
- `PYTHONPATH=mlsysim python3 book/tools/audit/fmt/codemod_fmt.py queue --root book/quarto/contents` PASS, `by kind: {}`
|
||||
- `PYTHONPATH=mlsysim python3 book/tools/audit/fmt/audit_prose_semantics.py --root book/quarto/contents` PASS, 0 findings
|
||||
- `PYTHONPATH=mlsysim python3 book/tools/audit/fmt/codemod_fmt.py queue --root book/quarto/contents` PASS, `by kind: {}`
|
||||
- `PYTHONPATH=mlsysim python3 book/tools/audit/fmt/audit_prose_semantics.py --root book/quarto/contents` PASS, 0 findings
|
||||
|
||||
## 2026-05-31 — Compound-scale suffix lane
|
||||
|
||||
Change type: typed formatter relocation with three deliberate adjudicated
|
||||
visible/value changes. Cleared all 14 `compound_scale` suffix sites:
|
||||
word-scale counts moved to direct `fmt_count(..., scale="million"|"billion")`,
|
||||
scaled count rates moved to `fmt_rate(..., scale=...)`, the `7-billion`
|
||||
modifier moved to `fmt_count(..., attributive=True)`, and word-scale FLOP prose
|
||||
now uses `fmt_qty(..., unit_label="billion FLOPs")` / `"trillion FLOPs"` so Pint
|
||||
still validates the value.
|
||||
|
||||
Touched chapters and equivalence:
|
||||
|
||||
| Chapter file | Result |
|
||||
|---|---|
|
||||
| `vol1/data_selection/data_selection.qmd` | `ActiveLearningRoi.cost_saving_str` value changed from `4.75 Million` to `\$4.75 Million`; substituted prose stayed identical because the external `\$` moved into `fmt_usd`. |
|
||||
| `vol1/ml_systems/ml_systems.qmd` | Word-scale parameter/query values stayed identical except the user-approved compact scale style changed `200 K parameters` to `200K parameters`; surrounding sentence was read and remains correct. |
|
||||
| `vol1/model_serving/model_serving.qmd` | `2.2 million tokens` and `45.2 million tokens/hour` stayed byte-identical. |
|
||||
| `vol1/nn_computation/nn_computation.qmd` | `7-billion` stayed byte-identical through `fmt_count(..., attributive=True)`. |
|
||||
| `vol1/training/training.qmd` | FLOP phrases stayed byte-identical; `32K tokens` changed to `32.8K tokens` because the exact batch count is 32,768 and `fmt_count` correctly refused to hide that at precision 0. |
|
||||
|
||||
Validation details:
|
||||
|
||||
- `audit_fmt_usage.py` reports no remaining `compound_scale` suffix bucket;
|
||||
remaining suffix bucket is `physical_unit` 1,126.
|
||||
- `assess_equiv.py baseline --ref HEAD` / `snapshot` checked touched chapters:
|
||||
`data_selection` 250 values + 128 prose lines; `ml_systems` 296 + 143;
|
||||
`model_serving` 365 + 172; `nn_computation` 200 + 107; `training` 453 + 214.
|
||||
- Verification: `git diff --check` PASS; `python3 -m py_compile
|
||||
mlsysim/mlsysim/fmt.py book/tools/audit/fmt/audit_fmt_usage.py` PASS;
|
||||
focused pytest suite PASS, 182 tests; `fmt_prose_contract.py` PASS, 0
|
||||
violations; `codemod_fmt.py queue` PASS, `by kind: {}`; `./book/binder check
|
||||
math` PASS; `audit_prose_semantics.py` PASS, 0 findings across 81 files.
|
||||
|
||||
## 2026-05-31 — Currency denominator relocation
|
||||
|
||||
|
||||
@@ -258,16 +258,14 @@ across 132 value exports and 75 prose lines. `fmt_semantic_suffix` and its test
|
||||
now flag `suffix=" pp"` as `pp_in_suffix`; `audit_fmt_usage.py` now classifies
|
||||
percentage-point suffixes separately and reports `fmt_pp` calls at 21.
|
||||
|
||||
**A19 — Scale-word and compound-scale formatter design: TODO.**
|
||||
Audit still reports 8 exact `scale_word` suffixes and grep finds compound
|
||||
suffixes such as `"million parameters"`, `"million queries"`,
|
||||
`"million tokens/hour"`, and `"billion FLOPs"`. Do not blindly convert these
|
||||
to `M`/`B` glyph style because word-scale prose may be intentional. Recommended
|
||||
next design: add a word-scale mode to `fmt_count`, a structured counted-rate
|
||||
path for tokens/hour, and either `fmt_qty`/Pint or a thin `fmt_ops` wrapper for
|
||||
FLOP-count prose. Then update `audit_fmt_usage.py` and `fmt_semantic_suffix` so
|
||||
compound scale suffixes do not fall through as `physical_unit`; add tests for
|
||||
word-scale output and checker coverage.
|
||||
**A19 — Scale-word and compound-scale formatter design: DONE.**
|
||||
`fmt_count` now accepts direct word scales such as `scale="million"` /
|
||||
`scale="billion"` while preserving compact glyph scales such as `scale="M"` /
|
||||
`scale="B"`. `fmt_rate` now accepts the same checked scale argument for
|
||||
counted rates such as `tokens/hour`, and word-scale FLOP prose uses
|
||||
`fmt_qty(..., unit_label="billion FLOPs")` to preserve visible wording while
|
||||
still converting through Pint. `fmt_count(..., scale_style="word")` remains for
|
||||
compatibility, but new QMD should prefer the clearer direct word-scale spelling.
|
||||
|
||||
**A20 — ML ops time suffixes: DONE.**
|
||||
All 19 `time_unit` suffix sites in `vol1/ml_ops/ml_ops.qmd` moved to
|
||||
@@ -357,7 +355,7 @@ pytest suite PASS (174 tests); `fmt_prose_contract.py` 0; `codemod_fmt.py
|
||||
queue` `by kind: {}`; `./book/binder check math` PASS;
|
||||
`audit_prose_semantics.py` CLEAN across 81 files.
|
||||
|
||||
**A27 — Exact scale-word suffix lane: DONE, API naming still open.**
|
||||
**A27 — Exact scale-word suffix lane: DONE.**
|
||||
Added `fmt_count(..., scale_style="word")` and migrated the 8 exact
|
||||
`suffix=" million"` / `suffix=" billion"` QMD sites to typed count formatting:
|
||||
`data_selection` (1), `introduction` (2), `ml_ops` (1), `training` (1),
|
||||
@@ -371,9 +369,9 @@ wrapped keyword arguments, after pre-commit exposed the false positive on
|
||||
`scale_style="word"`. Verification: `git diff --check` PASS; py_compile PASS;
|
||||
focused pytest suite PASS (176 tests); `fmt_prose_contract.py` 0;
|
||||
`codemod_fmt.py queue` `by kind: {}`; `./book/binder check math` PASS;
|
||||
`audit_prose_semantics.py` CLEAN across 81 files. User flagged
|
||||
`scale="B", scale_style="word"` as unclear; discuss a clearer public spelling
|
||||
such as `scale="billion"` before final API lock.
|
||||
`audit_prose_semantics.py` CLEAN across 81 files. The later compound-scale lane
|
||||
resolved the source spelling concern: new word-scale QMD should use
|
||||
`scale="billion"` rather than `scale="B", scale_style="word"`.
|
||||
|
||||
**A28 — Residual plain count-label suffix lane: DONE.**
|
||||
Migrated 17 remaining plain count-noun suffixes (`errors`, `steps`, `photos`,
|
||||
@@ -491,13 +489,30 @@ Values and substituted prose were byte-identical across `hw_acceleration`,
|
||||
`introduction`, `ml_systems`, `ml_workflow`, `nn_computation`, `training`, and
|
||||
`edge_intelligence`. `audit_fmt_usage.py` now reports no
|
||||
`unit_rate_or_denominator` suffix bucket and `fmt_qty` at 291. Remaining suffix
|
||||
buckets: `physical_unit` 1,126 and `compound_scale` 14. Verification: `git diff
|
||||
buckets before A37: `physical_unit` 1,126 and `compound_scale` 14. Verification: `git diff
|
||||
--check` PASS; py_compile PASS; focused pytest suite PASS (181 tests);
|
||||
`fmt_prose_contract.py` 0; `codemod_fmt.py queue` `by kind: {}`;
|
||||
`./book/binder check math` PASS; `audit_prose_semantics.py` CLEAN across 81
|
||||
files.
|
||||
|
||||
### B. WS4 — unit-suffix lane (~2,393 sites: `GB`/`ms`/`W`/`GB/s`/…) ← the big one
|
||||
**A37 — Compound-scale suffix lane: DONE.**
|
||||
Cleared all 14 `compound_scale` suffix sites. `fmt_count` now supports direct
|
||||
word scales (`scale="million"`, `scale="billion"`) plus
|
||||
`attributive=True` for `7-billion` noun modifiers; `fmt_rate` supports checked
|
||||
scaled count rates including `tokens/hour`; and word-scale FLOP phrases moved
|
||||
to `fmt_qty(..., unit_label="billion FLOPs")` / `"trillion FLOPs"` so Pint still
|
||||
dimension-checks the value. Most values were byte-identical. Intentional
|
||||
adjudicated changes: `cost_saving_str` now owns the escaped dollar sign through
|
||||
`fmt_usd` while substituted prose stayed identical; `200 K parameters` became
|
||||
the user-approved compact `200K parameters`; and the old floored
|
||||
`32K tokens` display for 32,768 tokens now renders as `32.8K tokens`.
|
||||
`audit_fmt_usage.py` now reports no `compound_scale` suffix bucket. Remaining
|
||||
suffix bucket: `physical_unit` 1,126. Verification: `git diff --check` PASS;
|
||||
py_compile PASS; focused pytest suite PASS (182 tests); `fmt_prose_contract.py`
|
||||
0; `codemod_fmt.py queue` `by kind: {}`; `./book/binder check math` PASS;
|
||||
`audit_prose_semantics.py` CLEAN across 81 files.
|
||||
|
||||
### B. WS4 — unit-suffix lane (remaining 1,126 physical-unit suffixes: `GB`/`MB`/`W`/`GB/s`/…) ← the big one
|
||||
**Risk: LOW** (a unit label can't cause a 0–1↔0–100 / 100× error). **Effort: HIGH**
|
||||
and NOT a clean codemod, because ~1,938 of the args are plain floats (e.g.
|
||||
`weights_gb`), not Pint Quantities, and `fmt_qty` requires a Pint Quantity to
|
||||
|
||||
@@ -37,13 +37,13 @@ python3 -m pytest mlsysim/tests/test_fmt.py book/tests/test_codemod_fmt.py book/
|
||||
|
||||
## NOW / NEXT (update before every commit)
|
||||
|
||||
**STATUS: Time-unit suffix migration and exact scale-word suffix migration complete; remaining suffix work is WS4 physical units, compound scale/rate/ops design, MarkdownStr review, render/PDF audit, API clarity, and final lock.**
|
||||
**STATUS: Compound-scale suffix migration complete; remaining suffix work is WS4 physical units, MarkdownStr review, render/PDF audit, API cleanup, and final lock.**
|
||||
|
||||
**State:** multiplier + percent + scale 100% migrated; pp → typed fmt_pp (14
|
||||
byte-identical sites + grammar fixes, plus the user-approved A2 benchmarking
|
||||
edits); 4 dangerous glyph stragglers killed; NEW semantic scanner gate (+ unit-dup
|
||||
bug fixes). 81/81 chapters exec clean; prose-contract 0; semantic scanner 0;
|
||||
codemod queue empty; 176 focused tests pass. User ruled for no-space scaled
|
||||
codemod queue empty; 182 focused tests pass. User ruled for no-space scaled
|
||||
counts, so `run_scale_style_lane.py` migrated the 44 queued scale sites to
|
||||
`fmt_count` and one manual `fmt(...) + "B"` blind spot. User also approved A2, so
|
||||
`benchmarking` now renders `0.9 percentage-point drop`, `below 1
|
||||
@@ -75,12 +75,11 @@ HTML-render-verified for `benchmarking`. The time-unit lane is now complete:
|
||||
`run_time_lane.py` migrated the remaining 522 exact `time_unit` suffix sites to
|
||||
`fmt_time(...)`, QMD `fmt_time` calls now use full unit-name strings in source,
|
||||
and `audit_fmt_usage.py` reports no remaining `time_unit` suffix bucket.
|
||||
The exact scale-word lane is also complete: `fmt_count` now supports word-scale
|
||||
output, the 8 exact `suffix=" million"` / `suffix=" billion"` QMD sites were
|
||||
migrated byte-identically, and `audit_fmt_usage.py` reports no `scale_word`
|
||||
suffix bucket. User flagged `fmt_count(..., scale="B", scale_style="word")` as
|
||||
unclear source spelling; `PLAN_OF_RECORD.md` records the API-clarity follow-up
|
||||
to discuss a cleaner spelling such as `scale="billion"` before final lock.
|
||||
The exact scale-word lane is also complete, and the later compound-scale lane
|
||||
resolved the API spelling: compact scales use `scale="B"` / `scale="M"` while
|
||||
word scales use `scale="billion"` / `scale="million"`. The older
|
||||
`scale_style="word"` form remains compatible but should not be added in new
|
||||
QMD edits.
|
||||
Residual plain count labels are also reduced: 17 suffixes such as `errors`,
|
||||
`steps`, `photos`, `requests`, `servers`, `workers`, `stages`, `V100s`, and
|
||||
`link tiers` now use `fmt_count(..., label=...)`, byte-identical across 9
|
||||
@@ -88,7 +87,7 @@ chapters.
|
||||
The time codemod now also recognizes word-form `microseconds` and
|
||||
`milliseconds`; the three remaining exact word-form time suffixes moved to
|
||||
`fmt_time(..., style="word")` byte-identically.
|
||||
`audit_fmt_usage.py` now splits the remaining suffix inventory into actionable
|
||||
`audit_fmt_usage.py` split the remaining suffix inventory into actionable
|
||||
sub-buckets: physical units (1,126), resource-time labels (19), unit
|
||||
rates/denominators (16), compound scale (14), operation counts (12), and
|
||||
time compounds (8). One more plain count label (`epochs`) was moved to
|
||||
@@ -107,18 +106,21 @@ Exact FLOP-count suffixes are also migrated: 12 `GFLOPs`/`MFLOPs`/`KFLOPs`/
|
||||
`PFLOPs` sites now use `fmt_qty(...)` with Pint FLOP units, byte-identical
|
||||
across 5 chapters. No separate `fmt_ops` wrapper was added because Pint already
|
||||
provides the unit check; word-scale FLOP phrases (`billion FLOPs`, `trillion
|
||||
FLOPs`) remain in `compound_scale` pending wording/API decisions.
|
||||
FLOPs`) now use `fmt_qty(..., unit_label=...)` so visible wording stays intact.
|
||||
The four `time_compound` suffixes are gone too: `ms latency` / `ms round-trip`
|
||||
now keep the unit in `fmt_time(...)`, and `ms+` uses checked
|
||||
`fmt_time(..., marker="+")`. The 16 `unit_rate_or_denominator` suffixes are now
|
||||
also migrated through `fmt_qty(...)`, using checked `unit_label=` where Pint's
|
||||
compact label did not match visible house style. Remaining small suffix bucket
|
||||
is `compound_scale` (14), plus 1,126 `physical_unit` suffixes. Remaining: the
|
||||
rest of WS4/WS3 and later PDF/lock phases. Nothing is half-done or broken.
|
||||
compact label did not match visible house style. Compound-scale suffixes are now
|
||||
gone too: direct word scales, checked scaled rates, attributive count modifiers,
|
||||
and `fmt_qty(unit_label=...)` cleared all 14 sites. Intentional visible fixes:
|
||||
`200 K parameters`→`200K parameters` and floored `32K tokens`→`32.8K tokens`
|
||||
for the exact 32,768-token batch. Remaining suffix bucket is only 1,126
|
||||
`physical_unit` suffixes. Remaining: the rest of WS4/WS3 and later PDF/lock
|
||||
phases. Nothing is half-done or broken.
|
||||
|
||||
**If continuing:** Continue with semantic lanes in `PLAN_OF_RECORD.md`. Good next
|
||||
targets are compound scale suffixes, then physical-unit suffixes, MarkdownStr
|
||||
sites, and the API-clarity pass.
|
||||
targets are physical-unit suffixes, MarkdownStr sites, and the API-cleanup pass.
|
||||
For physical Quantity-backed sites, continue WS4 with
|
||||
`PYTHONPATH=mlsysim python3 book/tools/audit/fmt/run_unit_lane.py --write <qmd>`
|
||||
one chapter at a time. The latest all-chapter run migrated the remaining
|
||||
@@ -520,6 +522,16 @@ drop. The adjacent prose/table text was updated to `1 percentage-point threshold
|
||||
and `(drop of 6.8 percentage points)`. No user-decision items remain open.
|
||||
|
||||
## Session commit log (newest first)
|
||||
- Codex compound-scale lane: cleared all 14 `compound_scale` suffix sites by
|
||||
adding direct word-scale support (`scale="million"` / `"billion"`),
|
||||
checked scaled `fmt_rate(..., scale=...)`, and `fmt_count(...,
|
||||
attributive=True)` for `7-billion` modifiers; word-scale FLOP phrases use
|
||||
`fmt_qty(..., unit_label=...)`. Intentional adjudicated changes:
|
||||
`200 K parameters`→`200K parameters`, `32K tokens`→`32.8K tokens`, and
|
||||
`cost_saving_str` now owns `\$` through `fmt_usd` while substituted prose is
|
||||
unchanged. No `compound_scale` suffix bucket remains; remaining suffix bucket
|
||||
is only `physical_unit` 1,126; contract 0, semantic 0, queue empty, targeted
|
||||
suite 182 passing.
|
||||
- Codex unit-rate/denominator lane: added checked `fmt_qty(unit_label=...)` and
|
||||
migrated all 16 `TFLOP/s per W`, `kg/kWh`, `MWh/household-year`,
|
||||
`FLOP/byte`, `GB/day`, `GB per day`, `MB/photo`, `KB/patient`, `MWh/year`,
|
||||
|
||||
@@ -81,32 +81,26 @@ For compact lower-bound notation that is intentionally written as a trailing
|
||||
plus, use checked `fmt_time(..., marker="+")` so the plus cannot become an
|
||||
unvalidated suffix.
|
||||
|
||||
### Scale-word and compound-scale backlog
|
||||
### Scale-word and compound-scale status
|
||||
|
||||
The audit still has a small `scale_word` bucket plus compound suffix blind
|
||||
spots such as `"million parameters"`, `"million queries"`, `"million tokens/hour"`,
|
||||
and `"billion FLOPs"`. Do not blindly convert these to `M`/`B` glyph style:
|
||||
phrases like "60 million parameters" may be intentional prose.
|
||||
The exact `scale_word` and `compound_scale` suffix buckets are migrated. The
|
||||
public spelling is now direct:
|
||||
|
||||
Recommended implementation:
|
||||
- `fmt_count(n, scale="B")` renders compact glyph style, e.g. `70B`.
|
||||
- `fmt_count(n, scale="billion", label="parameter")` renders word style, e.g.
|
||||
`70 billion parameters`.
|
||||
- `fmt_count(n, scale="billion", attributive=True)` renders hyphenated noun
|
||||
modifiers, e.g. `7-billion`.
|
||||
- `fmt_rate(tokens_per_hour, "tokens/hour", scale="million")` renders checked
|
||||
counted rates such as `45.2 million tokens/hour`.
|
||||
- Word-scale FLOP prose keeps Pint validation through
|
||||
`fmt_qty(flops * flop, GFLOPs, unit_label="billion FLOPs")`.
|
||||
|
||||
- extend `fmt_count` with a word-scale mode, e.g.
|
||||
`fmt_count(n, scale="M", scale_style="word", label="parameter")` renders
|
||||
`60 million parameters`;
|
||||
- add a structured rate form for word-scale counted rates such as tokens/hour;
|
||||
- decide whether FLOP-count prose should be `fmt_qty` with Pint FLOP units or a
|
||||
thin `fmt_ops` wrapper;
|
||||
- teach `audit_fmt_usage.py` and `fmt_semantic_suffix` to classify compound
|
||||
scale suffixes instead of falling through to `physical_unit`;
|
||||
- add tests for word-scale output, label pluralization, rate denominators,
|
||||
FLOP handling, and checker coverage for exact and compound scale suffixes.
|
||||
|
||||
Status: the exact `scale_word` bucket (`suffix=" million"` /
|
||||
`suffix=" billion"`) is migrated through `fmt_count(..., scale_style="word")`.
|
||||
Before the final API lock, revisit the source readability of that spelling.
|
||||
The user flagged calls such as `scale="B", scale_style="word"` as unclear; the
|
||||
candidate cleaner API is to let `scale="billion"` render the word form, while
|
||||
`scale="B"` keeps the compact glyph form.
|
||||
The old `fmt_count(..., scale="B", scale_style="word")` compatibility path
|
||||
still works, but new QMD should prefer `scale="billion"` because it is clearer
|
||||
at the call site. The compound migration also found one correctness fix:
|
||||
`32K tokens` from `32 * 1024` hid 768 tokens, so it now renders as
|
||||
`32.8K tokens`.
|
||||
|
||||
## 4. Formatter defaults
|
||||
|
||||
@@ -169,9 +163,9 @@ FLOP counts are already Pint quantities in this repo (`flop`, `KFLOPs`,
|
||||
`MFLOPs`, `GFLOPs`, `PFLOPs`, ...), so exact FLOP-count suffixes should use
|
||||
`fmt_qty(quantity, FLOP_unit)`. Do not add a separate `fmt_ops` wrapper unless a
|
||||
later audit shows repeated call-site mistakes that the wrapper would prevent.
|
||||
Word-scale phrases such as `billion FLOPs` and `trillion FLOPs` are a separate
|
||||
compound-scale/prose decision because converting them to `GFLOPs`/`TFLOPs`
|
||||
changes visible wording.
|
||||
Word-scale phrases such as `billion FLOPs` and `trillion FLOPs` keep their
|
||||
visible wording with `fmt_qty(..., unit_label="billion FLOPs")` after converting
|
||||
through the corresponding Pint FLOP unit.
|
||||
|
||||
When Pint's compact label does not match the book's house style, use
|
||||
`fmt_qty(..., unit_label="...")` rather than falling back to `suffix=`. The
|
||||
|
||||
+98
-43
@@ -169,11 +169,57 @@ _COUNT_LABEL_DENYLIST = {
|
||||
"GB/s", "MB/s", "TB/s", "Gb/s", "TFLOP/s", "PFLOP/s",
|
||||
"W", "kW", "MW", "J", "mJ", "Wh", "kWh", "MWh",
|
||||
"ms", "s", "min", "h", "ns", "us", "µs", "μs",
|
||||
"QPS", "FPS", "tokens/s", "img/s", "images/s", "req/s", "samples/s",
|
||||
"QPS", "FPS", "tokens/s", "tokens/hour", "img/s", "images/s", "req/s",
|
||||
"samples/s",
|
||||
"percent", "percentage point", "percentage points",
|
||||
}
|
||||
|
||||
|
||||
_DECIMAL_SCALE_FACTORS = {"K": 1e3, "M": 1e6, "B": 1e9, "T": 1e12}
|
||||
_DECIMAL_SCALE_WORDS = {
|
||||
"K": "thousand",
|
||||
"M": "million",
|
||||
"B": "billion",
|
||||
"T": "trillion",
|
||||
}
|
||||
_DECIMAL_SCALE_SYMBOL_BY_WORD = {
|
||||
word: symbol for symbol, word in _DECIMAL_SCALE_WORDS.items()
|
||||
}
|
||||
|
||||
|
||||
def _resolve_decimal_scale(scale, *, what, style="symbol", attributive=False):
|
||||
"""Return ``(divisor, suffix)`` for checked K/M/B/T decimal scales."""
|
||||
if style not in {"symbol", "word"}:
|
||||
raise ValueError(
|
||||
f"{what} style must be 'symbol' or 'word', got {style!r}."
|
||||
)
|
||||
if scale is None:
|
||||
if attributive:
|
||||
raise ValueError(f"{what} attributive=True requires scale=.")
|
||||
return 1, ""
|
||||
if not isinstance(scale, str):
|
||||
raise TypeError(
|
||||
f"{what} must be a string or None, got {type(scale).__name__}."
|
||||
)
|
||||
if scale in _DECIMAL_SCALE_FACTORS:
|
||||
if attributive and style != "word":
|
||||
raise ValueError(
|
||||
f"{what} attributive=True requires a word scale, got {scale!r}."
|
||||
)
|
||||
divisor = _DECIMAL_SCALE_FACTORS[scale]
|
||||
if style == "word":
|
||||
joiner = "-" if attributive else " "
|
||||
return divisor, f"{joiner}{_DECIMAL_SCALE_WORDS[scale]}"
|
||||
return divisor, scale
|
||||
word_key = scale.lower()
|
||||
if word_key in _DECIMAL_SCALE_SYMBOL_BY_WORD:
|
||||
symbol = _DECIMAL_SCALE_SYMBOL_BY_WORD[word_key]
|
||||
joiner = "-" if attributive else " "
|
||||
return _DECIMAL_SCALE_FACTORS[symbol], f"{joiner}{scale}"
|
||||
allowed = sorted(_DECIMAL_SCALE_FACTORS) + sorted(_DECIMAL_SCALE_SYMBOL_BY_WORD)
|
||||
raise ValueError(f"{what} must be one of {allowed}, got {scale!r}.")
|
||||
|
||||
|
||||
def _validate_count_label(label, *, what="label") -> str:
|
||||
"""Validate a count noun label before pluralization."""
|
||||
label = _clean_text_atom(label, what=what)
|
||||
@@ -342,7 +388,6 @@ def fmt_int(
|
||||
)
|
||||
|
||||
|
||||
_USD_SCALES = {"K": 1e3, "M": 1e6, "B": 1e9, "T": 1e12}
|
||||
_USD_MARKERS = {"*"}
|
||||
|
||||
|
||||
@@ -378,6 +423,8 @@ def fmt_usd(
|
||||
per="year") # "~\\$1,234/year"
|
||||
fmt_usd(gpt3_cost, precision=1,
|
||||
scale="M") # "\\$4.6M"
|
||||
fmt_usd(4_750_000, precision=2,
|
||||
scale="million") # "\\$4.75 million"
|
||||
fmt_usd(rate_per_gb, precision=2,
|
||||
commas=False, per="GB") # "\\$0.09/GB"
|
||||
|
||||
@@ -392,9 +439,10 @@ def fmt_usd(
|
||||
commas: Thousands separators (default ``True`` — currency usually
|
||||
groups: ``\\$15,000``). Pass ``False`` for small rates.
|
||||
approx: When ``True``, prepend ``~`` before the dollar sign.
|
||||
scale: Optional currency scale glyph (``"K"``, ``"M"``, ``"B"``,
|
||||
``"T"``). The raw dollar amount is divided by the scale here, so
|
||||
the magnitude and display glyph cannot drift apart.
|
||||
scale: Optional currency scale (``"K"``, ``"M"``, ``"B"``, ``"T"`` or
|
||||
word forms such as ``"million"``). The raw dollar amount is divided
|
||||
by the scale here, so the magnitude and display suffix cannot drift
|
||||
apart.
|
||||
per: Optional rate denominator (``"month"``, ``"GB"``, ``"kWh"``, or a
|
||||
Pint unit such as ``GB``). Pass without a leading slash.
|
||||
marker: Optional checked table marker appended after the currency value.
|
||||
@@ -417,13 +465,12 @@ def fmt_usd(
|
||||
raise ValueError("Use suffix= or structured scale=/per=/marker=, not both.")
|
||||
structured_suffix = ""
|
||||
if scale is not None:
|
||||
if scale not in _USD_SCALES:
|
||||
raise ValueError(
|
||||
f"fmt_usd scale must be one of {sorted(_USD_SCALES)}, "
|
||||
f"got {scale!r}."
|
||||
)
|
||||
amount = amount / _USD_SCALES[scale]
|
||||
structured_suffix += scale
|
||||
divisor, scale_suffix = _resolve_decimal_scale(
|
||||
scale,
|
||||
what="fmt_usd scale",
|
||||
)
|
||||
amount = amount / divisor
|
||||
structured_suffix += scale_suffix
|
||||
structured_suffix += _denominator_suffix(
|
||||
per,
|
||||
what="fmt_usd per",
|
||||
@@ -616,15 +663,6 @@ def fmt_multiple(factor, precision=1, commas=False):
|
||||
return fmt(v, precision=precision, commas=commas)
|
||||
|
||||
|
||||
_COUNT_SCALES = {"K": 1e3, "M": 1e6, "B": 1e9, "T": 1e12}
|
||||
_COUNT_SCALE_WORDS = {
|
||||
"K": "thousand",
|
||||
"M": "million",
|
||||
"B": "billion",
|
||||
"T": "trillion",
|
||||
}
|
||||
|
||||
|
||||
def fmt_count(
|
||||
value,
|
||||
scale=None,
|
||||
@@ -637,6 +675,7 @@ def fmt_count(
|
||||
lower_bound=False,
|
||||
allow_fractional=False,
|
||||
scale_style="symbol",
|
||||
attributive=False,
|
||||
):
|
||||
"""
|
||||
Format a **count** (a dimensionless tally of things), optionally with a
|
||||
@@ -650,7 +689,11 @@ def fmt_count(
|
||||
fmt_count(5_300_000, scale="M", precision=1) # "5.3M"
|
||||
fmt_count(5_300_000, scale="M",
|
||||
scale_style="word", precision=1) # "5.3 million"
|
||||
fmt_count(5_300_000, scale="million",
|
||||
precision=1) # "5.3 million"
|
||||
fmt_count(70e9, scale="B") # "70B" (e.g. params)
|
||||
fmt_count(7e9, scale="billion",
|
||||
attributive=True) # "7-billion"
|
||||
fmt_count(8192) # "8,192" (no scale)
|
||||
fmt_count(1024, label="GPU") # "1,024 GPUs"
|
||||
fmt_count(1024, label="GPU", approx=True) # "~1,024 GPUs"
|
||||
@@ -667,7 +710,7 @@ def fmt_count(
|
||||
``fmt_qty``. ``fmt_count`` is for pure tallies only.
|
||||
"""
|
||||
raw_v = _numeric_magnitude(value)
|
||||
require_integer = label is not None or plural_label is not None
|
||||
require_integer = label is not None or plural_label is not None or attributive
|
||||
_validate_count_value(
|
||||
raw_v,
|
||||
allow_fractional=allow_fractional,
|
||||
@@ -675,6 +718,11 @@ def fmt_count(
|
||||
)
|
||||
if suffix and (label is not None or plural_label is not None):
|
||||
raise ValueError("Use suffix= or structured label=, not both.")
|
||||
if attributive and (suffix or label is not None or plural_label is not None):
|
||||
raise ValueError(
|
||||
"fmt_count attributive=True formats only the scaled modifier; "
|
||||
"put the count noun in prose."
|
||||
)
|
||||
if scale_style not in {"symbol", "word"}:
|
||||
raise ValueError(
|
||||
"fmt_count scale_style must be 'symbol' or 'word', "
|
||||
@@ -682,19 +730,18 @@ def fmt_count(
|
||||
)
|
||||
if scale is None and scale_style != "symbol":
|
||||
raise ValueError("fmt_count scale_style='word' requires scale=.")
|
||||
if scale is None and attributive:
|
||||
raise ValueError("fmt_count attributive=True requires scale=.")
|
||||
v = raw_v
|
||||
glyph = ""
|
||||
if scale is not None:
|
||||
if scale not in _COUNT_SCALES:
|
||||
raise ValueError(
|
||||
f"fmt_count scale must be one of {sorted(_COUNT_SCALES)} or "
|
||||
f"None, got {scale!r}."
|
||||
)
|
||||
v = v / _COUNT_SCALES[scale]
|
||||
if scale_style == "word":
|
||||
glyph = f" {_COUNT_SCALE_WORDS[scale]}"
|
||||
else:
|
||||
glyph = scale
|
||||
divisor, glyph = _resolve_decimal_scale(
|
||||
scale,
|
||||
what="fmt_count scale",
|
||||
style=scale_style,
|
||||
attributive=attributive,
|
||||
)
|
||||
v = v / divisor
|
||||
suffix = suffix or _label_suffix(raw_v, label, plural_label)
|
||||
return fmt(
|
||||
v,
|
||||
@@ -707,7 +754,8 @@ def fmt_count(
|
||||
|
||||
|
||||
_RATE_UNITS = {
|
||||
"QPS", "FPS", "tokens/s", "img/s", "images/s", "req/s", "samples/s",
|
||||
"QPS", "FPS", "tokens/s", "tokens/hour", "img/s", "images/s", "req/s",
|
||||
"samples/s",
|
||||
}
|
||||
|
||||
|
||||
@@ -717,6 +765,7 @@ def fmt_rate(
|
||||
*,
|
||||
precision=0,
|
||||
commas=True,
|
||||
scale=None,
|
||||
approx=False,
|
||||
lower_bound=False,
|
||||
allow_negative=False,
|
||||
@@ -725,7 +774,8 @@ def fmt_rate(
|
||||
|
||||
Physical rates (``GB/s``, ``TFLOP/s``, ``W``) remain ``fmt_qty`` values.
|
||||
This helper is for named service/data rates whose numerator is a counted
|
||||
event rather than a Pint physical unit.
|
||||
event rather than a Pint physical unit. ``scale=`` accepts the same checked
|
||||
decimal scales as ``fmt_count``.
|
||||
"""
|
||||
unit = _clean_text_atom(unit, what="rate unit")
|
||||
if unit not in _RATE_UNITS:
|
||||
@@ -738,11 +788,18 @@ def fmt_rate(
|
||||
f"fmt_rate expects a non-negative rate, got {v}. Pass "
|
||||
f"allow_negative=True if a signed rate is genuinely intended."
|
||||
)
|
||||
scale_suffix = ""
|
||||
if scale is not None:
|
||||
divisor, scale_suffix = _resolve_decimal_scale(
|
||||
scale,
|
||||
what="fmt_rate scale",
|
||||
)
|
||||
v = v / divisor
|
||||
return fmt(
|
||||
v,
|
||||
precision=precision,
|
||||
commas=commas,
|
||||
suffix=f" {unit}",
|
||||
suffix=f"{scale_suffix} {unit}",
|
||||
approx=approx,
|
||||
lower_bound=lower_bound,
|
||||
)
|
||||
@@ -920,14 +977,12 @@ def fmt_count_range(
|
||||
hi_count = hi_raw
|
||||
glyph = ""
|
||||
if scale is not None:
|
||||
if scale not in _COUNT_SCALES:
|
||||
raise ValueError(
|
||||
f"fmt_count_range scale must be one of {sorted(_COUNT_SCALES)} "
|
||||
f"or None, got {scale!r}."
|
||||
)
|
||||
lo_raw = lo_raw / _COUNT_SCALES[scale]
|
||||
hi_raw = hi_raw / _COUNT_SCALES[scale]
|
||||
glyph = scale
|
||||
divisor, glyph = _resolve_decimal_scale(
|
||||
scale,
|
||||
what="fmt_count_range scale",
|
||||
)
|
||||
lo_raw = lo_raw / divisor
|
||||
hi_raw = hi_raw / divisor
|
||||
a = fmt(lo_raw, precision=precision, commas=commas)
|
||||
b = fmt(hi_raw, precision=precision, commas=commas)
|
||||
suffix = ""
|
||||
|
||||
@@ -177,6 +177,14 @@ class TestFmtUsd:
|
||||
|
||||
def test_structured_scale_and_denominator(self):
|
||||
assert fmt_usd(4_600_000, precision=1, commas=False, scale="M") == "\\$4.6M"
|
||||
assert (
|
||||
fmt_usd(4_750_000, precision=2, commas=False, scale="million")
|
||||
== "\\$4.75 million"
|
||||
)
|
||||
assert (
|
||||
fmt_usd(4_750_000, precision=2, commas=False, scale="Million")
|
||||
== "\\$4.75 Million"
|
||||
)
|
||||
assert fmt_usd(0.09, precision=2, commas=False, per="GB") == "\\$0.09/GB"
|
||||
assert fmt_usd(12_000, commas=False, scale="K", per="year") == "\\$12K/year"
|
||||
assert fmt_usd(8000, approx=True, marker="*") == "~\\$8,000*"
|
||||
@@ -220,6 +228,20 @@ class TestFmtRate:
|
||||
def test_formats_allowlisted_service_rates(self):
|
||||
assert fmt_rate(2500, "QPS") == "2,500 QPS"
|
||||
assert fmt_rate(1200, "tokens/s") == "1,200 tokens/s"
|
||||
assert (
|
||||
fmt_rate(500_000, "tokens/s", scale="K", commas=False)
|
||||
== "500K tokens/s"
|
||||
)
|
||||
assert (
|
||||
fmt_rate(
|
||||
45_200_000,
|
||||
"tokens/hour",
|
||||
scale="million",
|
||||
precision=1,
|
||||
commas=False,
|
||||
)
|
||||
== "45.2 million tokens/hour"
|
||||
)
|
||||
assert fmt_rate(2500, "QPS", commas=False) == "2500 QPS"
|
||||
assert fmt_rate(60, "FPS") == "60 FPS"
|
||||
|
||||
@@ -489,11 +511,12 @@ class TestFmtCount:
|
||||
|
||||
def test_scale_words(self):
|
||||
assert fmt_count(1_000_000, scale="M", scale_style="word") == "1 million"
|
||||
assert fmt_count(1_000_000, scale="million") == "1 million"
|
||||
assert fmt_count(1_000_000, scale="Million") == "1 Million"
|
||||
assert (
|
||||
fmt_count(
|
||||
60_000_000,
|
||||
scale="M",
|
||||
scale_style="word",
|
||||
scale="million",
|
||||
label="parameter",
|
||||
)
|
||||
== "60 million parameters"
|
||||
@@ -501,8 +524,7 @@ class TestFmtCount:
|
||||
assert (
|
||||
fmt_count(
|
||||
70_000_000_000,
|
||||
scale="B",
|
||||
scale_style="word",
|
||||
scale="billion",
|
||||
precision=0,
|
||||
commas=False,
|
||||
label="parameter",
|
||||
@@ -510,6 +532,27 @@ class TestFmtCount:
|
||||
== "70 billion parameters"
|
||||
)
|
||||
|
||||
def test_scale_word_attributive_modifier(self):
|
||||
assert (
|
||||
fmt_count(
|
||||
7_000_000_000,
|
||||
scale="billion",
|
||||
precision=0,
|
||||
commas=False,
|
||||
attributive=True,
|
||||
)
|
||||
== "7-billion"
|
||||
)
|
||||
with pytest.raises(ValueError, match="requires scale"):
|
||||
fmt_count(7, attributive=True)
|
||||
with pytest.raises(ValueError, match="only the scaled modifier"):
|
||||
fmt_count(
|
||||
7_000_000_000,
|
||||
scale="billion",
|
||||
label="parameter",
|
||||
attributive=True,
|
||||
)
|
||||
|
||||
def test_scale_inherits_precision_guard(self):
|
||||
# 5.3M at precision=0 would silently hide the .3 — guard refuses.
|
||||
with pytest.raises(ValueError, match="not integer-like"):
|
||||
@@ -614,6 +657,16 @@ class TestTypedRanges:
|
||||
)
|
||||
== "1M\u20132M parameters"
|
||||
)
|
||||
assert (
|
||||
fmt_count_range(
|
||||
1_000_000,
|
||||
2_000_000,
|
||||
scale="million",
|
||||
label="parameter",
|
||||
commas=False,
|
||||
)
|
||||
== "1 million\u20132 million parameters"
|
||||
)
|
||||
|
||||
def test_usd_range_supports_scale_and_denominator(self):
|
||||
assert (
|
||||
|
||||
Reference in New Issue
Block a user