I'll start with the reason behind this discrepancy.
TLDR; as of today there is no option to force BigQuery to materialize CTE results. It'd be useful when a CTE referenced more than once in a statement.
see below query:
with cte_1 as (
select count(1) as row_count
from `bigquery-public-data.austin_311.311_service_requests` as sr
)
, cte_2 as (
select row_count
from cte_1
)
select row_count
from cte_1
union all
select row_count
from cte_2;
when the execution plan is examined, you'll see 2 Input
stage for referenced sr
table.

It'd be great if we have an option to materialize CTE results. As I remember, oracle has this implicitly if CTE used more than once or explicitly via hints.
materializing q1
explicitly to table then use it twice might be a workaround. I'd prefer temporary table.
the drawback is; the cost may increase if your project uses on-demand pricing. (rather than flat-rate)